Chapter 9. User Interface Components with Swing

The last chapter was primarily designed to show you how to use the event model in Java. In the process you took the first steps toward learning how to build a graphical user interface. This chapter shows you the most important tools you’ll need to build more full-featured GUIs.

We start out with a tour of the architectural underpinnings of Swing. Knowing what goes on “under the hood” is important in understanding how to use some of the more advanced components effectively. We then show you how to use the most common user interface components in Swing such as text fields, radio buttons, and menus. Next, you will learn how to use the nifty layout manager features of Java to arrange these components in a window, regardless of the look and feel of a particular user interface. Finally, you’ll see how to implement dialog boxes in Swing.

This chapter covers basic Swing components such as text components, buttons, and sliders. These are the essential user interface components that you will need most frequently. We cover advanced Swing components in Volume 2. For an even more comprehensive look into all details of the Swing framework, we recommend the books Core JFC and Core Swing: Advanced Topics by Kim Topley (both published by Prentice Hall PTR).

The Model-View-Controller Design Pattern

As promised, we start this chapter with a section describing the architecture of Swing components. Before we explain just what the title of this section means, let’s step back for a minute and think about the pieces that make up a user interface component such as a button, a checkbox, a text field, or a sophisticated tree control. Every component has three characteristics:

  • Its content, such as the state of a button (pushed in or not), or the text in a text field;

  • Its visual appearance (color, size, and so on);

  • Its behavior (reaction to events).

Even a seemingly simple component such as a button exhibits some moderately complex interaction among these characteristics. Obviously, the visual appearance of a button depends on the look and feel. A Metal button looks different from a Windows button or a Motif button. In addition, the appearance depends on the button state: when a button is pushed in, it needs to be redrawn to look different. The state depends on the events that the button receives. When the user depresses the mouse inside the button, the button is pushed in.

Of course, when you use a button in your programs, you simply consider it as a button, and you don’t think too much about the inner workings and characteristics. That, after all, is the job of the programmer who implemented the button. However, those programmers who implement buttons are motivated to think a little harder about them. After all, they have to implement buttons, and all other user interface components, so that they work well no matter what look and feel is installed.

To do this, the Swing designers turned to a well-known design pattern: the model-view-controller pattern. This pattern, like many other design patterns, goes back to one of the principles of object-oriented design that we mentioned way back in Chapter 5: don’t make one object responsible for too much. Don’t have a single button class do everything. Instead, have the look and feel of the component associated with one object and store the content in another object. The model-view-controller (MVC) design pattern teaches how to accomplish this. Implement three separate classes:

  • The model, which stores the content

  • The view, which displays the content

  • The controller, which handles user input

The pattern specifies precisely how these three objects interact. The model stores the content and has no user interface. For a button, the content is pretty trivial—it is just a small set of flags that tells whether the button is currently pushed in or out, whether it is active or inactive, and so on. For a text field, the content is a bit more interesting. It is a string object that holds the current text. This is not the same as the view of the content—if the content is larger than the text field, the user sees only a portion of the text displayed (see Figure 9–1).

Model and view of a text fieldMVCmodel

Figure 9–1. Model and view of a text field

The model must implement methods to change the content and to discover what the content is. For example, a text model has methods to add or remove characters in the current text and to return the current text as a string. Again, keep in mind that the model is completely nonvisual. It is the job of a view to draw the data that is stored in the model.

Note

Note

The term “model” is perhaps unfortunate because we often think of a model as a representation of an abstract concept. Car and airplane designers build models to simulate real cars and planes. But that analogy really leads you astray when thinking about the model-view-controller pattern. In the design pattern, the model stores the complete content, and the view gives a (complete or incomplete) visual representation of the content. A better analogy might be the model who poses for an artist. It is up to the artist to look at the model and create a view. Depending on the artist, that view might be a formal portrait, an impressionist painting, or a cubist drawing that shows the limbs in strange contortions.

One of the advantages of the model-view-controller pattern is that a model can have multiple views, each showing a different part or aspect of the full content. For example, an HTML editor can offer two simultaneous views of the same content: a WYSIWYG view and a “raw tag” view (see Figure 9–2). When the model is updated through the controller of one of the views, it tells both attached views about the change. When the views are notified, they refresh themselves automatically. Of course, for a simple user interface component such as a button, you won’t have multiple views of the same model.

Two separate views of the same model

Figure 9–2. Two separate views of the same model

The controller handles the user-input events such as mouse clicks and keystrokes. It then decides whether to translate these events into changes in the model or the view. For example, if the user presses a character key in a text box, the controller calls the “insert character” command of the model. The model then tells the view to update itself. The view never knows why the text changed. But if the user presses a cursor key, then the controller may tell the view to scroll. Scrolling the view has no effect on the underlying text, so the model never knows that this event happened.

Figure 9–4 shows the interactions among model, view, and controller objects.

Interactions among model, view, and controller objectsMVCinteraction among objectscontrollersinteraction with model, viewmodelsinteraction with controller, viewviewsinteraction with model, controller

Figure 9–4. Interactions among model, view, and controller objects

As a programmer using Swing components, you generally don’t need to think about the model-view-controller architecture. Each user interface has a wrapper class (such as JButton or JTextField) that stores the model and the view. When you want to inquire about the content (for example, the text in a text field), the wrapper class asks the model and returns the answer to you. When you want to change the view (for example, move the caret position in a text field), the wrapper class forwards that request to the view. However, occasionally the wrapper class doesn’t work hard enough on forwarding commands. Then, you have to ask it to retrieve the model and work directly with the model. (You don’t have to work directly with the view—that is the job of the look-and-feel code.)

Besides being “the right thing to do,” the model-view-controller pattern was attractive for the Swing designers because it allowed them to implement pluggable look and feel. The model of a button or text field is independent of the look and feel. But of course the visual representation is completely dependent on the user interface design of a particular look and feel. The controller can vary as well. For example, in a voice-controlled device, the controller must cope with an entirely different set of events than in a standard computer with a keyboard and a mouse. By separating out the underlying model from the user interface, the Swing designers can reuse the code for the models and can even switch the look and feel in a running program.

Of course, patterns are only intended as guidance, not as religion. No pattern is applicable in all situations. For example, you may find it difficult to follow the “window places” pattern (see the sidebar on design patterns) to rearrange your cubicle. Similarly, the Swing designers found that the harsh reality of pluggable look-and-feel implementation does not always allow for a neat realization of the model-view-controller pattern. Models are easy to separate, and each user interface component has a model class. But the responsibilities of the view and controller are not always clearly separated and are distributed over a number of different classes. Of course, as a user of these classes, you won’t be concerned about this. In fact, as we pointed out before, you often won’t have to worry about the models either—you can just use the component wrapper classes.

A Model-View-Controller Analysis of Swing Buttons

You already learned how to use buttons in the previous chapter, without having to worry about the controller, model, or view for them. Still, buttons are about the simplest user interface elements, so they are a good place to become comfortable with the model-view-controller pattern. You will encounter similar kinds of classes and interfaces for the more sophisticated Swing components.

For most components, the model class implements an interface whose name ends in Model; thus the interface called ButtonModel. Classes implementing that interface can define the state of the various kinds of buttons. Actually, buttons aren’t all that complicated, and the Swing library contains a single class, called DefaultButtonModel, that implements this interface.

You can get a sense of what sort of data are maintained by a button model by looking at the methods of the ButtonModel interface. Table 9–1 shows the accessor methods.

Table 9–1. Accessor Methods of the ButtonModel Interface

getActionCommand()

The action command string associated with this button

getMnemonic()

The keyboard mnemonic for this button

isArmed()

true if the button was pressed and the mouse is still over the button

isEnabled()

true if the button is selectable

isPressed()

true if the button was pressed but the mouse button hasn’t yet been released

isRollover()

true if the mouse is over the button

isSelected()

true if the button has been toggled on (used for checkboxes and radio buttons)

Each JButton object stores a button model object, which you can retrieve.

JButton button = new JButton("Blue");
ButtonModel model = button.getModel();

In practice, you won’t care—the minutiae of the button state are only of interest to the view that draws it. And the important information—such as whether a button is enabled—is available from the JButton class. (The JButton then asks its model, of course, to retrieve that information.)

Have another look at the ButtonModel interface to see what isn’t there. The model does not store the button label or icon. There is no way to find out what’s on the face of a button just by looking at its model. (Actually, as you will see in the section on radio button groups, that purity of design is the source of some grief for the programmer.)

It is also worth noting that the same model (namely, DefaultButtonModel) is used for push buttons, radio buttons, checkboxes, and even menu items. Of course, each of these button types has different views and controllers. When using the Metal look and feel, the JButton uses a class called BasicButtonUI for the view and a class called ButtonUIListener as controller. In general, each Swing component has an associated view object that ends in UI. But not all Swing components have dedicated controller objects.

So, having read this short introduction to what is going on under the hood in a JButton, you may be wondering: Just what is a JButton really? It is simply a wrapper class inheriting from JComponent that holds the DefaultButtonModel object, some view data (such as the button label and icons), and a BasicButtonUI object that is responsible for the button view.

Introduction to Layout Management

Before we go on to discussing individual Swing components, such as text fields and radio buttons, we briefly cover how to arrange these components inside a frame. Unlike Visual Basic, the JDK has no form designer. 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” because even the best of these tools will usually require hand-tweaking.

Let’s start by reviewing the program from the last chapter that used buttons to change the background color of a frame (see Figure 9–5).

A panel with three buttons

Figure 9–5. A panel with three buttons

Let us quickly recall how we built this program:

  1. We defined the appearance of each button by setting the label string in the constructor, for example:

    JButton yellowButton = new JButton("Yellow")
    
  2. We then added the individual buttons to a panel, for example, with

    panel.add(yellowButton);
    
  3. We then added the needed event handlers, for example:

    yellowButton.addActionListener(listener);
    

What happens if we add more buttons? Figure 9–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 is no more room, a new row is started.

A panel with six buttons managed by a flow layout

Figure 9–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 Figure 9–7).

Changing the panel size rearranges the buttons automatically

Figure 9–7. Changing the panel size rearranges the buttons automatically

An elegant concept enables this dynamic layout: all components in a container are positioned 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.

panel.setLayout(new FlowLayout(FlowLayout.LEFT));

Note

Note

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

Note
java.awt.Container 1.0
  • setLayout(LayoutManager m)

    sets the layout manager for this container.

Note
java.awt.FlowLayout 1.0
  • FlowLayout(int align)

    constructs a new FlowLayout with the specified alignment.

    Parameters:

    align

    One of LEFT, CENTER, or RIGHT

  • FlowLayout(int align, int hgap, int vgap)

    constructs a new FlowLayout with the specified alignment and the specified horizontal and vertical gaps between components.

    Parameters:

    align

    One of LEFT, CENTER, or RIGHT

     

    hgap

    The horizontal gap to use in pixels (negative values force an overlap)

     

    vgap

    The vertical gap to use in pixels (negative values force an overlap)

Border Layout

Java comes with several layout managers, and you can also make your own layout managers. We cover all of them later in this chapter. However, to enable us to give you more interesting examples right away, we 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 Figure 9–8).

Border layoutnorth area of content panesouth area of content paneeast area of content panewest area of content panecenter area of content panecontent panes, arranging components in

Figure 9–8. Border layout

For example:

panel.setLayout(new BorderLayout());
panel.add(yellowButton, BorderLayout.SOUTH);

The edge components are laid out first, and the remaining available space is occupied by the center. When the container is resized, the dimensions of the edge components are 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.

Note

Note

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, frame.add(component, "South"). However, if you accidentally misspell a string, the compiler won’t catch that error.

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:

frame.add(yellowButton, BorderLayout.SOUTH);

However, this code fragment has a problem, which we take up in the next section.

Note
java.awt.Container 1.0
  • void add(Component c, Object constraints) 1.1

    adds a component to this container.

    Parameters:

    c

    The component to add

     

    constraints

    An identifier understood by the layout manager

Note
java.awt.Borderlayout 1.0
  • BorderLayout(int hgap, int vgap)

    constructs a new BorderLayout with the specified horizontal and vertical gaps between components.

    Parameters:

    hgap

    The horizontal gap to use in pixels (negative values force an overlap)

     

    vgap

    The vertical gap to use in pixels (negative values force an overlap)

Panels

A BorderLayout is not very useful by itself. Figure 9–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.

A single button managed by a border layout

Figure 9–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 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 Figure 9–10. The three buttons at the bottom of the screen are all contained in a panel. The panel is put into the southern region of the content pane.

Panel placed at the southern region of the frame

Figure 9–10. Panel placed at the southern region of the frame

So, suppose you want to add a panel with three buttons as in Figure 9–10. First create a new JPanel object, then add the individual buttons to the panel. 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. Because you are adding buttons to a panel and haven’t changed the default layout manager, the position and size 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 southern region of a frame.

JPanel panel = new JPanel();
panel.add(yellowButton);
panel.add(blueButton);
panel.add(redButton);
frame.add(panel, BorderLayout.SOUTH);

Note

Note

The panel boundaries are not visible to the user. Panels are just an organizing mechanism for the user interface designer.

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.

Note
javax.swing.JPanel 1.2
  • JPanel(LayoutManager m)

    sets the layout manager for the panel.

Grid Layout

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 Figure 9–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.

A calculator

Figure 9–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.

panel.add(new JButton("1"));
panel.add(new JButton("2"));

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. In this program, we call the pack method after adding the component to the frame. This method uses the preferred sizes of all components to compute the width and height of the frame.

Of course, few applications 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.*;
  4.
  5. public class Calculator
  6. {
  7.    public static void main(String[] args)
  8.    {
  9.       CalculatorFrame frame = new CalculatorFrame();
 10.      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 11.      frame.setVisible(true);
 12.   }
 13. }
 14.
 15. /**
 16.    A frame with a calculator panel.
 17. */
 18. class CalculatorFrame extends JFrame
 19. {
 20.    public CalculatorFrame()
 21.    {
 22.       setTitle("Calculator");
 23.       CalculatorPanel panel = new CalculatorPanel();
 24.       add(panel);
 25.       pack();
 26.    }
 27. }
 28.
 29. /**
 30.    A panel with calculator buttons and a result display.
 31. */
 32. class CalculatorPanel extends JPanel
 33. {
 34.    public CalculatorPanel()
 35.    {
 36.       setLayout(new BorderLayout());
 37.
 38.       result = 0;
 39.       lastCommand = "=";
 40.       start = true;
 41.
 42.       // add the display
 43.
 44.       display = new JButton("0");
 45.       display.setEnabled(false);
 46.       add(display, BorderLayout.NORTH);
 47.
 48.       ActionListener insert = new InsertAction();
 49.       ActionListener command = new CommandAction();
 50.
 51.       // add the buttons in a 4 x 4 grid
 52.
 53.       panel = new JPanel();
 54.       panel.setLayout(new GridLayout(4, 4));
 55.
 56.       addButton("7", insert);
 57.       addButton("8", insert);
 58.       addButton("9", insert);
 59.       addButton("/", command);
 60.
 61.       addButton("4", insert);
 62.       addButton("5", insert);
 63.       addButton("6", insert);
 64.       addButton("*", command);
 65.
 66.       addButton("1", insert);
 67.       addButton("2", insert);
 68.       addButton("3", insert);
 69.       addButton("-", command);
 70.
 71.       addButton("0", insert);
 72.       addButton(".", insert);
 73.       addButton("=", command);
 74.       addButton("+", command);
 75.
 76.       add(panel, BorderLayout.CENTER);
 77.    }
 78.
 79.    /**
 80.       Adds a button to the center panel.
 81.       @param label the button label
 82.       @param listener the button listener
 83.    */
 84.    private void addButton(String label, ActionListener listener)
 85.    {
 86.       JButton button = new JButton(label);
 87.       button.addActionListener(listener);
 88.       panel.add(button);
 89.    }
 90.
 91.    /**
 92.       This action inserts the button action string to the
 93.       end of the display text.
 94.    */
 95.    private class InsertAction implements ActionListener
 96.    {
 97.       public void actionPerformed(ActionEvent event)
 98.       {
 99.          String input = event.getActionCommand();
100.          if (start)
101.          {
102.             display.setText("");
103.             start = false;
104.          }
105.          display.setText(display.getText() + input);
106.       }
107.    }
108.
109.    /**
110.       This action executes the command that the button
111.       action string denotes.
112.    */
113.    private class CommandAction implements ActionListener
114.    {
115.       public void actionPerformed(ActionEvent event)
116.       {
117.         String command = event.getActionCommand();
118.
119.         if (start)
120.         {
121.            if (command.equals("-"))
122.            {
123.               display.setText(command);
124.               start = false;
125.            }
126.            else
127.               lastCommand = command;
128.         }
129.         else
130.         {
131.            calculate(Double.parseDouble(display.getText()));
132.            lastCommand = command;
133.            start = true;
134.          }
135.       }
136.    }
137.
138.    /**
139.       Carries out the pending calculation.
140.       @param x the value to be accumulated with the prior result.
141.    */
142.    public void calculate(double x)
143.    {
144.       if (lastCommand.equals("+")) result += x;
145.       else if (lastCommand.equals("-")) result -= x;
146.       else if (lastCommand.equals("*")) result *= x;
147.       else if (lastCommand.equals("/")) result /= x;
148.       else if (lastCommand.equals("=")) result = x;
149.       display.setText("" + result);
150.    }
151.
152.    private JButton display;
153.    private JPanel panel;
154.    private double result;
155.    private String lastCommand;
156.    private boolean start;
157. }
Calculator.java
java.awt.GridLayout 1.0
  • GridLayout(int rows, int cols)

    constructs a new GridLayout.

    Parameters:

    rows

    The number of rows in the grid

     

    columns

    The number of columns in the grid

  • GridLayout(int rows, int columns, int hgap, int vgap)

    constructs a new GridLayout with horizontal and vertical gaps between components.

    Parameters:

    rows

    The number of rows in the grid

     

    columns

    The number of columns in the grid

     

    hgap

    The horizontal gap to use in pixels (negative values force an overlap)

     

    vgap

    The vertical gap to use in pixels (negative values force an overlap)

Calculator.java
java.awt.Window 1.0
  • void pack()

    resizes this window, taking into account the preferred sizes of its components.

Text Input

We are finally ready to start introducing the Swing user interface components. We start with components that let a user input and edit text. You can use the JTextField and JTextArea components for gathering text input. A text field can accept only one line of text; a text area can accept multiple lines of text.

Both of these classes inherit from a class called JTextComponent. You will not be able to construct a JTextComponent yourself because it is an abstract class. On the other hand, as is so often the case in Java, when you go searching through the API documentation, you may find that the methods you are looking for are actually in the parent class JTextComponent rather than in the derived class. For example, the methods that get or set the text in a text field or text area are actually methods in JTextComponent.

Text Input
javax.swing.text.JTextComponent 1.2
  • void setText(String t)

    changes the text of a text component.

    Parameters:

    t

    The new text

  • String getText()

    returns the text contained in this text component.

  • void setEditable(boolean b)

    determines whether the user can edit the content of the JTextComponent.

Text Fields

The usual way to add a text field to a window is to add it to a panel or other container—just as you would a button:

JPanel panel = new JPanel();
JTextField textField = new JTextField("Default input", 20);
panel.add(textField);

This code adds a text field and initializes the text field by placing the string "Default input" inside it. The second parameter of this constructor sets the width. In this case, the width is 20 “columns.” Unfortunately, a column is a rather imprecise measurement. One column is the expected width of one character in the font you are using for the text. The idea is that if you expect the inputs to be n characters or less, you are supposed to specify n as the column width. In practice, this measurement doesn’t work out too well, and you should add 1 or 2 to the maximum input length to be on the safe side. Also, keep in mind that the number of columns is only a hint to the AWT that gives the preferred size. If the layout manager needs to grow or shrink the text field, it can adjust its size. The column width that you set in the JTextField constructor is not an upper limit on the number of characters the user can enter. The user can still type in longer strings, but the input scrolls when the text exceeds the length of the field. Users tend to find scrolling text fields irritating, so you should size the fields generously. If you need to reset the number of columns at run time, you can do that with the setColumns method.

Tip

Tip

After changing the size of a text box with the setColumns method, call the revalidate method of the surrounding container.

textField.setColumns(10);
panel.revalidate();

The revalidate method recomputes the size and layout of all components in a container. After you use the revalidate method, the layout manager resizes the container, and the changed size of the text field will be visible.

The revalidate method belongs to the JComponent class. It doesn’t immediately resize the component but merely marks it for resizing. This approach avoids repetitive calculations if multiple components request to be resized. However, if you want to recompute all components inside a JFrame, you have to call the validate method—JFrame doesn’t extend JComponent.

In general, you want to let the user add text (or edit the existing text) in a text field. Quite often these text fields start out blank. To make a blank text field, just leave out the string as a parameter for the JTextField constructor:

JTextField textField = new JTextField(20);

You can change the content of the text field at any time by using the setText method from the JTextComponent parent class mentioned in the previous section. For example:

textField.setText("Hello!");

And, as was also mentioned in the previous section, you can find out what the user typed by calling the getText method. This method returns the exact text that the user typed. To trim any extraneous leading and trailing spaces from the data in a text field, apply the trim method to the return value of getText:

String text = textField.getText().trim();

To change the font in which the user text appears, use the setFont method.

Tip
javax.swing.JTextField 1.2
  • JTextField(int cols)

    constructs an empty JTextField with a specified number of columns.

    Parameters:

    cols

    The number of columns in the field

  • JTextField(String text, int cols)

    constructs a new JTextField with an initial string and the specified number of columns.

    Parameters:

    text

    The text to display

     

    cols

    The number of columns

  • void setColumns(int cols)

    tells the text field the number of columns it should use.

    Parameters:

    cols

    The number of columns

Tip
javax.swing.JComponent 1.2
  • void revalidate()

    causes the position and size of a component to be recomputed.

Tip
java.awt.Component 1.0
  • void validate()

    recomputes the position and size of a component. If the component is a container, the positions and sizes of its components are recomputed.

Labels and Labeling Components

Labels are components that hold text. They have no decorations (for example, no boundaries). They also do not react to user input. You can use a label to identify components. For example, unlike buttons, text fields have no label to identify them. To label a component that does not itself come with an identifier:

  1. Construct a JLabel component with the correct text.

  2. Place it close enough to the component you want to identify so that the user can see that the label identifies the correct component.

The constructor for a JLabel lets you specify the initial text or icon, and optionally, the alignment of the content. You use constants from the SwingConstants interface to specify alignment. That interface defines a number of useful constants such as LEFT, RIGHT, CENTER, NORTH, EAST, and so on. The JLabel class is one of several Swing classes that implement this interface. Therefore, you can specify a right-aligned label either as

JLabel label = new JLabel("Minutes", SwingConstants.RIGHT);

or

JLabel label = new JLabel("Minutes", JLabel.RIGHT);

The setText and setIcon methods let you set the text and icon of the label at run time.

Tip

Tip

Beginning with JDK 1.3, you can use both plain and HTML text in buttons, labels, and menu items. We don’t recommend HTML in buttons—it interferes with the look and feel. But HTML in labels can be very effective. Simply surround the label string with <html>. . .</html>, like this:

label = new JLabel("<html><b>Required</b> entry:</html>");

Fair warning—the first component with an HTML label takes some time to be displayed because the rather complex HTML rendering code must be loaded.

Labels can be positioned inside a container like any other component. This means you can use the techniques you have seen before to place labels where you need them.

Tip
javax.swing.JLabel 1.2
  • JLabel(String text)

    constructs a label with left-aligned text.

    Parameters:

    text

    The text in the label

  • JLabel(Icon icon)

    constructs a label with a left-aligned icon.

    Parameters:

    icon

    The icon in the label

  • JLabel(String text, int align)

    constructs a label with the given text and alignment

    Parameters:

    text

    The text in the label

     

    align

    One of the SwingConstants constants LEFT, CENTER, or RIGHT

  • JLabel(String text, Icon icon, int align)

    constructs a label with both text and an icon. The icon is to the left of the text.

    Parameters:

    text

    The text in the label

     

    icon

    The icon in the label

     

    align

    One of the SwingConstants constants LEFT, CENTER, or RIGHT

  • void setText(String text)

    sets the text of this label.

    Parameters:

    text

    The text in the label

  • void setIcon(Icon icon)

    sets the icon of this label.

    Parameters:

    icon

    The icon in the label

Change Tracking in Text Fields

Let us put a few text fields to work. Figure 9–12 shows the running application listed in Example 9–2. The program shows a clock and two text fields that enter the hours and minutes. Whenever the content of the text fields changes, the clock is updated.

Text field exampleclock text field example

Figure 9–12. Text field example

To track every change in the text field requires a bit of an effort. First of all, note that it is not a good idea to monitor keystrokes. Some keystrokes (such as the arrow keys) don’t change the text. And, depending on the look and feel, there may be mouse actions that result in text changes. As you saw in the beginning of this chapter, the Swing text field is implemented in a rather general way: the string that you see in the text field is just a visible manifestation (the view) of an underlying data structure (the model). Of course, for a humble text field, there is no great difference between the two. The view is a displayed string, and the model is a string object. But the same architecture is used in more advanced editing components to present formatted text, with fonts, paragraphs, and other attributes that are internally represented by a more complex data structure. The model for all text components is described by the Document interface, which covers both plain text and formatted text (such as HTML). The point is that you can ask the document (and not the text component) to notify you whenever the data has changed, by installing a document listener:

textField.getDocument().addDocumentListener(listener);

When the text has changed, one of the following DocumentListener methods is called:

void insertUpdate(DocumentEvent event)
void removeUpdate(DocumentEvent event)
void changedUpdate(DocumentEvent event)

The first two methods are called when characters have been inserted or removed. The third method is not called at all for text fields. For more complex document types, it would be called when some other change, such as a change in formatting, has occurred. Unfortunately, there is no single callback to tell you that the text has changed—usually you don’t much care how it has changed. And there is no adapter class either. Thus, your document listener must implement all three methods. Here is what we do in our sample program:

private class ClockFieldListener implements DocumentListener
{
   public void insertUpdate(DocumentEvent event) { setClock(); }
   public void removeUpdate(DocumentEvent event) { setClock(); }
   public void changedUpdate(DocumentEvent event) {}
}

The setClock method uses the getText method to obtain the current user-input strings from the text fields. Unfortunately, that is what we get: strings. We need to convert the strings to integers by using the familiar, if cumbersome, incantation:

int hours = Integer.parseInt(hourField.getText().trim());
int minutes = Integer.parseInt(minuteField.getText().trim());

But this code won’t work right when the user types a noninteger string, such as "two", into the text field or even leaves the field blank. For now, we catch the NumberFormatException that the parseInt method throws, and we simply don’t update the clock when the text field entry is not a number. In the next section, you see how you can prevent the user from entering invalid input in the first place.

Note

Note

Instead of listening to document events, you can also add an action event listener to a text field. The action listener is notified whenever the user presses the ENTER key. We don’t recommend this approach, because users don’t always remember to press ENTER when they are done entering data. If you use an action listener, you should also install a focus listener so that you can track when the user leaves the text field.

Finally, note how the ClockPanel constructor sets the preferred size:

public ClockPanel()
{
   setPreferredSize(new Dimension(2 * RADIUS + 1, 2 * RADIUS + 1));
}

When the frame’s pack method computes the frame size, it uses the panel’s preferred size.

Example 9–2. TextTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.awt.geom.*;
  4. import javax.swing.*;
  5. import javax.swing.event.*;
  6.
  7. public class TextTest
  8. {
  9.    public static void main(String[] args)
 10.    {
 11.      TextTestFrame frame = new TextTestFrame();
 12.      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13.      frame.setVisible(true);
 14.    }
 15. }
 16.
 17. /**
 18.    A frame with two text fields to set a clock.
 19. */
 20. class TextTestFrame extends JFrame
 21. {
 22.    public TextTestFrame()
 23.    {
 24.       setTitle("TextTest");
 25.
 26.       DocumentListener listener = new ClockFieldListener();
 27.
 28.       // add a panel with text fields
 29.
 30.       JPanel panel = new JPanel();
 31.
 32.       panel.add(new JLabel("Hours:"));
 33.       hourField = new JTextField("12", 3);
 34.       panel.add(hourField);
 35.       hourField.getDocument().addDocumentListener(listener);
 36.
 37.       panel.add(new JLabel("Minutes:"));
 38.       minuteField = new JTextField("00", 3);
 39.       panel.add(minuteField);
 40.       minuteField.getDocument().addDocumentListener(listener);
 41.
 42.       add(panel, BorderLayout.SOUTH);
 43.
 44.       // add the clock
 45.
 46.       clock = new ClockPanel();
 47.       add(clock, BorderLayout.CENTER);
 48.       pack();
 49.    }
 50.
 51.    /**
 52.       Set the clock to the values stored in the text fields.
 53.    */
 54.    public void setClock()
 55.    {
 56.       try
 57.       {
 58.          int hours = Integer.parseInt(hourField.getText().trim());
 59.          int minutes = Integer.parseInt(minuteField.getText().trim());
 60.          clock.setTime(hours, minutes);
 61.       }
 62.       catch (NumberFormatException e) {}
 63.       // don't set the clock if the input can't be parsed
 64.    }
 65.
 66.    public static final int DEFAULT_WIDTH = 300;
 67.    public static final int DEFAULT_HEIGHT = 300;
 68.
 69.    private JTextField hourField;
 70.    private JTextField minuteField;
 71.    private ClockPanel clock;
 72.
 73.    private class ClockFieldListener implements DocumentListener
 74.    {
 75.       public void insertUpdate(DocumentEvent event) { setClock(); }
 76.       public void removeUpdate(DocumentEvent event) { setClock(); }
 77.       public void changedUpdate(DocumentEvent event) {}
 78.    }
 79. }
 80.
 81. /**
 82.    A panel that draws a clock.
 83. */
 84. class ClockPanel extends JPanel
 85. {
 86.    public ClockPanel()
 87.    {
 88.       setPreferredSize(new Dimension(2 * RADIUS + 1, 2 * RADIUS + 1));
 89.    }
 90.
 91.    public void paintComponent(Graphics g)
 92.    {
 93.       // draw the circular boundary
 94.
 95.       super.paintComponent(g);
 96.       Graphics2D g2 = (Graphics2D) g;
 97.       Ellipse2D circle = new Ellipse2D.Double(0, 0, 2 * RADIUS, 2 * RADIUS);
 98.       g2.draw(circle);
 99.
100.       // draw the hour hand
101.
102.       double hourAngle = Math.toRadians(90 - 360 * minutes / (12 * 60));
103.       drawHand(g2, hourAngle, HOUR_HAND_LENGTH);
104.
105.       // draw the minute hand
106.
107.       double minuteAngle = Math.toRadians(90 - 360 * minutes / 60);
108.       drawHand(g2, minuteAngle, MINUTE_HAND_LENGTH);
109.    }
110.
111.    public void drawHand(Graphics2D g2, double angle, double handLength)
112.    {
113.       Point2D end = new Point2D.Double(
114.          RADIUS + handLength * Math.cos(angle),
115.          RADIUS - handLength * Math.sin(angle));
116.       Point2D center = new Point2D.Double(RADIUS, RADIUS);
117.       g2.draw(new Line2D.Double(center, end));
118.    }
119.
120.    /**
121.       Set the time to be displayed on the clock
122.       @param h hours
123.       @param m minutes
124.    */
125.    public void setTime(int h, int m)
126.    {
127.       minutes = h * 60 + m;
128.       repaint();
129.    }
130.
131.    private double minutes = 0;
132.    private int RADIUS = 100;
133.    private double MINUTE_HAND_LENGTH = 0.8 * RADIUS;
134.    private double HOUR_HAND_LENGTH = 0.6 * RADIUS;
135. }
TextTest.java
javax.swing.JComponent 1.2
  • void setPreferredSize(Dimension d)

    sets the preferred size of this component.

TextTest.java
javax.swing.text.Document 1.2
  • int getLength()

    returns the number of characters currently in the document.

  • String getText(int offset, int length)

    returns the text contained within the given portion of the document.

    Parameters:

    offset

    The start of the text

     

    length

    The length of the desired string

  • void addDocumentListener(DocumentListener listener)

    registers the listener to be notified when the document changes.

TextTest.java
javax.swing.event.DocumentEvent 1.2
  • Document getDocument()

    gets the document that is the source of the event.

