Layout Managers

  • June 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Layout Managers as PDF for free.

More details

  • Words: 28,022
  • Pages: 93
Lesson: Laying Out Components Within a Container This lesson tells you how to use the layout managers provided by the Java platform. It also tells you how to use absolute positioning (no layout manager) and gives an example of writing a custom layout manager. For each layout manager (or lack thereof), this lesson points to an example that you can run using JavaTM Web Start. By resizing an example's window, you can see how size changes affect the layout.

Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

A Visual Guide to Layout Managers This section shows examples of the standard layout managers and points to the how-to section for each one.

Using Layout Managers This section gives general rules on using the standard layout managers. It includes how to set the layout manager, add components to a container, provide size and alignment hints, put space between components, and set the orientation of the container's layout so that it is appropriate for the locale in which the program is running. It also has some tips for choosing the right layout manager.

How Layout Management Works This section goes through a typical layout sequence and then describes what happens when a component's size changes.

How to Use ... This series of sections tells you how to use each of the general-purpose layout managers that the Java platform provides.

Creating a Custom Layout Manager Instead of using one of the Java platform's layout managers, you can write your own. Layout managers must implement the LayoutManager interface, which specifies the five methods every layout manager must define. Optionally, layout managers can implement LayoutManager2, which is a subinterface of LayoutManager. 840

Doing Without a Layout Manager (Absolute Positioning) If necessary, you can position components without using a layout manager. Generally, this solution is used to specify absolute sizes and positions for components.

Solving Common Layout Problems Some of the most common layout problems involve components that are displayed too small — or not at all. This section tells you how to fix these and other common layout problems.

Questions and Exercises Try these questions and exercises to test what you have learned in this lesson.

A Visual Guide to Layout Managers Several AWT and Swing classes provide layout managers for general use:        

BorderLayout BoxLayout CardLayout FlowLayout GridBagLayout GridLayout GroupLayout SpringLayout

This section shows example GUIs that use these layout managers, and tells you where to find the how-to page for each layout manager. You can find links for running the examples in the how-to pages and in the example index.

Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

BorderLayout

841

Every content pane is initialized to use a BorderLayout. (As Using Top-Level Containers explains, the content pane is the main container in all frames, applets, and dialogs.) A BorderLayout places components in up to five areas: top, bottom, left, right, and center. All extra space is placed in the center area. Tool bars that are created using JToolBar must be created within a BorderLayout container, if you want to be able to drag and drop the bars away from their starting positions. For further details, see How to Use BorderLayout.

BoxLayout

The BoxLayout class puts components in a single row or column. It respects the components' requested maximum sizes and also lets you align components. For further details, see How to Use BoxLayout.

CardLayout

The CardLayout class lets you implement an area that contains different components at different times. A CardLayout is often controlled by a combo box, with the state of the combo box determining which panel (group of components) the CardLayout displays. An alternative to using CardLayout is using a tabbed pane, which provides similar functionality but with a pre-defined GUI. For further details, see How to Use CardLayout.

FlowLayout

FlowLayout is the default layout manager for every JPanel. It simply lays out components in a

single row, starting a new row if its container is not sufficiently wide. Both panels in CardLayoutDemo, shown previously, use FlowLayout. For further details, see How to Use FlowLayout.

GridBagLayout

842

GridBagLayout is a sophisticated, flexible layout manager. It aligns components by placing them

within a grid of cells, allowing components to span more than one cell. The rows in the grid can have different heights, and grid columns can have different widths. For further details, see How to Use GridBagLayout.

GridLayout

GridLayout simply makes a bunch of components equal in size and displays them in the requested

number of rows and columns. For further details, see How to Use GridLayout.

GroupLayout

GroupLayout is a layout manager that was developed for use by GUI builder tools, but it can also be used manually. GroupLayout works with the horizontal and vertical layouts separately. The layout is

defined for each dimension independently. Consequently, however, each component needs to be defined twice in the layout. The Find window shown above is an example of a GroupLayout. For further details, see How to Use GroupLayout.

SpringLayout

843

SpringLayout is a flexible layout manager designed for use by GUI builders. It lets you specify

precise relationships between the edges of components under its control. For example, you might define that the left edge of one component is a certain distance (which can be dynamically calculated) from the right edge of a second component. SpringLayout lays out the children of its associated container according to a set of constraints, as shall be seen in How to Use SpringLayout.

Using Layout Managers A layout manager is an object that implements the LayoutManager interface* and determines the size and position of the components within a container. Although components can provide size and alignment hints, a container's layout manager has the final say on the size and position of the components within the container. Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

This section discusses some of the common tasks related to using layout managers:       

Setting the Layout Manager Adding Components to a Container Providing Size and Alignment Hints Putting Space Between Components Setting the Container's Orientation Tips on Choosing a Layout Manager Third-Party Layout Managers

Setting the Layout Manager As a rule, the only containers whose layout managers you need to worry about are JPanels and content panes. Each JPanel object is initialized to use a FlowLayout, unless you specify differently when creating the JPanel. Content panes use BorderLayout by default. If you do not like the default layout manager that a panel or content pane uses, you are free to change it to a different one. However, unless you are using JToolBar, the FlowLayout and BorderLayout managers are only useful for prototyping. Any real application will need to reset the layout manager. Again, you should use an appropriate tool to do this, rather than coding the manager by hand. 844

You can set a panel's layout manager using the JPanel constructor. For example: JPanel panel = new JPanel(new BorderLayout());

After a container has been created, you can set its layout manager using the setLayout method. For example: Container contentPane = frame.getContentPane(); contentPane.setLayout(new FlowLayout());

Although we strongly recommend that you use layout managers, you can perform layout without them. By setting a container's layout property to null, you make the container use no layout manager. With this strategy, called absolute positioning, you must specify the size and position of every component within that container. One drawback of absolute positioning is that it does not adjust well when the top-level container is resized. It also does not adjust well to differences between users and systems, such as different font sizes and locales.

Adding Components to a Container When you add components to a panel or content pane, the arguments you specify to the add method depend on the layout manager that the panel or content pane is using. In fact, some layout managers do not even require you to add the component explicitly; for example, GroupLayout. For example, BorderLayout requires that you specify the area to which the component should be added (using one of the constants defined in BorderLayout) using code like this: pane.add(aComponent, BorderLayout.PAGE_START);

The how-to section for each layout manager has details on what, if any, arguments you need to specify to the add method. Some layout managers, such as GridBagLayout and SpringLayout, require elaborate setup procedures. Many layout managers, however, simply place components based on the order they were added to their container. Swing containers other than JPanel and content panes generally provide API that you should use instead of the add method. For example, instead of adding a component directly to a scroll pane (or, actually, to its viewport), you either specify the component in the JScrollPane constructor or use setViewportView. Because of specialized API like this, you do not need to know which layout manager (if any) many Swing containers use. (For the curious: scroll panes happen to use a layout manager named ScrollPaneLayout.) For information about how to add components to a specific container, see the how-to page for the container. You can find the component how-to pages using How to Use Various Components.

Providing Size and Alignment Hints Sometimes you need to customize the size hints that a component provides to its container's layout manager, so that the component will be laid out well. You can do this by specifying one or more of the minimum, preferred, and maximum sizes of the component. You can invoke the component's methods for setting size hints — setMinimumSize, setPreferredSize, and setMaximumSize. Or you can create a subclass of the component that overrides the appropriate getter methods — getMinimumSize, getPreferredSize, and getMaximumSize. Here is an example of making a component's maximum size unlimited: component.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));

845

Many layout managers do not pay attention to a component's requested maximum size. However, BoxLayout and SpringLayout do. Furthermore, GroupLayout provides the ability to set the minimum, preferred or maximum size explicitly, without touching the component. Besides providing size hints, you can also provide alignment hints. For example, you can specify that the top edges of two components should be aligned. You set alignment hints either by invoking the component's setAlignmentX and setAlignmentY methods, or by overriding the component's getAlignmentX and getAlignmentY methods. Although most layout managers ignore alignment hints, BoxLayout honors them. You can find examples of setting the alignment in How to Use BoxLayout.

Putting Space Between Components Three factors influence the amount of space between visible components in a container: The layout manager Some layout managers automatically put space between components; others do not. Some let you specify the amount of space between components. See the how-to page for each layout manager for information about spacing support. Invisible components You can create lightweight components that perform no painting, but that can take up space in the GUI. Often, you use invisible components in containers controlled by BoxLayout. See How to Use BoxLayout for examples of using invisible components. Empty borders No matter what the layout manager, you can affect the apparent amount of space between components by adding empty borders to components. The best candidates for empty borders are components that typically have no default border, such as panels and labels. Some other components might not work well with borders in some lookand-feel implementations, because of the way their painting code is implemented. For information about borders, see How to Use Borders.

Setting the Container's Orientation This website is written in English, with text that runs from left to right, and then top to bottom. However, many other languages have different orientations. The componentOrientation property provides a way of indicating that a particular component should use something different from the default left-to-right, top-to-bottom orientation. In a component such as a radio button, the orientation might be used as a hint that the look and feel should switch the locations of the icon and text in the button. In a container, the orientation is used as a hint to the layout manager. To set a container's orientation, you can use either the Component-defined method setComponentOrientation or, to set the orientation on the container's children as well, applyComponentOrientation. The argument to either method can be a constant such as ComponentOrientation.RIGHT_TO_LEFT, or it can be a call to the ComponentOrientation method getOrientation(Locale). For example, the following code causes all JComponents to be initialized with an Arabic-language locale, and then sets the orientation of the content pane and all components inside it accordingly: JComponent.setDefaultLocale(new Locale("ar")); JFrame frame = new JFrame(); ... Container contentPane = frame.getContentPane(); contentPane.applyComponentOrientation( ComponentOrientation.getOrientation(

846

contentPane.getLocale()));

Here are two pictures showing how FlowLayout lays out components in containers that are exactly the same, except for their orientation.

Default orientation (left-to-right)

Right-to-left orientation The standard layout managers that support component orientation are FlowLayout, BorderLayout, BoxLayout, GridBagLayout, and GridLayout. Note: Care must be taken that the component orientation is applied to renderers, editors and any other components unreachable through normal traversal of the containment hierarchy.

Tips on Choosing a Layout Manager Layout managers have different strengths and weaknesses. This section discusses some common layout scenarios and which layout managers might work for each scenario. However, once again, it is strongly recommended that you use a builder tool to create your layout managers, such as the NetBeans IDE 5.5 Matisse GUI builder, rather than coding managers by hand. The scenarios listed below are given for information purposes, in case you are curious about which type of manager is used in different situations, or in case you absolutely must code your manager manually. If none of the layout managers we discuss is right for your situation and you cannot use a builder tool, feel free to use other layout managers that you may write or find. Also keep in mind that flexible layout managers such as GridBagLayout and SpringLayout can fulfill many layout needs. Scenario: You need to display a component in as much space as it can get. If it is the only component in its container, use GridLayout or BorderLayout. Otherwise, BorderLayout or GridBagLayout might be a good match. If you use BorderLayout, you will need to put the space-hungry component in the center. With GridBagLayout, you will need to set the constraints for the component so that fill=GridBagConstraints.BOTH. Another possibility is to use BoxLayout, making the space-hungry component specify very large preferred and maximum sizes. Scenario: You need to display a few components in a compact row at their natural size. Consider using a JPanel to group the components and using either the JPanel's default FlowLayout manager or the BoxLayout manager. SpringLayout is also good for this. Scenario: You need to display a few components of the same size in rows and columns. GridLayout is perfect for this.

847

Scenario: You need to display a few components in a row or column, possibly with varying amounts of space between them, custom alignment, or custom component sizes. BoxLayout is perfect for this. Scenario: You need to display aligned columns, as in a form-like interface where a column of labels is used to describe text fields in an adjacent column. SpringLayout is a natural choice for this. The SpringUtilities class used by several Tutorial examples defines a makeCompactGrid method that lets you easily align multiple rows and columns of components. Scenario: You have a complex layout with many components. Consider either using a very flexible layout manager such as GridBagLayout or SpringLayout, or grouping the components into one or more JPanels to simplify layout. If you take the latter approach, each JPanel might use a different layout manager.

Third Party Layout Managers Other third party layout managers have been created by the Swing community, to complement those provided by the Java platform. The following list is by no means definitive, but the layout managers listed below are the most popular:   

TableLayout MiGLayout Karsten Lentzsch's FormLayout

*Way back in JDK 1.1 a second interface, LayoutManager2, was introduced. LayoutManager2 extends LayoutManager, providing support for maximum size and alignment. LayoutManager2 also adds the methods addLayoutComponent, that takes an Object, and invalidateLayout. Layout managers also need the notifications provided by LayoutManager2, so any modern layout manager will need to implement it.

How Layout Management Works Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

Here is an example of a layout management sequence for a container using LayoutManager2. 1. Layout managers basically do two things: o Calculate the minimum/preferred/maximum sizes for a container. o Lay out the container's children. Layout managers do this based on the provided constraints, the container's properties (such as insets) and on the children's minimum/preferred/maximum sizes. If a child is itself a container then its own layout manger is used to get its minimum/preferred/maximum sizes and to lay it out.

848

