具有提取器的 JavaFX ObservableList 根据监听器的存在更改行为

标签 java javafx

以下问题是我的 JavaFX 应用程序中错误的根源。奇怪的是,该行为取决于特定的 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>使用提取器创建,以便更改为 nameProperty()列表元素由 ListChangeListener 报告。然后我们创建测试类的一个实例,将其添加到列表中并添加 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) -> {}));

它似乎按预期工作,并且列表不断生成更改通知。

仅仅观察一个属性不应该改变与该属性绑定(bind)的其他事物的行为,对吧?但如果这是 JavaFX 中的一个错误,那么在我看来,这将是一个非常明显且根本的错误。所以也许我确实搞砸了一些事情,尽管程序看起来很简单。顺便说一句,我在 Windows 10 上使用 OpenJFX 版本 21。

最佳答案

请注意,提取器返回一个 Observable 数组,如果我们查看 documentation :

Implementations of this class should strive to generate as few events as possible to avoid wasting too much time in event handlers. Implementations in this library mark themselves as invalid when the first invalidation event occurs. They do not generate anymore invalidation events until their value is recomputed and valid again.

name 属性一开始处于有效状态。您设置该属性,它就会失效,触发失效事件,最终列表更改监听器会收到更新通知。但是您永远不会查询 name 属性,这意味着它永远不会被验证。因此,尽管它被一遍又一遍地设置(设置为不同的值),但它仍然处于无效状态,并且不会触发任何更多的无效事件。如果您想继续收到失效事件的通知,您需要验证可观察量。

下面的示例显示了验证可观察值与不验证可观察值之间的区别:

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 (传递给 >启动)返回。

关于具有提取器的 JavaFX ObservableList 根据监听器的存在更改行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77180781/

相关文章:

java - 如何从JavaFX中的主阶段调用第二个阶段

java - 创建 JavaFX 可执行文件,包括 JRE 和外部库

java - ColdFusion 中的 System.err java 日志位置

java - SiteMesh - 仍然活跃? v2 与 v3?

JavaFX场景生成器: Creating a new scene or just making changes on a specific element?

java - FXLauncher - 安装后是否可以更改/更新 JRE

java - 创建标签 JavaFX 时子场景中的白色矩形

java - Java 11下使用CXF的类转换异常

java - Android 中的位置未更新

java - 了解 Java 内存管理