TextTest.java
javax.swing.event.DocumentListener 1.2
  • void changedUpdate(DocumentEvent event)

    is called whenever an attribute or set of attributes changes.

  • void insertUpdate(DocumentEvent event)

    is called whenever an insertion into the document occurs.

  • void removeUpdate(DocumentEvent event)

    is called whenever a portion of the document has been removed.

Password Fields

Password fields are a special kind of text field. To avoid nosy bystanders being able to glance at a password, the characters that the user entered are not actually displayed. Instead, each typed character is represented by an echo character, typically an asterisk (*). Swing supplies a JPasswordField class that implements such a text field.

The password field is another example of the power of the model-view-controller architecture pattern. The password field uses the same model to store the data as a regular text field, but its view has been changed to display all characters as echo characters.

Password FieldsSwingpassword fieldsfieldspassword fieldspasswordspassword text fieldstextpassword fields
javax.swing.JPasswordField 1.2
  • JPasswordField(String text, int columns)

    constructs a new password field.

    Parameters:

    text

    The text to be displayed, null if none

     

    columns

    The number of columns

  • void setEchoChar(char echo)

    sets the echo character for this password field. This is advisory; a particular look and feel may insist on its own choice of echo character. A value of 0 resets the echo character to the default.

    Parameters:

    echo

    The echo character to display instead of the text characters

  • char[] getPassword()

    returns the text contained in this password field. For stronger security, you should overwrite the content of the returned array after use. (The password is not returned as a String because a string would stay in the virtual machine until it is garbage-collected.)

Formatted Input Fields

In the last example program, we wanted the program user to type numbers, not arbitrary strings. That is, the user is allowed to enter only digits 0 through 9 and a hyphen (–). The hyphen, if present at all, must be the first symbol of the input string.

On the surface, this input validation task sounds simple. We can install a key listener to the text field and then consume all key events that aren’t digits or a hyphen. Unfortunately, this simple approach, although commonly recommended as a method for input validation, does not work well in practice. First, not every combination of the valid input characters is a valid number. For example, --3 and 3-3 aren’t valid, even though they are made up from valid input characters. But, more important, there are other ways of changing the text that don’t involve typing character keys. Depending on the look and feel, certain key combinations can be used to cut, copy, and paste text. For example, in the Metal look and feel, the CTRL+V key combination pastes the content of the paste buffer into the text field. That is, we also need to monitor that the user doesn’t paste in an invalid character. Clearly, trying to filter keystrokes to ensure that the content of the text field is always valid begins to look like a real chore. This is certainly not something that an application programmer should have to worry about.

Perhaps surprisingly, before JDK 1.4, there were no components for entering numeric values. Starting with the first edition of Core Java, we supplied an implementation for an IntTextField, a text field for entering a properly formatted integer. In every new edition, we changed the implementation to take whatever limited advantage we could from the various half-baked validation schemes that were added to each version of the JDK. Finally, in JDK 1.4, the Swing designers faced the issues head-on and supplied a versatile JFormattedTextField class that can be used not just for numeric input but also for dates and for even more esoteric formatted values such as IP addresses.

Integer Input

Let’s get started with an easy case first: a text field for integer input.

JFormattedTextField intField = new JFormattedTextField(NumberFormat.getIntegerInstance());

The NumberFormat.getIntegerInstance returns a formatter object that formats integers, using the current locale. In the US locale, commas are used as decimal separators, allowing users to enter values such as 1,729. The internationalization chapter in Volume 2 explains in detail how you can select other locales.

As with any text field, you can set the number of columns:

intField.setColumns(6);

You can set a default value with the setValue method. That method takes an Object parameter, so you’ll need to wrap the default int value in an Integer object:

intField.setValue(new Integer(100));

Typically, users will supply inputs in multiple text fields and then click a button to read all values. When the button is clicked, you can get the user-supplied value with the getValue method. That method returns an Object result, and you need to cast it into the appropriate type. The JFormattedTextField returns an object of type Long if the user edited the value. However, if the user made no changes, the original Integer object is returned. Therefore, you should cast the return value to the common superclass Number:

Number value = (Number) intField.getValue();
int v = value.intValue();

The formatted text field is not very interesting until you consider what happens when a user provides illegal input. That is the topic of the next section.

Behavior on Loss of Focus

Consider what happens when a user supplies input to a text field. The user types input and eventually decides to leave the field, perhaps by clicking on another component with the mouse. Then the text field loses focus. The I-beam cursor is no longer visible in the text field, and keystrokes are directed toward a different component.

When the formatted text field loses focus, the formatter looks at the text string that the user produced. If the formatter knows how to convert the text string to an object, the text is valid. Otherwise it is invalid. You can use the isEditValid method to check whether the current content of the text field is valid.

The default behavior on loss of focus is called “commit or revert.” If the text string is valid, it is committed. The formatter converts it to an object. That object becomes the current value of the field (that is, the return value of the getValue method that you saw in the preceding section). The value is then converted back to a string, which becomes the text string that is visible in the field. For example, the integer formatter recognizes the input 1729 as valid, sets the current value to new Long(1729) and then converts it back into a string with a decimal comma: 1,729.

Conversely, if the text string is invalid, then the current value is not changed and the text field reverts to the string that represents the old value. For example, if the user enters a bad value, such as x1, then the old value is restored when the text field loses focus.

Note

Note

The integer formatter regards a text string as valid if it starts with an integer. For example, 1729x is a valid string. It is converted to the number 1729, which is then formatted as the string 1,729.

You can set other behaviors with the setFocusLostBehavior method. The “commit” behavior is subtly different from the default. If the text string is invalid, then both the text string and the field value stay unchanged—they are now out of sync. The “persist” behavior is even more conservative. Even if the text string is valid, neither the text field nor the current value are changed. You would need to call commitEdit, setValue, or setText to bring them back in sync. Finally, there is a “revert” behavior that doesn’t ever seem to be useful. Whenever focus is lost, the user input is disregarded, and the text string reverts to the old value.

Note

Note

Generally, the “commit or revert” default behavior is reasonable. There is just one potential problem. Suppose a dialog box contains a text field for an integer value. A user enters a string " 1729", with a leading space and then clicks the OK button. The leading space makes the number invalid, and the field value reverts to the old value. The action listener of the OK button retrieves the field value and closes the dialog. The user never knows that the new value has been rejected. In this situation, it is appropriate to select the “commit” behavior and have the OK button listener check that all field edits are valid before closing the dialog.

Filters

This basic functionality of formatted text fields is straightforward and sufficient for most uses. However, you can add a couple of refinements. Perhaps you want to prevent the user from entering nondigits altogether. You achieve that behavior with a document filter. Recall that in the model-view-controller architecture, the controller translates input events into commands that modify the underlying document of the text field, that is, the text string that is stored in a PlainDocument object. For example, whenever the controller processes a command that causes text to be inserted into the document, it calls the “insert string” command. The string to be inserted can be either a single character or the content of the paste buffer. A document filter can intercept this command and modify the string or cancel the insertion altogether. Here is the code for the insertString method of a filter that analyzes the string to be inserted and inserts only the characters that are digits or a - sign. (The code handles supplementary Unicode characters, as explained in Chapter 3. See Chapter 12 for the StringBuilder class.)

public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
   throws BadLocationException
{
   StringBuilder builder = new StringBuilder(string);
   for (int i = builder.length() - 1; i >= 0; i--)
   {
      int cp = builder.codePointAt(i);
      if (!Character.isDigit(cp) && cp != '-')
      {
         builder.deleteCharAt(i);
         if (Character.isSupplementaryCodePoint(cp))
         {
            i--;
            builder.deleteCharAt(i);
         }
      }
   }
   super.insertString(fb, offset, builder.toString(), attr);
}

You should also override the replace method of the DocumentFilter class—it is called when text is selected and then replaced. The implementation of the replace method is straightforward—see the program at the end of this section.

Now you need to install the document filter. Unfortunately, there is no straightforward method to do that. You need to override the getDocumentFilter method of a formatter class, and pass an object of that formatter class to the JFormattedTextField. The integer text field uses an InternationalFormatter that is initialized with NumberFormat.getIntegerInstance(). Here is how you install a formatter to yield the desired filter:

JFormattedTextField intField = new JFormattedTextField(new
      InternationalFormatter(NumberFormat.getIntegerInstance())
      {
         protected DocumentFilter getDocumentFilter()
         {
            return filter;
         }
         private DocumentFilter filter = new IntFilter();
      });

Note

Note

The JDK documentation states that the DocumentFilter class was invented to avoid subclassing. Until JDK 1.3, filtering in a text field was achieved by extending the PlainDocument class and overriding the insertString and replace methods. Now the PlainDocument class has a pluggable filter instead. That is a splendid improvement. It would have been even more splendid if the filter had also been made pluggable in the formatter class. Alas, it was not, and we must subclass the formatter.

Try out the FormatTest example program at the end of this section. The third text field has a filter installed. You can insert only digits or the minus (’-’) character. Note that you can still enter invalid strings such as "1-2-3". In general, it is impossible to avoid all invalid strings through filtering. For example, the string "-" is invalid, but a filter can’t reject it because it is a prefix of a legal string "-1". Even though filters can’t give perfect protection, it makes sense to use them to reject inputs that are obviously invalid.

Tip

Tip

Another use for filtering is to turn all characters of a string to upper case. Such a filter is easy to write. In the insertString and replace methods of the filter, convert the string to be inserted to upper case and then invoke the superclass method.

Verifiers

There is another potentially useful mechanism to alert users to invalid inputs. You can attach a verifier to any JComponent. If the component loses focus, then the verifier is queried. If the verifier reports the content of the component to be invalid, the component immediately regains focus. The user is thus forced to fix the content before supplying other inputs.

A verifier must extend the abstract InputVerifier class and define a verify method. It is particularly easy to define a verifier that checks formatted text fields. The isEditValid method of the JFormattedTextField class calls the formatter and returns true if the formatter can turn the text string into an object. Here is the verifier.

class FormattedTextFieldVerifier extends InputVerifier
{
   public boolean verify(JComponent component)
   {
      JFormattedTextField field = (JFormattedTextField) component;
      return field.isEditValid();
   }
}

You can attach it to any JFormattedTextField:

intField.setInputVerifier(new FormattedTextFieldVerifier());

However, a verifier is not entirely foolproof. If you click on a button, then the button notifies its action listeners before an invalid component regains focus. The action listeners can then get an invalid result from the component that failed verification. There is a reason for this behavior: users may want to press a Cancel button without first having to fix an invalid input.

The fourth text field in the example program has a verifier attached. Try entering an invalid number (such as x1729) and press the TAB key or click with the mouse on another text field. Note that the field immediately regains focus. However, if you press the OK button, the action listener calls getValue, which reports the last good value.

Other Standard Formatters

Besides the integer formatter, the JFormattedTextField supports several other formatters. The NumberFormat class has static methods

getNumberInstance
getCurrencyInstance
getPercentInstance

that yield formatters of floating-point numbers, currency values, and percentages. For example, you can obtain a text field for the input of currency values by calling

JFormattedTextField currencyField = new JFormattedTextField(NumberFormat
Other Standard Formatters.getCurrencyInstance());

To edit dates and times, call one of the static methods of the DateFormat class:

getDateInstance
getTimeInstance
getDateTimeInstance

For example,

JFormattedTextField dateField = new JFormattedTextField(DateFormat.getDateInstance());

This field edits a date in the default or “medium” format such as

Feb 24, 2002

You can instead choose a “short” format such as

2/24/02

by calling

DateFormat.getDateInstance(DateFormat.SHORT)

Note

Note

By default, the date format is “lenient.” That is, an invalid date such as February 31, 2002, is rolled over to the next valid date, March 3, 2002. That behavior may be surprising to your users. In that case, call setLenient(false) on the DateFormat object.

The DefaultFormatter can format objects of any class that has a constructor with a string parameter and a matching toString method. For example, the URL class has a URL(String) constructor that can be used to construct a URL from a string, such as

URL url = new URL("http://java.sun.com");

Therefore, you can use the DefaultFormatter to format URL objects. The formatter calls toString on the field value to initialize the field text. When the field loses focus, the formatter constructs a new object of the same class as the current value, using the constructor with a String parameter. If that constructor throws an exception, then the edit is not valid. You can try that out in the example program by entering a URL that does not start with a prefix such as "http:".

Note

Note

By default, the DefaultFormatter is in overwrite mode. That is different from the other formatters and not very useful. Call setOverwriteMode(false) to turn off overwrite mode.

Finally, the MaskFormatter is useful for fixed-size patterns that contain some constant and some variable characters. For example, social security numbers (such as 078-05-1120) can be formatted with a

new MaskFormatter("###-##-####")

The # symbol denotes a single digit. Table 9–2 shows the symbols that you can use in a mask formatter.

Table 9–2. MaskFormatter Symbols

#

A digit

?

A letter

U

A letter, converted to upper case

L

A letter, converted to lower case

A

A letter or digit

H

A hexadecimal digit [0-9A-Fa-f]

*

Any character

'

Escape character to include a symbol in the pattern

You can restrict the characters that can be typed into the field by calling one of the methods of the MaskFormatter class:

setValidCharacters
setInvalidCharacters

For example, to read in a letter grade (such as A+ or F), you could use

MaskFormatter formatter = new MaskFormatter("U*");
formatter.setValidCharacters("ABCDF+- ");

However, there is no way of specifying that the second character cannot be a letter.

Note that the string that is formatted by the mask formatter has exactly the same length as the mask. If the user erases characters during editing, then they are replaced with the placeholder character. The default placeholder character is a space, but you can change it with the setPlaceholderCharacter method, for example,

formatter.setPlaceholderCharacter('0'),

By default, a mask formatter is in overtype mode, which is quite intuitive—try it out in the example program. Also note that the caret position jumps over the fixed characters in the mask.

The mask formatter is very effective for rigid patterns such as social security numbers or American telephone numbers. However, note that no variation at all is permitted in the mask pattern. For example, you cannot use a mask formatter for international telephone numbers that have a variable number of digits.

Custom Formatters

If none of the standard formatters is appropriate, it is fairly easy to define your own formatter. Consider 4-byte IP addresses such as

130.65.86.66

You can’t use a MaskFormatter because each byte might be represented by one, two, or three digits. Also, we want to check in the formatter that each byte’s value is at most 255.

To define your own formatter, extend the DefaultFormatter class and override the methods

String valueToString(Object value)
Object stringToValue(String text)

The first method turns the field value into the string that is displayed in the text field. The second method parses the text that the user typed and turns it back into an object. If either method detects an error, it should throw a ParseException.

In our example program, we store an IP address in a byte[] array of length 4. The valueToString method forms a string that separates the bytes with periods. Note that byte values are signed quantities between −128 and 127. To turn negative byte values into unsigned integer values, you add 256.

public String valueToString(Object value) throws ParseException
{
   if (!(value instanceof byte[]))
      throw new ParseException("Not a byte[]", 0);
   byte[] a = (byte[]) value;
   if (a.length != 4)
       throw new ParseException("Length != 4", 0);
   StringBuilder builder = new StringBuilder();
   for (int i = 0; i < 4; i++)
   {
      int b = a[i];
      if (b < 0) b += 256;
      builder.append(String.valueOf(b));
      if (i < 3) builder.append('.'),
   }
   return builder.toString();
}

Conversely, the stringToValue method parses the string and produces a byte[] object if the string is valid. If not, it throws a ParseException.

public Object stringToValue(String text) throws ParseException
{
   StringTokenizer tokenizer = new StringTokenizer(text, ".");
   byte[] a = new byte[4];
   for (int i = 0; i < 4; i++)
   {
      int b = 0;
      try
      {
         b = Integer.parseInt(tokenizer.nextToken());
      }
      catch (NumberFormatException e)
      {
         throw new ParseException("Not an integer", 0);
      }
      if (b < 0 || b >= 256)
         throw new ParseException("Byte out of range", 0);
      a[i] = (byte) b;
   }
   return a;
}

Try out the IP address field in the sample program. If you enter an invalid address, the field reverts to the last valid address.

The program in Example 9–3 shows various formatted text fields in action (see Figure 9–13). Click the Ok button to retrieve the current values from the fields.

The FormatTest program

Figure 9–13. The FormatTest program

Note

Note

The “Swing Connection” online newsletter has a short article describing a formatter that matches any regular expression. See http://java.sun.com/products/jfc/tsc/articles/reftf/.

Example 9–3. FormatTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.lang.reflect.*;
  4. import java.net.*;
  5. import java.text.*;
  6. import java.util.*;
  7. import javax.swing.*;
  8. import javax.swing.text.*;
  9.
 10. /**
 11.    A program to test formatted text fields
 12. */
 13. public class FormatTest
 14. {
 15.    public static void main(String[] args)
 16.    {
 17.       FormatTestFrame frame = new FormatTestFrame();
 18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 19.       frame.setVisible(true);
 20.    }
 21. }
 22.
 23. /**
 24.    A frame with a collection of formatted text fields and
 25.    a button that displays the field values.
 26. */
 27. class FormatTestFrame extends JFrame
 28. {
 29.    public FormatTestFrame()
 30.    {
 31.       setTitle("FormatTest");
 32.       setSize(WIDTH, HEIGHT);
 33.
 34.       JPanel buttonPanel = new JPanel();
 35.       okButton = new JButton("Ok");
 36.       buttonPanel.add(okButton);
 37.       add(buttonPanel, BorderLayout.SOUTH);
 38.
 39.       mainPanel = new JPanel();
 40.       mainPanel.setLayout(new GridLayout(0, 3));
 41.       add(mainPanel, BorderLayout.CENTER);
 42.
 43.       JFormattedTextField intField = new JFormattedTextField(NumberFormat
FormatTest.java.getIntegerInstance());
 44.       intField.setValue(new Integer(100));
 45.       addRow("Number:", intField);
 46.
 47.       JFormattedTextField intField2 = new JFormattedTextField(NumberFormat
FormatTest.java.getIntegerInstance());
 48.       intField2.setValue(new Integer(100));
 49.       intField2.setFocusLostBehavior(JFormattedTextField.COMMIT);
 50.       addRow("Number (Commit behavior):", intField2);
 51.
 52.       JFormattedTextField intField3
 53.          = new JFormattedTextField(new
 54.             InternationalFormatter(NumberFormat.getIntegerInstance())
 55.             {
 56.                protected DocumentFilter getDocumentFilter()
 57.                {
 58.                   return filter;
 59.                }
 60.                private DocumentFilter filter = new IntFilter();
 61.             });
 62.       intField3.setValue(new Integer(100));
 63.       addRow("Filtered Number", intField3);
 64.
 65.       JFormattedTextField intField4 = new JFormattedTextField(NumberFormat
FormatTest.java.getIntegerInstance());
 66.       intField4.setValue(new Integer(100));
 67.       intField4.setInputVerifier(new FormattedTextFieldVerifier());
 68.       addRow("Verified Number:", intField4);
 69.
 70.       JFormattedTextField currencyField
 71.          = new JFormattedTextField(NumberFormat.getCurrencyInstance());
 72.       currencyField.setValue(new Double(10));
 73.       addRow("Currency:", currencyField);
 74.
 75.       JFormattedTextField dateField = new JFormattedTextField(DateFormat
FormatTest.java.getDateInstance());
 76.       dateField.setValue(new Date());
 77.       addRow("Date (default):", dateField);
 78.
 79.       DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT);
 80.       format.setLenient(false);
 81.       JFormattedTextField dateField2 = new JFormattedTextField(format);
 82.       dateField2.setValue(new Date());
 83.       addRow("Date (short, not lenient):", dateField2);
 84.
 85.       try
 86.       {
 87.          DefaultFormatter formatter = new DefaultFormatter();
 88.          formatter.setOverwriteMode(false);
 89.          JFormattedTextField urlField = new JFormattedTextField(formatter);
 90.          urlField.setValue(new URL("http://java.sun.com"));
 91.          addRow("URL:", urlField);
 92.       }
 93.       catch (MalformedURLException e)
 94.       {
 95.          e.printStackTrace();
 96.       }
 97.
 98.       try
 99.       {
100.          MaskFormatter formatter = new MaskFormatter("###-##-####");
101.          formatter.setPlaceholderCharacter('0'),
102.          JFormattedTextField ssnField = new JFormattedTextField(formatter);
103.          ssnField.setValue("078-05-1120");
104.          addRow("SSN Mask:", ssnField);
105.       }
106.       catch (ParseException exception)
107.       {
108.          exception.printStackTrace();
109.       }
110.
111.       JFormattedTextField ipField = new JFormattedTextField(new IPAddressFormatter());
112.       ipField.setValue(new byte[] { (byte) 130, 65, 86, 66 });
113.       addRow("IP Address:", ipField);
114.    }
115.
116.    /**
117.       Adds a row to the main panel.
118.       @param labelText the label of the field
119.       @param field the sample field
120.    */
121.    public void addRow(String labelText, final JFormattedTextField field)
122.    {
123.       mainPanel.add(new JLabel(labelText));
124.       mainPanel.add(field);
125.       final JLabel valueLabel = new JLabel();
126.       mainPanel.add(valueLabel);
127.       okButton.addActionListener(new
128.          ActionListener()
129.          {
130.             public void actionPerformed(ActionEvent event)
131.             {
132.                Object value = field.getValue();
133.                if (value.getClass().isArray())
134.                {
135.                   StringBuilder builder = new StringBuilder();
136.                   builder.append('{'),
137.                   for (int i = 0; i < Array.getLength(value); i++)
138.                   {
139.                      if (i > 0) builder.append(','),
140.                      builder.append(Array.get(value, i).toString());
141.                   }
142.                   builder.append('}'),
143.                   valueLabel.setText(builder.toString());
144.                }
145.                else
146.                   valueLabel.setText(value.toString());
147.             }
148.          });
149.    }
150.
151.    public static final int WIDTH = 500;
152.    public static final int HEIGHT = 250;
153.
154.    private JButton okButton;
155.    private JPanel mainPanel;
156. }
157.
158. /**
159.    A filter that restricts input to digits and a '-' sign.
160. */
161. class IntFilter extends DocumentFilter
162. {
163.    public void insertString(FilterBypass fb, int offset, String string, AttributeSet
FormatTest.java attr)
164.       throws BadLocationException
165.    {
166.       StringBuilder builder = new StringBuilder(string);
167.       for (int i = builder.length() - 1; i >= 0; i--)
168.       {
169.          int cp = builder.codePointAt(i);
170.          if (!Character.isDigit(cp) && cp != '-')
171.          {
172.             builder.deleteCharAt(i);
173.             if (Character.isSupplementaryCodePoint(cp))
174.             {
175.                i--;
176.                builder.deleteCharAt(i);
177.             }
178.          }
179.       }
180.       super.insertString(fb, offset, builder.toString(), attr);
181.    }
182.
183.    public void replace(FilterBypass fb, int offset, int length, String string,
FormatTest.java AttributeSet attr)
184.       throws BadLocationException
185.    {
186.       if (string != null)
187.       {
188.          StringBuilder builder = new StringBuilder(string);
189.          for (int i = builder.length() - 1; i >= 0; i--)
190.          {
191.             int cp = builder.codePointAt(i);
192.             if (!Character.isDigit(cp) && cp != '-')
193.             {
194.                builder.deleteCharAt(i);
195.                if (Character.isSupplementaryCodePoint(cp))
196.                {
197.                   i--;
198.                   builder.deleteCharAt(i);
199.                }
200.             }
201.          }
202.          string = builder.toString();
203.       }
204.       super.replace(fb, offset, length, string, attr);
205.    }
206. }
207.
208. /**
209.    A verifier that checks whether the content of
210.    a formatted text field is valid.
211. */
212. class FormattedTextFieldVerifier extends InputVerifier
213. {
214.    public boolean verify(JComponent component)
215.    {
216.       JFormattedTextField field = (JFormattedTextField) component;
217.       return field.isEditValid();
218.    }
219. }
220.
221. /**
222.    A formatter for 4-byte IP addresses of the form a.b.c.d
223. */
224. class IPAddressFormatter extends DefaultFormatter
225. {
226.    public String valueToString(Object value)
227.       throws ParseException
228.    {
229.       if (!(value instanceof byte[]))
230.          throw new ParseException("Not a byte[]", 0);
231.       byte[] a = (byte[]) value;
232.       if (a.length != 4)
233.          throw new ParseException("Length != 4", 0);
234.       StringBuilder builder = new StringBuilder();
235.       for (int i = 0; i < 4; i++)
236.       {
237.          int b = a[i];
238.          if (b < 0) b += 256;
239.          builder.append(String.valueOf(b));
240.          if (i < 3) builder.append('.'),
241.       }
242.       return builder.toString();
243.    }
244.
245.    public Object stringToValue(String text) throws ParseException
246.    {
247.       StringTokenizer tokenizer = new StringTokenizer(text, ".");
248.       byte[] a = new byte[4];
249.       for (int i = 0; i < 4; i++)
250.       {
251.          int b = 0;
252.          if (!tokenizer.hasMoreTokens())
253.             throw new ParseException("Too few bytes", 0);
254.          try
255.          {
256.             b = Integer.parseInt(tokenizer.nextToken());
257.          }
258.          catch (NumberFormatException e)
259.          {
260.             throw new ParseException("Not an integer", 0);
261.          }
262.          if (b < 0 || b >= 256)
263.             throw new ParseException("Byte out of range", 0);
264.          a[i] = (byte) b;
265.       }
266.       if (tokenizer.hasMoreTokens())
267.          throw new ParseException("Too many bytes", 0);
268.       return a;
269.    }
270. }
FormatTest.java
javax.swing.JFormattedTextField 1.4
  • JFormattedTextField(Format fmt)

    constructs a text field that uses the specified format.

  • JFormattedTextField(JFormattedTextField.AbstractFormatter formatter)

    constructs a text field that uses the specified formatter. Note that DefaultFormatter and InternationalFormatter are subclasses of JFormattedTextField.AbstractFormatter.

  • Object getValue()

    returns the current valid value of the field. Note that this may not correspond to the string that is being edited.

  • void setValue(Object value)

    attempts to set the value of the given object. The attempt fails if the formatter cannot convert the object to a string.

  • void commitEdit()

    attempts to set the valid value of the field from the edited string. The attempt may fail if the formatter cannot convert the string.

  • boolean isEditValid()

    checks whether the edited string represents a valid value.

  • void setFocusLostBehavior(int behavior)

  • int getFocusLostBehavior()

    set or get the “focus lost” behavior. Legal values for behavior are the constants COMMIT_OR_REVERT, REVERT, COMMIT, and PERSIST of the JFormattedTextField class.

FormatTest.java
java.text.DateFormat 1.1
  • static DateFormat getDateInstance()

  • static DateFormat getDateInstance(int dateStyle)

  • static DateFormat getTimeInstance()

  • static DateFormat getTimeInstance(int timeStyle)

  • static DateFormat getDateTimeInstance()

  • static DateFormat getDateTimeInstance(int dateStyle, int timeStyle)

    return formatters that yield the date, time, or both date and time of Date objects. Legal values for dateStyle and timeStyle are the constants SHORT, MEDIUM, LONG, FULL, and DEFAULT of the DateFormat class.

FormatTest.java
javax.swing.JFormattedTextField.AbstractFormatter 1.4
  • abstract String valueToString(Object value)

    converts a value to an editable string. Throws a ParseException if value is not appropriate for this formatter.

  • abstract Object stringToValue(String s)

    converts a string to a value. Throws a ParseException if s is not in the appropriate format.

  • DocumentFilter getDocumentFilter()

    override this method to provide a document filter that restricts inputs into the text field. A return value of null indicates that no filtering is needed.

FormatTest.java
javax.swing.text.DefaultFormatter  1.3
  • void setOverwriteMode(boolean mode)

  • boolean getOverwriteMode()

    set or get the overwrite mode. If mode is true, then new characters overwrite existing characters when editing text.

FormatTest.java
javax.swing.text.DocumentFilter 1.4
  • void insertString(DocumentFilter.FilterBypass bypass, int offset, String text, AttributeSet attrib)

    is invoked before a string is inserted into a document. You can override the method and modify the string. You can disable insertion by not calling super.insertString or by calling bypass methods to modify the document without filtering.

    Parameters:

    bypass

    An object that allows you to execute edit commands that bypass the filter

     

    offset

    The offset at which to insert the text

     

    text

    The characters to insert

     

    attrib

    The formatting attributes of the inserted text

  • void replace(DocumentFilter.FilterBypass bypass, int offset, int length, String text, AttributeSet attrib)

    is invoked before a part of a document is replaced with a new string. You can override the method and modify the string. You can disable replacement by not calling super.replace or by calling bypass methods to modify the document without filtering.

    Parameters:

    bypass

    An object that allows you to execute edit commands that bypass the filter

     

    offset

    The offset at which to insert the text

     

    length

    The length of the part to be replaced

     

    text

    The characters to insert

     

    attrib

    The formatting attributes of the inserted text

  • void remove(DocumentFilter.FilterBypass bypass, int offset, int length)

    is invoked before a part of a document is removed. Get the document by calling bypass.getDocument() if you need to analyze the effect of the removal.

    Parameters:

    bypass

    An object that allows you to execute edit commands that bypass the filter

     

    offset

    The offset of the part to be removed

     

    length

    The length of the part to be removed

FormatTest.java
javax.swing.text.MaskFormatter 1.4
  • MaskFormatter(String mask)

    constructs a mask formatter with the given mask. See Table 9–2 on page 367 for the symbols in a mask.

  • void setValidCharacters(String characters)

  • String getValidCharacters()

    set or get the valid editing characters. Only the characters in the given string are accepted for the variable parts of the mask.

  • void setInvalidCharacters(String characters)

  • String getInvalidCharacters()

    set or get the invalid editing characters. None of the characters in the given string are accepted as input.

  • void setPlaceholderCharacter(char ch)

  • char getPlaceholderCharacter()

    set or get the placeholder character that is used for variable characters in the mask that the user has not yet supplied. The default placeholder character is a space.

  • void setPlaceholder(String s)

  • String getPlaceholder()

    set or get the placeholder string. Its tail end is used if the user has not supplied all variable characters in the mask. If it is null or shorter than the mask, then the placeholder character fills remaining inputs.

  • void setValueContainsLiteralCharacters(boolean b)

  • boolean getValueContainsLiteralCharacters()

    set or get the “value contains literal characters” flag. If this flag is true, then the field value contains the literal (nonvariable) parts of the mask. If it is false, then the literal characters are removed. The default is true.

Text Areas

Sometimes, you need to collect user input that is more than one line long. As mentioned earlier, you use the JTextArea component for this collection. When you place a text area component in your program, a user can enter any number of lines of text, using the ENTER key to separate them. Each line ends with a ' '. If you need to break up the user’s entry into separate lines, you can use the StringTokenizer class (see Chapter 12). Figure 9–14 shows a text area at work.

A text area

Figure 9–14. A text area

In the constructor for the JTextArea component, you specify the number of rows and columns for the text area. For example:

textArea = new JTextArea(8, 40); // 8 lines of 40 columns each

where the columns parameter works as before—and you still need to add a few more columns for safety’s sake. Also, as before, the user is not restricted to the number of rows and columns; the text simply scrolls when the user inputs too much. You can also use the setColumns method to change the number of columns, and the setRows method to change the number of rows. These numbers only indicate the preferred size—the layout manager can still grow or shrink the text area.

If there is more text than the text area can display, then the remaining text is simply clipped. You can avoid clipping long lines by turning on line wrapping:

textArea.setLineWrap(true); // long lines are wrapped

This wrapping is a visual effect only; the text in the document is not changed—no ' ' characters are inserted into the text.

In Swing, a text area does not have scrollbars. If you want scrollbars, you have to insert the text area inside a scroll pane.

textArea = new JTextArea(8, 40);
JScrollPane scrollPane = new JScrollPane(textArea);

The scroll pane now manages the view of the text area. Scrollbars automatically appear if there is more text than the text area can display, and they vanish again if text is deleted and the remaining text fits inside the area. The scrolling is handled internally in the scroll pane—your program does not need to process scroll events.

Tip

Tip

This is a general mechanism that you will encounter many times when working with Swing—to add scrollbars to a component, put them inside a scroll pane.

