Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In javaFx, Why do we need to call "getChildren()" when we add an element to a window?

For example, when we add a new button to a pane, we need to write the following code:

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

Why do we need to call "getChildren()?" what does it even do? Why can't we say:

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

We're adding the button to the pane. We're not adding it to its children,

like image 585
Rongeegee Avatar asked Feb 01 '17 23:02

Rongeegee


2 Answers

The short answer is simply "you have to do it that way, because that's how the API was written". Of course, what you are probably really asking is why the API was written like that. I think that really amounts to two (related) questions. One is about the method name, and what that method does, and one is about the actual API design.

In general, in computing, it is often beneficial to reuse existing solutions to problems. (Knowing when it is beneficial to do this is something of an art, but in both the cases here it's pretty clearly a benefit.) In two somewhat different ways, this API design reuses existing solutions to well-known problems, and consequently makes it easier to understand and use for programmers who have encountered those solutions before.


The Scene Graph

For the big picture, think about the overall structure of a general user interface. There is a large container of some kind (think of the root of the scene in JavaFX), which contains various UI components. Some of those might be simple controls, such as buttons and labels, etc, and some of them might be other containers, which in turn contain various UI components. And some of those, of course, may also be containers, and so on. Without imposing some kind of structure, this could get complex and difficult to use.

To understand the structure, we make it abstract. There is a root node, which has a collection of zero or more other nodes. Each of those has a collection of zero or more other nodes, and so on. This is a very well known abstract structure in computing, called a "tree", which basically every computer programmer (no matter which language they program in) is familiar with. So if we think of it as a tree structure, because we are already familiar with that, we can make the complexity easier to work with. In order to think of it as a tree structure, we use standard names for the tree-like aspects. Each node (except the root node) has exactly one "parent" (the container to which it belongs): so you'll find a method in the Node class ("node" is another piece of terminology from tree structures) called getParent(), which gives access to the parent node (the container containing the current node). Similarly, UI elements (nodes) that contain other nodes have a method called getChildren() that returns a collection of all the nodes contained in the current node. The Oracle JavaFX tutorial has a section on the Scene graph describing this, with lots of nice diagrams and related code.

So, in short, the reason there is a method called getChildren() is because we think of the collection of all the things in the UI as a tree structure, and this method name describes exactly what that method does in the context of the collection of all the UI elements being a tree. The terms "node", "parent" and "child" are instantly recognizable to programmers with a little bit of experience, and help them to understand the overall structure and to work with it. They can basically instantly infer that getChildren() returns a list of all the UI elements immediately contained in that container (the container is the StackPane in your example).


API design of Panes (such as StackPane)

To think about the API design, think about all the things you might want to do in terms of manipulating what is contained in your StackPane. As you observe, you might want to add a new UI element ("node") to the StackPane, so you might want a method called add(...) that accepts a Node. But there are a whole bunch of other things you might need or want to do too:

  • Remove a node from the StackPane
  • Add a whole collection of nodes to the StackPane
  • Remove a whole collection of nodes from the StackPane
  • Remove all the nodes from (clear) the StackPane
  • Since the order of nodes in the stack pane is important (it defines the z-order, i.e. if the nodes overlap it defines which are drawn in front and which behind), you might want to insert a node at a specific position (in front of something but behind something else)
  • Remove the node at the "back", or at the front, or at some specified position
  • and many, many others

So the designers of the StackPane class could have just written all these methods, but there's a better way.

If you think about implementing StackPane, you need some way to keep track of the nodes (UI elements) it contains. The order of these nodes is important, so we must keep track of that, and we need to have a nice way to define all the functionality listed above. The authors of the StackPane class (or its superclass, Pane) could have built a data structure to do this from scratch, but one already exists in the standard libraries. A data structure that holds a collection of objects of some specific type, and keeps track of their order, is called a List1, and the List interface is one that every single Java programmer is familiar with.

So if you think of the actual implementation of the StackPane, you could define methods for all the functionality listed above in the stack pane itself. This would end up looking something (really it would be more complex than this, I just want enough here to make the point) like

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...
}

I think you get the point. All this code is not really doing anything; it is simply invoking behavior that is already defined. So instead of this bloated API, it's possible to provide exactly the same functionality to the user of StackPane simply by providing access to the list itself2:

public class StackPane {
    private final List<Node> children = new ArrayList<>();

    public List<Node> getChildren() {
        return children ;
    }

    // layout code omitted...
}

Now the class is much less bloated, the API is less bloated, and additionally, the user receives a List, which as pointed out earlier is a very well known type of object. The new programmer to JavaFX, assuming they have some (any) Java experience, will already be familiar with the API for List and will not need to learn much that is new in order to use it. So the programmer can now do (with code laid out unconventionally and the thoughts of the programmer inserted):

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

and we have a happy programmer who immediately knows the API, and is instantly productive on a first encounter with managing the scene graph in JavaFX, and consequently a happy boss who sees great productivity from the programming team.

So in summary, by simply exposing the List of child nodes, the API can expose all the functionality it needs for manipulating the contents of the StackPane, without adding a huge list of methods to the StackPane class and do it in a way that leverages the existing knowledge of every Java programmer.


Footnotes

  1. Actually, Panes require a little more functionality than is defined in List: they need a convenient way of knowing when the list changes, by nodes being added or removed (because the pane needs to recompute its layout when this happens). So the JavaFX team defined a subinterface of List, called ObservableList, which has all the functionality of List and adds functionality for "observing" the list.
  2. There is one thing that the JavaFX team will have had to think hard about here: is all the functionality defined by List appropriate for the collection of child nodes? If the List interface defined some methods that really didn't make sense here, it would probably be a bad idea to use this implementation, and the more bloated API suggested in the first code block might actually be a better choice.
like image 147
James_D Avatar answered Nov 15 '22 18:11

James_D


Instead of providing Individual methods for different operations on children like add(),addAll(), remove(), removeAll() .. etc.

It is simple to provide well known collection so that every operation supported by List<E> are available and it will make the class simple and less verbose. In this case javafx.scene.layout.Pane class stores the children in ObservableList<Node> which is a List<E>

like image 38
ravthiru Avatar answered Nov 15 '22 17:11

ravthiru