-1

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?) :

  • Commenting a part of the code. Then run. Then fire the button --> Code COMMENTED

  • Un-commenting that same part. Then Run. Then fire the button --> enter image description here

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 :

  • There is something missing on the TreeTableView : CellValueFactory / CellFactory / TreeItems / ...
  • There is something wrong about my ObservableList : Another override I should implement / Wrong ascendant used / Should 'implement' and 'extend' rather than 'extend' ?

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);
    }

}
Daric
  • 83
  • 9
  • 2
    Why are you trying to do this? You can edit the question with your explanation. Also, normally it is best to provide an [mcve] but in this case, even if provided a complete compilable example, I wouldn't expect that you would get a lot of assistance without a really compelling reason for attempting this. – jewelsea Oct 26 '21 at 10:51
  • 1
    mipa already provided a sound strategy in his [prior answer](https://stackoverflow.com/a/69706456/1155209), why not follow that strategy instead? i.e. "Is overriding JavaFx TreeItem getChildren add a good idea?" -> probably not. – jewelsea Oct 26 '21 at 10:56
  • Hello @jewelsea, thank you for asking. My other quesiton is not well formulated. Tried to delete it but impossible : the wraping proposed (which I use from the beginning, as you can see in my comments of the question) is a workaround, not a proper solution, of what I investigated. I want to do better and improve my code, using inheritance. I thought hat would make the quesiton heavier, so I went straight to the point. I will edit and contextualize it. – Daric Oct 26 '21 at 12:09
  • Fair enough, I hadn't downvoted it, and upvoted it for your effort. I do think my previous comments provides some suggestions on how the question may be improved. I'd say if the reasoning was obvious simply asking the question without context is OK, but in this case context is needed, it does appear a bit like a [xy problem](https://xyproblem.info/), and context can help clear up if that is the case or not. Votes are often on whether or not the question may be useful for others, not just the asker, people often upvote because it is useful to them. – jewelsea Oct 26 '21 at 12:19
  • 1
    "I want to do better and improve my code, using inheritance." -> sometimes (often?) inheritance does not improve code. This is not really the place that such things are usually discussed in detail, but see: [Inheritance vs. Aggregation](https://stackoverflow.com/questions/269496/inheritance-vs-aggregation) for reference. That an experienced developer (mipa) looked at your example and recommended aggregation over inheritance, means that likely that aggregation is the preferred approach. But, as you know your problem domain best, it may be that your approach also has merit. – jewelsea Oct 26 '21 at 12:24
  • _There is something missing on the TreeTableView_ no. _There is something wrong about my ObservableList_ yes - your override is simply illegal: consider the children property as effectively immutable, you must not return an arbitrary list instead of the real children. And that's what you are seeing: with the overridden getChildren you are adding children to a list != treeItem.children (which is the property every collaborator - including the tree - is working/listening with/to). BTW: as to your first pic, that's expected because you are recursively printing a child's children :) – kleopatra Oct 28 '21 at 12:53

1 Answers1

1

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();
    }
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
Daric
  • 83
  • 9