JavaFx Combobox 延迟加载图像

标签 java combobox javafx

我是第一次使用 JavaFX Combobox...并且组合框中有超过 2000 个图标(可以通过我从 StackOverflow 找到的 AutoCompleteComboBoxListener 来过滤它们)。

我计划使用 ExecutorService 从 zip 文件中获取图像。

现在的问题是如何找出组合框中当前可见的项目?

我正在为 ComboBox 设置自定义 ListCellFactory,并且我有一个自定义 ListCell 类,它也显示图标。我可以以某种方式从 ListCell 对象中检查该项目是否“正在显示”吗?

谢谢。

最佳答案

首先请注意,如果您从单个文件而不是从 zip 文件加载图像,则有一种机制可以避免直接使用任何类型的线程:

ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> new ListCell<MyDataType>() {

    private ImageView imageView = new ImageView();

    @Override
    public void updateItem(MyDataType item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setGraphic(null);
        } else {
            String imageURL = item.getImageURL();
            Image image = new Image(imageURL, true); // true means load in background
            imageView.setImage(image);
            setGraphic(imageView);
        }
    }
});

不幸的是,如果您从 zip 文件加载,我认为您无法使用它,因此您需要创建自己的后台任务。您需要稍微小心,以确保如果单元格中的项目在加载过程中发生变化(如果用户滚动很多,则很可能发生这种情况),您不会使用在后台加载的图像。

(更新说明:我更改了此设置以监听单元格的 itemProperty() 中的更改,而不是使用 updateItem(...) 方法。 updateItem(...) 方法可以比单元格显示的实际项目发生更改时更频繁地调用,因此这种方法避免了不必要的“重新加载”图像。)

类似于:

ExecutorService exec = ... ;
ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> {

    ListCell<MyDataType> cell = new ListCell<MyDataType>() ;

    ImageView imageView = new ImageView();
    ObjectProperty<Task<Image>> loadingTask = new SimpleObjectProperty<>();

    cell.emptyProperty().addListener((obs, wasEmpty, isNotEmpty) -> {
        if (isNowEmpty) {
            cell.setGraphic(null);
            cell.setText(null);
        } else {
            cell.setGraphic(imageView);
        }
    });

    cell.itemProperty().addListener((obs, oldItem, newItem) ->  {
        if (loadingTask.get() != null && 
            loadingTask.get().getState() != Worker.State.SUCCEEDED &&
            loadingTask.get().getState() != Worker.State.FAILED) {

            loadingTask.get().cancel();
        }
        loadingTask.set(null) ;
        if (newItem != null) {
            Task<Image> task = new Task<Image>() {
                @Override
                public Image call() throws Exception {
                    Image image = ... ; // retrieve image for item
                    return image ;
                }
            };
            loadingTask.set(task);
            task.setOnSucceeded(event -> imageView.setImage(task.getValue()));
            exec.submit(task);

            cell.setText(...); // some text from item...
        }
    }); 

    return cell ;
});

关于性能的一些想法:

首先,ComboBox 的“虚拟化”机制意味着只会创建少量的这些单元格,因此您无需担心会立即启动数千个单元格。线程加载图像,或者实际上您将在内存中拥有数千张图像。

当用户滚动列表时,itemProperty(...) 可能会频繁更改,因为单元格会被重新用于新项目。重要的是要确保您不使用已启动但在项目再次更改之前未完成的线程中的图像;这就是在项目更改监听器开始时取消现有任务的目的。取消任务将阻止调用 onSucceeded 处理程序。但是,您仍然会运行这些线程,因此如果可能的话,您的 call() 方法的实现应该检查 isCancelled() 标志,并在出现异常时尽快中止返回真。这可能很难实现:我会先进行实验,看看它如何通过简单的实现来工作。

关于JavaFx Combobox 延迟加载图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25711103/

相关文章:

javascript - jQuery - 允许插入文本的组合框

eclipse - 需要Java FX Eclipse教程

java - 以点文件的形式生成AST

java - 带按钮的 Swing JPanel 无法重绘

java - 持久订阅 ActiveMQ

java - 如何将 "case"放入循环中

c# - 使用 MVVM 取消 WPF 中的组合框选择

java - 从独立客户端向 JBoss 7.1 中的 JMS 主题发送消息,未找到 ConnectionFactory

Java 主类找不到或加载错误

Java 没有找到合适的 addListener 方法