【问题标题】:JavaFX TableView: Rapid change in items list not reflectedJavaFX TableView:未反映项目列表中的快速变化
【发布时间】:2017-08-20 23:10:32
【问题描述】:

很难解释,所以我举个例子:

@Override
public void start(Stage primaryStage) throws Exception
{
    final VBox vbox = new VBox();
    final Scene sc = new Scene(vbox);
    primaryStage.setScene(sc);

    final TableView<Person> table = new TableView<>();
    final TableColumn<Person, String> columnName = new TableColumn<Person, String>("Name");
    table.getColumns().add(columnName);

    final ObservableList<Person> list = FXCollections.observableArrayList();
    list.add(new Person("Hello"));
    list.add(new Person("World"));
    Bindings.bindContent(table.getItems(), list);

    columnName.setCellValueFactory(new PropertyValueFactory<>("name"));

    vbox.getChildren().add(table);

    final Button button = new Button("test");
    button.setOnAction(event ->
    {
        final Person removed = list.remove(0);
        removed.setName("Bye");
        list.add(0, removed);
    });
    vbox.getChildren().add(button);


    primaryStage.show();
}

public static class Person
{
    private String name = "";

    public Person(String n)
    {
        name = n;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String n)
    {
        name = n;
    }
}

在这个例子中,我展示了一个TableView,它有一个名为“Name”的列。运行此示例代码,您将获得两行:第一行在“名称”列中带有“Hello”;第二行是“名称”列中的“世界”。

此外,还有一个按钮,该按钮从列表中删除第一个Person 对象,然后对该对象进行一些更改,然后将其重新添加到相同的索引处。这样做会导致添加到ObservableList 的任何ListChangeListener 被触发,我已经测试这是真的。

我希望将带有“Hello”的行替换为“Bye”,但似乎TableView 继续显示“Hello”。如果我在将删除的Person 对象添加回列表之前使用TimeLine 添加延迟,它将变为“Bye”。

final Timeline tl = new Timeline(new KeyFrame(Duration.millis(30), ae -> list.add(0, removed)));
tl.play();

API 有什么奇怪的地方吗?有什么办法可以解决这个问题吗?

【问题讨论】:

    标签: java javafx javafx-8 javafx-tableview


    【解决方案1】:

    这基本上是预期的行为。

    请注意(我猜您正在尝试解决此问题),如果您只是调用

    list.get(0).setName("Bye");
    

    在基础数据方面具有相同的效果,表格不会更新,因为它无法通知列表元素中的 String 字段 name 已更改。

    代码

    Person removed = list.remove(0);
    removed.setName("Bye");
    list.add(0, removed);
    

    真的等同于list.get(0).setName("Bye");:你只是在更改之前暂时从列表中删除项目,然后再将其添加回来。就列表而言,最终结果是相同的。我猜您这样做是希望从列表中删除和替换该项目将说服表注意到该项目的状态已更改。不能保证会出现这种情况。这是正在发生的事情:

    两个列表之间的绑定:

    Bindings.bindContent(table.getItems(), list);
    

    与任何其他绑定一样工作:它定义了如何获取绑定的值(list 的元素),并在 list 随时失效时将数据标记为无效。后者在您从 list 添加和删除元素时发生。

    TableView 不会在每次绑定到列表时执行布局;相反,当绑定无效(添加或删除元素)时,表格视图会将自身标记为可能需要重绘。然后,在下一个渲染脉冲上,表格将检查数据,看看是否真的需要重新绘制,如果需要重新渲染。此实现有明显的性能节省特性。

    因此,您的代码会从列表中删除一个项目,导致绑定被标记为无效。然后更改项目(通过调用setName(...)),然后将相同的项目添加回列表中的相同位置。这也会导致绑定被标记为无效,这没有任何效果(它已经无效)。

    在移除和重新添加此元素之间不会出现渲染脉冲。因此,表第一次真正查看对列表所做的更改必须在整个删除-更改-添加过程之后。此时,表格将看到列表仍然包含完全相同的元素,其顺序完全相同。 (其中一个元素的 内部状态 已更改,但由于这不是可观察值 - 不是 JavaFX 属性 - 表不知道这一点。)因此,表看不到任何更改(或看到所有更改都相互抵消了),并且不会重新渲染。

    在添加暂停的情况下,在删除项目和重新添加项目之间会出现一个(或两个)渲染帧。因此,表格实际上会渲染一两帧而没有项目,当它被重新添加时,它会将其添加回来并呈现当前值。 (您可能可以通过暂停 16 或 17 毫秒来使行为不可预测,这恰好是一个渲染帧的时间点。)

    目前尚不清楚您真正打算做什么。如果您试图说服表在不使用 JavaFX 属性的情况下进行更新,您可以这样做

    list.get(0).setName("Bye");
    table.refresh();
    

    虽然这不是一个非常令人满意的解决方案。

    请注意

    list.remove(0);
    list.add(0, new Person("Bye"));
    

    也可以使用(因为现在添加的元素与移除的元素不同)。

    更好的方法是使用 JavaFX 属性实现模型类:

    public static class Person
    {
        private final StringProperty name = new SimpleStringProperty("");
    
        public Person(String n)
        {
            setName(n);
        }
    
        public StringProperty nameProperty() {
            return name ;
        }
    
        public final String getName()
        {
            return nameProperty().get();
        }
    
        public final void setName(String n)
        {
            nameProperty().set(n);
        }
    }
    

    然后简单地调用

    list.get(0).setName("Bye");
    

    将更新表格(因为单元格将观察属性)。

    【讨论】:

    • 你是绝对正确的,我试图在不使用 FX 属性的情况下强制更新表,因为我的模型类来自服务器端 API 作为 DTO 类,我简单地重写很奇怪这些课程从头开始。你的回答真的很好解释。不过我还有一个问题:ObservableList 是否会自动订阅其元素的 FX 属性中的更改?如果是这样,this overload 是干什么用的?
    • @Jai,不,可观察列表不会自动侦听属于列表元素的属性(除非您提供提供这些属性的“提取器”)。但在这种情况下,如果您的模型类使用 JavaFX 属性,PropertyValueFactory 会将属于模型的属性提供给单元,并且单元会观察该属性的变化。
    • @Jai 请注意,根据服务器应用程序的性质,您可能仍希望使用 JavaFX 属性。请参阅thisthis。您还可以考虑this pattern,它使用适配器将传统的 JavaBean 属性公开为可观察的 JavaFX 属性。
    • 感谢您澄清并提供所有这些资料。对我来说不幸的是,我不是服务器团队的一员。我怀疑服务器人员会想要导入 JavaFX API,然后更改所有 DTO 类以使用 JavaFX 属性。此外,它们的 DTO 类是纯 POJO bean 类,因此没有任何 PropertyChangeListeners。再一次,我怀疑他们想改变它。所以我很坚持重写课程。 :(
    • @Jai PropertyChangeListeners 是 (POJO) JavaBean 规范的一部分,因此您可能有机会获得这些请求。或者,如您所说,为您的客户端代码创建一个特定于 JavaFX 的模型。
    猜你喜欢
    • 1970-01-01
    • 2013-01-15
    • 1970-01-01
    • 1970-01-01
    • 2012-08-18
    • 2013-09-21
    • 1970-01-01
    • 1970-01-01
    • 2017-07-22
    相关资源
    最近更新 更多