<div dir="ltr"><div class="gmail_default" style="font-family:verdana,sans-serif">Hi Andy,</div><div class="gmail_default" style="font-family:verdana,sans-serif"><br></div><div class="gmail_default" style="font-family:verdana,sans-serif">Thank you; yes, that report was also myself via <a href="http://bugreport.java.com">bugreport.java.com</a>, I think -- I didn't actually realise it ended up anywhere.</div><div class="gmail_default" style="font-family:verdana,sans-serif"><br></div><div class="gmail_default" style="font-family:verdana,sans-serif">The mail I dropped here though is somewhat easier to digest / has less typos (for anyone interested)!</div><div class="gmail_default" style="font-family:verdana,sans-serif"><br></div><div class="gmail_default" style="font-family:verdana,sans-serif"><br></div><div class="gmail_default" style="font-family:verdana,sans-serif"><br></div><div class="gmail_default" style="font-family:verdana,sans-serif">Kind Regards,</div><div class="gmail_default" style="font-family:verdana,sans-serif">Cormac</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, 6 Feb 2024 at 16:11, Andy Goryachev <<a href="mailto:andy.goryachev@oracle.com" target="_blank">andy.goryachev@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>





<div lang="EN-US">
<div>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16"">Thank you for reporting the issue!<u></u><u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16""><u></u> <u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16"">Is this the same scenario as described in
<a href="https://bugs.openjdk.org/browse/JDK-8321323" target="_blank">https://bugs.openjdk.org/browse/JDK-8321323</a> ?<u></u><u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16""><u></u> <u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16"">-andy<u></u><u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16""><u></u> <u></u></span></p>
<p class="MsoNormal"><span style="font-size:11pt;font-family:"Iosevka Fixed SS16""><u></u> <u></u></span></p>
<div id="m_5787379717460985454m_-3440920738548794837mail-editor-reference-message-container">
<div>
<div style="border-right:none;border-bottom:none;border-left:none;border-top:1pt solid rgb(181,196,223);padding:3pt 0in 0in">
<p class="MsoNormal" style="margin-bottom:12pt"><b><span style="font-size:12pt;font-family:Aptos,sans-serif;color:black">From:
</span></b><span style="font-size:12pt;font-family:Aptos,sans-serif;color:black">openjfx-dev <<a href="mailto:openjfx-dev-retn@openjdk.org" target="_blank">openjfx-dev-retn@openjdk.org</a>> on behalf of Cormac Redmond <<a href="mailto:credmond@certak.com" target="_blank">credmond@certak.com</a>><br>
<b>Date: </b>Monday, February 5, 2024 at 12:31<br>
<b>To: </b><a href="mailto:openjfx-dev@openjdk.org" target="_blank">openjfx-dev@openjdk.org</a> <<a href="mailto:openjfx-dev@openjdk.org" target="_blank">openjfx-dev@openjdk.org</a>><br>
<b>Subject: </b>TreeTableView / FilteredList momentary incorrect selection bug<u></u><u></u></span></p>
</div>
<div>
<div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">Hi folks,<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">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.<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">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.<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">Let's say you have a tree (TableTreeView) displayed like this (as per code below):<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:"Courier New";color:black">root (invisible)</span><span style="font-size:12pt;color:black"><u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:"Courier New";color:black">   | ggg1</span><span style="font-size:12pt;color:black"><u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:"Courier New";color:black">       | ggg1.1<br>
       | xxx1.2<br>
       | ggg1.3<br>
   | bbb2<br>
       | bbb2.1<br>
       | bbb2.2<br>
       | bbb2.3<br>
   | aaa3<br>
       | aaa3.1<br>
       | aaa3.2<br>
       | aaa3.3</span><span style="font-size:12pt;color:black"><u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">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.<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">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). <u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">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?<br>
