JavaFX GridPane – Why Attach Layout Properties to Components?

api-designguijavajavafxobject-oriented

I am currently learning JavaFX and I came across a very strange API for laying out GUI components. It is perhaps best explained by it's javadoc:

To use the GridPane, an application needs to set the layout
constraints on the children and add those children to the gridpane
instance. Constraints are set on the children using static setter
methods on the GridPane class:

 GridPane gridpane = new GridPane();

 // Set one constraint at a time...
 // Places the button at the first row and second column
 Button button = new Button();
 GridPane.setRowIndex(button, 0);
 GridPane.setColumnIndex(button, 1);

 // or convenience methods set more than one constraint at once...
 Label label = new Label();
 GridPane.setConstraints(label, 2, 0); // column=2 row=0

 // don't forget to add children to gridpane
 gridpane.getChildren().addAll(button, label);  

Applications may also use convenience methods which combine the steps
of setting the constraints and adding the children:

 GridPane gridpane = new GridPane();
 gridpane.add(new Button(), 1, 0); // column=1 row=0
 gridpane.add(new Label(), 2, 0);  // column=2 row=0

I am wondering what the purpose of having layout data be given in such a strange manner (ie. the first set of methods) rather than only offering the "convenience methods which combine the steps."

This seems odd for several reasons:

  1. Swing, the only other GUI library I know in detail has everything passed in as part of it's add() method in it's GridBagLayout which seems to be roughly equivalent to GridPane.
  2. It isn't obvious in what object the GridPane.setConstraints() method is actually storing it's data. I doubt it is in the actual component object as it wouldn't make much sense for every component to have member variables for a layout that may or may not be in use at any given time and the method doesn't have any reference to the specific GridPane in use so it can't add it there either.
  3. Other JavaFX layouts have something much more similar to what seems logical to me, giving layout data to the layout pane in the add method, with the BorderPane's setCenter(), setLeft(), etc. methods.

Best Answer

Regarding point 2: The constraints are stored in the Node that you add to the GridPane. Every Node (and every visible object in the scene graph descends from Node), has a HashMap called "properties".

Application developers can put arbitrary data in there if you wish, but each layout class also defines some properties that are used to control layout. So when you put a Node into a GridPane, if you want it to span multiple columns you set at "gridpane-column-span" property on the node, with the appropriate value.

Conceptually, I think of it a bit like CSS. You add a CSS class to a node, and that affects it's styling and layout based on where it is inserted into the scene graph (or document flow, for HTML). Similarly, you add a layout constraint to a node, and the layout component you insert the node into interprets those constraints when laying out it's child nodes.

I actually really like the model that JavaFX uses - any arbitrary node can contain any arbitrary layout constraint. It's up to the layout component to interpret them. That means you can create your own layout components to do custom layouts, and if necessary create your own custom layout constraints, and they will all work properly with any existing Node.

On point 3: the BorderPane is different in that there are very explicitly 5 "zones" into which you could place nodes. So I guess they gave it a "proper" API for convenience.