Understanding Layout
Martin Sladecek
martin.sladecek at oracle.com
Fri Dec 19 09:12:28 UTC 2014
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