【问题标题】:JavaFX custom list cell, updateItem being called a lotJavaFX自定义列表单元格,updateItem被调用了很多
【发布时间】:2014-10-04 11:18:57
【问题描述】:

我在 JavaFX 应用程序中使用 ListView。列表中的项目不仅需要一个字符串来显示它们,因此我对ListCell<T> 进行了自定义实现,其中T 是我正在显示的对象的类。在这个自定义类中,我在构造函数中创建了一个BorderPane 并覆盖了updateItem(T item, boolean empty)。这个想法是,当调用updateItem 时,我将单元格的图形设置为BorderPane,同时更改其中Labels 的一些属性。但是,我注意到updateItem 不会被调用一次(例如,当通过滚动回收单元格或添加新项目时),而是一直调用,例如当焦点从一个单元格变为另一个单元格时(即使没有滚动),或者调整场景大小,或者按下边框内的按钮时,...

我需要一个带有自定义ListCells 的ListView。我想在重用单元格时接收 one 回调,将要呈现的新项目作为参数传递。然后我想用那个项目作为模型来构造一个视图控制器对,我把它的视图用作单元格的graphic。此视图中的按钮和其他控件应该可以工作。这在 JavaFX 中可行吗?

相关问题:

【问题讨论】:

    标签: listview javafx


    【解决方案1】:

    基本思想是很少构造单元格,但updateItem(...) 方法可能会被频繁调用。无法实际保证在对updateItem(...) 的调用之间该项目确实发生了变化。 updateItem(...) 的默认实现负责处理选择、焦点等,因此如果这些属性中的任何一个发生更改(即使项目没有更改),则可能需要调用此方法。

    因此,您应该努力减少updateItem(...) 方法的开销。目前尚不清楚多次频繁调用是否会阻止您做您想做的事情(例如,当您将新项目作为参数传递给模型时,在进行任何更新之前检查它是否真的与您已有的不同) .

    我认为updateItem(...) 的命名确实有点错误:不仅在更新item 时调用它,而且实际上在任何时候可能需要更新单元格时都会调用它。已经有一种机制可以仅在item 更改时执行代码:只需向单元格的itemProperty() 注册一个侦听器。您可以使用这种技术(我通常更喜欢)创建不同风格的ListCell

    ListView<...> listView = ... ;
    listView.setCellFactory(lv -> {
        BorderPane cellRoot = new BorderPane();
        // create nodes, register listeners on them, populate cellRoot, etc...
        ListCell<...> cell = new ListCell<>();
        cell.itemProperty().addListener((obs, oldItem, newItem) -> {
            if (newItem != null) {
                // update cellRoot (or its child nodes' properties) accordingly
            }
        });
        cell.emptyProperty().addListener((obs, wasEmpty, isEmpty) -> {
            if (isEmpty) {
                cell.setGraphic(null);
            } else {
                cell.setGraphic(cellRoot);
            }
        });
        cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        return cell ;
    });
    

    这种方法可能更适合您的场景。

    【讨论】:

    • 我通过使用 ScrollPane 和自定义 BorderPanes 来模拟列表单元格解决了我当前的问题。我想你的解决方案可以工作,当我有更多时间时我会测试这个。但是,我认为 updateItem 根据 API 的工作方式真的很糟糕。为什么每次项目更新都不会调用一个函数? :(
    • 我的解决方案确实有效,并准确演示了如何编写仅在项目更改时调用的回调。 API 的目的是允许高效的ListViews 可能包含数百万个项目(如果需要的话)。为了做到这一点,需要有一个回调,每当单元格的表示可能发生变化时调用,而不是在项目发生变化时调用。 updateItem(...) 就是那个方法。
    • @Warkst 你不是唯一一个不喜欢updateItem 工作方式的人。我创建了Flowless 来解决updateItem 调用过多的问题。您可以使用它来代替您的解决方法。 Flowless 不会强迫您重复使用单元格,IMO 会导致使用更简单。在仅创建可见单元并将其附加到场景图的意义上,该实现仍然有效。
    • @Warkst 对“但是,我认为 updateItem 根据 API 的工作方式真的很糟糕”的声明表示敬意。应该将机制更改为相当明智的机制。
    • @James_D 我正在尝试这个解决方案,我看到itemProperty 的回调被连续多次调用,并且总是将oldItem 设为null,newItem 使用相同的项目.触发监听的是super.updateItem中的setItem被调用
    【解决方案2】:

    我刚开始使用 JavaFX,我无法解释它为什么起作用的所有小细节(它在新项目和现有项目上调用该方法),但我解决问题的方法是简单地调用myContainer.getChildren().removeAll( ... )

    在我的 ListCell public class MyCell extends ListCell&lt;MyContent&gt; 实现中,我创建了许多不同的项目并将它们添加到一个容器中,这成为我列表视图中的一行。所以,我只需要在重新添加之前从该容器中删除相同的项目。这是我所做的简化版本。似乎工作得很好。我确实尝试了 James_D 的建议,但它对我不起作用并且产生了同样的错误。

    @Override
        public void updateItem(MyContent item, boolean empty) {
    
            super.updateItem(item, empty);
    
            if (item == null || empty == true) {
                setGraphic(null);
                setText(null);
            } else {    
    
                // here I user the "item" to decide how to create 
                // objects a, b, c ... which are Rectangle, Label and Label
                // I also update myMainContainer, which is a Pane
    
                myMainContainer.getChildren().removeAll(a, b, c);
                myMainContainer.getChildren().addAll(a, b, c);
                setGraphic(myMainContainer);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-13
      • 1970-01-01
      • 2015-02-22
      • 1970-01-01
      • 1970-01-01
      • 2013-10-15
      • 1970-01-01
      • 2017-06-25
      相关资源
      最近更新 更多