JavaFX 1.3: Growing, Shrinking and Filling

Establishing dynamic sizing behavior is one of the trickier aspects of GUI layout.   For JavaFX 1.3, we’ve introduced the grow/shrink/fill layout preferences on Resizable nodes to support more sensible default resizing behavior of controls and containers out of the box.   And while this feature is at first hard to get your head around, once it clicks, it pays.

We have to credit this one to Stephen Chin (JFXtras), who proposed this concept from his Grid API, which we then generalized across the containers.  (Thanks, Stephen!)

Filling

Let’s take filling first.  A Resizable node’s fill policy determines whether the node should be resized to fill its assigned layout area or kept to its preferred size and aligned within the area.

The screen shot below shows two stages which illustrate the difference between no-fill vs. fill:

No Fill Fill

In 1.2, the fill policy was dependent on which container was used; Stack and Tile implemented filling while HBox, VBox, and Flow did not.  So if you threw some buttons into a Tile container (the example above) you would get the fill result (on RIGHT) where all the buttons expand to fill each tile.  However, if the buttons were placed in an HBox, they would remain their preferred size, regardless of how much space the Hbox had.

Turns out that whether or not a node should fill is more naturally determined by the node type, rather than its parent container.  For example, a Button typically does not want to fill, whereas a ListView often does.  So in 1.3 we’ve added getHFill()/getVFill() to Resizable to allow container and control classes to return appropriate values which are honored by the container parents.

Growing & Shrinking

When a container is resized larger or smaller than its preferred size, the question is what to do with that surplus or deficit space.  In 1.2 we simply left that differential space hanging, but what an application really needs is a mechanism to identify which nodes should absorb it.  We considered a number of API approaches (weights, span percentages, etc) but ultimately chose a priority scheme based on Stephen’s Grid API.

A container uses the grow and shrink priorities of its children to determine how it should distribute a surplus or deficit of space in circumstances where the children are competing for that space. Child nodes can specify one of the following grow/shrink priorities:

  • NEVER: Child’s layout area will never grow (shrink) when there is an increase (decrease) in space available in the container.
  • SOMETIMES:  If no siblings have a grow (shrink) priority of ALWAYS or those layout areas didn’t absorb all of the increased (decreased) space, then will share the increase (decrease) in space with other sibling layout area’s of SOMETIMES.
  • ALWAYS: Child’s layout area will always grow (shrink), sharing the increase (decrease) in space with other layout areas that have a grow (shrink) of ALWAYS.
Note: There is a bug in the javafx1.3 online api documentation where the docs for javafx.scene.layout.Priority enum is mysteriously missing (JIRA RT-8740), so the doc text is copied above.

Below is an example an HBox using its childrens’ grow/shrink priorities to adjust layout as it is resized larger and smaller:

Note that grow priorities apply to the layout area assigned to the node.  Whether or not a resizable node is sized beyond its preferred to fill that extra space is determined by its fill preference.     In the example above, the toggle-group has grow=ALWAYS but fill=false, so only it’s layout area grows, whereas the search-hbox has grow=ALWAYS but a fill=true, so it resizes to fill the larger area.

Shrinking is not symmetrical, however.   If a resizable node’s shrink priority causes it to lose space, it will be resized down to fit within that space.

Similar to filling,  we’ve added getHGrow()/getVGrow()/getHShrink()/getVShrink() to Resizable to enable controls and containers to express appropriate preferences for their node type.   See the table of Control Fill/Grow/Shrink defaults at the end of this post.

Not All Containers Support Grow/Fill

It only makes sense for containers to support these preferences when nodes are in contention for space, so HBox supports it horizontally, VBox supports it vertically, and Grid supports it in both directions.    For Stack, since child nodes share the same layout area, childrens’ grow/shrink priorities do not apply. And since Tile and Flow redistribute space by wrapping child nodes rather than resizing them, child grow/shrink priorities are also ignored. 

Overriding Fill/Grow/Shrink Defaults

As with the other Resizable preferences,  LayoutInfo can be used to override a node’s default fill/grow/shrink defaults.   In the HBox example pictured above, most of the defaults took care of the desired behavior, however we had to override the defaults for MyToggleBox and the “?” Stack:

HBox {
    spacing: 8
    nodeVPos: VPos.BASELINE
    content: [
        Button {
            // no grow by default, so ok
            text: "Today"
        }
        MyToggleBox {
            // no grow by default, so turn on grow
            layoutInfo: LayoutInfo { hgrow: Priority.SOMETIMES hpos: HPos.CENTER }
            items: [ "Day", "Week", "Month" ]
        }
        HBox {
            // grows/fills by default, so ok
            nodeVPos: VPos.BASELINE
            content: [
                Label { text: "Search:" },
                TextBox {}
            ]
        }
        Stack {
             // grows/fills by default, so turn off grow
             layoutInfo: LayoutInfo { hgrow: Priority.NEVER }
             content: [ Circle {..}, Text {..}  ]
        }
    ]
}