Example 9–4 is the complete code for the text area demo. This program simply lets you edit text in a text area. Click on “Insert” to insert a sentence at the end of the text. Click the second button to turn line wrapping on and off. (Its name toggles between “Wrap” and “No wrap”.) Of course, you can simply use the keyboard to edit the text in the text area. Note how you can highlight a section of text, and how you can cut, copy, and paste with the CTRL+X, CTRL+C, and CTRL+V keys. (Keyboard shortcuts are specific to the look and feel. These particular key combinations work for the Metal and Windows look and feel.)

Note

Note

The JTextArea component displays plain text only, without special fonts or formatting. To display formatted text (such as HTML or RTF), you can use the JEditorPane and JTextPane classes. These classes are discussed in Volume 2.

Example 9–4. TextAreaTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class TextAreaTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       TextAreaFrame frame = new TextAreaFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a text area and buttons for text editing
17. */
18. class TextAreaFrame extends JFrame
19. {
20.    public TextAreaFrame()
21.    {
22.       setTitle("TextAreaTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       buttonPanel = new JPanel();
26.
27.       // add button to append text into the text area
28.
29.       JButton insertButton = new JButton("Insert");
30.       buttonPanel.add(insertButton);
31.       insertButton.addActionListener(new
32.          ActionListener()
33.          {
34.             public void actionPerformed(ActionEvent event)
35.             {
36.                textArea.append("The quick brown fox jumps over the lazy dog. ");
37.             }
38.          });
39.
40.       // add button to turn line wrapping on and off
41.
42.       wrapButton = new JButton("Wrap");
43.       buttonPanel.add(wrapButton);
44.       wrapButton.addActionListener(new
45.          ActionListener()
46.          {
47.             public void actionPerformed(ActionEvent event)
48.             {
49.                boolean wrap = !textArea.getLineWrap();
50.                textArea.setLineWrap(wrap);
51.                scrollPane.revalidate();
52.                wrapButton.setText(wrap ? "No Wrap" : "Wrap");
53.             }
54.          });
55.
56.        add(buttonPanel, BorderLayout.SOUTH);
57.
58.        // add a text area with scrollbars
59.
60.        textArea = new JTextArea(8, 40);
61.        scrollPane = new JScrollPane(textArea);
62.
63.       add(scrollPane, BorderLayout.CENTER);
64.    }
65.
66.    public static final int DEFAULT_WIDTH = 300;
67.    public static final int DEFAULT_HEIGHT = 300;
68.
69.    private JTextArea textArea;
70.    private JScrollPane scrollPane;
71.    private JPanel buttonPanel;
72.    private JButton wrapButton;
73. }
TextAreaTest.java
javax.swing.JTextArea 1.2
  • JTextArea(int rows, int cols)

    constructs a new text area.

    Parameters:

    rows

    The number of rows

     

    cols

    The number of columns

  • JTextArea(String text, int rows, int cols)

    constructs a new text area with an initial text.

    Parameters:

    text

    The initial text

     

    rows

    The number of rows

     

    cols

    The number of columns

  • void setColumns(int cols)

    tells the text area the preferred number of columns it should use.

    Parameters:

    cols

    The number of columns

  • void setRows(int rows)

    tells the text area the preferred number of rows it should use.

    Parameters:

    rows

    The number of rows

  • void append(String newText)

    appends the given text to the end of the text already in the text area.

    Parameters:

    newText

    The text to append

  • void setLineWrap(boolean wrap)

    turns line wrapping on or off.

    Parameters:

    wrap

    true if lines should be wrapped

  • void setWrapStyleWord(boolean word)

    If word is true, then long lines are wrapped at word boundaries. If it is false, then long lines are broken without taking word boundaries into account.

  • void setTabSize(int c)

    sets tab stops every c columns. Note that the tabs aren’t converted to spaces but cause alignment with the next tab stop.

    Parameters:

    c

    The number of columns for a tab stop

TextAreaTest.java
javax.swing.JScrollPane 1.2
  • JScrollPane(Component c)

    creates a scroll pane that displays the content of the specified component. Scrollbars are supplied when the component is larger than the view.

    Parameters:

    c

    The component to scroll

Choice Components

You now know how to collect text input from users, but there are many occasions for which you would rather give users a finite set of choices than have them enter the data in a text component. Using a set of buttons or a list of items tells your users what choices they have. (It also saves you the trouble of error checking.) In this section, you learn how to program checkboxes, radio buttons, lists of choices, and sliders.

Checkboxes

If you want to collect just a “yes” or “no” input, use a checkbox component. Checkboxes automatically come with labels that identify them. The user usually checks the box by clicking inside it and turns off the check mark by clicking inside the box again. To toggle the check mark, the user can also press the space bar when the focus is in the checkbox.

Figure 9–15 shows a simple program with two checkboxes, one to turn on or off the italic attribute of a font, and the other for boldface. Note that the second checkbox has focus, as indicated by the rectangle around the label. Each time the user clicks one of the checkboxes, we refresh the screen, using the new font attributes.

Checkboxes

Figure 9–15. Checkboxes

Checkboxes need a label next to them to identify their purpose. You give the label text in the constructor.

bold = new JCheckBox("Bold");

You use the setSelected method to turn a checkbox on or off. For example,

bold.setSelected(true);

The isSelected method then retrieves the current state of each checkbox. It is false if unchecked; true if checked.

When the user clicks on a checkbox, this triggers an action event. As always, you attach an action listener to the checkbox. In our program, the two checkboxes share the same action listener.

ActionListener listener = . . .
bold.addActionListener(listener);
italic.addActionListener(listener);

The actionPerformed method queries the state of the bold and italic checkboxes and sets the font of the panel to plain, bold, italic, or both bold and italic.

public void actionPerformed(ActionEvent event)
{
   int mode = 0;
   if (bold.isSelected()) mode += Font.BOLD;
   if (italic.isSelected()) mode += Font.ITALIC;
   label.setFont(new Font("Serif", mode, FONTSIZE));
}

Example 9–5 is the complete program listing for the checkbox example.

Example 9–5. CheckBoxTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class CheckBoxTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       CheckBoxFrame frame = new CheckBoxFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a sample text label and checkboxes for
17.    selecting font attributes.
18. */
19. class CheckBoxFrame extends JFrame
20. {
21.    public CheckBoxFrame()
22.    {
23.       setTitle("CheckBoxTest");
24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25.
26.       // add the sample text label
27.
28.       label = new JLabel("The quick brown fox jumps over the lazy dog.");
29.       label.setFont(new Font("Serif", Font.PLAIN, FONTSIZE));
30.       add(label, BorderLayout.CENTER);
31.
32.       // this listener sets the font attribute of
33.       // the label to the checkbox state
34.
35.       ActionListener listener = new
36.          ActionListener()
37.          {
38.             public void actionPerformed(ActionEvent event)
39.             {
40.                int mode = 0;
41.                if (bold.isSelected()) mode += Font.BOLD;
42.                if (italic.isSelected()) mode += Font.ITALIC;
43.                label.setFont(new Font("Serif", mode, FONTSIZE));
44.             }
45.          };
46.
47.       // add the checkboxes
48.
49.       JPanel buttonPanel = new JPanel();
50.
51.       bold = new JCheckBox("Bold");
52.       bold.addActionListener(listener);
53.       buttonPanel.add(bold);
54.
55.       italic = new JCheckBox("Italic");
56.       italic.addActionListener(listener);
57.       buttonPanel.add(italic);
58.
59.       add(buttonPanel, BorderLayout.SOUTH);
60.    }
61.
62.    public static final int DEFAULT_WIDTH = 300;
63.    public static final int DEFAULT_HEIGHT = 200;
64.
65.    private JLabel label;
66.    private JCheckBox bold;
67.    private JCheckBox italic;
68.
69.    private static final int FONTSIZE = 12;
70. }
CheckBoxTest.java
javax.swing.JCheckBox 1.2
  • JCheckBox(String label)

    constructs a checkbox with the given label that is initially unselected.

  • JCheckBox(String label, boolean state)

    constructs a checkbox with the given label and initial state.

  • JCheckBox(String label, Icon icon)

    constructs a checkbox with the given label and icon that is initially unselected.

  • boolean isSelected ()

    returns the state of the checkbox.

  • void setSelected(boolean state)

    sets the checkbox to a new state.

Radio Buttons

In the previous example, the user could check either, both, or neither of the two checkboxes. In many cases, we want to require the user to check only one of several boxes. When another box is checked, the previous box is automatically unchecked. Such a group of boxes is often called a radio button group because the buttons work like the station selector buttons on a radio. When you push in one button, the previously depressed button pops out. Figure 9–16 shows a typical example. We allow the user to select a font size from among the choices—Small, Medium, Large, and Extra large—but, of course, we will allow the user to select only one size at a time.

A radio button group

Figure 9–16. A radio button group

Implementing radio button groups is easy in Swing. You construct one object of type ButtonGroup for every group of buttons. Then, you add objects of type JRadioButton to the button group. The button group object is responsible for turning off the previously set button when a new button is clicked.

ButtonGroup group = new ButtonGroup();

JRadioButton smallButton = new JRadioButton("Small", false);
group.add(smallButton);

JRadioButton mediumButton = new JRadioButton("Medium", true);
group.add(mediumButton);
. . .

The second argument of the constructor is true for the button that should be checked initially and false for all others. Note that the button group controls only the behavior of the buttons; if you want to group the buttons for layout purposes, you also need to add them to a container such as a JPanel.

If you look again at Figures 9–15 and 9–16, you will note that the appearance of the radio buttons is different from that of checkboxes. Checkboxes are square and contain a check mark when selected. Radio buttons are round and contain a dot when selected.

The event notification mechanism for radio buttons is the same as for any other buttons. When the user checks a radio button, the radio button generates an action event. In our example program, we define an action listener that sets the font size to a particular value:

ActionListener listener = new
   ActionListener()
   {
      public void actionPerformed(ActionEvent event)
      {
         // size refers to the final parameter of the addRadioButton method
         label.setFont(new Font("Serif", Font.PLAIN, size));
      }
   };

Compare this listener setup with that of the checkbox example. Each radio button gets a different listener object. Each listener object knows exactly what it needs to do—set the font size to a particular value. In the case of the checkboxes, we used a different approach. Both checkboxes have the same action listener. It called a method that looked at the current state of both checkboxes.

Could we follow the same approach here? We could have a single listener that computes the size as follows:

if (smallButton.isSelected()) size = 8;
else if (mediumButton.isSelected()) size = 12;
. . .

However, we prefer to use separate action listener objects because they tie the size values more closely to the buttons.

Note

Note

If you have a group of radio buttons, you know that only one of them is selected. It would be nice to be able to quickly find out which one without having to query all the buttons in the group. Because the ButtonGroup object controls all buttons, it would be convenient if this object could give us a reference to the selected button. Indeed, the ButtonGroup class has a getSelection method, but that method doesn’t return the radio button that is selected. Instead, it returns a ButtonModel reference to the model attached to the button. Unfortunately, none of the ButtonModel methods are very helpful. The ButtonModel interface inherits a method getSelectedObjects from the ItemSelectable interface that, rather uselessly, returns null. The getActionCommand method looks promising because the “action command” of a radio button is its text label. But the action command of its model is null. Only if you explicitly set the action commands of all radio buttons with the setActionCommand method do the models’ action command values also get set. Then you can retrieve the action command of the currently selected button with buttonGroup.getSelection().getActionCommand().

Example 9–6 is the complete program for font size selection that puts a set of radio buttons to work.

Example 9–6. RadioButtonTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class RadioButtonTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       RadioButtonFrame frame = new RadioButtonFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a sample text label and radio buttons for
17.    selecting font sizes.
18. */
19. class RadioButtonFrame extends JFrame
20. {
21.    public RadioButtonFrame()
22.    {
23.       setTitle("RadioButtonTest");
24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25.
26.       // add the sample text label
27.
28.       label = new JLabel("The quick brown fox jumps over the lazy dog.");
29.       label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
30.       add(label, BorderLayout.CENTER);
31.
32.       // add the radio buttons
33.
34.       buttonPanel = new JPanel();
35.       group = new ButtonGroup();
36.
37.       addRadioButton("Small", 8);
38.       addRadioButton("Medium", 12);
39.       addRadioButton("Large", 18);
40.       addRadioButton("Extra large", 36);
41.
42.       add(buttonPanel, BorderLayout.SOUTH);
43.    }
44.
45.    /**
46.       Adds a radio button that sets the font size of the
47.       sample text.
48.       @param name the string to appear on the button
49.       @param size the font size that this button sets
50.    */
51.    public void addRadioButton(String name, final int size)
52.    {
53.       boolean selected = size == DEFAULT_SIZE;
54.       JRadioButton button = new JRadioButton(name, selected);
55.       group.add(button);
56.       buttonPanel.add(button);
57.
58.       // this listener sets the label font size
59.
60.       ActionListener listener = new
61.          ActionListener()
62.          {
63.             public void actionPerformed(ActionEvent event)
64.             {
65.                // size refers to the final parameter of the addRadioButton method
66.                label.setFont(new Font("Serif", Font.PLAIN, size));
67.             }
68.          };
69.
70.       button.addActionListener(listener);
71.    }
72.
73.    public static final int DEFAULT_WIDTH = 400;
74.    public static final int DEFAULT_HEIGHT = 200;
75.
76.    private JPanel buttonPanel;
77.    private ButtonGroup group;
78.    private JLabel label;
79.
80.    private static final int DEFAULT_SIZE = 12;
81. }
RadioButtonTest.java
javax.swing.JRadioButton 1.2
  • JRadioButton(String label, boolean state)

    constructs a radio button with the given label and initial state.

  • JRadioButton(String label, Icon icon)

    constructs a radio button with the given label and icon that is initially unselected.

RadioButtonTest.java
javax.swing.ButtonGroup 1.2
  • void add(AbstractButton b)

    adds the button to the group.

  • ButtonModel getSelection()

    returns the button model of the selected button.

RadioButtonTest.java
javax.swing.ButtonModel 1.2
  • String getActionCommand()

    returns the action command for this button model.

RadioButtonTest.java
javax.swing.AbstractButton 1.2
  • void setActionCommand(String s)

    sets the action command for this button and its model.

Borders

If you have multiple groups of radio buttons in a window, you will want to visually indicate which buttons are grouped. Swing provides a set of useful borders for this purpose. You can apply a border to any component that extends JComponent. The most common usage is to place a border around a panel and fill that panel with other user interface elements such as radio buttons.

You can choose from quite a few borders, but you follow the same steps for all of them.

  1. Call a static method of the BorderFactory to create a border. You can choose among the following styles (see Figure 9–17):

    • Lowered bevel

    • Raised bevel

    • Etched

    • Line

    • Matte

    • Empty (just to create some blank space around the component)

    Testing border types

    Figure 9–17. Testing border types

  2. If you like, add a title to your border by passing your border to BorderFactory.createTitledBorder.

  3. If you really want to go all out, combine several borders with a call to BorderFactory.createCompoundBorder.

  4. Add the resulting border to your component by calling the setBorder method of the JComponent class.

For example, here is how you add an etched border with a title to a panel:

Border etched = BorderFactory.createEtchedBorder()
Border titled = BorderFactory.createTitledBorder(etched, "A Title");
panel.setBorder(titled);

Run the program in Example 9–7 to get an idea what the various borders look like.

The various borders have different options for setting border widths and colors. See the API notes for details. True border enthusiasts will appreciate that there is also a SoftBevelBorder class for beveled borders with softened corners and that a LineBorder can have rounded corners as well. You can construct these borders only by using one of the class constructors—there is no BorderFactory method for them.

Example 9–7. BorderTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4. import javax.swing.border.*;
 5.
 6. public class BorderTest
 7. {
 8.    public static void main(String[] args)
 9.    {
10.       BorderFrame frame = new BorderFrame();
11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
12.       frame.setVisible(true);
13.    }
14. }
15.
16. /**
17.    A frame with radio buttons to pick a border style.
18. */
19. class BorderFrame extends JFrame
20. {
21.    public BorderFrame()
22.    {
23.       setTitle("BorderTest");
24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25.
26.       demoPanel = new JPanel();
27.       buttonPanel = new JPanel();
28.       group = new ButtonGroup();
29.
30.       addRadioButton("Lowered bevel", BorderFactory.createLoweredBevelBorder());
31.       addRadioButton("Raised bevel", BorderFactory.createRaisedBevelBorder());
32.       addRadioButton("Etched", BorderFactory.createEtchedBorder());
33.       addRadioButton("Line", BorderFactory.createLineBorder(Color.BLUE));
34.       addRadioButton("Matte", BorderFactory.createMatteBorder(10, 10, 10, 10, Color
BorderTest.java.BLUE));
35.       addRadioButton("Empty", BorderFactory.createEmptyBorder());
36.
37.       Border etched = BorderFactory.createEtchedBorder();
38.       Border titled = BorderFactory.createTitledBorder(etched, "Border types");
39.       buttonPanel.setBorder(titled);
40.
41.       setLayout(new GridLayout(2, 1));
42.       add(buttonPanel);
43.       add(demoPanel);
44.    }
45.
46.    public void addRadioButton(String buttonName, final Border b)
47.    {
48.       JRadioButton button = new JRadioButton(buttonName);
49.       button.addActionListener(new
50.          ActionListener()
51.          {
52.             public void actionPerformed(ActionEvent event)
53.             {
54.                demoPanel.setBorder(b);
55.                validate();
56.             }
57.          });
58.       group.add(button);
59.       buttonPanel.add(button);
60.    }
61.
62.    public static final int DEFAULT_WIDTH = 600;
63.    public static final int DEFAULT_HEIGHT = 200;
64.
65.    private JPanel demoPanel;
66.    private JPanel buttonPanel;
67.    private ButtonGroup group;
68. }
BorderTest.java
javax.swing.BorderFactory 1.2
  • static Border createLineBorder(Color color)

  • static Border createLineBorder(Color color, int thickness)

    create a simple line border.

  • static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color)

  • static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon tileIcon)

    create a thick border that is filled with a color or a repeating icon.

  • static Border createEmptyBorder()

  • static Border createEmptyBorder(int top, int left, int bottom, int right)

    create an empty border.

  • static Border createEtchedBorder()

  • static Border createEtchedBorder(Color highlight, Color shadow)

  • static Border createEtchedBorder(int type)

  • static Border createEtchedBorder(int type, Color highlight, Color shadow)

    create a line border with a 3D effect.

    Parameters:

    highlight, shadow

    Colors for 3D effect

     

    type

    One of EtchedBorder.RAISED, EtchedBorder.LOWERED

  • static Border createBevelBorder(int type)

  • static Border createBevelBorder(int type, Color highlight, Color shadow)

  • static Border createLoweredBevelBorder()

  • static Border createRaisedBevelBorder()

    create a border that gives the effect of a lowered or raised surface.

    Parameters:

    type

    One of BevelBorder.LOWERED, BevelBorder.RAISED

     

    highlight, shadow

    Colors for 3D effect

  • static TitledBorder createTitledBorder(String title)

  • static TitledBorder createTitledBorder(Border border)

  • static TitledBorder createTitledBorder(Border border, String title)

  • static TitledBorder createTitledBorder(Border border, String title, int justification, int position)

  • static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font)

  • static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font, Color color)

    Creates a titled border with the specified properties.

    Parameters:

    title

    The title string

     

    border

    The border to decorate with the title

     

    justification

    One of the TitledBorder constants LEFT, CENTER, RIGHT, LEADING, TRAILING, or DEFAULT_JUSTIFICATION (left)

     

    position

    One of the TitledBorder constants ABOVE_TOP, TOP, BELOW_TOP, ABOVE_BOTTOM, BOTTOM, BELOW_BOTTOM, or DEFAULT_POSITION (top)

     

    font

    The font for the title

     

    color

    The color of the title

  • static CompoundBorder createCompoundBorder(Border outsideBorder, Border insideBorder)

    combines two borders to a new border.

BorderTest.java
javax.swing.border.SoftBevelBorder 1.2
  • SoftBevelBorder(int type)

  • SoftBevelBorder(int type, Color highlight, Color shadow)

    create a bevel border with softened corners.

    Parameters:

    type

    One of BevelBorder.LOWERED, BevelBorder.RAISED

     

    highlight, shadow

    Colors for 3D effect

BorderTest.java
javax.swing.border.LineBorder 1.2
  • public LineBorder(Color color, int thickness, boolean roundedCorners)

    creates a line border with the given color and thickness. If roundedCorners is true, the border has rounded corners.

BorderTest.java
javax.swing.JComponent 1.2
  • void setBorder(Border border)

    sets the border of this component.

Combo Boxes

If you have more than a handful of alternatives, radio buttons are not a good choice because they take up too much screen space. Instead, you can use a combo box. When the user clicks on the component, a list of choices drops down, and the user can then select one of them (see Figure 9–18).

A combo box

Figure 9–18. A combo box

If the drop-down list box is set to be editable, then you can edit the current selection as if it were a text field. For that reason, this component is called a combo box—it combines the flexibility of a text field with a set of predefined choices. The JComboBox class provides a combo box component.

You call the setEditable method to make the combo box editable. Note that editing affects only the current item. It does not change the content of the list.

You can obtain the current selection or edited text by calling the getSelectedItem method.

In the example program, the user can choose a font style from a list of styles (Serif, SansSerif, Monospaced, etc.). The user can also type in another font.

You add the choice items with the addItem method. In our program, addItem is called only in the constructor, but you can call it any time.

faceCombo = new JComboBox();
faceCombo.setEditable(true);
faceCombo.addItem("Serif");
faceCombo.addItem("SansSerif");
. . .

This method adds the string at the end of the list. You can add new items anywhere in the list with the insertItemAt method:

faceCombo.insertItemAt("Monospaced", 0); // add at the beginning

You can add items of any type—the combo box invokes each item’s toString method to display it.

If you need to remove items at run time, you use the removeItem or removeItemAt method, depending on whether you supply the item to be removed or its position.

faceCombo.removeItem("Monospaced");
faceCombo.removeItemAt(0); // remove first item

The removeAllItems method removes all items at once.

Tip

Tip

If you need to add a large number of items to a combo box, the addItem method will perform poorly. Instead, construct a DefaultComboBoxModel, populate it by calling addElement, and then call the setModel method of the JComboBox class.

When the user selects an item from a combo box, the combo box generates an action event. To find out which item was selected, call getSource on the event parameter to get a reference to the combo box that sent the event. Then call the getSelectedItem method to retrieve the currently selected item. You need to cast the returned value to the appropriate type, usually String.

public void actionPerformed(ActionEvent event)
{
   label.setFont(new Font(
      (String) faceCombo.getSelectedItem(),
      Font.PLAIN,
      DEFAULT_SIZE));
}

Example 9–8 shows the complete program.

Note

Note

If you want to show a permanently displayed list instead of a dropdown list, use the JList component. We cover JList in Chapter 6 of Volume 2.

Example 9–8. ComboBoxTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class ComboBoxTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       ComboBoxFrame frame = new ComboBoxFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a sample text label and a combo box for
17.    selecting font faces.
18. */
19. class ComboBoxFrame extends JFrame
20. {
21.    public ComboBoxFrame()
22.    {
23.       setTitle("ComboBoxTest");
24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25.
26.       // add the sample text label
27.
28.       label = new JLabel("The quick brown fox jumps over the lazy dog.");
29.       label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE));
30.       add(label, BorderLayout.CENTER);
31.
32.       // make a combo box and add face names
33.
34.       faceCombo = new JComboBox();
35.       faceCombo.setEditable(true);
36.       faceCombo.addItem("Serif");
37.       faceCombo.addItem("SansSerif");
38.       faceCombo.addItem("Monospaced");
39.       faceCombo.addItem("Dialog");
40.       faceCombo.addItem("DialogInput");
41.
42.       // the combo box listener changes the label font to the selected face name
43.
44.       faceCombo.addActionListener(new
45.          ActionListener()
46.          {
47.             public void actionPerformed(ActionEvent event)
48.             {
49.                label.setFont(new Font(
50.                   (String) faceCombo.getSelectedItem(),
51.                   Font.PLAIN,
52.                   DEFAULT_SIZE));
53.             }
54.          });
55.
56.       // add combo box to a panel at the frame's southern border
57.
58.       JPanel comboPanel = new JPanel();
59.       comboPanel.add(faceCombo);
60.       add(comboPanel, BorderLayout.SOUTH);
61.    }
62.
63.    public static final int DEFAULT_WIDTH = 300;
64.    public static final int DEFAULT_HEIGHT = 200;
65.
66.    private JComboBox faceCombo;
67.    private JLabel label;
68.    private static final int DEFAULT_SIZE = 12;
69. }
ComboBoxTest.java
javax.swing.JComboBox 1.2
  • void setEditable(boolean b)

    Parameters:

    b

    true if the combo box field can be edited by the user, false otherwise

  • void addItem(Object item)

    adds an item to the item list.

  • void insertItemAt(Object item, int index)

    inserts an item into the item list at a given index.

  • void removeItem(Object item)

    removes an item from the item list.

  • void removeItemAt(int index)

    removes the item at an index.

  • void removeAllItems()

    removes all items from the item list.

  • Object getSelectedItem()

    returns the currently selected item.

Sliders

Combo boxes let users choose from a discrete set of values. Sliders offer a choice from a continuum of values, for example, any number between 1 and 100.

The most common way of constructing a slider is as follows:

JSlider slider = new JSlider(min, max, initialValue);

If you omit the minimum, maximum, and initial values, they are initialized with 0, 100, and 50, respectively.

Or if you want the slider to be vertical, then use the following constructor call:

JSlider slider = new JSlider(SwingConstants.VERTICAL, min, max, initialValue);

These constructors create a plain slider, such as the top slider in Figure 9–19. You will see presently how to add decorations to a slider.

Sliders

Figure 9–19. Sliders

As the user slides the slider bar, the value of the slider moves between the minimum and the maximum values. When the value changes, a ChangeEvent is sent to all change listeners. To be notified of the change, you call the addChangeListener method and install an object that implements the ChangeListener interface. That interface has a single method, stateChanged. In that method, you should retrieve the slider value:

public void stateChanged(ChangeEvent event)
{
   JSlider slider = (JSlider) event.getSource();
   int value = slider.getValue();
   . . .
}

You can embellish the slider by showing ticks. For example, in the sample program, the second slider uses the following settings:

slider.setMajorTickSpacing(20);
slider.setMinorTickSpacing(5);

The slider is decorated with large tick marks every 20 units and small tick marks every 5 units. The units refer to slider values, not pixels.

These instructions only set the units for the tick marks. To actually have the tick marks appear, you also call

slider.setPaintTicks(true);

The major and minor tick marks are independent. For example, you can set major tick marks every 20 units and minor tick marks every 7 units, but you’ll get a very messy scale.

You can force the slider to snap to ticks. Whenever the user has finished dragging a slider in snap mode, it is immediately moved to the closest tick. You activate this mode with the call

slider.setSnapToTicks(true);

Note

Note

The “snap to ticks” behavior doesn’t work as well as you might imagine. Until the slider has actually snapped, the change listener still reports slider values that don’t correspond to ticks. And if you click next to the slider—an action that normally advances the slider a bit in the direction of the click—a slider with “snap to ticks” does not move to the next tick.

You can ask for tick mark labels for the major tick marks by calling

slider.setPaintLabels(true);

For example, with a slider ranging from 0 to 100 and major tick spacing of 20, the ticks are labeled 0, 20, 40, 60, 80, and 100.

You can also supply other tick marks, such as strings or icons (see Figure 9–19). The process is a bit convoluted. You need to fill a hash table with keys of type Integer and values of type Component. (Autoboxing makes this simple in JDK 5.0 and beyond.) You then call the setLabelTable method. The components are placed under the tick marks. Usually, you use JLabel objects. Here is how you can label ticks as A, B, C, D, E, and F.

Hashtable<Integer, Component> labelTable = new Hashtable<Integer, Component>();
labelTable.put(0, new JLabel("A"));
labelTable.put(20, new JLabel("B"));
. . .

labelTable.put(100, new JLabel("F"));
slider.setLabelTable(labelTable);

See Chapter 2 of Volume 2 for more information about hash tables.

Example 9–9 also shows a slider with icons as tick labels.

Tip

Tip

If your tick marks or labels don’t show, double-check that you called setPaintTicks(true) and setPaintLabels(true).

To suppress the “track” in which the slider moves, call

slider.setPaintTrack(false);

The fourth slider in Figure 9–19 has no track.

The fifth slider has its direction reversed by a call to

slider.setInverted(true);

The example program shows all these visual effects with a collection of sliders. Each slider has a change event listener installed that places the current slider value into the text field at the bottom of the frame.

Example 9–9. SliderTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.util.*;
  4. import javax.swing.*;
  5. import javax.swing.event.*;
  6.
  7. public class SliderTest
  8. {
  9.    public static void main(String[] args)
 10.    {
 11.       SliderTestFrame frame = new SliderTestFrame();
 12.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13.       frame.setVisible(true);
 14.    }
 15. }
 16.
 17. /**
 18.    A frame with many sliders and a text field to show slider
 19.    values.
 20. */
 21. class SliderTestFrame extends JFrame
 22. {
 23.    public SliderTestFrame()
 24.    {
 25.       setTitle("SliderTest");
 26.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 27.
 28.       sliderPanel = new JPanel();
 29.       sliderPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
 30.
 31.       // common listener for all sliders
 32.       listener = new
 33.          ChangeListener()
 34.          {
 35.             public void stateChanged(ChangeEvent event)
 36.             {
 37.                // update text field when the slider value changes
 38.                JSlider source = (JSlider) event.getSource();
 39.                textField.setText("" + source.getValue());
 40.             }
 41.          };
 42.
 43.       // add a plain slider
 44.
 45.       JSlider slider = new JSlider();
 46.       addSlider(slider, "Plain");
 47.
 48.       // add a slider with major and minor ticks
 49.
 50.       slider = new JSlider();
 51.       slider.setPaintTicks(true);
 52.       slider.setMajorTickSpacing(20);
 53.       slider.setMinorTickSpacing(5);
 54.       addSlider(slider, "Ticks");
 55.
 56.       // add a slider that snaps to ticks
 57.
 58.       slider = new JSlider();
 59.       slider.setPaintTicks(true);
 60.       slider.setSnapToTicks(true);
 61.       slider.setMajorTickSpacing(20);
 62.       slider.setMinorTickSpacing(5);
 63.       addSlider(slider, "Snap to ticks");
 64.
 65.       // add a slider with no track
 66.
 67.       slider = new JSlider();
 68.       slider.setPaintTicks(true);
 69.       slider.setMajorTickSpacing(20);
 70.       slider.setMinorTickSpacing(5);
 71.       slider.setPaintTrack(false);
 72.       addSlider(slider, "No track");
 73.
 74.       // add an inverted slider
 75.
 76.       slider = new JSlider();
 77.       slider.setPaintTicks(true);
 78.       slider.setMajorTickSpacing(20);
 79.       slider.setMinorTickSpacing(5);
 80.       slider.setInverted(true);
 81.       addSlider(slider, "Inverted");
 82.
 83.       // add a slider with numeric labels
 84.
 85.       slider = new JSlider();
 86.       slider.setPaintTicks(true);
 87.       slider.setPaintLabels(true);
 88.       slider.setMajorTickSpacing(20);
 89.       slider.setMinorTickSpacing(5);
 90.       addSlider(slider, "Labels");
 91.
 92.       // add a slider with alphabetic labels
 93.
 94.       slider = new JSlider();
 95.       slider.setPaintLabels(true);
 96.       slider.setPaintTicks(true);
 97.       slider.setMajorTickSpacing(20);
 98.       slider.setMinorTickSpacing(5);
 99.
100.       Dictionary<Integer, Component> labelTable = new Hashtable<Integer, Component>();
101.       labelTable.put(0, new JLabel("A"));
102.       labelTable.put(20, new JLabel("B"));
103.       labelTable.put(40, new JLabel("C"));
104.       labelTable.put(60, new JLabel("D"));
105.       labelTable.put(80, new JLabel("E"));
106.       labelTable.put(100, new JLabel("F"));
107.
108.       slider.setLabelTable(labelTable);
109.       addSlider(slider, "Custom labels");
110.
111.       // add a slider with icon labels
112.
113.       slider = new JSlider();
114.       slider.setPaintTicks(true);
115.       slider.setPaintLabels(true);
116.       slider.setSnapToTicks(true);
117.       slider.setMajorTickSpacing(20);
118.       slider.setMinorTickSpacing(20);
119.
120.       labelTable = new Hashtable<Integer, Component>();
121.
122.       // add card images
123.
124.       labelTable.put(0, new JLabel(new ImageIcon("nine.gif")));
125.       labelTable.put(20, new JLabel(new ImageIcon("ten.gif")));
126.       labelTable.put(40, new JLabel(new ImageIcon("jack.gif")));
127.       labelTable.put(60, new JLabel(new ImageIcon("queen.gif")));
128.       labelTable.put(80, new JLabel(new ImageIcon("king.gif")));
129.       labelTable.put(100, new JLabel(new ImageIcon("ace.gif")));
130.
131.       slider.setLabelTable(labelTable);
132.       addSlider(slider, "Icon labels");
133.
134.       // add the text field that displays the slider value
135.
136.       textField = new JTextField();
137.       add(sliderPanel, BorderLayout.CENTER);
138.       add(textField, BorderLayout.SOUTH);
139.    }
140.
141.    /**
142.       Adds a slider to the slider panel and hooks up the listener
143.       @param s the slider
144.       @param description the slider description
145.    */
146.    public void addSlider(JSlider s, String description)
147.    {
148.       s.addChangeListener(listener);
149.       JPanel panel = new JPanel();
150.       panel.add(s);
151.       panel.add(new JLabel(description));
152.       sliderPanel.add(panel);
153.    }
154.
155.    public static final int DEFAULT_WIDTH = 350;
156.    public static final int DEFAULT_HEIGHT = 450;
157.
158.    private JPanel sliderPanel;
159.    private JTextField textField;
160.    private ChangeListener listener;
161. }
SliderTest.java
javax.swing.JSlider 1.2
  • JSlider()

  • JSlider(int direction)

  • JSlider(int min, int max)

  • JSlider(int min, int max, int initialValue)

  • JSlider(int direction, int min, int max, int initialValue)

    construct a horizontal slider with the given direction and minimum, maximum, and initial values.

    Parameters:

    direction

    One of SwingConstants.HORIZONTAL or SwingConstants.VERTICAL. The default is horizontal.

     

    min, max

    The minimum and maximum for the slider values. Defaults are 0 and 100.

     

    initialValue

    The initial value for the slider. The default is 50.

  • void setPaintTicks(boolean b)

    displays ticks if b is true.

  • void setMajorTickSpacing(int units)

  • void setMinorTickSpacing(int units)

    set major or minor ticks at multiples of the given slider units.

  • void setPaintLabels(boolean b)

    displays tick labels if b is true.

  • void setLabelTable(Dictionary table)

    sets the components to use for the tick labels. Each key/value pair in the table has the form new Integer(value)/component.

  • void setSnapToTicks(boolean b)

    if b is true, then the slider snaps to the closest tick after each adjustment.

  • void setPaintTrack(boolean b)

    if b is true, then a track is displayed in which the slider runs.

