13

In C# I found a method that was pretty sweet that allowed you to get all the descendants and all of THEIR descendants from a specified control.

I'm looking for a similar method for JavaFX.

I saw that the Parent class is what I want to work with since it is the class from which all Node classes that bear children are derived.

This is what I have so far (and I haven't really found anything on google with searches like "JavaFX get all nodes from a scene"):

public static ArrayList<Node> GetAllNodes(Parent root){
    ArrayList<Node> Descendents = new ArrayList<>();
    root.getChildrenUnmodifiable().stream().forEach(N -> {
        if (!Descendents.contains(N)) Descendents.add(N);
        if (N.getClass() == Parent.class) Descendents.addAll(
            GetAllNodes((Parent)N)
        );
    });
}

So how do I tell if N is a parent (or extended from a parent)? Am I doing that right? It doesn't seem to be working... It's grabbing all the nodes from the root (parent) node but not from the nodes with children in them. I feel like this is something that's probably got an answer to it but I'm just asking the question... wrong. How do I go about doing this?

Will
  • 3,413
  • 7
  • 50
  • 107

6 Answers6

25
public static ArrayList<Node> getAllNodes(Parent root) {
    ArrayList<Node> nodes = new ArrayList<Node>();
    addAllDescendents(root, nodes);
    return nodes;
}

private static void addAllDescendents(Parent parent, ArrayList<Node> nodes) {
    for (Node node : parent.getChildrenUnmodifiable()) {
        nodes.add(node);
        if (node instanceof Parent)
            addAllDescendents((Parent)node, nodes);
    }
}
Hans Brende
  • 7,847
  • 4
  • 37
  • 44
  • 1
    Why the extra method though? What is wrong with keeping it all in one method? – Will Jul 28 '14 at 00:05
  • 1
    @Will It uses less memory because with the second method you don't have to create a new ArrayList every single time the method is called--you just add all the nodes to the same one. Just good programming practice. – Hans Brende Jul 28 '14 at 00:07
  • @Will the performance is also faster because you avoid the many `arraycopy` operations that would occur if you were constantly merging lists together. Again, just good programming practice. – Hans Brende Jul 28 '14 at 00:14
  • _performance is also faster_ Did you measure that? If not, it's nothing but a wild-wild guess :-) – kleopatra Jul 28 '14 at 14:30
  • 9
    @kleopatra You don't have to perform any testing to realize that n `add` operations + m `arraycopy` operations + m `new ArrayList()` operations (m > 0) will take longer to perform than n `add` operations + 0 `arraycopy` operations + 0 `new ArrayList()` operations, ceteris paribus. I'm sure it wouldn't make much of a difference at all unless you were working with huge numbers. But you're welcome to measure the difference yourself and then come back with something constructive to say :-) – Hans Brende Jul 28 '14 at 15:58
  • _unless you were working with huge number_ exactly :-) So your performance argument is either beside any point in ui or you can prove - by measuring - that the number of copies in a typical ui context is near-enough to huge that it matters (very like not). – kleopatra Jul 28 '14 at 17:20
1

I use this,

public class NodeUtils {

    public static <T extends Pane> List<Node> paneNodes(T parent) {
        return paneNodes(parent, new ArrayList<Node>());
    }

    private static <T extends Pane> List<Node> paneNodes(T parent, List<Node> nodes) {
        for (Node node : parent.getChildren()) {
            if (node instanceof Pane) {
                paneNodes((Pane) node, nodes);
            } else {
                nodes.add(node);
            }
        }

        return nodes;
    }
}

Usage,

List<Node> nodes = NodeUtils.paneNodes(aVBoxOrAnotherContainer);

This source code uses the references of the existing nodes. It does not clone them.

Georgios Syngouroglou
  • 18,813
  • 9
  • 90
  • 92
  • It will not find nodes inside a TitledPane even though they can have child nodes, as they are not Panes. But changing Pane to Region and using `getChildrenUnmodifiable()` instead of `getChildren()` seems to work – golimar Nov 28 '17 at 13:49
1

This seems to get ALL nodes. (In Kotlin)

fun getAllNodes(root: Parent): ArrayList<Node> {
    var nodes = ArrayList<Node>()
    fun recurseNodes(node: Node) {
        nodes.add(node)
        if(node is Parent)
            for(child in node.childrenUnmodifiable) {
                recurseNodes(child)
            }
    }
    recurseNodes(root)
    return nodes
}
Red wine
  • 162
  • 2
  • 11
0

I'd like to add to Hans' answer, that you have to check if parent is a SplitPane. Because SplitPanes have an empty list using getUnmodifiableChildren(), you'll have to use getItems() instead. (I do not know if there are other parents that do not provide their children via getUnmodifiableChildren(). SplitPane was the first I found...)

Michael Jaros
  • 4,586
  • 1
  • 22
  • 39
0

Unfortunately this won't get subnodes for most container components. If you try a TabPane as parent, you'll find no children, but you can find tabs in it with getTabs(). The same is with SplitPane and other. So every container will require a specific approach.

You could use node.lookupAll("*"), but it also doesn't look inside.

The solution could be a "Prototype" pattern - creating a meta class with common interface of getChildren() method, which is realized in subclasses - one for each type.

Approach example is given here.

Community
  • 1
  • 1
Zon
  • 18,610
  • 7
  • 91
  • 99
-1

This works for me:

public class FXUtil {

    public static final List<Node> getAllChildren(final Parent parent) {
        final List<Node> result = new LinkedList<>();

        if (parent != null) {

            final List<Node> childrenLvl1 = parent.getChildrenUnmodifiable();
            result.addAll(childrenLvl1);

            final List<Node> childrenLvl2 =
                    childrenLvl1.stream()
                                .filter(c -> c instanceof Parent)
                                .map(c -> (Parent) c)
                                .map(FXUtil::getAllChildren)
                                .flatMap(List::stream)
                                .collect(Collectors.toList());
            result.addAll(childrenLvl2);
        }

        return result;
    }

}