Layout in Java

Two factors conspired to land the client-side Java development community with a very difficult problem. The first was Java's 'write once, run anywhere' premise, the second was the commitment that the Java platform would use the underlying platform's widgets instead of defining its own. The advantage of the second commitment lay making it possible for Java programs to look like native applications. Its disadvantage was that it prevented Java from allowing layout tools to define layouts using fixed, pixel-based, distances as earlier toolkits had done.

The approach that Java's designers took to provide the abstractions that are necessary to avoid talking pixels was based on Donald Knuth's TeX's document formatting system. The basic idea is to allow each component to specify the size it would like to be and a cost for every possible deviation from the ideal.

Preferred Size

In Java, a layout manager finds out how much space a component requires by calling the component's getPreferredSize() method. The methods returns a Dimension object that defines a width and height.

This brings us briskly to the first of the fundamental problems with Java's approach.

The problem with setting preferred sizes

When using, for example, a JTextField in a form, a designer typically needs to specify the width of the space that should be made available for user input, but does not want to specify its height - because it will invariably change when the GUI is run on a different platform.

In this case, the preferred size of a component does not provide the independent control of height and width that the designer needs. As soon as cases like the one above are encountered, efforts to the control the layout with preferred sizes must be abandoned and supplanted with a more general mechanism that provides independent control along each axis. It is simpler to continue our analysis of the fundamentals of layout by assuming that this more general mechanism exists and that we will always use it instead of setting preferred sizes.

Minimum and Maximum Sizes

As well as preferred size, TeX put forward a notion of stretchiness in the 'boxes and glue' elements that made up the document. In Java, components specify their willingness to shrink and stretch by specifying minimum and maximum sizes respectively. Although the maximum size was introduced half-way through the evolutionary flourish of layout manager development (with the introduction of the LayoutManager2 interface), two layout managers treat maximum size in a way that is both consistent with TeX and easy to define. Those layout managers are BoxLayout and SpringLayout and they both use the difference between a component's preferred size and its minimum size to define how willing it is to shrink and the difference between a component's maximum size and its preferred size to define its willingness to stretch.

With this information then, we imagine layout managers can share out extra space, in a simply defined way that follows TeX. The details run roughly as follows:

If we have a two components in a row and they specify minimum, preferred and maximum widths of say: [0, 100, 200], [0, 100, 300] we would decide that an overall width of 350 pixels is most fairly distributed by allocating a width of 150 to the first component and a width of 200 to the second. We do this because these sizes put both components exactly half-way between their preferred and maximum values.

Algorithmically, we distribute space evenly by summing the minimum values, the preferred values and the maximum values to give three numbers that define the minimum, preferred and maximum values of the aggregate. If the desired size is lower than the preferred size of the aggregate we will shrink each of the components, otherwise we will stretch them. It is a simple mathematical fact that, if we set each of the components a fixed percentage (50% in the example above) between their preferred and maximum values, the size of the aggregate will be the same fixed percentage between the preferred and maximum values of the aggregate. This is true for all percentages, even negative ones, and so provides an elegant way to share out a given 'delta' between an arbitrary set of components.

Problems with setting minimum and maximum sizes

General problems

  1. If a component's preferred size is computed, we cannot set minimum and maximum values without fixing them to values that may not be appropriate to the different values a component's preferred size may take on different platforms and locales.
  2. Size characteristics belong to components and so cannot be used to control the space between components. Layout managers address this issue in different ways. SpringLayout and GroupLayout split a component's size requirements into a pair of springs that can then be used to control space as well as component sizes. BoxLayout uses the component model for space by creating a transparent Filler widget to characterize it.
  3. Lack of independent control over width and height - as discussed in the section on preferred sizes.

Problems with setting minimum sizes

In practice we never want a component's size to shrink below zero. So, if we expect the proportional scheme above to be in force, a component that wants to define a cut-off point below which it should not be shrunk needs to have its minimum size equal to its preferred size. Most of the simple awt/swing components, like JButton and JLabel, do this. With the minimum size essentially removed as a means of control, the maximum size of a component is left to control stretchiness.

Problems with setting maximum sizes

  1. Swing components tend to adopt different conventions for maximum sizes. JLabels and JButtons, for example, define a maximum size that is equal to the preferred size. JTextField's use the value Integer.MAX_VALUE where JSlider's use the value Short.MAX_VALUE.
  2. Very large values such as Integer.MAX_VALUE cause the proportional scheme to degenerate into one that shares space out equally rather than proportionally and require care to avoid overflow in layout calculations.
  3. Maximum values were not part of the original LayoutManager interface and are ignored by older layout managers, like GridBagLayout.

All in all, the problems listed above prevent tools from controlling layout management effectively using minimum and maximum sizes.

Summary: what to do with minimum, preferred and maximum sizes

A component's preferred size should be treated as a read-only property. Minimum and maximum sizes should be ignored.

Existing Layout Managers

Java 6.0 offers the following public layout managers for general use:

  1. null (absolute positioning)
  2. BorderLayout
  3. CardLayout
  4. FlowLayout
  5. GridLayout
  6. GridBagLayout
  7. BoxLayout
  8. SpringLayout
  9. GroupLayout
Of these, only those from GridBagLayout down are viable options to support the assembly of cross-platform forms of reasonable complexity. Here is a, far from exhaustive, list of popular layout managers written by third parties:
  1. TableLayout
  2. FormLayout
  3. MiGLayout

We will examine each of these in turn making use of the following example layouts.