The JSpinner Component

A JSpinner is a text field with two small buttons on the side. When the buttons are clicked, the text field value is incremented or decremented (see Figure 9–20).

Several variations of the JSpinner component

Figure 9–20. Several variations of the JSpinner component

The values in the spinner can be numbers, dates, values from a list, or, in the most general case, any sequence of values for which predecessors and successors can be determined. The JSpinner class defines standard data models for the first three cases. You can define your own data model to describe arbitrary sequences.

By default, a spinner manages an integer, and the buttons increment or decrement it by 1. You can get the current value by calling the getValue method. That method returns an Object. Cast it to an Integer and retrieve the wrapped value.

JSpinner defaultSpinner = new JSpinner();
. . .
int value = (Integer) defaultSpinner.getValue();

You can change the increment to a value other than 1, and you can also supply lower and upper bounds. Here is a spinner with starting value 5, bounded between 0 and 10, and an increment of 0.5:

JSpinner boundedSpinner = new JSpinner(new SpinnerNumberModel(5, 0, 10, 0.5));

There are two SpinnerNumberModel constructors, one with only int parameters and one with double parameters. If any of the parameters is a floating-point number, then the second constructor is used. It sets the spinner value to a Double object.

Spinners aren’t restricted to numeric values. You can have a spinner iterate through any collection of values. Simply pass a SpinnerListModel to the JSpinner constructor. You can construct a SpinnerListModel from an array or a class implementing the List interface (such as an ArrayList). In our sample program, we display a spinner control with all available font names.

String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment()
Several variations of the JSpinner component.getAvailableFontFamilyNames();
JSpinner listSpinner = new JSpinner(new SpinnerListModel(fonts));

However, we found that the direction of the iteration was mildly confusing because it is opposite from the user experience with a combo box. In a combo box, higher values are below lower values, so you would expect the downward arrow to navigate toward higher values. But the spinner increments the array index so that the upward arrow yields higher values. There is no provision for reversing the traversal order in the SpinnerListModel, but an impromptu anonymous subclass yields the desired result:

JSpinner reverseListSpinner = new JSpinner(
   new SpinnerListModel(fonts)
   {
      public Object getNextValue()
      {
          return super.getPreviousValue();

      }
      public Object getPreviousValue()
      {
          return super.getNextValue();
      }
   });

Try out both versions and see which you find more intuitive.

Another good use for a spinner is for a date that the user can increment or decrement. You get such a spinner, initialized with today’s date, with the call

JSpinner dateSpinner = new JSpinner(new SpinnerDateModel());

However, if you look carefully at Figure 9–20, you will see that the spinner text shows both date and time, such as

3/12/02 7:23 PM

The time doesn’t make any sense for a date picker. It turns out to be somewhat difficult to make the spinner show just the date. Here is the magic incantation:

JSpinner betterDateSpinner = new JSpinner(new SpinnerDateModel());
String pattern = ((SimpleDateFormat) DateFormat.getDateInstance()).toPattern();
betterDateSpinner.setEditor(new JSpinner.DateEditor(betterDateSpinner, pattern));

Using the same approach, you can also make a time picker. Then use the SpinnerDateModel constructor that lets you specify a Date, the lower and upper bounds (or null if there are no bounds), and the Calendar field (such as Calendar.HOUR) to be modified.

JSpinner timeSpinner = new JSpinner(
   new SpinnerDateModel(
      new GregorianCalendar(2000, Calendar.JANUARY, 1, 12, 0, 0).getTime(),
         null, null, Calendar.HOUR));

However, if you want to update the minutes in 15-minute increments, then you exceed the capabilities of the standard SpinnerDateModel class.

You can display arbitrary sequences in a spinner by defining your own spinner model. In our sample program, we have a spinner that iterates through all permutations of the string “meat”. You can get to “mate”, “meta”, “team”, and another 20 permutations by clicking the spinner buttons.

When you define your own model, you should extend the AbstractSpinnerModel class and define the following four methods:

Object getValue()
void setValue(Object value)
Object getNextValue()
Object getPreviousValue()

The getValue method returns the value stored by the model. The setValue method sets a new value. It should throw an IllegalArgumentException if the new value is not appropriate.

Caution

Caution

The setValue method must call the fireStateChanged method after setting the new value. Otherwise, the spinner field won’t be updated.

The getNextValue and getPreviousValue methods return the values that should come after or before the current value, or null if the end of the traversal has been reached.

Caution

Caution

The getNextValue and getPreviousValue methods should not change the current value. When a user clicks on the upward arrow of the spinner, the getNextValue method is called. If the return value is not null, it is set by a call to setValue.

In the sample program, we use a standard algorithm to determine the next and previous permutations. The details of the algorithm are not important.

Example 9–10 shows how to generate the various spinner types. Click on the Ok button to see the spinner values.

Example 9–10. SpinnerTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.text.*;
  4. import java.util.*;
  5. import javax.swing.*;
  6.
  7. /**
  8.    A program to test spinners.
  9. */
 10. public class SpinnerTest
 11. {
 12.    public static void main(String[] args)
 13.    {
 14.       SpinnerFrame frame = new SpinnerFrame();
 15.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 16.       frame.setVisible(true);
 17.    }
 18. }
 19.
 20. /**
 21.    A frame with a panel that contains several spinners and
 22.    a button that displays the spinner values.
 23. */
 24. class SpinnerFrame extends JFrame
 25. {
 26.    public SpinnerFrame()
 27.    {
 28.       setTitle("SpinnerTest");
 29.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 30.       JPanel buttonPanel = new JPanel();
 31.       okButton = new JButton("Ok");
 32.       buttonPanel.add(okButton);
 33.       add(buttonPanel, BorderLayout.SOUTH);
 34.
 35.       mainPanel = new JPanel();
 36.       mainPanel.setLayout(new GridLayout(0, 3));
 37.       add(mainPanel, BorderLayout.CENTER);
 38.
 39.       JSpinner defaultSpinner = new JSpinner();
 40.       addRow("Default", defaultSpinner);
 41.
 42.       JSpinner boundedSpinner = new JSpinner(new SpinnerNumberModel(5, 0, 10, 0.5));
 43.       addRow("Bounded", boundedSpinner);
 44.
 45.       String[] fonts = GraphicsEnvironment
 46.          .getLocalGraphicsEnvironment()
 47.          .getAvailableFontFamilyNames();
 48.
 49.       JSpinner listSpinner = new JSpinner(new SpinnerListModel(fonts));
 50.       addRow("List", listSpinner);
 51.
 52.       JSpinner reverseListSpinner = new JSpinner(
 53.          new
 54.             SpinnerListModel(fonts)
 55.             {
 56.                public Object getNextValue()
 57.                {
 58.                   return super.getPreviousValue();
 59.                }
 60.                public Object getPreviousValue()
 61.                {
 62.                   return super.getNextValue();
 63.                }
 64.             });
 65.       addRow("Reverse List", reverseListSpinner);
 66.
 67.       JSpinner dateSpinner = new JSpinner(new SpinnerDateModel());
 68.       addRow("Date", dateSpinner);
 69.
 70.       JSpinner betterDateSpinner = new JSpinner(new SpinnerDateModel());
 71.       String pattern = ((SimpleDateFormat) DateFormat.getDateInstance()).toPattern();
 72.       betterDateSpinner.setEditor(new JSpinner.DateEditor(betterDateSpinner, pattern));
 73.       addRow("Better Date", betterDateSpinner);
 74.
 75.       JSpinner timeSpinner = new JSpinner(
 76.          new SpinnerDateModel(
 77.             new GregorianCalendar(2000, Calendar.JANUARY, 1, 12, 0, 0).getTime(),
 78.                null, null, Calendar.HOUR));
 79.       addRow("Time", timeSpinner);
 80.
 81.       JSpinner permSpinner = new JSpinner(new PermutationSpinnerModel("meat"));
 82.       addRow("Word permutations", permSpinner);
 83.    }
 84.
 85.    /**
 86.       Adds a row to the main panel.
 87.       @param labelText the label of the spinner
 88.       @param spinner the sample spinner
 89.    */
 90.    public void addRow(String labelText, final JSpinner spinner)
 91.    {
 92.       mainPanel.add(new JLabel(labelText));
 93.       mainPanel.add(spinner);
 94.       final JLabel valueLabel = new JLabel();
 95.       mainPanel.add(valueLabel);
 96.       okButton.addActionListener(new
 97.          ActionListener()
 98.          {
 99.             public void actionPerformed(ActionEvent event)
100.             {
101.                Object value = spinner.getValue();
102.                valueLabel.setText(value.toString());
103.             }
104.          });
105.    }
106.
107.    public static final int DEFAULT_WIDTH = 400;
108.    public static final int DEFAULT_HEIGHT = 250;
109.
110.    private JPanel mainPanel;
111.    private JButton okButton;
112. }
113.
114. /**
115.    A model that dynamically generates word permutations
116. */
117. class PermutationSpinnerModel extends AbstractSpinnerModel
118. {
119.    /**
120.       Constructs the model.
121.       @param w the word to permute
122.    */
123.    public PermutationSpinnerModel(String w)
124.    {
125.       word = w;
126.    }
127.
128.    public Object getValue()
129.    {
130.       return word;
131.    }
132.
133.    public void setValue(Object value)
134.    {
135.       if (!(value instanceof String))
136.          throw new IllegalArgumentException();
137.       word = (String) value;
138.       fireStateChanged();
139.    }
140.
141.    public Object getNextValue()
142.    {
143.       int[] codePoints = toCodePointArray(word);
144.       for (int i = codePoints.length - 1; i > 0; i--)
145.       {
146.          if (codePoints[i - 1] < codePoints[i])
147.          {
148.             int j = codePoints.length - 1;
149.             while (codePoints[i - 1] > codePoints[j]) j--;
150.             swap(codePoints, i - 1, j);
151.             reverse(codePoints, i, codePoints.length - 1);
152.             return new String(codePoints, 0, codePoints.length);
153.          }
154.       }
155.       reverse(codePoints, 0, codePoints.length - 1);
156.       return new String(codePoints, 0, codePoints.length);
157.    }
158.
159.    public Object getPreviousValue()
160.    {
161.       int[] codePoints = toCodePointArray(word);
162.       for (int i = codePoints.length - 1; i > 0; i--)
163.       {
164.          if (codePoints[i - 1] > codePoints[i])
165.          {
166.             int j = codePoints.length - 1;
167.             while (codePoints[i - 1] < codePoints[j]) j--;
168.             swap(codePoints, i - 1, j);
169.             reverse(codePoints, i, codePoints.length - 1);
170.             return new String(codePoints, 0, codePoints.length);
171.          }
172.       }
173.       reverse(codePoints, 0, codePoints.length - 1);
174.       return new String(codePoints, 0, codePoints.length);
175.    }
176.
177.    private static int[] toCodePointArray(String str)
178.    {
179.       int[] codePoints = new int[str.codePointCount(0, str.length())];
180.       for (int i = 0, j = 0; i < str.length(); i++, j++)
181.       {
182.          int cp = str.codePointAt(i);
183.          if (Character.isSupplementaryCodePoint(cp)) i++;
184.          codePoints[j] = cp;
185.       }
186.       return codePoints;
187.    }
188.
189.    private static void swap(int[] a, int i, int j)
190.    {
191.       int temp = a[i];
192.       a[i] = a[j];
193.       a[j] = temp;
194.    }
195.
196.    private static void reverse(int[] a, int i, int j)
197.    {
198.       while (i < j) { swap(a, i, j); i++; j--; }
199.    }
200.
201.    private String word;
202. }
SpinnerTest.java
javax.swing.JSpinner 1.4
  • JSpinner()

    constructs a spinner that edits an integer with starting value 0, increment 1, and no bounds.

  • JSpinner(SpinnerModel model)

    constructs a spinner that uses the given data model.

  • Object getValue()

    gets the current value of the spinner.

  • void setValue(Object value)

    attempts to set the value of the spinner. Throws an IllegalArgumentException if the model does not accept the value.

  • void setEditor(JComponent editor)

    sets the component that is used for editing the spinner value.

SpinnerTest.java
javax.swing.SpinnerNumberModel 1.4
  • SpinnerNumberModel(int initval, int minimum, int maximum, int stepSize)

  • SpinnerNumberModel(double initval, double minimum, double maximum, double stepSize)

    these constructors yield number models that manage an Integer or Double value. Use the MIN_VALUE and MAX_VALUE constants of the Integer and Double classes for unbounded values.

    Parameters:

    initval

    The initial value

     

    minimum

    The minimum valid value

     

    maximum

    The maximum valid value

     

    stepSize

    The increment or decrement of each spin

SpinnerTest.java
javax.swing.SpinnerListModel 1.4
  • SpinnerListModel(Object[] values)

  • SpinnerListModel(List values)

    these constructors yield models that select a value from among the given values.

SpinnerTest.java
javax.swing.SpinnerDateModel 1.4
  • SpinnerDateModel()

    constructs a date model with today’s date as the initial value, no lower or upper bounds, and an increment of Calendar.DAY_OF_MONTH.

  • SpinnerDateModel(Date initval, Comparable minimum, Comparable maximum, int step)

    Parameters:

    initval

    The initial value

     

    minimum

    The minimum valid value, or null if no lower bound is desired

     

    maximum

    The maximum valid value, or null if no lower bound is desired

     

    step

    The date field to increment or decrement of each spin. One of the constants ERA, YEAR, MONTH, WEEK_OF_YEAR, WEEK_OF_MONTH, DAY_OF_MONTH, DAY_OF_YEAR, DAY_OF_WEEK, DAY_OF_WEEK_IN_MONTH, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, or MILLISECOND of the Calendar class

SpinnerTest.java
java.text.SimpleDateFormat 1.1
  • String toPattern() 1.2

    gets the editing pattern for this date formatter. A typical pattern is "yyyy-MM-dd". See the JDK documentation for more details about the pattern.

SpinnerTest.java
javax.swing.JSpinner.DateEditor 1.4
  • DateEditor(JSpinner spinner, String pattern)

    constructs a date editor for a spinner.

    Parameters:

    spinner

    The spinner to which this editor belongs

     

    pattern

    The format pattern for the associated SimpleDateFormat

SpinnerTest.java
javax.swing.AbstractSpinnerModel 1.4
  • Object getValue()

    gets the current value of the model.

  • void setValue(Object value)

    attempts to set a new value for the model. Throws an IllegalArgumentException if the value is not acceptable. When overriding this method, you should call fireStateChanged after setting the new value.

  • Object getNextValue()

  • Object getPreviousValue()

    compute (but do not set) the next or previous value in the sequence that this model defines.

Menus

We started this chapter by introducing the most common components that you might want to place into a window, such as various kinds of buttons, text fields, and combo boxes. Swing also supports another type of user interface element, the pull-down menus that are familiar from GUI applications.

A menu bar on top of the window contains the names of the pull-down menus. Clicking on a name opens the menu containing menu items and submenus. When the user clicks on a menu item, all menus are closed and a message is sent to the program. Figure 9–21 shows a typical menu with a submenu.

A menu with a submenu

Figure 9–21. A menu with a submenu

Menu Building

Building menus is straightforward. You create a menu bar:

JMenuBar menuBar = new JMenuBar();

A menu bar is just a component that you can add anywhere you like. Normally, you want it to appear at the top of a frame. You can add it there with the setJMenuBar method:

frame.setJMenuBar(menuBar);

For each menu, you create a menu object:

JMenu editMenu = new JMenu("Edit");

You add the top-level menus to the menu bar:

menuBar.add(editMenu);

You add menu items, separators, and submenus to the menu object:

JMenuItem pasteItem = new JMenuItem("Paste");
editMenu.add(pasteItem);
editMenu.addSeparator();
JMenu optionsMenu = . . .; // a submenu
editMenu.add(optionsMenu);

You can see separators in Figure 9–21 below the “Paste” and “Read-only” menu items.

When the user selects a menu, an action event is triggered. You need to install an action listener for each menu item.

ActionListener listener = . . .;
pasteItem.addActionListener(listener);

The method JMenu.add(String s) conveniently adds a menu item to the end of a menu, for example:

editMenu.add("Paste");

The add method returns the created menu item, so you can capture it and then add the listener, as follows:

JMenuItem pasteItem = editMenu.add("Paste");
pasteItem.addActionListener(listener);

It often happens that menu items trigger commands that can also be activated through other user interface elements such as toolbar buttons. In Chapter 8, you saw how to specify commands through Action objects. You define a class that implements the Action interface, usually by extending the AbstractAction convenience class. You specify the menu item label in the constructor of the AbstractAction object, and you override the actionPerformed method to hold the menu action handler. For example,

Action exitAction = new
   AbstractAction("Exit") // menu item text goes here
   {
      public void actionPerformed(ActionEvent event)
      {
         // action code goes here
         System.exit(0);
      }
   };

You can then add the action to the menu:

JMenuItem exitItem = fileMenu.add(exitAction);

This command adds a menu item to the menu, using the action name. The action object becomes its listener. This is just a convenient shortcut for

JMenuItem exitItem = new JMenuItem(exitAction);
fileMenu.add(exitItem);

Note

Note

In Windows and Macintosh programs, menus are generally defined in an external resource file and tied to the application with resource identifiers. It is possible to build menus programmatically, but it is not commonly done. In Java, menus are still usually built inside the program because the mechanism for dealing with external resources is far more limited than it is in Windows or Mac OS.

Note
javax.swing.JMenu 1.2
  • JMenu(String label)

    constructs a menu.

    Parameters:

    label

    The label for the menu in the menu bar or parent menu

  • JMenuItem add(JMenuItem item)

    adds a menu item (or a menu).

    Parameters:

    item

    The item or menu to add

  • JMenuItem add(String label)

    adds a menu item to this menu.

    Parameters:

    label

    The label for the menu items

  • JMenuItem add(Action a)

    adds a menu item and associates an action with it.

    Parameters:

    a

    An action encapsulating a name, optional icon, and listener (see Chapter 8)

  • void addSeparator()

    adds a separator line to the menu.

  • JMenuItem insert(JMenuItem menu, int index)

    adds a new menu item (or submenu) to the menu at a specific index.

    Parameters:

    menu

    The menu to be added

     

    index

    Where to add the item

  • JMenuItem insert(Action a, int index)

    adds a new menu item at a specific index and associates an action with it.

    Parameters:

    a

    An action encapsulating a name, optional icon, and listener

     

    index

    Where to add the item

  • void insertSeparator(int index)

    adds a separator to the menu.

    Parameters:

    index

    Where to add the separator

  • void remove(int index)

    removes a specific item from the menu.

    Parameters:

    index

    The position of the item to remove

  • void remove(JMenuItem item)

    removes a specific item from the menu.

    Parameters:

    item

    The item to remove

Note
javax.swing.JMenuItem 1.2
  • JMenuItem(String label)

    constructs a menu item with a given label.

  • JMenuItem(Action a) 1.3

    constructs a menu item for the given action.

    Parameters:

    a

    An action encapsulating a name, optional icon, and listener

Note
javax.swing.AbstractButton 1.2
  • void setAction(Action a) 1.3

    sets the action for this button or menu item.

    Parameters:

    a

    An action encapsulating a name, optional icon, and listener

Note
javax.swing.JFrame 1.2
  • void setJMenuBar(JMenuBar menubar)

    sets the menu bar for this frame.

Icons in Menu Items

Menu items are very similar to buttons. In fact, the JMenuItem class extends the AbstractButton class. Just like buttons, menus can have just a text label, just an icon, or both. You can specify the icon with the JMenuItem(String, Icon) or JMenuItem(Icon) constructor, or you can set it with the setIcon method that the JMenuItem class inherits from the AbstractButton class. Here is an example:

JMenuItem cutItem = new JMenuItem("Cut", new ImageIcon("cut.gif"));

Figure 9–22 shows a menu with icons next to several menu items. As you can see, by default, the menu item text is placed to the right of the icon. If you prefer the text to be placed on the left, call the setHorizontalTextPosition method that the JMenuItem class inherits from the AbstractButton class. For example, the call

cutItem.setHorizontalTextPosition(SwingConstants.LEFT);
Icons in menu items

Figure 9–22. Icons in menu items

moves the menu item text to the left of the icon.

You can also add an icon to an action:

cutAction.putValue(Action.SMALL_ICON, new ImageIcon("cut.gif"));

Whenever you construct a menu item out of an action, the Action.NAME value becomes the text of the menu item and the Action.SMALL_ICON value becomes the icon.

Alternatively, you can set the icon in the AbstractAction constructor:

cutAction = new
   AbstractAction("Cut", new ImageIcon("cut.gif"))
   {
      public void actionPerformed(ActionEvent event)
      {
         // action code goes here
      }
   };
Icons in menu items
javax.swing.JMenuItem 1.2
  • JMenuItem(String label, Icon icon)

    constructs a menu item with the given label and icon.

Icons in menu items
javax.swing.AbstractButton 1.2
  • void setHorizontalTextPosition(int pos)

    sets the horizontal position of the text relative to the icon.

    Parameters:

    pos

    SwingConstants.RIGHT (text is to the right of icon) or SwingConstants.LEFT

Icons in menu items
javax.swing.AbstractAction 1.2
  • AbstractAction(String name, Icon smallIcon)

    constructs an abstract action with the given name and icon.

Checkbox and Radio Button Menu Items

Checkbox and radio button menu items display a checkbox or radio button next to the name (see Figure 9–23). When the user selects the menu item, the item automatically toggles between checked and unchecked.

A checked menu item and menu items with radio buttons

Figure 9–23. A checked menu item and menu items with radio buttons

Apart from the button decoration, you treat these menu items just as you would any others. For example, here is how you create a checkbox menu item.

JCheckBoxMenuItem readonlyItem = new JCheckBoxMenuItem("Read-only");
optionsMenu.add(readonlyItem);

The radio button menu items work just like regular radio buttons. You must add them to a button group. When one of the buttons in a group is selected, all others are automatically deselected.

ButtonGroup group = new ButtonGroup();
JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert");
insertItem.setSelected(true);
JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype");
group.add(insertItem);
group.add(overtypeItem);
optionsMenu.add(insertItem);
optionsMenu.add(overtypeItem);

With these menu items, you don’t necessarily want to be notified at the exact moment the user selects the item. Instead, you can simply use the isSelected method to test the current state of the menu item. (Of course, that means that you should keep a reference to the menu item stored in an instance field.) Use the setSelected method to set the state.

A checked menu item and menu items with radio buttons
javax.swing.JCheckBoxMenuItem 1.2
  • JCheckBoxMenuItem(String label)

    constructs the checkbox menu item with the given label.

  • JCheckBoxMenuItem(String label, boolean state)

    constructs the checkbox menu item with the given label and the given initial state (true is checked).

A checked menu item and menu items with radio buttons
javax.swing.JRadioButtonMenuItem 1.2
  • JRadioButtonMenuItem(String label)

    constructs the radio button menu item with the given label.

  • JRadioButtonMenuItem(String label, boolean state)

    constructs the radio button menu item with the given label and the given initial state (true is checked).

A checked menu item and menu items with radio buttons
javax.swing.AbstractButton 1.2
  • boolean isSelected()

    returns the check state of this item (true is checked).

  • void setSelected(boolean state)

    sets the check state of this item.

Pop-Up Menus

A pop-up menu is a menu that is not attached to a menu bar but that floats somewhere (see Figure 9–24).

A pop-up menu

Figure 9–24. A pop-up menu

You create a pop-up menu similarly to the way you create a regular menu, but a pop-up menu has no title.

JPopupMenu popup = new JPopupMenu();

You then add menu items in the usual way:

JMenuItem item = new JMenuItem("Cut");
item.addActionListener(listener);
popup.add(item);

Unlike the regular menu bar that is always shown at the top of the frame, you must explicitly display a pop-up menu by using the show method. You specify the parent component and the location of the pop-up, using the coordinate system of the parent. For example:

popup.show(panel, x, y);

Usually you write code to pop up a menu when the user clicks a particular mouse button, the so-called pop-up trigger. In Windows and Linux, the pop-up trigger is the nonprimary (usually, the right) mouse button. To pop up a menu when the user clicks on a component, using the pop-up trigger, simply call the method

component.setComponentPopupMenu(popup);

Very occasionally, you may place a component inside another component that has a pop-up menu. The child component can inherit the parent component’s pop-up menu by calling

child.setInheritsPopupMenu(true);

These methods were added in JDK 5.0 to insulate programmers from system dependencies with pop-up menus. Before JDK 5.0, you had to install a mouse listener and add the following code to both the mousePressed and the mouseReleased listener methods:

if (popup.isPopupTrigger(event))
   popup.show(event.getComponent(), event.getX(), event.getY());

Some systems trigger pop-ups when the mouse button goes down, others when the mouse button goes up.

A pop-up menu
javax.swing.JPopupMenu 1.2
  • void show(Component c, int x, int y)

    shows the pop-up menu.

    Parameters:

    c

    The component over which the pop-up menu is to appear

     

    x, y

    The coordinates (in the coordinate space of c) of the top-left corner of the pop-up menu

  • boolean isPopupTrigger(MouseEvent event) 1.3

    returns true if the mouse event is the pop-up menu trigger.

A pop-up menu
java.awt.event.MouseEvent 1.1
  • boolean isPopupTrigger()

    returns true if this mouse event is the pop-up menu trigger.

A pop-up menu
javax.swing.JComponent 1.2
  • void setComponentPopupMenu(JPopupMenu popup) 5.0

  • JPopup getComponentPopupMenu() 5.0

    set or get the pop-up menu for this component.

  • void setInheritsPopupMenu(boolean b) 5.0

  • boolean getInheritsPopupMenu() 5.0

    set or get the inheritsPopupMenu property. If the property is set and this component’s pop-up menu is null, it uses its parent’s pop-up menu.

Keyboard Mnemonics and Accelerators

It is a real convenience for the experienced user to select menu items by keyboard mnemonics. You can specify keyboard mnemonics for menu items by specifying a mnemonic letter in the menu item constructor:

JMenuItem cutItem = new JMenuItem("Cut", 'T'),

The keyboard mnemonic is displayed automatically in the menu, with the mnemonic letter underlined (see Figure 9–25). For example, in the item defined in the last example, the label will be displayed as “Cut” with an underlined letter ‘t’. When the menu is displayed, the user just needs to press the T key, and the menu item is selected. (If the mnemonic letter is not part of the menu string, then typing it still selects the item, but the mnemonic is not displayed in the menu. Naturally, such invisible mnemonics are of dubious utility.)

Keyboard mnemonics

Figure 9–25. Keyboard mnemonics

Sometimes, you don’t want to underline the first letter of the menu item that matches the mnemonic. For example, if you have a mnemonic “A” for the menu item “Save As,” then it makes more sense to underline the second “A” (Save As). As of JDK 1.4, you can specify which character you want to have underlined; call the setDisplayedMnemonicIndex method.

If you have an Action object, you can add the mnemonic as the value of the Action.MNEMONIC_KEY key, as follows:

cutAction.putValue(Action.MNEMONIC_KEY, new Integer('T'));

You can supply a mnemonic letter only in the constructor of a menu item, not in the constructor for a menu. Instead, to attach a mnemonic to a menu, you call the setMnemonic method:

JMenu helpMenu = new JMenu("Help");
helpMenu.setMnemonic('H'),

To select a top-level menu from the menu bar, you press the ALT key together with the mnemonic letter. For example, you press ALT+H to select the Help menu from the menu bar.

Keyboard mnemonics let you select a submenu or menu item from the currently open menu. In contrast, accelerators are keyboard shortcuts that let you select menu items without ever opening a menu. For example, many programs attach the accelerators CTRL+O and CTRL+S to the Open and Save items in the File menu. You use the setAccelerator method to attach an accelerator key to a menu item. The setAccelerator method takes an object of type Keystroke. For example, the following call attaches the accelerator CTRL+O to the openItem menu item.

openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));

When the user presses the accelerator key combination, this automatically selects the menu option and fires an action event, as if the user had selected the menu option manually.

You can attach accelerators only to menu items, not to menus. Accelerator keys don’t actually open the menu. Instead, they directly fire the action event that is associated with a menu.

Conceptually, adding an accelerator to a menu item is similar to the technique of adding an accelerator to a Swing component. (We discussed that technique in Chapter 8.) However, when the accelerator is added to a menu item, the key combination is automatically displayed in the menu (see Figure 9–26).

Accelerators

Figure 9–26. Accelerators

Note

Note

Under Windows, ALT+F4 closes a window. But this is not an accelerator that was programmed in Java. It is a shortcut defined by the operating system. This key combination will always trigger the WindowClosing event for the active window regardless of whether there is a Close item on the menu.

Note
javax.swing.JMenuItem 1.2
  • JMenuItem(String label, int mnemonic)

    constructs a menu item with a given label and mnemonic.

    Parameters:

    label

    The label for this menu item

     

    mnemonic

    The mnemonic character for the item; this character will be underlined in the label

  • void setAccelerator(KeyStroke k)

    sets the keystroke k as accelerator for this menu item. The accelerator key is displayed next to the label.

Note
javax.swing.AbstractButton 1.2
  • void setMnemonic(int mnemonic)

    sets the mnemonic character for the button. This character will be underlined in the label.

    Parameters:

    mnemonic

    The mnemonic character for the button

  • void setDisplayedMnemonicIndex(int index) 1.4

    sets the index of the character to be underlined in the button text. Use this method if you don’t want the first occurrence of the mnemonic character to be underlined.

    Parameters:

    index

    The index of the button text character to be underlined

Enabling and Disabling Menu Items

