Reputation: 13
Im trying to populata a tableview with objects thats have ImageView as one of their values, but only the last item in the tableview is displaying the image, none of the rest do
public class MainController implements Initializable {
public TableView<Apple> table;
public TableColumn nameColumn;
public TableColumn imageColumn;
ImageView imageView = new ImageView(new Image("download.jpg"));
Apple apple = new Apple("Bob",imageView);
Apple apple2 = new Apple("John",imageView);
Apple[] apples = {apple2,apple};
List<Apple> appleList = Arrays.asList(apples);
@Override
public void initialize(URL location, ResourceBundle resources) {
imageColumn.setCellValueFactory(new PropertyValueFactory<>("image"));
nameColumn.setCellValueFactory((new PropertyValueFactory<>("name")));
table.setItems(FXCollections.observableArrayList(appleList));
}
}
public class Apple {
private final int SIZE = 20;
String name;
ImageView image;
public Apple(String name, ImageView image) {
this.name = name;
this.image = image;
image.setFitHeight(SIZE);
image.setFitWidth(SIZE);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ImageView getImage() {
return image;
}
public void setImage(ImageView image) {
this.image = image;
}
}
Im not sure why this is happening, i've seen people online setting the images 1 by 1 using loops though.
Upvotes: 1
Views: 217
Reputation: 209225
As stated in the comments by @jewelsea, your model class (Apple
) should contain only data; it should not contain UI fields such as ImageView
. Any Node
can only appear once in the scene graph, which is why you only see the ImageView
in a single row in the table.
You should instead store either the path to the image, or the Image
(which is only data) itself in the model class. The trade-off between these two choices is a compute-time versus memory consumption trade-off. If you store the path to the image, the image will need to be loaded each time a cell updates (e.g. during scrolling), which takes time. On the other hand, if you store the Image
, then all images needed for the entire table will need to be stored in memory, whether or not they are displayed.
I would recommend storing the Image
in the model class if your table only needs a small number of images. This may happen if the table only has a few rows, or if there are a small number of images and multiple rows show the same image. Note that Image
s can be shared by multiple ImageView
s, so there is no need to load any single Image
more than once.
Using Image
in the model class would look like this:
public class Apple {
private String name;
private Image image;
public Apple(String name, Image image) {
this.name = name;
this.image = image;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
}
You will need a cell implementation to display the image:
public class ImageCell extends TableCell<Apple, Image> {
private static final IMAGE_SIZE = 20 ;
private final ImageView imageView ;
public TableCell() {
imageView = new ImageView();
imageView.setFitWidth(IMAGE_SIZE);
imageView.setFitHeight(IMAGE_SIZE);
imageView.setPreserveRatio(true);
}
@Override
protected void updateItem(Image image, boolean empty) {
if (empty || item == null) {
setGraphic(null);
} else {
imageView.setImage(image);
setGraphic(imageView);
}
}
}
And your application code looks like:
public class MainController implements Initializable {
public TableView<Apple> table;
public TableColumn<Apple, String> nameColumn;
public TableColumn<Apple, Image> imageColumn;
Image downloadImage = new Image("download.jpg");
Apple apple = new Apple("Bob",downloadImage);
Apple apple2 = new Apple("John",downloadImage);
@Override
public void initialize(URL location, ResourceBundle resources) {
imageColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue().getImage()));
imageColumn.setCellFactory(column -> new ImageCell());
nameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName()));
table.setItems(FXCollections.observableArrayList(apple2, apple));
}
}
If you have a large number of distinct images in the table, which is probably unlikely, you should represent the path to the image in the model class instead:
public class Apple {
private String name;
private String imagePath;
public Apple(String name, String imagePath) {
this.name = name;
this.imagePath = imagePath;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImagePath() {
return imagePath;
}
public void setImage(String imagePath) {
this.imagePath = imagePath;
}
}
Then your cell class needs to load the image:
public class ImageCell extends TableCell<Apple, String> {
private static final IMAGE_SIZE = 20 ;
private final ImageView imageView ;
public TableCell() {
imageView = new ImageView();
imageView.setFitWidth(IMAGE_SIZE);
imageView.setFitHeight(IMAGE_SIZE);
imageView.setPreserveRatio(true);
}
@Override
protected void updateItem(String imagePath, boolean empty) {
if (empty || item == null) {
setGraphic(null);
} else {
imageView.setImage(new Image(imagePath));
setGraphic(imageView);
}
}
}
and your application code has the obvious modifications:
public class MainController implements Initializable {
public TableView<Apple> table;
public TableColumn<Apple, String> nameColumn;
public TableColumn<Apple, String> imageColumn;
Apple apple = new Apple("Bob", "download.jpg");
Apple apple2 = new Apple("John", "download.jpg");
@Override
public void initialize(URL location, ResourceBundle resources) {
imageColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getImagePath()));
imageColumn.setCellFactory(column -> new ImageCell());
nameColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getName()));
table.setItems(FXCollections.observableArrayList(apple2, apple));
}
}
Upvotes: 2