JavaFX supports automatic self-adjusting layout that resizes controls along with the containing window, but this may require some non-obvious changes to the controls’ default properties. The problem I recently ran into was automatically resizing a
ListView within a
VBox. This article describes the undesirable default behavior and its correction, based on JavaFX 2.2 in JDK 7. It might also prove useful for similar issues involving other containers.
I wrote a tiny JavaFX program, ListViewSizing.java, to demonstrate the problem and the various stages of its solution. The following screenshots were taken on Windows 8.1 at 120 DPI (125%). The layout issues exist independently of DPI settings, but I found they appear most obviously and conveniently at 120 DPI. Since we’re interested in window resizing, I took two screenshots of the same eight window (
Stage) configurations, with all windows in the second screenshot twice the height of those in the first.
What are we looking at? From left to right, we start with the unsatisfactory default property values (A1/B1), then change two different properties (A2-3/B2-3), until we achieve our desired layout behavior (A4/B4). Here’s the structure of each window:
- The (invisible) root node is a
VBoxwith two children.
- The top child is a simple
Textin A1-4, and the same
Textwrapped in a
- The bottom child is a
ListViewwhich should fill the entire rest of the window.
- The “Height” shown at the top is the actual height of the
- The “prefHeight” is its preferred height used in layout calculations, i.e.
- And “Vgrow” is the grow priority of the
In the following analysis, I’ll use “short” and “tall” to reference the windows as shown in the top and bottom screenshot, respectively, e.g. “A1 (short)” versus “A1 (tall)”.
In the first column, only A1 (short) is correct. The default property values cause various layout failures in every other case.
- A1 (tall) – the
ListViewstops growing at 400 pixels, leaving empty space.
- B1 (short) – the
- B1 (tall) – the
ListViewstill partially overlaps the
ListView’s default preferred height (
USE_COMPUTED_SIZE) doesn’t work too well. But merely setting it to zero is no solution, either. As the second column shows, the
ListView simply disappears in this case, with an actual height of two pixels.
In the third column, we leave the preferred height alone and instead set the
VBox grow priority to
ALWAYS. This fixes one case, namely A3 (tall) where the
ListView now correctly stretches to the bottom of the window. The faulty B3 cases with the hidden or overlapped
TabPane remain unchanged, though.
The fourth column combines a preferred height of zero with a grow priority of
ALWAYS – and now everything works. The plain
Text variant in A4 appears as before, but the
TabPane in B4 is finally properly cleared at both window sizes. In the tall versions, the actual
ListView height difference between B1/3 and B4 is small – 396 versus 392 pixels – but that was large enough to cause an ugly overlap.
Bonus Bug. You might try to encourage a
ListView to occupy the maximum available size by setting its preferred size to
Double.MAX_VALUE. Not only does this not work – it will lock up the application! JavaFX apparently actually tries to allocate a
ListView of infinite height…
First, where does the maximum height of 400 pixels in A1 (tall) come from? Digging through the JDK library
jfxrt.jar with Emmanuel Dupuy’s Java Decompiler, I found the method
computePrefHeight in class
com.sun.javafx.scene.control.skin.ListViewSkin which simply returns the constant value 400. The companion method
computePrefWidth takes that method’s result and multiplies it with 0.618033987. These values are clearly arbitrary but I’m not sure how to improve on them, since a
ListView by its very nature is not tied to some fixed or natural size. I think a
ListView should never supply a default preferred size in the first place.
ListView overlapping the
TabPane in the B rows is a tougher question. In the short samples, why does a largely empty
ListView completely hide a
TabPane with fixed text content? That looks like an error in the JavaFX layout algorithm to me. In the tall samples, the mysterious height of 396 pixels is just halfway between the correct height of 392 pixels and the hard-coded preferred height of 400 pixels. Was the layout algorithm trying to find a compromise between correct and preferred size? At any rate, the
TabPane evidently doesn’t push back hard enough in the internal layout calculations.
Whatever the causes, the lessons for
ListView users are clear enough.
The “computed” default value for the preferred size is useless. Either specify your own fixed size if you have one, or set the preferred size to zero. Otherwise it may cause undesirable interference with layout calculations.
To correctly have a
ListViewgrow with its container, look for the corresponding property on the container itself. If none exists you will have to directly bind the
ListView’s size properties to the container size.
Once you know what to do it’s simple enough. I do wish we’d get the obviously desirable and sensible behavior by default: grow to all available space not otherwise occupied, but no further. It would be great if that could be fixed in a future JavaFX version.