2. A container can be valid (namely, isValid() returns true) or invalid. For a container to be valid, all the container's children must be laid out already and must all be valid also. The Container.validate method can be used to validate an invalid container. This method triggers the layout for the container and all the child containers down the component hierarchy and marks this container as valid. 3. After a component is created it is in the invalid state by default. The Window.pack method validates the window and lays out the window's component hierarchy for the first time. The end result is that to determine the best size for the container, the system determines the sizes of the containers at the bottom of the containment hierarchy. These sizes then percolate up the containment hierarchy, eventually determining the container's total size. If the size of a component changes, for example following a change of font, the component must be resized and repainted by calling the revalidate and repaint methods on that component. Both revalidate and repaint are thread-safe — you need not invoke them from the event-dispatching thread. When you invoke revalidate on a component, a request is passed up the containment hierarchy until it encounters a container, such as a scroll pane or top-level container, that should not be affected by the component's resizing. (This is determined by calling the container's isValidateRoot method.) The container is then laid out, which has the effect of adjusting the revalidated component's size and the size of all affected components.

How to Use Various Layout Managers Each of the following pages describes how to use a particular kind of layout manager. Another way to get to these pages is through A Visual Guide to Layout Managers. Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

       

How to Use BorderLayout How to Use BoxLayout How to Use CardLayout How to Use FlowLayout How to Use GridBagLayout How to Use GridLayout How to Use GroupLayout How to Use SpringLayout

How to Use BorderLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

849

The following figure represents a snapshot of an application that uses the BorderLayout class.

Click the Launch button to run BorderLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The complete code of this demo is in the BorderLayoutDemo.java file. As the preceding picture shows, a BorderLayout object has five areas. These areas are specified by the BorderLayout constants:     

PAGE_START PAGE_END LINE_START LINE_END CENTER

Version note: Before JDK release 1.4, the preferred names for the various areas were different, ranging from points of the compass (for example, BorderLayout.NORTH for the top area) to wordier versions of the constants we use in our examples. The constants our examples use are preferred because they are standard and enable programs to adjust to languages that have different orientations.

If the window is enlarged, the center area gets as much of the available space as possible. The other areas expand only as much as necessary to fill all available space. Often a container uses only one or two of the areas of the BorderLayout object — just the center, or the center and the bottom. The following code adds components to a frame's content pane. Because content panes use the BorderLayout class by default, the code does not need to set the layout manager. The complete program is in the BorderLayoutDemo.java file. ...//Container pane = aFrame.getContentPane()... JButton button = new JButton("Button 1 (PAGE_START)"); pane.add(button, BorderLayout.PAGE_START); //Make the center component big, since that's the //typical usage of BorderLayout. button = new JButton("Button 2 (CENTER)"); button.setPreferredSize(new Dimension(200, 100)); pane.add(button, BorderLayout.CENTER); button = new JButton("Button 3 (LINE_START)"); pane.add(button, BorderLayout.LINE_START);

850

button = new JButton("Long-Named Button 4 (PAGE_END)"); pane.add(button, BorderLayout.PAGE_END); button = new JButton("5 (LINE_END)"); pane.add(button, BorderLayout.LINE_END);

Specify the component's location (for example, BorderLayout.LINE_END) as one of the arguments to the add method. If this component is missing from a container controlled by a BorderLayout object, make sure that the component's location was specified and no another component was placed in the same location. All tutorial examples that use the BorderLayout class specify the component as the first argument to the add method. For example: add(component, BorderLayout.CENTER)

//preferred

However, the code in other programs specifies the component as the second argument. For example, here are alternate ways of writing the preceding code: add(BorderLayout.CENTER, component) or add("Center", component)

//valid but old fashioned //valid but error prone

The BorderLayout API The following table lists constructors and methods to specify gaps (in pixels). Specifying gaps Constructor or Method Purpose BorderLayout(int horizontalGap, int Defines a border layout with specified gaps verticalGap) between components. setHgap(int)

Sets the horizontal gap between components.

setVgap(int)

Sets the vertical gap between components.

Examples that Use BorderLayout The following table lists code examples that use the BorderLayout class and provides links to related sections. Where Example Notes Described BorderLayoutDemo This page Puts a component in each of the five possible locations. One of many examples that puts a single component in the How to Use TabbedPaneDemo center of a content pane, so that the component is as large as Tabbed Panes possible. Creates a JPanel object that uses the BorderLayout class. How to Use CheckBoxDemo Puts components into the left (actually, LINE_START) and Check Boxes center locations.

851

How to Use BoxLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. The Swing packages include a general purpose layout manager named BoxLayout. BoxLayout either stacks its components on top of each other or places them in a row — your choice. You might think of it as a version of FlowLayout, but with greater functionality. Here is a picture of an application that demonstrates using BoxLayout to display a centered column of components:

Click the Launch button to run BoxLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

You can see the code in BoxLayoutDemo.java. The following figure shows a GUI that uses two instances of BoxLayout. In the top part of the GUI, a top-to-bottom box layout places a label above a scroll pane. In the bottom part of the GUI, a left-toright box layout places two buttons next to each other. A BorderLayout combines the two parts of the GUI and ensures that any excess space is given to the scroll pane.

You can find links for running ListDialog and for its source files in the example index for Using Swing Components. The following code, taken from ListDialog.java, lays out the GUI. This code is in the constructor for the dialog, which is implemented as a JDialog subclass. The bold lines of code set up the box layouts and add components to them. JScrollPane listScroller = new JScrollPane(list); listScroller.setPreferredSize(new Dimension(250, 80));

852

listScroller.setAlignmentX(LEFT_ALIGNMENT); ... //Lay out the label and scroll pane from top to bottom. JPanel listPane = new JPanel(); listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); JLabel label = new JLabel(labelText); ... listPane.add(label); listPane.add(Box.createRigidArea(new Dimension(0,5))); listPane.add(listScroller); listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); //Lay out the buttons from left to right. JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(cancelButton); buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); buttonPane.add(setButton); //Put everything together, using the content pane's BorderLayout. Container contentPane = getContentPane(); contentPane.add(listPane, BorderLayout.CENTER); contentPane.add(buttonPane, BorderLayout.PAGE_END);

The first bold line creates a top-to-bottom box layout and sets it up as the layout manager for listPane. The two arguments to the BoxLayout constructor are the container that it manages and the axis along which the components will be laid out. The PAGE_AXIS constant specifies that components should be laid out in the direction that lines flow across a page as determined by the target container's ComponentOrientation property. The LINE_AXIS constant specifies that components should be laid out in the direction of a line of text as determined by the target container's ComponentOrientation property. These constants allow for internationalization, by laying out components in their container with the correct left-to-right, right-to-left or top-to-bottom orientation for the language being used. The next three bold lines add the label and scroll pane to the container, separating them with a rigid area — an invisible component used to add space between components. In this case, the rigid area has no width and puts exactly 5 pixels between the label and scroll pane. Rigid areas are discussed later, in Using Invisible Components as Filler. The next chunk of bold code creates a left-to-right box layout and sets it up for the buttonPane container. Then the code adds two buttons to the container, using a rigid area to put 10 pixels between the buttons. To place the buttons at the right side of their container, the first component added to the container is glue. This glue is an invisible component that grows as necessary to absorb any extra space in its container. Glue is discussed in Using Invisible Components as Filler. As an alternative to using invisible components, you can sometimes use empty borders to create space around components, most particularly panels. For example, the preceding code snippet uses empty borders to put 10 pixels between all sides of the dialog and its contents, and between the two parts of the contents. Borders are completely independent of layout managers. They are simply how Swing components draw their edges and provide padding between the content of the component and the edge. See How to Use Borders for more information. The following sections discuss BoxLayout in more detail: 

Box layout features 853

    

Using invisible components as filler Fixing alignment problems Specifying component sizes The box layout API Examples that use box layouts

Do not let the length of the BoxLayout discussion scare you! You can probably use BoxLayout with the information you already have. If you run into trouble or you want to take advantage of BoxLayout's power, read on.

Box Layout Features As said before, BoxLayout arranges components either on top of each other or in a row. As the box layout arranges components, it takes the components' alignments and minimum, preferred, and maximum sizes into account. In this section, we will talk about top-to-bottom layout. The same concepts apply to left-to-right or right-to-left layout. You simply substitute X for Y, height for width, and so on. Version note: Before JDK version 1.4, no constants existed for specifying the box layout's axis in a localizable way. Instead, you specified X_AXIS (left to right) or Y_AXIS (top to bottom) when creating the BoxLayout. Our examples now use the constants LINE_AXIS and PAGE_AXIS, which are preferred because they enable programs to adjust to languages that have different orientations. In the default, left-to-right orientation, LINE_AXIS specifies left-to-right layout and PAGE_AXIS specifies top-to-bottom layout.

When a BoxLayout lays out components from top to bottom, it tries to size each component at the component's preferred height. If the vertical space of the layout does not match the sum of the preferred heights, then BoxLayout tries to resize the components to fill the space. The components either grow or shrink to fill the space, with BoxLayout honoring the minimum and maximum sizes of each of the components. Any extra space appears at the bottom of the container. For a top-to-bottom box layout, the preferred width of the container is that of the maximum preferred width of the children. If the container is forced to be wider than that, BoxLayout attempts to size the width of each component to that of the container's width (minus insets). If the maximum size of a component is smaller than the width of the container, then X alignment comes into play. The X alignments affect not only the components' positions relative to each other, but also the location of the components (as a group) within their container. The following figures illustrate alignment of components that have restricted maximum widths.

854

In the first figure, all three components have an X alignment of 0.0 (Component.LEFT_ALIGNMENT). This means that the components' left sides should be aligned. Furthermore, it means that all three components are positioned as far left in their container as possible. In the second figure, all three components have an X alignment of 0.5 (Component.CENTER_ALIGNMENT). This means that the components' centers should be aligned, and that the components should be positioned in the horizontal center of their container. In the third figure, the components have an X alignment of 1.0 (Component.RIGHT_ALIGNMENT). You can guess what that means for the components' alignment and position relative to their container. You might be wondering what happens when the components have both restricted maximum sizes and different X alignments. The next figure shows an example of this:

As you can see, the left side of the component with an X alignment of 0.0 (Component.LEFT_ALIGNMENT) is aligned with the center of the component that has the 0.5 X alignment (Component.CENTER_ALIGNMENT), which is aligned with the right side of the component that has an X alignment of 1.0 (Component.RIGHT_ALIGNMENT). Mixed alignments like this are further discussed in Fixing Alignment Problems. What if none of the components has a maximum width? In this case, if all the components have identical X alignment, then all components are made as wide as their container. If the X alignments are different, then any component with an X alignment of 0.0 (left) or 1.0 (right) will be smaller. All components with an intermediate X alignment (such as center) will be as wide as their container. Here are two examples:

To get to know BoxLayout better, you can run your own experiments with BoxLayoutDemo2. Try this: 855

1. Click the Launch button to run BoxLayoutDemo2 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

You can see the code in BoxLayoutDemo2.java. You will see a window like the one above that contains three rectangles. Each rectangle is an instance of BLDComponent, which is a JComponent subclass. 2. Click inside one of the rectangles. This is how you change the rectangle's X alignment. 3. Click the check box at the bottom of the window. This turns off restricted sizing for all the rectangles. 4. Make the window taller. This makes the rectangles' container larger than the sum of the rectangles' preferred sizes. The container is a JPanel that has a red outline, so that you can tell where the container's edges are.

Using Invisible Components as Filler Each component controlled by a box layout butts up against its neighboring components. If you want to have space between components, you can either add an empty border to one or both components, or insert invisible components to provide the space. You can create invisible components with the help of the Box class. The Box class defines a nested class, Box.Filler, that is a transparent component that paints nothing, and is used to provide space between other components. However, Filler is not actually invisible, because setVisible(false) is not invoked. The Box class provides convenience methods to help you create common kinds of filler. The following table gives details about creating invisible components with Box and Box.Filler. Type

Size Constraints

rigid area

glue

How to Create Box.createRigidArea(size)

horizontal

Box.createHorizontalGlue()

vertical

Box.createVerticalGlue()

custom Box.Filler (as specified)

new Box.Filler(minSize, prefSize, maxSize)

Here is how you generally use each type of filler: Rigid area Use this when you want a fixed-size space between two components. For example, to put 5 pixels between two components in a left-to-right box, you can use this code: container.add(firstComponent);

856

container.add(Box.createRigidArea(new Dimension(5,0))); container.add(secondComponent);

Note: The Box class provides another kind of filler for putting fixed space between components: a vertical or horizontal strut. Unfortunately, struts have unlimited maximum heights or widths (for horizontal and vertical struts, respectively). This means that if you use a horizontal box within a vertical box, for example, the horizontal box can sometimes become too tall. For this reason, we recommend that you use rigid areas instead of struts. Glue Use this to specify where excess space in a layout should go. Think of it as a kind of elastic glue — stretchy and expandable, yet taking up no space unless you pull apart the components that it is sticking to. For example, by putting horizontal glue between two components in a left-to-right box, you make any extra space go between those components, instead of to the right of all the components. Here is an example of making the space in a left-to-right box go between two components, instead of to the right of the components: container.add(firstComponent); container.add(Box.createHorizontalGlue()); container.add(secondComponent);

Custom Box.Filler Use this to specify a component with whatever minimum, preferred, and maximum sizes you want. For example, to create some filler in a left-to-right layout that puts at least 5 pixels between two components and ensures that the container has a minimum height of 100 pixels, you could use this code: container.add(firstComponent); Dimension minSize = new Dimension(5, 100); Dimension prefSize = new Dimension(5, 100); Dimension maxSize = new Dimension(Short.MAX_VALUE, 100); container.add(new Box.Filler(minSize, prefSize, maxSize)); container.add(secondComponent);

Fixing Alignment Problems 857

Two types of alignment problems sometimes occur with BoxLayout: 

A group of components all have the same alignment, but you want to change their alignment to make them look better. For example, instead of having the centers of a group of left-to-right buttons all in a line, you might want the bottoms of the buttons to be aligned. Here is an example:



Two or more components controlled by a BoxLayout have different default alignments, which causes them to be mis-aligned. For example, as the following shows, if a label and a panel are in a top-to-bottom box layout, the label's left edge is, by default, aligned with the center of the panel.

In general, all the components controlled by a top-to-bottom BoxLayout object should have the same X alignment. Similarly, all the components controlled by a left-to-right Boxlayout should generally have the same Y alignment. You can set a JComponent's X alignment by invoking its setAlignmentX method. An alternative available to all components is to override the getAlignmentX method in a custom subclass of the component class. Similarly, you set the Y alignment of a component by invoking the setAlignmentY method or by overriding getAlignmentY. Here is an example, taken from an application called BoxAlignmentDemo, of changing the Y alignments of two buttons so that the bottoms of the buttons are aligned: button1.setAlignmentY(Component.BOTTOM_ALIGNMENT); button2.setAlignmentY(Component.BOTTOM_ALIGNMENT);

858

Click the Launch button to run BoxAlignmentDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

By default, most components have center X and Y alignment. However, buttons, combo boxes, labels, and menu items have a different default X alignment value: LEFT_ALIGNMENT. The previous picture shows what happens if you put a left-aligned component such as a label together with a center-aligned component in a container controlled by a top-to-bottom BoxLayout. The BoxAlignmentDemo program gives examples of fixing mismatched alignment problems. Usually, it is as simple as making an offending button or label be center aligned. For example: label.setAlignmentX(Component.CENTER_ALIGNMENT);

Specifying Component Sizes As mentioned before, BoxLayout pays attention to a component's requested minimum, preferred, and maximum sizes. While you are fine-tuning the layout, you might need to adjust these sizes. Sometimes the need to adjust the size is obvious. For example, a button's maximum size is generally the same as its preferred size. If you want the button to be drawn wider when additional space is available, then you need to change its maximum size. Sometimes, however, the need to adjust size is not so obvious. You might be getting unexpected results with a box layout, and you might not know why. In this case, it is usually best to treat the problem as an alignment problem first. If adjusting the alignments does not help, then you might have a size problem. We'll discuss this further a bit later.

Note: Although BoxLayout pays attention to a component's maximum size, many layout managers do not. For example, if you put a button in the bottom part of a BorderLayout, the button will probably be wider than its preferred width, no matter what the button's maximum size is. BoxLayout, on the other hand, never makes a button wider than its maximum size.

You can change the minimum, preferred, and maximum sizes in two ways: 

By invoking the appropriate setXxxSize method (which is defined by the JComponent class). For example:

          

comp.setMinimumSize(new Dimension(50, 25)); comp.setPreferredSize(new Dimension(50, 25)); comp.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); By overriding the appropriate getXxxSize method. For example: ...//in a subclass of a component class: public Dimension getMaximumSize() { size = getPreferredSize(); size.width = Short.MAX_VALUE; return size; }

859

If you are running into trouble with a box layout and you have ruled out alignment problems, then the trouble might well be size-related. For example, if the container controlled by the box layout is taking up too much space, then one or more of the components in the container probably needs to have its maximum size restricted. You can use two techniques to track down size trouble in a box layout: 

Add a garish line border to the outside of the Swing components in question. This lets you see what size they really are. For example:

   

comp.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.red), comp.getBorder())); Use System.out.println to print the components' minimum, preferred, and

maximum sizes, and perhaps their bounds.

The Box Layout API The following tables list the commonly used BoxLayout and Box constructors and methods. The API for using box layouts falls into these categories:   

Creating BoxLayout objects Creating space fillers Other useful methods Creating BoxLayout Objects Purpose

Constructor or Method

BoxLayout(Container, int)

Creates a BoxLayout instance that controls the specified Container. The integer argument specifies the axis along which the container's components should be laid out. When the container has the default component orientation, BoxLayout.LINE_AXIS specifies that the components be laid out from left to right, and BoxLayout.PAGE_AXIS specifies that the components be laid out from top to bottom.

Box(int)

Creates a Box — a container that uses a BoxLayout with the specified axis. As of release 1.3, Box extends JComponent. Before that, it was implemented as a subclass of Container.

static Box createHorizontalBox() (in Box)

Creates a Box that lays out its components from left to right.

static Box createVerticalBox() (in Box)

Creates a Box that lays out its components from top to bottom.

Creating Space Fillers These methods are defined in the Box class. Constructor or Method Purpose Component createRigidArea(Dimension)

Create a rigid component.

Component createHorizontalGlue() Create a glue component. Horizontal glue and vertical glue Component createVerticalGlue() can be very useful. Component createGlue()

860

Component createHorizontalStrut() Component createVerticalStrut()

Create a "strut" component. We recommend using rigid areas instead of struts.

Box.Filler(Dimension, Dimension, Dimension)

Creates a component with the specified minimum, preferred, and maximum sizes (with the arguments specified in that order). See the custom Box.Filler discussion, earlier in this section, for details. Other Useful Methods

Method void changeShape(Dimension, Dimension, Dimension) (in Box.Filler)

Purpose Change the minimum, preferred, and maximum sizes of the recipient Box.Filler object. The layout changes accordingly.

Examples that Use Box Layouts The following table lists some of the many examples that use box layouts. Example Where Described Notes BoxLayoutDemo2 This page Uses a box layout to create a centered column of components. BoxAlignmentDemo This page Demonstrates how to fix common alignment problems. BoxLayoutDemo This page Lets you play with alignments and maximum sizes. ListDialog This page A simple yet realistic example of using both a topto-bottom box layout and a left-to-right one. Uses horizontal glue, rigid areas, and empty borders. Also sets the X alignment of a component. InternalFrameEventDemo How to Write an Uses a top-to-bottom layout to center buttons and Internal Frame a scroll pane in an internal frame. Listener MenuGlueDemo Customizing Shows how to right-align a menu in the menu bar, Menu Layout using a glue component. MenuLayoutDemo Customizing Shows how to customize menu layout by changing Menu Layout the menu bar to use a top-to-bottom box layout, and the popup menu to use a left-to-right box layout. How to Use Aligns two components in different box-layoutConversionPanel.java in Panels controlled containers by setting the components' the Converter demo widths to be the same, and their containers' widths to be the same.

How to Use CardLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the 861

NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

The following figure represents a snapshot of an application that uses the CardLayout class to switch between two panels.

