LayoutChildren documentation

John Hendrikx john.hendrikx at gmail.com
Sun Sep 11 06:08:36 UTC 2022


TLDR; I think layoutChildren should mention it is not a good practice to 
modify the list of children during the layout pass as CSS won't be 
applied to them for 1 rendered frame, and perhaps mention a solution 
(Scene#addPreLayoutPulseListener?)

I've been recently bit by an issue that I think might need to be 
documented a bit better.

LayoutChildren is typically overriden for custom layouts.

It's documentation:

      * Invoked during the layout pass to layout the children in this
      * {@code Parent}. By default it will only set the size of managed,
      * resizable content to their preferred sizes and does not do any node
      * positioning.
      * <p>
      * Subclasses should override this function to layout content as 
needed.

A custom layout can be simple, but sometimes may involve adding or 
removing children dynamically based on the content of the control. What 
layoutChildren here does not mention is that if you add children during 
layoutChildren, there will be 1 rendered frame where these children 
don't have any CSS styling applied. The system detects however that 
children were modified and layoutChildren will be called again (in the 
same pass AFAIK), resulting hopefully in a stable set of children.

The problem here is the fact that a frame is rendered without any CSS 
applied. For applications using a black background in general, the new 
children with a white background will cause a temporary "white flash" to 
be rendered. It took me ages to figure out where it came from, and it 
wasn't always easy to reproduce (if the children already existed, they 
were just modified, it only happened for cases where new children had to 
be added during the layout pass and which were clearly visible somewhere).

Obviously, changes to CSS files had no effect on solving this (like 
setting everything to have a black or transparent) background at ".root" 
level.

Calling #applyCSS on the newly added children during #layoutChildren 
does not solve the problem. Still one frame is rendered without CSS, and 
a white flash occurs.

Adding the children during the #computeXXX methods is too late as well, 
the CSS pass has finished already by that time.  This does however avoid 
having #layoutChildren called again in the same pass if children are 
added. There is this issue that relates to this: 
https://bugs.openjdk.org/browse/JDK-8091873 where it is suggested to 
perhaps add a more general method for this.

What I needed is a callback for my control before layout occurs and 
before CSS is applied.

Scene has a #addPreLayoutPulseListener which mentions it is called 
before CSS is applied. Its #addPostLayoutPulseListener counterpart also 
mentions `AnimationTimer` as a way to get a callback before CSS is 
applied. Implementing this call back and changing the list of children 
there solved the "white flash" problem. It feels a bit overkill though 
to listen to every pulse on a Scene just for the layout of one control 
(that may not need layout during most passes).

Aside from changing the documentation to ensure people understand that 
modifying the list of children during layout can have temporary CSS 
issues, I'm wondering what would be the preferred solution to this 
problem. Is there a different way of handling this that I haven't found, 
aside from the #addPreLayoutPulseListener or AnimationTimer ?  If not, 
could the issue (https://bugs.openjdk.org/browse/JDK-8091873) perhaps be 
resolved by adding two methods?  One that is called before CSS is 
applied, and one before any compute methods are called?  If adding a 
method that is called before CSS, is there even a way to determine if a 
Node needs this call to prevent calling it on every Node on every layout 
pulse?

The current solution I'm using is like 15 lines of code as part of a 
ListView skin:

     private final Runnable pulseListener = () -> content.manageCells();

     private void updatePreLayoutPulseListener(Scene old, Scene current) {
       if(old != null) {
         old.removePreLayoutPulseListener(pulseListener);
       }
       if(current != null) {
         current.addPreLayoutPulseListener(pulseListener);
       }
     }

And in the constructor:

     updatePreLayoutPulseListener(null, skinnable.getScene());

     skinnable.sceneProperty().addListener((obs, old, current) -> 
updatePreLayoutPulseListener(old, current));

And in dispose:

     updatePreLayoutPulseListener(skinnable.getScene(), null);

--John







More information about the openjfx-dev mailing list