<br>
Sample output of running the below code:<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;color:black"><br>
</span><span style="font-size:12pt;font-family:"Courier New";color:black">Value of aaa3.2 from tree (for verification): aaa3.2      <---- printed to show the node about to be selected is the correct node<br>
Selecting item: aaa3.2        <---- printed to show the code is about to select it<br>
Selected item (as per listener): aaa3.2         <---- printed by the listener, showing it was selected<br>
About to filter on "ggg"            <---- printed to show you hit the button, now the list is filtering which will change the tree<br>
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.<br>
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</span><span style="font-size:12pt;color:black"><u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">Runnable code:<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal" style="margin-bottom:12pt"><span style="font-size:12pt;font-family:"Courier New";color:black">import javafx.application.Application;<br>
import javafx.beans.binding.Bindings;<br>
import javafx.beans.property.ObjectProperty;<br>
import javafx.beans.property.SimpleObjectProperty;<br>
import javafx.beans.value.ObservableValue;<br>
import javafx.collections.FXCollections;<br>
import javafx.collections.transformation.FilteredList;<br>
import javafx.scene.Scene;<br>
import javafx.scene.control.*;<br>
import javafx.scene.layout.VBox;<br>
import javafx.stage.Stage;<br>
<br>
import java.util.ArrayList;<br>
import java.util.List;<br>
import java.util.function.Predicate;<br>
<br>
public class TreeTableSelectBug extends Application {<br>
    private final TreeTableView<String> tree = new TreeTableView<>();<br>
    private final ObjectProperty<Predicate<String>> filterPredicate = new SimpleObjectProperty<>();<br>
<br>
    @Override<br>
    public void start(Stage primaryStage) throws Exception {<br>
        final VBox outer = new VBox();<br>
<br>
        tree.setShowRoot(false);<br>
        tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);<br>
        tree.setRoot(createTree());<br>
        addColumn();<br>
<br>
        // 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.<br>
        tree.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue)<br>
                -> System.out.println("Selected item (as per listener): " + (tree.getSelectionModel().getSelectedItem() == null ? "null" : tree.getSelectionModel().getSelectedItem().getValue())));<br>
<br>
        final Button filterButton = new Button("Filter on \"ggg\"");<br>
<br>
        outer.getChildren().addAll(filterButton, tree);<br>
        final Scene scene = new Scene(outer, 640, 480);<br>
        primaryStage.setScene(scene);<br>
        primaryStage.show();<br>
<br>
        // Select a lead node: aaa3 -> aaa3.2 (as an example)<br>
        final TreeItem<String> aaa32 = tree.getRoot().getChildren().get(2).getChildren().get(1);<br>
        System.out.println("Value of aaa3.2 from tree (for verification): " + aaa32.getValue());<br>
<br>
        // Expand it -- without expanding it, the bug won't occur<br>
        aaa32.getParent().setExpanded(true);<br>
<br>
        System.out.println("Selecting item: " + aaa32.getValue());<br>
        // Select an item, note it is printed. Same as a user clicking the row.<br>
        tree.getSelectionModel().select(aaa32);<br>
<br>
        filterButton.setOnAction(event -> {<br>
            System.out.println("About to filter on \"ggg\"");<br>
<br>
            // Filter based on "ggg" (the top parent node)<br>
            filterPredicate.set(string -> string.toLowerCase().trim().contains("ggg"));<br>
<br>
            // BUG: The output is the below. Note that "bbb2" gets selected along the way, for some reason. This is the bug.<br>
            //<br>
            // Output:<br>
            // Value of aaa3.2 from tree (for verification): aaa3.2<br>
            // Selecting item: aaa3.2<br>
            // Selected item (as per listener): aaa3.2<br>
            // About to filter on "ggg": aaa3.2<br>
            // Selected item (as per listener): bbb2<br>
            // Selected item (as per listener): null<br>
        });<br>
    }<br>
