java - 在javaFx中,为什么在向窗口添加元素时为什么需要调用 “getChildren()”?

标签 java user-interface javafx

例如,当我们向 Pane 添加新按钮时,我们需要编写以下代码:

 StackPane pane = new StackPane();
 pane.getChildren().add(new Button("OK"));

为什么我们需要调用“getChildren()”?它甚至做什么?我们为什么不能说:
 StackPane pane = new StackPane();
 pane.add(new Button("OK"));

我们将按钮添加到 Pane 中。我们不会将其添加到其子项中,

最佳答案

简短的答案就是“您必须那样做,因为这就是API的编写方式”。当然,您可能真正要问的是为什么要这样编写API。我认为这实际上是两个(相关的)问题。一种与方法名称有关,以及该方法的作用,另一种与实际的API设计有关。

通常,在计算中,重用现有的问题解决方案通常是有益的。 (知道何时进行此操作是一种艺术,但是在这两种情况下显然都是有益的。)通过两种不同的方式,此API设计将现有的解决方案重用到了众所周知的问题上,从而使之成为现实。对于以前遇到过这些解决方案的程序员来说,更容易理解和使用。

场景图

从总体上看,考虑一下通用用户界面的整体结构。有一个大容器(包含JavaFX场景的根源),其中包含各种UI组件。其中一些可能是简单的控件,例如按钮和标签等,其中一些可能是其他容器,这些容器又包含各种UI组件。当然,其中一些也可能是容器,依此类推。如果不强加某种结构,这可能变得复杂且难以使用。

为了理解结构,我们将其抽象化。有一个根节点,它有零个或多个其他节点的集合。每个节点都有零个或多个其他节点的集合,依此类推。这是计算中众所周知的抽象结构,称为“树”,基本上每个计算机程序员(无论使用哪种语言)都熟悉该结构。因此,如果我们将其视为树结构,因为我们已经很熟悉它,那么可以使复杂性更易于使用。为了将其视为树结构,我们对树状方面使用标准名称。每个节点(根节点除外)都只有一个“父”(它所属的容器):因此您会在Node类中找到一个方法( getParent() ,这是树结构中的另一种术语),可以访问父节点(包含当前节点的容器)。类似地,包含其他节点的UI元素(节点)具有一种称为getChildren()的方法,该方法返回当前节点中包含的所有节点的集合。 Oracle JavaFX教程在Scene graph上有一节对此进行了描述,其中包含许多漂亮的图表和相关代码。

简而言之,之所以有一种名为getChildren()的方法,是因为我们将UI中所有事物的集合视为树结构,并且此方法名称准确地描述了该方法在所有集合的上下文中的作用UI元素是一棵树。具有一点经验的程序员可以立即识别术语“节点”,“父”和“子”,并帮助他们理解整体结构并与之合作。他们基本上可以立即推断出getChildren()返回该容器中立即包含的所有UI元素的列表(该容器在您的示例中为StackPane)。
Pane的API设计(例如StackPane)

