当前位置: 首页 > 知识库问答 >
问题:

JavaFX ObservableList(提取器更新事件在应该触发时未触发)

司空锋
2023-03-14

一点背景:

public class Person {
    private final BooleanProperty adult;

    public Person(boolean adult) {
        this.adult = new SimpleBooleanProperty(adult);

        // Randomize "adult" value every 5 seconds
        ListViewExtractorTest.scheduledExecutorService.scheduleAtFixedRate(() -> {
            this.adult.set(Math.random() > 0.5);
            System.out.println("Updating adult: " + this);
        }, 5, 5, TimeUnit.SECONDS);

    // Getters setters
}
public class PersonUI {

    private final Person person;
    private final BooleanProperty adult;

    public PersonUI(Person person) {
        this.person = person;
        adult = new SimpleBooleanProperty(person.isAdult());
        person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
            Platform.runLater(() -> this.adult.set(newValue));
        });
    }

    // Getters and setters
}

我对ObservableList也做了同样的操作--创建了一个将ListChangeListener添加到源列表的方法,并且每当更新源列表时,它将使用JavaFX线程上的UI等价类更新目标列表:

public static <T, R> ObservableList<R> bindListContentPlatformRunLater(ObservableList<T> srcList, Function<T, R> function, ObservableList<R> dstList) {
    for (T item : srcList) {
        dstList.add(function.apply(item));
    }
    // Maybe should wrap the whole while loop in Platform.runLater()
    // Less runnables, but big changes might hang up the UI.
    srcList.addListener((ListChangeListener<? super T>) change -> {
        while (change.next()) {
            int from = change.getFrom();
            int to = change.getTo();
            if (change.wasPermutated()) {
                Platform.runLater(() -> dstList.subList(from, to).clear());
                List<? extends T> addItems = change.getList().subList(from, to);
                for (int i = 0; i < addItems.size(); i++) {
                    final int index = i;
                    T addItem = addItems.get(i);
                    Platform.runLater(() -> dstList.add(from + index, function.apply(addItem)));
                }
            } else {
                if (change.wasRemoved()) {
                    int removedSize = change.getRemovedSize();
                    Platform.runLater(() -> dstList.subList(from, from + removedSize).clear());
                }

                if (change.wasAdded()) {
                    List<? extends T> addedSubList = change.getAddedSubList();
                    for (int i = 0; i < addedSubList.size(); i++) {
                        final int index = i;
                        T item = addedSubList.get(i);
                        Platform.runLater(() -> dstList.add(from + index, function.apply(item)));
                    }
                }
            }
        }
    });
    return dstList;
}

因此,现在如果我有更新的observableList 并且其项属性更新不是在JavaFX线程上,我就可以轻松地获得一个在JavaFX中显示的列表:

ObservableList<PersonUI> secondList = FXCollections.observableArrayList(personUI -> new Observable[]{personUI.adultProperty()});
UIClassUtil.bindListContentPlatformRunLater(originalList, PersonUI::new, secondList);

所以现在的问题是:

我的预期是,当secondList显示在ListView 中时,只要Person#AdultProperty值发生更改,它就会更新,但预期的行为只持续几秒钟,之后ListView停止更新,这是因为secondList“update”事件停止触发。我猜PersonUI#adultProperty会在几秒钟后被垃圾收集,因为它除了在提取器中外没有在其他地方使用??

为了重现这个问题,我每隔几秒钟随机更改Person#adultProperty的值。不含PersonPersonUI类的完整代码

public final class ListViewExtractorTest extends Application {

    private static final ObservableList<Person> originalList = FXCollections.observableArrayList();
    private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

    static {
        for ( int a = 0; a < 5; a++ ) {
            originalList.add(new Person(Math.random() > 0.5));
        }
    }

    @Override
    public void start(Stage stage) throws Exception {
        ObservableList<PersonUI> secondList = FXCollections.observableArrayList(personUI -> new Observable[]{personUI.adultProperty()});
        UIClassUtil.bindListContentPlatformRunLater(originalList, PersonUI::new, secondList);

        secondList.addListener((ListChangeListener<? super PersonUI>) change -> {
            while (change.next()) {
                if (change.wasUpdated()) {
                    System.out.println("List updated!");
                }
            }
        });
        ListView<PersonUI> listViewPerson = new ListView<>(secondList);
        ListView<PersonUI> listViewForceRefresh = new ListView<>(secondList);
        stage.setScene(new Scene(new HBox(5, listViewPerson, listViewForceRefresh)));
        stage.setTitle("Hello");
        stage.show();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            Platform.runLater(listViewForceRefresh::refresh);
        }, 0, 100, TimeUnit.MILLISECONDS);
    }


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