Click the Launch button to run CardLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The complete code of this demo is in the CardLayoutDemo.java file. The CardLayout class manages two or more components (usually JPanel instances) that share the same display space. When using the CardLayout class, let the user choose between the components by using a combo box. The CardLayoutDemo application is an example to illustrate this feature. Another way to accomplish the same task is to use a tabbed pane. The following picture shows a tabbed pane version of the preceding example:

Because a tabbed pane provides its own GUI, using a tabbed pane is simpler than using the CardLayout class. For example, implementing the preceding example using a tabbed pane results in a program with fewer lines of code. Click the Launch button to run TabDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The complete code of this demo is in the TabDemo.java file. Conceptually, each component that a CardLayout manages is like a playing card or trading card in a stack, where only the top card is visible at any time. You can choose the card that is showing in any of the following ways:   

By asking for either the first or last card, in the order it was added to the container By flipping through the deck backwards or forwards By specifying a card with a specific name 862

The CardLayoutDemo class uses the last scheme. The following code snippet from the CardLayoutDemo.java application creates the CardLayout object and the components it manages. //Where instance variables are declared: JPanel cards; final static String BUTTONPANEL = "Card with JButtons"; final static String TEXTPANEL = "Card with JTextField"; //Where the components controlled by the CardLayout are initialized: //Create the "cards". JPanel card1 = new JPanel(); ... JPanel card2 = new JPanel(); ... //Create the panel that contains the "cards". cards = new JPanel(new CardLayout()); cards.add(card1, BUTTONPANEL); cards.add(card2, TEXTPANEL); To add a component to a container that a CardLayout object manages, specify a string that identifies the component being added. For example, in this demo, the first panel has the string "Card with JButtons", and the second panel has the string "Card with JTextField". In this demo those

strings are also used in the combo box. To choose which component a CardLayout object shows, put additional code in your code example: //Where the GUI is assembled: //Put the JComboBox in a JPanel to get a nicer look. JPanel comboBoxPane = new JPanel(); //use FlowLayout String comboBoxItems[] = { BUTTONPANEL, TEXTPANEL }; JComboBox cb = new JComboBox(comboBoxItems); cb.setEditable(false); cb.addItemListener(this); comboBoxPane.add(cb); ... pane.add(comboBoxPane, BorderLayout.PAGE_START); pane.add(cards, BorderLayout.CENTER); ... //Method came from the ItemListener class implementation, //contains functionality to process the combo box item selecting public void itemStateChanged(ItemEvent evt) { CardLayout cl = (CardLayout)(cards.getLayout()); cl.show(cards, (String)evt.getItem()); } This example shows that to use the show method of the CardLayout class, you must set the currently visible component. The first argument in the show method is the container the CardLayout controls — that is, the container of the components the CardLayout manages. The second argument is the

string that identifies the component to show. This string is the same string that was used when adding the component to the container.

The CardLayout API The following table lists the CardLayout class methods that are used to choose a component. For each method, the first argument is the container for which the CardLayout is the layout manager (the container of the cards the CardLayout controls). 863

Method

Purpose

first (Container parent)

Flips to the first card of the container.

next (Container parent)

Flips to the next card of the container. If the currently visible card is the last one, this method flips to the first card in the layout.

previous (Container parent)

Flips to the previous card of the container. If the currently visible card is the first one, this method flips to the last card in the layout.

last (Container parent)

Flips to the last card of the container.

show (Container parent, String name)

Flips to the component that was added to this layout with the specified name, using the addLayoutComponent method.

Examples that Use CardLayout Only one example in this trail uses CardLayout, and this is the CardLayoutDemo. Generally, our examples use tabbed panes instead of CardLayout, since a tabbed pane provides its own GUI.

How to Use FlowLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. The FlowLayout class provides a very simple layout manager that is used, by default, by the JPanel objects. The following figure represents a snapshot of an application that uses the flow layout:

Click the Launch button to run FlowLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The complete code of this demo is in the FlowLayoutDemo.java file. The FlowLayout class puts components in a row, sized at their preferred size. If the horizontal space in the container is too small to put all the components in one row, the FlowLayout class uses multiple rows. If the container is wider than necessary for a row of components, the row is, by default, centered horizontally within the container. To specify that the row is to aligned either to the left or right, use a FlowLayout constructor that takes an alignment argument. Another constructor of the FlowLayout class specifies how much vertical or horizontal padding is put around the components. The code snippet below creates a FlowLayout object and the components it manages. 864

FlowLayout experimentLayout = new FlowLayout(); ... compsToExperiment.setLayout(experimentLayout); compsToExperiment.add(new compsToExperiment.add(new compsToExperiment.add(new compsToExperiment.add(new compsToExperiment.add(new

JButton("Button 1")); JButton("Button 2")); JButton("Button 3")); JButton("Long-Named Button 4")); JButton("5"));

Select either the Left to Right or Right to Left option and click the Apply orientation button to set up the component's orientation. The following code snippet applies the Left to Right components orientation to the experimentLayout. compsToExperiment.setComponentOrientation( ComponentOrientation.LEFT_TO_RIGHT);

The FlowLayout API The following table lists constructors of the FlowLayout class. Constructor

Purpose

FlowLayout()

Constructs a new FlowLayout object with a centered alignment and horizontal and vertical gaps with the default size of 5 pixels.

FlowLayout(int align)

Creates a new flow layout manager with the indicated alignment and horizontal and vertical gaps with the default size of 5 pixels. The alignment argument can be FlowLayout.LEADING, FlowLayout.CENTER, or FlowLayout.TRAILING. When the FlowLayout object controls a container with a left-to right component orientation (the default), the LEADING value specifies the components to be left-aligned and the TRAILING value specifies the components to be right-aligned.

FlowLayout (int align, int hgap, int vgap)

Creates a new flow layout manager with the indicated alignment and the indicated horizontal and vertical gaps. The hgap and vgap arguments specify the number of pixels to put between components.

Examples that Use FlowLayout The following table lists code examples that use the FlowLayout class and provides links to related sections. Example Where Described Notes FlowLayoutDemo This page Sets up a content pane to use FlowLayout. If you set the RIGHT_TO_LEFT constant to true and recompile, you can see how FlowLayout handles a container that has a right-toleft component orientation. CardLayoutDemo How to Use Centers a component nicely in the top part of a CardLayout BorderLayout, and puts the component in a JPanel that uses a FlowLayout. ButtonDemo How to Use Buttons, Uses the default FlowLayout of a JPanel. Check Boxes, and 865

Radio Buttons TextInputDemo How to Use

Formatted Text Fields

Uses a panel with a right-aligned FlowLayout presenting two buttons.

How to Use GridBagLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. Here is a picture of an example that uses GridBagLayout.

Click the Launch button to run GridBagLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The code for GridBagDemo is in GridBagLayoutDemo.java. GridBagLayout is one of the most flexible — and complex — layout managers the Java platform provides. A GridBagLayout places components in a grid of rows and columns, allowing specified

components to span multiple rows or columns. Not all rows necessarily have the same height. Similarly, not all columns necessarily have the same width. Essentially, GridBagLayout places components in rectangles (cells) in a grid, and then uses the components' preferred sizes to determine how big the cells should be. The following figure shows the grid for the preceding applet. As you can see, the grid has three rows and three columns. The button in the second row spans all the columns; the button in the third row spans the two right columns.

866

If you enlarge the window as shown in the following figure, you will notice that the bottom row, which contains Button 5, gets all the new vertical space. The new horizontal space is split evenly among all the columns. This resizing behavior is based on weights the program assigns to individual components in the GridBagLayout. You will also notice that each component takes up all the available horizontal space — but not (as you can see with button 5) all the available vertical space. This behavior is also specified by the program.

The way the program specifies the size and position characteristics of its components is by specifying constraints for each component. The preferred approach to set constraints on a component is to use the Container.add variant, passing it a GridBagConstraints object, as demonstrated in the next sections. The following sections explain the constraints you can set and provide examples.

Specifying Constraints The following code is typical of what goes in a container that uses a GridBagLayout. You will see a more detailed example in the next section. JPanel pane = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); //For each component to be added to this container: //...Create the component... //...Set instance variables in the GridBagConstraints instance... pane.add(theComponent, c);

As you might have guessed from the above example, it is possible to reuse the same GridBagConstraints instance for multiple components, even if the components have different constraints. However, it is recommended that you do not reuse GridBagConstraints, as this can very easily lead to you introducing subtle bugs if you forget to reset the fields for each new instance. Note: The following discussion assumes that the GridBagLayout controls a container that has a leftto-right component orientation. 867

You can set the following GridBagConstraints instance variables: gridx, gridy

Specify the row and column at the upper left of the component. The leftmost column has address gridx=0 and the top row has address gridy=0. Use GridBagConstraints.RELATIVE (the default value) to specify that the component be placed just to the right of (for gridx) or just below (for gridy) the component that was added to the container just before this component was added. We recommend specifying the gridx and gridy values for each component rather than just using GridBagConstraints.RELATIVE; this tends to result in more predictable layouts. gridwidth, gridheight

Specify the number of columns (for gridwidth) or rows (for gridheight) in the component's display area. These constraints specify the number of cells the component uses, not the number of pixels it uses. The default value is 1. Use GridBagConstraints.REMAINDER to specify that the component be the last one in its row (for gridwidth) or column (for gridheight). Use GridBagConstraints.RELATIVE to specify that the component be the next to last one in its row (for gridwidth) or column (for gridheight). We recommend specifying the gridwidth and gridheight values for each component rather than just using GridBagConstraints.RELATIVE and GridBagConstraints.REMAINDER; this tends to result in more predictable layouts. Note: GridBagLayout does not allow components to span multiple rows unless the component is in the leftmost column or you have specified positive gridx and gridy values for the component. fill

Used when the component's display area is larger than the component's requested size to determine whether and how to resize the component. Valid values (defined as GridBagConstraints constants) include NONE (the default), HORIZONTAL (make the component wide enough to fill its display area horizontally, but do not change its height), VERTICAL (make the component tall enough to fill its display area vertically, but do not change its width), and BOTH (make the component fill its display area entirely). ipadx, ipady

Specifies the internal padding: how much to add to the size of the component. The default value is zero. The width of the component will be at least its minimum width plus ipadx*2 pixels, since the padding applies to both sides of the component. Similarly, the height of the component will be at least its minimum height plus ipady*2 pixels. insets

Specifies the external padding of the component -- the minimum amount of space between the component and the edges of its display area. The value is specified as an Insets object. By default, each component has no external padding. anchor

Used when the component is smaller than its display area to determine where (within the area) to place the component. Valid values (defined as GridBagConstraints constants) are CENTER (the default), PAGE_START, PAGE_END, LINE_START, LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_END, and LAST_LINE_START. 868

Here is a picture of how these values are interpreted in a container that has the default, left-to-right component orientation. ------------------------------------------------|FIRST_LINE_START PAGE_START FIRST_LINE_END| | | | | |LINE_START CENTER LINE_END| | | | | |LAST_LINE_START PAGE_END LAST_LINE_END| -------------------------------------------------

Version note: The PAGE_* and *LINE_* constants were introduced in 1.4. Previous releases require values named after points of the compass. For example, NORTHEAST indicates the top-right part of the display area. We recommend that you use the new constants, instead, since they enable easier localization. weightx, weighty

Specifying weights is an art that can have a significant impact on the appearance of the components a GridBagLayout controls. Weights are used to determine how to distribute space among columns (weightx) and among rows (weighty); this is important for specifying resizing behavior. Unless you specify at least one non-zero value for weightx or weighty, all the components clump together in the center of their container. This is because when the weight is 0.0 (the default), the GridBagLayout puts any extra space between its grid of cells and the edges of the container. Generally weights are specified with 0.0 and 1.0 as the extremes: the numbers in between are used as necessary. Larger numbers indicate that the component's row or column should get more space. For each column, the weight is related to the highest weightx specified for a component within that column, with each multicolumn component's weight being split somehow between the columns the component is in. Similarly, each row's weight is related to the highest weighty specified for a component within that row. Extra space tends to go toward the rightmost column and bottom row. The next section discusses constraints in depth, in the context of explaining how the example program works.

The Example Explained Here, again, is a picture of the GridBagLayoutDemo application.

869

Click the Launch button to run GridBagLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