Occasionally, a particular menu item should be selected only in certain contexts. For example, when a document is opened for reading only, then the Save menu item is not meaningful. Of course, we could remove the item from the menu with the JMenu.remove method, but users would react with some surprise to menus whose content keeps changing. Instead, it is better to deactivate the menu items that lead to temporarily inappropriate commands. A deactivated menu item is shown in gray, and it cannot be selected (see Figure 9–27).

Disabled menu itemsdisabling menu items

Figure 9–27. Disabled menu items

To enable or disable a menu item, use the setEnabled method:

saveItem.setEnabled(false);

There are two strategies for enabling and disabling menu items. Each time circumstances change, you can call setEnabled on the relevant menu items or actions. For example, as soon as a document has been set to read-only mode, you can locate the Save and Save As menu items and disable them. Alternatively, you can disable items just before displaying the menu. To do this, you must register a listener for the “menu selected” event. The javax.swing.event package defines a MenuListener interface with three methods:

void menuSelected(MenuEvent event)
void menuDeselected(MenuEvent event)
void menuCanceled(MenuEvent event)

The menuSelected method is called before the menu is displayed. It can therefore be used to disable or enable menu items. The following code shows how to disable the Save and Save As actions whenever the Read Only checkbox menu item is selected:

public void menuSelected(MenuEvent event)
{
   saveAction.setEnabled(!readonlyItem.isSelected());
   saveAsAction.setEnabled(!readonlyItem.isSelected());
}

Caution

Caution

Disabling menu items just before displaying the menu is a clever idea, but it does not work for menu items that also have accelerator keys. Because the menu is never opened when the accelerator key is pressed, the action is never disabled, and it is still triggered by the accelerator key.

Caution
javax.swing.JMenuItem 1.2
  • void setEnabled(boolean b)

    enables or disables the menu item.

Caution
javax.swing.event.MenuListener 1.2
  • void menuSelected(MenuEvent e)

    is called when the menu has been selected, before it is opened.

  • void menuDeselected(MenuEvent e)

    is called when the menu has been deselected, after it has been closed.

  • void menuCanceled(MenuEvent e)

    is called when the menu has been canceled, for example, by a user clicking outside the menu.

Example 9–11 is a sample program that generates a set of menus. It shows all the features that you saw in this section: nested menus, disabled menu items, checkbox and radio button menu items, a pop-up menu, and keyboard mnemonics and accelerators.

Example 9–11. MenuTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4. import javax.swing.event.*;
  5.
  6. public class MenuTest
  7. {
  8.    public static void main(String[] args)
  9.    {
 10.       MenuFrame frame = new MenuFrame();
 11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 12.       frame.setVisible(true);
 13.    }
 14. }
 15.
 16. /**
 17.    A frame with a sample menu bar.
 18. */
 19. class MenuFrame extends JFrame
 20. {
 21.    public MenuFrame()
 22.    {
 23.       setTitle("MenuTest");
 24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 25.
 26.       JMenu fileMenu = new JMenu("File");
 27.       JMenuItem newItem = fileMenu.add(new TestAction("New"));
 28.
 29.       // demonstrate accelerators
 30.
 31.       JMenuItem openItem = fileMenu.add(new TestAction("Open"));
 32.       openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent
MenuTest.java.CTRL_MASK));
 33.
 34.       fileMenu.addSeparator();
 35.
 36.       saveAction = new TestAction("Save");
 37.       JMenuItem saveItem = fileMenu.add(saveAction);
 38.       saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent
MenuTest.java.CTRL_MASK));
 39.
 40.       saveAsAction = new TestAction("Save As");
 41.       JMenuItem saveAsItem = fileMenu.add(saveAsAction);
 42.       fileMenu.addSeparator();
 43.
 44.       fileMenu.add(new
 45.          AbstractAction("Exit")
 46.          {
 47.             public void actionPerformed(ActionEvent event)
 48.             {
 49.                System.exit(0);
 50.             }
 51.          });
 52.
 53.       // demonstrate checkbox and radio button menus
 54.
 55.       readonlyItem = new JCheckBoxMenuItem("Read-only");
 56.       readonlyItem.addActionListener(new
 57.          ActionListener()
 58.          {
 59.             public void actionPerformed(ActionEvent event)
 60.             {
 61.                boolean saveOk = !readonlyItem.isSelected();
 62.                saveAction.setEnabled(saveOk);
 63.                saveAsAction.setEnabled(saveOk);
 64.             }
 65.          });
 66.
 67.       ButtonGroup group = new ButtonGroup();
 68.
 69.       JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert");
 70.       insertItem.setSelected(true);
 71.       JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype");
 72.
 73.       group.add(insertItem);
 74.       group.add(overtypeItem);
 75.
 76.       // demonstrate icons
 77.
 78.       Action cutAction = new TestAction("Cut");
 79.       cutAction.putValue(Action.SMALL_ICON, new ImageIcon("cut.gif"));
 80.       Action copyAction = new TestAction("Copy");
 81.       copyAction.putValue(Action.SMALL_ICON, new ImageIcon("copy.gif"));
 82.       Action pasteAction = new TestAction("Paste");
 83.       pasteAction.putValue(Action.SMALL_ICON, new ImageIcon("paste.gif"));
 84.
 85.       JMenu editMenu = new JMenu("Edit");
 86.       editMenu.add(cutAction);
 87.       editMenu.add(copyAction);
 88.       editMenu.add(pasteAction);
 89.
 90.       // demonstrate nested menus
 91.
 92.       JMenu optionMenu = new JMenu("Options");
 93.
 94.       optionMenu.add(readonlyItem);
 95.       optionMenu.addSeparator();
 96.       optionMenu.add(insertItem);
 97.       optionMenu.add(overtypeItem);
 98.
 99.       editMenu.addSeparator();
100.       editMenu.add(optionMenu);
101.
102.       // demonstrate mnemonics
103.
104.       JMenu helpMenu = new JMenu("Help");
105.       helpMenu.setMnemonic('H'),
106.
107.       JMenuItem indexItem = new JMenuItem("Index");
108.       indexItem.setMnemonic('I'),
109.       helpMenu.add(indexItem);
110.
111.       // you can also add the mnemonic key to an action
112.       Action aboutAction = new TestAction("About");
113.       aboutAction.putValue(Action.MNEMONIC_KEY, new Integer('A'));
114.       helpMenu.add(aboutAction);
115.
116.       // add all top-level menus to menu bar
117.
118.       JMenuBar menuBar = new JMenuBar();
119.       setJMenuBar(menuBar);
120.
121.       menuBar.add(fileMenu);
122.       menuBar.add(editMenu);
123.       menuBar.add(helpMenu);
124.
125.       // demonstrate pop-ups
126.
127.       popup = new JPopupMenu();
128.       popup.add(cutAction);
129.       popup.add(copyAction);
130.       popup.add(pasteAction);
131.
132.       JPanel panel = new JPanel();
133.       panel.setComponentPopupMenu(popup);
134.       add(panel);
135.
136.       // the following line is a workaround for bug 4966109
137.       panel.addMouseListener(new MouseAdapter() {});
138.    }
139.
140.    public static final int DEFAULT_WIDTH = 300;
141.    public static final int DEFAULT_HEIGHT = 200;
142.
143.    private Action saveAction;
144.    private Action saveAsAction;
145.    private JCheckBoxMenuItem readonlyItem;
146.    private JPopupMenu popup;
147. }
148.
149. /**
150.    A sample action that prints the action name to System.out
151. */
152. class TestAction extends AbstractAction
153. {
154.    public TestAction(String name) { super(name); }
155.
156.    public void actionPerformed(ActionEvent event)
157.    {
158.       System.out.println(getValue(Action.NAME) + " selected.");
159.    }
160. }

Toolbars

A toolbar is a button bar that gives quick access to the most commonly used commands in a program (see Figure 9–28).

A toolbar

Figure 9–28. A toolbar

What makes toolbars special is that you can move them elsewhere. You can drag the toolbar to one of the four borders of the frame (see Figure 9–29). When you release the mouse button, the toolbar is dropped into the new location (see Figure 9–30).

Dragging the toolbar

Figure 9–29. Dragging the toolbar

Dragging the toolbar to another border

Figure 9–30. Dragging the toolbar to another border

Note

Note

Toolbar dragging works if the toolbar is inside a container with a border layout, or any other layout manager that supports the North, East, South, and West constraints.

The toolbar can even be completely detached from the frame. A detached toolbar is contained in its own frame (see Figure 9–31). When you close the frame containing a detached toolbar, the toolbar jumps back into the original frame.

Detaching the toolbar

Figure 9–31. Detaching the toolbar

Toolbars are straightforward to program. You add components into the toolbar:

JToolBar bar = new JToolBar();
bar.add(blueButton);

The JToolBar class also has a method to add an Action object. Simply populate the toolbar with Action objects, like this:

bar.add(blueAction);

The small icon of the action is displayed in the toolbar.

You can separate groups of buttons with a separator:

bar.addSeparator();

For example, the toolbar in Figure 9–28 has a separator between the third and fourth button.

Then, you add the toolbar to the frame.

add(bar, BorderLayout.NORTH);

You can also specify a title for the toolbar that appears when the toolbar is undocked:

bar = new JToolBar(titleString);

By default, toolbars are initially horizontal. To have a toolbar start out as vertical, use

bar = new JToolBar(SwingConstants.VERTICAL)

or

bar = new JToolBar(titleString, SwingConstants.VERTICAL)

Buttons are the most common components inside toolbars. But there is no restriction on the components that you can add to a toolbar. For example, you can add a combo box to a toolbar.

Tooltips

A disadvantage of toolbars is that users are often mystified by the meanings of the tiny icons in toolbars. To solve this problem, user interface designers invented tooltips. A tooltip is activated when the cursor rests for a moment over a button. The tooltip text is displayed inside a colored rectangle. When the user moves the mouse away, the tooltip is removed. (See Figure 9–32.)

A tooltip

Figure 9–32. A tooltip

In Swing, you can add tooltips to any JComponent simply by calling the setToolTipText method:

exitButton.setToolTipText("Exit");

Alternatively, if you use Action objects, you associate the tooltip with the SHORT_DESCRIPTION key:

exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");

Example 9–12 shows how the same Action objects can be added to a menu and a toolbar. Note that the action names show up as the menu item names in the menu, and the short descriptions as the tooltips in the toolbar.

Example 9–12. ToolBarTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import java.beans.*;
 4. import javax.swing.*;
 5.
 6. public class ToolBarTest
 7. {
 8.    public static void main(String[] args)
 9.    {
10.       ToolBarFrame frame = new ToolBarFrame();
11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
12.       frame.setVisible(true);
13.    }
14. }
15.
16. /**
17.    A frame with a toolbar and menu for color changes.
18. */
19. class ToolBarFrame extends JFrame
20. {
21.    public ToolBarFrame()
22.    {
23.       setTitle("ToolBarTest");
24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
25.
26.       // add a panel for color change
27.
28.       panel = new JPanel();
29.
30.       // set up actions
31.
32.       Action blueAction = new ColorAction("Blue",
33.          new ImageIcon("blue-ball.gif"), Color.BLUE);
34.       Action yellowAction = new ColorAction("Yellow",
35.          new ImageIcon("yellow-ball.gif"), Color.YELLOW);
36.       Action redAction = new ColorAction("Red",
37.          new ImageIcon("red-ball.gif"), Color.RED);
38.
39.       Action exitAction = new
40.          AbstractAction("Exit", new ImageIcon("exit.gif"))
41.          {
42.             public void actionPerformed(ActionEvent event)
43.             {
44.                System.exit(0);
45.             }
46.          };
47.       exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");
48.
49.       // populate toolbar
50.
51.       JToolBar bar = new JToolBar();
52.       bar.add(blueAction);
53.       bar.add(yellowAction);
54.       bar.add(redAction);
55.       bar.addSeparator();
56.       bar.add(exitAction);
57.       add(bar, BorderLayout.NORTH);
58.
59.       // populate menu
60.
61.       JMenu menu = new JMenu("Color");
62.       menu.add(yellowAction);
63.       menu.add(blueAction);
64.       menu.add(redAction);
65.       menu.add(exitAction);
66.       JMenuBar menuBar = new JMenuBar();
67.       menuBar.add(menu);
68.       setJMenuBar(menuBar);
69.    }
70.
71.    public static final int DEFAULT_WIDTH = 300;
72.    public static final int DEFAULT_HEIGHT = 200;
73.
74.    private JPanel panel;
75.
76.    /**
77.       The color action sets the background of the frame to a
78.       given color.
79.    */
80.    class ColorAction extends AbstractAction
81.    {
82.       public ColorAction(String name, Icon icon, Color c)
83.       {
84.          putValue(Action.NAME, name);
85.          putValue(Action.SMALL_ICON, icon);
86.          putValue(Action.SHORT_DESCRIPTION, name + " background");
87.          putValue("Color", c);
88.       }
89.
90.       public void actionPerformed(ActionEvent event)
91.       {
92.          Color c = (Color) getValue("Color");
93.          panel.setBackground(c);
94.       }
95.    }
96. }
ToolBarTest.java
javax.swing.JToolBar 1.2
  • JToolBar()

  • JToolBar(String titleString)

  • JToolBar(int orientation)

  • JToolBar(String titleString, int orientation)

    construct a toolbar with the given title string and orientation. orientation is one of SwingConstants.HORIZONTAL (the default) and SwingConstants.VERTICAL.

  • JButton add(Action a)

    constructs a new button inside the toolbar with name, icon, short description, and action callback from the given action, and adds the button to the end of the toolbar.

  • void addSeparator()

    adds a separator to the end of the toolbar.

ToolBarTest.java
javax.swing.JComponent 1.2
  • void setToolTipText(String text)

    sets the text that should be displayed as a tooltip when the mouse hovers over the component.

Sophisticated Layout Management

We have managed to lay out the user interface components of our sample applications so far by using only the border layout, flow layout, and grid layout. For more complex tasks, this is not going to be enough. In this section, we give you a detailed discussion of the advanced layout managers that the standard Java library provides to organize components.

Windows programmers may well wonder why Java makes so much fuss about layout managers. After all, in Windows, layout management is not a big deal: First, you use a dialog editor to drag and drop your components onto the surface of the dialog, and then you use editor tools to line up components, to space them equally, to center them, and so on. If you are working on a big project, you probably don’t have to worry about component layout at all—a skilled user interface designer does all this for you.

The problem with this approach is that the resulting layout must be manually updated if the size of the components changes. Why would the component size change? There are two common cases. First, a user may choose a larger font for button labels and other dialog text. If you try this out for yourself in Windows, you will find that many applications deal with this exceedingly poorly. The buttons do not grow, and the larger font is simply crammed into the same space as before. The same problem can occur when the strings in an application are translated to a foreign language. For example, the German word for “Cancel” is “Abbrechen.” If a button has been designed with just enough room for the string “Cancel”, then the German version will look broken, with a clipped command string.

Why don’t Windows buttons simply grow to accommodate the labels? Because the designer of the user interface gave no instructions in which direction they should grow. After the dragging and dropping and arranging, the dialog editor merely remembers the pixel position and size of each component. It does not remember why the components were arranged in this fashion.

The Java layout managers are a much better approach to component layout. With a layout manager, the layout comes with instructions about the relationships among the components. This was particularly important in the original AWT, which used native user interface elements. The size of a button or list box in Motif, Windows, and the Macintosh could vary widely, and an application or applet would not know a priori on which platform it would display its user interface. To some extent, that degree of variability has gone away with Swing. If your application forces a particular look and feel, such as the Metal look and feel, then it looks identical on all platforms. However, if you let users of your application choose their favorite look and feel, then you again need to rely on the flexibility of layout managers to arrange the components.

Of course, to achieve complex layouts, you will need to have more control over the layout than the border layout, flow layout, and grid layout give you. In this section, we discuss the layout managers that the standard Java library has to offer. Using a sophisticated layout manager combined with the appropriate use of multiple panels will give you complete control over how your application will look.

Tip

Tip

If none of the layout schemes fit your needs, break the surface of your window into separate panels and lay out each panel separately. Then, use another layout manager to organize the panels.

First, let’s review a few basic principles. As you know, in the AWT, components are laid out inside containers. Buttons, text fields, and other user interface elements are components and can be placed inside containers. Therefore, these classes extend the class Component. Containers such as panels can themselves be put inside other containers. Therefore, the class Container derives from Component. Figure 9–33 shows the inheritance hierarchy for Component.

Inheritance hierarchy for the Component classhierarchiesComponentComponent

Figure 9–33. Inheritance hierarchy for the Component class

Note

Note

Some objects belong to classes extending Component even though they are not user interface components and cannot be inserted into containers. Top-level windows such as JFrame and JApplet cannot be contained inside another window or panel.

As you have seen, to organize the components in a container, you first specify a layout manager. For example, the statement

panel.setLayout(new GridLayout(4, 4));

will use the GridLayout class to lay out the panels. After you set the layout manager, you add components to the container. The add method of the container passes the component and any placement directions to the layout manager.

With the border layout manager, you give a string to specify component placement:

panel.add(new JTextField(), BorderLayout.SOUTH);

With the grid layout, you need to add components sequentially:

panel.add(new JCheckBox("italic"));
panel.add(new JCheckBox("bold"));

The grid layout is useful for arranging components in a grid, somewhat like the rows and columns of a spreadsheet. However, all rows and columns of the grid have identical size, which is not all that useful in practice.

To overcome the limitations of the grid layout, the AWT supplies the grid bag layout. It, too, lays out components in rows and columns, but the row and column sizes are flexible and components can span multiple rows and columns. This layout manager is very flexible, but it is also very complex. The mere mention of the words “grid bag layout” has been known to strike fear in the hearts of Java programmers. Actually, in most common situations, the grid bag layout is not that hard to use, and we tell you a strategy that should make grid bag layouts relatively painless.

In an (unsuccessful) attempt to design a layout manager that would free programmers from the tyranny of the grid bag layout, the Swing designers came up with the box layout. The box layout simply arranges a sequence of components horizontally or vertically. When arranging components horizontally, the box layout is similar to the flow layout; however, components do not “wrap” to a new row when one row is full. By placing a number of horizontal box layouts inside a vertical box layout (or the other way around), you can give some order to a set of components in a two-dimensional area. However, because each box is laid out independently, you cannot use box layouts to arrange neighboring components both horizontally and vertically.

JDK 1.4 saw yet another attempt to design a replacement for the grid bag layout—the spring layout. We discuss the spring layout on page 440, and we leave it to you to decide whether it succeeds in its goal.

Swing also contains an overlay layout that lets you place components on top of each other. This layout manager is not generally useful, and we don’t discuss it.

Finally, there is a card layout that was used in the original AWT to produce tabbed dialogs. Because Swing has a much better tabbed dialog container (called JTabbedPane—see Volume 2), we do not cover the card layout here.

We end the discussion of layout managers by showing you how you can bypass layout management altogether and place components manually and by showing you how you can write your own layout manager.

Box Layout

The box layout lets you lay out a single row or column of components with more flexibility than the grid layout affords. There is even a container—the Box class—whose default layout manager is the BoxLayout (unlike the JPanel class whose default layout manager is the FlowLayout). Of course, you can also set the layout manager of a JPanel to the box layout, but it is simpler to just start with a Box container. The Box class also contains a number of static methods that are useful for managing box layouts.

To create a new container with a box layout, you can simply call

Box b = Box.createHorizontalBox();

or

Box b = Box.createVerticalBox();

You then add components in the usual way:

b.add(okButton);
b.add(cancelButton);

In a horizontal box, the components are arranged left to right. In a vertical box, the components are arranged top to bottom. Let us look at the horizontal layout more closely.

Each component has three sizes:

  • The preferred size—. the width and height at which the component would like to be displayed

  • The maximum size—. the largest width and height at which the component is willing to be displayed

  • The minimum size—. the smallest width and height at which the component is willing to be displayed

Here are details about what the box layout manager does:

  1. It computes the maximum (!) height of the tallest component.

  2. It tries to grow all components vertically to that height.

  3. If a component does not actually grow to that height when requested, then its y-alignment is queried by a call to its getAlignmentY method. That method returns a floating-point number between 0 (align on top) and 1 (align on bottom). The default in the Component class is 0.5 (center). The value is used to align the component vertically.

  4. The preferred width of each component is obtained. All preferred widths are added up.

  5. If the total preferred width is less than the box width, then the components are expanded by being allowed to grow to their maximum width. Components are then placed, from left to right, with no additional space between them. If the total preferred width is greater than the box width, the components are shrunk, potentially down to their minimum width but no further. If the components don’t all fit at their minimum width, some of them will not be shown.

For vertical layouts, the process is analogous.

Tip

Tip

It is unfortunate that BoxLayout tries to grow components beyond the preferred size. In particular, text fields have maximum width and height set to Integer.MAX_VALUE; that is, they are willing to grow as much as necessary. If you put a text field into a box layout, it will grow to monstrous proportions. Remedy: set the maximum size to the preferred size with

textField.setMaximumSize(textField.getPreferredSize());

Fillers

By default, there is no space between the components in a box layout. (Unlike the flow layout, the box layout does not have a notion of gaps between components.) To space the components out, you add invisible fillers. There are three kinds of fillers:

  • Struts

  • Rigid areas

  • Glue

A strut simply adds some space between components. For example, here is how you can add 10 pixels of space between two components in a horizontal box:

b.add(label);
b.add(Box.createHorizontalStrut(10));
b.add(textField);

You add a horizontal strut into a horizontal box, or a vertical strut into a vertical box, to add space between components. You can also add a vertical strut into a horizontal box, but that does not affect the horizontal layout. Instead, it sets the minimum height of the box.

The rigid area filler is similar to a pair of struts. It separates adjacent components but also adds a height or width minimum in the other direction. For example,

b.add(Box.createRigidArea(new Dimension(5, 20));

adds an invisible area with minimum, preferred, and maximum width of 5 pixels and height of 20 pixels, and centered alignment. If added into a horizontal box, it acts like a strut of width 5 and also forces the minimum height of the box to be 20 pixels.

By adding struts, you separate adjacent components by a fixed amount. Adding glue separates components as much as possible. The (invisible) glue expands to consume all available empty space, pushing the components away from each other. (We don’t know why the designers of the box layout came up with the name “glue”—”spring” would have been a more appropriate name.)

For example, here is how you space two buttons in a box as far apart as possible:

b.add(button1);
b.add(Box.createGlue());
b.add(button2);

If the box contains no other components, then button1 is moved all the way to the left and button2 is moved all the way to the right.

The program in Example 9–13 arranges a set of labels, text fields, and buttons, using a set of horizontal and vertical box layouts. Each row is placed in a horizontal box. Struts separate the labels from the text fields. Glue pushes the two buttons away from each other. The three horizontal boxes are placed in a vertical box, with glue pushing the button box to the bottom (see Figure 9–34).

Example 9–13. BoxLayoutTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class BoxLayoutTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       BoxLayoutFrame frame = new BoxLayoutFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame that uses box layouts to organize various components.
17. */
18. class BoxLayoutFrame extends JFrame
19. {
20.    public BoxLayoutFrame()
21.    {
22.       setTitle("BoxLayoutTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       // construct the top horizontal box
26.
27.       JLabel label1 = new JLabel("Name:");
28.       JTextField textField1 = new JTextField(10);
29.       textField1.setMaximumSize(textField1.getPreferredSize());
30.
31.       Box hbox1 = Box.createHorizontalBox();
32.       hbox1.add(label1);
33.       // separate with a 10-pixel strut
34.       hbox1.add(Box.createHorizontalStrut(10));
35.       hbox1.add(textField1);
36.
37.       // construct the middle horizontal box
38.
39.       JLabel label2 = new JLabel("Password:");
40.       JTextField textField2 = new JTextField(10);
41.       textField2.setMaximumSize(textField2.getPreferredSize());
42.
43.
44.       Box hbox2 = Box.createHorizontalBox();
45.       hbox2.add(label2);
46.       // separate with a 10-pixel strut
47.       hbox2.add(Box.createHorizontalStrut(10));
48.       hbox2.add(textField2);
49.
50.       // construct the bottom horizontal box
51.
52.       JButton button1 = new JButton("Ok");
53.       JButton button2 = new JButton("Cancel");
54.
55.       Box hbox3 = Box.createHorizontalBox();
56.       hbox3.add(button1);
57.       // use "glue" to push the two buttons apart
58.       hbox3.add(Box.createGlue());
59.       hbox3.add(button2);
60.
61.       // add the three horizontal boxes inside a vertical box
62.
63.       Box vbox = Box.createVerticalBox();
64.       vbox.add(hbox1);
65.       vbox.add(hbox2);
66.       vbox.add(Box.createGlue());
67.       vbox.add(hbox3);
68.
69.       add(vbox, BorderLayout.CENTER);
70.    }
71.
72.    public static final int DEFAULT_WIDTH = 200;
73.    public static final int DEFAULT_HEIGHT = 200;
74. }
BoxLayoutTest.java
javax.swing.Box 1.2
  • static Box createHorizontalBox()

  • static Box createVerticalBox()

    create a container that arranges its content horizontally or vertically.

  • static Component createHorizontalGlue()

  • static Component createVerticalGlue()

  • static Component createGlue()

    create an invisible component that can expand infinitely horizontally, vertically, or in both directions.

  • static Component createHorizontalStrut(int width)

  • static Component createVerticalStrut(int height)

  • static Component createRigidArea(Dimension d)

    create an invisible component with fixed width, fixed height, or fixed width and height.

BoxLayoutTest.java
java.awt.Component 1.0
  • float getAlignmentX() 1.1

  • float getAlignmentY() 1.1

    return the alignment along the x- or y-axis, a value between 0 and 1. The value 0 denotes alignment on top or left, 0.5 is centered, 1 is aligned on bottom or right.

Box layouts

Figure 9–34. Box layouts

The Grid Bag Layout

The grid bag layout is the mother of all layout managers. You can think of a grid bag layout as a grid layout without the limitations. In a grid bag layout, the rows and columns can have variable sizes. You can join adjacent cells to make room for larger components. (Many word processors, as well as HTML, have the same capability when tables are edited: you start out with a grid and then merge adjacent cells if need be.) The components need not fill the entire cell area, and you can specify their alignment within cells.

Fair warning: using grid bag layouts can be incredibly complex. The payoff is that they have the most flexibility and will work in the widest variety of situations. Keep in mind that the purpose of layout managers is to keep the arrangement of the components reasonable under different font sizes and operating systems, so it is not surprising that you need to work somewhat harder than when you design a layout just for one environment.

Note

Note

According to the JDK documentation of the BoxLayout class: “Nesting multiple panels with different combinations of horizontal and vertical [sic] gives an effect similar to GridBagLayout, without the complexity.” However, as you can see from Figure 9–34, the effect that you can achieve from multiple box layouts is plainly not useful in practice. No amount of fussing with boxes, struts, and glue will ensure that the components line up. When you need to arrange components so that they line up horizontally and vertically, you should consider the GridBagLayout class.

Consider the font selection dialog of Figure 9–35. It consists of the following components:

  • Two combo boxes to specify the font face and size

  • Labels for these two combo boxes

  • Two check boxes to select bold and italic

  • A text area for the sample string

Font dialog box

Figure 9–35. Font dialog box

Now, chop up the dialog box into a grid of cells, as shown in Figure 9–36. (The rows and columns need not have equal size.) Each checkbox spans two columns, and the text area spans four rows.

Dialog box grid used in design

Figure 9–36. Dialog box grid used in design

To describe the layout to the grid bag manager, you must go through the following convoluted procedure.

  1. Create an object of type GridBagLayout. You don’t tell it how many rows and columns the underlying grid has. Instead, the layout manager will try to guess it from the information you give it later.

  2. Set this GridBagLayout object to be the layout manager for the component.

  3. For each component, create an object of type GridBagConstraints. Set field values of the GridBagConstraints object to specify how the components are laid out within the grid bag.

  4. Then (finally), add the component with the constraints by using the call:

    add(component, constraints);
    

Here’s an example of the code needed. (We go over the various constraints in more detail in the sections that follow—so don’t worry if you don’t know what some of the constraints do.)

GridBagLayout layout = new GridBagLayout();
panel.setLayout(layout);
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 100;
constraints.weighty = 100;
constraints.gridx = 0;
constraints.gridy = 2;
constraints.gridwidth = 2;
constraints.gridheight = 1;
panel.add(style, bold);

The trick is knowing how to set the state of the GridBagConstraints object. We go over the most important constraints for using this object in the sections that follow.

The gridx, gridy, gridwidth, and gridheight Parameters

These constraints define where the component is located in the grid. The gridx and gridy values specify the column and row positions of the upper-left corner of the component to be added. The gridwidth and gridheight values determine how many columns and rows the component occupies.

The grid coordinates start with 0. In particular, gridx = 0 and gridy = 0 denotes the top-left corner.

For example, the text area in our example has gridx = 2, gridy = 0 because it starts in column 2 (that is, the third column) of row 0. It has gridwidth = 1 and gridheight = 4 because it spans one column and four rows.

Weight Fields

You always need to set the weight fields (weightx and weighty) for each area in a grid bag layout. If you set the weight to 0, then the area never grows or shrinks beyond its initial size in that direction. In the grid bag layout for Figure 9–35, we set the weightx field of the labels to be 0. This allows the labels to remain a constant width when you resize the window. On the other hand, if you set the weights for all areas to 0, the container will huddle in the center of its allotted area rather than stretching to fill it.

Conceptually, the problem with the weight parameters is that weights are properties of rows and columns, not individual cells. But you need to specify them in terms of cells because the grid bag layout does not expose the rows and columns. The row and column weights are computed as the maxima of the cell weights in each row or column. Thus, if you want a row or column to stay at a fixed size, you need to set the weights of all components in it to zero.

Note that the weights don’t actually give the relative sizes of the columns. They tell what proportion of the “slack” space should be allocated to each area if the container exceeds its preferred size. This isn’t particularly intuitive. We recommend that you set all weights at 100. Then, run the program and see how the layout looks. Resize the dialog to see how the rows and columns adjust. If you find that a particular row or column should not grow, set the weights of all components in it to zero. You can tinker with other weight values, but it is usually not worth the effort.

The fill and anchor Parameters

If you don’t want a component to stretch out and fill the entire area, you set the fill constraint. You have four possibilities for this parameter: the valid values are used in the forms GridBagConstraints.NONE, GridBagConstraints.HORIZONTAL, GridBagConstraints.VERTICAL, and GridBagConstraints.BOTH.

If the component does not fill the entire area, you can specify where in the area you want it by setting the anchor field. The valid values are GridBagConstraints.CENTER (the default), GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, and so on.

Padding

You can surround a component with additional blank space by setting the insets field of GridBagConstraints. Set the left, top, right and bottom values of the Insets object to the amount of space that you want to have around the component. This is called the external padding.

The ipadx and ipady values set the internal padding. These values are added to the minimum width and height of the component. This ensures that the component does not shrink down to its minimum size.

Alternative Method to Specify the gridx, gridy, gridwidth, and gridheight Parameters

The AWT documentation recommends that instead of setting the gridx and gridy values to absolute positions, you set them to the constant GridBagConstraints.RELATIVE. Then, add the components to the grid bag layout in a standardized order, going from left to right in the first row, then moving along the next row, and so on.

You still specify the number of rows and columns spanned, by giving the appropriate gridheight and gridwidth fields. Except, if the component extends to the last row or column, you aren’t supposed to specify the actual number, but the constant GridBagConstraints.REMAINDER. This tells the layout manager that the component is the last one in its row.

This scheme does seem to work. But it sounds really goofy to hide the actual placement information from the layout manager and hope that it will rediscover it.

All this sounds like a lot of trouble and complexity. But in practice, the strategy in the following recipe makes grid bag layouts relatively trouble-free.

A Helper Class to Tame the Grid Bag Constraints

The most tedious aspect of the grid bag layout is writing the code that sets the constraints. Most programmers write helper functions or a small helper class for this purpose. We present such a class after the complete code for the font dialog example. This class has the following features:

  • Its name is short: GBC instead of GridBagConstraints

  • It extends GridBagConstraints, so you can use shorter names such as GBC.EAST for the constants.

  • Use a GBC object when adding a component, such as

    add(component, new GBC(1, 2));
    
  • There are two constructors to set the most common parameters: gridx and gridy, or gridx, gridy, gridwidth, and gridheight.

    add(component, new GBC(1, 2, 1, 4));
    
  • There are convenient setters for the fields that come in x/y pairs:

    add(component, new GBC(1, 2).setWeight(100, 100));
    
  • The setter methods return this, so you can chain them:

    add(component, new GBC(1, 2).setAnchor(GBC.EAST).setWeight(100, 100));
    
  • The setInsets methods construct the Insets object for you. To get one-pixel insets, simply call

    add(component, new GBC(1, 2).setAnchor(GBC.EAST).setInsets(1));
    

Example 9–14 shows the complete code for the font dialog example. Here is the code that adds the components to the grid bag:

add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST));
add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1));
add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST));
add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1));
add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100));

