Hi John, This is not a solution for your specific problem (new children added without CSS being applied), but in general, I have run into similar issues many times. I find the JavaFX JavaDoc very valuable for most JavaFX application developers, but for lower-level library development, or for JavaFX core development, there are a number of things that are crucial but not in the javadoc.
One of the key things I often have to almost reverse engineer, is the exact flow that happens when a pulse is requested (e.g. Toolkit.java, QuantumToolkit.java and the Scene.ScenePulseListener). I personally don't think JavaDoc is the best place to address this, as it might confuse the app developers. I think the openjfx wiki would be a good place for this but it requires time and a good understanding (which requires even more time). The page at https://wiki.openjdk.org/display/OpenJFX/Graphics provides the skeleton that can be the basis for this. As usual, the problem is that time spent on this can not be spent in fixing bugs -- although in the medium/long term, having this improved semi-internal (or at least lower-level) doc would allow more developers to fix issues faster. - Johan On Sun, Sep 11, 2022 at 8:10 AM John Hendrikx <[email protected]> wrote: > 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 > > > > > >