因此,我有两个ListView:左边的一个在Person#AdultProperty值更改时更新,右边的一个我强制每100ms刷新一次。起初,列表是同步的,但几秒钟后,只有右边的列表显示正确的值,左边的列表则停留在相同的值上,ListChangeListener的“list Updated”只在相同的头几秒钟内打印。这意味着列表的“Update”事件只在相同的最初几秒内被激发。

但是,一旦我开始使用PersonUI#adultProperty(每隔几秒钟使用另一个线程在控制台中打印一次),它就开始以预期的行为运行,所以我猜测如果不使用它,它就会被垃圾回收。

有任何想法,我如何使UI类的这些属性工作,只要UI类本身是活的,或者以某种方式实现预期的行为?

共有1个答案

陈寒
2023-03-14

防止属性过早被垃圾收集的一个简单修复方法是从以下位置更新PersonUI构造函数中的侦听器:

person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
    Platform.runLater(() -> this.adult.set(newValue));
});

致:

person.adultProperty().addListener((observableValue, oldValue, newValue) -> {
    this.adult.get()
    Platform.runLater(() -> this.adult.set(newValue));
});
 类似资料:
  • 问题内容: 所以我有两个这样的桌子… 我正在尝试创建一个触发器,该触发器将: 更新时被更新。 为了进一步使事情复杂化, 如果在when 更新中不存在,我想将其插入并设置为1。 我一直在寻找类似的问题: 1. 在使用自动增量字段插入触发器之前/之后,以及 2. 使用触发器来更新另一个数据库中的表以 尝试将2.合并在一起。这是我到目前为止的内容: 任何建议和指示,我们将不胜感激。或者可能是我忽略的另一

  • 问题内容: 我的整个项目都使用(Bluebird)Promises,但是有一个使用EventEmitter的特定库。 我想要实现以下目标: 我在Promises链中读了EventEmitter的答案。这给了我一种执行’connect’事件的回调的方法。这是我到目前为止所到之处 现在如何进一步链接“ eventB”? 问题答案: 我假设您想为每个事件做不同的事情。即使由的动作触发,您也可以将其视为另

  • 问题内容: 我有3个文件: js_json.js->用于我的json代码 javascript.js->用于我的javascript函数 index.php 这里的代码为: 这是我的代码: 这里的代码: 我的问题是: 当我单击链接“ Hola Test 1”时,它将起作用并显示消息。问题是,在单击选择选项之后,出现了链接“ Hola Test”,然后单击该链接(“ Hola Test”),该消息没

  • 问题内容: 我有一个带有一列复选框的GridView(GridView的其余部分正在从数据库中填充)。我正在使用AJAX执行不同的功能,并且想知道我是否只是在正确的位置调用了OnCheckedChanged事件。是否应该将其包装在某种UpdatePanel中?我对这一切的工作方式仍然很陌生…基本上,我的目标是在选中复选框后更改数据库中的位值。我知道该怎么做的逻辑,我只是​​不知道我是否以正确的方式

  • 我正在为android创建一个phonegap应用程序,并想使用一些phonegap事件,如“恢复”、“暂停”、“后退按钮”等,但除了“deviceready”事件外,这些事件都不会被触发。以下是我的javascript代码,请检查我是否犯了任何错误: “ondeviceredy()”函数中的警报正在工作。 请帮忙,提前谢谢。

  • 问题内容: 我有一个div,其内容可能会以各种方式更改:例如,可以通过innerHTML重新加载其整个内容,或者可以通过DOM方法添加节点。反过来,这可能是通过本地javascript发生的,也可能是通过调用jQuery API或其他库间接发生的。 我希望在div的内容改变为执行一些代码,但我 绝对没有控制 上 如何 将改变。确实,我正在设计一个可供其他人使用的小部件,他们可以自由选择自己喜欢的d