Once you understand the grid bag constraints, this kind of code is fairly easy to read and debug.

Note

Note

The Sun tutorial at http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbag.html suggests that you reuse the same GridBagConstraints object for all components. We find the resulting code hard to read and error prone. For example, look at the demo at http://java.sun.com/docs/books/tutorial/uiswing/events/containerlistener.html. Was it really intended that the buttons are stretched horizontally, or did the programmer just forget to turn off the BOTH setting for the fill constraint?

Example 9–14. FontDialog.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4. import javax.swing.event.*;
 5.
 6. public class FontDialog
 7. {
 8.    public static void main(String[] args)
 9.    {
10.       FontDialogFrame frame = new FontDialogFrame();
11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
12.       frame.setVisible(true);
13.    }
14. }
15.
16. /**
17.    A frame that uses a grid bag layout to arrange font
18.    selection components.
19. */
20. class FontDialogFrame extends JFrame
21. {
22.    public FontDialogFrame()
23.    {
24.       setTitle("FontDialog");
25.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
26.
27.       GridBagLayout layout = new GridBagLayout();
28.       setLayout(layout);
29.
30.       ActionListener listener = new FontAction();
31.
32.       // construct components
33.
34.       JLabel faceLabel = new JLabel("Face: ");
35.
36.       face = new JComboBox(new String[]
37.          {
38.             "Serif", "SansSerif", "Monospaced",
39.             "Dialog", "DialogInput"
40.          });
41.
42.       face.addActionListener(listener);
43.
44.       JLabel sizeLabel = new JLabel("Size: ");
45.
46.       size = new JComboBox(new String[]
47.          {
48.             "8", "10", "12", "15", "18", "24", "36", "48"
49.          });
50.
51.       size.addActionListener(listener);
52.
53.       bold = new JCheckBox("Bold");
54.       bold.addActionListener(listener);
55.
56.       italic = new JCheckBox("Italic");
57.       italic.addActionListener(listener);
58.
59.       sample = new JTextArea();
60.       sample.setText("The quick brown fox jumps over the lazy dog");
61.       sample.setEditable(false);
62.       sample.setLineWrap(true);
63.       sample.setBorder(BorderFactory.createEtchedBorder());
64.
65.       // add components to grid, using GBC convenience class
66.
67.       add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST));
68.       add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1));
69.       add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST));
70.       add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL).setWeight(100, 0).setInsets(1));
71.       add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
72.       add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100));
73.       add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100));
74.    }
75.
76.    public static final int DEFAULT_WIDTH = 300;
77.    public static final int DEFAULT_HEIGHT = 200;
78.
79.    private JComboBox face;
80.    private JComboBox size;
81.    private JCheckBox bold;
82.    private JCheckBox italic;
83.    private JTextArea sample;
84.
85.    /**
86.       An action listener that changes the font of the
87.       sample text.
88.    */
89.    private class FontAction implements ActionListener
90.    {
91.       public void actionPerformed(ActionEvent event)
92.       {
93.          String fontFace = (String) face.getSelectedItem();
94.          int fontStyle = (bold.isSelected() ? Font.BOLD : 0)
95.             + (italic.isSelected() ? Font.ITALIC : 0);
96.          int fontSize = Integer.parseInt((String) size.getSelectedItem());
97.          Font font = new Font(fontFace, fontStyle, fontSize);
98.          sample.setFont(font);
99.          sample.repaint();
100.       }
101.    }
102. }

Example 9–15 shows the code of the GBC helper class.

Example 9–15. GBC.java

  1. /*
  2. GBC - A convenience class to tame the GridBagLayout
  3.
  4. Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
  5.
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
 10.
 11. This program is distributed in the hope that it will be useful,
 12. but WITHOUT ANY WARRANTY; without even the implied warranty of
 13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14. GNU General Public License for more details.
 15.
 16. You should have received a copy of the GNU General Public License
 17. along with this program; if not, write to the Free Software
 18. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19. */
 20.
 21. import java.awt.*;
 22.
 23. /**
 24.    This class simplifies the use of the GridBagConstraints
 25.    class.
 26. */
 27. public class GBC extends GridBagConstraints
 28. {
 29.    /**
 30.       Constructs a GBC with a given gridx and gridy position and
 31.       all other grid bag constraint values set to the default.
 32.       @param gridx the gridx position
 33.       @param gridy the gridy position
 34.    */
 35.    public GBC(int gridx, int gridy)
 36.    {
 37.       this.gridx = gridx;
 38.       this.gridy = gridy;
 39.    }
 40.
 41.    /**
 42.       Constructs a GBC with given gridx, gridy, gridwidth, gridheight
 43.       and all other grid bag constraint values set to the default.
 44.       @param gridx the gridx position
 45.       @param gridy the gridy position
 46.       @param gridwidth the cell span in x-direction
 47.       @param gridheight the cell span in y-direction
 48.    */
 49.    public GBC(int gridx, int gridy, int gridwidth, int gridheight)
 50.    {
 51.       this.gridx = gridx;
 52.       this.gridy = gridy;
 53.       this.gridwidth = gridwidth;
 54.       this.gridheight = gridheight;
 55.    }
 56.
 57.    /**
 58.       Sets the anchor.
 59.       @param anchor the anchor value
 60.       @return this object for further modification
 61.    */
 62.    public GBC setAnchor(int anchor)
 63.    {
 64.       this.anchor = anchor;
 65.       return this;
 66.    }
 67.
 68.    /**
 69.       Sets the fill direction.
 70.       @param fill the fill direction
 71.       @return this object for further modification
 72.    */
 73.    public GBC setFill(int fill)
 74.    {
 75.       this.fill = fill;
 76.       return this;
 77.    }
 78.
 79.    /**
 80.       Sets the cell weights.
 81.       @param weightx the cell weight in x-direction
 82.       @param weighty the cell weight in y-direction
 83.       @return this object for further modification
 84.    */
 85.    public GBC setWeight(double weightx, double weighty)
 86.    {
 87.       this.weightx = weightx;
 88.       this.weighty = weighty;
 89.       return this;
 90.    }
 91.
 92.    /**
 93.       Sets the insets of this cell.
 94.       @param distance the spacing to use in all directions
 95.       @return this object for further modification
 96.    */
 97.    public GBC setInsets(int distance)
 98.    {
 99.       this.insets = new Insets(distance, distance, distance, distance);
100.       return this;
101.    }
102.
103.    /**
104.       Sets the insets of this cell.
105.       @param top the spacing to use on top
106.       @param left the spacing to use to the left
107.       @param bottom the spacing to use on the bottom
108.       @param right the spacing to use to the right
109.       @return this object for further modification
110.    */
111.    public GBC setInsets(int top, int left, int bottom, int right)
112.    {
113.       this.insets = new Insets(top, left, bottom, right);
114.       return this;
115.    }
116.
117.    /**
118.       Sets the internal padding
119.       @param ipadx the internal padding in x-direction
120.       @param ipady the internal padding in y-direction
121.       @return this object for further modification
122.    */
123.    public GBC setIpad(int ipadx, int ipady)
124.    {
125.       this.ipadx = ipadx;
126.       this.ipady = ipady;
127.       return this;
128.    }
129. }

Tip

Tip

Some GUI builders have tools for specifying the constraints visually—see Figure 9–37 for the configuration dialog in NetBeans.

Specifying grid bag constraints in NetBeansgrid bag layoutconstraints

Figure 9–37. Specifying grid bag constraints in NetBeans

Specifying grid bag constraints in NetBeansgrid bag layoutconstraints
java.awt.GridBagConstraints 1.0
  • int gridx, gridy

    specifies the starting column and row of the cell. The default is 0.

  • int gridwidth, gridheight

    specifies the column and row extent of the cell. The default is 1.

  • double weightx, weighty

    specifies the capacity of the cell to grow. The default is 0.

  • int anchor

    indicates the alignment of the component inside the cell. You can choose between absolute positions—

    NORTHWEST

    NORTH

    NORTHEAST

    WEST

    CENTER

    EAST

    SOUTHWEST

    SOUTH

    SOUTHEAST

    —or their orientation-independent counterparts

    FIRST_LINE_START

    LINE_START

    FIRST_LINE_END

    PAGE_START

    CENTER

    PAGE_END

    LAST_LINE_START

    LINE_END

    LAST_LINE_END

    Use the latter if your application may be localized for right-to-left or top-to-bottom text. The default is CENTER.

  • int fill

    specifies the fill behavior of the component inside the cell, one of NONE, BOTH, HORIZONTAL, or VERTICAL. The default is NONE.

  • int ipadx, ipady

    specifies the “internal” padding around the component. The default is 0.

  • Insets insets

    specifies the “external” padding along the cell boundaries. The default is no padding.

  • GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady) 1.2

    constructs a GridBagConstraints with all its fields specified in the arguments. Sun recommends that this constructor be used only by automatic code generators because it makes your source code very hard to read.

The Spring Layout

Ever since programmers met the GridBagLayout, they begged the Java team for a layout manager that is equally flexible but more intuitive. Finally, JDK 1.4 features a contender, the SpringLayout. In this section you will see how it measures up.

With the spring layout, you attach springs to each component. A spring is a device for specifying component positions. As shown in Figure 9–38, each spring has

  • A minimum value

  • A preferred value

  • A maximum value

  • An actual value

A spring

Figure 9–38. A spring

When the spring is compressed or expanded in the layout phase, the actual value is fixed; it falls between the minimum and maximum value and is as close to the preferred value as the other springs allow. Then the actual value determines the position of the component to which it has been attached.

The spring class defines a sum operation that takes two springs and produces a new spring that combines the characteristics of the individual springs. When you lay out a number of components in a row, you attach several springs so that their sum spans the entire container—see Figure 9–39. That sum spring is now compressed or expanded so that its value equals the dimension of the container. This operation exerts a strain on the individual springs. Each spring value is set so that the strain of each spring equals the strain of the sum. Thus, the values of the individual springs are determined, and the layout is fixed. (If you are interested in the details of the strain computations, check the API documentation of the Spring class for more information.)

Summing springs

Figure 9–39. Summing springs

Let’s run through a simple example. Suppose you want to lay out three buttons horizontally.

JButton b1 = new JButton("Yellow");
JButton b2 = new JButton("Blue");
JButton b3 = new JButton("Red");

You first set the layout manager of the frame to a SpringLayout and add the components.

SpringLayout layout = new SpringLayout();
panel.setLayout(layout);
panel.add(b1);
panel.add(b2);
panel.add(b3);

Now construct a spring with a good amount of compressibility. The static Spring.constant method produces a spring with given minimum, preferred, and maximum values. (The spring isn’t actually constant—it can be compressed or expanded.)

Spring s = Spring.constant(0, 10000, 10000);

Next, attach one copy of the spring from the west side of the container to the west side of b1:

layout.putConstraint(SpringLayout.WEST, b1, s, SpringLayout.WEST, panel);

The putConstraint method adds the given spring so that it ends at the first parameter set (the west side of b1 in our case) and starts from the second parameter set (the west side of the content pane).

Next, you link up the other springs:

layout.putConstraint(SpringLayout.WEST, b2, s, SpringLayout.EAST, b1);
layout.putConstraint(SpringLayout.WEST, b3, s, SpringLayout.EAST, b2);

Finally, you hook up a spring with the east wall of the container:

layout.putConstraint(SpringLayout.EAST, panel, s, SpringLayout.EAST, b3);

The result is that the four springs are compressed to the same size, and the buttons are equally spaced (see Figure 9–40).

Equally spaced buttonsbuttonsequally spacingbuttonsspring layoutstrutslayoutstruts and springsSwinglayout managersstruts and springsSwingstruts and springs

Figure 9–40. Equally spaced buttons

Alternatively, you may want to vary the distances. Let’s suppose you want to have a fixed distance between the buttons. Use a strut—a spring that can’t be expanded or compressed. You get such a spring with the single-parameter version of the Spring.constant method:

Spring strut = Spring.constant(10);

If you add two struts between the buttons, but leave the springs at the ends, the result is a button group that is centered in the container (see Figure 9–41).

Springs and struts

Figure 9–41. Springs and struts

Of course, you don’t really need the spring layout for such a simple arrangement. Let’s look at something more complex, a portion of the font dialog of the preceding example. We have two combo boxes with labels, and we want to have the west sides of both combo boxes start after the longer label (see Figure 9–42).

Lining up columnscolumns, lining up in layoutadvanced layoutcolumns, lining upadvanced layoutSwing, grid bag layout

Figure 9–42. Lining up columns

This calls for another spring operation. You can form the maximum of two springs with the static Spring.max method. The result is a spring that is as long as the longer of the two inputs.

We get the maximum of the two east sides like this:

Spring labelsEast = Spring.max(
   layout.getConstraint(SpringLayout.EAST, faceLabel),
   layout.getConstraint(SpringLayout.EAST, sizeLabel));

Note that the getConstraint method yields a spring that reaches all the way from the west side of the container to the given sides of the component

Let’s add a strut so that there is some space between the labels and the combo boxes:

Spring combosWest = Spring.sum(labelsEast, strut);

Now we attach this spring to the west side of both combo boxes. The starting point is the start of the container because the labelsEast spring starts there.

layout.putConstraint(SpringLayout.WEST, face, combosWest,SpringLayout.WEST, panel);
layout.putConstraint(SpringLayout.WEST, size, combosWest,SpringLayout.WEST, panel);

Now the two combo boxes line up because they are held by the same spring.

However, there is a slight blemish. We’d prefer the labels to be right-aligned. It is possible to achieve this effect as well, but it requires a more precise understanding of spring attachments.

Let’s look at the horizontal springs in detail. Vertical springs follow the same logic. Figure 9–43 shows the three ways in which horizontal springs can be attached:

  • Connecting the west side of the component with the west side of the component;

  • Traversing the width of the component;

  • Connecting the west side of the component with the east side of the component.

Horizontal springs attached to a component

Figure 9–43. Horizontal springs attached to a component

You get these springs as follows:

Spring west = layout.getConstraints(component).getX();
Spring width = layout.getConstraints(component).getWidth();
Spring east = layout.getConstraint(SpringLayout.EAST, component);

The getConstraints method yields an object of type SpringLayout.Constraints. You can think of such an object as a rectangle, except that the x, y, width, and height values are springs, not numbers. The getConstraint method yields a single spring that reaches to one of the four component boundaries. You can also get the west spring as

Spring west = layout.getConstraint(SpringLayout.WEST, component);

Of course, the three springs are related: The spring sum of west and width must equal east.

When the component constraints are first set, the width is set to a spring whose parameters are the minimum, preferred, and maximum width of the component. The west is set to 0.

Caution

Caution

If you don’t set the west (and north) spring of a component, then the component stays at offset 0 in the container.

If a component has two springs set and you add a third one, then it becomes overconstrained. One of the existing springs is removed and its value is computed as the sum or difference of the other springs. Table 9–3 shows which spring is recomputed.

Table 9–3. Adding a Spring to an Overconstrained Component

Added Spring

Removed Spring

Replaced By

West

width

east - west

Width

east

west + width

East

west

east - width

Note

Note

The difference between two springs may not be intuitive, but it makes sense in the spring algebra. There is no Java method for spring subtraction. If you need to compute the difference of two springs, use

Spring.sum(s, Spring.minus(t))

Now you know enough about springs to solve the “right alignment” problem. Compute the maximum of the widths of the two labels. Then set the east spring of both labels to that maximum. As you can see from Table 9–3, the label widths don’t change, the west springs are recomputed, and the labels become aligned at the eastern boundary.

Spring labelsEast = Spring.sum(strut,
   Spring.max(layout.getConstraints(faceLabel).getWidth(),
   Spring.max(layout.getConstraints(sizeLabel).getWidth()));
layout.putConstraint(SpringLayout.EAST, faceLabel, labelsEast, SpringLayout.WEST, panel);
layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout.WEST, panel);

Example 9–16 shows how to lay out the font dialog with springs. If you look at the code, you will probably agree that the spring layout is quite a bit less intuitive than the grid bag layout. We hope to someday see tools that make the spring layout more approachable. However, in the meantime we recommend that you stick with the grid bag layout for complex layouts.

Example 9–16. SpringLayoutTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4. import javax.swing.event.*;
  5.
  6. public class SpringLayoutTest
  7. {
  8.    public static void main(String[] args)
  9.    {
 10.       FontDialogFrame frame = new FontDialogFrame();
 11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 12.       frame.setVisible(true);
 13.    }
 14. }
 15.
 16. /**
 17.    A frame that uses a spring layout to arrange font
 18.    selection components.
 19. */
 20. class FontDialogFrame extends JFrame
 21. {
 22.    public FontDialogFrame()
 23.    {
 24.       setTitle("FontDialog");
 25.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 26.
 27.       JPanel panel = new JPanel();
 28.       SpringLayout layout = new SpringLayout();
 29.       panel.setLayout(layout);
 30.
 31.       ActionListener listener = new FontAction();
 32.
 33.       // construct components
 34.
 35.       JLabel faceLabel = new JLabel("Font Face: ");
 36.
 37.       face = new JComboBox(new String[]
 38.          {
 39.             "Serif", "SansSerif", "Monospaced",
 40.             "Dialog", "DialogInput"
 41.          });
 42.
 43.       face.addActionListener(listener);
 44.
 45.       JLabel sizeLabel = new JLabel("Size: ");
 46.
 47.       size = new JComboBox(new String[]
 48.          {
 49.             "8", "10", "12", "15", "18", "24", "36", "48"
 50.          });
 51.
 52.       size.addActionListener(listener);
 53.
 54.       bold = new JCheckBox("Bold");
 55.       bold.addActionListener(listener);
 56.
 57.       italic = new JCheckBox("Italic");
 58.       italic.addActionListener(listener);
 59.
 60.       sample = new JTextArea();
 61.       sample.setText("The quick brown fox jumps over the lazy dog");
 62.       sample.setEditable(false);
 63.       sample.setLineWrap(true);
 64.       sample.setBorder(BorderFactory.createEtchedBorder());
 65.
 66.       panel.add(faceLabel);
 67.       panel.add(sizeLabel);
 68.       panel.add(face);
 69.       panel.add(size);
 70.       panel.add(bold);
 71.       panel.add(italic);
 72.       panel.add(sample);
 73.
 74.       // add springs to lay out components
 75.       Spring strut = Spring.constant(10);
 76.
 77.       Spring labelsEast = Spring.sum(strut,
 78.          Spring.max(
 79.             layout.getConstraints(faceLabel).getWidth(),
 80.             layout.getConstraints(sizeLabel).getWidth()));
 81.
 82.       layout.putConstraint(SpringLayout.EAST, faceLabel, labelsEast, SpringLayout
SpringLayoutTest.java.WEST, panel);
 83.       layout.putConstraint(SpringLayout.EAST, sizeLabel, labelsEast, SpringLayout
SpringLayoutTest.java.WEST, panel);
 84.
 85.       layout.putConstraint(SpringLayout.NORTH, faceLabel, strut,  SpringLayout.NORTH,
SpringLayoutTest.java panel);
 86.       layout.putConstraint(SpringLayout.NORTH, face, strut, SpringLayout.NORTH, panel);
 87.
 88.       Spring secondRowNorth = Spring.sum(strut,
 89.          Spring.max(
 90.             layout.getConstraint(SpringLayout.SOUTH, faceLabel),
 91.             layout.getConstraint(SpringLayout.SOUTH, face)));
 92.
 93.       layout.putConstraint(SpringLayout.NORTH, sizeLabel, secondRowNorth,
SpringLayoutTest.java SpringLayout.NORTH,
 94.          panel);
 95.       layout.putConstraint(SpringLayout.NORTH, size, secondRowNorth, SpringLayout
SpringLayoutTest.java.NORTH, panel);
 96.
 97.       layout.putConstraint(SpringLayout.WEST, face, strut, SpringLayout.EAST, faceLabel);
 98.       layout.putConstraint(SpringLayout.WEST, size, strut, SpringLayout.EAST, sizeLabel);
 99.
100.       layout.putConstraint(SpringLayout.WEST, bold, strut, SpringLayout.WEST, panel);
101.       layout.putConstraint(SpringLayout.WEST, italic, strut, SpringLayout.WEST, panel);
102.
103.       Spring s = Spring.constant(10, 10000, 10000);
104.
105.       Spring thirdRowNorth = Spring.sum(s,
106.          Spring.max(
107.             layout.getConstraint(SpringLayout.SOUTH, sizeLabel),
108.             layout.getConstraint(SpringLayout.SOUTH, size)));
109.
110.       layout.putConstraint(SpringLayout.NORTH, bold, thirdRowNorth, SpringLayout
SpringLayoutTest.java.NORTH, panel);
111.       layout.putConstraint(SpringLayout.NORTH, italic, s, SpringLayout.SOUTH, bold);
112.       layout.putConstraint(SpringLayout.SOUTH, panel, s, SpringLayout.SOUTH, italic);
113.
114.       Spring secondColumnWest = Spring.sum(strut,
115.          Spring.max(
116.             layout.getConstraint(SpringLayout.EAST, face),
117.             layout.getConstraint(SpringLayout.EAST, size)));
118.
119.       layout.putConstraint(SpringLayout.WEST, sample, secondColumnWest, SpringLayout
SpringLayoutTest.java.WEST, panel);
120.       layout.putConstraint(SpringLayout.SOUTH, sample, Spring.minus(strut),
SpringLayoutTest.java SpringLayout.SOUTH,
121.          panel);
122.       layout.putConstraint(SpringLayout.NORTH, sample, strut, SpringLayout.NORTH, panel);
123.       layout.putConstraint(SpringLayout.EAST, panel, strut, SpringLayout.EAST, sample);
124.
125.       add(panel);
126.    }
127.
128.    public static final int DEFAULT_WIDTH = 400;
129.    public static final int DEFAULT_HEIGHT = 200;
130.
131.    private JComboBox face;
132.    private JComboBox size;
133.    private JCheckBox bold;
134.    private JCheckBox italic;
135.    private JTextArea sample;
136.
137.    /**
138.       An action listener that changes the font of the
139.       sample text.
140.    */
141.    private class FontAction implements ActionListener
142.    {
143.       public void actionPerformed(ActionEvent event)
144.       {
145.          String fontFace = (String) face.getSelectedItem();
146.          int fontStyle = (bold.isSelected() ? Font.BOLD : 0)
147.             + (italic.isSelected() ? Font.ITALIC : 0);
148.          int fontSize = Integer.parseInt((String) size.getSelectedItem());
149.          Font font = new Font(fontFace, fontStyle, fontSize);
150.          sample.setFont(font);
151.          sample.repaint();
152.       }
153.    }
154. }
SpringLayoutTest.java
javax.swing.SpringLayout 1.4
  • SpringLayout.Constraints getConstraints(Component c)

    gets the constraints of the given component.

    Parameters:

    c

    One of the components or the container managed by this layout manager

  • void putConstraint(String endSide, Component end, Spring s, String startSide, Component start)

  • void putConstraint(String endSide, Component end, int pad, String startSide, Component start)

    set the given side of the end component to a spring that is obtained by adding the spring s, or a strut with size pad, to the spring that reaches from the left end of the container to the given side of the start container.

    Parameters:

    endSide, startSide

    WEST, EAST, NORTH, or SOUTH

     

    end

    The component to which a spring is added

     

    s

    One of the summands of the added spring

     

    pad

    The size of the strut summand

     

    start

    The component to which the other summand spring reaches

SpringLayoutTest.java
javax.swing.SpringLayout.Constraints 1.4
  • Constraints(Component c) 5.0

    constructs a Constraints object whose positions, width, and springs match the given component.

  • Spring getX()

  • Spring getY()

    return the spring reaching from the start of the container to the west or north end of the constrained component.

  • Spring getWidth()

  • Spring getHeight()

    return the spring spanning the width or height of the constrained component.

  • Spring getConstraint(String side)

  • void setConstraint(String edge, Spring s)

    get or set a spring reaching from the start of the container to the given side of the constrained component.

    Parameters:

    side

    One of the constants WEST, EAST, NORTH, or SOUTH of the SpringLayout class

     

    s

    The spring to set

SpringLayoutTest.java
javax.swing.Spring 1.4
  • static Spring constant(int preferred)

    constructs a strut with the given preferred size. The minimum and maximum sizes are set to the preferred size.

  • static Spring constant(int minimum, int preferred, int maximum)

    constructs a spring with the given minimum, preferred, and maximum sizes.

  • static Spring sum(Spring s, Spring t)

    returns the spring sum of s and t.

  • static Spring max(Spring s, Spring t)

    returns the spring maximum of s and t.

  • static Spring minus(Spring s)

    returns the opposite of the spring s.

  • static Spring scale(Spring s, float factor) 5.0

    scales the minimum, preferred, and maximum sizes of s by the given factor. If the factor is negative, the scaled opposite of s is returned.

  • static Spring width(Component c) 5.0

  • static Spring height(Component c) 5.0

    return a spring whose minimum, preferred, and maximum sizes equal the minimum, preferred, and maximum width or height of the given component.

  • int getMinimumValue()

  • int getPreferredValue()

  • int getMaximumValue()

    return the minimum, preferred, and maximum value of this spring.

  • int getValue()

  • void setValue(int newValue)

    get and set the spring value. When setting the value of a compound spring, the values of the components are set as well.

Using No Layout Manager

There will be times when you don’t want to bother with layout managers but just want to drop a component at a fixed location (sometimes called absolute positioning). This is not a great idea for platform-independent applications, but there is nothing wrong with using it for a quick prototype.

Here is what you do to place a component at a fixed location:

  1. Set the layout manager to null.

  2. Add the component you want to the container.

  3. Then specify the position and size that you want.

    setLayout(null);
    JButton ok = new JButton("Ok");
    add(ok);
    ok.setBounds(10, 10, 30, 15);
    
Using No Layout Manager
java.awt.Component 1.0
  • void setBounds(int x, int y, int width, int height)

    moves and resizes a component.

    Parameters:

    x, y

    The new top-left corner of the component

     

    width, height

    The new size of the component

Custom Layout Managers

In principle, you can design your own LayoutManager class that manages components in a special way. For example, you could arrange all components in a container to form a circle. This will almost always be a major effort and a real time sink, but as Figure 9–44 shows, the results can be quite dramatic.

Circle layout

Figure 9–44. Circle layout

If you do feel you can’t live without your own layout manager, here is what you do. Your own layout manager must implement the LayoutManager interface. You need to override the following five methods.

void addLayoutComponent(String s, Component c);
void removeLayoutComponent(Component c);
Dimension preferredLayoutSize(Container parent);
Dimension minimumLayoutSize(Container parent);
void layoutContainer(Container parent);

The first two functions are called when a component is added or removed. If you don’t keep any additional information about the components, you can make them do nothing. The next two functions compute the space required for the minimum and the preferred layout of the components. These are usually the same quantity. The fifth function does the actual work and invokes setBounds on all components.

Note

Note

The AWT has a second interface, called LayoutManager2, with 10 methods to implement rather than 5. The main point of the LayoutManager2 interface is to allow the user to use the add method with constraints. For example, the BorderLayout and GridBagLayout implement the LayoutManager2 interface.

Example 9–17 is a simple implementation of the CircleLayout manager, which, amazingly and uselessly enough, lays out the components along a circle inside the parent.

Example 9–17. CircleLayoutTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4.
  5. public class CircleLayoutTest
  6. {
  7.    public static void main(String[] args)
  8.    {
  9.       CircleLayoutFrame frame = new CircleLayoutFrame();
 10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 11.       frame.pack();
 12.       frame.setVisible(true);
 13.    }
 14. }
 15.
 16. /**
 17.    A frame that shows buttons arranged along a circle.
 18. */
 19. class CircleLayoutFrame extends JFrame
 20. {
 21.    public CircleLayoutFrame()
 22.    {
 23.       setTitle("CircleLayoutTest");
 24.
 25.       setLayout(new CircleLayout());
 26.       add(new JButton("Yellow"));
 27.       add(new JButton("Blue"));
 28.       add(new JButton("Red"));
 29.       add(new JButton("Green"));
 30.       add(new JButton("Orange"));
 31.       add(new JButton("Fuchsia"));
 32.       add(new JButton("Indigo"));
 33.    }
 34. }
 35.
 36. /**
 37.    A layout manager that lays out components along a circle.
 38. */
 39. class CircleLayout implements LayoutManager
 40. {
 41.    public void addLayoutComponent(String name, Component comp)
 42.    {}
 43.
 44.    public void removeLayoutComponent(Component comp)
 45.    {}
 46.
 47.    public void setSizes(Container parent)
 48.    {
 49.       if (sizesSet) return;
 50.       int n = parent.getComponentCount();
 51.
 52.       preferredWidth = 0;
 53.       preferredHeight = 0;
 54.       minWidth = 0;
 55.       minHeight = 0;
 56.       maxComponentWidth = 0;
 57.       maxComponentHeight = 0;
 58.
 59.       // compute the maximum component widths and heights
 60.       // and set the preferred size to the sum of the component sizes.
 61.       for (int i = 0; i < n; i++)
 62.       {
 63.          Component c = parent.getComponent(i);
 64.          if (c.isVisible())
 65.          {
 66.             Dimension d = c.getPreferredSize();
 67.             maxComponentWidth = Math.max(maxComponentWidth, d.width);
 68.             maxComponentHeight = Math.max(maxComponentHeight, d.height);
 69.             preferredWidth += d.width;
 70.             preferredHeight += d.height;
 71.          }
 72.       }
 73.       minWidth = preferredWidth / 2;
 74.       minHeight = preferredHeight / 2;
 75.       sizesSet = true;
 76.    }
 77.
 78.    public Dimension preferredLayoutSize(Container parent)
 79.    {
 80.       setSizes(parent);
 81.       Insets insets = parent.getInsets();
 82.       int width = preferredWidth + insets.left + insets.right;
 83.       int height = preferredHeight + insets.top + insets.bottom;
 84.       return new Dimension(width, height);
 85.    }
 86.
 87.    public Dimension minimumLayoutSize(Container parent)
 88.    {
89.       setSizes(parent);
90.       Insets insets = parent.getInsets();
91.       int width = minWidth + insets.left + insets.right;
92.       int height = minHeight + insets.top + insets.bottom;
93.       return new Dimension(width, height);
94.    }
95.
96.    public void layoutContainer(Container parent)
97.    {
98.       setSizes(parent);
99.
100.       // compute center of the circle
101.
102.       Insets insets = parent.getInsets();
103.       int containerWidth = parent.getSize().width - insets.left - insets.right;
104.       int containerHeight = parent.getSize().height - insets.top - insets.bottom;
105.
106.       int xcenter = insets.left + containerWidth / 2;
107.       int ycenter = insets.top + containerHeight / 2;
108.
109.       // compute radius of the circle
110.
111.       int xradius = (containerWidth - maxComponentWidth) / 2;
112.       int yradius = (containerHeight - maxComponentHeight) / 2;
113.       int radius = Math.min(xradius, yradius);
114.
115.       // lay out components along the circle
116.
117.       int n = parent.getComponentCount();
118.       for (int i = 0; i < n; i++)
119.       {
120.          Component c = parent.getComponent(i);
121.          if (c.isVisible())
122.          {
123.             double angle = 2 * Math.PI * i / n;
124.
125.             // center point of component
126.             int x = xcenter + (int)(Math.cos(angle) * radius);
127.             int y = ycenter + (int)(Math.sin(angle) * radius);
128.
129.             // move component so that its center is (x, y)
130.             // and its size is its preferred size
131.             Dimension d = c.getPreferredSize();
132.             c.setBounds(x - d.width / 2, y - d.height / 2, d.width, d.height);
133.          }
134.       }
135.    }
136.
137.    private int minWidth = 0;
138.    private int minHeight = 0;
139.    private int preferredWidth = 0;
140.    private int preferredHeight = 0;
141.    private boolean sizesSet = false;
142.    private int maxComponentWidth = 0;
143.    private int maxComponentHeight = 0;
144. }
CircleLayoutTest.java
java.awt.LayoutManager 1.0
  • void addLayoutComponent(String name, Component comp)

    adds a component to the layout.

    Parameters:

    name

    An identifier for the component placement

     

    comp

    The component to be added

  • void removeLayoutComponent(Component comp)

    removes a component from the layout.

    Parameters:

    comp

    The component to be removed

  • Dimension preferredLayoutSize(Container parent)

    returns the preferred size dimensions for the container under this layout.

    Parameters:

    parent

    The container whose components are being laid out

  • Dimension minimumLayoutSize(Container parent)

    returns the minimum size dimensions for the container under this layout.

    Parameters:

    parent

    The container whose components are being laid out

  • void layoutContainer(Container parent)

    lays out the components in a container.

    Parameters:

    parent

    The container whose components are being laid out

