'JavaFX wait for all listeners to finish
I created a method that accepts a CheckListView (controlsFX), an item List, a TextField searchField and a BiPredicate. This functions allows for filtering the input checklist using the bipredicate as search function and the textField for input text. The method is as follows:
private static <T> void addFilterToCheckList(CheckListView<T> listView, List<T> items, TextField searchField,
BiPredicate<T, String> filterBiPredicate) {
ObservableList<T> observableList = FXCollections.observableArrayList(items);
listView.setItems(observableList);
Set<T> checks = new HashSet<>();
searchField.textProperty().addListener((obs, oldValue, newValue) -> {
List<T> checkedItems = listView.getCheckModel().getCheckedItems();
checks.addAll(checkedItems);
checks.removeIf(c -> listView.getItems().contains(c) && !checkedItems.contains(c));
if (StringUtils.isEmpty(newValue)) {
listView.setItems(observableList);
} else {
List<T> newList = items.stream().filter(t -> filterBiPredicate.test(t, newValue))
.collect(Collectors.toList());
newList.addAll(checkedItems.stream().filter(i -> !newList.contains(i)).collect(Collectors.toList()));
newList.sort(Comparator.comparingInt(items::indexOf));
listView.setItems(FXCollections.observableArrayList(newList));
}
checks.stream().filter(i -> listView.getItems().contains(i))
.forEach(i -> listView.getCheckModel().check(i));
searchField.requestFocus();
});
}
The method works perfectly: the filter works and if I select something it will be added to the filtered result even if the Bipredicate doesn't return true.
However if there are some selected items and I try to filter (or unfilter) I lose the focus on the searchField. This does NOT happen if there are no items checked.
My guess is that the listeners on the checkedIndicesList are not finished.
This is the "check" code:
/** {@inheritDoc} */
@Override
public void check(int index) {
if (index < 0 || index >= getItemCount()) return;
checkedIndices.set(index);
final int changeIndex = checkedIndicesList.indexOf(index);
checkedIndicesList.callObservers(new NonIterableChange.SimpleAddChange<>(changeIndex, changeIndex+1, checkedIndicesList));
}
/** {@inheritDoc} */
@Override
public void check(T item) {
int index = getItemIndex(item);
check(index);
}
Is there a way to "wait" for the listeners to finish their job before calling searchField.requestFocus()
?
The problem is present in Java 8 and 11.
EDIT: while trying to create a MRE I found out that everything works as supposed, so the problem should be somewhere else. I'll give myself some time to find the exact problem and I'll post the solution closing the question as soon as possible.
Anyway here's the MRE...even if it works.
public class Tester extends Application {
public static void main(String[] args) {
launch(args);
}
private static void addRequestFocusToControl(Scene scene, TextField toFocus, Control parent) {
scene.focusOwnerProperty().addListener((obs, oldValue, newValue) -> {
if (newValue != null && newValue != toFocus && isParent(parent, newValue)) {
toFocus.requestFocus();
toFocus.deselect();
}
});
}
private static boolean isParent(Parent parent, Node child) {
Node nodeParent = child.getParent();
if (nodeParent == parent) {
return true;
}
if (nodeParent instanceof Parent) {
return isParent(parent, nodeParent);
}
return false;
}
private static <T> void addFilterToCheckList(CheckListView<T> listView, List<T> items, TextField fieldRicerca,
BiPredicate<T, String> filterBiPredicate) {
addRequestFocusToControl(fieldRicerca.getScene(), fieldRicerca, listView);
ObservableList<T> observableList = FXCollections.observableArrayList(items);
listView.setItems(observableList);
Set<T> checks = new HashSet<>();
fieldRicerca.textProperty().addListener((obs, oldValue, newValue) -> {
List<T> checkedItems = listView.getCheckModel().getCheckedItems();
checks.removeIf(c -> listView.getItems().contains(c) && !checkedItems.contains(c));
checks.addAll(checkedItems);
if (StringUtils.isEmpty(newValue)) {
listView.setItems(observableList);
} else {
List<T> newList = items.stream().filter(t -> filterBiPredicate.test(t, newValue))
.collect(Collectors.toList());
newList.addAll(checkedItems.stream().filter(i -> !newList.contains(i)).collect(Collectors.toList()));
newList.sort(Comparator.comparingInt(items::indexOf));
listView.setItems(FXCollections.observableArrayList(newList));
}
checks.stream().filter(i -> listView.getItems().contains(i))
.forEach(i -> listView.getCheckModel().check(i));
});
}
@Override
public void start(Stage primaryStage) throws Exception {
List<String> items = new ArrayList<>();
Random rndm = new Random();
for (int i = 0; i < 50; i++) {
StringBuilder title = new StringBuilder().append(rndm.nextFloat()).append(" ").append(i).append(" ")
.append(rndm.nextFloat());
if (i % 2 == 0) {
title.append(" looooooong tail");
}
items.add(title.toString());
}
CheckListView<String> listView = new CheckListView<>();
TextField tf = new TextField();
VBox box = new VBox();
box.getChildren().addAll(listView, tf);
primaryStage.setScene(new Scene(box));
addFilterToCheckList(listView, items, tf, StringUtils::containsIgnoreCase);
primaryStage.show();
}
}
EDIT 2: The problem is something regarding the scrollabars. Now the MRE reproduces "correctly" the issue.
EDIT 3: I've ALMOST managed to solve all the problems. Now the problem is that when filtering the list the textfield would select all the text. I've updated the MRE.
Even if I try to call toFocus.deselect();
after calling toFocus.requestFocus();
the text will still be selected.
A partial solution would be adding
toFocus.selectedTextProperty().addListener((obs, oldValue, newValue) -> {
if (StringUtils.isNotBlank(newValue)) {
toFocus.deselect();
toFocus.selectEnd();
}
});
to the method addRequestFocusToControl
but this would stop user from selecting the text.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|