Example: Form1

The first is a very simple form, with two JLabels and two JTextFields in the conventional layout used in forms.

Name:
Address:

Of this form we expect the following:

  1. Text in rows shall be aligned on baseline for all fonts on all platforms.
  2. The first column shall be right-aligned and take the width of the longer of the two labels.

Example: Form2

The next form set out here is a set of three JLabel components each with bounds around its text.

one small step for man
stumble a bit
then take one  giant leap for mankind
  1. Rows 1 and 2 shall be right aligned
  2. Rows 2 and 3 shall be left aligned
  3. The layout manager shall observe 1 and 2 and also handle the possibility of row 2 expanding, on internationalization, to be wider than either of the others.

In all cases the layout manager must compute the correct container bounds and component locations so that components fit the bounds of the container and are aligned as above.

None of the layout managers listed above can support this operation! What is worse is that the majority of the layout mangers allow it to be defined and fail unpredictably when it is.

GridBagLayout

GridBagLayout has a number of open bugs that have been in the bug database for over 10 years. That said, base-line support was added for SDK 6.0.

As of SDK 6.0, GridBagLayout can easily support Form1. Form2 tickles several bugs in the algorithm GridBagLayout uses to deduce the size of its columns. Its algorithm is dependent on the order that the components are placed in the container and no order produces the correct results.

The biggest downsides to GridBagLayout are the steep learning curve required to configure the layout manager to achieve a given result - and the lack of a good tool free-form tool to hide this complexity from designers.

BoxLayout

BoxLayout cannot, on its own, support Form1 as it only supports a single row or column of components. By nesting components it is possible to use three BoxLayouts to support a vertical arrangement of horizontal rows. BoxLayout does not have baseline support however, so different fonts will not be aligned correctly if the fonts differ. More importantly, it is not possible to left-align the two labels in the first column as the relevant information was buried when the components were made into rows.

SpringLayout

SpringLayout also had baseline support added to SDK 6.0. It is possible to configure springs so as to support Form1 but, like GridBagLayout, creating the configuration is too fiddly to do by hand and few tools allow the designer to work without understanding the underlying complexity of the layout manager (JFormDesigner and GUIDE are possible exceptions).

SpringLayout also has a serious fundamental flaw that its author (who also wrote this article) realized only many years after adding the layout manager to the SDK. SpringLayout uses two operators sum and maxto group springs together in series and parallel respectively. SpringLayout cannot correctly layout Form2 because SpringLayout only works on a set of relationships that form a series-parallel graph. Form2 is not a series-parallel graph because there is no way to break the three components into a smaller set that is in parallel or series.

By hand it would be possible to use Spring's minus operator to ensure that the container would be of a size that is the sum of the widths of the first and last rows, minus the width of the second - but this would not correctly handle the case when the middle row became wider than the other rows. In this case SpringLayout would allow components to escape the borders of the container and so could not be considered a correct solution.

GroupLayout

GroupLayout was introduced to the SDK after SpringLayout and is 'informally derived' from it (the code for javax.swing.Spring was cut-and-pasted into the GroupLayout class as the private superclass of GroupLayout's Group class). Because of this GroupLayout has the same limitations as SpringLayout: relationships between components in a GroupLayout must form a series-parallel graph.

All configurations that have less than three components are series-parallel but Example 2 is just one of a set of configurations of three components that are not series-parallel. The chance of a random configuration of components forming a series-parallel graph goes down very quickly as the number of components in the configuration increases. In practice, during the construction of a non-trvial form in a free-form tool it is very uncommon indeed for all the intermediate steps to have the series-parallel property.

It also appears to very difficult to effect graceful degradation when problematic graphs are encountered during free-form layout. Matisse is prone to scattering components around the container randomly when this happens and this presents a significant adoption barrier.

Although some of these problems are rooted in bugs in the implementations of Matisse and GroupLayout that may one day be fixed, the anomalies associated with the series-parallel restriction are fundamental to the approach and provably impossible to overcome. Mitigation is possible either by discarding the parts of the topology that cause the problems or by nesting components into smaller groups in which the problem is less likely to occur, but both tactics come with their own set of drawbacks.

FormLayout

In both its API and its operation, FormLayout is similar to GridBagLayout and appears to offer the important features of tabular layout in a well crafted and less buggy implementation than GridBagLayout. One important caveat is that it predates the introduction of baseline support in the SDK and does not support baseline alignment in rows. Hopefully this will be added in a future release.

FormLayout aligns the components correctly in Example Form 2 but mis-calculates the size of the container and positions components outside its bounds.

General Comments about the future of layout on the Java platform

Users of Apple's Interface Builder have sworn by free-form layout since the late eighties. In the end it appears that it was the production of the Matisse GUI builder that did far more than any layout manager to sell the idea of free-form layout to the Java community. While both SpringLayout and GroupLayout were laudably motivated on the premise that the designer should be free to move components around unconstrained by the artifacts of a layout manager, this truism need not, with hindsight, have precluded a grid-based approach being used in the layout manager itself. The deep topological restrictions associated with SpringLayout and GroupLayout mean some new tactic is needed as their inability to scale to real user interfaces appears to preclude their adoption at the kind of levels enjoyed by tools such as Interface Builder and Visual Studio.

It now seems plausible that the ideal solution for Java might well come from its original tabular camp, with new tools that bridge the gap between what a layout manager needs to store and what a designer needs to see.


Philip Milne - 25 November 2008