Traversal Order

When you add many components into a window, you need to give some thought to the traversal order. When a window is first displayed, the first component in the traversal order has the keyboard focus. Each time the user presses the TAB key, the next component gains focus. (Recall that a component that has the keyboard focus can be manipulated with the keyboard. For example, a button can be “clicked” with the space bar when it has focus.) You may not personally care about using the TAB key to navigate through a set of controls, but plenty of users do. Among them are the mouse haters and those who cannot use a mouse, perhaps because of a handicap or because they are navigating the user interface by voice. For that reason, you need to know how Swing handles traversal order.

The traversal order is straightforward, first left to right and then top to bottom. For example, in the font dialog example, the components are traversed in the following order (see Figure 9–45):

  1. Face combo box

  2. Sample text area (press CTRL+TAB to move to the next field; the TAB character is considered text input)

  3. Size combo box

  4. Bold checkbox

  5. Italic checkbox

Geometric traversal order

Figure 9–45. Geometric traversal order

Note

Note

In the old AWT, the traversal order was determined by the order in which you inserted components into a container. In Swing, the insertion order does not matter—only the layout of the components is considered.

The situation is more complex if your container contains other containers. When the focus is given to another container, it automatically ends up within the top-left component in that container and then it traverses all other components in that container. Finally, the focus is given to the component following the container.

You can use this to your advantage by grouping related elements in another container such as a panel.

Note

Note

As of JDK 1.4, you call

component.setFocusable(false);

to remove a component from the focus traversal. In older versions of the JDK, you had to override the isFocusTraversable method, but that method is now deprecated.

In summary, there are two standard traversal policies in JDK 1.4:

  • Pure AWT applications use the DefaultFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable, and if their native peers are focusable. The components are traversed in the order in which they were inserted in the container.

  • Swing applications use the LayoutFocusTraversalPolicy. Components are included in the focus traversal if they are visible, displayable, enabled, and focusable. The components are traversed in geometric order: left to right, then top to bottom. However, a container introduces a new “cycle”—its components are traversed first before the successor of the container gains focus.

Note

Note

The “cycle” notion is a bit confusing. After reaching the last element in a child container, the focus does not go back to its first element, but instead to the container’s successor. The API supports true cycles, including keystrokes that move up and down in a cycle hierarchy. However, the standard traversal policy does not use hierarchical cycles. It flattens the cycle hierarchy into a linear (depth-first) traversal.

Note

Note

In JDK 1.3, you could change the default traversal order by calling the setNextFocusableComponent method of the JComponent class. That method is now deprecated. To change the traversal order, try grouping related components into panels so that they form cycles. If that doesn’t work, you have to either install a comparator that sorts the components differently or completely replace the traversal policy. Neither operation seems intended for the faint of heart—see the Sun API documentation for details.

Dialog Boxes

So far, all our user interface components have appeared inside a frame window that was created in the application. This is the most common situation if you write applets that run inside a web browser. But if you write applications, you usually want separate dialog boxes to pop up to give information to or get information from the user.

Just as with most windowing systems, AWT distinguishes between modal and modeless dialog boxes. A modal dialog box won’t let users interact with the remaining windows of the application until he or she deals with it. You use a modal dialog box when you need information from the user before you can proceed with execution. For example, when the user wants to read a file, a modal file dialog box is the one to pop up. The user must specify a file name before the program can begin the read operation. Only when the user closes the (modal) dialog box can the application proceed.

A modeless dialog box lets the user enter information in both the dialog box and the remainder of the application. One example of a modeless dialog is a toolbar. The toolbar can stay in place as long as needed, and the user can interact with both the application window and the toolbar as needed.

We start this section with the simplest dialogs—modal dialogs with just a single message. Swing has a convenient JOptionPane class that lets you put up a simple dialog without writing any special dialog box code. Next, you see how to write more complex dialogs by implementing your own dialog windows. Finally, you see how to transfer data from your application into a dialog and back.

We conclude this section by looking at two standard dialogs: file dialogs and color dialogs. File dialogs are complex, and you definitely want to be familiar with the Swing JFileChooser for this purpose—it would be a real challenge to write your own. The JColorChooser dialog is useful when you want users to pick colors.

Option Dialogs

Swing has a set of ready-made simple dialogs that suffice when you need to ask the user for a single piece of information. The JOptionPane has four static methods to show these simple dialogs:

showMessageDialog

Show a message and wait for the user to click OK

showConfirmDialog

Show a message and get a confirmation (like OK/Cancel)

showOptionDialog

Show a message and get a user option from a set of options

showInputDialog

Show a message and get one line of user input

Figure 9–46 shows a typical dialog. As you can see, the dialog has the following components:

  • An icon

  • A message

  • One or more option buttons

An option dialog

Figure 9–46. An option dialog

The input dialog has an additional component for user input. This can be a text field into which the user can type an arbitrary string, or a combo box from which the user can select one item.

The exact layout of these dialogs, and the choice of icons for standard message types, depend on the pluggable look and feel.

The icon on the left side depends on one of five message types:

ERROR_MESSAGE
INFORMATION_MESSAGE
WARNING_MESSAGE
QUESTION_MESSAGE
PLAIN_MESSAGE

The PLAIN_MESSAGE type has no icon. Each dialog type also has a method that lets you supply your own icon instead.

For each dialog type, you can specify a message. This message can be a string, an icon, a user interface component, or any other object. Here is how the message object is displayed:

String:

Draw the string

Icon:

Show the icon

Component:

Show the component

Object[]:

Show all objects in the array, stacked on top of each other

any other object:

Apply toString and show the resulting string

You can see these options by running the program in Example 9–18.

Of course, supplying a message string is by far the most common case. Supplying a Component gives you ultimate flexibility because you can make the paintComponent method draw anything you want.

The buttons on the bottom depend on the dialog type and the option type. When calling showMessageDialog and showInputDialog, you get only a standard set of buttons (OK and OK/Cancel, respectively). When calling showConfirmDialog, you can choose among four option types:

DEFAULT_OPTION
YES_NO_OPTION
YES_NO_CANCEL_OPTION
OK_CANCEL_OPTION

With the showOptionDialog you can specify an arbitrary set of options. You supply an array of objects for the options. Each array element is rendered as follows:

String:

Make a button with the string as label

Icon:

Make a button with the icon as label

Component:

Show the component

any other object:

Apply toString and make a button with the resulting string as label

The return values of these functions are as follows:

showMessageDialog

None

showConfirmDialog

An integer representing the chosen option

showOptionDialog

An integer representing the chosen option

showInputDialog

The string that the user supplied or selected

The showConfirmDialog and showOptionDialog return integers to indicate which button the user chose. For the option dialog, this is simply the index of the chosen option or the value CLOSED_OPTION if the user closed the dialog instead of choosing an option. For the confirmation dialog, the return value can be one of the following:

OK_OPTION
CANCEL_OPTION
YES_OPTION
NO_OPTION
CLOSED_OPTION

This all sounds like a bewildering set of choices, but in practice it is simple:

  1. Choose the dialog type (message, confirmation, option, or input).

  2. Choose the icon (error, information, warning, question, none, or custom).

  3. Choose the message (string, icon, custom component, or a stack of them).

  4. For a confirmation dialog, choose the option type (default, Yes/No, Yes/No/Cancel, or OK/Cancel).

  5. For an option dialog, choose the options (strings, icons, or custom components) and the default option.

  6. For an input dialog, choose between a text field and a combo box.

  7. Locate the appropriate method to call in the JOptionPane API.

For example, suppose you want to show the dialog in Figure 9–46. The dialog shows a message and asks the user to confirm or cancel. Thus, it is a confirmation dialog. The icon is a warning icon. The message is a string. The option type is OK_CANCEL_OPTION. Here is the call you would make:

int selection = JOptionPane.showConfirmDialog(parent,
   "Message", "Title",
   JOptionPane.OK_CANCEL_OPTION,
   JOptionPane.WARNING_MESSAGE);
if (selection == JOptionPane.OK_OPTION) . . .

Tip

Tip

The message string can contain newline (' ') characters. Such a string is displayed in multiple lines.

The program in Example 9–18 lets you make the selections shown in Figure 9–47. It then shows you the resulting dialog.

Example 9–18. OptionDialogTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.awt.geom.*;
  4. import java.util.*;
  5. import javax.swing.*;
  6. import javax.swing.border.*;
  7.
  8. public class OptionDialogTest
  9. {
 10.    public static void main(String[] args)
 11.    {
 12.       OptionDialogFrame frame = new OptionDialogFrame();
 13.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 14.       frame.setVisible(true);
 15.    }
 16. }
 17.
 18. /**
 19.     A panel with radio buttons inside a titled border.
 20. */
 21. class ButtonPanel extends JPanel
 22. {
 23.    /**
 24.       Constructs a button panel.
 25.       @param title the title shown in the border
 26.       @param options an array of radio button labels
 27.    */
 28.    public ButtonPanel(String title, String[] options)
 29.    {
 30.       setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),
OptionDialogTest.java title));
 31.       setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
 32.       group = new ButtonGroup();
 33.
 34.       // make one radio button for each option
 35.       for (int i = 0; i < options.length; i++)
 36.       {
 37.          JRadioButton b = new JRadioButton(options[i]);
 38.          b.setActionCommand(options[i]);
 39.          add(b);
 40.          group.add(b);
 41.          b.setSelected(i == 0);
 42.       }
 43.    }
 44.
 45.    /**
 46.       Gets the currently selected option.
 47.       @return the label of the currently selected radio button.
 48.    */
 49.    public String getSelection()
 50.    {
 51.       return group.getSelection().getActionCommand();
 52.    }
 53.
 54.    private ButtonGroup group;
 55. }
 56.
 57. /**
 58.    A frame that contains settings for selecting various option
 59.    dialogs.
 60. */
 61. class OptionDialogFrame extends JFrame
 62. {
 63.    public OptionDialogFrame()
 64.    {
 65.       setTitle("OptionDialogTest");
 66.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 67.
 68.       JPanel gridPanel = new JPanel();
 69.       gridPanel.setLayout(new GridLayout(2, 3));
 70.
 71.       typePanel = new ButtonPanel("Type",
 72.          new String[]
 73.          {
 74.             "Message",
 75.             "Confirm",
 76.             "Option",
 77.             "Input"
 78.          });
 79.
 80.       messageTypePanel = new ButtonPanel("Message Type",
 81.          new String[]
 82.          {
 83.             "ERROR_MESSAGE",
 84.             "INFORMATION_MESSAGE",
 85.             "WARNING_MESSAGE",
 86.             "QUESTION_MESSAGE",
 87.             "PLAIN_MESSAGE"
 88.          });
 89.
 90.       messagePanel = new ButtonPanel("Message",
 91.          new String[]
 92.          {
 93.             "String",
 94.             "Icon",
 95.             "Component",
 96.             "Other",
 97.             "Object[]"
 98.          });
 99.
100.       optionTypePanel = new ButtonPanel("Confirm",
101.          new String[]
102.          {
103.             "DEFAULT_OPTION",
104.             "YES_NO_OPTION",
105.             "YES_NO_CANCEL_OPTION",
106.             "OK_CANCEL_OPTION"
107.          });
108.
109.       optionsPanel = new ButtonPanel("Option",
110.          new String[]
111.          {
112.             "String[]",
113.             "Icon[]",
114.             "Object[]"
115.          });
116.
117.       inputPanel = new ButtonPanel("Input",
118.          new String[]
119.          {
120.             "Text field",
121.             "Combo box"
122.          });
123.
124.       gridPanel.add(typePanel);
125.       gridPanel.add(messageTypePanel);
126.       gridPanel.add(messagePanel);
127.       gridPanel.add(optionTypePanel);
128.       gridPanel.add(optionsPanel);
129.       gridPanel.add(inputPanel);
130.
131.       // add a panel with a Show button
132.
133.       JPanel showPanel = new JPanel();
134.       JButton showButton = new JButton("Show");
135.       showButton.addActionListener(new ShowAction());
136.       showPanel.add(showButton);
137.
138.       add(gridPanel, BorderLayout.CENTER);
139.       add(showPanel, BorderLayout.SOUTH);
140.    }
141.
142.    /**
143.       Gets the currently selected message.
144.       @return a string, icon, component, or object array,
145.       depending on the Message panel selection
146.    */
147.    public Object getMessage()
148.    {
149.       String s = messagePanel.getSelection();
150.       if (s.equals("String"))
151.          return messageString;
152.       else if (s.equals("Icon"))
153.          return messageIcon;
154.       else if (s.equals("Component"))
155.          return messageComponent;
156.       else if (s.equals("Object[]"))
157.          return new Object[]
158.          {
159.             messageString,
160.             messageIcon,
161.             messageComponent,
162.             messageObject
163.          };
164.       else if (s.equals("Other"))
165.          return messageObject;
166.       else return null;
167.    }
168.
169.    /**
170.       Gets the currently selected options.
171.       @return an array of strings, icons, or objects, depending
172.       on the Option panel selection
173.    */
174.    public Object[] getOptions()
175.    {
176.       String s = optionsPanel.getSelection();
177.       if (s.equals("String[]"))
178.          return new String[] { "Yellow", "Blue", "Red" };
179.       else if (s.equals("Icon[]"))
180.          return new Icon[]
181.          {
182.             new ImageIcon("yellow-ball.gif"),
183.             new ImageIcon("blue-ball.gif"),
184.             new ImageIcon("red-ball.gif")
185.          };
186.       else if (s.equals("Object[]"))
187.          return new Object[]
188.          {
189.             messageString,
190.             messageIcon,
191.             messageComponent,
192.             messageObject
193.          };
194.       else
195.          return null;
196.    }
197.
198.    /**
199.       Gets the selected message or option type
200.       @param panel the Message Type or Confirm panel
201.       @return the selected XXX_MESSAGE or XXX_OPTION constant
202.       from the JOptionPane class
203.    */
204.    public int getType(ButtonPanel panel)
205.    {
206.       String s = panel.getSelection();
207.       try
208.       {
209.          return JOptionPane.class.getField(s).getInt(null);
210.       }
211.       catch(Exception e)
212.       {
213.          return -1;
214.       }
215.    }
216.
217.    /**
218.       The action listener for the Show button shows a
219.       Confirm, Input, Message, or Option dialog depending
220.       on the Type panel selection.
221.    */
222.    private class ShowAction implements ActionListener
223.    {
224.       public void actionPerformed(ActionEvent event)
225.       {
226.          if (typePanel.getSelection().equals("Confirm"))
227.             JOptionPane.showConfirmDialog(
228.                OptionDialogFrame.this,
229.                getMessage(),
230.                "Title",
231.                getType(optionTypePanel),
232.                getType(messageTypePanel));
233.          else if (typePanel.getSelection().equals("Input"))
234.          {
235.             if (inputPanel.getSelection().equals("Text field"))
236.                JOptionPane.showInputDialog(
237.                   OptionDialogFrame.this,
238.                   getMessage(),
239.                   "Title",
240.                   getType(messageTypePanel));
241.             else
242.                JOptionPane.showInputDialog(
243.                   OptionDialogFrame.this,
244.                   getMessage(),
245.                   "Title",
246.                   getType(messageTypePanel),
247.                   null,
248.                   new String[] { "Yellow", "Blue", "Red" },
249.                   "Blue");
250.          }
251.          else if (typePanel.getSelection().equals("Message"))
252.             JOptionPane.showMessageDialog(
253.                OptionDialogFrame.this,
254.                getMessage(),
255.                "Title",
256.                getType(messageTypePanel));
257.          else if (typePanel.getSelection().equals("Option"))
258.             JOptionPane.showOptionDialog(
259.                OptionDialogFrame.this,
260.                getMessage(),
261.                "Title",
262.                getType(optionTypePanel),
263.                getType(messageTypePanel),
264.                null,
265.                getOptions(),
266.                getOptions()[0]);
267.       }
268.    }
269.
270.    public static final int DEFAULT_WIDTH = 600;
271.    public static final int DEFAULT_HEIGHT = 400;
272.
273.    private ButtonPanel typePanel;
274.    private ButtonPanel messagePanel;
275.    private ButtonPanel messageTypePanel;
276.    private ButtonPanel optionTypePanel;
277.    private ButtonPanel optionsPanel;
278.    private ButtonPanel inputPanel;
279.
280.    private String messageString = "Message";
281.    private Icon messageIcon = new ImageIcon("blue-ball.gif");
282.    private Object messageObject = new Date();
283.    private Component messageComponent = new SamplePanel();
284. }
285.
286. /**
287.    A panel with a painted surface
288. */
289.
290. class SamplePanel extends JPanel
291. {
292.    public void paintComponent(Graphics g)
293.    {
294.       super.paintComponent(g);
295.       Graphics2D g2 = (Graphics2D) g;
296.       Rectangle2D rect = new Rectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1);
297.       g2.setPaint(Color.YELLOW);
298.       g2.fill(rect);
299.       g2.setPaint(Color.BLUE);
300.       g2.draw(rect);
301.    }
302.
303.    public Dimension getMinimumSize()
304.    {
305.       return new Dimension(10, 10);
306.    }
307. }
OptionDialogTest.java
javax.swing.JOptionPane 1.2
  • static void showMessageDialog(Component parent, Object message, String title, int messageType, Icon icon)

  • static void showMessageDialog(Component parent, Object message, String title, int messageType)

  • static void showMessageDialog(Component parent, Object message)

  • static void showInternalMessageDialog(Component parent, Object message, String title, int messageType, Icon icon)

  • static void showInternalMessageDialog(Component parent, Object message, String title, int messageType)

  • static void showInternalMessageDialog(Component parent, Object message)

    show a message dialog or an internal message dialog. (An internal dialog is rendered entirely within its owner frame.)

    Parameters:

    parent

    The parent component (can be null)

     

    message

    The message to show on the dialog (can be a string, icon, component, or an array of them)

     

    title

    The string in the title bar of the dialog

     

    messageType

    One of ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE

     

    icon

    An icon to show instead of one of the standard icons

  • static int showConfirmDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon)

  • static int showConfirmDialog(Component parent, Object message, String title, int optionType, int messageType)

  • static int showConfirmDialog(Component parent, Object message, String title, int optionType)

  • static int showConfirmDialog(Component parent, Object message)

  • static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon)

  • static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType, int messageType)

  • static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType)

  • static int showInternalConfirmDialog(Component parent, Object message)

    show a confirmation dialog or an internal confirmation dialog. (An internal dialog is rendered entirely within its owner frame.) Returns the option selected by the user (one of OK_OPTION, CANCEL_OPTION, YES_OPTION, NO_OPTION), or CLOSED_OPTION if the user closed the dialog.

    Parameters:

    parent

    The parent component (can be null)

     

    message

    The message to show on the dialog (can be a string, icon, component, or an array of them)

     

    title

    The string in the title bar of the dialog

     

    messageType

    One of ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE

     

    optionType

    One of DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION

     

    icon

    An icon to show instead of one of the standard icons

  • static int showOptionDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object default)

  • static int showInternalOptionDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object default)

    show an option dialog or an internal option dialog. (An internal dialog is rendered entirely within its owner frame.) Returns the index of the option selected by the user, or CLOSED_OPTION if the user canceled the dialog.

    Parameters:

    parent

    The parent component (can be null)

     

    message

    The message to show on the dialog (can be a string, icon, component, or an array of them)

     

    title

    The string in the title bar of the dialog

     

    messageType

    One of ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE

     

    optionType

    One of DEFAULT_OPTION, YES_NO_OPTION, YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION

     

    icon

    An icon to show instead of one of the standard icons

     

    options

    An array of options (can be strings, icons, or components)

     

    default

    The default option to present to the user

  • static Object showInputDialog(Component parent, Object message, String title, int messageType, Icon icon, Object[] values, Object default)

  • static String showInputDialog(Component parent, Object message, String title, int messageType)

  • static String showInputDialog(Component parent, Object message)

  • static String showInputDialog(Object message)

  • static String showInputDialog(Component parent, Object message, Object default) 1.4

  • static String showInputDialog(Object message, Object default) 1.4

  • static Object showInternalInputDialog(Component parent, Object message, String title, int messageType, Icon icon, Object[] values, Object default)

  • static String showInternalInputDialog(Component parent, Object message, String title, int messageType)

  • static String showInternalInputDialog(Component parent, Object message)

    show an input dialog or an internal input dialog. (An internal dialog is rendered entirely within its owner frame.) Returns the input string typed by the user, or null if the user canceled the dialog.

    Parameters:

    parent

    The parent component (can be null)

     

    message

    The message to show on the dialog (can be a string, icon, component, or an array of them)

     

    title

    The string in the title bar of the dialog

     

    messageType

    One of ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE

     

    icon

    An icon to show instead of one of the standard icons

     

    values

    An array of values to show in a combo box

     

    default

    The default value to present to the user

The OptionDialogTest program

Figure 9–47. The OptionDialogTest program

Creating Dialogs

In the last section, you saw how to use the JOptionPane class to show a simple dialog. In this section, you see how to create such a dialog by hand.

Figure 9–48 shows a typical modal dialog box, a program information box that is displayed when the user clicks the About button.

An About dialog box

Figure 9–48. An About dialog box

To implement a dialog box, you derive a class from JDialog. This is essentially the same process as deriving the main window for an application from JFrame. More precisely:

  1. In the constructor of your dialog box, call the constructor of the superclass JDialog. You will need to tell it the owner frame (the frame window over which the dialog pops up), the title of the dialog frame, and a Boolean flag to indicate if the dialog box is modal or modeless.

    You should supply the owner frame so that the dialog can be displayed on top of its owner. Windowing systems typically require that every pop-up window is owned by another frame. You can also supply a null owner, but that is a bit risky—the dialog might be hidden behind other windows. (Dialogs with a null owner are actually owned by a shared hidden frame.)

  2. Add the user interface components of the dialog box.

  3. Add the event handlers.

  4. Set the size for the dialog box.

Here’s an example dialog box:

public AboutDialog extends JDialog
{
   public AboutDialog(JFrame owner)
   {
      super(owner, "About DialogTest", true);
      add(new JLabel(
         "<html><h1><i>Core Java</i></h1><hr>By Cay Horstmann and Gary Cornell</html>"),
         BorderLayout.CENTER);

      JPanel panel = new JPanel();
      JButton ok = new JButton("Ok");

      ok.addActionListener(new
         ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               setVisible(false);
            }
         });

      panel.add(ok);
      add(panel, BorderLayout.SOUTH);

      setSize(250, 150);
   }
}

As you can see, the constructor adds user interface elements: in this case, labels and a button. It adds a handler to the button and sets the size of the dialog.

To display the dialog box, you create a new dialog object and make it visible:

JDialog dialog = new AboutDialog(this);
dialog.setVisible(true);

Actually, in the sample code below, we create the dialog box only once, and we can reuse it whenever the user clicks the About button.

if (dialog == null) // first time
   dialog = new AboutDialog(this);
dialog.setVisible(true);

When the user clicks the Ok button, the dialog box should close. This is handled in the event handler of the Ok button:

ok.addActionListener(new
   ActionListener()
   {
      public void actionPerformed(ActionEvent event)
      {
         setVisible(false);
      }
   });

When the user closes the dialog by clicking on the Close box, then the dialog is also hidden. Just as with a JFrame, you can override this behavior with the setDefaultCloseOperation method.

Example 9–19 is the code for the About dialog box test program.

Example 9–19. DialogTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4.
  5. public class DialogTest
  6. {
  7.    public static void main(String[] args)
  8.    {
  9.       DialogFrame frame = new DialogFrame();
 10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 11.       frame.setVisible(true);
 12.    }
 13. }
 14.
 15. /**
 16.    A frame with a menu whose File->About action shows a dialog.
 17. */
 18. class DialogFrame extends JFrame
 19. {
 20.    public DialogFrame()
 21.    {
 22.       setTitle("DialogTest");
 23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 24.
 25.       // construct a File menu
 26.
 27.       JMenuBar menuBar = new JMenuBar();
 28.       setJMenuBar(menuBar);
 29.       JMenu fileMenu = new JMenu("File");
 30.       menuBar.add(fileMenu);
 31.
 32.       // add About and Exit menu items
 33.
 34.       // The About item shows the About dialog
 35.
 36.       JMenuItem aboutItem = new JMenuItem("About");
 37.       aboutItem.addActionListener(new
 38.          ActionListener()
 39.          {
 40.             public void actionPerformed(ActionEvent event)
 41.             {
 42.                if (dialog == null) // first time
 43.                   dialog = new AboutDialog(DialogFrame.this);
 44.                dialog.setVisible(true); // pop up dialog
 45.             }
 46.          });
 47.       fileMenu.add(aboutItem);
 48.
 49.       // The Exit item exits the program
 50.
 51.       JMenuItem exitItem = new JMenuItem("Exit");
 52.       exitItem.addActionListener(new
 53.          ActionListener()
 54.          {
 55.             public void actionPerformed(ActionEvent event)
 56.             {
 57.                System.exit(0);
 58.             }
 59.          });
 60.       fileMenu.add(exitItem);
 61.    }
 62.
 63.    public static final int DEFAULT_WIDTH = 300;
 64.    public static final int DEFAULT_HEIGHT = 200;
 65.
 66.    private AboutDialog dialog;
 67. }
 68.
 69. /**
 70.    A sample modal dialog that displays a message and
 71.    waits for the user to click the Ok button.
 72. */
 73. class AboutDialog extends JDialog
 74. {
 75.    public AboutDialog(JFrame owner)
 76.    {
 77.       super(owner, "About DialogTest", true);
 78.
 79.       // add HTML label to center
 80.
 81.       add(new JLabel(
 82.          "<html><h1><i>Core Java</i></h1><hr>By Cay Horstmann and Gary Cornell</html>"),
 83.          BorderLayout.CENTER);
 84.
 85.       // Ok button closes the dialog
 86.
 87.       JButton ok = new JButton("Ok");
 88.       ok.addActionListener(new
 89.          ActionListener()
 90.          {
 91.             public void actionPerformed(ActionEvent event)
 92.             {
 93.                setVisible(false);
 94.             }
 95.          });
 96.
 97.       // add Ok button to southern border
 98.
 99.       JPanel panel = new JPanel();
100.       panel.add(ok);
101.       add(panel, BorderLayout.SOUTH);
102.
103.       setSize(250, 150);
104.    }
105. }
DialogTest.java
javax.swing.JDialog 1.2
  • public JDialog(Frame parent, String title, boolean modal)

    constructs a dialog. The dialog is not visible until it is explicitly shown.

    Parameters:

    parent

    The frame that is the owner of the dialog

     

    title

    The title of the dialog

     

    modal

    True for modal dialogs (a modal dialog blocks input to other windows)

Data Exchange

The most common reason to put up a dialog box is to get information from the user. You have already seen how easy it is to make a dialog box object: give it initial data and then call setVisible(true) to display the dialog box on the screen. Now let us see how to transfer data in and out of a dialog box.

Consider the dialog box in Figure 9–49 that could be used to obtain a user name and a password to connect to some on-line service.

Password dialog boxpasswordsdialog box input

Figure 9–49. Password dialog box

Your dialog box should provide methods to set default data. For example, the PasswordChooser class of the example program has a method, setUser, to place default values into the next fields:

public void setUser(User u)
{
   username.setText(u.getName());
}

Once you set the defaults (if desired), you show the dialog by calling setVisible(true). The dialog is now displayed.

The user then fills in the information and clicks the Ok or Cancel button. The event handlers for both buttons call setVisible(false), which terminates the call to setVisible(true). Alternatively, the user may close the dialog. If you did not install a window listener for the dialog, then the default window closing operation applies: the dialog becomes invisible, which also terminates the call to setVisible(true).

The important issue is that the call to setVisible(true) blocks until the user has dismissed the dialog. This makes it easy to implement modal dialogs.

You want to know whether the user has accepted or canceled the dialog. Our sample code sets the ok flag to false before showing the dialog. Only the event handler for the Ok button sets the ok flag to true. In that case, you can retrieve the user input from the dialog.

Note

Note

Transferring data out of a modeless dialog is not as simple. When a modeless dialog is displayed, the call to setVisible(true) does not block and the program continues running while the dialog is displayed. If the user selects items on a modeless dialog and then clicks Ok, the dialog needs to send an event to some listener in the program.

The example program contains another useful improvement. When you construct a JDialog object, you need to specify the owner frame. However, quite often you want to show the same dialog with different owner frames. It is better to pick the owner frame when you are ready to show the dialog, not when you construct the PasswordChooser object.

The trick is to have the PasswordChooser extend JPanel instead of JDialog. Build a JDialog object on the fly in the showDialog method:

public boolean showDialog(Frame owner, String title)
{
   ok = false;

   if (dialog == null || dialog.getOwner() != owner)
   {
      dialog = new JDialog(owner, true);
      dialog.add(this);
      dialog.pack();
   }

   dialog.setTitle(title);
   dialog.setVisible(true);
   return ok;
}

Note that it is safe to have owner equal to null.

You can do even better. Sometimes, the owner frame isn’t readily available. It is easy enough to compute it from any parent component, like this:

Frame owner;
if (parent instanceof Frame)
   owner = (Frame) parent;
else
   owner = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, parent);

We use this enhancement in our sample program. The JOptionPane class also uses this mechanism.

Many dialogs have a default button, which is automatically selected if the user presses a trigger key (ENTER in most “look and feel” implementations). The default button is specially marked, often with a thick outline.

You set the default button in the root pane of the dialog:

dialog.getRootPane().setDefaultButton(okButton);

If you follow our suggestion of laying out the dialog in a panel, then you must be careful to set the default button only after you wrapped the panel into a dialog. The panel itself has no root pane.

Example 9–20 is the complete code that illustrates the data flow into and out of a dialog box.

