Understanding Layout

Scott Palmer swpalmer at gmail.com
Fri Dec 19 18:04:01 UTC 2014

Thanks Martin that helps a bit.

Here's a test case that reproduces the layout issue I was seeing. The
layout code is very basic and computation of the size is left to the base
class. The computed width is wrong.

The error seems to be that the compute* methods in SkinBase simply don't
apply.  I believe this is the issue I had with BreadCrumbBar in ControlsFX.
Would that be your evaluation as well?
It seems odd, since the compute* methods in Parent and SkinBase only look
at *managed* nodes in the calculation.  Doesn't that partially
contradict, "...we
can use layout bound's x, y already at compute* methods as this is
something not managed by Parent and may be set through relocate()
explicitly by the developer.", because managed nodes would be positioned by
the layout code.



On Fri, Dec 19, 2014 at 4:12 AM, Martin Sladecek <martin.sladecek at oracle.com
> wrote:
> Hi Scott,
> On 18.12.2014 22:49, Scott Palmer wrote:
>> Short Version:
>> Is there good documentation on how JavaFX performs layout? (Not specific
>> layouts, but layout in general.)
> Not an official documentation I'm afraid (at least I don't know of any),
> just the javadoc and a few blog posts:
> Amy Fowler made some short introduction about layouts here:
> https://amyfowlersblog.wordpress.com/2011/06/02/
> javafx2-0-layout-a-class-tour/
> I tried to explain some parts of the layout process that were confusing
> people from time to time (based on the JIRA issues that were reported to
> me):
> https://blogs.oracle.com/jfxprg/entry/the_peculiarities_of_javafx_layout
> (I'm sorry I never got to make part 2 :-( ).
>> Long Version:
>> I was looking into a layout issue in ControlsFX (BreadCrumbBar width is
>> reported incorrectly and it doesn't behave when right-aligned).
>> What I found is that Parent and SkinBase assume that layout has already
>> occurred on child nodes when computing the preferred width/height.  The
>> JavaDocs indicate this is a known assumption:
>>       * Calculates the preferred width of this {@code SkinBase}. The
>> default
>>       * implementation calculates this width as the width of the area
>> occupied
>>       * by its managed children when they are positioned at their
>>       * current positions at their preferred widths.
>> Parent.computePrefWidth(double height) javadocs contain the exact same
>> comment.
>> Note however that the Region class documents
>> Region.computePrefWidth(double
>> height) differently.  It simply states:
>>       * Computes the preferred width of this region for the given height.
>>       * Region subclasses should override this method to return an
>> appropriate
>>       * value based on their content and layout strategy.  If the subclass
>>       * doesn't have a VERTICAL content bias, then the height parameter
>> can
>> be
>>       * ignored.
>> Node.prefWidth(double height), which is what
>> Control.computePrefWidth(double height) delegates to if there is no
>> SkinBase, clearly states:
>>   "Returns the node's preferred width for use in layout calculations."
>> It's the "for use in layout calculations" part, combined with the fact
>> that
>> prefWidth relies on the layout having already happened, that confuses me.
>> Clearly layout is working in most cases, so this must all work out in the
>> general case.  But as I understand it layout happens top-down, so
>> constraints imposed by the parent container must be accounted for when
>> positioning and sizing the children.  That implies that the child nodes
>> are
>> not yet laid out when their preferred size is queried.
>> I fixed the issues I was having with BreadCrumbBar by overriding the
>> implementation of computePrefWidth(double height) with one that does not
>> care about the current X position of the child Nodes.  It still relies on
>> the layout bounds via Node.prefWidth(double height), and duplicates some
>> of
>> the calculations that an actual layout will do, but it worked to solve the
>> problems of right-aligned layout and a widthProperty that was clearly
>> lying.
>> I feel like I am missing something fundamental about how Layout works
>> though.
>> Is there documentation that explains the general layout algorithm and how
>> to make sense of the apparent circular dependency?
> The way it should work is that pref* min* max* methods
> should be able to compute the layout without the layout actually
> happening. This is basically a kind-of 2-pass top-down layout:
> take a root (or any other parent in a tree) you want to layout. First, the
> computation of size hints happens (and might be traversing down the tree).
> After this stage, you know the size hints of the root's children, but no
> layout happened yet.
> Now the layout happens and the children are given their sizes (which might
> not be equal to pref size), the layout is done recursively on the children
> (now they know their final size they have all the information).
> Theoretically, you could even cache the size hints in your skins (until
> some of their prerequisites change) and use them at any stage of the layout
> pass, even as the parent's sizes change during the layout.
> This approach also means that size hints cannot depend on their ancestor's
> size (which is something many people tried to do).
> But it seems you already understand all this well.
> Now the confusing parts:
> * Some default implementation look like they rely on the layout, but this
> is actually because they have no layout by default. Or more commonly, the
> layout does just the resize, not movement. This means we can use layout
> bound's x, y already at compute* methods as this is something not managed
> by Parent and may be set through relocate() explicitly by the developer. I
> see SkinBase does actually moves the children by default (to contentX,
> contentY) and yet still uses layout bounds in compute* methods. This might
> lead to a bug in certain cases IMO, but usually would work as the position
> does not change. Maybe some child's margin change could break this, I would
> have to play with it to find out.
> Probably one JIRA task here you could file would be to improve the javadoc
> so that it's clear what is managed by class and what not, so that the
> developer knows what is expected to be changed explicitly and is never
> changed from within the class.
> *Group (with autosize) is an exception and it's preferred size depends on
> children's layout bounds which depend on the actual layout (!). This might
> complicate the layout and brings a certain complexity to the whole layout
> mechanism, so I'm not particularly proud of this, but there was no other
> way to fix the problems we had with Group without braking
> backward-compatibility and actually the most important functionality of the
> Group - that is that it's layout bounds include the full bounds of it's
> children (inc. effects et al.), which means we can't compute it's preferred
> size based on the children's size hints, we really need to do the actual
> layout first.
> Hope it helps!
> -Martin

More information about the openjfx-dev mailing list