如何在JavaFX中获取父节点下的所有节点?

13

在C#中,我找到了一个非常好的方法,可以让你获取指定控件的所有子节点以及它们的所有子孙节点。

我正在寻找一个类似的JavaFX方法。

我发现Parent类是我想要使用的类,因为它是所有带有子节点的Node类派生的类。

这是我目前的代码(我在Google上搜索“JavaFX从场景获取所有节点”等内容,但并没有找到很多信息):

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)
        );
    });
}
那么,如何判断N是父节点(或从父节点扩展)?我这样做对吗?它似乎不起作用...它只获取来自根(父)节点的所有节点,而不是其中具有子节点的节点。我觉得这可能有一个答案,但我可能是在问错误的问题...我该怎么做呢?

也许将标题从“场景”改为“父级”? - Lealo
请更改标题,因为Scene不是Parent。 - Árpád Magosányi
6个回答

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

1
为什么要额外的方法呢?把所有东西都放在一个方法里有什么问题吗? - Will
1
@Will 第二种方法使用更少的内存,因为每次调用该方法时无需创建新的 ArrayList -- 您只需将所有节点添加到同一个列表中即可。这是良好的编程实践。 - Hans Brende
@Will,性能也更快,因为您避免了不断合并列表时会发生的许多arraycopy操作。再次强调,这是良好的编程实践。 - Hans Brende
性能也更快。你有测量过吗?如果没有,那只是一种瞎猜而已 :-) - kleopatra
9
你不必进行任何测试即可意识到,与只有n个“add”操作的情况相比,n个“add”操作+ m个“arraycopy”操作+ m个“new ArrayList()”操作(m>0)需要更长时间完成,其他条件相同。除非你处理的是巨大的数字,否则这几乎没有什么区别。但是你可以自己测量差异,然后回来说些有建设性的话 :-) - Hans Brende
除非你正在处理大量的数字,否则就是这样 :-) 因此,你的性能论点要么与 UI 中的任何观点无关,要么你可以通过测量证明,在典型的 UI 上下文中,副本数量足够大以至于它很重要(很可能不是这样)。 - kleopatra

1
我使用这个,
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;
    }
}

使用方法,
List<Node> nodes = NodeUtils.paneNodes(aVBoxOrAnotherContainer);

这段源代码使用现有节点的引用,而不是克隆它们。

尽管TitledPane可以有子节点,但它不会查找其中的节点,因为它们不是Panes。但是将Pane更改为Region并使用getChildrenUnmodifiable()而不是getChildren()似乎可以解决问题。 - golimar

1

这似乎获取所有节点。(在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
}

0
我想要补充Hans的回答,你必须检查父级是否是SplitPane。因为SplitPane使用getUnmodifiableChildren()将返回一个空列表,你必须改用getItems()。(我不知道是否还有其他父级不通过getUnmodifiableChildren()提供其子级的情况。SplitPane是我发现的第一个...)

1
是的,有相当多的东西。包括菜单。这很混乱。 - Daniel Ziltener

0

不幸的是,大多数容器组件都无法获取子节点。如果您尝试将TabPane作为父级,则找不到子项,但可以使用getTabs()在其中找到选项卡。 SplitPane和其他组件也是如此。因此,每个容器都需要采用特定的方法。

您可以使用node.lookupAll("*"),但它也不会查找内部。

解决方案可能是“原型”模式-创建一个具有getChildren()方法通用接口的元类,在子类中实现-每种类型一个子类。

示例方法在这里给出。


-1
这个对我有用:
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;
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接