The following code creates the GridBagLayout and the components it manages. You can find the entire source file in GridBagLayoutDemo.java. JButton button; pane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); if (shouldFill) { //natural height, maximum width c.fill = GridBagConstraints.HORIZONTAL; } button = new JButton("Button 1"); if (shouldWeightX) { c.weightx = 0.5; } c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; pane.add(button, c); button = new JButton("Button 2"); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 0.5; c.gridx = 1; c.gridy = 0; pane.add(button, c); button = new JButton("Button 3"); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 0.5; c.gridx = 2; c.gridy = 0; pane.add(button, c); button = new JButton("Long-Named Button 4"); c.fill = GridBagConstraints.HORIZONTAL; c.ipady = 40; //make this component tall c.weightx = 0.0; c.gridwidth = 3; c.gridx = 0; c.gridy = 1; pane.add(button, c); button = new JButton("5"); c.fill = GridBagConstraints.HORIZONTAL; c.ipady = 0; //reset to default c.weighty = 1.0; //request any extra vertical space

870

c.anchor = GridBagConstraints.PAGE_END; //bottom of space c.insets = new Insets(10,0,0,0); //top padding c.gridx = 1; //aligned with button 2 c.gridwidth = 2; //2 columns wide c.gridy = 2; //third row pane.add(button, c);

This example uses one GridBagConstraints instance for all the components the GridBagLayout manages, however in real-life situations it is recommended that you do not reuse GridBagConstraints, as this can very easily lead to you introducing subtle bugs if you forget to reset the fields for each new instance. Just before each component is added to the container, the code sets (or resets to default values) the appropriate instance variables in the GridBagConstraints object. It then adds the component to its container, specifying the GridBagConstraints object as the second argument to the add method. For example, to make button 4 be extra tall, the example has this code: c.ipady = 40;

And before setting the constraints of the next component, the code resets the value of ipady to the default: c.ipady = 0;

If a component's display area is larger than the component itself, then you can specify whereabouts in the display area the component will be displayed by using the GridBagConstraints.anchor constraint. The anchor constraint's values can be absolute (north, south, east, west, and so on), or orientation-relative (at start of page, at end of line, at the start of the first line, and so on), or relative to the component's baseline. For a full list of the possible values of the anchor constraint, including baseline-relative values,see the API documentation for GridBagConstraints.anchor. You can see in the code extract above that Button 5 specifies that it should be displayed at the end of the display area by setting an anchor at GridBagConstraints.PAGE_END.

Note: The Tutorial's examples used to specify the constraints object a different way, which you might see in other programs as well. Rather than specifying the constraints with the add method, our examples used to invoke the setConstraints method on the GridBagLayout object. For example: GridBagLayout gridbag = new GridBagLayout(); pane.setLayout(gridbag); ... gridbag.setConstraints(button, c); pane.add(button); However, we recommend you use the Container.add method since it makes for cleaner code than if you were to use setConstraints.

Here is a table that shows all the constraints for each component in GridBagLayoutDemo's content pane. Values that are not the default are marked in boldface. Values that are different from those in the previous table entry are marked in italics. Component Constraints All components ipadx = 0 fill = GridBagConstraints.HORIZONTAL

Button 1

ipady = 0 weightx = 0.5 weighty = 0.0 gridwidth = 1 anchor = GridBagConstraints.CENTER

871

insets = new Insets(0,0,0,0) gridx = 0 gridy = 0

Button 2 Button 3 Button 4

Button 5

weightx = 0.5 gridx = 1 gridy = 0 weightx = 0.5 gridx = 2 gridy = 0 ipady = 40 weightx = 0.0 gridwidth = 3 gridx = 0 gridy = 1 ipady = 0 weightx = 0.0 weighty = 1.0 anchor = GridBagConstraints.PAGE_END insets = new Insets(10,0,0,0) gridwidth = 2 gridx = 1 gridy = 2

GridBagLayoutDemo has two components that span multiple columns (buttons 4 and 5). To make button 4 tall, we added internal padding (ipady) to it. To put space between buttons 4 and 5, we used insets to add a minimum of 10 pixels above button 5, and we made button 5 hug the bottom edge of its cell. All the components in the pane container are as wide as possible, given the cells that they occupy. The program accomplishes this by setting the GridBagConstraints fill instance variable to GridBagConstraints.HORIZONTAL, leaving it at that setting for all the components. If the program did not specify the fill, the buttons would be at their natural width, like this:

When you enlarge GridBagLayoutDemo's window, the columns grow proportionately. This is because each component in the first row, where each component is one column wide, has weightx = 1.0. The actual value of these components' weightx is unimportant. What matters is that all the components, and consequently, all the columns, have an equal weight that is greater than 0. If no component managed by the GridBagLayout had weightx set, then when the components' container was made wider, the components would stay clumped together in the center of the container, like this:

872

If the container is given a size that is smaller or bigger than the prefered size, then any space is distributed according to the GridBagContainer weights. Note that if you enlarge the window, the last row is the only one that gets taller. This is because only button 5 has weighty greater than zero.

The GridBagLayout API The GridBagLayout and GridBagConstraints classes each have only one constructor, with no arguments. Instead of invoking methods on a GridBagConstraints object, you manipulate its instance variables, as described in Specifying Constraints. Generally, the only method you invoke on a GridBagLayout object is setConstraints, as demonstrated in The Example Explained.

Examples that Use GridBagLayout You can find examples of using GridBagLayout throughout this tutorial. The following table lists a few. Example Where Described Notes GridBagLayoutDemo This section Uses many features — weights, insets, internal padding, horizontal fill, exact cell positioning, multi-column cells, and anchoring (component positioning within a cell). TextSamplerDemo Using Text Aligns two pairs of labels and text fields, plus adds a label Components across the full width of the container. ContainerEventDemo How to Write a Positions five components within a container, using Container Listener weights, fill, and relative positioning.

873

How to Use GridLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. The following figure represents a snapshot of an application that uses the GridLayout class.

Click the Launch button to run GridLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index. The complete code of this demo is in the GridLayoutDemo.java file. A GridLayout object places components in a grid of cells. Each component takes all the available space within its cell, and each cell is exactly the same size. If the GridLayoutDemo window is resized, the GridLayout object changes the cell size so that the cells are as large as possible, given the space available to the container. The code snippet below creates the GridLayout object and the components it manages. GridLayout experimentLayout = new GridLayout(0,2); ... compsToExperiment.setLayout(experimentLayout); compsToExperiment.add(new JButton("Button 1")); compsToExperiment.add(new JButton("Button 2")); compsToExperiment.add(new JButton("Button 3")); compsToExperiment.add(new JButton("Long-Named Button 4")); compsToExperiment.add(new JButton("5")); The constructor of the GridLayout class creates an instance that has two columns and as many rows

as necessary. Use combo boxes to set up how much vertical or horizontal padding is put around the components. Then click the Apply gaps button. The following code snippet shows how your selection is processed by using the setVgap and setHgap methods of the GridLayout class: applyButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){

874

//Get the horizontal gap value String horGap = (String)horGapComboBox.getSelectedItem(); //Get the vertical gap value String verGap = (String)verGapComboBox.getSelectedItem(); //Set up the horizontal gap value experimentLayout.setHgap(Integer.parseInt(horGap)); //Set up the vertical gap value experimentLayout.setVgap(Integer.parseInt(verGap)); //Set up the layout of the buttons experimentLayout.layoutContainer(compsToExperiment); } });

The GridLayout API The following table lists constructors of the GridLayout class that specify the number of rows and columns.

Constructor GridLayout(int rows, int cols)

The GridLayout class constructors Purpose Creates a grid layout with the specified number of rows and columns. All components in the layout are given equal size. One, but not both, of rows and cols can be zero, which means that any number of objects can be placed in a row or in a column. Creates a grid layout with the specified number of rows and columns.

GridLayout(int rows, In addition, the horizontal and vertical gaps are set to the specified int cols, int hgap, int values. Horizontal gaps are places between each of columns. Vertical vgap)

gaps are placed between each of the rows. The GridLayout class has two constructors:

Examples that Use GridLayout The following table lists code examples that use the GridLayout class and provides links to related sections. Example Where Described Notes GridLayoutDemo This page Uses a 2-column grid. ComboBoxDemo2 How to Use Combo One of many examples that use a 1x1 grid to make a Boxes component as large as possible. LabelDemo How to Use Labels Uses a 3-row grid. DragPictureDemo Introduction to DnD Uses a 4-row grid to present 12 components that display photographs.

875

How to Use GroupLayout roupLayout is a layout manager that was developed for GUI builders such as Matisse, the GUI

builder provided with the NetBeans IDE. Although the layout manager was originally designed to suit the GUI builder needs, it also works well for manual coding. This discussion will teach you how GroupLayout works and show you how you can use GroupLayout to build GUIs, whether you choose to use a GUI builder like Matisse or write your own code.

Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

Design Principle: Independent Dimensions GroupLayout works with the horizontal and vertical layouts separately. The layout is defined for

each dimension independently. You do not need to worry about the vertical dimension when defining the horizontal layout, and vice versa, as the layout along each axis is totally independent of the layout along the other axis. When focusing on just one dimension, you only have to solve half the problem at one time. This is easier than handling both dimensions at once. This means, of course, that each component needs to be defined twice in the layout. If you forget to do this, GroupLayout will generate an exception.

Layout Organization: Hierarchical Groups GroupLayout uses two types of arrangements -- sequential and parallel, combined with hierarchical

composition. 1. With sequential arrangement, the components are simply placed one after another, just like BoxLayout or FlowLayout would do along one axis. The position of each component is defined as being relative to the preceding component. 2. The second way places the components in parallel—on top of each other in the same space. They can be baseline-, top-, or bottom-aligned along the vertical axis. Along the horizontal axis, they can be left-, right-, or center-aligned if the components are not all the same size. Usually, components placed in parallel in one dimension are in a sequence in the other, so that they do not overlap. What makes these two arrangements powerful is that they can be nested hierarchically. For this purpose GroupLayout defines layout groups. A group is either sequential or parallel and may contain components, other groups, and gaps (discussed below). The size of a sequential group is the sum of the sizes of the contained elements, and the size of a parallel group corresponds to the size of the largest element (although, depending on the elements and where the baseline lands, the size of a baseline-aligned group may be a bit larger than the largest element). 876

Defining a layout means defining how the components should be grouped by combining the sequential and parallel arrangements. Let us use a simple example to see how it works in practice.

An Example Let us start with something simple, just three components in a row:

We will express this layout using groups. Starting with the horizontal axis it is easy to see there is a sequential group of 3 components arranged from left to right. Along the vertical axis there is a parallel group of the same 3 components with the same location, size, and baseline:

In pseudo code, the layout specification might look like this (the real code is in the Writing Code section below): horizontal layout = sequential group { c1, c2, c3 } vertical layout = parallel group (BASELINE) { c1, c2, c3 }

This illustrates a principle mentioned earlier: components grouped sequentially in one dimension usually form a parallel group in the other dimension. Now let us add one more component, C4, left-aligned with C3:

Along the horizontal axis the new component occupies the same horizontal space as C3 so that it forms a parallel group with C3. Along the vertical axis C4 forms a sequential group with the original parallel group of the three components.

In pseudo code, the layout specification now looks like this: horizontal layout = sequential group { c1, c2, parallel group (LEFT) { c3, c4 } } vertical layout = sequential group { parallel group (BASELINE) { c1, c2, c3 }, c4 }

877

Now you understand the most important aspects of designing layouts with GroupLayout. There are just a few more details to explain: how to add gaps, how to define size and resize behavior, how to define justified layout, and how to write real code.

Gaps A gap can be thought of as an invisible component of a certain size. Gaps of arbitrary size can be added to groups just like components or other groups. Using gaps you can precisely control the distance between components or from the container border. GroupLayout also defines automatic gaps that correspond to preferred distances between

neighboring components (or between a component and container border). The size of such a gap is computed dynamically based on the look and feel the application is using (the LayoutStyle class is used for this). There are two advantages to using automatic (preferred) gaps: you do not have to specify the pixel sizes of the gaps, and they automatically adjust to the look and feel the UI runs with, reflecting the actual look and feel guidelines. GroupLayout distinguishes between (a) the preferred gap between two components and (b) the

preferred gap between a component and the container border. There are corresponding methods in the GroupLayout API for adding these gaps (addPreferredGap and addContainerGap). There are three types of component gaps: related, unrelated and indented. The LayoutStyle.ComponentPlacement enum defines corresponding constants to be used as parameters of the addPreferredGap method: RELATED, UNRELATED and INDENT. The difference between related and unrelated gaps is just in size—the distance between unrelated components is a bit bigger. Indented represents a preferred horizontal distance of two components when one of them is positioned underneath the second with an indent.

As mentioned above, GroupLayout can insert gaps automatically—if you do not add your own gaps explicitly, it adds the related preferred gaps for you. This is not the default behavior, however. You have to turn this feature on by invoking setAutoCreateGaps(true) and setAutoCreateContainerGaps(true) on the GroupLayout. Then you will get correct spacing automatically.

Writing Code Now, let us take a look at the actual code to create the layout described above. Let us assume we have a container named panel and the same four components already presented (c1, c2, c3, and c4). First, we create a new GroupLayout object and associate it with the panel: GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout);

We specify automatic gap insertion: layout.setAutoCreateGaps(true); layout.setAutoCreateContainerGaps(true);

878

Then, we define the groups and add the components. We establish a root group for each dimension using the setHorizontalGroup and setVerticalGroup methods. Groups are created via createSequentialGroup and createParallelGroup methods. Components are added to groups by using the addComponent method. layout.setHorizontalGroup( layout.createSequentialGroup() .addComponent(c1) .addComponent(c2) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(c3) .addComponent(c4)) ); layout.setVerticalGroup( layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(c1) .addComponent(c2) .addComponent(c3)) .addComponent(c4) );

You can specify the alignment for parallel groups. It can be one of the following constants defined in the GroupLayout.Alignment enum: LEADING, TRAILING, CENTER, and BASELINE. These constants are used for both dimensions and depend on whether the component orientation is left-to-right or right-to-left (top-to-bottom or bottom-to-top). For example, if the horizontal (vertical) component orientation is left-to-right (top-to-bottom) LEADING means left (top) while TRAILING means right (bottom). CENTER means "centered" in both dimensions. If you do not specify the alignment, LEADING will be used. The BASELINE alignment is valid only in the vertical dimension.

Note: Alignment in the layout of a group only has meaning for components of different sizes. Components of the same size will be automatically aligned for each of the GroupLayout.Alignment constants.

Some comments about the code:  



You do not need to add the component directly to the container—that is done for you implicitly when using one of the addComponent methods. Note the chained calls of the addComponent methods used to fill the groups. The addComponent method always returns the group on which it is called. Thanks to this you do not need to use local variables to hold the groups. It is a good idea to indent the code so it is easy to see the hierarchical structure of the groups. Give each component a new line, add one level of indent for each new group in the hierarchy. A good source editor will help you with pairing the parenthesis to close the createXXXGroup methods. By following these simple rules, it is easier to add a new component or remove an existing one.

Component Size and Resizability There is no limit on the number of resizable components in a layout.

879

The size of each component in a GroupLayout is constrained by three values; minimum size, preferred size and maximum size. These sizes control how the component resizes within the layout. The GroupLayout.addComponent(...) method allows the size constraints to be specified. If not specified explicitly, the layout asks the component for its default sizes (by using the component's getMinimumSize(), getPreferredSize() and getMaximumSize() methods). You do not need to specify anything for most of the components, like making JTextField resizable or JButton fixed, because the components themselves have the desired resizing behavior as default. On the other hand you can override the default behavior. For example you can make a JTextField fixed or JButton resizable. GroupLayout defines constants that provide precise control over resize behavior. They can be used as parameters in the addComponent(Component comp, int min, int pref, int max) method.

Here are two examples: 1. To force a component to be resizable (allow shrinking and growing): 2.

group.addComponent(component, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ...

This allows the component to resize between zero size (minimum) to any size (Short.MAX_VALUE as maximum size means "infinite"). If we wanted the component not to shrink below its default minimum size, we would use GroupLayout.DEFAULT_SIZE instead of 0 in the second parameter. 3. To make a component fixed size (suppress resizing): 4.

group.addComponent(component, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 5. GroupLayout.PREFERRED_SIZE) ...

In these examples the initial size of the component is not altered, its default size is the component's preferred size. If we wanted a specific size for the component, we would specify it in the second parameter instead of using GroupLayout.DEFAULT_SIZE. Resizable gaps Specifying size and resizability applies to gaps as well, including the preferred ones. For example, you can specify a preferred gap between two components that acts like a spring pushing the components away from each other (to the opposite sides of the container). The preferred distance of the two components is only used as the minimum size of the gap. See the following snippet: layout.createSequentialGroup() .addComponent(c1) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(c2);

Sizing in Parallel Groups Resizable elements placed in a parallel group are stretched to fill the space of the group determined by the largest element in the group, so they end up aligned with the same size. GroupLayout also provides control over whether the enclosing parallel group itself should resize. If group resizing is suppressed, it prevents the contained elements from growing over the preferred size of the group.

880

This way you can make a block of components align on both sides, or constrain individual components to have the same size. Let us try to achieve the same size for two components from our example (c3 and c4 in the horizontal dimension): layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) .addComponent(c3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(c4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE);

The underlying mechanism works as follows: 1. The size of the parallel group is set to the preferred size of the largest element; so to the preferred size of c4 in our example. 2. Resizable elements are stretched to the size of the group. In our example, only c3 is effectively stretched, the size of c4 already corresponds to the size of the group. As a result, c3 and c4 would have the same width. The components would not resize further because the parallel group itself is not resizable (the second parameter of the createParallelGroup method, above, is false).

Question for attentive readers: Why do we define both components in the parallel group as resizable in this example? It seems enough to have just c3 resizable since c4 is not stretched anyway... The answer is: because of platform and localization independence. Otherwise we would have to rely on that c4 component always being bigger than c3. But this may change when the application runs on different platform or is translated to another language. By having both components resizeable they adjust to each other, no matter which one is bigger at a given moment.

Making Components the Same Size The previous case is special because the components are in the same parallel group. But what if we wanted unrelated components to have the same size? Clearly, the same size cannot always be ensured by grouping. The OK and Cancel buttons in a row at the bottom of a dialog are a good example. For this purpose GroupLayout provides a linkSize method. This method allows the size of arbitrary components to be linked regardless of where they are placed. The resulting size of the linked components is set according to the largest component. For example: layout.linkSize(SwingConstants.HORIZONTAL, c3, c4);

In this example, the size is linked selectively for the horizontal dimension.

881

Runtime Changes to Your GUI There are two important methods that you can use to make changes to your GUI at runtime, replace() and setHonorsVisibility(). Using these two methods, you can exchange components or change the visibility of components at runtime and have the GUI rearrange itself accordingly. replace(Component existingComponent, Component newComponent) replaces an existing

component with a new one. One of the common operations needed for dynamic layouts is the ability to replace components like this. For example, perhaps a check box toggles between a component displaying a graph or a tree. GroupLayout makes this scenario simple with the replace() method. You can swap components without recreating all the groups. Another common operation in user interfaces is to dynamically change the visibility of components. Perhaps components are shown only as a user completes earlier portions of a form. To avoid components shuffling around in such a scenario, space should be taken up regardless of the visibility of the components. GroupLayout offers two ways to configure how invisible components are treated. The setHonorsVisibility(boolean) method globally sets how invisible components are handled. A value of true, the default, indicates invisible components are treated as if they are not there. On the other hand, a value of false provides space for invisible components, treating them as though they were visible. The setHonorsVisibility(Component,Boolean) method can be used to configure the behavior at the level of a specific component. To determine how visibility is handled, GroupLayout first checks if a value has been specified for the Component, if not, it checks the setting of the global property.

Some history: GroupLayout in the Java Standard Edition 6 consists of three distinct bodies of work: the ability to get the baseline for a component, the ability to get the preferred gap between components (LayoutStyle), and GroupLayout. This work was originally done as an open source project at http://swing-layout.dev.java.net NetBeans 5.0 supports GroupLayout by way of the swing-layout project. Because of the success of this work, all three portions have been rolled into GroupLayout in Java Standard Edition version 6. The main difference between the GroupLayout in Java SE 6 and swing-layout is in the package name and method names. NetBeans 5.5 provides the ability to target either the GroupLayout in Java SE 6, or the GroupLayout in swing-layout. Which version NetBeans targets is determined by the version of the Java platform your project targets. A project targeting Java SE 6 uses the GroupLayout in Java SE, otherwise GroupLayout in swing-layout is used.

A GroupLayout Example Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. As an example of GUI creation with GroupLayout, let us create a layout for this "Find" dialog box:

882

Horizontal layout Examining the horizontal dimension from left to right, we can see there are 3 groups in a sequence. The first one is actually not a group, just a component -- the label. The second one is a group containing the text field and the check boxes (we will decompose it later). And the third is a group of the two buttons. As illustrated here:

Let us sketch out the sequential group in code. Note that GroupLayout.Alignment.LEADING corresponds to left alignment in the horizontal dimension. Also note we do not specify gaps, assuming the gap auto-insertion feature is turned on. layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(label) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)) );

Now let us decompose the group in the middle. This is the hardest one. There is a text field in parallel with a sequence of two parallel groups each containing two check boxes. See the following illustration:

Let us add the corresponding code: layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(label) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(textField) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(caseCheckBox) .addComponent(wholeCheckBox)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(wrapCheckBox)

883

.addComponent(backCheckBox)))) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)) );

We want the text field to be resizable, but that happens automatically since JTextField returns the right maximum size by default. The remaining group on the right is trivial: it contains just two buttons. Here is the code: layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(label) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(textField) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(caseCheckBox) .addComponent(wholeCheckBox)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(wrapCheckBox) .addComponent(backCheckBox)))) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(findButton) .addComponent(cancelButton)) );

And finally, we would like the buttons to be always the same size, so let us link them: layout.linkSize(SwingConstants.HORIZONTAL, findButton, cancelButton);

Now we are done with the horizontal dimension. Let us switch to the vertical dimension. From now, we will only need to think about the y axis.

Vertical layout In the vertical dimension, we examine the layout from top to bottom. We definitely want all the components on the first line aligned on the baseline. So along the vertical axis there is a sequence of the baseline group, followed by a group of the remaining components. See the following picture.

Let us sketch out the code. First, we need to define two parallel groups. Note that GroupLayout.Alignment.LEADING corresponds to the top alignment in the vertical dimension. layout.setVerticalGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)) );

We can fill the baseline group right away: layout.setVerticalGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)

884

.addComponent(label) .addComponent(textField) .addComponent(findButton)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)) );

Now let us look at the bottom group. Note the Cancel button is not on a shared baseline with the check boxes; it is aligned at the top. So the second parallel group comprises the button and a sequential group of two baseline groups with check boxes:

The corresponding code looks as follows: layout.setVerticalGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(label) .addComponent(textField) .addComponent(findButton)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(caseCheckBox) .addComponent(wrapCheckBox)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(wholeCheckBox) .addComponent(backCheckBox))) .addComponent(cancelButton)) );

So, we have created a complete layout, including resize behavior, without specifying a single number in pixels—a true cross platform layout. Note that we do not need to specify gaps between components, we get correct spacing automatically and according to the look and feel guidelines. Here is the complete code for the Find dialog's layout: GroupLayout layout = new GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setAutocreateGaps(true); layout.setAutocreateContainerGaps(true); layout.setHorizontalGroup(layout.createSequentialGroup() .addComponent(label) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(textField) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(caseCheckBox) .addComponent(wholeCheckBox)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(wrapCheckBox) .addComponent(backCheckBox)))) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(findButton) .addComponent(cancelButton)) ); layout.linkSize(SwingConstants.HORIZONTAL, findButton, cancelButton); layout.setVerticalGroup(layout.createSequentialGroup()

885

.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(label) .addComponent(textField) .addComponent(findButton)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(caseCheckBox) .addComponent(wrapCheckBox)) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(wholeCheckBox) .addComponent(backCheckBox))) .addComponent(cancelButton)) );

Here is the complete Find.java file. You can compile and run it. Try resizing the dialog horizontally to see how the layout automatically adjusts to the new size.

How to Use SpringLayout Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. The SpringLayout class was added in JDK version 1.4 to support layout in GUI builders. SpringLayout is a very flexible layout manager that can emulate many of the features of other layout managers. SpringLayout is, however, very low-level and as such you really should only use it with a GUI builder, rather than attempting to code a spring layout manager by hand. This section begins with a simple example showing all the things you need to remember to create your first spring layout — and what happens when you forget them! Later it presents utility methods that let you lay out components in a couple of different types of grids. Here are pictures of some of the layouts we will cover:

886

How Spring Layouts Work Spring layouts do their job by defining directional relationships, or constraints, between the edges of components. For example, you might define that the left edge of one component is a fixed distance (5 pixels, say) from the right edge of another component. In a SpringLayout, the position of each edge is dependent on the position of just one other edge. If a constraint is subsequently added to create a new binding for an edge, the previous binding is discarded and the edge remains dependent on a single edge. Unlike many layout managers, SpringLayout does not automatically set the location of the components it manages. If you hand-code a GUI that uses SpringLayout, remember to initialize component locations by constraining the west/east and north/south locations. Depending on the constraints you use, you may also need to set the size of the container explicitly. Components define edge properties, which are connected by Spring instances. Each spring has four properties — its minimum, preferred, and maximum values, and its actual (current) value. The springs associated with each component are collected into a SpringLayout.Constraints object. An instance of the Spring class holds three properties that characterize its behavior: the minimum, preferred, and maximum values. Each of these properties may be involved in defining its fourth, value, property based on a series of rules. An instance of the Spring class can be visualized as a mechanical spring that provides a corrective force as the spring is compressed or stretched away from its preferred value. This force is modelled as linear function of the distance from the preferred value, but with two different constants -- one for the compressional force and one for the tensional one. Those constants are specified by the minimum and maximum values of the spring such that a spring at its minimum value produces an equal and opposite force to that which is created when it is at its maximum value. The difference between the preferred and minimum values, therefore, represents the ease with which the spring can be compressed. The difference between its maximum and preferred values indicates the ease with which the Spring can be extended. Based on this, a SpringLayout can be visualized as a set of objects that are connected by a set of springs on their edges.

887

Example: SpringDemo This section takes you through the typical steps of specifying the constraints for a container that uses SpringLayout. The first example, SpringDemo1.java, is an extremely simple application that features a label and a text field in a content pane controlled by a spring layout. Here is the relevant code: public class SpringDemo1 { public static void main(String[] args) { ... Container contentPane = frame.getContentPane(); SpringLayout layout = new SpringLayout(); contentPane.setLayout(layout); contentPane.add(new JLabel("Label: ")); contentPane.add(new JTextField("Text field", 15)); ... frame.pack(); frame.setVisible(true); } }

Click the Launch button to run SpringDemo1 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Here is what the GUI looks like when it first comes up:

Here is what it looks like when it is resized to be bigger:

Obviously, we have some problems. Not only does the frame come up way too small, but even when it is resized the components are all located at (0,0). This happens because we have set no springs specifying the components' positions and the width of the container. One small consolation is that at least the components are at their preferred sizes — we get that for free from the default springs created by SpringLayout for each component. Our next example, SpringDemo2.java, improves the situation a bit by specifying locations for each component.Click the Launch button to run SpringDemo2 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

In this example, we will specify that the components should appear in a single row, with 5 pixels between them. The following code specifies the location of the label: //Adjust constraints for the label so it's at (5,5). layout.putConstraint(SpringLayout.WEST, label,

888

5, SpringLayout.WEST, contentPane); layout.putConstraint(SpringLayout.NORTH, label, 5, SpringLayout.NORTH, contentPane); The first putConstraint call specifies that the label's left (west) edge should be 5 pixels from its container's left edge. This translates to an x coordinate of 5. The second putConstraint call sets up

a similar relationship between the top (north) edges of the label and its container, resulting in a y coordinate of 5. Here is the code that sets up the location of the text field: //Adjust constraints for the text field so it's at //( + 5, 5). layout.putConstraint(SpringLayout.WEST, textField, 5, SpringLayout.EAST, label); layout.putConstraint(SpringLayout.NORTH, textField, 5, SpringLayout.NORTH, contentPane);

The first putConstraint call makes the text field's left (west) edge be 5 pixels away from the label's right (east) edge. The second putConstraint call is just like the second call in the first snippet, and has the same effect of setting the component's y coordinate to 5. The previous example still has the problem of the container coming up too small. But when we resize the window, the components are in the right place:

To make the container initially appear at the right size, we need to set the springs that define the right (east) and bottom (south) edges of the container itself. No constraints for the right and bottom container edges are set by default. The size of the container is defined by setting these constraints. SpringDemo3.java shows how to do this. Click the Launch button to run SpringDemo3 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Here is the code that sets the container's springs: layout.putConstraint(SpringLayout.EAST, contentPane, 5, SpringLayout.EAST, textField); layout.putConstraint(SpringLayout.SOUTH, contentPane, 5, SpringLayout.SOUTH, textField);

The first putConstraint call makes the container's right edge be 5 pixels to the right of the text field's right edge. The second one makes its bottom edge be 5 pixels beyond the bottom edge of the tallest component (which, for simplicity's sake, we've assumed is the text field).

889

Finally, the window comes up at the right size:

When we make the window larger we can see the spring layout in action, distributing the extra space between the available components.

In this case the spring layout has chosen to give all the extra space to the text field. Although it seems like the spring layout treats labels and text fields differently, spring layout has no special knowledge of any Swing or AWT components. It relies on the values of a component's minimum, preferred, and maximum size properties. The next section discusses how spring layout uses these properties, and why they can cause uneven space distribution.

Springs and Component Size A SpringLayout object automatically installs Springs for the height and width of each component that the SpringLayout controls. These springs are essentially covers for the componentÂ’s getMinimumSize, getPreferredSize, and getMaximumSize methods. By "covers" we mean that not only are the springs initialized with the appropriate values from these methods, but also that the springs track those values. For example, the Spring object that represents the width of a component is a special kind of spring that simply delegates its implementation to the relevant size methods of the component. That way the spring stays in sync with the size methods as the characteristics of the component change. When a component's getMaximumSize and getPreferredSize methods return the same value, SpringLayout interprets this as meaning that the component should not be stretched. JLabel and JButton are examples of components implemented this way. For this reason, the label in the SpringDemo3 example does not stretch. The getMaximumSize method of some components, such as JTextField, returns the value Integer.MAX_VALUE for the width and height of its maximum size, indicating that the component can grow to any size. For this reason, when the SpringDemo3 window is enlarged, SpringLayout distributes all the extra space to the only springs that can grow — those determining the size of the text field.

More About the SpringLayout API The SpringDemo examples used the SpringLayout method putConstraint to set the springs associated with each component. The putConstraint method is a convenience method that lets you modify a component's constraints without needing to use the full spring layout API. Here, again, is the code from SpringDemo3 that sets the location of the label: layout.putConstraint(SpringLayout.WEST, label,

890

5, SpringLayout.WEST, contentPane); layout.putConstraint(SpringLayout.NORTH, label, 5, SpringLayout.NORTH, contentPane); Here is equivalent code that uses the SpringLayout.Constraints and Spring classes directly: SpringLayout.Constraints contentPaneCons = layout.getConstraints(contentPane); contentPaneCons.setX(Spring.sum(Spring.constant(5), contentPaneCons .getConstraint(SpringLayout.WEST))); contentPaneCons.setY(Spring.sum(Spring.constant(5), contentPaneCons .getConstraint(SpringLayout.NORTH))); To see the entire demo converted to use this API, look at SpringDemo4.java. That file also includes

a more polished (and much longer) version of the code that sets the container's size. Click the Launch button to run SpringDemo4 using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

As the preceding snippets imply, SpringLayout and SpringLayout.Constraints tend to use different conventions for describing springs. The SpringLayout API uses edges to define its constraints. Springs connect edges to establish linear relations between them. Edges are defined by components, using the following constants:  

SpringLayout.NORTH specifies the top edge of a component's bounding rectangle. SpringLayout.SOUTH specifies the bottom edge of a component's bounding

rectangle.    

SpringLayout.EAST specifies the right edge of a component's bounding rectangle. SpringLayout.WEST specifies the left edge of a component's bounding rectangle. SpringLayout.BASELINE specifies the baseline of a component. SpringLayout.HORIZONTAL_CENTER specifies the horizontal center of a component's

bounding rectangle. 

SpringLayout.VERTICAL_CENTER specifies the vertical center of a component's

bounding rectangle. Edges differ from Spring objects The SpringLayout.Constraints class knows about edges, but only has Spring objects for the following properties:    

x y width height

Each Constraints object maintains the following relationships between its springs and the edges they represent: west north east south

= = = =

x y x + width y + height

If you are confused, do not worry. The next section presents utility methods you can use to accomplish some common layout tasks without knowing anything about the spring layout API.

891

Utility Methods for Grids Because the SpringLayout class was created for GUI builders, setting up individual springs for a layout can be cumbersome to code by hand. This section presents a couple of methods you can use to install all the springs needed to lay out a group of components in a grid. These methods emulate some of the features of the GridLayout, GridBagLayout, and BoxLayout classes. The two methods, called makeGrid and makeCompactGrid, are defined in SpringUtilities.java. Both methods work by grouping the components together into rows and columns and using the Spring.max method to make a width or height spring that makes a row or column big enough for all the components in it. In the makeCompactGrid method the same width or height spring is used for all components in a particular column or row, respectively. In the makeGrid method, by contrast, the width and height springs are shared by every component in the container, forcing them all to be the same size. Furthermore, factory methods are provided by Spring for creating different kinds of springs, including springs that depend on other springs. Let us see these methods in action. Our first example, implemented in the source file SpringGrid.java, displays a bunch of numbers in text fields. The center text field is much wider than the others. Just as with GridLayout, having one large cell forces all the cells to be equally large. Click the Launch button to run SpringGrid using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Here is the code that creates and lays out the text fields in SpringGrid: JPanel panel = new JPanel(new SpringLayout()); for (int i = 0; i < 9; i++) { JTextField textField = new JTextField(Integer.toString(i)); ...//when i==4, put long text in the text field... panel.add(textField); } ... SpringUtilities.makeGrid(panel, 3, 3, //rows, cols 5, 5, //initialX, initialY 5, 5);//xPad, yPad

Now let us look at an example, in the source file SpringCompactGrid.java, that uses the makeCompactGrid method instead of makeGrid. This example displays lots of numbers to show off spring layout's ability to minimize the space required. Click the Launch button to run SpringCompactGrid using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Here is what the SpringCompactGrid GUI looks like: 892

Here is the code that creates and lays out the text fields in SpringCompactGrid: JPanel panel = new JPanel(new SpringLayout()); int rows = 10; int cols = 10; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { int anInt = (int) Math.pow(r, c); JTextField textField = new JTextField(Integer.toString(anInt)); panel.add(textField); } } //Lay out the panel. SpringUtilities.makeCompactGrid(panel, //parent rows, cols, 3, 3, //initX, initY 3, 3); //xPad, yPad

One of the handiest uses for the makeCompactGrid method is associating labels with components, where the labels are in one column and the components in another. The file SpringForm.java uses makeCompactGrid in this way, as the following figure demonstrates.

Click the Launch button to run SpringForm using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Here is the code that creates and lays out the label-text field pairs in SpringForm: 893

String[] labels = {"Name: ", "Fax: ", "Email: ", "Address: "}; int numPairs = labels.length; //Create and populate the panel. JPanel p = new JPanel(new SpringLayout()); for (int i = 0; i < numPairs; i++) { JLabel l = new JLabel(labels[i], JLabel.TRAILING); p.add(l); JTextField textField = new JTextField(10); l.setLabelFor(textField); p.add(textField); } //Lay out the panel. SpringUtilities.makeCompactGrid(p, numPairs, 2, //rows, cols 6, 6, //initX, initY 6, 6); //xPad, yPad

Because we are using a real layout manager instead of absolute positioning, the layout manager responds dynamically to changes in components involved. For example, if the names of the labels are localized, the spring layout produces a configuration that gives the first column more or less room, as needed. And as the following figure shows, when the window is resized, the flexibly sized components — the text fields — take all the excess space, while the labels stick to what they need.

Our last example of the makeCompactGrid method, in SpringBox.java, shows some buttons configured to be laid out in a single row. Click the Launch button to run SpringBox using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Note that the behavior is almost identical to that of BoxLayout in the case of a single row. Not only are the components laid out as BoxLayout would arrange them but the minimum, preferred, and maximum sizes of the container that uses the SpringLayout return the same results that BoxLayout would. Here is the call to makeCompactGrid that produces this layout: //Lay out the buttons in one row and as many columns //as necessary, with 6 pixels of padding all around. SpringUtilities.makeCompactGrid(contentPane, 1, contentPane.getComponentCount(),

894

6, 6, 6, 6);

Let us look at what happens when we resize this window. This is an odd special case that is worth taking note of as you may run into it by accident in your first layouts.

Nothing moved! That is because none of the components (buttons) or the spacing between them was defined to be stretchable. In this case the spring layout calculates a maximum size for the parent container that is equal to its preferred size, meaning the parent container itself is not stretchable. It would perhaps be less confusing if the AWT refused to resize a window that was not stretchable, but it does not. The layout manager cannot do anything sensible here as none of the components will take up the required space. Instead of crashing, it just does nothing, leaving all the components as they were.

The SpringLayout API The API for using SpringLayout is spread across three classes:   

SpringLayout SpringLayout.Constraints Spring

SpringLayout Constructor or Method

Purpose

SpringLayout()

Create a SpringLayout instance.

SpringLayout.Constraints getConstraints(Component)

Get the constraints (set of springs) associated with the specified component.

Spring getConstraint(String, Component)

Get the spring for an edge of a component. The first argument specifies the edge and must be one of the following SpringLayout constants: NORTH, SOUTH, EAST, or WEST.

void putConstraint(String, Component, int, String, Component) void putConstraint(String, Component, Spring, String, Component)

Convenience methods for defining relationships between the edges of two components. The first two arguments specify the first component and its affected edge. The last two arguments specify the second component and its affected edge. The third argument specifies the spring that determines the distance between the two. When the third argument is an integer, a constant spring is created to provide a fixed distance between the component edges. SpringLayout.Constraints

Constructor or Method SpringLayout.Constraints() SpringLayout.Constraints(Spring, Spring)

Purpose Create a SpringLayout.Constraints instance. The first two arguments, if present, specify the X and Y springs, respectively. The second two arguments, if present, specify the 895

SpringLayout.Constraints(Spring, Spring, Spring, Spring) Spring getConstraint(String) Spring getHeight() Spring getWidth() Spring getX() Spring getY() void setConstraint(String, Spring) void setHeight(Spring) void setWidth(Spring) void setX(Spring) void setY(Spring)

height and width springs, respectively. Omitting an argument causes the corresponding spring to be null, which SpringLayout generally replaces with suitable defaults.

Get or set the specified spring. The string argument to the getConstraint and setConstraint methods specifies an edge name, and must be one of the SpringLayout constants NORTH, SOUTH, EAST, or WEST.

Spring Method

static Spring constant(int) static Spring constant(int, int, int)

static Spring sum(Spring, Spring) static Spring max(Spring, Spring) static Spring minus(Spring)

Purpose Create a spring that does not track a component's sizes. The three-argument version creates a spring with its minimum, preferred, and maximum values set to the specified values, in that order. The one-argument version creates a spring with its minimum, preferred, and maximum values all set to the specified integer. Despite the name, springs returned by constant are mutable. To make a layout work out, SpringLayout might be forced to adjust a "constant" spring. For this reason, you should avoid reusing constant springs unless (1) you truly want the springs to always be precisely alike and (2) other springs provide some flexibility in the layout. Create a spring that is the result of some mathematical manipulation. The sum method adds two springs. The max method returns a spring whose value is always greater than or equal to the values of the two arguments. The minus method returns a spring running in the opposite direction of the argument. The minus method can be used to create an argument for the sum method, allowing you to get the difference between two springs.

int Get the corresponding value from the spring. For a SpringLayout-created getMinimumValue() int getPreferredValue() spring that automatically tracks a component, these methods result in calls int to the component's corresponding getXxxSize method. getMaximumValue() int getValue() setValue(int)

Get or set the spring's current value.

Examples that Use SpringLayout The following table lists some examples that use spring layout. Where Example Notes Described SpringDemo3 This page Uses SpringLayout to create a row of evenly spaced, naturalsize components. SpringDemo4 This page Reimplements SpringDemo3 to use 896

SpringLayout.Constraints and Spring directly. SpringGrid

This page

Uses SpringLayout and the makeGrid utility method to create a layout where all the components are the same size. SpringCompactGrid This page Uses SpringLayout and the makeCompactGrid utility method to create a layout where all the components in a row have the same height, and all components in a column have the same width. SpringForm This page Uses SpringLayout and makeCompactGrid to align label-text field pairs. SpringBox This page Uses SpringLayout and makeCompactGrid to demonstrate laying out a single row of components, and what happens when no springs can grow. SpinnerDemo How to Use Uses SpringLayout and makeCompactGrid to lay out rows of Spinners label-spinner pairs. TextInputDemo How to Use Uses SpringLayout and makeCompactGrid to lay out rows of Formatted Text labeled components. The components are a mix of text fields, Fields formatted text fields, and spinners.

Creating a Custom Layout Manager Before you start creating a custom layout manager, make sure that no existing layout manager will meet your requirements. In particular, layout managers such as GridBagLayout, SpringLayout, and BoxLayout are flexible enough to work in many cases. You can also find layout managers from other sources, such as from the Internet. Finally, you can simplify layout by grouping components into containers such as panels. Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

To create a custom layout manager, you must create a class that implements the LayoutManager interface. You can either implement it directly, or implement its subinterface, LayoutManager2. Every layout manager must implement at least the following five methods, which are required by the LayoutManager interface: void addLayoutComponent(String, Component) Called by the Container class's add methods. Layout managers that do not associate strings

with their components generally do nothing in this method. void removeLayoutComponent(Component) Called by the Container methods remove and removeAll. Layout managers override this method to clear an internal state they may have associated with the Component. Dimension preferredLayoutSize(Container) Called by the Container class's getPreferredSize method, which is itself called under a

variety of circumstances. This method should calculate and return the ideal size of the container, assuming that the components it contains will be at or above their preferred sizes. 897

This method must take into account the container's internal borders, which are returned by the getInsets method. Dimension minimumLayoutSize(Container) Called by the Container getMinimumSize method, which is itself called under a variety of

circumstances. This method should calculate and return the minimum size of the container, assuming that the components it contains will be at or above their minimum sizes. This method must take into account the container's internal borders, which are returned by the getInsets method. void layoutContainer(Container)

Called to position and size each of the components in the container. A layout manager's layoutContainer method does not actually draw components. It simply invokes one or more of each component's setSize, setLocation, and setBounds methods to set the component's size and position. This method must take into account the container's internal borders, which are returned by the getInsets method. If appropriate, it should also take the container's orientation (returned by the getComponentOrientation method) into account. You cannot assume that the preferredLayoutSize or minimumLayoutSize methods will be called before layoutContainer is called. Besides implementing the preceding five methods, layout managers generally implement at least one public constructor and the toString method. If you wish to support component constraints, maximum sizes, or alignment, then your layout manager should implement the LayoutManager2 interface. In fact, for these reasons among many others, nearly all modern layout managers will need to implement LayoutManager2. That interface adds five methods to those required by LayoutManager:     

addLayoutComponent(Component, Object) getLayoutAlignmentX(Container) getLayoutAlignmentY(Container) invalidateLayout(Container) maximumLayoutSize(Container)

Of these methods, the most important are addLayoutComponent(Component, Object) and invalidateLayout(Container). The addLayoutComponent method is used to add components to the layout, using the specified constraint object. The invalidateLayout method is used to invalidate the layout, so that if the layout manager has cached information, this should be discarded. For more information about LayoutManager2, see the LayoutManager2 API documentation. Finally, whenever you create custom layout managers, you should be careful of keeping references to Component instances that are no longer children of the Container. Namely, layout managers should override removeLayoutComponent to clear any cached state related to the Component.

Example of a Custom Layout The example CustomLayoutDemo uses a custom layout manager called DiagonalLayout. You can find the layout manager's source code in DiagonalLayout.java. DialogLayout lays out components diagonally, from left to right, with one component per row. Here is a picture of CustomLayoutDemo using DialogLayout to lay out five buttons.

898

Click the Launch button to run CustomLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Doing Without a Layout Manager (Absolute Positioning) Although it is possible to do without a layout manager, you should use a layout manager if at all possible. A layout manager makes it easier to adjust to look-and-feel-dependent component appearances, to different font sizes, to a container's changing size, and to different locales. Layout managers also can be reused easily by other containers, as well as other programs. Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager.

If a container holds components whose size is not affected by the container's size or by font, lookand-feel, or language changes, then absolute positioning might make sense. Desktop panes, which contain internal frames, are in this category. The size and position of internal frames does not depend directly on the desktop pane's size. The programmer determines the initial size and placement of internal frames within the desktop pane, and then the user can move or resize the frames. A layout manager is unnecessary in this situation. Another situation in which absolute positioning might make sense is that of a custom container that performs size and position calculations that are particular to the container, and perhaps require knowledge of the container's specialized state. This is the situation with split panes. Creating a container without a layout manager involves the following steps. 1. Set the container's layout manager to null by calling setLayout(null). 2. Call the Component class's setbounds method for each of the container's children. 3. Call the Component class's repaint method. However, creating containers with absolutely positioned containers can cause problems if the window containing the container is resized. Here is a snapshot of a frame whose content pane uses absolute positioning.

899

Click the Launch button to run AbsoluteLayoutDemo using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Its code is in AbsoluteLayoutDemo.java. The following code snippet shows how the components in the content pane are created and laid out. pane.setLayout(null); JButton b1 = new JButton("one"); JButton b2 = new JButton("two"); JButton b3 = new JButton("three"); pane.add(b1); pane.add(b2); pane.add(b3); Insets insets = pane.getInsets(); Dimension size = b1.getPreferredSize(); b1.setBounds(25 + insets.left, 5 + insets.top, size.width, size.height); size = b2.getPreferredSize(); b2.setBounds(55 + insets.left, 40 + insets.top, size.width, size.height); size = b3.getPreferredSize(); b3.setBounds(150 + insets.left, 15 + insets.top, size.width + 50, size.height + 20); ...//In the main method: Insets insets = frame.getInsets(); frame.setSize(300 + insets.left + insets.right, 125 + insets.top + insets.bottom);

Solving Common Layout Problems Note: This lesson covers writing layout code by hand, which can be challenging. If you are not interested in learning all the details of layout management, you might prefer to use the GroupLayout layout manager combined with a builder tool to lay out your GUI. One such builder tool is the NetBeans IDE. Otherwise, if you want to code by hand and do not want to use GroupLayout, then GridBagLayout is recommended as the next most flexible and powerful layout manager. Problem: How do I specify a component's exact size? 

A handful of the more modern layout managers provide ways to override the size set by the component. Check whether the layout manager you are using allows you to specify component sizes. 900







 

Make sure that you really need to set the component's exact size. Each Swing component has a different preferred size, depending on the font it uses and the look and feel. For this reason, it often does not make sense to specify a Swing component's exact size. If the component is not controlled by a layout manager, you can set its size by invoking the setSize or setBounds method on it. Otherwise, you need to provide size hints and then make sure you are using a layout manager that respects the size hints. If you extend a Swing component class, you can give size hints by overriding the component's getMinimumSize, getPreferredSize, and getMaximumSize methods. What is nice about this approach is that each getXxxxSize method can get the component's default size hints by invoking super.getXxxxSize(). Then it can adjust the size, if necessary, before returning it. This is particularly handy for text components, where you might want to fix the width, but have the height determined from the content. However, sometimes problems can be encountered with GridBagLayout and text fields, wherein if the size of the container is smaller than the preferred size, the minimum size gets used, which can cause text fields to shrink quite substantially. Another way to give size hints is to invoke the component's setMinimumSize, setPreferredSize, and setMaximumSize methods. If you specify new size hints for a component that is already visible, you then need to invoke the revalidate method on it, to make sure that its containment hierarchy is laid out again. Then invoke the repaint method.

Note: No matter how you specify your component's size, be sure that your component's container uses a layout manager that respects the requested size of the component. The FlowLayout and GridBagLayout managers use the component's preferred size (the latter depending on the constraints that you set), but BorderLayout and GridLayout usually do not. The BoxLayout manager generally uses a component's preferred size (although components can be larger), and is one of the few layout managers that respects the component's maximum size.

Problem: My component does not appear after I have added it to the container. 

You need to invoke revalidate and repaint after adding a component before it will show up in your container.

Problem: My custom component is being sized too small.  

Does the component implement the getPreferredSize and getMinimumSize methods? If so, do they return the right values? Are you using a layout manager that can use as much space as is available? See Tips on Choosing a Layout Manager for some tips on choosing a layout manager and specifying that it use the maximum available space for a particular component.

If you do not see your problem in this list, see Solving Common Component Problems. Questions and Exercises: Laying Out Components within a Container

Questions In each of the following questions, choose the layout manager(s) most naturally suited for the described layout. Assume that the container controlled by the layout manager is a JPanel. [Hint: 901

Two sections that might help are A Visual Index to Swing Components and Tips on Choosing a Layout Manager.] 1. The container has one component that should take up as much space as possible

a. BorderLayout b. GridLayout c. GridBagLayout d. a and b e. b and c 2. The container has a row of components that should all be displayed at the same size, filling the container’s entire area.

a. FlowLayout b. GridLayout c. BoxLayout d. a and b

3. The container displays a number of components in a column, with any extra space going between the first two components.

902

a. FlowLayout b. BoxLayout c. GridLayout d. BorderLayout 4. The container can display three completely different components at different times, depending perhaps on user input or program state. Even if the components’ sizes differ, switching from one component to the next shouldn’t change the amount of space devoted to the component.

a. SpringLayout b. BoxLayout c. CardLayout d. GridBagLayout

Exercises 1. Implement the layout described and shown in question 1. 2. Implement the layout described and shown in question 2. 3. Implement the layout described and shown in question 3. 4. Implement the layout described and shown in question 4. 5. By adding a single line of code, make the program you wrote for Exercise 2 display the components from right-to-left, instead of from left-to-right.

Answers: Laying Out Components within a Container 903

Questions In each of the following questions, choose the layout manager(s) most naturally suited for the described layout. Assume that the container controlled by the layout manager is a JPanel. [Hint: Two sections that might help are A Visual Index to Swing Components and Tips on Choosing a Layout Manager.] Question 1. The container has one component that should take up as much space as possible

a. BorderLayout b. GridLayout c. GridBagLayout d. a and b e. b and c Answer 1: d. BorderLayout and GridLayout easily deal with this situation. Although you could use GridBagLayout, it's much more complex than necessary.

Question 2. The container has a row of components that should all be displayed at the same size, filling the container’s entire area.

a. FlowLayout b. GridLayout c. BoxLayout d. a and b

Answer 2: b. This type of same-size layout — whether in a row, a column, or a grid — is what GridLayout is best at. 904

Question 3. The container displays a number of components in a column, with any extra space going between the first two components.

a. FlowLayout b. BoxLayout c. GridLayout d. BorderLayout Answer 3: b. BoxLayout lays out components in either a column or a row. You can specify extra space using an invisible component.

Question 4. The container can display three completely different components at different times, depending perhaps on user input or program state. Even if the components’ sizes differ, switching from one component to the next shouldn’t change the amount of space devoted to the component.

a. SpringLayout b. BoxLayout c. CardLayout d. GridBagLayout

905

Answer 4: c. CardLayout exists to allow components to share the same space. Although it's simpler to use a JTabbedPane component to control an area, CardLayout is the solution when you don't want tabs.

Exercises Exercise 1. Implement the layout described and shown in question 1. Answer 1: See Layout1.java . Here's the code that implements the layout: JPanel p = new JPanel(new BorderLayout()); p.add(createComponent("Component 1"), BorderLayout.CENTER); frame.setContentPane(p);

Exercise 2. Implement the layout described and shown in question 2. Answer 2: See Layout2.java . Here's the code that implements the layout: JPanel p = new JPanel(new GridLayout(1,0)); p.add(createComponent("Component 1")); p.add(createComponent("Component 2")); p.add(createComponent("Component 3")); p.add(createComponent("Component 4")); frame.setContentPane(p);

Exercise 3. Implement the layout described and shown in question 3. Answer 3: See Layout3.java . Here's the code that implements the layout: JPanel p = new JPanel(); p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS)); p.add(createComponent("Component 1")); p.add(Box.createVerticalGlue()); p.add(createComponent("Component 2")); p.add(createComponent("Component 3")); p.add(createComponent("Component 4")); frame.setContentPane(p);

Exercise 4. Implement the layout described and shown in question 4. Answer 4: See Layout4.java . Here's the code that implements the layout: ...//Where the radio buttons are set up: for (int i= 0; i < strings.length; i++) { ... rb[i].setActionCommand(String.valueOf(i)); ... } ...//Where the panel to contain the shared-space components is set up: cards = new JPanel(new CardLayout()); for (int i = 0; i < strings.length; i++) { cards.add(createComponent(strings[i]), String.valueOf(i)); }

906

...//In the action listener for the radio buttons: public void actionPerformed(ActionEvent evt) { CardLayout cl = (CardLayout)(cards.getLayout()); cl.show(cards, (String)evt.getActionCommand()); }

Exercise 5. By adding a single line of code, make the program you wrote for Exercise 2 display the components from right-to-left, instead of from left-to-right.

Answer 5: You can change the horizontal orientation using the setComponentOrientation method defined by the Component class. For example: p.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);

907

