Layouts

The development of user interfaces in Java typically involves dealing with layout managers. This means that components cannot easily be positioned using absolute coordinates and sizes, but the layout manager is responsible for placing the components on the screen based on constraints the developer has defined. While this approach is very flexible and enables a Java application to cope with different platforms, it certainly does not simplify the creation of a good-looking and ergonomic GUI.

From the beginning, the Java development kit offered a number of layout managers. Most of them are pretty simple and do not support the creation of a sophisticated user interface out of the box. The idea was to nest multiple panels with different layout managers to achieve more complex layouts. This works to a certain degree, but if the UI becomes more complex, the required code for building the panels and nesting them properly also grows more and more complex; and if you want to rearrange something later, you are in a maintenance nightmare! In addition to these simple layout managers, the java.awt package provides the GridBagLayout class, which in principle allows arbitrary complex UIs. However, this layout manager is very hard to understand - this is probably the reason why it never becomes pretty popular.

With the PercentLayout layout manager the JGUIraffe library tries to find a compromise: a layout that should not be too difficult to grasp, but that is powerful enough for complex user interfaces. PercentLayout is similar to the popular FormLayout of jGoodies. It provides the basic layout functionality implemented within JGUIraffe. On top of this class there are a couple of other layout managers for special purposes or frequent use cases. In the following sections the basics of PercentLayout are covered. Then we discuss the other layout managers shipped with JGUIraffe and present a usage example.

Basics of PercentLayout

Similar to jGoodies' FormLayout PercentLayout organizes the UI in a tabular structure. The available space is grouped in columns and rows. Each cell in this table has certain properties that determine its size in both the X- and the Y-direction. Each cell can also host a component (e.g. a label, a checkbox, or a text field). Then its properties also define the component's size and alignment. For a better understanding take a look at the following figure:

Basic layout schema

This picture shows a layout consisting of 9 columns and 9 rows. The structure of the layout is pretty regular: It contains labels and text input fields. In most cases the labels are contained in the columns with the indices 1 and 5, the input fields are located in the columns 3 and 7. So, from the user's point of view, a UI results consisting of two columns with input elements. PercentLayout is well suited for such regular layouts as it allows defining the properties for the columns and rows globally. Line 5 is somewhat special: it contains a larger text area spanning multiple columns, and it is also higher than the other rows. This demonstrates that single components can be treated in a specific way. Now, which properties are available for defining a cell in the layout?