要考虑API设计,请考虑操作StackPane中包含的内容,您可能想要做的所有事情。如您所见,您可能想向StackPane添加一个新的UI元素(“节点”),因此您可能想要一个称为add(...)的方法来接受Node。但是,您可能还需要或想要做很多其他事情:

  • StackPane删除一个节点
  • 将整个节点集合添加到StackPane
  • StackPane
  • 中删除整个节点集合
  • 从(清除)StackPane
  • 中删除所有节点
  • 由于堆栈 Pane 中节点的顺序很重要(它定义了z顺序,即,如果节点重叠,它定义了哪些绘制在前面和后面,那么您可能需要在特定位置插入一个节点(在前面的东西,但后面的东西)
  • 删除“背面”,前面或某个指定位置处的节点
  • 和很多其他很多的

  • 因此StackPane类的设计人员可能已经编写了所有这些方法,但是有更好的方法。

    如果考虑实现StackPane,则需要某种方式来跟踪其包含的节点(UI元素)。这些节点的顺序很重要,因此我们必须对其进行跟踪,并且需要有一种很好的方法来定义上面列出的所有功能。 StackPane类(或其父类(super class)Pane)的作者可能已经构建了一个数据结构来从头开始执行此操作,但是标准库中已经存在一个数据结构。拥有某种特定类型的对象的集合并跟踪其顺序的数据结构称为 List 1,并且List接口(interface)是每个Java程序员都熟悉的接口(interface)。

    因此,如果您想到StackPane的实际实现,则可以在堆栈 Pane 本身中为上面列出的所有功能定义方法。最终看起来像什么(真的会比这更复杂,我只想在这里指出这一点)就像
    public class StackPane {
    
        private final List<Node> children = new ArrayList<>(); // or some other list implementation...
    
        public void add(Node node) {
            children.add(node);
        }
    
        public boolean remove(Node node) {
            return children.remove(node);
        }
    
        public void add(int index, Node node) {
            children.add(index, node);
        }
    
        public boolean remove(int index) {
            return children.remove(index);
        }
    
        public void addAll(Collection<Node> nodes) {
            children.addAll(nodes);
        }
    
        // lots more methods like this...
    
        // lots of layout code omitted...
    }
    

    我认为你说对了。所有这些代码实际上并没有做什么。只是调用已定义的行为。因此,可以使用列表本身的访问权限2,而不是这个this肿的API,而是可以向StackPane用户提供完全相同的功能:
    public class StackPane {
        private final List<Node> children = new ArrayList<>();
    
        public List<Node> getChildren() {
            return children ;
        }
    
        // layout code omitted...
    }
    

    现在,该类的工作量大大减少了,API的工作量大大减少了,此外,用户还收到了List,正如前面所指出的,这是一种非常知名的对象类型。假设他们具有一定的Java经验,那么JavaFX的新程序员将已经熟悉List的API,并且无需学习太多新知识就可以使用它。这样,程序员现在可以执行操作(使用非常规方式放置代码,并插入程序员的思想):
    StackPane pane = new StackPane();
    
    Button button = new Button("OK");
    
    pane.getChildren()
        // oooh, a List, I know how those work
        .add(button);
    
    List<Label> lotsOfLabels = Arrays.asList(new Label("One"), new Label("Two"), new Label("Three"));
    
    pane.getChildren()
        // still a list, I know more things I can do here:
        .addAll(lotsOfLabels);
    
    pane.getChildren().remove(button);
    
    pane.getChildren().clear();
    

    我们有一个高兴的程序员,他立即了解API,并且在第一次接触JavaFX中的场景图时立即产生了生产力,因此,一个高兴的老板也看到了编程团队的高生产力。

    因此,总而言之,通过简单地公开子节点的List,API可以公开处理StackPane内容所需的所有功能,而无需在StackPane类中添加大量方法并以利用每个Java程序员的现有知识。

    脚注
  • 实际上,Pane所需要的功能比List中定义的要多:它们需要一种方便的方式来了解列表何时更改(通过添加或删除节点)(因为发生这种情况时 Pane 需要重新计算其布局)。因此,JavaFX团队定义了List的子接口(interface)ObservableList,它具有List的所有功能,并添加了“观察”列表的功能。
  • JavaFX团队在这里必须认真考虑一件事:List定义的所有功能是否适合于子节点的集合?如果List接口(interface)定义了一些在这里实际上没有意义的方法,那么使用此实现可能是一个坏主意,并且在第一个代码块中建议使用的更为膨胀的API实际上可能是一个更好的选择。
  • 关于java - 在javaFx中,为什么在向窗口添加元素时为什么需要调用 “getChildren()”?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41991574/

    相关文章:

    objective-c - 为什么设置我的导航栏背景图片会影响导航栏的大小?

    java - 基于外键的增量字段(JPA、Hibernate、Oracle DB)

    java - Spring web 套接字和 Stomp 日志中的 "No decoder for session id"

    python - 在 GUI 中嵌入窗口

    c++ - Windows 32 API : 2 Checkboxes acting as one?

    java - 动态更改 ListView 中单元格的背景

    java - 如何更改 JavaFX 中 PathTransition 上节点的起始位置?

    arraylist - Javafx - 使用数组列表填充表格 View 而不添加新列

    java - Android 的替代 XML 解析器实现

    java - 在 Azure 应用服务中运行 Wildfly,如何配置 JGroups 进行 session 复制