Lesson: Modifying the Look and Feel How to Set the Look and Feel The architecture of Swing is designed so that you may change the "look and feel" (L&F) of your application's GUI (see A Swing Architecture Overview). "Look" refers to the appearance of GUI widgets (more formally, JComponents) and "feel" refers to the way the widgets behave. Swing's architecture enables multiple L&Fs by separating every JComponent into two distinct classes: a JComponent subclass and a corresponding ComponentUI subclass. For example, every JList instance has a concrete implementation of ListUI (ListUI extends ComponentUI). The ComponentUI subclass is referred to by various names in Swing's documentation—"the UI," "component UI," "UI delegate," and "look and feel delegate" are all used to identify the ComponentUI subclass. Most developers never need to interact with the UI delegate directly. For the most part, the UI delegate is used internally by the JComponent subclass for crucial functionality, with cover methods provided by the JComponent subclass for all access to the UI delegate. For example, all painting in JComponent subclasses is delegated to the UI delegate. By delegating painting, the 'look' can vary depending upon the L&F. It is the responsibility of each L&F to provide a concrete implementation for each of the ComponentUI subclasses defined by Swing. For example, the Java Look and Feel creates an instance of MetalTabbedPaneUI to provide the L&F for JTabbedPane. The actual creation of the UI delegate is handled by Swing for you—for the most part you never need to interact directly with the UI delegate. The rest of this section discusses the following subjects:         

Available Look and Feels Programatically Setting the Look and Feel Specifying the Look and Feel: Command Line Specifying the Look and Feel: swing.properties How the UI Manager Chooses the Look and Feel Changing the Look and Feel After Startup An Example Themes The SwingSet2 Demonstration Program

Available Look and Feels Sun's JRE provides the following L&Fs: 1. CrossPlatformLookAndFeel—this is the "Java L&F" (also called "Metal") that looks the same on all platforms. It is part of the Java API (javax.swing.plaf.metal) and is the default that will be used if you do nothing in your code to set a different L&F. 2. SystemLookAndFeel—here, the application uses the L&F that is native to the system it is running on. The System L&F is determined at runtime, where the application asks the system to return the name of the appropriate L&F. 908

3. Synth—the basis for creating your own look and feel with an XML file. 4. Multiplexing— a way to have the UI methods delegate to a number of different look and feels at the same time. For Linux and Solaris, the System L&Fs are "GTK+" if GTK+ 2.2 or later is installed, "Motif" otherwise. For Windows, the System L&F is "Windows," which mimics the L&F of the particular Windows OS that is running—classic Windows, XP, or Vista. The GTK+, Motif, and Windows L&Fs are provided by Sun and shipped with the Java SDK and JRE, although they are not part of the Java API. Apple provides its own JVM which includes their proprietary L&F. In summary, when you use the SystemLookAndFeel, this is what you will see: Platform

Look and Feel

Solaris, Linux with GTK+ 2.2 or later GTK+ Other Solaris, Linux

Motif

IBM UNIX

IBM*

HP UX

HP*

Classic Windows

Windows

Windows XP

Windows XP

Windows Vista

Windows Vista

Macintosh

Macintosh*

* Supplied by the system vendor. You don't see the System L&F in the API. The GTK+, Motif, and Windows packages that it requires are shipped with the Java SDK as: com.sun.java.swing.plaf.gtk.GTKLookAndFeel com.sun.java.swing.plaf.motif.MotifLookAndFeel com.sun.java.swing.plaf.windows.WindowsLookAndFeel Note that the path includes java, and not javax.

Note: The GTK+ L&F will only run on UNIX or Linux systems with GTK+ 2.2 or later installed, while the Windows L&F runs only on Windows systems. Like the Java (Metal) L&F, the Motif L&F will run on any platform.

All of Sun's L&Fs have a great deal of commonality. This commonality is defined in the Basic look and feel in the API (javax.swing.plaf.basic). The Motif and Windows L&Fs are each built by extending the UI delegate classes in javax.swing.plaf.basic (a custom L&F can be built by doing the same thing). The "Basic" L&F is not used without being extended. In the API you will see four L&F packages: 

javax.swing.plaf.basic—basic UI Delegates to be extended when creating a

custom L&F 909







javax.swing.plaf.metal—the Java L&F, also known as the CrossPlatform L&F

("Metal" was the Sun project name for this L&F) The current default "theme" (discussed below) for this L&F is "Ocean, so this is often referred to as the Ocean L&F. javax.swing.plaf.multi—a multiplexing L&F that allows the UI methods to delegate to a number of look and feels at the same time. It can be used to augment the behavior of a particular L&F, for example with a L&F that provides audio cues on top of the Windows L&F. This is a way of creating a handicapped-accessible L&F. javax.swing.plaf.synth—an easily configured L&F using XML files (discussed in the next section of this lesson)

You aren't limited to the L&Fs supplied with the Java platform. You can use any L&F that is in your program's class path. External L&Fs are usually provided in one or more JAR files that you add to your program's class path at runtime. For example: java -classpath .;C:\java\lafdir\customlaf.jar YourSwingApplication

Once an external L&F is in your program's class path, your program can use it just like any of the L&Fs shipped with the Java platform.

Programatically Setting the Look and Feel Note: If you are going to set the L&F, you should do it as the very first step in your application. Otherwise you run the risk of initializing the Java L&F regardless of what L&F you've requested. This can happen inadvertently when a static field references a Swing class, which causes the L&F to be loaded. If no L&F has yet been specified, the default L&F for the JRE is loaded. For Sun's JRE the default is the Java L&F, for Apple's JRE the Apple L&F, and so forth.

The L&F that Swing components use is specified by way of the UIManager class in the javax.swing package. Whenever a Swing component is created,the component asks the UI manager for the UI delegate that implements the component's L&F. For example, each JLabel constructor queries the UI manager for the UI delegate object appropriate for the label. It then uses that UI delegate object to implement all of its drawing and event handling. To programatically specify a L&F, use the UIManager.setLookAndFeel() method with the fully qualified name of the appropriate subclass of LookAndFeel as its argument. For example, the bold code in the following snippet makes the program use the cross-platform Java L&F: public static void main(String[] args) { try { // Set cross-platform Java L&F (also called "Metal") UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException e) { // handle exception } catch (ClassNotFoundException e) { // handle exception } catch (InstantiationException e) { // handle exception }

910