When defining a column or a row the following properties can be set (note that these properties are independent for the cell's width and height; so, for instance, a different alignment can be set for the width than for the height):

  • The cell size: This property determines how the preferred size of the cell is calculated. The size can either be derived from a component that is stored in this cell or it can be set to a fix value. The following values are possible:
    • PREFERRED: The size of the cell is determined by the preferred size of the component stored in this cell.
    • MINIMUM: The size of the cell is determined by the minimum size of the component stored in this cell.
    • NONE: The size of the cell does not depend on a component stored in this cell.
    For instance, column 1 in the example layout shown above has its width set to PREFERRED. This means that the preferred width of all label components is calculated. The total width of the column is the maximum of the widths of the single cells in this column. The columns 0, 2, 4, 6, and 8 do not contain components. They are used to define spacing between the components. Therefore their size is set to NONE, and they define a minimum size (see below, and also refer to the notes about shrinking at the end of this sub section).
  • A minimum size: Sometimes it makes sense to state that a cell must have at least a given size. This is especially useful for columns or rows acting as separators between GUI elements, e.g. the rows 0, 2, 4, or the columns 4, 6, 8 in the example figure. For this use case the cell size can be set to NONE, and the minimum size is set to the desired value. Another use case are components whose size is dependent on their content. For instance, a combo box that does not contain any entries or a text area with no text may be displayed very small per default. In this case the cell size can be set to PREFERRED, and additionally a minimum size can be specified. This means that the actual size of the cell is determined by the size of the component contained in it, but a certain minimum size is guaranteed.
  • A weight factor: By evaluating the cell size and the minimum size properties the layout manager can determine a preferred layout size. This size will be set initially for the container. The weight factor determines how additional space should be handled that becomes available, i.e. if the user enlarges the window, how should this space be distributed between the cells of the layout? PercentLayout evaluates all weight factors and assigns additional space to cells with a weight factor > 0 in proportion to these factors. In principle, arbitrary integer numbers can be used as weight factors. However, it is recommended to use numbers that sum up to 100. Then the weight factor can be interpreted as percentage rate of the additional space that is allocated to a cell. To make this clearer, take another look at the figure with the example layout. If the user enlarges the window, it makes sense that the columns containing text fields (i.e. the columns 3 and 7) grow accordingly. So we can set the weight factors of these columns to 50. Or maybe the text fields in column 7 are more important than the ones in column 3? Then we could set the weight factor of column 7 to 67 and the one of column 3 to 33. This means that 67% of additional space is assigned to column 7, while only 33% is allocated to column 3 (provided that all other weight factors are set to 0). By the way: The interpretation of the weight factors as percentage rate is the reason for the name PercentLayout.
  • A cell alignment: After the size of a cell has been determined the next question is how to position the component in this cell. This is controlled by the cell alignment property. It can take the following values:
    • FULL: The component takes the full space of its cell. This makes sense for instance for columns with text fields. Here the text field should fill the whole width of its cell.
    • START: This alignment means that the component in this cell keeps its preferred size. It is positioned at the left (for horizontal alignment) or top (for vertical alignment) margin of the cell. If the cell is larger than the component, there is unused space at the right or bottom of the cell. A use case for this alignment are columns with labels: They can be aligned to the left of their cell. The following image shows an example:
      Layout with left alignment
    • END: The END alignment is very similar to START, with the difference that the component is positioned to the right or the bottom of the cell; unused space is at the left or the top. Again columns with labels are a good usage example for this alignment if the labels should be right-aligned. Have a look at the following image for an example:
      Layout with right alignment
    • CENTER: The CENTER alignment also keeps the size of the component, but it is positioned in the middle of the cell. Unused space is distributed to equal parts at the left and right (or the top and the bottom respective) of the cell. CENTER is often a suitable alignment for rows containing single line text fields: Even if the row grows higher, there is no point in enlarging the height of the text fields as only a single line of text can be entered. Therefore the component should keep its size, but it can be placed at the center of the row.

In addition to the properties described so far, there is another aspect that influences the size of a cell: the so-called cell groups. A cell group combines multiple columns or rows with each other and ensures that they all have the same width or height. A use case for cell groups are columns with labels. In the example layout shown at the beginning of this section there are two columns with labels: column 1 and column 5. Per default the sizes of these columns would depend on the widths of the labels contained in them. So if column 1 contained only labels with short texts and column 5 contained labels with longer texts, the columns would have different widths. To avoid this a cell group can be defined that combines columns 1 and 5. This guarantees that these columns have the same width, i.e. the maximum width of both columns. The result certainly looks much better than a layout with different column sizes.

When defining a layout, often the preferred size of the components involved is used to determine the size of the layout. When the associated container is initially created the percent layout uses this information to calculate the container's size. This should result in an ideal appearance. We have already explained what happens if additional space becomes available: it is distributed over the layout's cells based on their weight factors. But what happens if the container becomes smaller?

The percent layout manager provides a boolean flag that determines its behavior in this case: canShrink. If this flag is set to false (the default value is true), the cells cannot become smaller than the size calculated based on the preferred sizes. If the user decreases the container's size more and more, parts of the UI are cut. This is shown in the following figure. As can be seen, parts of the lower right become invisible as the window becomes too small.

Layout that cannot shrink

Note: There does not seem to be a reliable way in Swing to enforce a minimum size of a window. Even if all components involved have a minimum size set, the window can be dragged smaller - with the effect that parts of the UI become invisible as demonstrated by the figure. For dialog windows that do not benefit from additional size (e.g. because only small input fields for numbers or dates are contained) it may be a good solution to disable sizing at all. However, if the dialog contains text areas, tables, or other scrollable elements, you do not want to do this.

If the canShrink attribute of a percent layout is set to true, the layout manager's behavior when calculating the cell sizes depends on the space currently available. If there is enough space, the calculation is performed as described above. If the space shrinks below this threshold, the layout manager acts as if the cell sizes of all cells had been set to minimum rather than preferred. This means that this time the minimum size of all components involved is queried. For components whose minimum size is equal to their preferred size, or for cells whose size has already been minimum, there is no difference. But other components and cells may be able to shrink. After determining the minimum sizes the layout manager checks whether the layout now fits in the space available or if even more space is available. In this case, the remaining space is again distributed over the cells with a weight factor. This approach allows the layout to shrink in a graceful way. As an example consider the following simple window that only contains a text area. It has a border layout, and the text area is added to the center.

Shrinable layout with preferred size

Because the text area has set the number of columns and rows to be displayed it has a preferred size. After opening the window this size is used. The figure shows the window in its preferred size. Now the window's size is reduced. Because the layout can shrink, the text area component becomes smaller, too, which is indicated by the scroll bars.

Shrinable layout with smaller size

When working with layouts that can shrink be careful with the components involved: All components with a preferred size larger than the minimum size should be added to cells with a non-null weight factor. Otherwise, the size of these components may suddenly jump from preffered to minimum when the user drags the window to become smaller. This is probably confusing.

Helper classes

After the concepts of the PercentLayout manager have been discussed it is time to have a closer look at the implementation classes. At first there are some helper classes that need to be introduced.

NumberWithUnit

When dealing with GUI programming the default unit for specifying sizes or gaps is typically pixels. This is pretty natural, however, the results of this approach may strongly depend on the users' screen resolution. This means that a layout may look good on one screen, while it is too small or too big on another one. To address this problem the PercentLayout layout manager supports multiple units for sizes or gaps.

All numeric values required for a layout definition are specified as instances of the NumberWithUnit class. As the name implies, this class stores a numeric value (as a floating point number) and an associated unit. It defines methods for converting this value into pixels, which is required for internal layout calculations. The supported units are defined by the Unit enumeration class. These are the following:

  • Pixels: Of course, pixels are supported.
  • Inches: It is possible to define numbers as inches. In this case the screen resolution (in dots per inch) is used for converting these values to pixels. This guarantees that the resulting layouts have the same sizes on different screen resolutions.
  • Centimeters: Like inches, but for the ones who prefer the metric system.
  • Dialog units: A dialog unit is a unit whose exact size depends on the font of the container it is used in. A horizontal dialog unit equals the fourth part of the container's font's average character width; a vertical dialog unit equals the eighth part of the character height. The advantage of this unit is that layouts can automatically adapt to certain system settings. For instance, if the user has enabled large fonts on his operation system, the layouts will resize correspondingly.

To create an instance of NumberWithUnit a double for the actual value and a constant of the Unit enumaration class have to be passed to the constructor. There is also a convenience constructor for pixels, which expects an int. Finally, there is a constructor that takes a string and parses it to a number with a unit. The string must start with a floating point number, followed by optional whitespace and the short name of the unit. Examples for valid strings are 10cm, 10 IN, 10 Dlu, 10px.

CellConstraints

The CellConstraints class is used for defining the properties of a cell (a column or a row) in the layout. An instance stores exactly the properties that have been discused in the last section: the cell size (provided as an instance of the CellSize enumeration class), the minimum size of the cell, the cell alignment (provided as an instance of the CellAlignment enumeration class), and a weight factor.

Instances of CellConstraints are not created directly, but a builder is used for this purpose. This makes the creation of instances pretty convenient as only the properties have to be defined that are needed. The following code fragment shows how the builder is used:

// First create the builder
CellConstraints.Builder builder = new CellConstraints.Builder();

// Now create instances
CellConstraints cc = builder.withCellSize(CellSize.MINIMUM)
                            .withCellAlignment(CellAlignment.CENTER)
                            .withWeight(50)
                            .create();
    

Basically the several with() methods of the builder are called to define the desired properties. With a call of the create() method the CellConstraints instance is actually created.

Instead of setting the properties of a CellConstraints instance step by step, an instance can also be defined using a specification string. This is a string that defines all possible properties in a compact form. The syntax is as follows:

<cell alignment>/<cell size>(<minimum size>)/<weight>

The alignment, the cell size, and the weight factor can be defined separated by slash ("/") characters. The alignment and the weight factor are optional, only the cell size must be defined. The strings are not case sensitive. The following strings are all valid specification strings:

start/preferred
Cell alignment is set to START, cell size is set to PREFERRED.
FULL/PREFERRED(1in)/50
Cell alignment is set to FULL, cell size is the PREFERRED size with a minimum size of 1 inch (as can be seen, it is possible to define a unit for the minimum size), the weight factor is set to 50.
preferred
Only the cell size is set to PREFERRED.
3dlu
Only a minimum size is set to 3 dialog units. This implies a cell size of NONE.
The builder for CellConstraints has a fromString() method that returns an instance for a valid specification string. For more options of CellConstraints have a look at the Javadocs of this class.

PercentData

After a layout has been created and configured the components to be managed have to be added. More complex layout managers require constraints objects that exactly specify how a specific component should be handled. For instance, the well-known BorderLayout layout manager from the java.awt package needs a name of a cell, in which the component is to be added (e.g. NORTH or CENTER), as constraints. The java.awt.GridBagLayout layout manager uses the GridBagConstraints class for this purpose. PercentLayout also defines its own class for layout constraints: PercentData.

A PercentData object has the following properties:

  • The position of the cell in the tabular-structured layout of the cell in which the component should be placed. This is the 0-based column and row index. This information is mandatory.
  • Per default a component is added to exactly one cell. In some situations, e.g. for longer text fields or text areas or for header lines, it makes sense that a component spans multiple columns or rows. PercentData allows defining the number of cells spanned by the component in X and Y direction.
  • If a component spans multiple columns or rows, it is not a priori clear, to which column or row the size of the component should be counted when calculating the minimum column or row size. In this case a so-called target column and row can be defined. These properties specify the column and/or row to which the component logically belongs. If these properties are not set, the size of this component is not taken into account when determining the minimum or preferred sizes for the layout's columns and rows.
  • Per default the cell constraints defined for the current cell are applied for the component. PercentLayout supports setting special CellConstraints objects for both the column and the row, which override the default settings. Using this feature special properties can be set for specific components.

To create new PercentData objects an analogous approach is used as for CellConstraints: There is a Builder class which allows setting of properties in a convenient way. A typical sequence for creating PercentData objects using the builder could look as follows:

// First create the builder
PercentData.Builder b = new PercentData.Builder();

// Now create instances
PercentData pd1 = b.xy(1, 2).create();
PercentData pd2 = b.xy(1, 3).spanX(2).withColumnConstraints(columnConstr).create();
    

The Builder class defines a bunch of methods that correspond to the properties supported by PercentData. The methods withColumnConstraints(), withRowConstraints(), withTargetColumn(), and withTargetRow() should be self-explanatory. With the xy() method the indices of the column and the row the component is to be placed in are specified. The number of columns or rows spanned by the component can be set either separately using the spanX() and spanY() methods or in a single operation using the span() method. Often a PercentData object will only be initialized with the column and row indices - these are the only mandatory properties. For this special use case the Builder class defines the convenience method pos(). pos() sets the indices and creates the new instance immideately. So the create() method need not be called.

CellGroup

The concept of cell groups has already been introduced. Cell groups allow the definition of columns or rows that should always have the same width or height. The corresponding API of PercentLayout makes use of the CellGroup class.

CellGroup is a pretty simple class. It basically manages a number of (0-based) indices for the columns or rows that belong to the group. Typically cell groups do not have too many elements. Therefore there are convenience constructors that accept 2, 3, and 4 indices - this should cover the most frequent use cases. If this is not enough, there is the static fromArray() method, which reads the indices of the group from an arbitrary array.

The static fromString() method allows initializing a CellGroup from a string representation. Valid strings contain the numeric indices separated by various supported separater characters, for instance:

  • 1,3,4
  • 4 3 1
  • 1, 2;3 / 4
As can be seen, many different separator characters are possible which can also be mixed. Later in the examples section we will present a code fragment that demonstrates how CellGroup objects are created and passed to a PercentLayout object.

Platform-independent layouts

The implementation of the PercentLayout manager is somewhat special because it does not implement a platform-specific layout manager interface. For instance, when working with Swing all layout managers must implement the java.awt.LayoutManager2 interface. The implementation classes of PercentLayout do not do this. Rather, they implement only the layout algorithm in a way that is independent on a concrete GUI library. For accessing platform-specific functionality (e.g. setting or obtaining the size of a component) an adapter is used. The functionality of this adapter is defined by the PercentLayoutPlatformAdapter interface.

The advantage of this approach is that the PercentLayout manager can easily be ported to different UI libraries. The base classes can remain unchanged; only a specialized adapter has to be written. For Swing there is already a fully functional adapter implementation: SwingPercentLayoutAdapter. SwingPercentLayoutAdapter also implements the java.awt.LayoutManager2 interface. When an instance is constructed it is passed the PercentLayout object it is associated with. Then it can be installed as a regular Swing layout manager, for instance:

JPanel panel = new JPanel();
PercentLayoutBase percentLayout = ...
SwingPercentLayoutAdapter adapter = new SwingPercentLayoutAdapter(percentLayout);
panel.setLayout(adapter);
    

The PercentLayout class

The layout functionality discussed so far is implemented by the PercentLayout class. To install a layout of this type an instance of PercentLayout has to be created and configured. Then a platform-specific adapter has to be associated with it as shown in the code fragment above.

PercentLayout provides three different constructors:

  • One constructor expects two collections with CellConstraints objects. These collections define the properties of the columns and the rows of the layout. The resulting layout has as many columns and rows as these collections contain elements.
  • There is another constructor that accepts two strings. It has the same meaning, but the CellConstraints objects defining the layout's columns and rows are specified as strings conforming to the format already discussed in this document. The first string contains an arbitrary number of CellConstraints specification strings for the columns of the layout separated by "," characters, the second string is analogous, but it defines the constraints for the layout's rows. This is a pretty compact way of defining the layout:
    PercentLayout layout = new PercentLayout(
        "3dlu, end/preferred, 3dlu, full/preferred(1in)/50, 7dlu, end/preferred, "
            + "3dlu, full/preferred(1in)/50, 3dlu",
        "3dlu, preferred, 3dlu, preferred, 3dlu, preferred, 7dlu, preferred, "
            + 3dlu, preferred, 3dlu, full/preferred(1in)/100, 3dlu");
        
    However, the variant with the collections is slightly more efficient as there is no need of string parsing.
  • Finally there is a constructor that only takes two integer values representing the dimensions of the layout (i.e. the number of columns and rows). This constructor leaves the constraints undefined. They can be specified later using the methods setColumnConstraints() and setRowConstraints() which allow assigning a contraints object to a single column or row. This constructor is mainly intended to be used by sub classes.

After an instance of PercentLayout was created further configuration can be performed:

  • As was already pointed out, single CellConstraints objects for columns or rows can be set using the setColumnConstraints() and setRowConstraints() methods.
  • CellGroup objects (see above) can be added using the addColumnGroup() and addRowGroup() methods. This determines the columns or rows which should have the same size.

After the configuration of the PercentLayout object, and with the PercentLayoutPlatformAdapter in place the layout can be used as any regular layout manager instance. This means that components can be added to a container using PercentData objects as layout constraints. We will show this later in the examples section.

BorderLayout

PercentLayout is the most generic and powerful layout implementation shipped with the JGUIraffe library. There are a couple of other layout manager implementations on top of PercentLayout for special use cases. One of these is BorderLayout

BorderLayout is very similar to the layout manager with the same name from the standard java.awt package. (More information about java.awt.BorderLayout can be found for instance in the Swing Tutorial.) It provides some additional functionality, e.g. the definition of margins and gaps between the components it manages. And, it is also written in a platform-independent way; so it can easily be ported to other GUI libraries which do not support this type of layout out of the box.

Using BorderLayout is simple. After creating a BorderLayout instance (which does not require any parameters) some configuration properties can be set using the following methods:

  • setLeftMargin() allows defining the left margin of the container. This is space on the left that remains unused.
  • setTopMargin() allows defining the top margin of the container. This is space on the top that remains unused.
  • setRightMargin() allows defining the right margin of the container. This is space on the right that remains unused.
  • setBottomMargin() allows defining the bottom margin of the container. This is space on the bottom that remains unused.
  • setNorthGap() defines the gap between the north component and the center.
  • setWestGap() defines the gap between the west component and the center.
  • setEastGap() defines the gap between the east component and the center.
  • setSouthGap() defines the gap between the south component and the center.
All these methods expect a NumberWithUnit object. So the margins and gaps can be specified in any of the supported units. The default is 0 for all margins and gaps (which conforms to the behavior of the classic java.awt.BorderLayout).

BorderLayout also needs a PercentLayoutPlatformAdpater instance. If this adapter has been installed, components can be added to the associated container in the usual way using the CENTER, NORTH, WEST, EAST or SOUTH constants of BorderLayout as constraints. These constraints define, in which of the managed cells the component is to be placed. We will see BorderLayout in action in the examples section.

ButtonLayout

Dialog boxes often have a bar with buttons at their botton defining the actions the user can invoke, e.g. Save, Cancel, or Help. Given the frequency of this use case, it is surprisingly difficult to create a corresponding layout using Java board means. A bunch of code is required to ensure that the buttons in the button bar are properly aligned and all have the same width. The ButtonLayout class is focused on this use case.

A ButtonLayout object can be instantiated using its default constructor. Then the following properties can be configured:

  • The margins around the button bar. These are defined using the methods setLeftMargin(), setTopMargin(), setRightMargin(), and setBottomMargin(). Again, the NumberWithUnit class is used for defining the margins.
  • The width of the gaps between the buttons using the setGap() method. The gap is also specified using a NumberWithUnit object.
  • The alignment of the button bar. Here a constant of the ButtonLayout.Alignment enumeration class can be passed to the setAlignment() method. The alignment determines whether the buttons should be aligned to the left, the right, or the center of the whole dialog.

As for the other layout classes covered so far a PercentLayoutPlatformAdapter is required to embed the layout into a specific GUI platform. Then buttons can be created and added to the container representing the button bar without passing additional constraints objects. They will be automatically added to the layout and placed accordingly. This is shown in detail in the examples section.

A complete example

In this section a dialog box using the layout classes discussed in this document is developed. We only focus on the code required to construct the UI; no additional functionality (e.g. for the handling of button clicks) is added. As GUI library we will set on Swing. The following figure shows the final dialog box:

Example layout

The example layout contains two (logic) columns with labels and input fields in the upper part. All labels have the same length. This is also true for the input fields. In the lower part there are longer input fields. Their labels, however, are aligned with the first labels of the upper part. There are also headings with a larger width.

As a general implementation strategy we will use a BorderLayout that divides the dialog box in a section with the actual input fields (this is a panel which is added to the CENTER) and a section with the buttons (another panel in the SOUTH). All components in the CENTER area can be arranged using a single PercentLayout object. For the buttons of course ButtonLayout is the appropriate choice. The following code shows how the BorderLayout is created and installed at the content pane of the dialog box:

// The layout of the content pane
BorderLayout borderLayout = new BorderLayout();
getContentPane().setLayout(new SwingPercentLayoutAdapter(borderLayout));
    

As the BorderLayout needs no special configuration, this code is very simple. We could have also set some of the margins, but in this example we use a properly setup PercentLayout to define the margins of the dialog box.

The main area of the dialog box that contains all labels and input elements is realized using PercentLayout. When working with PercentLayout at first the UI has to be devided into columns and rows; then the properties for these columns and rows have to be defined. For our example layout we use 9 columns for the following elements:

  • The first column defines the left margin of the dialog box. Note that margins and gaps are realized by additional columns and rows. Such cells typically only have a minimum width set.
  • The next column contains the labels for the input fields on the left side. Its width is determined by the labels; the labels are aligned to the right.
  • The next column defines the space between the labels and their input fields. Again only its minimum size will be specified.
  • The fourth column contains the input fields on the left side. Again the width of this column in determined by the components it contains, but this time we also specify a minimum width to ensure that the text fields do not become too small. The input fields should cover the whole space of the cell. If the user enlarges the dialog box, the additional space should be added to same parts to the two columns with input fields. So a weight factor of 50 has to be set.
  • Now a divider column follows that defines the space between the left and the right components. It has again only a minimum size, but it is slightly bigger than than the margins and the spaces between labels and input fields.
  • The remaining columns repeat the settings for the components on the right side. So the sixth column contains the next labels.
  • The next columns defines the space between the right labels and the right input fields.
  • It follows the column for the right input fields. Here the weight factor is again set to 50. This means that the columns with input fields grow by the same amount if additional space becomes available.
  • Finally there is a column for the right margin.

The constraints for the layout's rows are less complicated because there are only two types of rows: rows containing components (labels and/or input fields) whose height is derived from their content and rows with a minimum height serving as separators between the other rows. All rows with an even row index (starting at 0) are separator rows. An exception is the row containing the text area. Here also a minimum height is defined so that the text area has a certain initial size. Additionally we specify that additional space in the Y direction should be fully consumed by this row because the text area is the only component in this dialog for which growing in the height makes sense. This is achieved by setting the weight factor of this row to 100. To actually create the layout we use string representations for the constraints we thought about in the last paragraphs. This looks as follows:

// setup the main panel and its layout
JPanel pnlMain = new JPanel();
PercentLayout layout = new PercentLayout(
    "3dlu, end/preferred, 3dlu, full/preferred(1in)/50, 7dlu, end/preferred, "
        + "3dlu, full/preferred(1in)/50, 3dlu",
    "3dlu, preferred, 3dlu, preferred, 3dlu, preferred, 7dlu, preferred, "
        + 3dlu, preferred, 3dlu, full/preferred(1in)/100, 3dlu");
    

The first string parameter of the constructor defines all columns, the second one defines the rows. As units for margins and spaces we use dialog units. The meaning of the single definition strings should be clear after reading the section about cell constraints. With this constructor all constraints of the layout are specified. What remains is the requirement that some columns should have the same width: the columns with the labels and the columns with the text fields. To achieve this some CellGroup objects are added to the layot.

layout.addColumnGroup(new CellGroup(1, 5));
layout.addColumnGroup(new CellGroup(3, 7));
pnlMain.setLayout(new SwingPercentLayoutAdapter(layout));
    

These lines add two CellGroup objects for columns. One for the columns with the labels and one for the columns with the text fields. The last line installs the layout in the panel by making use of a Swing-specific layout adapter.

Now it is time for adding components to the layout. First some builder objects for creating helper objects are setup. Then the first header line is defined:

// Builder for creating constraints objects
CellConstraints.Builder cb = layout.getConstraintsBuilder();
PercentData.Builder pcb = new PercentData.Builder();

// Fill a header line
pnlMain.add(
        new JLabel("General information"),
        pcb.xy(1, 1).spanX(7).withColumnConstraints(cb.defaultColumn().create())
           .create());
    

Each PercentLayout object has its own builder for CellConstraints objects. This builder can also be used by client code. We do this here. The builder for PercentData instances has to be created manually.

The header line consists of a single label. What makes this line special is the fact that the label spans multiple columns - the X span in the PercentData object is set to 7. We also use a specific CellConstraints object. The default constraints for column 1 state that components should be right-aligned. The label acting as header is left-aligned. Now the two rows with labels and text fields are constructed:

// Fill the first data line
pnlMain.add(new JLabel("Name:"), pcb.pos(1, 3));
pnlMain.add(new JTextField(), pcb.pos(3, 3));
pnlMain.add(new JLabel("Firstname:"), pcb.pos(5, 3));
pnlMain.add(new JTextField(), pcb.pos(7, 3));

// Fill the second data line
pnlMain.add(new JLabel("Street:"), pcb.pos(1, 5));
pnlMain.add(new JTextField(), pcb.pos(3, 5));
pnlMain.add(new JLabel("City:"), pcb.pos(5, 5));
pnlMain.add(new JTextField(), pcb.pos(7, 5));
    

This is pretty straight-forward: The Swing components for the labels and text fields are created and placed into the layout. The PercentData objects used as constraints only define the position in the tabular layout. Because no other properties need to be set the pos() convenience method of the PercentData.Builder class can be used for this purpose.

The lower half of the dialog box is a bit more irregular. There is again a header line which is analogous to the first one. In the following lines the input fields span multiple columns. The line with the text area is heigher than the other rows. Let's have a look at the code:

// Fill another header line
pnlMain.add(
        new JLabel("Specifics"),
        pcb.xy(1, 7).spanX(7).withColumnConstraints(cb.defaultColumn().create())
           .create());

// Fill third data line
pnlMain.add(new JLabel("Email:"), pcb.pos(1, 9));
pnlMain.add(new JTextField(), pcb.xy(3, 9).spanX(5).create());

// Fill fourth data line
pnlMain
        .add(
                new JLabel("Remarks:"),
                pcb.xy(1, 11).withRowConstraints(
                    cb.withCellAlignment(CellAlignment.START)
                      .withCellSize(CellSize.PREFERRED)
                      .create())
                   .create());
pnlMain.add(new JScrollPane(new JTextArea()),
        pcb.xy(3, 11).spanX(5).create());
    

The header line and the line with the first text field contain nothing new. They define an X span so they cover multiple columns. The row with the text area is a bit more complicated. First, when the layout was constructed a minimum height was defined for this row by specifying the following constraints string: full/preferred(1in)/100. This means that this row has a height of at least 1 inch, which guarantees an appropriate size of the text area. Therefore when adding the text area no special constraints have to be provided except for the column span. For the label however, we have to override the global row constraints. Here the alignment is set to CENTER, which would cause the label to be centered relative to the text area. This would look strange; so we set the alignment in the row constraints to START. This causes the label to be top-aligned. Now the main area of the layout with all input components is complete. What is missing is the button bar:

// The button bar
JPanel pnlButtons = new JPanel();
ButtonLayout buttonLayout = new ButtonLayout();
buttonLayout.setGap(new NumberWithUnit(4, Unit.DLU));
buttonLayout.setLeftMargin(new NumberWithUnit(3, Unit.DLU));
buttonLayout.setRightMargin(new NumberWithUnit(3, Unit.DLU));
pnlButtons.setLayout(new SwingPercentLayoutAdapter(buttonLayout));
pnlButtons.add(new JButton("OK"));
pnlButtons.add(new JButton("Cancel"));
pnlButtons.add(new JButton("Help"));
    

For the button bar a new panel is created. The layout manager is set to a ButtonLayout instance (using a SwingPercentLayoutAdapter as mediator). Some properties of the ButtonLayout are set, then the buttons are added. That's all. Finally the panels for the main area and the button bar have to be added to the dialog's content pane. It has to be ensured that the appropriate cells of the BorderLyout are used:

getContentPane().add(pnlMain, BorderLayout.CENTER);
getContentPane().add(pnlButtons, BorderLayout.SOUTH);
    

This was all the code for constructing the UI. For a better overview the whole code of the demo application is presented below. The code lines we just discussed can be found in the init() method.

package net.sf.jguiraffe.examples.gui.layout;

import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import net.sf.jguiraffe.gui.layout.BorderLayout;
import net.sf.jguiraffe.gui.layout.ButtonLayout;
import net.sf.jguiraffe.gui.layout.CellAlignment;
import net.sf.jguiraffe.gui.layout.CellConstraints;
import net.sf.jguiraffe.gui.layout.CellGroup;
import net.sf.jguiraffe.gui.layout.CellSize;
import net.sf.jguiraffe.gui.layout.NumberWithUnit;
import net.sf.jguiraffe.gui.layout.PercentData;
import net.sf.jguiraffe.gui.layout.PercentLayout;
import net.sf.jguiraffe.gui.layout.Unit;
import net.sf.jguiraffe.gui.platform.swing.layout.SwingPercentLayoutAdapter;

/**
 * An example of using PercentLayout with Swing.
 */
@SuppressWarnings("serial")
public class SwingPercentLayoutExample extends JFrame
{

    public SwingPercentLayoutExample()
    {
        super();
        init();
        pack();
    }

    /**
     * Constructs the GUI of this frame.
     */
    protected void init()
    {
        // init frame
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("PercentLayout demo");

        // The layout of the content pane
        BorderLayout borderLayout = new BorderLayout();
        getContentPane().setLayout(new SwingPercentLayoutAdapter(borderLayout));

        // setup the main panel and its layout
        JPanel pnlMain = new JPanel();
        PercentLayout layout = new PercentLayout(
                "3dlu, end/preferred, 3dlu, full/preferred(1in)/50, 7dlu, "
                + "end/preferred, 3dlu, full/preferred(1in)/50, 3dlu",
                "3dlu, preferred, 3dlu, preferred, 3dlu, preferred, 7dlu, "
                + "preferred, 3dlu, preferred, 3dlu, full/preferred(1in)/100, 3dlu");
        layout.addColumnGroup(new CellGroup(1, 5));
        layout.addColumnGroup(new CellGroup(3, 7));
        pnlMain.setLayout(new SwingPercentLayoutAdapter(layout));

        // Builder for creating constraints objects
        CellConstraints.Builder cb = layout.getConstraintsBuilder();
        PercentData.Builder pcb = new PercentData.Builder();

        // Fill a header line
        pnlMain.add(new JLabel("General information"), pcb.xy(1, 1).spanX(7)
                .withColumnConstraints(cb.defaultColumn().create()).create());

        // Fill the first data line
        pnlMain.add(new JLabel("Name:"), pcb.pos(1, 3));
        pnlMain.add(new JTextField(), pcb.pos(3, 3));
        pnlMain.add(new JLabel("Firstname:"), pcb.pos(5, 3));
        pnlMain.add(new JTextField(), pcb.pos(7, 3));

        // Fill the second data line
        pnlMain.add(new JLabel("Street:"), pcb.pos(1, 5));
        pnlMain.add(new JTextField(), pcb.pos(3, 5));
        pnlMain.add(new JLabel("City:"), pcb.pos(5, 5));
        pnlMain.add(new JTextField(), pcb.pos(7, 5));

        // Fill another header line
        pnlMain.add(new JLabel("Specifics"), pcb.xy(1, 7).spanX(7)
                .withColumnConstraints(cb.defaultColumn().create()).create());

        // Fill third data line
        pnlMain.add(new JLabel("Email:"), pcb.pos(1, 9));
        pnlMain.add(new JTextField(), pcb.xy(3, 9).spanX(5).create());

        // Fill fourth data line
        pnlMain.add(new JLabel("Remarks:"), pcb.xy(1, 11).withRowConstraints(
                cb.withCellAlignment(CellAlignment.START).withCellSize(
                        CellSize.PREFERRED).create()).create());
        pnlMain.add(new JScrollPane(new JTextArea()), pcb.xy(3, 11).spanX(5)
                .create());

        // The button bar
        JPanel pnlButtons = new JPanel();
        ButtonLayout buttonLayout = new ButtonLayout();
        buttonLayout.setGap(new NumberWithUnit(4, Unit.DLU));
        buttonLayout.setLeftMargin(new NumberWithUnit(3, Unit.DLU));
        buttonLayout.setRightMargin(new NumberWithUnit(3, Unit.DLU));
        pnlButtons.setLayout(new SwingPercentLayoutAdapter(buttonLayout));
        pnlButtons.add(new JButton("OK"));
        pnlButtons.add(new JButton("Cancel"));
        pnlButtons.add(new JButton("Help"));

        getContentPane().add(pnlMain, BorderLayout.CENTER);
        getContentPane().add(pnlButtons, BorderLayout.SOUTH);
    }

    public static void main(String[] args)
    {
        final SwingPercentLayoutExample exFrame = new SwingPercentLayoutExample();
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                exFrame.setVisible(true);
            }
        });
    }
}