Example 9–20. DataExchangeTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4.
  5. public class DataExchangeTest
  6. {
  7.    public static void main(String[] args)
  8.    {
  9.       DataExchangeFrame frame = new DataExchangeFrame();
 10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 11.       frame.setVisible(true);
 12.    }
 13. }
 14.
 15. /**
 16.    A frame with a menu whose File->Connect action shows a
 17.    password dialog.
 18. */
 19. class DataExchangeFrame extends JFrame
 20. {
 21.    public DataExchangeFrame()
 22.    {
 23.       setTitle("DataExchangeTest");
 24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 25.
 26.       // construct a File menu
 27.
 28.       JMenuBar mbar = new JMenuBar();
 29.       setJMenuBar(mbar);
 30.       JMenu fileMenu = new JMenu("File");
 31.       mbar.add(fileMenu);
 32.
 33.       // add Connect and Exit menu items
 34.
 35.       JMenuItem connectItem = new JMenuItem("Connect");
 36.       connectItem.addActionListener(new ConnectAction());
 37.       fileMenu.add(connectItem);
 38.
 39.       // The Exit item exits the program
 40.
 41.       JMenuItem exitItem = new JMenuItem("Exit");
 42.       exitItem.addActionListener(new
 43.          ActionListener()
 44.          {
 45.             public void actionPerformed(ActionEvent event)
 46.             {
 47.                System.exit(0);
 48.             }
 49.          });
 50.       fileMenu.add(exitItem);
 51.
 52.       textArea = new JTextArea();
 53.       add(new JScrollPane(textArea), BorderLayout.CENTER);
 54.    }
 55.
 56.    public static final int DEFAULT_WIDTH = 300;
 57.    public static final int DEFAULT_HEIGHT = 200;
 58.
 59.    private PasswordChooser dialog = null;
 60.    private JTextArea textArea;
 61.
 62.    /**
 63.       The Connect action pops up the password dialog.
 64.    */
 65.
 66.    private class ConnectAction implements ActionListener
 67.    {
 68.       public void actionPerformed(ActionEvent event)
 69.       {
 70.          // if first time, construct dialog
 71.
 72.          if (dialog == null)
 73.             dialog = new PasswordChooser();
 74.
 75.          // set default values
 76.          dialog.setUser(new User("yourname", null));
 77.
 78.          // pop up dialog
 79.          if (dialog.showDialog(DataExchangeFrame.this, "Connect"))
 80.          {
 81.             // if accepted, retrieve user input
 82.             User u = dialog.getUser();
 83.             textArea.append(
 84.                "user name = " + u.getName()
 85.                + ", password = " + (new String(u.getPassword()))
 86.                + "
");
 87.          }
 88.       }
 89.    }
 90. }
 91.
 92. /**
 93.    A password chooser that is shown inside a dialog
 94. */
 95. class PasswordChooser extends JPanel
 96. {
 97.    public PasswordChooser()
 98.    {
 99.       setLayout(new BorderLayout());
100.
101.       // construct a panel with user name and password fields
102.
103.       JPanel panel = new JPanel();
104.       panel.setLayout(new GridLayout(2, 2));
105.       panel.add(new JLabel("User name:"));
106.       panel.add(username = new JTextField(""));
107.       panel.add(new JLabel("Password:"));
108.       panel.add(password = new JPasswordField(""));
109.       add(panel, BorderLayout.CENTER);
110.
111.       // create Ok and Cancel buttons that terminate the dialog
112.
113.       okButton = new JButton("Ok");
114.       okButton.addActionListener(new
115.          ActionListener()
116.          {
117.             public void actionPerformed(ActionEvent event)
118.             {
119.                ok = true;
120.                dialog.setVisible(false);
121.             }
122.          });
123.
124.       JButton cancelButton = new JButton("Cancel");
125.       cancelButton.addActionListener(new
126.          ActionListener()
127.          {
128.             public void actionPerformed(ActionEvent event)
129.             {
130.                dialog.setVisible(false);
131.             }
132.          });
133.
134.       // add buttons to southern border
135.
136.       JPanel buttonPanel = new JPanel();
137.       buttonPanel.add(okButton);
138.       buttonPanel.add(cancelButton);
139.       add(buttonPanel, BorderLayout.SOUTH);
140.    }
141.
142.    /**
143.       Sets the dialog defaults.
144.       @param u the default user information
145.    */
146.    public void setUser(User u)
147.    {
148.       username.setText(u.getName());
149.    }
150.
151.    /**
152.       Gets the dialog entries.
153.       @return a User object whose state represents
154.       the dialog entries
155.    */
156.    public User getUser()
157.    {
158.       return new User(username.getText(), password.getPassword());
159.    }
160.
161.    /**
162.       Show the chooser panel in a dialog
163.       @param parent a component in the owner frame or null
164.       @param title the dialog window title
165.    */
166.    public boolean showDialog(Component parent, String title)
167.    {
168.       ok = false;
169.
170.       // locate the owner frame
171.
172.       Frame owner = null;
173.       if (parent instanceof Frame)
174.          owner = (Frame) parent;
175.       else
176.          owner = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, parent);
177.
178.       // if first time, or if owner has changed, make new dialog
179.
180.       if (dialog == null || dialog.getOwner() != owner)
181.       {
182.          dialog = new JDialog(owner, true);
183.          dialog.add(this);
184.          dialog.getRootPane().setDefaultButton(okButton);
185.          dialog.pack();
186.       }
187.
188.       // set title and show dialog
189.
190.       dialog.setTitle(title);
191.       dialog.setVisible(true);
192.       return ok;
193.    }
194.
195.    private JTextField username;
196.    private JPasswordField password;
197.    private JButton okButton;
198.    private boolean ok;
199.    private JDialog dialog;
200. }
201.
202. /**
203.    A user has a name and password. For security reasons, the
204.    password is stored as a char[], not a String.
205. */
206. class User
207. {
208.    public User(String aName, char[] aPassword)
209.    {
210.       name = aName;
211.       password = aPassword;
212.    }
213.
214.    public String getName() { return name; }
215.    public char[] getPassword() { return password; }
216.
217.    public void setName(String aName) { name = aName; }
218.    public void setPassword(char[] aPassword) { password = aPassword; }
219.
220.    private String name;
221.    private char[] password;
222. }
DataExchangeTest.java
javax.swing.SwingUtilities 1.2
  • Container getAncestorOfClass(Class c, Component comp)

    returns the innermost parent container of the given component that belongs to the given class or one of its subclasses.

DataExchangeTest.java
javax.swing.JComponent 1.2
  • JRootPane getRootPane()

    gets the root pane enclosing this component, or null if this component does not have an ancestor with a root pane.

DataExchangeTest.java
javax.swing.JRootPane 1.2
  • void setDefaultButton(JButton button)

    sets the default button for this root pane. To deactivate the default button, call this method with a null parameter.

DataExchangeTest.java
javax.swing.JButton 1.2
  • boolean isDefaultButton()

    returns true if this button is the default button of its root pane.

File Dialogs

When you write an application, you often want to be able to open and save files. A good file dialog box that shows files and directories and lets the user navigate the file system is hard to write, and you definitely don’t want to reinvent that wheel. Fortunately, Swing provides a JFileChooser class that allows you to display a file dialog box similar to the one that most native applications use. JFileChooser dialogs are always modal. Note that the JFileChooser class is not a subclass of JDialog. Instead of calling setVisible(true), you call showOpenDialog to display a dialog for opening a file or you call showSaveDialog to display a dialog for saving a file. The button for accepting a file is then automatically labeled Open or Save. You can also supply your own button label with the showDialog method. Figure 9–50 shows an example of the file chooser dialog box.

File chooser dialog box

Figure 9–50. File chooser dialog box

Here are the steps needed to put up a file dialog box and recover what the user chooses from the box.

  1. Make a JFileChooser object. Unlike the constructor for the JDialog class, you do not supply the parent component. This allows you to reuse a file chooser dialog with multiple frames.

    For example:

    JFileChooser chooser = new JFileChooser();
    

    Tip

    Tip

    Reusing a file chooser object is a good idea because the JFileChooser constructor can be quite slow, especially on Windows if the user has many mapped network drives.

  2. Set the directory by calling the setCurrentDirectory method.

    For example, to use the current working directory:

    chooser.setCurrentDirectory(new File("."));
    

    You need to supply a File object. File objects are explained in detail in Chapter 12. All you need to know for now is that the constructor File(String filename) turns a file or directory name into a File object.

  3. If you have a default file name that you expect the user to choose, supply it with the setSelectedFile method:

    chooser.setSelectedFile(new File(filename));
    
  4. To enable the user to select multiple files in the dialog, call the setMultiSelectionEnabled method. This is, of course, entirely optional and not all that common.

    chooser.setMultiSelectionEnabled(true);
    
  5. If you want to restrict the display of files in the dialog to those of a particular type (for example, all files with extension .gif), then you need to set a file filter. We discuss file filters later in this section.

  6. By default, a user can select only files with a file chooser. If you want the user to select directories, use the setFileSelectionMode method. Call it with JFileChooser.FILES_ONLY (the default), JFileChooser.DIRECTORIES_ONLY, or JFileChooser.FILES_AND_DIRECTORIES.

  7. Show the dialog box by calling the showOpenDialog or showSaveDialog method. You must supply the parent component in these calls:

    int result = chooser.showOpenDialog(parent);
    

    or

    int result = chooser.showSaveDialog(parent);
    

    The only difference between these calls is the label of the “approve button,” the button that the user clicks to finish the file selection. You can also call the showDialog method and pass an explicit text for the approve button:

    int result = chooser.showDialog(parent, "Select");
    

    These calls return only when the user has approved, canceled, or dismissed the file dialog. The return value is JFileChooser.APPROVE_OPTION, JFileChooser.CANCEL_OPTION, or JFileChooser.ERROR_OPTION

  8. You get the selected file or files with the getSelectedFile() or getSelectedFiles() method. These methods return either a single File object or an array of File objects. If you just need the name of the file object, call its getPath method. For example,

    String filename = chooser.getSelectedFile().getPath();
    

For the most part, these steps are simple. The major difficulty with using a file dialog is to specify a subset of files from which the user should choose. For example, suppose the user should choose a GIF image file. Then, the file chooser should only display files with extension .gif. It should also give the user some kind of feedback that the displayed files are of a particular category, such as “GIF Images.” But the situation can be more complex. If the user should choose a JPEG image file, then the extension can be either .jpg or .jpeg. Rather than coming up with a mechanism to codify these complexities, the designers of the file chooser supply a more elegant mechanism: to restrict the displayed files, you supply an object that extends the abstract class javax.swing.filechooser.FileFilter. The file chooser passes each file to the file filter and displays only the files that the file filter accepts.

At the time of this writing, only one such subclass is supplied: the default filter that accepts all files. However, it is easy to write ad hoc file filters. You simply implement the two abstract methods of the FileFilter superclass:

public boolean accept(File f);
public String getDescription();

Note

Note

An unrelated FileFilter interface in the java.io package has a single method, boolean accept(File f). It is used in the listFiles method of the File class to list files in a directory. We do not know why the designers of Swing didn’t extend this interface—perhaps the Java class library has now become so complex that even the programmers at Sun are no longer aware of all the standard classes and interfaces.

You will need to resolve the name conflict between these two identically named types if you import both the java.io and the javax.swing.filechooser package. The simplest remedy is to import javax.swing.filechooser.FileFilter, not javax.swing.filechooser.*.

The first method tests whether a file should be accepted. The second method returns a description of the file type that can be displayed in the file chooser dialog. For example, to filter for GIF files, you might use

public class GifFilter extends FileFilter
{
   public boolean accept(File f)
   {
      return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory();
   }

   public String getDescription()
   {
      return "GIF Image";
   }
}

Once you have a file filter object, you use the setFileFilter method of the JFileChooser class to install it into the file chooser object:

chooser.setFileFilter(new GifFilter());

In our sample program, we supply a class ExtensionFileFilter, to be used as follows:

ExtensionFileFilter filter = new ExtensionFileFilter();
filter.addExtension("jpg");
filter.addExtension("gif");
filter.setDescription("Image files");

The implementation of the ExtensionFileFilter is a straightforward generalization of the GifFilter class. You may want to use that class in your own programs.

Note

Note

The JDK contains a similar class, ExampleFileFilter, in the demo/jfc/FileChooserDemo directory.

You can install multiple filters to the file chooser by calling

chooser.addChoosableFileFilter(new GifFilter());
chooser.addChoosableFileFilter(new JpegFilter());
. . .

The user selects a filter from the combo box at the bottom of the file dialog. By default, the “All files” filter is always present in the combo box. This is a good idea, just in case a user of your program needs to select a file with a nonstandard extension. However, if you want to suppress the “All files” filter, call

chooser.setAcceptAllFileFilterUsed(false)

Caution

Caution

If you reuse a single file chooser for loading and saving different file types, call

chooser.resetChoosableFilters()

to clear any old file filters before adding new ones.

Finally, you can customize the file chooser by providing special icons and file descriptions for each file that the file chooser displays. You do this by supplying an object of a class extending the FileView class in the javax.swing.filechooser package. This is definitely an advanced technique. Normally, you don’t need to supply a file view—the pluggable look and feel supplies one for you. But if you want to show different icons for special file types, you can install your own file view. You need to extend the FileView class and implement five methods:

Icon getIcon(File f);
String getName(File f);
String getDescription(File f);
String getTypeDescription(File f);
Boolean isTraversable(File f);

Then you use the setFileView method to install your file view into the file chooser.

The file chooser calls your methods for each file or directory that it wants to display. If your method returns null for the icon, name, or description, the file chooser then consults the default file view of the look and feel. That is good, because it means you need to deal only with the file types for which you want to do something different.

The file chooser calls the isTraversable method to decide whether to open a directory when a user clicks on it. Note that this method returns a Boolean object, not a boolean value! This seems weird, but it is actually convenient—if you aren’t interested in deviating from the default file view, just return null. The file chooser will then consult the default file view. In other words, the method returns a Boolean to let you choose among three options: true (Boolean.TRUE), false (Boolean.FALSE), and don’t care (null).

The example program contains a simple file view class. That class shows a particular icon whenever a file matches a file filter. We use it to display a palette icon for all image files.

class FileIconView extends FileView
{
   public FileIconView(FileFilter aFilter, Icon anIcon)
   {
      filter = aFilter;
      icon = anIcon;
   }

   public Icon getIcon(File f)
   {
      if (!f.isDirectory() && filter.accept(f))
         return icon;
      else return null;
   }

   private FileFilter filter;
   private Icon icon;
}

Caution

Caution

In JDK version 1.2, you must define all five methods of your FileView subclass. Simply return null in the methods that you don’t need. In JDK version 1.3, the FileView methods are no longer abstract.

You install this file view into your file chooser with the setFileView method:

chooser.setFileView(new FileIconView(filter,
   new ImageIcon("palette.gif")));

The file chooser will then show the palette icon next to all files that pass the filter and use the default file view to show all other files. Naturally, we use the same filter that we set in the file chooser.

Tip

Tip

You can find a more useful ExampleFileView class in the demo/jfc/FileChooserDemo directory of the JDK. That class lets you associate icons and descriptions with arbitrary extensions.

Finally, you can customize a file dialog by adding an accessory component. For example, Figure 9–51 shows a preview accessory next to the file list. This accessory displays a thumbnail view of the currently selected file.

A file dialog with a preview accessorypreview in dialog boxesdialog boxespreview capacity

Figure 9–51. A file dialog with a preview accessory

An accessory can be any Swing component. In our case, we extend the JLabel class and set its icon to a scaled copy of the graphics image:

class ImagePreviewer extends JLabel
{
   public ImagePreviewer(JFileChooser chooser)
   {
      setPreferredSize(new Dimension(100, 100));
      setBorder(BorderFactory.createEtchedBorder());
   }

   public void loadImage(File f)
   {
      ImageIcon icon = new ImageIcon(f.getPath());
      if(icon.getIconWidth() > getWidth())
         icon = new ImageIcon(icon.getImage().getScaledInstance(
            getWidth(), -1, Image.SCALE_DEFAULT));
      setIcon(icon);
      repaint();
   }
}

There is just one challenge. We want to update the preview image whenever the user selects a different file. The file chooser uses the “JavaBeans” mechanism of notifying interested listeners whenever one of its properties changes. The selected file is a property that you can monitor by installing a PropertyChangeListener. We discuss this mechanism in greater detail in Volume 2. Here is the code that you need to trap the notifications:

chooser.addPropertyChangeListener(new
   PropertyChangeListener()
   {
      public void propertyChange(PropertyChangeEvent event)
      {
         if (event.getPropertyName() == JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)
         {
            File newFile = (File) event.getNewValue()
             // update the accessory
            . . .
         }
    }
});

In our example program, we add this code to the ImagePreviewer constructor.

Example 9–21 contains a modification of the ImageViewer program from Chapter 2, in which the file chooser has been enhanced by a custom file view and a preview accessory.

Example 9–21. FileChooserTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.awt.image.*;
  4. import java.beans.*;
  5. import java.util.*;
  6. import java.io.*;
  7. import javax.swing.*;
  8. import javax.swing.filechooser.FileFilter;
  9. import javax.swing.filechooser.FileView;
 10.
 11. public class FileChooserTest
 12. {
 13.    public static void main(String[] args)
 14.    {
 15.       ImageViewerFrame frame = new ImageViewerFrame();
 16.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 17.       frame.setVisible(true);
 18.    }
 19. }
 20.
 21. /**
 22.    A frame that has a menu for loading an image and a display
 23.    area for the loaded image.
 24. */
 25. class ImageViewerFrame extends JFrame
 26. {
 27.    public ImageViewerFrame()
 28.    {
 29.       setTitle("FileChooserTest");
 30.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 31.
 32.       // set up menu bar
 33.       JMenuBar menuBar = new JMenuBar();
 34.       setJMenuBar(menuBar);
 35.
 36.       JMenu menu = new JMenu("File");
 37.       menuBar.add(menu);
 38.
 39.       JMenuItem openItem = new JMenuItem("Open");
 40.       menu.add(openItem);
 41.       openItem.addActionListener(new FileOpenListener());
 42.
 43.       JMenuItem exitItem = new JMenuItem("Exit");
 44.       menu.add(exitItem);
 45.       exitItem.addActionListener(new
 46.          ActionListener()
 47.          {
 48.             public void actionPerformed(ActionEvent event)
 49.             {
 50.                System.exit(0);
 51.             }
 52.          });
 53.
 54.       // use a label to display the images
 55.       label = new JLabel();
 56.       add(label);
 57.
 58.       // set up file chooser
 59.       chooser = new JFileChooser();
 60.
 61.       // accept all image files ending with .jpg, .jpeg, .gif
 62.       final ExtensionFileFilter filter = new ExtensionFileFilter();
 63.       filter.addExtension("jpg");
 64.       filter.addExtension("jpeg");
 65.       filter.addExtension("gif");
 66.       filter.setDescription("Image files");
 67.       chooser.setFileFilter(filter);
 68.
 69.       chooser.setAccessory(new ImagePreviewer(chooser));
 70.
 71.       chooser.setFileView(new FileIconView(filter, new ImageIcon("palette.gif")));
 72.    }
 73.
 74.    /**
 75.       This is the listener for the File->Open menu item.
 76.    */
 77.    private class FileOpenListener implements ActionListener
 78.    {
 79.       public void actionPerformed(ActionEvent event)
 80.       {
 81.          chooser.setCurrentDirectory(new File("."));
 82.
 83.          // show file chooser dialog
 84.          int result = chooser.showOpenDialog(ImageViewerFrame.this);
 85.
 86.          // if image file accepted, set it as icon of the label
 87.          if(result == JFileChooser.APPROVE_OPTION)
 88.          {
 89.             String name = chooser.getSelectedFile().getPath();
 90.             label.setIcon(new ImageIcon(name));
 91.          }
 92.       }
 93.    }
 94.
 95.    public static final int DEFAULT_WIDTH = 300;
 96.    public static final int DEFAULT_HEIGHT = 400;
 97.
 98.    private JLabel label;
 99.    private JFileChooser chooser;
100. }
101.
102. /**
103.    This file filter matches all files with a given set of
104.    extensions.
105. */
106. class ExtensionFileFilter extends FileFilter
107. {
108.    /**
109.       Adds an extension that this file filter recognizes.
110.       @param extension a file extension (such as ".txt" or "txt")
111.    */
112.    public void addExtension(String extension)
113.    {
114.       if (!extension.startsWith("."))
115.          extension = "." + extension;
116.       extensions.add(extension.toLowerCase());
117.    }
118.
119.    /**
120.       Sets a description for the file set that this file filter
121.       recognizes.
122.       @param aDescription a description for the file set
123.    */
124.    public void setDescription(String aDescription)
125.    {
126.       description = aDescription;
127.    }
128.
129.    /**
130.       Returns a description for the file set that this file
131.       filter recognizes.
132.       @return a description for the file set
133.    */
134.    public String getDescription()
135.    {
136.       return description;
137.    }
138.
139.    public boolean accept(File f)
140.    {
141.       if (f.isDirectory()) return true;
142.       String name = f.getName().toLowerCase();
143.
144.       // check if the file name ends with any of the extensions
145.       for (String extension : extensions)
146.          if (name.endsWith(extension))
147.             return true;
148.       return false;
149.    }
150.
151.    private String description = "";
152.    private ArrayList<String> extensions = new ArrayList<String>();
153. }
154.
155. /**
156.    A file view that displays an icon for all files that match
157.    a file filter.
158. */
159. class FileIconView extends FileView
160. {
161.    /**
162.        Constructs a FileIconView.
163.        @param aFilter a file filter--all files that this filter
164.        accepts will be shown with the icon.
165.        @param anIcon--the icon shown with all accepted files.
166.    */
167.    public FileIconView(FileFilter aFilter, Icon anIcon)
168.    {
169.       filter = aFilter;
170.       icon = anIcon;
171.    }
172.
173.    public Icon getIcon(File f)
174.    {
175.       if (!f.isDirectory() && filter.accept(f))
176.          return icon;
177.       else return null;
178.    }
179.
180.    private FileFilter filter;
181.    private Icon icon;
182. }
183.
184. /**
185.    A file chooser accessory that previews images.
186. */
187. class ImagePreviewer extends JLabel
188. {
189.    /**
190.       Constructs an ImagePreviewer.
191.       @param chooser the file chooser whose property changes
192.       trigger an image change in this previewer
193.    */
194.    public ImagePreviewer(JFileChooser chooser)
195.    {
196.       setPreferredSize(new Dimension(100, 100));
197.       setBorder(BorderFactory.createEtchedBorder());
198.
199.       chooser.addPropertyChangeListener(new
200.          PropertyChangeListener()
201.          {
202.             public void propertyChange(PropertyChangeEvent
203.                event)
204.             {
205.                if (event.getPropertyName() ==
206.                   JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)
207.                {
208.                   // the user has selected a new file
209.                   File f = (File) event.getNewValue();
210.                   if (f == null) { setIcon(null); return; }
211.
212.                   // read the image into an icon
213.                   ImageIcon icon = new ImageIcon(f.getPath());
214.
215.                   // if the icon is too large to fit, scale it
216.                   if(icon.getIconWidth() > getWidth())
217.                      icon = new ImageIcon(icon.getImage().getScaledInstance(
218.                         getWidth(), -1, Image.SCALE_DEFAULT));
219.
220.                   setIcon(icon);
221.                }
222.          }
223.          });
224.    }
225. }
FileChooserTest.java
javax.swing.JFileChooser 1.2
  • JFileChooser()

    creates a file chooser dialog box that can be used for multiple frames.

  • void setCurrentDirectory(File dir)

    sets the initial directory for the file dialog box.

  • void setSelectedFile(File file)

  • void setSelectedFiles(File[] file)

    set the default file choice for the file dialog box.

  • void setMultiSelectionEnabled(boolean b)

    sets or clears multiple selection mode.

  • void setFileSelectionMode(int mode)

    lets the user select files only (the default), directories only, or both files and directories. The mode parameter is one of JFileChooser.FILES_ONLY, JFileChooser.DIRECTORIES_ONLY, and JFileChooser.FILES_AND_DIRECTORIES.

  • int showOpenDialog(Component parent)

  • int showSaveDialog(Component parent)

  • int showDialog(Component parent, String approveButtonText)

    show a dialog in which the approve button is labeled “Open”, “Save”, or with the approveButtonText string. Returns APPROVE_OPTION, CANCEL_OPTION (if the user selected the cancel button or dismissed the dialog), or ERROR_OPTION (if an error occurred).

  • File getSelectedFile()

  • File[] getSelectedFiles()

    get the file or files that the user selected (or return null if the user didn’t select any file).

  • void setFileFilter(FileFilter filter)

    sets the file mask for the file dialog box. All files for which filter.accept returns true will be displayed. Also adds the filter to the list of choosable filters.

  • void addChoosableFileFilter(FileFilter filter)

    adds a file filter to the list of choosable filters.

  • void setAcceptAllFileFilterUsed(boolean b)

    includes or suppresses an “All files” filter in the filter combo box.

  • void resetChoosableFileFilters()

    clears the list of choosable filters. Only the “All files” filter remains unless it is explicitly suppressed.

  • void setFileView(FileView view)

    sets a file view to provide information about the files that the file chooser displays.

  • void setAccessory(JComponent component)

    sets an accessory component.

FileChooserTest.java
javax.swing.filechooser.FileFilter 1.2
  • boolean accept(File f)

    returns true if the file chooser should display this file.

  • String getDescription()

    returns a description of this file filter, for example, "Image files (*.gif,*.jpeg)".

FileChooserTest.java
javax.swing.filechooser.FileView 1.2
  • String getName(File f)

    returns the name of the file f, or null. Normally, this method simply returns f.getName().

  • String getDescription(File f)

    returns a humanly readable description of the file f, or null. For example, if f is an HTML document, this method might return its title.

  • String getTypeDescription(File f)

    returns a humanly readable description of the type of the file f, or null. For example, if f is an HTML document, this method might return a string "Hypertext document".

  • Icon getIcon(File f)

    returns an icon for the file f, or null. For example, if f is a JPEG file, this method might return a thumbnail icon.

  • Boolean isTraversable(File f)

    returns Boolean.TRUE if f is a directory that the user can open. This method might return false if a directory is conceptually a compound document. Like all FileView methods, this method can return null to signify that the file chooser should consult the default view instead.

Color Choosers

As you saw in the preceding section, a high-quality file chooser is an intricate user interface component that you definitely do not want to implement yourself. Many user interface toolkits provide other common dialogs: to choose a date/time, currency value, font, color, and so on. The benefit is twofold. Programmers can simply use a high-quality implementation rather than rolling their own. And users have a common experience for these selections.

At this point, Swing provides only one additional chooser, the JColorChooser (see Figures 9–52 through 9–54). You use it to let users pick a color value. Like the JFileChooser class, the color chooser is a component, not a dialog, but it contains convenience methods to create dialogs that contain a color chooser component.

The “swatches” pane of a color chooser

Figure 9–52. The “swatches” pane of a color chooser

The RGB pane of a color chooser

Figure 9–54. The RGB pane of a color chooser

Here is how you show a modal dialog with a color chooser:

Color selectedColor = JColorChooser.showDialog(parent,title, initialColor);

Alternatively, you can display a modeless color chooser dialog. You supply

  • A parent component;

  • The title of the dialog;

  • A flag to select either a modal or a modeless dialog;

  • A color chooser; and

  • Listeners for the OK and Cancel buttons (or null if you don’t want a listener).

The HSB pane of a color chooser

Figure 9–53. The HSB pane of a color chooser

Here is how you make a modeless dialog that sets the background color when the user clicks the OK button:

chooser = new JColorChooser();
dialog = JColorChooser.createDialog(
   parent,
   "Background Color",
   false /* not modal */,
   chooser,
   new ActionListener() // OK button listener
      {
         public void actionPerformed(ActionEvent event)
         {
            setBackground(chooser.getColor());
        }
      },
   null /* no Cancel button listener */);

You can do even better than that and give the user immediate feedback of the color selection. To monitor the color selections, you need to obtain the selection model of the chooser and add a change listener:

chooser.getSelectionModel().addChangeListener(new   ChangeListener()   {      public void stateChanged(ChangeEvent event)      {         do something with chooser.getColor();      }   });

In this case, there is no benefit to the OK and Cancel buttons that the color chooser dialog provides. You can just add the color chooser component directly into a modeless dialog:

dialog = new JDialog(parent, false /* not modal */);
dialog.add(chooser);
dialog.pack();

The program in Example 9–22 shows the three types of dialogs. If you click on the Modal button, you must select a color before you can do anything else. If you click on the Modeless button, you get a modeless dialog, but the color change only happens when you click the OK button on the dialog. If you click the Immediate button, you get a modeless dialog without buttons. As soon as you pick a different color in the dialog, the background color of the panel is updated.

This ends our discussion of user interface components. The material in Chapters 7 through 9 showed you how to implement simple GUIs in Swing. Turn to Volume 2 for more advanced Swing components and sophisticated graphics techniques.

Example 9–22. ColorChooserTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import javax.swing.*;
  4. import javax.swing.event.*;
  5.
  6. public class ColorChooserTest
  7. {
  8.    public static void main(String[] args)
  9.    {
 10.       ColorChooserFrame frame = new ColorChooserFrame();
 11.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 12.       frame.setVisible(true);
 13.    }
 14. }
 15.
 16. /**
 17.    A frame with a color chooser panel
 18. */
 19. class ColorChooserFrame extends JFrame
 20. {
 21.    public ColorChooserFrame()
 22.    {
 23.       setTitle("ColorChooserTest");
 24.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 25.
 26.       // add color chooser panel to frame
 27.
 28.       ColorChooserPanel panel = new ColorChooserPanel();
 29.       add(panel);
 30.    }
 31.
 32.    public static final int DEFAULT_WIDTH = 300;
 33.    public static final int DEFAULT_HEIGHT = 200;
 34. }
 35.
 36. /**
 37.    A panel with buttons to pop up three types of color choosers
 38. */
 39. class ColorChooserPanel extends JPanel
 40. {
 41.    public ColorChooserPanel()
 42.    {
 43.       JButton modalButton = new JButton("Modal");
 44.       modalButton.addActionListener(new ModalListener());
 45.       add(modalButton);
 46.
 47.       JButton modelessButton = new JButton("Modeless");
 48.       modelessButton.addActionListener(new ModelessListener());
 49.       add(modelessButton);
 50.
 51.       JButton immediateButton = new JButton("Immediate");
 52.       immediateButton.addActionListener(new ImmediateListener());
 53.       add(immediateButton);
 54.    }
 55.
 56.    /**
 57.       This listener pops up a modal color chooser
 58.    */
 59.    private class ModalListener implements ActionListener
 60.    {
 61.       public void actionPerformed(ActionEvent event)
 62.       {
 63.          Color defaultColor = getBackground();
 64.          Color selected = JColorChooser.showDialog(
 65.             ColorChooserPanel.this,
 66.             "Set background",
 67.             defaultColor);
 68.          if (selected != null) setBackground(selected);
 69.       }
 70.    }
 71.
 72.    /**
 73.       This listener pops up a modeless color chooser.
 74.       The panel color is changed when the user clicks the OK
 75.       button.
 76.    */
 77.    private class ModelessListener implements ActionListener
 78.    {
 79.       public ModelessListener()
 80.       {
 81.          chooser = new JColorChooser();
 82.          dialog = JColorChooser.createDialog(
 83.             ColorChooserPanel.this,
 84.             "Background Color",
 85.             false /* not modal */,
 86.             chooser,
 87.             new ActionListener() // OK button listener
 88.                {
 89.                   public void actionPerformed(ActionEvent event)
 90.                   {
 91.                      setBackground(chooser.getColor());
 92.                   }
 93.                },
 94.             null /* no Cancel button listener */);
 95.       }
 96.
 97.       public void actionPerformed(ActionEvent event)
 98.       {
 99.          chooser.setColor(getBackground());
100.          dialog.setVisible(true);
101.       }
102.
103.       private JDialog dialog;
104.       private JColorChooser chooser;
105.    }
106.
107.    /**
108.       This listener pops up a modeless color chooser.
109.       The panel color is changed immediately when the
110.       user picks a new color.
111.    */
112.    private class ImmediateListener implements ActionListener
113.    {
114.       public ImmediateListener()
115.       {
116.          chooser = new JColorChooser();
117.          chooser.getSelectionModel().addChangeListener(new
118.             ChangeListener()
119.             {
120.                public void stateChanged(ChangeEvent event)
121.                {
122.                   setBackground(chooser.getColor());
123.                }
124.             });
125.
126.          dialog = new JDialog(
127.             (Frame) null,
128.             false /* not modal */);
129.          dialog.add(chooser);
130.          dialog.pack();
131.       }
132.
133.       public void actionPerformed(ActionEvent event)
134.       {
135.          chooser.setColor(getBackground());
136.          dialog.setVisible(true);
137.       }
138.
139.       private JDialog dialog;
140.       private JColorChooser chooser;
141.    }
142. }
ColorChooserTest.java
javax.swing.JColorChooser 1.2
  • JColorChooser()

    constructs a color chooser with an initial color of white.

  • Color getColor()

  • void setColor(Color c)

    get and set the current color of this color chooser.

  • static Color showDialog(Component parent, String title, Color initialColor)

    shows a modal dialog that contains a color chooser.

    Parameters:

    parent

    The component over which to pop up the dialog

     

    title

    The title for the dialog box frame

     

    initialColor

    The initial color to show in the color chooser

  • static JDialog createDialog(Component parent, String title, boolean modal, JColorChooser chooser, ActionListener okListener, ActionListener cancelListener)

    creates a dialog box that contains a color chooser.

    Parameters:

    parent

    The component over which to pop up the dialog

     

    title

    The title for the dialog box frame

     

    modal

    true if this call should block until the dialog is closed

     

    chooser

    The color chooser to add to the dialog

     

    okListener, cancelListener

    The listeners of the OK and Cancel buttons

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset