inheritancejavafxtreeviewoverridingtreetable

JavaFx, how to correctly override both : getChildren() and getChildren().add(newNode) in a Tree Table View


Inside an application which uses very extensively TreeTableViews, I came across a need to fire a code each time a child is added to this tree.

My first approach was to 'encapsulate' the myTree.getChildren().add(...) inside a method, for instance :

    public boolean addToChildren(TreeItemPlaylist tritPlaylist) {
        boolean resAdd = false;
        resAdd = getChildren().add(tritPlaylist);
        [... personalized code which might affect the resAdd ...]
        return resAdd;
    }

This still leaves the getChildren().add(...) accessible, and my personalized code could thus be bypassed.

I'm looking to get something cleaner, and am stuck trying to override the add(newNode) method of getChildren() in that TreeTableView.

I have made quite some research, and ended up trying to make :

  1. A parallel LIST of children. Which then will be accessible to
  2. Have it's 'add(...) overriden
  3. And that list returned by the getChildren() of the TreeItem

The Overriden part for the getChildren() was inspired very strongly from https://docs.oracle.com/javafx/2/api/javafx/scene/control/TreeItem.html

The part of making a parallel LIST (an FXCollections.observableArrayList() to be precise), inside a class which would extend a SimpleListProperty<T>, was suggested in a PDF I can't find back, of an Indian teacher as exercise for his JavaFx class.

The following code just needs to be copied, and so I have included inside a 'main' class, all underlying classes... I know this is NOT best practice, but for the sake of making it as easy as possible for you to make the code run, I decided to do so... please tell me if you think it's better to make it as usual : 1 Class = 1 File.

PROBLEM : The problem I have is illustrated by using the MCE in 2 runs (would a checkbox be better to do that?) :

Manifestation 1 : Tree

So in both cases, my FIRST children list exists... They are under the ROOT tree item, but : They are not shown in the tree, under the ROOT, when going through customized ObservableList.

Of what I understood, the mechanism of the TreeTableView is relying on specific calls to the TreeItem list : children, and probably I'm missing something there... Since I made a parallel LIST which is therefore not called... sort of... Did some research and experiments, but no luck so far :-(.

Is it because :

Manifestation 2 : Children / Parent

I have broken the children/parent ! This also has to do with the fact that I tried to build a parallel LIST and made a customized getChildren(). Should be a customization of super.getChildren() I suppose, but then again, tried many ways without success. And if so, I don't get my customnized LIST but the original (correct) one.

Help would be greatly appreciated.

MCE :

package overrides;

import javafx.application.Application;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class CustomedTypedTreeTableSample extends Application {

    public final class ObservableListOfTreeItemPlaylist<T> extends SimpleListProperty<T> {

        ObservableListOfTreeItemPlaylist() {
            super(FXCollections.observableArrayList());
        }

        @Override
        public boolean add(T element) {
            System.out.println("Adding an element");
            return super.add(element);
        }

    }

    public class TreeItemPlaylist extends TreeItem<TreeTableRowPlaylist> {

        private ObservableListOfTreeItemPlaylist<TreeItem<TreeTableRowPlaylist>> playlistItems = new ObservableListOfTreeItemPlaylist<>();

        public TreeItemPlaylist(TreeTableRowPlaylist treeTableRow) {
            super(treeTableRow);
        }

        ////////////////////////
        // COMMENT / UNCOMMENT the next overridden method to see my problem
        //
        // When commenting : - GUI tree is filled with the correct children. All is fine... but, of course, no customized 'add(...'
        // - When printing to console the children of root : All branches and children are there !
        //
        // When NOT commenting : - The overridden 'add(T element)' of class ObservableListOfTreeItemPlaylist is fired...
        // - GUI tree is left with only the ROOT element
        // - When printing to console only the first children of root are there ! But hey... they are there on the console, but not in the GUI !
        @Override
        public ObservableListOfTreeItemPlaylist<TreeItem<TreeTableRowPlaylist>> getChildren() {
            return playlistItems;
        }
        ////////////////////////
    }

    public class TreeTableRowPlaylist extends TreeTableRow<TreeTableRowPlaylist> {

        // Initialized during constructor
        private SimpleStringProperty name;

        public String getName() {
            return name.get();
        }

        public void setName(String name) {
            this.name.set(name);
        }

        public SimpleStringProperty nameProperty() {
            return name;
        }

        public TreeTableRowPlaylist(String name) {
            super();
            this.name = new SimpleStringProperty(name);
        }
    }

    @SuppressWarnings ("unchecked")
    @Override
    public void start(Stage primaryStage) {
        ///// TreeTableView basics
        // Tree table view
        TreeTableView<TreeTableRowPlaylist> tableView = new TreeTableView<>();
        // Column for the name
        TreeTableColumn<TreeTableRowPlaylist, String> nameColumn = new TreeTableColumn<>("Name");
        nameColumn.setCellValueFactory(param -> param.getValue().getValue().nameProperty());
        // Add column
        tableView.getColumns().addAll(nameColumn);

        ///// Dummy tree building
        tableView.setRoot(new TreeItemPlaylist(new TreeTableRowPlaylist("ROOT")));
        tableView.getRoot().getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf 1")));
        tableView.getRoot().getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf 2")));
        TreeItemPlaylist aTreeBranchA = new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Branch A"));
        aTreeBranchA.getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf A.1")));
        aTreeBranchA.getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf A.2")));
        tableView.getRoot().getChildren().add(aTreeBranchA);

        ///// Testing if overriding is working
        // button to get the list of children
        Button display = new Button("Print out children list to console");
        display.setOnAction(event -> tableView.getRoot().getChildren().forEach(playlist -> printChildrenToConsole(playlist)));

        ///// Setting the GUI
        VBox vbox = new VBox(0, tableView, display);

        Scene scene = new Scene(vbox);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void printChildrenToConsole(TreeItem<TreeTableRowPlaylist> playlist) {
        System.out.println(playlist.getValue().getName());
        if (!playlist.isLeaf()) {
            playlist.getChildren().forEach(childPlaylist -> printChildrenToConsole(childPlaylist));
        }
    }

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

}

Solution

  • Sometimes, it's best to just take an eagle eye and restart the complete architecture from scratch. This lead to one of the best answers possible in my case... I hope... And it works well !

    The approach of overriding the getChildren.add(...) is a wrong way to go in this case.

    The correct direction is about events and in this case the addEventHandler : https://docs.oracle.com/javafx/2/api/javafx/scene/control/TreeItem.html#addEventHandler(javafx.event.EventType,%20javafx.event.EventHandler)

    And by using the TreeItem.childrenModificationEvent(), added to each element of the tree, it was quite straightforward to end up with the result wanted : any addition to my TreeTableView is going through my personalized code.

    enter image description here

    Here is the MCE with the solution implemented :

    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TreeItem;
    import javafx.scene.control.TreeTableColumn;
    import javafx.scene.control.TreeTableRow;
    import javafx.scene.control.TreeTableView;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class CustomedTypedTreeTableSample2 extends Application {
    
        public class TreeTableRowPlaylist extends TreeTableRow<TreeTableRowPlaylist> {
    
            // Initialized during constructor
            private SimpleStringProperty name;
    
            public String getName() {
                return name.get();
            }
    
            public void setName(String name) {
                this.name.set(name);
            }
    
            public SimpleStringProperty nameProperty() {
                return name;
            }
    
            public TreeTableRowPlaylist(String name) {
                super();
                this.name = new SimpleStringProperty(name);
            }
        }
    
        private final class TreeItemPlaylist extends TreeItem<TreeTableRowPlaylist> {
            // private boolean isFirstTimeChildren = true;
    
            private TreeItemPlaylist(TreeTableRowPlaylist treeTableRowPlaylist) {
                super(treeTableRowPlaylist);
    
                addEventHandler(TreeItem.childrenModificationEvent(), this::childrenModification);
    
                // Same code in a 'anonymous' implementation :
    //          addEventHandler(TreeItem.childrenModificationEvent(), new EventHandler<TreeModificationEvent<TreeTableRowPlaylist>>() {
    //              @Override
    //              public void handle(TreeModificationEvent<TreeTableRowPlaylist> event) {
    //                  childrenModification(event);
    //              }
    //          });
            }
    
            private void childrenModification(TreeModificationEvent<TreeTableRowPlaylist> event) {
                if (event.wasAdded()) {
                    for (TreeItem<TreeTableRowPlaylist> item : event.getAddedChildren()) {
                        System.out.println("Node " + item.getValue().getName() + " has been added.");
                    }
                }
            }
        }
    
        private void printChildrenToConsole(TreeItem<TreeTableRowPlaylist> playlist) {
            System.out.println(playlist.getValue().getName());
            if (!playlist.isLeaf()) {
                playlist.getChildren().forEach(childPlaylist -> printChildrenToConsole(childPlaylist));
            }
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
            ///// TreeTableView basics
            // Tree table view
            TreeTableView<TreeTableRowPlaylist> tableView = new TreeTableView<>();
            // Column for the name
            TreeTableColumn<TreeTableRowPlaylist, String> nameColumn = new TreeTableColumn<>("Name");
            nameColumn.setCellValueFactory(param -> param.getValue().getValue().nameProperty());
            // Add column
            tableView.getColumns().addAll(nameColumn);
    
            ///// Dummy tree building
            tableView.setRoot(new TreeItemPlaylist(new TreeTableRowPlaylist("ROOT")));
            tableView.getRoot().getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf 1")));
            tableView.getRoot().getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf 2")));
            TreeItemPlaylist aTreeBranchA = new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Branch A"));
            aTreeBranchA.getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf A.1")));
            aTreeBranchA.getChildren().add(new TreeItemPlaylist(new TreeTableRowPlaylist("Playlist Leaf A.2")));
            tableView.getRoot().getChildren().add(aTreeBranchA);
    
            ///// Testing if overriding is working
            // button to get the list of children
            Button display = new Button("Print out children tree to console");
            display.setOnAction(event -> tableView.getRoot().getChildren().forEach(playlist -> printChildrenToConsole(playlist)));
    
            ///// Setting the GUI
            VBox vbox = new VBox(0, tableView, display);
    
            Scene scene = new Scene(vbox);
    
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    }