Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8091687

Create possibility to customize the implementation of ObservableList used in TreeItem

    Details

    • Type: Enhancement
    • Status: Open
    • Priority: P4
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: tbd_major
    • Component/s: javafx
    • Labels:
      None

      Description

      It should be possible to customize the implementation of ObservableList used in TreeItem. A common use case is to use a SortedList or a FilteredList. When using a FilteredList the TreeView does not update correctly when all children of a TreeItem are filtered. In this case the expand/collapse button should be hidden, but this is not the case.

      The reason behind this is that TreeItem attaches its childrenListener only to the original ObservableList that is created lazily in getChildren(). This listener is responsible for setting the read only leaf property.

        Activity

        Hide
        ckeimeljfx Christoph Keimel (Inactive) added a comment - - edited
        In this example I used a subclass of TreeItem to override getChildren() to return an instance of FilteredList. If you start the Application and enter "Folder" in the text field, you will notice that only folder items remain, as is to be expected. Unfortunately the collapse/expand buttons for these items will still be displayed.

        import java.util.function.Predicate;
        import javafx.application.Application;
        import javafx.beans.binding.Bindings;
        import javafx.beans.property.ObjectProperty;
        import javafx.beans.property.SimpleObjectProperty;
        import javafx.collections.FXCollections;
        import javafx.collections.ObservableList;
        import javafx.collections.transformation.FilteredList;
        import javafx.event.Event;
        import javafx.scene.Node;
        import javafx.scene.Parent;
        import javafx.scene.Scene;
        import javafx.scene.control.TextField;
        import javafx.scene.control.TitledPane;
        import javafx.scene.control.TreeItem;
        import javafx.scene.control.TreeView;
        import javafx.scene.layout.BorderPane;
        import javafx.scene.layout.HBox;
        import javafx.scene.layout.Priority;
        import javafx.scene.layout.VBox;
        import javafx.stage.Stage;

        public class FilterableTreeItemTest extends Application {

            public static void main(String[] args) {
                launch(args);
            }

            private TextField filterField;
            private FilterableTreeItem<String> folder1;

            @Override
            public void start(Stage stage) throws Exception {
                try {
                    Parent root = createContents();
                    Scene scene = new Scene(root, 800, 600);
                    stage.setScene(scene);
                    stage.show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            private Parent createContents() {
                VBox vbox = new VBox(6);
                vbox.getChildren().add(createFilterPane());
                Node demoPane = createDemoPane();
                VBox.setVgrow(demoPane, Priority.ALWAYS);
                vbox.getChildren().add(demoPane);
                return new BorderPane(vbox);
            }

            private Node createFilterPane() {
                filterField = new TextField();
                filterField.setPromptText("Enter filter text ...");
                TitledPane pane = new TitledPane("Filter", filterField);
                pane.setCollapsible(false);
                return pane;
            }

            private Node createDemoPane() {
                HBox hbox = new HBox(6);
                Node filteredTree = createFilteredTree();
                HBox.setHgrow(filteredTree, Priority.ALWAYS);
                hbox.getChildren().add(filteredTree);
                return hbox;
            }

            private Node createFilteredTree() {
                FilterableTreeItem<String> root = getTreeModel();
                root.predicate.bind(Bindings.createObjectBinding(() -> {
                    if (filterField.getText() == null || filterField.getText().isEmpty())
                        return null;
                    return actor -> actor.contains(filterField.getText());
                }, filterField.textProperty()));

                TreeView<String> treeView = new TreeView<>(root);
                treeView.setShowRoot(false);

                TitledPane pane = new TitledPane("Filtered TreeView", treeView);
                pane.setCollapsible(false);
                pane.setMaxHeight(Double.MAX_VALUE);
                return pane;
            }

            private FilterableTreeItem<String> getTreeModel() {
                FilterableTreeItem<String> root = new FilterableTreeItem<>("Root");
                folder1 = createFolder("Folder 1");
                folder1.setExpanded(true);
                root.getInternalChildren().add(folder1);
                root.getInternalChildren().add(createFolder("Folder 2"));
                root.getInternalChildren().add(createFolder("Folder 3"));
                return root;
            }

            private FilterableTreeItem<String> createFolder(String name) {
                FilterableTreeItem<String> folder = new FilterableTreeItem<>(name);
                getActorList().forEach(actor -> folder.getInternalChildren().add(new FilterableTreeItem<>(actor)));
                return folder;
            }

            private Iterable<String> getActorList() {
                ObservableList<String> actorList = FXCollections.observableArrayList(
                        "Jack Nicholson", "Marlon Brando", "Robert De Niro",
                        "Al Pacino", "Daniel Day-Lewis", "Dustin Hoffman", "Tom Hanks",
                        "Anthony Hopkins", "Paul Newman", "Denzel Washington",
                        "Spencer Tracy", "Laurence Olivier", "Jack Lemmon");
                return actorList;
            }

            private static class FilterableTreeItem<T> extends TreeItem<T> {
                private FilteredList<TreeItem<T>> filteredList;

                ObjectProperty<Predicate<T>> predicate = new SimpleObjectProperty<Predicate<T>>() {
                    @Override
                    protected void invalidated() {
                        fireChildrenModificationEvent();
                    }
                };

                public FilterableTreeItem(T value) {
                    super(value);
                    this.filteredList = new FilteredList<>(super.getChildren());
                    this.filteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> {
                        return child -> {
                            // Set the predicate of child items to force filtering
                            if (child instanceof FilterableTreeItem) {
                                FilterableTreeItem<T> filterableChild = (FilterableTreeItem<T>) child;
                                filterableChild.predicate.set(this.predicate.get());
                            }
                            // If there is no predicate, keep this tree item
                            if (this.predicate.get() == null)
                                return true;
                            // If there are children, keep this tree item
                            if (child.getChildren().size() > 0)
                                return true;
                            // Otherwise ask the TreeItemPredicate
                            return this.predicate.get().test(
                                child.getValue());
                        };
                    }, this.predicate));
                }

                @Override
                public ObservableList<TreeItem<T>> getChildren() {
                    return this.filteredList;
                }

                public ObservableList<TreeItem<T>> getInternalChildren() {
                    return super.getChildren();
                }

                void fireChildrenModificationEvent() {
                    TreeModificationEvent<T> event = new TreeModificationEvent<T>(TreeItem.childrenModificationEvent(), this);
                    Event.fireEvent(this, event);
                }

                // FIXME Workaround
                // @Override
                // public boolean isLeaf() {
                // return getChildren().isEmpty();
                // }

            }

        }
        Show
        ckeimeljfx Christoph Keimel (Inactive) added a comment - - edited In this example I used a subclass of TreeItem to override getChildren() to return an instance of FilteredList. If you start the Application and enter "Folder" in the text field, you will notice that only folder items remain, as is to be expected. Unfortunately the collapse/expand buttons for these items will still be displayed. import java.util.function.Predicate; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.event.Event; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.TextField; import javafx.scene.control.TitledPane; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class FilterableTreeItemTest extends Application {     public static void main(String[] args) {         launch(args);     }     private TextField filterField;     private FilterableTreeItem<String> folder1;     @Override     public void start(Stage stage) throws Exception {         try {             Parent root = createContents();             Scene scene = new Scene(root, 800, 600);             stage.setScene(scene);             stage.show();         } catch (Exception e) {             e.printStackTrace();         }     }     private Parent createContents() {         VBox vbox = new VBox(6);         vbox.getChildren().add(createFilterPane());         Node demoPane = createDemoPane();         VBox.setVgrow(demoPane, Priority.ALWAYS);         vbox.getChildren().add(demoPane);         return new BorderPane(vbox);     }     private Node createFilterPane() {         filterField = new TextField();         filterField.setPromptText("Enter filter text ...");         TitledPane pane = new TitledPane("Filter", filterField);         pane.setCollapsible(false);         return pane;     }     private Node createDemoPane() {         HBox hbox = new HBox(6);         Node filteredTree = createFilteredTree();         HBox.setHgrow(filteredTree, Priority.ALWAYS);         hbox.getChildren().add(filteredTree);         return hbox;     }     private Node createFilteredTree() {         FilterableTreeItem<String> root = getTreeModel();         root.predicate.bind(Bindings.createObjectBinding(() -> {             if (filterField.getText() == null || filterField.getText().isEmpty())                 return null;             return actor -> actor.contains(filterField.getText());         }, filterField.textProperty()));         TreeView<String> treeView = new TreeView<>(root);         treeView.setShowRoot(false);         TitledPane pane = new TitledPane("Filtered TreeView", treeView);         pane.setCollapsible(false);         pane.setMaxHeight(Double.MAX_VALUE);         return pane;     }     private FilterableTreeItem<String> getTreeModel() {         FilterableTreeItem<String> root = new FilterableTreeItem<>("Root");         folder1 = createFolder("Folder 1");         folder1.setExpanded(true);         root.getInternalChildren().add(folder1);         root.getInternalChildren().add(createFolder("Folder 2"));         root.getInternalChildren().add(createFolder("Folder 3"));         return root;     }     private FilterableTreeItem<String> createFolder(String name) {         FilterableTreeItem<String> folder = new FilterableTreeItem<>(name);         getActorList().forEach(actor -> folder.getInternalChildren().add(new FilterableTreeItem<>(actor)));         return folder;     }     private Iterable<String> getActorList() {         ObservableList<String> actorList = FXCollections.observableArrayList(                 "Jack Nicholson", "Marlon Brando", "Robert De Niro",                 "Al Pacino", "Daniel Day-Lewis", "Dustin Hoffman", "Tom Hanks",                 "Anthony Hopkins", "Paul Newman", "Denzel Washington",                 "Spencer Tracy", "Laurence Olivier", "Jack Lemmon");         return actorList;     }     private static class FilterableTreeItem<T> extends TreeItem<T> {         private FilteredList<TreeItem<T>> filteredList;         ObjectProperty<Predicate<T>> predicate = new SimpleObjectProperty<Predicate<T>>() {             @Override             protected void invalidated() {                 fireChildrenModificationEvent();             }         };         public FilterableTreeItem(T value) {             super(value);             this.filteredList = new FilteredList<>(super.getChildren());             this.filteredList.predicateProperty().bind(Bindings.createObjectBinding(() -> {                 return child -> {                     // Set the predicate of child items to force filtering                     if (child instanceof FilterableTreeItem) {                         FilterableTreeItem<T> filterableChild = (FilterableTreeItem<T>) child;                         filterableChild.predicate.set(this.predicate.get());                     }                     // If there is no predicate, keep this tree item                     if (this.predicate.get() == null)                         return true;                     // If there are children, keep this tree item                     if (child.getChildren().size() > 0)                         return true;                     // Otherwise ask the TreeItemPredicate                     return this.predicate.get().test(                         child.getValue());                 };             }, this.predicate));         }         @Override         public ObservableList<TreeItem<T>> getChildren() {             return this.filteredList;         }         public ObservableList<TreeItem<T>> getInternalChildren() {             return super.getChildren();         }         void fireChildrenModificationEvent() {             TreeModificationEvent<T> event = new TreeModificationEvent<T>(TreeItem.childrenModificationEvent(), this);             Event.fireEvent(this, event);         }         // FIXME Workaround         // @Override         // public boolean isLeaf() {         // return getChildren().isEmpty();         // }     } }
        Hide
        duke J. Duke (Inactive) added a comment -
        You might want to try the reflection hack in RT-34943 as a work-around for your problem.
        Show
        duke J. Duke (Inactive) added a comment - You might want to try the reflection hack in RT-34943 as a work-around for your problem.

          People

          • Assignee:
            jgiles Jonathan Giles
            Reporter:
            ckeimeljfx Christoph Keimel (Inactive)
          • Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:
              Imported: