TreeTableView / FilteredList momentary incorrect selection bug

Cormac Redmond credmond at certak.com
Tue Feb 6 16:20:58 UTC 2024


Hi Andy,

Thank you; yes, that report was also myself via bugreport.java.com, I think
-- I didn't actually realise it ended up anywhere.

The mail I dropped here though is somewhat easier to digest / has less
typos (for anyone interested)!



Kind Regards,
Cormac

On Tue, 6 Feb 2024 at 16:11, Andy Goryachev <andy.goryachev at oracle.com>
wrote:

> Thank you for reporting the issue!
>
>
>
> Is this the same scenario as described in
> https://bugs.openjdk.org/browse/JDK-8321323 ?
>
>
>
> -andy
>
>
>
>
>
> *From: *openjfx-dev <openjfx-dev-retn at openjdk.org> on behalf of Cormac
> Redmond <credmond at certak.com>
> *Date: *Monday, February 5, 2024 at 12:31
> *To: *openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>
> *Subject: *TreeTableView / FilteredList momentary incorrect selection bug
>
> Hi folks,
>
>
>
> I have noticed an issue when combining TreeTableView and FilteredLists,
> where a wrong node is "selected" (I believe during some shift selection
> functionality in TreeTableView). Currently using JavaFX 21-ea+5 on Windows,
> but occurs in later builds too.
>
>
>
> First noticed in a much more complex scenario with many components, I
> narrowed it down quite a bit, and created the simplest example I could, to
> demonstrate what I think is a bug.
>
>
>
> Let's say you have a tree (TableTreeView) displayed like this (as per code
> below):
>
>
>
> root (invisible)
>
>    | ggg1
>
>        | ggg1.1
>        | xxx1.2
>        | ggg1.3
>    | bbb2
>        | bbb2.1
>        | bbb2.2
>        | bbb2.3
>    | aaa3
>        | aaa3.1
>        | aaa3.2
>        | aaa3.3
>
>
>
> If you select leaf node "aaa3.2", for example, and then filter using a
> string "ggg", the node "bbb2", is being selected unexpectedly/incorrectly
> in the process, where it shouldn't. This is the bug.
>
>
>
> Here's a simple way to reproduce the issue. Run the code, and look at the
> tree first. Observe that a leaf node "aaa3.2" is selected for you (the code
> selects this as a shortcut for you).
>
>
>
> Hit the button to filter with string "ggg", and notice the logging showing
> that "bbb2" -- the leaf node's parent's sibling, is incorrectly momentarily
> selected, before "null" is settled as the final selected value (null being
> correct). Why is this happening?
>
> Sample output of running the below code:
>
>
> Value of aaa3.2 from tree (for verification): aaa3.2      <---- printed to
> show the node about to be selected is the correct node
> Selecting item: aaa3.2        <---- printed to show the code is about to
> select it
> Selected item (as per listener): aaa3.2         <---- printed by the
> listener, showing it was selected
> About to filter on "ggg"            <---- printed to show you hit the
> button, now the list is filtering which will change the tree
> Selected item (as per listener): bbb2            <----  printed by the
> listener, showing bbb2 is selected , why is this happening along the way?
> This seems like a bug. Maybe it's part of some "let's select the closest
> sibling" logic, but...why? And if so, it's not a consistent pattern/logic
> that I can understand.
> Selected item (as per listener): null         <---- printed by the
> listener, showing null is "selected", which is fine / expected, as the
> *real* selected item has been filtered out
>
>
>
> Runnable code:
>
>
>
> import javafx.application.Application;
> import javafx.beans.binding.Bindings;
> import javafx.beans.property.ObjectProperty;
> import javafx.beans.property.SimpleObjectProperty;
> import javafx.beans.value.ObservableValue;
> import javafx.collections.FXCollections;
> import javafx.collections.transformation.FilteredList;
> import javafx.scene.Scene;
> import javafx.scene.control.*;
> import javafx.scene.layout.VBox;
> import javafx.stage.Stage;
>
> import java.util.ArrayList;
> import java.util.List;
> import java.util.function.Predicate;
>
> public class TreeTableSelectBug extends Application {
>     private final TreeTableView<String> tree = new TreeTableView<>();
>     private final ObjectProperty<Predicate<String>> filterPredicate = new
> SimpleObjectProperty<>();
>
>     @Override
>     public void start(Stage primaryStage) throws Exception {
>         final VBox outer = new VBox();
>
>         tree.setShowRoot(false);
>         tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
>         tree.setRoot(createTree());
>         addColumn();
>
>         // Print selection changes: there should only be two (initial
> selection, then final selection to "null" when nodes are filtered), but
> there is an extra one ("bbb2") in the middle.
>
> tree.getSelectionModel().selectedItemProperty().addListener((observable,
> oldValue, newValue)
>                 -> System.out.println("Selected item (as per listener): "
> + (tree.getSelectionModel().getSelectedItem() == null ? "null" :
> tree.getSelectionModel().getSelectedItem().getValue())));
>
>         final Button filterButton = new Button("Filter on \"ggg\"");
>
>         outer.getChildren().addAll(filterButton, tree);
>         final Scene scene = new Scene(outer, 640, 480);
>         primaryStage.setScene(scene);
>         primaryStage.show();
>
>         // Select a lead node: aaa3 -> aaa3.2 (as an example)
>         final TreeItem<String> aaa32 =
> tree.getRoot().getChildren().get(2).getChildren().get(1);
>         System.out.println("Value of aaa3.2 from tree (for verification):
> " + aaa32.getValue());
>
>         // Expand it -- without expanding it, the bug won't occur
>         aaa32.getParent().setExpanded(true);
>
>         System.out.println("Selecting item: " + aaa32.getValue());
>         // Select an item, note it is printed. Same as a user clicking the
> row.
>         tree.getSelectionModel().select(aaa32);
>
>         filterButton.setOnAction(event -> {
>             System.out.println("About to filter on \"ggg\"");
>
>             // Filter based on "ggg" (the top parent node)
>             filterPredicate.set(string ->
> string.toLowerCase().trim().contains("ggg"));
>
>             // BUG: The output is the below. Note that "bbb2" gets
> selected along the way, for some reason. This is the bug.
>             //
>             // Output:
>             // Value of aaa3.2 from tree (for verification): aaa3.2
>             // Selecting item: aaa3.2
>             // Selected item (as per listener): aaa3.2
>             // About to filter on "ggg": aaa3.2
>             // Selected item (as per listener): bbb2
>             // Selected item (as per listener): null
>         });
>     }
>
>     private SimpleTreeItem<String> createTree() {
>
>         // So, we have a tree like this:
>         // ggg1
>         //  | ggg1.1
>         //  | xxx1.2
>         //  | ggg1.3
>         // bbb2
>         //  | bbb2.1
>         //  | bbb2.2
>         //  | bbb2.3
>         // aaa3
>         //  | children
>         //  | aaa3.1
>         //  | aaa3.2
>         //  | aaa3.3
>
>         final List<SimpleTreeItem<String>> gggChildren = new ArrayList<>();
>         gggChildren.add(new SimpleTreeItem<>("ggg1.1", null,
> filterPredicate));
>         gggChildren.add(new SimpleTreeItem<>("xxx1.2", null,
> filterPredicate));
>         gggChildren.add(new SimpleTreeItem<>("ggg1.3", null,
> filterPredicate));
>         final SimpleTreeItem<String> gggTree = new
> SimpleTreeItem<>("ggg1", gggChildren, filterPredicate);
>
>         final List<SimpleTreeItem<String>> bbbChildren = new ArrayList<>();
>         bbbChildren.add(new SimpleTreeItem<>("bbb2.1", null,
> filterPredicate));
>         bbbChildren.add(new SimpleTreeItem<>("bbb2.2", null,
> filterPredicate));
>         bbbChildren.add(new SimpleTreeItem<>("bbb2.3", null,
> filterPredicate));
>         final SimpleTreeItem<String> bbbTree = new
> SimpleTreeItem<>("bbb2", bbbChildren, filterPredicate);
>
>         final List<SimpleTreeItem<String>> aaaChildren = new ArrayList<>();
>         aaaChildren.add(new SimpleTreeItem<>("aaa3.1", null,
> filterPredicate));
>         aaaChildren.add(new SimpleTreeItem<>("aaa3.2", null,
> filterPredicate));
>         aaaChildren.add(new SimpleTreeItem<>("aaa3.3", null,
> filterPredicate));
>         final SimpleTreeItem<String> aaaTree = new
> SimpleTreeItem<>("aaa3", aaaChildren, filterPredicate);
>
>         final List<SimpleTreeItem<String>> rootChildren = new
> ArrayList<>();
>         rootChildren.add(gggTree);
>         rootChildren.add(bbbTree);
>         rootChildren.add(aaaTree);
>
>         return new SimpleTreeItem<>("root",
>                 rootChildren,
>                 filterPredicate);
>     }
>
>     static class SimpleTreeItem<T> extends TreeItem<T> {
>
>         private final ObjectProperty<Predicate<T>> filter = new
> SimpleObjectProperty<>();
>         private FilteredList<SimpleTreeItem<T>> children;
>
>         public SimpleTreeItem(final T value, List<SimpleTreeItem<T>>
> children, ObservableValue<Predicate<T>> filter) {
>             super(value, null);
>
>             if (filter != null) {
>                 this.filter.bind(filter);
>             }
>
>             if (children != null) {
>                 addChildren(children);
>             }
>         }
>
>         private void addChildren(List<SimpleTreeItem<T>> childrenParam) {
>             children = new
> FilteredList<>(FXCollections.observableArrayList(childrenParam));
>
> children.predicateProperty().bind(Bindings.createObjectBinding(() ->
> SimpleTreeItem.this::showNode, filter));
>
>             Bindings.bindContent(getChildren(), children);
>         }
>
>         private boolean showNode(SimpleTreeItem<T> node) {
>             if (filter.get() == null) {
>                 return true;
>             }
>
>             if (filter.get().test(node.getValue())) {
>                 // Node is directly matched -> so show it
>                 return true;
>             }
>
>             if (node.children != null) {
>                 // Are there children (or children of children...) that
> are matched? If yes we also need to show this node
>                 return
> node.children.getSource().stream().anyMatch(this::showNode);
>
>             }
>             return false;
>         }
>     }
>
>     protected void addColumn() {
>         TreeTableColumn<String, String> column = new
> TreeTableColumn<>("Some column");
>         column.setPrefWidth(150);
>
>         column.setCellFactory(param -> new TreeTableCell<>() {
>             @Override
>             protected void updateItem(String item, boolean empty) {
>                 super.updateItem(item, empty);
>                 if (empty || item == null) {
>                     setText(null);
>                 } else {
>                     setText(item);
>                 }
>             }
>         });
>
>         column.setCellValueFactory(
>                 param -> param.getValue().valueProperty()
>         );
>         tree.getColumns().add(column);
>     }
>
>     public static void main(String[] args) {
>         launch(args);
>     }
> }
>
>
>
>
>
>
>
> Kind Regards,
>
> Cormac
>
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20240206/327a1a76/attachment-0001.htm>


More information about the openjfx-dev mailing list