An Introduction to Layout Management
Before we go on to discussing individual Swing components, such as text fields and radio buttons, we need to briefly cover how to arrange these components inside a frame. Since the Java SDK has no form designer like those in Visual Basic or Delphi, you need to write code to position (lay out) the user interface components where you want them to be. Of course, if you have a Java-enabled development environment, it will probably have a layout tool that automates some or all of these tasks. Nevertheless, it is important to know exactly what goes on "under the hood" since even the best of these tools will usually require hand-tweaking to get a professional look and feel. Let's start by reviewing the program from the last chapter that used buttons to change the background color of a frame (see Screenshot-5).
Screenshot-5. A panel with three buttons
Let us quickly recall how we built this program:
We defined the look of each button by setting the label string in the constructor, for example:
JButton yellowButton = new JButton("Yellow")
We then added the individual buttons to a panel, for example, with:
Then, we added the needed event handlers, for example:
What happens if we add more buttons? Screenshot-6 shows what happens when you add six buttons to the panel. As you can see, they are centered in a row, and when there isn't any more room, a new row is started.
Screenshot-6. A panel with six buttons managed by a flow layout
Moreover, the buttons stay centered in the panel, even when the user resizes the frame (see Screenshot-7).
Screenshot-7. Changing the panel size rearranges the buttons automatically
Java has a very elegant concept to enable this dynamic layout: all components in a container are managed by a layout manager. In our example, the buttons are managed by the flow layout manager, the default layout manager for a panel. The flow layout manager lines the components horizontally until there is no more room and then starts a new row of components. When the user resizes the container, the layout manager automatically reflows the components to fill the available space. You can choose how you want to arrange the components in each row. The default is to center them in the container. The other choices are to align them to the left or to the right of the container. To use one of these alignments, specify LEFT or RIGHT in the constructor of the FlowLayout object.
Normally, you just let the flow layout manager control the vertical and horizontal gaps between the components. You can, however, force a specific horizontal or vertical gap by using another version of the flow layout constructor. (See the API notes.)
Java comes with several layout managers, and you can also make your own layout managers: We will cover all of them later on in this chapter. However, to enable us to give you more interesting examples right away, we need to briefly describe another layout manager called the border layout manager. This is the default layout manager of the content pane of every JFrame. Unlike the flow layout manager, which completely controls the position of each component, the border layout manager lets you choose where you want to place each component. You can choose to place the component in the center, north, south, east, or west of the content pane (see Screenshot-8).
Screenshot-8. Border layout
class MyPanel extends JPanel
. . .
The edge components are laid out first, and the remaining available space is occupied by the center. When the container is resized, the thickness of the edge components is unchanged, but the center component changes its size. You add components by specifying a constant CENTER, NORTH, SOUTH, EAST, or WEST of the BorderLayout class. Not all of the positions need to be occupied. If you don't supply any value, CENTER is assumed.
Unlike the flow layout, the border layout grows all components to fill the available space. (The flow layout leaves each component at its preferred size.) As with flow layouts, if you want to specify a gap between the regions, you can do so in the constructor of the BorderLayout. As previously noted, the content pane of a JFrame uses a border layout. Up to now, we never took advantage of this—we simply added panels into the default (center) area. But you can add components into the other areas as well:
The BorderLayout constants are defined as strings. For example, BorderLayout.SOUTH is defined as the string "South". Many programmers prefer to use the strings directly because they are shorter, for example, contentPane.add(component, "South"). However, if you accidentally misspell a string, the compiler won't catch that error.
Container contentPane = getContentPane();
However, there is a problem with this code fragment that we take up in the next section.
A BorderLayout is not very useful by itself. Screenshot-9 shows what happens when you use the code fragment above. The button has grown to fill the entire southern region of the frame. And, if you were to add another button to the southern region, it would just displace the first button.
Screenshot-9. A single button managed by a border layout
One common method to overcome this problem is to use additional panels. Panels act as (smaller) containers for interface elements and can themselves be arranged inside a larger panel under the control of a layout manager. For example, you can have one panel in the southern area for the buttons and another in the center for text. By nesting panels and using a mixture of border layouts and flow layouts, you can achieve fairly precise positioning of components. This approach to layout is certainly enough for prototyping, and it is the approach that we will use for the example programs in the first part of this chapter. See the section on the GridBagLayout later in this chapter for the most precise way to position components. For example, look at Screenshot-10. The three buttons at the bottom of the screen are all contained in a panel. The panel is put into the southern end of the content pane.
Screenshot-10. A panel placed at the south end of the frame
So, suppose you want to add a panel with three buttons as in Screenshot-10. As you might expect, you first create a new instance of a JPanel object before you add the individual buttons to it. The default layout manager for a panel is a FlowLayout, which is a good choice for this situation. Finally, you add the individual buttons, using the add method you have seen before. Since you are adding buttons to a panel and haven't changed the default layout manager, the position of the buttons is under the control of the FlowLayout manager. This means the buttons stay centered within the panel and they do not expand to fill the entire panel area. Here's a code fragment that adds a panel containing three buttons in the south end of a container.
Container contentPane = getContentPane();
JPanel panel = new JPanel();
As you just saw, the JPanel class uses a FlowLayout as the default layout manager. For a JPanel, you can supply a different layout manager object in the constructor. However, most other containers do not have such a constructor. But all containers have a setLayout method to set the layout manager to something other than the default for the container.
The panel boundaries are not visible to the user. Panels are just an organizing mechanism for the user interface designer.
The grid layout arranges all components in rows and columns like a spreadsheet. However, for a grid layout, cells are always the same size. The calculator program in Screenshot-11 uses a grid layout to arrange the calculator buttons. When you resize the window, the buttons grow and shrink, but all buttons have identical sizes.
Screenshot-11. A calculator
In the constructor of the grid layout object, you specify how many rows and columns you need.
panel.setLayout(new GridLayout(5, 4));
As with the border layout and flow layout managers, you can also specify the vertical and horizontal gaps you want.
panel.setLayout(new GridLayout(5, 4, 3, 3));
The last two parameters of this constructor specify the size of the horizontal and vertical gaps (in pixels) between the components. You add the components, starting with the first entry in the first row, then the second entry in the first row, and so on.
Example 9-1 is the source listing for the calculator program. This is a regular calculator, not the "reverse Polish" variety that is so oddly popular in Java tutorials. The program uses a JLabel to display the results. You will find more information about labels later in this chapter. Of course, few apps have as rigid a layout as the face of a calculator. In practice, small grids (usually with just one row or one column) can be useful to organize partial areas of a window. For example, if you want to have a row of buttons with identical size, then you can put the buttons inside a panel that is governed by a grid layout with a single row.
Example 9-1 Calculator.java
1. import java.awt.*;
2. import java.awt.event.*;
3. import javax.swing.*;
5. public class Calculator
7. public static void main(String args)
9. CalculatorFrame frame = new CalculatorFrame();
16. A frame with a calculator panel.
18. class CalculatorFrame extends JFrame
20. public CalculatorFrame()
24. Container contentPane = getContentPane();
25. CalculatorPanel panel = new CalculatorPanel();
33. A panel with calculator buttons and a result display.
35. class CalculatorPanel extends JPanel
37. public CalculatorPanel()
39. setLayout(new BorderLayout());
41. result = 0;
42. lastCommand = "=";
43. start = true;
45. // add the display
47. display = new JLabel("0");
48. add(display, BorderLayout.NORTH);
50. ActionListener insert = new InsertAction();
51. ActionListener command = new CommandAction();
53. // add the buttons in a 4 x 4 grid
55. panel = new JPanel();
56. panel.setLayout(new GridLayout(4, 4));
58. addButton("7", insert);
59. addButton("8", insert);
60. addButton("9", insert);
61. addButton("/", command);
63. addButton("4", insert);
64. addButton("5", insert);
65. addButton("6", insert);
66. addButton("*", command);
68. addButton("1", insert);
69. addButton("2", insert);
70. addButton("3", insert);
71. addButton("-", command);
73. addButton("0", insert);
74. addButton(".", insert);
75. addButton("=", command);
76. addButton("+", command);
78. add(panel, BorderLayout.CENTER);
82. Adds a button to the center panel.
83. @param label the button label
84. @param listener the button listener
86. private void addButton(String label, ActionListener listener)
88. JButton button = new JButton(label);
94. This action inserts the button action string to the
95. end of the display text.
97. private class InsertAction implements ActionListener
99. public void actionPerformed(ActionEvent event)
101. String input = event.getActionCommand();
102. if (start)
105. start = false;
107. display.setText(display.getText() + input);
112. This action executes the command that the button
113. action string denotes.
115. private class CommandAction implements ActionListener
117. public void actionPerformed(ActionEvent evt)
119. String command = evt.getActionCommand();
121. if (start)
123. if (command.equals("-"))
126. start = false;
129. lastCommand = command;
134. lastCommand = command;
135. start = true;
141. Carries out the pending calculation.
142. @param x the value to be accumulated with the prior result.
144. public void calculate(double x)
146. if (lastCommand.equals("+")) result += x;
147. else if (lastCommand.equals("-")) result -= x;
148. else if (lastCommand.equals("*")) result *= x;
149. else if (lastCommand.equals("/")) result /= x;
150. else if (lastCommand.equals("=")) result = x;
151. display.setText("" + result);
154. private JLabel display;
155. private JPanel panel;
156. private double result;
157. private String lastCommand;
158. private boolean start;