JavaFX layout, a silver lining?

  • Post category:Java / javafx / UI

As described in the previous post, I believe that JavaFX’s layout mechanism is not as good as it could (should) have been. Naturally it is one thing to complain, another to offer improvement suggestions, but the best is to provide alternatives. First there is MigPane, which is a very powerful layout manager, but it is also possible to ‘slap’ on a different API onto existing layout managers. And that is what is being attempted in the JFXtra’s drop-in replacement layout managers. These layout managers extend the existing one, but add a different style.

The basic idea is that instead of writing:

VBox lVBox = new VBox(5.0);
Button b1 = new Button("short");
lVBox.getChildren().add(b1);
VBox.setVgrow(b1, Priority.ALWAYS);

You can write:

VBox lVBox = new VBox(5.0);
lVBox.add(new Button("short"), new VBox.C().vgrow(Priority.ALWAYS));

A more elaborate example:

VBox lVBox = new VBox(5.0);
lVBox.add(new Button("short"), new VBox.C().vgrow(Priority.ALWAYS));
lVBox.add(new Button("medium length"), new VBox.C().vgrow(Priority.ALWAYS));
lVBox.add(new Button("a longer description in order to test things"), new VBox.C().vgrow(Priority.ALWAYS));
lVBox.add(new Button("margin 5 grow"), new VBox.C().margin(new Insets(5.0)).vgrow(Priority.ALWAYS));
lVBox.getChildren().add(new Button("old style"));
lVBox.add(new Button("margin 20 nogrow"), new VBox.C().margin(new Insets(20.0)));
lVBox.add(new Button("grow maxwidth 150"), new VBox.C().vgrow(Priority.ALWAYS).maxWidth(150.0));

Which results in the following layout:

At the time of writing VBox and HBox (jfxtras.labs.scene.layout) have been ‘extended’ and can be downloaded in the JFXtras 2.2-r5-SNAPSHOT. It is good to get feedback if this works and is an improvement, or not.

Update 2012-11-19:

GridPane now also has a replacement. Because GridPane already has constraints for row and column, the API only adds constraints for the nodes.

An example:

    GridPane grid = new GridPane()
		.withHGap(10)
		.withVGap(10)
		.withPadding(new Insets(0, 10, 0, 10));
    grid.add(new Text("Sales:"), new GridPane.C().col(1).row(0));
    grid.add(new Text("Goods and Services"), new GridPane.C().col(1).row(1).colSpan(2).rowSpan(1));
    grid.add(new Text("Goods 80%"), new GridPane.C().col(0).row(2).valignment(VPos.BOTTOM));

This Post Has 14 Comments

  1. Werner Lehmann

    I have to say the VBoxBuilder style is easier to read for my eyes.

    VBox lVBox = VBoxBuilder.create()
    .spacing(5.0)
    .children(new Button(“short”))
    .build();

    The only problem is with those static properties like VBox.vgrow. Can’t set those on a button I am adding at the same time. Since those properties are actually stored in a map on the particular node, a button in this case, it should be possible to support them in Button (etc) builder:

    ButtonBuilder.create()
    .text(“short”)
    .property(VBox.VGROW, VBox.ALWAYS)
    .build();

    1. Tom

      I agree that the builder pattern fixes a lot of the API problems, but having to set a VBox constraint in the ButtonBuilder to me feels totally wrong.

  2. Mikael Grev

    Walter, there has been CC-like classes in MigLayout/MigPane since 2.0 (I think). It is a complete alternative to strings and fully type safe. I think about 80% use the strings though.

    1. tbeernot

      Personally I prefer the CC, especially the code completion.

    2. Walter Laan

      I know there is, what I meant is that the new CC() part is ugly like the VBox.C() here and probably part of the reason why most prefer the String constraint option.
      By returning the constraint the programmer doesn’t have to explicitly create it and looks quite natural to me, for example with MigPane:

      MigPane pane = new MigPane();
      pane.add(firstNameLabel);
      pane.add(firstNameTextField);
      pane.add(lastNameLabel).gapLeft(“unrelated”); // not fully type save 😛
      pane.add(lastNameTextField).wrap();
      pane.add(addressLabel);
      pane.add(addressTextField).spanX().grow();

      1. tbeernot

        It certainly has merrits, personally I’m not a fan of deviating from the common pattern, but -as said- if more people think this is a good API, I’ll happily add it.

  3. Walter Laan

    Why not let add() just return the layout constraints builder/object?
    lVBox.add(new Button(“grow maxwidth 150”)).vgrow(Priority.ALWAYS).maxWidth(150.0);

    1. tbeernot

      Also a suggestion with certain merrits, but not an approach that is very common in such a scenario. If you look at other layout frameworks, mostly you provide an layout object as a parameter.

      Other drawbacks are that when the returned constraint object is not modified, you do not know for certain if the user meant it that way, or simply forgot to set its properties. To confirm this, you would need a closing method.

      lVBox.add(new Button(“grow maxwidth 150″)).done();

      lVBox.add(new Button(“grow maxwidth 150″)).vgrow(Priority.ALWAYS).maxWidth(150.0).done();

      And that is ugly, in my opinion.

      However, if more people feel this to be a good API, then there is no harm in having add return an object.

      1. Walter Laan

        Not as ugly as new VBox.C(), which would also need a done() method by your reasoning.
        Technically it’s not needed I think if the layout allows constraint modification after adding the component (not sure if that holds for JavaFX).

        Passing a constraint object as parameter is logic on one hand, but then you don’t have a natural starting point for creating you constraints object. MigLayout and others solve this by allowing Strings that are parsed to a constraints object, but I don’t like that because it not type safe and you need a cheat sheet if you don’t know all the options.

  4. Tom Schindl

    That looks nice. What I find a bit strange is the new VBox.C(), you can get rid of the new if you’d make C() a static factory method on VBox.

    Your code look like this then:
    lVBox.add(new Button(“short”), VBox.C().vgrow(Priority.ALWAYS));

    BTW to avoid name clashes wouldn’t it be better to give it a different name than the original class.

    1. tbeernot

      Hm. There are merrits for both suggestions, I agree, but also some arguments against.

      The factory method indeed saves the “new “, but it also makes it a little less clear what is going on. If I had put the constraints in a separate class “HBoxConstraints”, would you have suggested a factory method as well? Anyhow, if there are more proponents the method can easily be added, and ppl can still use the constructor as well.

      About name clashes; Jonathan suggested the same. But because of easy adoption there is also a merit to be able to simply global-replace the import statements. And, since it extends JavaFX’s HBox, it still is100% HBox. I agree that “HBoxPlus” or something would indicate the usage of a different class better. Trade off. Matter of taste.

  5. Aleix

    I agree with you that the easier, the best, but when you need to elaborate a true big business UI, I think that I will write UI code by hand, so I have SceneBuilder that generates code for me. As a business programmer I will never choose a technoogy without a GUI Builder for my daily work. As in swing we have the great Netbeans Matisse GUI Builder.

Leave a Reply to tbeernot Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.