JavaFX ListView Sizing

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.

Demonstration

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.

Short WindowsTall Windows

Click on each image to show a full-size version in a new window

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 VBox with two children.
  • The top child is a simple Text in A1-4, and the same Text wrapped in a TabPane in B1-4.
  • The bottom child is a ListView which should fill the entire rest of the window.
  • The “Height” shown at the top is the actual height of the ListView, i.e. getHeight.
  • The “prefHeight” is its preferred height used in layout calculations, i.e. getPrefHeight.
  • And “Vgrow” is the grow priority of the ListView within the VBox, i.e. VBox.getVgrow.

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)”.

Analysis

In the first column, only A1 (short) is correct. The default property values cause various layout failures in every other case.

  • A1 (tall) – the ListView stops growing at 400 pixels, leaving empty space.
  • B1 (short) – the ListView hides the TabPane entirely!
  • B1 (tall) – the ListView still partially overlaps the TabPane.

Obviously, 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…

Causes

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.

The 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.

Summary

Whatever the causes, the lessons for ListView users are clear enough.

  1. 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.

  2. To correctly have a ListView grow 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.

10 thoughts on “JavaFX ListView Sizing

  1. Pingback: JavaFX links of the week, November 25 // JavaFX News, Demos and Insight // FX Experience

  2. Pingback: Java desktop links of the week, November 25 « Jonathan Giles

  3. Vaughn Teegarden

    I am encountering the same problem, but with a twist. I have a VBox, which contains in order, a Label, a ListView, another Label, another ListView. Ideally, each listview would only grow to be as long as needed to fit its items. I have tried:

    Which results in two equally sized ListViews, each filling about half the vertical space, minus the label space. This is surprising to me, since I expected the entire VBox to be no bigger than the contents, but the ListViews expand vertically to fill the space.

    I then tried:

    Hoping the bottom ListView would expand to fill any whitespace. However, this squashes the top ListView to nothing. Setting a prefHeight=0 on both has no effect. Setting a prefHeight to any other value of course means it’s no longer dynamically sized.

    Any idea on how to make this work, or do I need to abandon ListView ?

    Reply
  4. Werner Lehmann

    Regarding the overlapping vbox children, I suspect the likely reason is that the combined pref-height of the vbox children would be too big to fit. In this case the vbox layouting must somehow distribute the available space to its children, shrinking them over their pref-height. Unlike vgrow there is no vshrink priority. This is a known gap. See

    [#RT-37309] [VBox, HBox, GridPane]: hshrink/vshrink layout hint similar to vgrow
    https://javafx-jira.kenai.com/browse/RT-37309

    Reply
  5. Stefan Gramsch

    Good analysis! Had the same issues with TreeView (which has the same strange fixed 400px height – welcome c&p code ;)). I fixed it by setting prefHeight to a high value like 1000.

    Reply

Leave a Reply