Zephyr
Zephyr

Reputation: 10253

How to style a TableCell based on properties of the row's item?

My application has a TableView that is populated with a list of references to image files.

The data is loaded from a database and just provides information on how to locate the image file itself (so it indicates a subfolder and filename for the image).

In my TableView, I want to style the text of my "Filename" column red if the physical file does not exist. I have implemented the CellFactory and it "kind of" works..sometimes. The updateItem() method is overridden to check for the existance of the file in question, but it is not always correct: some rows will be styled with red text, others will not, even though they point to the same file.

Also, while scrolling the list, the values can change sporadically.

Very hard to describe, so I created an MCVE below.

Main.java

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.File;

public class Main extends Application {

    private ObservableList<DataItem> dataItems;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        initData();

        // Main interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        // Setup the TableView
        TableView<DataItem> tableView = new TableView<>();
        TableColumn<DataItem, ImageCategory> colCategory = new TableColumn<>("Category");
        TableColumn<DataItem, String> colFilename = new TableColumn<>("Filename");

        // Initialize the column data
        colCategory.setCellValueFactory(cellData -> cellData.getValue().categoryProperty());
        colFilename.setCellValueFactory(cellData -> cellData.getValue().filenameProperty());
        tableView.getColumns().add(colCategory);
        tableView.getColumns().add(colFilename);

        // Style text based on file exists
        colFilename.setCellFactory(filenameCell -> new TableCell<DataItem, String>() {
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);

                if (item == null || empty) {
                    setText(null);
                    setStyle("");
                } else {

                    // Check if file exists
                    DataItem thisItem = getTableView().getItems().get(getIndex());
                    File imageFile = new File("C:\\Users\\XXX\\Desktop\\"
                            + thisItem.getCategory().getCategoryName() + "\\"
                            + item);
                    if (!imageFile.exists()) {
                        setStyle("-fx-text-fill: red");
                    }
                    setText(item);
                }

            }
        });

        tableView.setItems(dataItems);
        root.getChildren().add(tableView);

        primaryStage.setScene(new Scene(root));
        primaryStage.setWidth(300);
        primaryStage.setHeight(200);
        primaryStage.show();
    }

    private void initData() {

        dataItems = FXCollections.observableArrayList();

        for (int i = 0; i < 15; i++) {
            dataItems.add(new DataItem(
                    new ImageCategory(1, "Application Icon"),
                    "icon.png"));
        }
          for (int i = 0; i < 15; i++) {
            dataItems.add(new DataItem(
                    new ImageCategory(1, "Logo"),
                    "logo.png"));
        }
    }
}

DataItem.java

import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;

class DataItem {

    private SimpleObjectProperty<ImageCategory> category = new SimpleObjectProperty<>();
    private SimpleStringProperty filename = new SimpleStringProperty();

    public DataItem(ImageCategory category, String filename) {
        this.category.set(category);
        this.filename.set(filename);
    }

    public ImageCategory getCategory() {
        return category.get();
    }

    public SimpleObjectProperty<ImageCategory> categoryProperty() {
        return category;
    }

    public String getFilename() {
        return filename.get();
    }

    public SimpleStringProperty filenameProperty() {
        return filename;
    }
}

ImageCategory.java

import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;

class ImageCategory {
    private SimpleIntegerProperty categoryId = new SimpleIntegerProperty();
    private SimpleStringProperty categoryName = new SimpleStringProperty();

    public ImageCategory(int categoryId, String categoryName) {
        this.categoryId.set(categoryId);
        this.categoryName.set(categoryName);
    }

    public int getCategoryId() {
        return categoryId.get();
    }

    public SimpleIntegerProperty categoryIdProperty() {
        return categoryId;
    }

    public String getCategoryName() {
        return categoryName.get();
    }

    public SimpleStringProperty categoryNameProperty() {
        return categoryName;
    }

    @Override
    public String toString() {
        return getCategoryName();
    }
}

Obviously, you would need a sample file to test this. Here is a screenshot of the results from running this. Note that Logo/logo.png does exist:

Screenshot of display issue

You see that the first "logo.png" is styled correctly, as plain text, but subsequent entries seem to indicate that the file does not exist. Resizing the window or scrolling the list can also sometimes cause the styled text to change from one row to another; this is fairly random.

How can I accurately test if a file represented by the row actually exists?

Side Question: in my production application, the scrolling is very slow and delayed as well, as checking for the existence of the file is slow over a network shared drive. Is there a way to style these items as their first loaded instead of each time the cell is rendered?

Upvotes: 0

Views: 35

Answers (1)

fabian
fabian

Reputation: 82461

Regarding the improper style of teh cells:

You don't change the style, if the item is changed to a existing one. This means that your cell text remains red, if you change from a non-existing file to an existing one. You need to handle this case properly:

@Override
protected void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);

    if (item == null || empty) {
        setText(null);
        setStyle("");
    } else {

        // Check if file exists
        DataItem thisItem = getTableView().getItems().get(getIndex());
        File imageFile = new File("C:\\Users\\XXX\\Desktop\\"
                + thisItem.getCategory().getCategoryName() + "\\"
                + item);
        if (!imageFile.exists()) {
            setStyle("-fx-text-fill: red");
        } else {
            // modification here ----------------------------------------------
            setStyle("");
        }
        setText(item);
    }

}

As for the scrolling issues:

updateItem is run on the JavaFX application thread. Doing long-running operations such as communication with a remote directory on this thread makes the UI unresponsive. You can easily avoid doing the check multiple times by storing the info either in the item class iself or adding some structure containing the data, e.g. a Map.

Communication with the remote directory should still be done on a seperate thread. You could do the assinment of the file state (EXISTENT, NON_EXISTENT/UNKNOWN) on a Task scheduled on a ExecutorService...

Upvotes: 1

Related Questions