JavaFx Combobox延迟加载图像

时间:2014-09-07 14:12:06

标签: java combobox javafx

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

我打算使用ExecutorService从zip文件中获取图像。

现在,问题是如何找出Combobox中当前可见的项目?

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

感谢。

3 个答案:

答案 0 :(得分:5)

首先请注意,如果您从单个文件而不是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()标志,并在返回true时尽快中止。这可能很难实现:我将首先尝试使用简单的实现来试验它是如何工作的。

答案 1 :(得分:1)

即使您的列表有2000个项目,javafx也只会为可见单元格创建listcell对象(加上一半或两个半可见单元格),所以对于你来说加载图像懒惰并不是很多TODO - 只需在updateItem中加载它们被调用 - 并且可能缓存已经加载的图像在lifo Cache中,以便它们不会全部保留在内存中

答案 2 :(得分:0)

当前可见项表示组合框中当前选定的项目。您可以使用

获取所选项目
comboboxname.getSelectionModel().getSelectedItem();