一点背景:
我遇到问题之前我有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
的值。没有Person
和PersonUI
类的完整代码:
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类的这些属性工作?
答案 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));
});