JavaFX 1.3: Autosizing Everywhere

Resizable nodes (Controls & Containers) generally know best how big they should be and they express this from the inside-out through their “preferred size”; as the node’s internal state changes (content, styling, etc), it’s preferred size usually also changes.

When a resizable node’s preferred size changes, rather than immediately resizing itself, it invokes requestLayout() to inform its parent that it wants to be resized on the next layout pass. The reason the resizing task falls to the parent (rather than the child itself) is because the parent may need to balance the use of real estate across its children and hence it needs to call the shots (if you have kids, you understand). And this is what we mean by “autosizing”; that is, the parent automatically sets the width and height of the child (usually to the child’s preferred size) during layout.

In 1.2, only Container parents performed autosizing, so if you placed your button or textbox inside a Group, CustomNode, or at the root of a Scene, and the text in the button changed, the button would not be automatically resized to accommodate the new text.    This has lead to endless confusion because controls behaved differently depending on their parent type.

In 1.3, autosizing becomes universal across all Parent subclasses:  Container, Group, CustomNode, and that secret parent hiding as the root of every Scene.

This change means you need to just learn one set of rules which apply consistently across the scene-graph:

  • Rule: Do not set a Resizable’s width/height variables directly;  if you need to explicitly set its size, use LayoutInfo to override it’s preferred size:

Example:

Label {
    layoutInfo: LayoutInfo { width: 100 }
}

And note that this applies only to Resizable children; non-Resizable nodes (shapes, Text, ImageView) are not affected by autosizing.

If for some reason you don’t want a Group to auto-size its resizable children, you can set autoSizeChildren to false, restoring the 1.2 behavior.

Universal autosizing puts huge pressure on all resizables to correctly report their preferred size, which leads to a second rule:

  • Rule:  When creating Resizable classes, always override getPrefWidth()/getPrefHeight() functions to return valid values.

Internally after turning on universal autosizing, we discovered many bugs in our Control classes where preferred sizes weren’t being returned properly.  So if your resizable node suddenly disappears in 1.3, check to make sure its preferred size isn’t 0×0!

Why CSS Fueled Autosizing

In 1.2, prior to our CSS-stylable version of Controls, we were able to initialize Controls and Containers to their preferred size during initialization. This meant that if you then threw one of these guys into a Group, CustomNode, or Scene, it would appear with the correct initial size, even though subsequent changes to its preferred size wouldn’t update since those parents didn’t autosize in 1.2.

With 1.3’s introduction of CSS-stylable controls, we can no longer prime the size of controls during initialization.  This is due to the hierarchical nature of CSS, where styles are applied lazily to nodes on the first scene pulse (which you can think of as the heartbeat that drives rendering of the scene-graph to the display), which means that Controls (and the parents that contain them) are oblivious to their preferred sizes at init time.   Universal autosizing kicks in during that first layout pass (but after styles are applied) and ensures that everything is sized properly (just once) before you see it.

  • Rule:  You cannot reliably measure the size of node branches that contain Controls until they are connected to a displayed scene.

But what if you need to get the size of something before it’s displayed?   This is certainly a valid need in some applications.  Well, recall that layout happens regardless of whether a node is visible or not, so make it invisible, add it to a visible scene’s content, and it will be autosized and you can measure it.  Not exactly obvious or elegant, but works until we create a better mechanism.

Posted in Uncategorized | 5 Comments

JavaFX 1.3: Taming the Layout Beast

In the midst of celebrating JavaFX 1.3‘s performance improvements and CSS-stylable controls, let’s not forget about changes aimed to tame the beast we call “layout”.   My JavaFX 1.2 layout blog covered the fundamentals of bridging our declarative, animating scene-graph with traditional GUI layout and here I’ll follow up with some layout guidelines and a series of blogs describing how the changes we made in 1.3 are expressly intended to make your life easier, even if at first, being human, you feel a little resistant to change.

Most of the core concepts in JavaFX layout (logical vs. visual bounds, resizable vs not, use of binding vs. containers) remain complex and there’s no getting around spending  time to understand them (not unlike 2D or CSS).   But the 1.3 API changes should make it easier to get better dynamic layout with less code and fuss and here I’ve rewritten my layout guidelines to take into account 1.3 and another year’s worth of experience in constructing apps:

  1. Use the Container classes (HBox, VBox, Stack, Flow, Tile, *Grid (preview))  (rather than binding or static layout) whenever possible, as they handle the complexities of dynamic scene-graph layout for you.
    1. If you need something custom, consider writing your own Container subclass with a procedural doLayout function rather than a bunch of complex binding expressions.
    2. Remember that containers lay out children regardless of their visibility bit, so if you don’t want an invisible child to be layed out, then bind managed to visible.
    3. Use of LayoutInfo to customize the layout of a node inside a container should be the exception rather than the rule, as in 1.3 containers and controls have more natural layout default behaviors.
  2. For Resizable nodes (Containers & Controls), do not set their width/height vars directly, as all parent types now automatically set those values (usually to the resizable’s preferred size) during layout.  If you need to directly control the size of a node, override it’s preferred size using LayoutInfo.
  3. For non-Resizable nodes (Shape, Text, ImageView) you are responsible for establishing their size by setting the appropriate geometric or content variables.
  4. When arranging nodes directly inside a Group, CustomNode, or Scene (vs. Container) follow these guidelines:
    1. position child nodes by setting layoutX/layoutY, leaving translateX/translateY for adjustments and animation.  Remember that these are offsets, so always use the formula:
      layoutX: bind finalx - node.layoutBounds.minX
      layoutY: bind finaly - node.layoutBounds.minY.
    2. Use binding to establish dynamic behavior, but be careful not to create circular dependencies.
  5. Use layoutBounds as the basis of all layout-related calculations and understand that it may not correspond to the node’s visual bounds on the screen:
    1. a Resizable’s layoutBounds will always be 0,0 width x height
    2. a shape’s layoutBounds will be the bounds required to enclose their geometry (including stroke), but does not include effects, clip, or transforms.
    3. a Text node’s layoutBounds are now the logical bounds based on the font’s height and the content width, including any white space (in 1.2 it was the tight visual bounds around the characters, excluding whitespace).
    4. A Group’s layoutBounds is the union of the visual bounds (boundsInParent) of all its children.
    5. if you want a node’s effect, clip, or transforms (or any animation Timeline or Transition thereof) to be factored into its layout, then wrap it in a Group.

And finally, I’ve rewritten almost all of the layout related documentation in 1.3, so take a look and send me feedback.  I’m still wrestling this beast to the ground.

* we reserve the right to change preview APIs when we move them into core and already anticipate changes to Grid to support more dynamic node placement.

Posted in Uncategorized | 11 Comments