catch (IllegalAccessException e) { // handle exception } new SwingApplication(); //Create and show the GUI. }

Alternatively, this code makes the program use the System L&F: public static void main(String[] args) { try { // Set System L&F UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException e) { // handle exception } catch (ClassNotFoundException e) { // handle exception } catch (InstantiationException e) { // handle exception } catch (IllegalAccessException e) { // handle exception } new SwingApplication(); //Create and show the GUI. }

You can also use the actual class name of a Look and Feel as the argument to UIManager.setLookAndFeel(). For example, // Set cross-platform Java L&F (also called "Metal") UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

or // Set Motif L&F on any platform UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");

You aren't limited to the preceding arguments. You can specify the name for any L&F that is in your program's class path.

Specifying the Look and Feel: Command Line You can specify the L&F at the command line by using the -D flag to set the swing.defaultlaf property. For example: java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel MyApp java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel MyApp

Specifying the Look and Feel: swing.properties File Yet another way to specify the current L&F is to use the swing.properties file to set the swing.defaultlaf property. This file, which you may need to create, is located in the lib directory of Sun's Java release (other vendors of Java may use a different location). For example, if you're using the Java interpreter in javaHomeDirectory\bin, then the swing.properties file (if it exists) is in javaHomeDirectory\lib. Here is an example of the contents of a swing.properties file: 911

# Swing properties swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

How the UI Manager Chooses the Look and Feel Here are the look-and-feel determination steps that occur when the UI manager needs to set a L&F: 1. If the program sets the L&F before a look and feel is needed, the UI manager tries to create an instance of the specified look-and-feel class. If successful, all components use that L&F. 2. If the program hasn't successfully specified a L&F, then the UI manager uses the L&F specified by the swing.defaultlaf property. If the property is specified in both the swing.properties file and on the command line, the command-line definition takes precedence. 3. If none of these steps has resulted in a valid L&F, Sun's JRE uses the Java L&F. Other vendors, such as Apple, will use their default L&F.

Changing the Look and Feel After Startup You can change the L&F with setLookAndFeel even after the program's GUI is visible. To make existing components reflect the new L&F, invoke the SwingUtilities updateComponentTreeUI method once per top-level container. Then you might wish to resize each top-level container to reflect the new sizes of its contained components. For example: UIManager.setLookAndFeel(lnfName); SwingUtilities.updateComponentTreeUI(frame); frame.pack();

An Example In the following example, LookAndFeelDemo.java, you can experiment with different Look and Feels. The program creates a simple GUI with a button and a label. Every time you click the button, the label increments. You can change the L&F by changing the LOOKANDFEEL constant on line 18. The comments on the preceding lines tell you what values are acceptable: // Specify the look and feel to use by defining the LOOKANDFEEL constant // Valid values are: null (use the default), "Metal", "System", "Motif", // and "GTK" final static String LOOKANDFEEL = "Motif";

Here the constant is set to "Motif", which is a L&F that will run on any platform (as will the default, "Metal"). "GTK+" will not run on Windows, and "Windows" will run only on Windows. If you choose a L&F that will not run, you will get the Java, or Metal, L&F. In the section of the code where the L&F is actually set, you will see several different ways to set it, as discussed above: if (LOOKANDFEEL.equals("Metal")) { lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); // an alternative way to set the Metal L&F is to replace the // previous line with: // lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";

You can verify that both arguments work by commenting/un-commenting the two alternatives. 912

Here is a listing of the LookAndFeelDemo source file: package lookandfeel; import import import import

javax.swing.*; java.awt.*; java.awt.event.*; javax.swing.plaf.metal.*;

public class LookAndFeelDemo implements ActionListener { private static String labelPrefix = "Number of button clicks: "; private int numClicks = 0; final JLabel label = new JLabel(labelPrefix + "0 "); // Specify the look and feel to use by defining the LOOKANDFEEL constant // Valid values are: null (use the default), "Metal", "System", "Motif", // and "GTK" final static String LOOKANDFEEL = "Metal"; // If you choose the Metal L&F, you can also choose a theme. // Specify the theme to use by defining the THEME constant // Valid values are: "DefaultMetal", "Ocean", and "Test" final static String THEME = "Test";

public Component createComponents() { JButton button = new JButton("I'm a Swing button!"); button.setMnemonic(KeyEvent.VK_I); button.addActionListener(this); label.setLabelFor(button); JPanel pane = new JPanel(new GridLayout(0, 1)); pane.add(button); pane.add(label); pane.setBorder(BorderFactory.createEmptyBorder( 30, //top 30, //left 10, //bottom 30) //right ); return pane; } public void actionPerformed(ActionEvent e) { numClicks++; label.setText(labelPrefix + numClicks); } private static void initLookAndFeel() { String lookAndFeel = null; if (LOOKANDFEEL != null) { if (LOOKANDFEEL.equals("Metal")) { lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); // an alternative way to set the Metal L&F is to replace the // previous line with: // lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel"; } else if (LOOKANDFEEL.equals("System")) {

913

lookAndFeel = UIManager.getSystemLookAndFeelClassName(); } else if (LOOKANDFEEL.equals("Motif")) { lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; } else if (LOOKANDFEEL.equals("GTK")) { lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; } else { System.err.println("Unexpected value of LOOKANDFEEL specified: " + LOOKANDFEEL); lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); } try {

UIManager.setLookAndFeel(lookAndFeel); // If L&F = "Metal", set the theme if (LOOKANDFEEL.equals("Metal")) { if (THEME.equals("DefaultMetal")) MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); else if (THEME.equals("Ocean")) MetalLookAndFeel.setCurrentTheme(new OceanTheme()); else MetalLookAndFeel.setCurrentTheme(new TestTheme()); UIManager.setLookAndFeel(new MetalLookAndFeel()); }

} catch (ClassNotFoundException e) { System.err.println("Couldn't find class for specified look and feel:" + lookAndFeel); System.err.println("Did you include the L&F library in the class path?"); System.err.println("Using the default look and feel."); } catch (UnsupportedLookAndFeelException e) { System.err.println("Can't use the specified look and feel (" + lookAndFeel + ") on this platform."); System.err.println("Using the default look and feel."); } catch (Exception e) { System.err.println("Couldn't get specified look and feel (" + lookAndFeel + "), for some reason."); System.err.println("Using the default look and feel."); e.printStackTrace(); } }

914

} private static void createAndShowGUI() { //Set the look and feel. initLookAndFeel(); //Make sure we have nice window decorations. JFrame.setDefaultLookAndFeelDecorated(true); //Create and set up the window. JFrame frame = new JFrame("SwingApplication"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); LookAndFeelDemo app = new LookAndFeelDemo(); Component contents = app.createComponents(); frame.getContentPane().add(contents, BorderLayout.CENTER); //Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { //Schedule a job for the event dispatch thread: //creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } }

Themes Themes were introduced as a way of easily changing the colors and fonts of the cross-platform Java (Metal) Look and Feel. In the sample program, LookAndfeelDemo.java, listed above, you can change the theme of the Metal L&F by setting the THEME constant on line 23 to one of three values:   

DefaultMetal Ocean Test

Ocean, which is a bit softer than the pure Metal look, has been the default theme for the Metal (Java) L&F since Java SE 5. Despite its name, DefaultMetal is not the default theme for Metal (it was before Java SE 5, which explains its name). The Test theme is a theme defined in TestTheme.java, which you must compile with LookAndfeelDemo.java. As it is written, TestTheme.java sets the three primary colors (with somewhat bizarre results). You can modify TestTheme.java to test any

colors you like. The section of code where the theme is set is found beginning on line 92 of LookAndfeelDemo.java. Note that you must be using the Metal L&F to set a theme. if (LOOKANDFEEL.equals("Metal")) { if (THEME.equals("DefaultMetal")) MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); else if (THEME.equals("Ocean")) MetalLookAndFeel.setCurrentTheme(new OceanTheme()); else

915

MetalLookAndFeel.setCurrentTheme(new TestTheme()); UIManager.setLookAndFeel(new MetalLookAndFeel()); }

The SwingSet2 Demonstration Program In your JDK 6 installation, there is a demo\jfc folder that contains a demonstration program called SwingSet2. This program has a graphically rich GUI and allows you to change the Look and Feel from the menu. Further, if you are using the Java (Metal) Look and Feel, you can choose a variety of different themes. The files for the various themes (for example, RubyTheme.java) are found in the SwingSet2\src folder. This is the "Ocean" theme, which is the default for the cross-platform Java (Metal) Look and Feel:

This is the "Steel" theme, which was the original theme for the cross-platform Java (Metal) Look and Feel:

916

To run the SwingSet2 demo program on a system that has the JDK installed, use this command: java -jar SwingSet2.jar

This will give you the default theme of Ocean. To get the metal L&F, run this: java -Dswing.metalTheme=steel -jar SwingSet2.jar

It is also possible to run the SwingSet2 demo program with Java Web Start: http://java.sun.com/products/jfc/jws/SwingSet2.jnlp

The Synth Look and Feel Creating a custom look and feel, or modifying an existing one, can be a daunting task. The javax.swing.plaf.synth package can be used to create a custom look and feel with much less 917

effort. You can create a Synth look and feel either programatically or through the use of an external XML file. The discussion below is devoted to the creation of a Synth look and feel using an external XML file. Creating a Synth L&F programatically is discussed in the API documentation. With the Synth look and feel, you provide the "look." Synth itself provides the "feel." Thus, you can think of the Synth L&F as a "skin."

The Synth Architecture Recall from the previous topic that it is the responsibility of each L&F to provide a concrete implementation for each of the many ComponentUI subclasses defined by Swing. The Synth L&F takes care of this for you. To use Synth, you need not create any ComponentUIs—rather you need only specify how each component is painted, along with various properties that effect the layout and size. Synth operates at a more granular level than a component—this granular level is called a "region." Each component has one or more regions. Many components have only one region, such as JButton. Others have multiple regions, such as JScrollBar. Each of the ComponentUIs provided by Synth associates a SynthStyle with each of the regions defined by the ComponentUI. For example, Synth defines three regions for JScrollBar: the track, the thumb and the scroll bar itself. The ScrollBarUI (the ComponentUI subclass defined for JScrollBar) implementation for Synth associates a SynthStyle with each of these regions.

918

SynthStyle provides style information used by the Synth ComponentUI implementation. For example, SynthStyle defines the foreground and background color, font information, and so forth. In addition, each SynthStyle has a SynthPainter that is used to paint the region. For example, SynthPainter defines the two methods paintScrollBarThumbBackground and paintScrollBarThumbBorder, which are used to paint the scroll bar thumb regions.

Each of the ComponentUIs in Synth obtain SynthStyles using a SynthStyleFactory. There are two ways to define a SynthStyleFactory: through a Synth XML file, or programatically. The following code shows how to load an XML file dictating the look of Synth—beneath the covers this creates a SynthStyleFactory implementation populated with SynthStyles from the XML file: SynthLookAndFeel laf = new SynthLookAndFeel(); laf.load(MyClass.class.getResourceAsStream("laf.xml"), MyClass.class); UIManager.setLookAndFeel(laf);

The programmatic route involves creating an implementation of SynthStyleFactory that returns SynthStyles. The following code creates a custom SynthStyleFactory that returns distinct SynthStyles for buttons and trees: class MyStyleFactory extends SynthStyleFactory { public SynthStyle getStyle(JComponent c, Region id) { if (id == Region.BUTTON) { return buttonStyle; } else if (id == Region.TREE) { return treeStyle; } return defaultStyle; } } SynthLookAndFeel laf = new SynthLookAndFeel(); UIManager.setLookAndFeel(laf); SynthLookAndFeel.setStyleFactory(new MyStyleFactory());

The XML File An explanation of the DTD for the Synth XML file can be found at javax.swing.plaf.synth/doc-files/synthFileFormat.html. When you load a Synth look and feel, only those GUI components (or regions) for which there is a definition (a "style" bound to the region, as discussed below) are rendered. There is no default behavior for any components—without style definitions in the Synth XML file, the GUI is a blank canvas. To specify the rendering of a component (or region), your XML file must contain a <style> element, which is then bound to the region using the element. As an example, let's define a style that includes the font, foreground color, and background color, and then bind that style to all components. It is a good idea to include such an element in your Synth XML file while you are developing it— then, any components you haven't yet defined will at least have colors and a font: <synth> <style id="basicStyle"> <state>

919



Let's analyse this style definition: 1. The <style> element is the basic building block of the Synth XML file. It contains all the information needed to describe a region's rendering. A <style> element can describe more than one region, as is done here. In general, though, it is best to create a <style> element for each component or region. Note that the <style> element is given an identifier, the string "basicStyle." This identifier will be used later in the element.

2. The element of the <style> element sets the font to Verdana, size 16.

3. The <state> element of the <style> element will be discussed below. The <state> element of a region can have one, or a mixture, of seven possible values. When the value is not specified, the definition applies to all states, which is the intention here. Therefore, the background and foreground colors "for all states" are defined in this element.

4. Finally, the <style> element with the identifier "basicStyle" that has just been defined is bound to all regions. The element binds "basicStyle" to "region" types. Which region type or types the binding applies to is given by the "key" attribute, which is ".*" in this case, the regular expression for "all." Let's look at the pieces of the Synth XML file before creating some working examples. We'll start with the element, showing how a given <style> is applied to a component or region.

The Element Whenever a <style> element is defined, it must be bound to one or more components or regions before it has an effect. The element is used for this purpose. It requires three attributes: 1. style is the unique identifier of a previously defined style.

2. type is either "name" or "region." If type is a name, obtain the name with the component.getName() method. If type is a region, use the appropriate constant defined in the Region class in the javax.swing.plaf.synth package.

3. key is a regular expression used to determine which components or regions the style is bound to.

920

A Region is a way of identifying a component or part of a component. Regions are based on the constants in the Region class, modified by stripping out underscores: For example, to identify the SPLIT_PANE region you would use SPLITPANE, splitpane, or SplitPane (case insensitive). When you bind a style to a region, that style will apply to all of the components with that region. You can bind a style to more than one region, and you can bind more than one style to a region. For example, <style id="styleOne"> <style id="styleTwo">

You can bind to individual, named components, whether or not they are also bound as regions. For example, suppose you want to have the "OK" and "Cancel" buttons in your GUI treated differently than all the other buttons. First, you would give the OK and Cancel buttons names, using the component.setName() method. Then, you would define three styles: one for buttons in general (region = "Button"), one for the OK button (name = "OK"), and one for the Cancel button (name = "Cancel"). Finally, you would bind these styles like this:

As a result, the "OK" button is bound to both "styleButton" and "styleOK," while the "Cancel" button is bound to both "styleButton" and "styleCancel." When a component or region is bound to more than one style, the styles are merged

Note: Just as a style can be bound to multiple regions or names, multiple styles can be bound to a region or name. These multiple styles are merged for the region or name. Precedence is given to styles defined later in the file.

The <state> Element The <state> element allows you to define a look for a region that depends on its "state." For example, you will usually want a button that has been PRESSED to look different than the button in its ENABLED state. There are seven possible values for <state> that are defined in the Synth XML DTD. They are: 1. ENABLED 921

2. 3. 4. 5. 6. 7.

MOUSE_OVER PRESSED DISABLED FOCUSED SELECTED DEFAULT

You can also have composite states, separated by 'and'—for example, ENABLED and FOCUSED. If you do not specify a value, the defined look will apply to all states. As an example, here is a style that specifies painters per state. All buttons are painted a certain way, unless the state is "PRESSED," in which case they are painted differently: <style id="buttonStyle"> <property key="Button.textShiftOffset" type="integer" value="1"/> <state> <state value="PRESSED">

Ignoring the <property> and elements for the moment, you can see that a pressed button is painted differently than an unpressed button. The <state> value that is used is the defined state that most closely matches the state of the region. Matching is determined by the number of values that match the state of the region. If none of the state values match, then the state with no value is used. If there are matches, the state with the most individual matches will be chosen. For example, the following code defines three states: <state id="zero"> <state value="SELECTED and PRESSED" id="one"> <state value="SELECTED" id="two">

If the state of the region contains at least SELECTED and PRESSED, state one will be chosen. If the state contains SELECTED, but not does not contain PRESSED, state two will be used. If the state contains neither SELECTED nor PRESSED, state zero will be used. When the current state matches the same number of values for two state definitions, the one that is used is the first one defined in the style. For example, the MOUSE_OVER state is always true of a PRESSED button (you can't press a button unless the mouse is over it). So, if the MOUSE_OVER state is

922

declared first, it will always be chosen over PRESSED, and any painting defined for PRESSED will not be done. <state value="PRESSED"> <state value="MOUSE_OVER"> The code above will work properly. However, if you reverse the order of the MOUSE_OVER and PRESSED states in the file, the PRESSED state will never be used. This is because any state that is PRESSED state is also a MOUSE_OVER state. Since the MOUSE_OVER state was defined first, it is the one

that will be used.

Colors and Fonts The element requires two attributes: 1. value can be any one of the java.awt.Color constants, such as RED, WHITE, BLACK, BLUE, etc. It can also be a hex representation of RGB values, such as #FF00FF or #326A3B.

2. type describes where the color applies—it can be BACKGROUND, FOREGROUND, FOCUS, TEXT_BACKGROUND, OR TEXT_FOREGROUND. For example: <style id="basicStyle"> <state>

The element has three attributes: 1. name—the name of the font. For example, Arial or Verdana.

2. size—the size of the font in pixels.

3. style (optional)—BOLD, ITALIC, OR BOLD ITALIC. If omitted, you get a normal font. For example: <style id="basicStyle">

923



Each of the element and the element has an alternate usage. Each can have an id attribute or an idref attribute. Using the id attribute, you can define a color that you can reuse later by using the idref attribute. For example, ... ... ...

Insets The insets add to the size of a component as it is drawn. For example, without insets, a button with a caption of Cancel will be just large enough to contain the caption in the chosen font. With an element like this ,

the button will be made larger by 15 pixels above and below the caption and 20 pixels to the left and right of the caption.

Painting With Images Synth's file format allows customizing the painting by way of images. Synth's image painter breaks an image into nine distinct areas: top, top right, right, bottom right, bottom, bottom left, left, top left, and center. Each of the these areas is painted into the destination. The top, left, bottom, and right edges are tiled or stretched, while the corner portions (sourceInsets) remain fixed.

Note: There is no relation between the element and the sourceInsets attribute. The element defines the space taken up by a region, while the sourceInsets attributes define how to paint an image. The and sourceInsets will often be similar, but they need not be.

You can specify whether the center area should be painted with the paintCenter attribute. The following image shows the nine areas:

924

Let's create a button as an example. To do this we can use the following image (shown larger than its actual size):

The red box at the upper left corner is 10 pixels square (including the box border)—it shows the corner region that should not be stretched when painting. To achieve this, the top and left sourceInsets should be set to 10. We'll use the following style and binding: <style id="buttonStyle"> <state>

The lines inside the <state> element specify that the background of buttons should be painted using the image images/button.png. That path is relative to the Class that is passed into SynthLookAndFeel's load method. The sourceInsets attribute specifies the areas of the image that are not to be stretched. In this case the top, left, bottom, and right insets are each 10. This will cause the painter not to stretch a 10 x 10 pixel area at each corner of the image. The binds buttonStyle to all buttons. The element provides all the information needed to render a portion of a region. It requires only a few attributes: 

method—this specifies which of the methods in the javax.swing.plaf.synth.SynthPainter class is to be used for painting. The SynthPainter class contains about 100 methods that begin with paint. When you determine which one you need, you remove the paint prefix, change the remaining first letter to lowercase, and use the result as the method attribute. For example, the SynthPainter method paintButtonBackground becomes the attribute buttonBackground.



path—the path to the image to be used, relative to the Class that is passed into SynthLookAndFeel's load method.



sourceInsets—the insets in pixels, representing the width and height of the corner areas that should not be stretched They map to the top, left, bottom, and right, in that order.

925



paintCenter (optional) : This attribute lets you keep the center of an image or get rid of it (in a text field, for example, so text can be drawn).

The listing below shows the XML code for loading different images depending on the <state> of the button <style id="buttonStyle"> <property key="Button.textShiftOffset" type="integer" value="1"/> <state> <state value="PRESSED">

button2.png shows the depressed version of button.png, shifted one pixel to the right. The line <property key="Button.textShiftOffset" type="integer" value="1"/>

shifts the button text accordingly, as discussed in the next section.

The <property> Element <property> elements are used to add key value pairs to a <style> element. Many components use the key value pairs for configuring their visual appearance. The <property> element has three attributes: 

key—the name of the property.



type—the data type of the property.



value—the value of the property.

There is a property table (componentProperties.html) that lists the properties each component supports: javax/swing/plaf/synth/doc-files/componentProperties.html. Since the button2.png image shifts the visual button one pixel when it is depressed, we should also shift the button text. There is a button property that does this: <property key="Button.textShiftOffset" type="integer" value="1"/>

An Example Here is an example, using the button style defined above. The button style, plus a "backing style" with definitions of font and colors that are bound to all regions (similar to the "basicStyle" shown in 926

the section titled "The XML File," above) are combined in buttonSkin.xmlHere is a listing of buttonSkin.xml: <synth> <style id="backingStyle"> <state> <style id="buttonStyle"> <property key="Button.textShiftOffset" type="integer" value="1"/> <state> <state value="PRESSED">

We can load this XML file to use the Synth look and feel for a simple application called SynthApplication.java. The GUI for this application includes a button and a label. Every time the button is clicked, the label increments. Note: The label is painted, even though buttonSkin.xml does not contain a style for it. This is because there is a general "backingStyle" that includes a font and colors.

Here is the listing of the SynthApplication.javafile.

Try this: Click the Launch button to run the SynthApplication example using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

Painting With Icons Radio buttons and check boxes typically render their state by fixed-size icons. For these, you can create an icon and bind it to the appropriate property (refer to the properties table, javax/swing/plaf/synth/doc-files/componentProperties.html). For example, to paint radio buttons that are selected or unselected, use this code: 927

<style id="radioButton"> <property key="RadioButton.icon" value="radio_off"/> <state value="SELECTED"> <property key="RadioButton.icon" value="radio_on"/>

Custom Painters Synth's file format allows for embedding arbitrary objects by way of the long-term persistence of JavaBeans components . This ability is particularly useful in providing your own painters beyond the image-based ones Synth provides. For example, the following XML code specifies that a gradient should be rendered in the background of text fields: <synth> <style id="textfield"> <painter method="textFieldBackground" idref="gradient"/>

Where the GradientPainter class looks like this: public class GradientPainter extends SynthPainter { public void paintTextFieldBackground(SynthContext context, Graphics g, int x, int y, int w, int h) { // For simplicity this always recreates the GradientPaint. In a // real app you should cache this to avoid garbage. Graphics2D g2 = (Graphics2D)g; g2.setPaint(new GradientPaint((float)x, (float)y, Color.WHITE, (float)(x + w), (float)(y + h), Color.RED)); g2.fillRect(x, y, w, h); g2.setPaint(null); } }

Conclusion In this lesson, we have covered the use of the javax.swing.plaf.synth package to create a custom look and feel. The emphasis of the lesson has been on using an external XML file to define the look and feel. The next lesson presents a sample application that creates a search dialog box using the Synth framework with an XML file.

A Synth Example In the lesson titled A GroupLayout Example , GroupLayout was used to create a search dialog box called "Find." The program that created the dialog box, Find.java, used the cross platform ("Metal") look and feel with the "Ocean" theme:

928

This lesson creates the same dialog box with Synth, using an external XML file. Here is the listing of the SynthDialog.javafile. SynthDialog.java is exactly the same as Find.java except for the initLookAndFeel() method, which has been altered to use the Synth look and feel with an external file called synthDemo.xml. Here is the new initLookAndFeel() method: private static void initLookAndFeel() { SynthLookAndFeel lookAndFeel = new SynthLookAndFeel(); // SynthLookAndFeel load() method throws a checked exception // (java.text.ParseException) so it must be handled try { lookAndFeel.load(SynthDialog.class.getResourceAsStream("synthDemo.xml"), SynthDialog.class); UIManager.setLookAndFeel(lookAndFeel); } catch (ParseException e) { System.err.println("Couldn't get specified look and feel (" + lookAndFeel + "), for some reason."); System.err.println("Using the default look and feel."); e.printStackTrace(); } }

The XML File The XML file, synthDemo.xml, begins with a style bound to all regions. It is good practice to do this to ensure that regions without a style bound to them will contain something. This style makes all regions paint their background in an opaque color. It also sets a default font and default colors. <style id="backingStyle"> <state>

Notes: 1. The color definitions must be inside a <state> element. This permits changing colors depending on state. The <state> element in backingStyle has no attributes and is therefore applied to all regions, 929

irrespective of their state. If a region has other states, the states are merged with precedence given to state definitions that appear later in the file. 2. The font definition is not inside a <state> element because the font should not change when there is a change of state (many components are sized depending on their font, and a change in font could cause components to change in size unintentionally).

The next <style> element defined is for the text field, which is painted using an image. <style id="textfield"> <state>

Notes: 1. The font and color definitions override the definitions in backingStyle. 2. The insets and sourceInsets are given the same values, which is just a coincidence because they are unrelated to each other. 3. The BACKGROUND color, #D2DFF2, is a pale blue—the same color as the background in the image, textfield.png. 4. paintCenter is false so that you can see the background color.

The next <style> element is for buttons that are painted with different images, depending on the button state. When the mouse passes over the button, its appearance changes. When it is clicked (PRESSED) the image changes again. <style id="button"> <property key="Button.textShiftOffset" type="integer" value="1"/> <state> <state value="PRESSED">

930

<state value="MOUSE_OVER">

Notes: 1. The font and color definitions inside the <state> element without attributes apply to all button states. This is because the definitions of all states that apply (and the <state> element without attributes is one of these) will merge and there are no other font and color definitions that might take precedence. 2. The sourceInsets values are large enough that the curved corners of the button image will not be stretched. 3. The order of the PRESSED and MOUSE_OVER states is important. Since the mouse will always be over the button when it is pressed, both states will apply to a pressed button and the first state defined (PRESSED) will apply. When the mouse is over the button but it is not pressed, only the MOUSE_OVER state applies. If the order of the PRESSED and MOUSE_OVER states is reversed, the PRESSED state image will never be used.

The next <style> element is for checkboxes that are painted with different icons, depending on the checkbox state. <style id="checkbox"> <property key="CheckBox.icon" value="check_off"/> <state value="SELECTED"> <property key="CheckBox.icon" value="check_on"/>

Notes: 1. You must use the element to define any icons to be used. 2. The element and the sourceInsets attribute are not used with icons because they are rendered in their fixed size and are not stretched. 3. The icon used to render the checkbox is the icon named in the CheckBox.icon property. (see javax/swing/plaf/synth/doc-files/componentProperties.html), which is the icon with id="check_off" unless the checkbox state is SELECTED.

931

The synthDemo.xml file is constructed of the styles presented above, wrapped in <synth> tags. You can open the completed file by clicking synthDemo.xml.

Try this: Click the Launch button to run the SynthDialog example using Java™ Web Start (download JDK 6). Alternatively, to compile and run the example yourself, consult the example index.

932

Related Documents

Layout Managers
June 2020 7
11 Layout Managers
November 2019 7
Managers
November 2019 22
Managers
December 2019 19
Layout
November 2019 52
Layout
November 2019 56