JavaFXObservableList与提取器根据侦听器的存在改变行为

pkln4tw6  于 2023-09-29  发布在  Java
关注(0)|答案(1)|浏览(92)

下面的问题是我的JavaFX应用程序中的一个bug的来源。奇怪的是,行为取决于特定的JavaFX属性是否附加了侦听器。当ChangeListener观察到属性时,一切都正常,否则不正常。快把我逼疯了。。
我设法把它分解成一个最小的代码示例。首先,我们需要一个类来公开一个不时更改的属性。在这里它被称为nameProperty()。在这个例子中,我选择生成一个单独的线程,它会不断地修改属性,但在真实的的应用程序中,这是通过用户交互实现的。

class TestClass {
    private final SimpleObjectProperty<String> name = new SimpleObjectProperty<>();

    public TestClass() {
        new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(1000);
                    Platform.runLater(() -> name.set("A"));
                    Thread.sleep(1000);
                    Platform.runLater(() -> name.set("B"));
                }
                catch(InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

    public ReadOnlyObjectProperty<String> nameProperty() {
        return name;
    }
}

在main方法中,在FX应用程序线程上,使用提取器创建ObservableList<TestClass>,以便ListChangeListener报告对nameProperty()列表元素的更改。然后,我们创建测试类的一个示例,将其添加到列表中,并添加一个ListChangeListener来观察列表的更新。

public static void main(String[] args) throws Exception {
    Platform.startup(() -> {
        Callback<TestClass, Observable[]> extractor = obj -> new Observable[]{ obj.nameProperty() };
        ObservableList<TestClass> list = FXCollections.observableArrayList(extractor);

        TestClass test = new TestClass();
        list.add(test);

        list.addListener((ListChangeListener<TestClass>) c -> {
            while(c.next()) {
                if(c.wasUpdated()) {
                    System.out.println("List element was updated");
                }
            }
        });
    });

    Thread.sleep(60*60*1000);
}

由于列表提取器和属性不断被修改,我对输出的期望是它看起来像这样:

List element was updated
List element was updated
List element was updated
List element was updated
List element was updated
...

但它看起来像这样:

List element was updated
*silence*

现在奇怪的是,只要在代码中的任何地方将ChangeListener添加到nameProperty(),例如

test.nameProperty().addListener(((observable, oldValue, newValue) -> {}));

它看起来像预期的那样工作,并且列表不断地产生更改通知。
仅仅观察一个属性不应该改变绑定到该属性的其他事物的行为,对吗?但如果这是JavaFX中的一个bug,在我看来,这将是一个非常明显和基本的bug。所以也许我确实搞砸了一些事情,尽管程序看起来很简单。顺便说一下,我在Windows 10上使用OpenJFX版本21。

wbgh16ku

wbgh16ku1#

注意,提取器返回一个Observable数组,如果我们看documentation
此类的实现应努力生成尽可能少的事件,以避免在事件处理程序中浪费太多时间。当第一个失效事件发生时,这个库中的实现将自己标记为无效。它们不再生成失效事件,直到它们的值被重新计算并再次有效。
name属性以有效状态开始。您设置了该属性,它就失效了,触发了一个失效事件,最终列表更改侦听器会收到更新的通知。但是您永远不会查询name属性,这意味着它永远不会被验证。因此,它保持在无效状态,并且不触发任何更多的无效事件,尽管它被反复设置(为不同的值)。如果您希望继续收到失效事件的通知,则需要验证可观察对象。
下面是一个例子,展示了验证Observable和不验证Observable之间的区别:

import javafx.beans.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

public class Main {

    public static void main(String[] args) {
        doTestWithoutValidation();
        doTestWithValidation();
    }

    static void doTestWithoutValidation() {
        StringProperty element = new SimpleStringProperty();

        ObservableList<Observable> list = FXCollections.observableArrayList(e -> new Observable[]{e});
        list.add(element);

        list.addListener((ListChangeListener.Change<? extends Observable> change) -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    System.out.println("List element updated");
                }
            }
        });

        System.out.println("Doing test WITHOUT validations...");

        element.set("Foo");
        element.set("Bar");
        element.set("Baz");

        System.out.println("DONE!");
        System.out.println();
    }

    static void doTestWithValidation() {
        StringProperty element = new SimpleStringProperty();

        ObservableList<Observable> list = FXCollections.observableArrayList(e -> new Observable[]{e});
        list.add(element);

        list.addListener((ListChangeListener.Change<? extends Observable> change) -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    System.out.println("List element updated");
                }
            }
        });

        System.out.println("Doing test WITH validations...");

        element.set("Foo");
        element.get(); // validate
        element.set("Bar");
        element.get(); // validate
        element.set("Baz");

        System.out.println("DONE!");
        System.out.println();
    }
}

输出量:

Doing test WITHOUT validations...
List element updated
DONE!

Doing test WITH validations...  
List element updated
List element updated
List element updated
DONE!

您还必须小心垃圾收集。在您的代码中,在更新它以解决存在的并发问题之后,我相信在Runnable(传递给startup)返回之后,ObservableList本身可能适合进行垃圾收集。

相关问题