<br>
    private SimpleTreeItem<String> createTree() {<br>
<br>
        // So, we have a tree like this:<br>
        // ggg1<br>
        //  | ggg1.1<br>
        //  | xxx1.2<br>
        //  | ggg1.3<br>
        // bbb2<br>
        //  | bbb2.1<br>
        //  | bbb2.2<br>
        //  | bbb2.3<br>
        // aaa3<br>
        //  | children<br>
        //  | aaa3.1<br>
        //  | aaa3.2<br>
        //  | aaa3.3<br>
<br>
        final List<SimpleTreeItem<String>> gggChildren = new ArrayList<>();<br>
        gggChildren.add(new SimpleTreeItem<>("ggg1.1", null, filterPredicate));<br>
        gggChildren.add(new SimpleTreeItem<>("xxx1.2", null, filterPredicate));<br>
        gggChildren.add(new SimpleTreeItem<>("ggg1.3", null, filterPredicate));<br>
        final SimpleTreeItem<String> gggTree = new SimpleTreeItem<>("ggg1", gggChildren, filterPredicate);<br>
<br>
        final List<SimpleTreeItem<String>> bbbChildren = new ArrayList<>();<br>
        bbbChildren.add(new SimpleTreeItem<>("bbb2.1", null, filterPredicate));<br>
        bbbChildren.add(new SimpleTreeItem<>("bbb2.2", null, filterPredicate));<br>
        bbbChildren.add(new SimpleTreeItem<>("bbb2.3", null, filterPredicate));<br>
        final SimpleTreeItem<String> bbbTree = new SimpleTreeItem<>("bbb2", bbbChildren, filterPredicate);<br>
<br>
        final List<SimpleTreeItem<String>> aaaChildren = new ArrayList<>();<br>
        aaaChildren.add(new SimpleTreeItem<>("aaa3.1", null, filterPredicate));<br>
        aaaChildren.add(new SimpleTreeItem<>("aaa3.2", null, filterPredicate));<br>
        aaaChildren.add(new SimpleTreeItem<>("aaa3.3", null, filterPredicate));<br>
        final SimpleTreeItem<String> aaaTree = new SimpleTreeItem<>("aaa3", aaaChildren, filterPredicate);<br>
<br>
        final List<SimpleTreeItem<String>> rootChildren = new ArrayList<>();<br>
        rootChildren.add(gggTree);<br>
        rootChildren.add(bbbTree);<br>
        rootChildren.add(aaaTree);<br>
<br>
        return new SimpleTreeItem<>("root",<br>
                rootChildren,<br>
                filterPredicate);<br>
    }<br>
<br>
    static class SimpleTreeItem<T> extends TreeItem<T> {<br>
<br>
        private final ObjectProperty<Predicate<T>> filter = new SimpleObjectProperty<>();<br>
        private FilteredList<SimpleTreeItem<T>> children;<br>
<br>
        public SimpleTreeItem(final T value, List<SimpleTreeItem<T>> children, ObservableValue<Predicate<T>> filter) {<br>
            super(value, null);<br>
<br>
            if (filter != null) {<br>
                this.filter.bind(filter);<br>
            }<br>
<br>
            if (children != null) {<br>
                addChildren(children);<br>
            }<br>
        }<br>
<br>
        private void addChildren(List<SimpleTreeItem<T>> childrenParam) {<br>
            children = new FilteredList<>(FXCollections.observableArrayList(childrenParam));<br>
            children.predicateProperty().bind(Bindings.createObjectBinding(() -> SimpleTreeItem.this::showNode, filter));<br>
<br>
            Bindings.bindContent(getChildren(), children);<br>
        }<br>
<br>
        private boolean showNode(SimpleTreeItem<T> node) {<br>
            if (filter.get() == null) {<br>
                return true;<br>
            }<br>
<br>
            if (filter.get().test(node.getValue())) {<br>
                // Node is directly matched -> so show it<br>
                return true;<br>
            }<br>
<br>
            if (node.children != null) {<br>
                // Are there children (or children of children...) that are matched? If yes we also need to show this node<br>
                return node.children.getSource().stream().anyMatch(this::showNode);<br>
<br>
            }<br>
            return false;<br>
        }<br>
    }<br>
<br>
    protected void addColumn() {<br>
        TreeTableColumn<String, String> column = new TreeTableColumn<>("Some column");<br>
        column.setPrefWidth(150);<br>
<br>
        column.setCellFactory(param -> new TreeTableCell<>() {<br>
            @Override<br>
            protected void updateItem(String item, boolean empty) {<br>
                super.updateItem(item, empty);<br>
                if (empty || item == null) {<br>
                    setText(null);<br>
                } else {<br>
                    setText(item);<br>
                }<br>
            }<br>
        });<br>
<br>
        column.setCellValueFactory(<br>
                param -> param.getValue().valueProperty()<br>
        );<br>
        tree.getColumns().add(column);<br>
    }<br>
<br>
    public static void main(String[] args) {<br>
        launch(args);<br>
    }<br>
}</span><span style="font-size:12pt;color:black"><u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">Kind Regards,<u></u><u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12pt;font-family:Verdana,sans-serif;color:black">Cormac<u></u><u></u></span></p>
</div>
</div>
<div>
<div>
<div>
<div>
<p class="MsoNormal"><span style="font-size:11pt"><u></u> <u></u></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:11pt"><u></u> <u></u></span></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

</div></blockquote></div>