具有提取器更新事件的JavaFX ObservableList应该在不应该触发时触发

时间:2018-02-09 20:29:32

标签: java javafx properties observable observablelist

一点背景:

我遇到问题之前我有ObservableList提取器,但提取器正在侦听的属性是从 NOT JavaFX线程更新的。我想出的解决方案是创建" UI等效的"将复制原始类的值的类,只需在JavaFX线程上更新它们的属性。例如,如果我们有Person

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
}

UI等效类将是:

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<Person>并且在JavaFX线程上更新了项目属性 NOT ,我可以轻松获得一个可以在JavaFX中显示的列表:

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

现在问题出现了:

我的期望是当secondList中显示ListView<PersonUI>时,只要Person#adultProperty值发生更改,它就会更新,但预期的行为只会持续几秒钟,之后ListView会停止更新这是因为secondList&#34; update&#34;事件停止射击。我的猜测是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值发生变化时更新,另一个在我强制每100毫秒刷新一次。首先列表会同步,但几秒钟后,只有正确的列表显示正确的值,左侧列表会卡在相同的值上,并且“#34;列表已更新&#34; ListChangeListener只在相同的前几秒打印。这意味着&#34;更新&#34;列表的事件仅在相同的前几秒触发。

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

任何想法,只要UI类本身存在或以其他方式实现预期的行为,我怎么能使UI类的这些属性工作?

1 个答案:

答案 0 :(得分:0)

防止属性过早收集垃圾的简单修复方法是更新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));
});