Container/Control Defaults & Theoretical Propagation

The proper default grow/shrink/fill values for Stack, HBox, and VBox should be the greatest value of their children.  In other words, if Stack has any child with grow=ALWAYS or fill=true, then the stack should report its own preference as ALWAYS/true; if it only contains non-Resizables (shapes, etc) then it should report its own grow/shrink as NEVER and fill=false. This propagation of layout preferences has great power for the punch, enabling automatic resizing behavior on deeply nested hierarchies with little-to-no layoutInfo customization…..if…only….

…it weren’t for a bug in 1.3 (RT-8240) where the containers all report grow/shrink=SOMETIMES, fill=true, regardless of their content.      This means that in 1.3, you’ll often see containers growing when their content doesn’t really want them to.  The workaround is to set LayoutInfo as needed to adjust that behavior.  Double sigh.

Following is the current set of Control defaults (derived from their skins), which we’ll certainly tune as we put more mileage on this feature:

Class hgrow vgrow hshrink vshrink hfill vfill
Button NEVER NEVER NEVER NEVER false false
CheckBox NEVER NEVER NEVER NEVER false false
ChoiceBox NEVER NEVER NEVER NEVER false false
Hyperlink NEVER NEVER NEVER NEVER false false
Label NEVER NEVER NEVER NEVER false false
ListView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true
PasswordBox NEVER NEVER NEVER NEVER false false
ProgressBar NEVER NEVER NEVER NEVER false false
ProgressIndicator NEVER NEVER NEVER NEVER false false
RadioButton NEVER NEVER NEVER NEVER false false
ScrollBar NEVER NEVER NEVER NEVER true* true*
ScrollView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true
Separator NEVER NEVER NEVER NEVER true* true*
Slider NEVER NEVER NEVER NEVER false false
TextBox SOMETIMES SOMETIMES* SOMETIMES SOMETIMES* true true*
ToggleButton NEVER NEVER NEVER NEVER false false
Preview Controls:
CheckBoxMenuItem NEVER NEVER NEVER NEVER false false
Menu NEVER NEVER NEVER NEVER false false
MenuBar SOMETIMES NEVER SOMETIMES NEVER true false
MenuItem NEVER NEVER NEVER NEVER false false
MenuButton NEVER NEVER NEVER NEVER false false
PopupMenu NEVER NEVER NEVER NEVER false false
RadioMenuItem NEVER NEVER NEVER NEVER false false
SplitMenuButton NEVER NEVER NEVER NEVER false false
ToolBar SOMETIMES* SOMETIMES* SOMETIMES* SOMETIMES* true* true*
TreeView SOMETIMES SOMETIMES SOMETIMES SOMETIMES true true

* = value depends on orientation

About these ads
This entry was posted in Uncategorized. Bookmark the permalink.

10 Responses to JavaFX 1.3: Growing, Shrinking and Filling

  1. Guido says:

    Wonderful job and very well explained!
    It’s a bit clearer for me now, thanks!

  2. Dean Iverson says:

    Great information, Amy. One note, the text in the paragraph below the grow/shrink diagram doesn’t seem to agree with the captions in the diagram (SOMETIMES vs ALWAYS). In this case, whether they are both SOMETIMES or both ALWAYS, the grow result will be the same anyway, right?

    Dean

    • Amy Fowler says:

      Dean – thanks for pointing out my mistake — I’ve changed to text to ALWAYS to match the diagram. This does bring up a subtle point about ALWAYS/SOMETIMES worth making…Note that the Control/Container defaults are either NEVER or SOMETIMES (never ALWAYS); this makes it easy for applications to easily change the balance of priorities — if an application overrides with ALWAYS, it will beat out any controls with a default of SOMETIMES. If an app wants to generally work ‘with’ the defaults, it’s better to override using SOMETIMES, that way the override won’t take a higher priority than the defaults. Which is why the sample source uses SOMETIMES instead of ALWAYS. I neglected to update the diagram to match, but hopefully it’s still clear.

  3. Dean Iverson says:

    That is subtle but good to know. We layout geeks are really enjoying your articles. Keep them coming, please! :-)

  4. Rafał says:

    Thanks for good articles about layout mechanism in javafx.
    Please, could you clarify problem described in:
    http://forums.sun.com/thread.jspa?threadID=5440654

  5. Pingback: JavaFX links of the week, May 31 // JavaFX News, Demos and Insight // FX Experience

  6. Pingback: Java desktop links of the week, May 31 | Jonathan Giles

  7. Pingback: JavaFX 1.3 Layout // JavaFX News, Demos and Insight // FX Experience

  8. Hello Amy,
    I’ve seen that the button ‘s label is not right order.
    According to java BorderLayout, the right one should be ‘East’ and the left one should be ‘West’ but your ‘no fill’ and ‘fill’ figure is showing exactly opposite..

    Thanks Narayan

  9. Amy Fowler says:

    @Narayan: I wish I could say I intentionally put that in there to see who was paying attention, but it was an inadvertent reversal of label strings. :-)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s