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));
}
}