首页 > 解决方案 > 如何根据行项目的属性设置 TableCell 样式?

问题描述

我的应用程序有一个TableView填充了对图像文件的引用列表。

数据从数据库加载,仅提供有关如何定位图像文件本身的信息(因此它指示图像的子文件夹和文件名)。

在我TableView的 中,如果物理文件不存在,我想将“文件名”列的文本设置为红色。我已经实现了CellFactory它,它“有点”工作......有时。该updateItem()方法被覆盖以检查相关文件是否存在,但这并不总是正确的:一些行将使用红色文本设置样式,而其他行则不会,即使它们指向同一个文件。

此外,在滚动列表时,值可能会偶尔更改。

很难描述,所以我在下面创建了一个 MCVE。

主.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"));
        }
    }
}

数据项.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();
    }
}

显然,您需要一个示例文件来测试它。这是运行此结果的屏幕截图。请注意,Logo/logo.png 确实存在:

显示问题截图

您会看到第一个“logo.png”的样式正确,为纯文本,但随后的条目似乎表明该文件不存在。调整窗口大小或滚动列表有时也会导致样式文本从一行更改为另一行;这是相当随机的。

如何准确测试该行表示的文件是否实际存在?

附带问题:在我的生产应用程序中,滚动非常缓慢且延迟,因为通过网络共享驱动器检查文件是否存在很慢。有没有办法将这些项目设置为第一次加载而不是每次渲染单元格时?

标签: javajavafxtableview

解决方案


关于不正确的单元格样式:

如果项目更改为现有项目,则无需更改样式。这意味着如果您从不存在的文件更改为现有文件,您的单元格文本将保持红色。您需要正确处理这种情况:

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

}

至于滚动问题:

updateItem在 JavaFX 应用程序线程上运行。在此线程上执行长时间运行的操作(例如与远程目录通信)会使 UI 无响应。您可以通过将信息存储在项目类本身中或添加一些包含数据的结构(例如Map.

与远程目录的通信仍应在单独的线程上完成。您可以按计划在EXISTENT...NON_EXISTENTUNKNOWNTaskExecutorService


推荐阅读