<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<meta name="Generator" content="Microsoft Word 15 (filtered medium)">
<style><!--
/* Font Definitions */
@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
@font-face
        {font-family:Verdana;
        panose-1:2 11 6 4 3 5 4 4 2 4;}
@font-face
        {font-family:Aptos;
        panose-1:2 11 0 4 2 2 2 2 2 4;}
@font-face
        {font-family:"Iosevka Fixed SS16";
        panose-1:2 0 5 9 3 0 0 0 0 4;}
@font-face
        {font-family:"Times New Roman \(Body CS\)";
        panose-1:2 11 6 4 2 2 2 2 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        font-size:10.0pt;
        font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:#0563C1;
        text-decoration:underline;}
span.EmailStyle18
        {mso-style-type:personal-reply;
        font-family:"Iosevka Fixed SS16";
        color:windowtext;}
.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;
        mso-ligatures:none;}
@page WordSection1
        {size:8.5in 11.0in;
        margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
        {page:WordSection1;}
--></style>
</head>
<body lang="EN-US" link="#0563C1" vlink="#954F72" style="word-wrap:break-word">
<div class="WordSection1">
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Thank you for reporting the issue!<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">Is this the same scenario as described in
<a href="https://bugs.openjdk.org/browse/JDK-8321323">https://bugs.openjdk.org/browse/JDK-8321323</a> ?<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16"">-andy<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<p class="MsoNormal"><span style="font-size:11.0pt;font-family:"Iosevka Fixed SS16""><o:p> </o:p></span></p>
<div id="mail-editor-reference-message-container">
<div>
<div style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal" style="margin-bottom:12.0pt"><b><span style="font-size:12.0pt;font-family:"Aptos",sans-serif;color:black">From:
</span></b><span style="font-size:12.0pt;font-family:"Aptos",sans-serif;color:black">openjfx-dev <openjfx-dev-retn@openjdk.org> on behalf of Cormac Redmond <credmond@certak.com><br>
<b>Date: </b>Monday, February 5, 2024 at 12:31<br>
<b>To: </b>openjfx-dev@openjdk.org <openjfx-dev@openjdk.org><br>
<b>Subject: </b>TreeTableView / FilteredList momentary incorrect selection bug<o:p></o:p></span></p>
</div>
<div>
<div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black">Hi folks,<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black">Let's say you have a tree (TableTreeView) displayed like this (as per code below):<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Courier New";color:black">root (invisible)</span><span style="font-size:12.0pt;color:black"><o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Courier New";color:black">   | ggg1</span><span style="font-size:12.0pt;color:black"><o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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:12.0pt;color:black"><o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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.<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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). <o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;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:<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;color:black"><br>
</span><span style="font-size:12.0pt;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:12.0pt;color:black"><o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black">Runnable code:<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal" style="margin-bottom:12.0pt"><span style="font-size:12.0pt;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:12.0pt;color:black"><o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black">Kind Regards,<o:p></o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:12.0pt;font-family:"Verdana",sans-serif;color:black">Cormac<o:p></o:p></span></p>
</div>
</div>
<div>
<div>
<div>
<div>
<p class="MsoNormal"><span style="font-size:11.0pt"><o:p> </o:p></span></p>
</div>
<div>
<p class="MsoNormal"><span style="font-size:11.0pt"><o:p> </o:p></span></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>