Search the Catalog
Java Foundation Classes in a Nutshell

Java Foundation Classes in a Nutshell

By David Flanagan
1st Edition September 1999
1-56592-488-6, Order Number: 4886
748 pages, $29.95

Chapter 3 Swing Programming Topics

The last chapter provided an architectural overview of AWT and Swing; it explained how to create a graphical user interface by placing components inside containers, arranging them with layout managers, and handling the events that they generate. This chapter builds on that architectural foundation and introduces many other important features of Swing. Most of the topics discussed herein are independent of one another, so you can think of each section as a short essay on a particular topic, where the sections can be read in any order.

This chapter introduces many of the new components and features of Swing, but it cannot cover them in full detail. For more information on the topics covered herein, see Java Swing, by Robert Eckstein, Marc Loy, and Dave Wood (O'Reilly).

3.1 Versions of Swing

Swing is a core part of the Java 2 platform, so many developers will simply obtain the Swing libraries when they download the Java 2 SDK. Swing is also available as a separate download for use as an extension to Java 1.1. When you download Swing independently of the SDK, you must pay attention to the Swing version number. Swing 1.0.3 is an early version of Swing that was released before Version 1.2 of Java 2. It is now outdated and is not documented in this book. Swing 1.1 is the version of Swing that is being bundled with Java 1.2. You can download a version of it for use with Java 1.1 from http://java.sun.com/products/jfc/.

As this book goes to press, the most recent version of Swing is Swing 1.1.1. This version of Swing is bundled with Java 1.2.2 and is also available for use with Java 1.1 from the web site mentioned in the previous paragraph. Swing 1.1.1 fixes many bugs in the initial release of Swing 1.1 but does not change the Swing 1.1 API in any way. Its use is strongly recommended. Swing 1.1.1 is the last release of Swing that will be available for use with Java 1.1.

Development of Swing continues, and Java 1.3 will ship with a new version that includes a number of minor changes and improvements to the Swing API. This future release will focus on improving the existing APIs and should not add many new APIs.

3.2 Labels and HTML

In the initial releases of Swing 1.1 and Java 1.2, the JLabel, JButton, and related classes that display textual labels can display only a single line of text using a single font. In Swing 1.1.1 and Java 1.2.2, however, components like these can display multiline, multifont text using simple HTML formatting. To display formatted text, simply specify a string of HTML text that begins with an <HTML> tag. You can use this feature to present text using multiple fonts, font styles, and colors. Just as important, however, the introduction of HTML allows you to specify multiline labels.

This new formatted text display feature is available in Java 1.2.2 for the JLabel, JButton, MenuItem, JMenu, JCheckBoxMenuItem, JRadioButtonMenuItem, JTabbedPane, and JToolTip classes. It is not supported (at least in Java 1.2.2) by JCheckBox or JRadioButton, however. Formatted text display is particularly useful with JOptionPane dialog boxes (described later in this chapter), as they display text using internal JLabel objects.

3.3 Actions

A GUI application often allows a user to invoke an operation in a number of different ways. For example, the user may be able to save a file by either selecting an item from a menu or clicking on a button in a toolbar. The resulting operation is exactly the same; it is simply presented to the user through two different interfaces.

Swing defines a simple but powerful javax.swing.Action interface that encapsulates information about such an operation. The Action interface extends the ActionListener interface, so it contains the actionPerformed() method. It is this method that you implement to actually perform the desired action. Each Action object also has an arbitrary set of name/value pairs that provide additional information about the action. The values typically include: a short string of text that names the operation, an image that can be used to represent the action graphically, and a longer string of text suitable for use in a tooltip for the action. In addition, each Action object has an enabled property and a setEnabled() method that allows it to be enabled and disabled. (If there is no text selected in a text editor, for example, the "Cut" action is usually disabled.)

You can add an Action object directly to a JMenu or JToolBar component. When you do this, the component automatically creates a JMenuItem or JButton to represent the action, making the action's operation available to the user and displaying the action's textual description and graphical image as appropriate. When an action is disabled, the JMenuItem or JButton component that represents the action displays it in a grayed-out style and does not allow it to be selected or invoked.

One shortcoming of working with actions is that there is no way to tell a JMenuBar or JToolBar to display just text or just icons for actions. Although you might like an action's name to be displayed in a menu and its icon to be displayed in a toolbar, both JMenuBar and JToolBar display an action's textual name and its icon.

The Action interface helps you implement a clean separation between GUI code and application logic. Remember, however, that you cannot just instantiate Action objects directly. Since Action is a kind of ActionListener, you must define an individual subclass of Action that implements the actionPerformed() method for each of your desired actions. The AbstractAction class is helpful here; it implements everything except the actionPerformed() method.

3.4 Tooltips

A Swing component can display context-sensitive help to the user in the form of a tooltip: a small window that pops up when the user lets the mouse rest over the component. You can display text in this window that explains the purpose or function of the component. Specify this text with the setToolTipText() method. This toolTipText property is inherited from JComponent, so it is shared by all Swing components.

While it is a good idea to provide tooltips for the benefit of your novice users, your experienced users may find them annoying, so it is nice to provide a way to turn them off. You can do this programatically by setting the enabled property of the ToolTipManager object. The code looks like this:

ToolTipManager.sharedInstance().setEnabled(false);

3.5 Timers

The javax.swing.Timer object generates single or multiple ActionEvent events at time intervals that you specify. Thus, a Timer is useful for performing a repeated operation like an animation. They are also useful for triggering operations that must occur at some point in the future. For example, an application might display a message in a status line and then set up a Timer object that erases the message after 5,000 milliseconds. These operations can also be performed with threads, of course, but since Swing is not designed for thread safety, it is usually more convenient to use a Timer.

You use Timer objects just like regular components. A Timer has property accessor methods and an addActionListener() method that you can use to add event listeners. The initialDelay property specifies how many milliseconds the Timer waits before firing its first ActionEvent. If the repeats property is true, the Timer generates a new ActionEvent each time delay milliseconds passes. When an application (or the system in general) is very busy or when the delay property is very small, the timer may fire events faster than the application can process them. If the coalesce property is true, the Timer combines multiple pending events into a single ActionEvent, rather than letting a queue of unprocessed events build up.

3.6 The Event Dispatch Thread

For efficiency reasons, Swing components are not designed to be thread safe. This means that Swing components should be manipulated by a single thread at a time. The easiest way to ensure this is to do all your GUI manipulations from the event dispatch thread. Every GUI application has an event dispatch thread: it is the thread that waits for events to occur and then dispatches those events to the appropriate event handlers. All of your event listener methods are invoked by the event dispatch thread, so any GUI manipulations you perform from an event listener are safe.

There are times, however, when you need to update your UI in response to some kind of external event, such as a response from a server that arrives in a separate thread. To accommodate these situations, Swing provides two utility methods that allow you ask the event dispatch thread to run arbitrary code. The methods are SwingUtilities.invokeLater() and SwingUtilities.invokeAndWait(). You pass a Runnable object to each method, and the run() method of this object is invoked from the event thread. invokeLater() returns right away, regardless of when the run() method is invoked, while invokeAndWait() does not return until the run() method has completed.

The invokeLater() and invokeAndWait() methods do not run your Runnable object right away. Instead, each method encapsulates the Runnable object within a special event object and places the event on the event queue. Then, when all pending events have been handled, the Runnable object is extracted from the event queue and the event dispatch thread calls its run() method. This means that invokeLater() provides a useful way to defer the execution of some chunk of code until after all pending events have been processed. There are times when you may even want to do this with code that is already running within the event dispatch thread.

3.7 Client Properties

In addition to its normal set of properties, JComponent includes a hashtable in which it can store arbitrary name/value pairs. These name/value pairs are called client properties, and they can be set and queried with the putClientProperty() and getClientProperty() methods. Since these are JComponent methods, they are inherited by all Swing components. Although both the name and value of a client property can be arbitrary objects, the name is usually a String object.

Client properties allow arbitrary data to be associated with any Swing component. This can be useful in a number of situations. For example, suppose you've created a JMenu that contains 10 JMenuItem components. Each component notifies the same ActionListener object when it is invoked. This action listener has to decide which of the 10 menu items invoked it and then perform whatever action is appropriate for that menu item. One way the action listener can distinguish among the menu items is by looking at the text that each displays. But this approach doesn't work well if you plan to translate your menu system into other languages. A better approach is to use the setActionCommand() method (inherited from AbstractButton) to associate a string with each of the JMenuItem components. Then the action listener can use this string to distinguish among the various menu items. But what if the action listener needs to check some kind of object other than a String in order to decide how to process the action event? Client properties are the solution: they allow you to associate an arbitrary object (or multiple objects) with each JMenuItem.

Client properties are used within Swing to set properties that are specific to a single look-and-feel implementation. For example, the default Java look-and-feel examines the client properties of a few components to obtain additional information about how it should display the components. Here are some details on these particular client properties:

"JInternalFrame.isPalette"

When a JInternalFrame is being used as a floating palette, set this client property to Boolean.TRUE to change the look of the border.

"JScrollBar.isFreeStanding"

JScrollPane sets this client property to Boolean.FALSE on the JScrollBar components it creates.

"JSlider.isFilled"

Setting this client property of a JSlider to Boolean.TRUE causes the slider to display a different background color on either side of the slider thumb.

"JToolBar.isRollover"

Setting this client property to Boolean.TRUE on a JToolBar causes the component to highlight the border of whatever child component the mouse is currently over.

"JTree.lineStyle"

This client property specifies how the JTree component draws the branches of its tree. The default value is the string "Horizontal"; other possible values are "Angled" and "None".

3.8 Keyboard Shortcuts

A full-featured user interface does not require the user to use the mouse all the time. Instead, it provides keyboard shortcuts that allow the user to operate the application primarily or entirely with the keyboard. Swing has a number of features that support keyboard shortcuts. Every Swing component is designed to respond to keyboard events and support keyboard operation automatically. For example, a JButton is activated when it receives a KeyEvent that tells it that the user pressed the Spacebar or the Enter key. Similarly, JMenu and JList respond to the arrow keys.

3.8.1 Focus Management

In order for a Swing component to receive keyboard events, it must first have the keyboard focus. In the old days, before graphical interfaces, when you typed on the keyboard, the characters always appeared on the screen. There was only one "window," so there was only one place to send key events. This changes with the introduction of windowing systems and GUIs, however, as there are now lots of places that keyboard events can be directed to. When there is more than one window open on the screen, one window is singled out as the current window (or the focused window). Most windowing systems highlight this window somehow. When you type at the keyboard, it is understood that your keystrokes are directed at the current window.

Just as a screen may contain many application windows, a single application window usually contains many GUI components. An application window must redirect the keyboard events it receives to only one of these components, called the focused component. Like most GUI toolkits, Swing highlights the component that has the keyboard focus, to let the user know where keyboard events are being directed. The details of the highlight depend on the look-and-feel that is currently in effect, but focus is often indicated by drawing a bold border around a component.

A Swing component can be operated from the keyboard when it has the focus. The user can usually direct keyboard focus to a given component by clicking on that component with the mouse, but this defeats the whole point of not using the mouse. The missing piece of the picture is focus traversal, otherwise known as keyboard navigation, which allows the user to use the keyboard to change focus from one component to the next.

Swing uses the Tab key to implement focus traversal. When the user presses Tab, Swing moves the keyboard focus from the current component to the next component that can accept the focus. (Some components, such as JLabel objects, do not respond to keyboard events and are therefore never given the focus.) When the user types Shift-Tab, Swing moves keyboard focus backward to the previous focusable component. By default, keyboard focus moves from left to right and top to bottom within a container. You can override this, however, by setting the nextFocusableComponent property of your components, chaining them together in whatever order you desire.

When a container is given focus through this mechanism, it passes that focus on to its first focusable child. When the focus reaches the last focusable child, some containers relinquish the focus and allow it to move on, while other containers retain the focus and give it back to the first focusable child. You can determine the behavior of a container by calling isFocusCycleRoot(). If this method returns true, the container defines a focus cycle and retains the focus. The user must type Ctrl-Tab to traverse to the next focus cycle or Ctrl-Shift-Tab to traverse to the previous focus cycle. There is no setFocusCycleRoot() method: the only way you can change this behavior is by subclassing a container and overriding the isFocusCycleRoot() method. Also note that multiline text components such as JTextArea and JEditorPane use the Tab key for their own purposes. These components behave like focus cycles, so the user must type Ctrl-Tab to move the focus away from such a component.

An application sometimes needs to set the keyboard focus to a particular component explicitly. You can do this by calling the requestFocus() method of that component. Components typically call requestFocus() themselves under certain circumstances, such as when they are clicked on. If you do not want a component to respond to requestFocus() calls, set its requestFocusEnabled property to false. For example, you might set this property on a JButton so that the user can click on it without taking keyboard focus away from whatever component currently has it.

Swing focus management is handled by the currently installed javax.swing.FocusManager object. You can obtain this object with FocusManager.getCurrentFocusManager(). If you implement your own manager, you can install it with FocusManager.setCurrentFocusManager().

3.8.2 Menu Mnemonics and Accelerators

Although Swing components can all be operated automatically from the keyboard, doing so is often cumbersome. The solution is to provide additional explicit keyboard shortcuts for common actions, as is commonly done with items on pull-down menus. Swing pull-down menus support two traditional types of keyboard shortcuts: mnemonics and accelerators. Figure 3.1 shows both types of menu shortcuts.

Figure 3.1: Swing menu mnemonics and accelerators

Figure 3.1

A menu mnemonic is a single-letter abbreviation for a menu command. When the menu has already been pulled down, the user can type this single key to invoke that menu item. The mnemonic for a menu item is typically indicated by underlining the letter of the shortcut in the menu item name, which means that you must select a shortcut letter that appears in the menu item label. Mnemonics must be unique within a menu, of course, but multiple menu panes can reuse mnemonics. Items in a menu bar may also have mnemonics. You specify a mnemonic for a menu or a menu item with the setMnemonic() method (inherited from AbstractButton):

JMenu file = new JMenu("File");
file.setMnemonic('F');
JMenuItem save = new JMenuItem("Save");
save.setMnemonic('S');            // Always use a capital letter
file.add(save);

A menu accelerator is a unique keyboard command that can be used to invoke a menu item even when the menu is not displayed. An accelerator is represented by a javax.swing.KeyStroke object and usually includes a keyboard modifier such as Ctrl or Alt. Unlike mnemonics, accelerators can be applied only to menu items, not to menus in a menu bar. You can create an accelerator for a menu item by calling setAccelerator(). To obtain an appropriate KeyStroke object, call the static KeyStroke.getKeyStroke() method with the keycode and modifier mask for the keyboard command you want to use:

JMenuItem save = new JMenuItem("Save");
save.setAccelerator(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S,
                                           java.awt.Event.CTRL_MASK));

3.8.3 Keyboard Actions

Sometimes even the keyboard shortcuts supported by menus are not enough. An application may need to define keyboard shortcuts for actions that are not available through the menu system. For example, an application that uses a JScrollPane to display a large drawing might want to allow the user to scroll the drawing with the arrow keys and the PageUp and PageDown keys.

Fortunately, every Swing component maintains a table of KeyStroke-to- ActionListener bindings. When a particular keystroke is bound to an ActionListener, the component will perform the action (i.e., invoke the actionPerformed() method) when the user types the keystroke. You can register a keyboard shortcut for a component with registerKeyboardAction(). For instance:

Action scroll;   // This action object is initialized elsewhere
JPanel panel;    // The application's main container; initialized elsewhere

KeyStroke up = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_UP);
KeyStroke down = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DOWN);
KeyStroke pgup = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_PAGE_UP);
KeyStroke pgdown=KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_PAGE_DOWN);

panel.registerKeyboardAction(scroll, "lineup", up,
                             JComponent.WHEN_ANCESTOR_OF_FOCUSED_WINDOW);
panel.registerKeyboardAction(scroll, "linedown", down,
                             JComponent.WHEN_ANCESTOR_OF_FOCUSED_WINDOW);
panel.registerKeyboardAction(scroll, "pageup", pgup,
                             JComponent.WHEN_ANCESTOR_OF_FOCUSED_WINDOW);
panel.registerKeyboardAction(scroll, "pagedown", pgdown,
                             JComponent.WHEN_ANCESTOR_OF_FOCUSED_WINDOW);

This code registers four keystrokes that all invoke the scroll action. When the user types one of these keystrokes, the actionPerformed() method is passed an ActionEvent object. The getActionCommand() method of this ActionEvent returns one of the strings "lineup", "linedown", "pageup", or "pagedown". The hypothetical scroll action we are using here would examine this string to determine what kind of scrolling to perform.

The fourth argument to registerKeyboardAction() is a constant that defines under what circumstances the keyboard action should be available to the user. The value used here, WHEN_ANCESTOR_OF_FOCUSED_WINDOW, specifies that the keyboard binding should be in effect whenever the panel or any of its descendants has the focus. You can also specify a value of WHEN_IN_FOCUSED_WINDOW, which means that the keyboard action is available whenever the window containing the component has the focus. This is useful for shortcuts registered on default buttons within dialog boxes. The final allowable value for this argument is WHEN_FOCUSED, which specifies that the key binding is in effect only when the component itself has the focus. This is useful when you are adding key bindings to an individual component like a JTree.

3.8.4 Keymaps

Swing supports a general, yet powerful text-editing subsystem. The javax.swing.text.JTextComponent is the base component in this system; it is the superclass of JTextField, JTextEditor, and JEditorPane, among others.

Because text editing typically involves many keyboard shortcuts, Swing defines the javax.swing.text.Keymap interface, which represents a set of KeyStroke-to- Action bindings. As you might expect, when a text component has the keyboard focus and the user types a keystroke that is bound to an action, the text component invokes that action. A Keymap can have a parent Keymap from which it inherits bindings, making it easy to override a few bindings of an existing keymap without redefining all the bindings from scratch. When you are working with a large number of keyboard shortcuts, it is easier to use a Keymap than to register each one individually with registerKeyboardAction().

JTextComponent defines getKeymap() and setKeymap() methods you can use to query and set the current keymap of a text component. There are no public implementations of the Keymap interface, so you cannot instantiate one directly. Instead, create a new Keymap by calling the static JTextComponent.addKeymap() method. This method allows you to specify a name and parent for the new Keymap. Both arguments are optional, however, so you may pass in null.

3.9 Serialization

The AWT Component class implements the java.io.Serializable marker interface, and JComponent reimplements this interface. This means that all AWT and Swing components are serializable, or, in other words, the state of an AWT or Swing component can be stored as a stream of bytes that can be written to a file. Components serialized to a file can be restored to their original state at a later date. When a component is serialized, all the components it contains are also automatically serialized as part of the same stream.

You serialize a component (or any serializable object) with the java.io.ObjectOutputStream class and reconstruct a serialized component with the java.io.ObjectInputStream. See Java in a Nutshell for more information about these classes. Because the byte stream format used in serialization changed between Java 1.1 and Java 1.2, Swing components serialized by a Java 1.2 application cannot be deserialized by a Java 1.1 application.

The serializability of Swing and AWT components is a powerful feature that is exploited by some GUI design tools. Thus, an application may create its graphical interface simply by reading and deserializing an already-built interface from a file. This is usually much simpler than creating the components of the GUI individually.

3.10 Borders

Every Swing component inherits a border property from JComponent, so you can call setBorder() to specify a Border object for a Swing component. This Border object displays some kind of decoration around the outside of the component. The javax.swing.border package contains this Border interface and a number of useful implementations of it. Table 3.1 lists the available border styles, and Figure 3.2 illustrates them.


Table 3.1: Swing Border Styles
Border Description
BevelBorder

Gives the component a beveled edge that makes it appear raised or lowered.

CompoundBorder

Combines two other Border types to create a compound border.

EmptyBorder

A border with no appearance. This is a useful way to place an empty margin around a component.

EtchedBorder

Draws a line around the component, using a 3D effect that makes the line appear etched into or raised out of the surrounding container.

LineBorder

Draws a line, with a color and thickness you specify, around the component.

MatteBorder

Draws the border using a solid color or a tiled image. You specify the border dimensions for all four sides.

SoftBevelBorder

Like BevelBorder, but with somewhat more complex graphics that give the bevel a softer edge.

TitledBorder

A border that combines text with an EtchedBorder or any other border you specify.

Figure 3.2: Swing border styles

Figure 3.2

The Border implementations defined in javax.swing.border cover just about every possible border you are likely to want to display. But if you ever find yourself needing a specialized border, simply implement the Border interface yourself.

Most of the Border implementations in javax.swing.border are immutable objects, designed to be shared. If two components have the same style of border, they can use the same Border immutable object. The javax.swing.BorderFactory class contains static methods that return various commonly used Border objects suitable for sharing.

3.11 Icons

All buttons, labels, and menu items in Swing can display both text and graphic elements. If you are familiar with the AWT, you might expect Swing to use the java.awt.Image class to represent these graphic elements. Instead, however, it uses javax.swing.Icon. This interface represents a graphic element more generally. Its paintIcon() method is called to display the graphic, and this method can do anything necessary to display it.

Swing includes an Icon implementation called ImageIcon. This commonly used class is an Image-based implementation of Icon. ImageIcon also simplifies the process of reading images from external files. One of the constructors for ImageIcon simply takes the name of the desired image file.

A related utility function is the static method GrayFilter.createDisabledImage(). This version produces a grayed-out version of a given Image, which can be used to create an ImageIcon that represents a disabled action or capability.

3.12 Cursors

The cursor, or mouse pointer, is the graphic that appears on the screen and tracks the position of the mouse. Java support for cursors has evolved in each Java release. Java 1.0 and 1.1 included 14 predefined cursors but did not support custom cursors. In Java 1.0, the predefined cursors were represented by constants defined by java.awt.Frame and they could be specified only for these top-level Frame components. These Frame constants and the corresponding setCursor() method of Frame are now deprecated.

Java 1.1 included a new java.awt.Cursor class and defined a new setCursor() method for all Component objects. Even though cursors had a class of their own in Java 1.1, the Cursor() constructor and the Cursor.getPredefinedCursor() method could still return only the same 14 predefined cursors. Despite their limited number, these predefined cursors are often useful. Figure 3.3 shows what they look like on a Unix machine running the X Window System.

Figure 3.3: The standard Java cursors, on a Unix platform

Figure 3.3

Java 1.2 includes an API to support custom cursors, at least when running on top of a native windowing system that supports them. In Java 1.2, the Cursor class has a new getSystemCustomCursor() method that returns a named cursor defined by a system administrator in a systemwide cursors.properties file. Since there is no way to query the list of system-specific custom cursors, however, this method is rarely used. Instead, an application may create its own custom cursors by calling the createCustomCursor() method of the Toolkit object. First, however, the application should check whether custom cursors are supported, by calling the getBestCursorSize() method of the Toolkit. If this method indicates a width or height of 0, custom cursors are not supported (by either the Java implementation or the underlying windowing system).

To create a custom cursor, you might use code like this:

Cursor c;
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension bestsize = tk.getBestCursorSize(24,24);
if (bestsize.width != 0)
  c = tk.createCustomCursor(cursorImage, cursorHotSpot, cursorName);
else
  c = Cursor.getDefaultCursor();
      

3.13 Double-Buffering

Double-buffering is the process of drawing graphics into an off-screen image buffer and then copying the contents of the buffer to the screen all at once. For complex graphics, using double-buffering can reduce flickering. Swing automatically supports double-buffering for all of its components. To enable it, simply call the setDoubleBuffered() method (inherited from JComponent) to set the doubleBuffered property to true for any components that should use double-buffered drawing.

Remember that double-buffering is memory intensive. Its use is typically only justified for components that are repainted very frequently or have particularly complex graphics to display. Note, however, that if a container uses double-buffering, any double-buffered children it has share the off-screen buffer of the container, so the required off-screen buffer is never larger than the on-screen size of the application.

3.14 The Box Container

Chapter 2, Swing and AWT Architecture, discussed the general task of arranging components within containers and listed the layout managers provided by AWT and Swing. This section describes a commonly used Swing layout management technique in detail. The easiest way to create complex arrangements of Swing components is often with the javax.swing.Box container.[ 1] Box arranges its components into a single row or a single column. You can then use nested Box containers to create a two-dimensional arrangement of components.

[1] For some reason, Box does not begin with the letter J as other Swing components and containers do. Nevertheless, it is a very useful and commonly used container.

The Box container uses the BoxLayout layout manager, but this layout manager is automatically assigned, so you never need to work with it explicitly. The easiest way to create a Box is with the static Box.createHorizontalBox() or Box.createVerticalBox() method. Once you have created a Box, simply add children to it. They will be arranged from left to right or from top to bottom.

The unique power of the Box actually comes from an inner class called Box.Filler. This class is a simple component that has no appearance; it exists simply to insert blank space in a layout and to affect the resize behavior of the layout. You do not create Box.Filler objects directly. Instead, you create them using the following static methods of Box:

Box.createHorizontalStrut(int 

width)
Box.createVerticalStrut(int 

height)
Box.createHorizontalGlue()
Box.createVerticalGlue()

If you are arranging a row of components, you can call createHorizontalStrut() to insert a fixed number of pixels of blank horizontal space. For a column of components, use createVerticalStrut() to insert a blank vertical space.

The glue methods are different. They insert stretchy horizontal or vertical space into a row or column. By default, the space is zero pixels wide or zero pixels high. But, if the row or column is stretched so that it becomes wider or higher than its default size, these glue components stretch to take up that extra space. For example, say you fill a row with some horizontal glue, a JButton component, and some more horizontal glue. Now, no matter how wide the row becomes, the JButton is always centered in it. This is because the two glue components (and possibly the JButton) grow equally to take up the extra space. On the other hand, if the row consists of only one glue component followed by a JButton, the JButton always appears right justified in the row, since the glue component grows to take up all the space to the left of the button.

As another example, consider a Box used in a dialog to hold a row of OK, Cancel, and Help buttons. Without any glue, the buttons are resized to fill up the entire row, with no extra space between them. If we intersperse the three buttons with four glue components, however, the buttons are always nicely spaced out and the buttons and the spaces between them grow proportionally as the dialog box becomes wider.

3.14.1 Minimum, Preferred, and Maximum Sizes

In order to fully understand the behavior of the Box container and its glue, it is important to understand that Swing components can have a minimum size, a preferred size, and a maximum size. Many components have a natural size. For example, with a JButton, the natural size is the space required to accommodate the button text and/or Icon, plus the space required for the button border. By default, a JButton reports its natural size as its minimum size and as its preferred size. When asked for its maximum size, a JButton returns very large integers, indicating that it can grow to become arbitrarily wide and arbitrarily tall.

Swing components (but not AWT components) allow you to specify their minimum, preferred, and maximum sizes. For example, if you do not want to allow a JButton to become arbitrarily large as its container grows larger, you can set a maximum size for it by calling setMaximumSize(). Setting a preferred size for a JButton is an uncommon thing to do, as JButton has a perfectly good natural size. But some components, such as JScrollPane objects, do not have a natural size. For components like these, it is usually important that you establish a default size with setPreferredSize(). If you want to prevent a JScrollPane or similar component from becoming arbitrarily small or arbitrarily large, you should also call setMinimumSize() and setMaximumSize().

Now that you understand the concepts of minimum, preferred, and maximum sizes, we can return to the Box container and its struts and glue. Both struts and glue are instances of the Box.Filler component. When you create a Box.Filler, you are actually specifying minimum, preferred, and maximum sizes for the component. A horizontal strut is simply a Box.Filler with its minimum, preferred, and maximum width set to the number of pixels you specify. A vertical strut has a fixed minimum, preferred, and maximum height.

Horizontal glue has a minimum and preferred width of zero, but a very large maximum width. This means that the glue takes up no space by default but grows as necessary to fill up extra space. Vertical glue does the same thing in the other dimension. In order to understand glue, it is also important to understand how the Box container distributes excess space to its children. If a horizontal Box becomes wider, the extra width is allocated among the children based on their maximum widths. Children with larger maximums are given a proportionally larger amount of the extra space. When you intersperse JButton objects with glue, all the components have effectively infinite maximum widths, so all grow by equal amounts. Suppose, instead, that you restricted the sizes of your buttons like this:

okayButton.setMaximumSize(okayButton.getPreferredSize());
cancelButton.setMaximumSize(cancelButton.getPreferredSize());
helpButton.setMaximumSize(helpButton.getPreferredSize());
In this case, the buttons are already at their maximum sizes, so no extra space is allocated to them. Now the glue between the buttons gets all the extra space.

I just said that glue components have a preferred size of zero. With regard to the example of three buttons interspersed with four glue components, this means that when the row of buttons is displayed at its default size, the buttons bump into one another and appear awkwardly crowded. To remedy this, you might place horizontal struts and horizontal glue between the buttons. In this case, the struts provide the default and minimum spacing, while the glue components make the spacing grow. There is a more efficient way to do this, however. You can explicitly create Box.Filler components that combine the nonzero default size of a strut with the infinite maximum size of a glue object. You can create such a filler object as follows:

Dimension fixedwidth = new Dimension(15, 0);
Dimension infinitewidth = new Dimension(Short.MAX_VALUE, 0);
Box.Filler filler = new Box.Filler(fixedwidth, fixedwidth, infinitewidth);

3.14.2 The Other Dimension

So far, our discussion of the Box container has covered only how components are arranged horizontally in a horizontal box or vertically in a vertical box. What does Box do in the other dimension? When laying out components in a row, the Box makes the row as tall as the tallest component and then attempts to make all the components as tall as the row. Similarly, when it lays out components in a column, Box tries to make all components as wide as the widest component.

As we've discussed, however, components can have a maximum size. If a row becomes taller than a component's maximum height or a column becomes wider than a component's maximum width, the Box must decide how to position the component with respect to the others in the row or column. For a column, the component can be left, center, or right justified or positioned anywhere in between. A component in a row can be aligned along the top or bottom of the row or placed somewhere in between.

A Box positions such a component based on its alignmentX or alignmentY property. Each is a float property that should have a value between 0.0 and 1.0. The default for both is 0.5. When a component needs to be positioned horizontally in a column, the Box uses the alignmentX property. A value of 0.0 means the component is left justified, 1.0 means the component is right justified, and 0.5 means the component is centered. Other values position the component appropriately between these positions. When a Box needs to position a component vertically in a row, it uses the component's alignmentY property to place the component in the vertical plane in an analogous way.

3.15 Simple Dialogs

GUIs often use dialog boxes to handle simple interactions with the user. javax.swing.JOptionPane is a Swing component that is designed to serve as a highly configurable body of a dialog box. Instead of using the JOptionPane directly, however, most Swing programs use one or more of the many static methods defined by JOptionPane. These methods make it quite easy to implement simple dialog-based interactions.

If you take a look at the API for JOptionPane, you'll see that the class defines a group of static methods whose names begin with show and another whose names begin with showInternal. The show methods display simple dialog boxes within JDialog windows, while the showInternal methods display the same dialog boxes inside JInternalFrame windows. These static methods are further broken down by the type of dialog they display. There are several versions of showMessageDialog(), showConfirmDialog(), and showInputDialog(), as well as showInternal versions of the same methods. We'll consider these three types of dialogs - message, confirm, and input - in the sections that follow.

3.15.1 Message Dialogs

Message dialogs are used to display important information to users in a way that is difficult or impossible for them to miss. For example, you might use a message dialog to tell the user that a requested file was not found. To display this message with a JOptionPane, you can use code like this:

JOptionPane.showMessageDialog(mainpanel, "The file you requested, " + 
                              filename + ", was not found. Please try again");
This code produces the dialog shown in Figure 3.4. The dialog remains visible until the user dismisses it by clicking OK.

Figure 3.4: A JOptionPane message dialog

Figure 3.4

The first argument to showMessageDialog() is the component over which the dialog is to appear. You typically specify the main window or panel of your application. If you specify null, then the dialog will simply be centered on the screen. The second argument is obviously the message to be displayed. If you look at the API again, however, you'll notice that the message argument to this and other JOptionPane methods is defined as an Object, not a String. This means that you are not limited to textual messages. If you pass a Component or an Icon, the JOptionPane displays it as the message. If you pass some other kind of object, JOptionPane attempts to convert it to a string by calling its toString() method. You can even pass an array of objects as the message argument. When you pass more than one object, the objects are displayed top to bottom in the resulting dialog. So, to display a multiline message, for example, you can just pass in an array of String objects, instead of a single long String.

The showMessageDialog() function has variants that take more arguments. The title argument specifies the text to appear in the titlebar of the dialog. The messageType argument specifies the general type of the message. Legal values are the JOptionPane constants that end with _MESSAGE. The values you are most likely to use are INFORMATION_MESSAGE, WARNING_MESSAGE, and ERROR_MESSAGE. Specifying a message type implicitly specifies the icon that appears in the dialog box. If you don't like the default icons, however, there is a version of showMessageDialog() that lets you specify your own icon to display.

3.15.2 Confirm Dialogs

You can use JOptionPane.showConfirmDialog() or JOptionPane.showInternalConfirmDialog() when you want to ask the user a simple question that requires a Yes or No (or perhaps Cancel) answer. For example, you can use one of these methods to present the dialog shown in Figure 3.5.

Figure 3.5: A JOptionPane confirm dialog

Figure 3.5

The arguments to showConfirmDialog() are much like the arguments to showMessageDialog(), with the addition of the optionType argument. This argument specifies the set of buttons that appears at the bottom of the dialog. Legal values are OK_CANCEL_OPTION, YES_NO_OPTION, and YES_NO_CANCEL_OPTION.

A confirm dialog asks the user a question. The return value of showOptionDialog() or showInternalOptionDialog() is an integer that represents the user's answer in terms of the button the user clicked to dismiss the dialog. The possible values are OK_OPTION, YES_OPTION, NO_OPTION, CANCEL_OPTION, and CLOSED_OPTION. This last value is returned if the user did not click any of the dialog buttons but instead dismissed the dialog by closing the window. Here is some simple code that asks a question with a confirm dialog (note the use of a string array for the message argument):

int response = JOptionPane.showConfirmDialog(mainpanel, new String[] {
       /* first line of the message */       "There are unsaved files.",
       /* second line of message    */       "Save them before quitting?"},
       /* dialog title              */       "Save Before Quitting?",
       /* what buttons to display   */       JOptionPane.YES_NO_CANCEL_OPTION,
       /* icon type to display      */       JOptionPane.WARNING_MESSAGE);
switch(response) {
  case JOptionPane.YES_OPTION:     saveAndQuit();
  case JOptionPane.NO_OPTION:      quitWithoutSaving();
  case JOptionPane.CANCEL_OPTION:
  case JOptionPane.CLOSED_OPTION:  break;  // Don't quit!
}

3.15.3 Input Dialogs

The showInputDialog() and showInternalInputDialog() methods are designed to ask for input that is more complex than a yes-or-no answer. The simple versions of showInputDialog() support asking a question like "What is your name?" and letting the user type a response in a text input area:

String name = JOptionPane.showInputDialog(frame, "What is your name?");

The more complex version of this method allows the user to select an object from a list or pull-down menu of predefined options.

The arguments to showInputDialog() are quite similar to those passed to showMessageDialog() and showConfirmDialog(). To display a list of options to the user, use the seven-argument version of the method and pass in an array of choices and the default choice to display. For example:

String response = (String) JOptionPane.showInputDialog(
		      contentpane,                       // parent
		     "Who is your favorite chipmunk?",   // message
		     "Pick a Chipmunk",                  // dialog title
		      JOptionPane.QUESTION_MESSAGE,      // icon type
		      null,                              // no explicit icon
		      new String[] {                     // choices
			 "Alvin", "Simon", "Theodore" 
		      },  
		      "Alvin");                          // default choice

3.16 JFileChooser

javax.swing.JFileChooser is a specialized component that allows the user to browse the filesystem and select a file. The easiest way to use it is with the showOpenDialog() and showSaveDialog() methods. These methods differ only in the text that appears in the "Okay" button. You can also call the showDialog() method and specify your own text for that button. Each of these methods returns an integer status code that specifies how the user dismissed the dialog. If the return value is APPROVE_OPTION, the user actually selected a file, which you can obtain with the getSelectedFile() method. For example:

public void saveAs() {
  JFileChooser chooser = new JFileChooser();
  int result = chooser.showSaveDialog(mainpane);
  if (result == JFileChooser.APPROVE_OPTION)
    save(chooser.getSelectedFile());
}

Note that showSaveDialog() and showOpenDialog() are instance methods, not static methods like those used with JOptionPane. This means that you can customize the dialog by setting properties on your JFileChooser object. You may be interested in setting the currentDirectory and fileSelectionMode properties before you display a JFileChooser. fileSelectionMode can be set to FILES_ONLY, DIRECTORIES_ONLY, or FILES_AND_DIRECTORIES. Once you create a JFileChooser for an application, you may want to reuse it, rather than creating a new one each time you need one. If you do so, the JFileChooser automatically remembers the currentDirectory most recently selected by the user.

3.16.1 Using File Filters

The javax.swing.filechooser package defines auxiliary classes that are used by JFileChooser. One of the most important of these is FileFilter. The abstract javax.swing.filechooser.FileFilter class is much like the java.io.FileFilter interface. Each defines an accept() method that is passed File objects and returns true for each file that should be displayed. The FileFilter class used by JFileChooser has an additional getDescription() method that returns a string that names the types of files accepted by the filter. For example, you might define a FileFilter subclass that accepts files with names ending in .htm or .html and returns a description of "HTML Files."

When you create a JFileChooser, you can specify the FileFilter it is to use with setFileFilter(). Alternately, you can specify an array of FileFilter objects with setChoosableFileFilters(). In this case, JFileChooser displays the descriptions of the filters and allows the user to choose one.

3.16.2 Customizing JFileChooser

The behavior of a JFileChooser can be customized by providing your own implementation of FileView and FileSystemView. Both of these abstract classes are defined in the javax.swing.filechooser class. FileView defines methods that affect the way individual files are displayed by the JFileChooser, while FileSystemView defines methods that enable the JFileChooser to handle operating-system dependencies in the filesystem. FileSystemView understands the notion of hidden files, and it can return a complete list of filesystem roots, a capability that was lacking from the basic java.io.File class prior to Java 1.2. The default FileView and FileSystemView classes provided by JFileChooser are perfectly adequate for most purposes, so you typically don't have to implement these classes yourself.

It is also possible to customize a JFileChooser by providing an accessory component. If you pass a JComponent to the setAccessory() method of JFileChooser, the Swing component you specify is displayed in the file chooser dialog box. A common use of a file chooser accessory is as a file preview component. In order to provide a preview of the currently selected file, the accessory must know what the currently selected file is. It can get this information by implementing the PropertyChangeListener interface and listening for changes to the selectedFile property. In order for this to work, you have to pass the accessory object to the addPropertyChangeListener() method of the JFileChooser, of course.

3.17 JColorChooser

Just as JFileChooser allows the user to choose a file, javax.swing.JColorChooser allows the user to choose a color. Figure 3.6 shows a JColorChooser dialog. You can embed a JColorChooser component directly in your application or in a custom dialog box, but the most common way to use it is to simply call the static showDialog() method:

Color c = JColorChooser.showDialog(contentpane,    // Dialog appears over this
                                   "Pick a Color", // Dialog title 
                                   Color.white);   // Default color selection

Figure 3.6: A JColorChooser dialog

Figure 3.6

As you can see from Figure 3.6, JColorChooser displays a color selection pane and a color preview pane. The selection pane is actually a JTabbedPane that allows colors to be selected in three different ways. The Swatches pane lets the user select a color from a palette of color swatches. With the RGB pane, the user picks a color by specifying the red, green, and blue components of the color, while with the HSV pane, the user specifies the hue, saturation, and value components of the color.

Instead of displaying a generic JColorChooser with the static showDialog() method, you can create your own instance of the JColorChooser class. You can then set properties on the color chooser object and display it in any way you want. The static JColorChooser.createDialog() method is useful here. It creates a dialog box to hold your JColorChooser pane and allows you to specify two ActionListener objects that are invoked in response to the OK and Cancel buttons in the dialog box.

You can customize a JColorChooser by adding a new color selection panel or a new color preview panel. To add a new color selection panel (for example, a panel that allows the user to select a grayscale color or a CMYK color), implement a subclass of AbstractColorChooserPanel (from the javax.swing.colorchooser package) and pass it to the addChooserPanel() method of your JColorChooser. Your custom panel contains a ColorSelectionModel that serves as the interface between your pane and the JColorChooser. All your pane needs to do is update the selected color of its ColorSelectionModel ( ColorSelectionModel is also part of the javax.swing.colorchooser package).

You can use any JComponent as a custom preview panel for your JColorChooser. Simply pass the component to setPreviewPanel(). The preview component has to track the currently selected color by listening for ChangeEvent events generated by the ColorSelectionModel of the JColorChooser.

3.18 Menus

In Swing, menu bars, menu panes, and menu items are components, just like all other Swing components. JMenuBar is a container designed to hold JMenu objects. JMenu is a container designed to hold JMenuItem objects and other JMenu objects (as submenus). Working with menus is not exactly the same as working with other types of components, however, and Example 3.1 shows a simple example of creating pull-down and pop-up menus.

Example 3.1: Creating Pull-Down and Pop-Up Menus in Swing

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class MenuDemo {
  public static void main(String[] args) {
    // Create a window for this demo
    JFrame frame = new JFrame("Menu Demo");
    JPanel panel = new JPanel();
    frame.getContentPane().add(panel, "Center");

    // Create an action listener for the menu items we will create
    // The MenuItemActionListener class is defined below
    ActionListener listener = new MenuItemActionListener(panel);

    // Create some menu panes, and fill them with menu items
    // The menuItem() method is important.  It is defined below.
    JMenu file = new JMenu("File");
    file.setMnemonic('F');
    file.add(menuItem("New", listener, "new", 'N', KeyEvent.VK_N));
    file.add(menuItem("Open...", listener, "open", 'O', KeyEvent.VK_O));
    file.add(menuItem("Save", listener, "save", 'S', KeyEvent.VK_S));
    file.add(menuItem("Save As...", listener, "saveas", 'A', KeyEvent.VK_A));

    JMenu edit = new JMenu("Edit");
    edit.setMnemonic('E');
    edit.add(menuItem("Cut", listener, "cut", 0, KeyEvent.VK_X));
    edit.add(menuItem("Copy", listener, "copy", 'C', KeyEvent.VK_C));
    edit.add(menuItem("Paste", listener, "paste", 0, KeyEvent.VK_V));

    // Create a menu bar and add these panes to it.
    JMenuBar menubar = new JMenuBar();
    menubar.add(file);
    menubar.add(edit);

    // Add menu bar to the main window.  Note special method to add menu bars.
    frame.setJMenuBar(menubar); 

    // Now create a popup menu and add the some stuff to it
    final JPopupMenu popup = new JPopupMenu();
    popup.add(menuItem("Open...", listener, "open", 0, 0));
    popup.addSeparator();                // Add a separator between items
    JMenu colors = new JMenu("Colors");  // Create a submenu
    popup.add(colors);                   // and add it to the popup menu
    // Now fill the submenu with mutually exclusive radio buttons
    ButtonGroup colorgroup = new ButtonGroup();
    colors.add(radioItem("Red", listener, "color(red)", colorgroup));
    colors.add(radioItem("Green", listener, "color(green)", colorgroup));
    colors.add(radioItem("Blue", listener, "color(blue)", colorgroup));

    // Arrange to display the popup menu when the user clicks in the window
    panel.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
	// Check whether this is the right type of event to pop up a popup
	// menu on this platform.  Usually checks for right button down.
	if (e.isPopupTrigger()) 
	  popup.show((Component)e.getSource(), e.getX(), e.getY());
      }
    });

    // Finally, make our main window appear
    frame.setSize(450, 300);
    frame.setVisible(true);
  }

  // A convenience method for creating menu items
  public static JMenuItem menuItem(String label, 
				   ActionListener listener, String command, 
				   int mnemonic, int acceleratorKey) {
    JMenuItem item = new JMenuItem(label);
    item.addActionListener(listener);
    item.setActionCommand(command);
    if (mnemonic != 0) item.setMnemonic((char) mnemonic);
    if (acceleratorKey != 0) 
      item.setAccelerator(KeyStroke.getKeyStroke(acceleratorKey, 
						 java.awt.Event.CTRL_MASK));
    return item;
  }

  // A convenience method for creating radio button menu items
  public static JMenuItem radioItem(String label, ActionListener listener, 
				    String command, ButtonGroup mutExGroup) {
    JMenuItem item = new JRadioButtonMenuItem(label);
    item.addActionListener(listener);
    item.setActionCommand(command);
    mutExGroup.add(item);
    return item;
  }

  // An event listener class used with the menu items created above
  // For this demo, it just displays a dialog box when an item is selected
  public static class MenuItemActionListener implements ActionListener {
    Component parent;
    public MenuItemActionListener(Component parent) { this.parent = parent; }
    public void actionPerformed(ActionEvent e) {
      JMenuItem item = (JMenuItem) e.getSource();
      String cmd = item.getActionCommand();
      JOptionPane.showMessageDialog(parent, cmd + " was selected.");
    }
  }
}

3.19 JTree and TreeModel

The javax.swing.JTree class is a powerful Swing component for displaying tree-structured data. Like all Swing components, JTree relies on a separate model object to hold and represent the data that it displays. Most Swing components create this model object automatically, and you never need to work with it explicitly. The JTree component, however, displays data that is much more complex than a typical Swing component. When you are working with a JTree, you must create a model object that implements the javax.swing.tree.TreeModel interface.

One approach is to use the DefaultTreeModel class, which implements the TreeModel interface using the TreeNode and MutableTreeNode interfaces (all defined in javax.swing.tree). To use DefaultTreeModel, you must implement your hierarchical data structures so that each element of the tree implements the TreeNode or MutableTreeNode interface. Now you can create a DefaultTreeModel object simply by passing the root TreeNode of your tree to a DefaultTreeModel constructor. Then you create a JTree component to display your tree simply by passing the DefaultTreeModel to the setModel() method of the JTree.

Sometimes, however, you do not have the luxury of designing the data structures used to represent your tree, so implementing the TreeNode interface is simply not an option. In this case, you can implement the TreeModel interface directly. The resulting TreeModel object serves as the interface between your data and the JTree component that displays the data. Your TreeModel implementation provides the methods that allow the JTree component to traverse the nodes of your tree, regardless of the actual representation of the tree data.

Example 3.2 shows a program that implements the TreeModel interface to represent the hierarchical structure of the filesystem, thereby allowing the file and directory tree to be displayed in a JTree component. Notice how a relatively simple implementation of TreeModel enables the powerful tree- browsing capabilities shown in Figure 3.7.

Figure 3.7: The JTree component

Figure 3.7

Example 3.2: Using JTree and TreeModel

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.io.File;

public class FileTreeDemo {
  public static void main(String[] args) {
    // Figure out where in the filesystem to start displaying
    File root;
    if (args.length > 0) root = new File(args[0]);
    else root = new File(System.getProperty("user.home"));

    // Create a TreeModel object to represent our tree of files
    FileTreeModel model = new FileTreeModel(root);

    // Create a JTree and tell it to display our model
    JTree tree = new JTree();
    tree.setModel(model);

    // The JTree can get big, so allow it to scroll
    JScrollPane scrollpane = new JScrollPane(tree);
    
    // Display it all in a window and make the window appear
    JFrame frame = new JFrame("FileTreeDemo");
    frame.getContentPane().add(scrollpane, "Center");
    frame.setSize(400,600);
    frame.setVisible(true);
  }
}

/**
 * The methods in this class allow the JTree component to traverse
 * the file system tree and display the files and directories.
 **/
class FileTreeModel implements TreeModel {
  // We specify the root directory when we create the model.
  protected File root;
  public FileTreeModel(File root) { this.root = root; }

  // The model knows how to return the root object of the tree
  public Object getRoot() { return root; }

  // Tell JTree whether an object in the tree is a leaf
  public boolean isLeaf(Object node) {  return ((File)node).isFile(); }

  // Tell JTree how many children a node has
  public int getChildCount(Object parent) {
    String[] children = ((File)parent).list();
    if (children == null) return 0;
    return children.length;
  }

  // Fetch any numbered child of a node for the JTree.
  // Our model returns File objects for all nodes in the tree.  The
  // JTree displays these by calling the File.toString() method.
  public Object getChild(Object parent, int index) {
    String[] children = ((File)parent).list();
    if ((children == null) || (index >= children.length)) return null;
    return new File((File) parent, children[index]);
  }

  // Figure out a child's position in its parent node.
  public int getIndexOfChild(Object parent, Object child) {
    String[] children = ((File)parent).list();
    if (children == null) return -1;
    String childname = ((File)child).getName();
    for(int i = 0; i < children.length; i++) {
      if (childname.equals(children[i])) return i;
    }
    return -1;
  }

  // This method is invoked by the JTree only for editable trees.  
  // This TreeModel does not allow editing, so we do not implement 
  // this method.  The JTree editable property is false by default.
  public void valueForPathChanged(TreePath path, Object newvalue) {}

  // Since this is not an editable tree model, we never fire any events,
  // so we don't actually have to keep track of interested listeners
  public void addTreeModelListener(TreeModelListener l) {}
  public void removeTreeModelListener(TreeModelListener l) {}
}

3.20 JTable and TableModel

javax.swing.JTable is another powerful Swing component for displaying complex data structures. Like JTree, JTable relies on a separate model object to hold and represent the data it displays and has its own package of helper classes, javax.swing.table. This package contains the TableModel interface and its default implementations, AbstractTableModel and DefaultTableModel.

If your table data is tidily organized, it is easy to use JTable without worrying about the TableModel. If your data is an array of rows, where each row is an array of objects, you can just pass this Object[][] directly to the JTable constructor. If you want, you can also specify an optional array of column names. This is all you need to do: the JTable does the rest. This technique also works if your data is stored in a Vector of rows, where each row is itself a Vector.

Often, however, your data is not as regular as that. When you want to display a tabular view of data that is not, by nature, tabular, you must implement the TableModel interface (or, more likely, subclass the AbstractTableModel class). The job of this TableModel implementation is to serve as the interface between your data, which is not neatly organized into a table, and the JTable object, which wants to display a table. In other words, your TableModel presents a neat tabular view of your data, regardless of how the data is organized underneath.

Example 3.3 shows how this can be done. Given a File object that represents a directory in the filesystem, this example displays the contents of that directory in tabular form, as shown in Figure 3.8. Once again, notice how a relatively simple TableModel implementation enables the use of the powerful table-display capabilities of the JTable component.

Figure 3.8: The JTable component

Figure 3.8

Example 3.3: Using JTable and TableModel

import javax.swing.*;
import javax.swing.table.*;
import java.io.File;
import java.util.Date;

public class FileTableDemo {
  public static void main(String[] args) {
    // Figure out what directory to display
    File dir;
    if (args.length > 0) dir = new File(args[0]);
    else dir = new File(System.getProperty("user.home"));

    // Create a TableModel object to represent the contents of the directory
    FileTableModel model = new FileTableModel(dir);

    // Create a JTable and tell it to display our model
    JTable table = new JTable(model);

    // Display it all in a scrolling window and make the window appear
    JFrame frame = new JFrame("FileTableDemo");
    frame.getContentPane().add(new JScrollPane(table), "Center");
    frame.setSize(600, 400);
    frame.setVisible(true);
  }
}

/**
 * The methods in this class allow the JTable component to get
 * and display data about the files in a specified directory.
 * It represents a table with six columns: filename, size, modification date, 
 * plus three columns for flags: directory, readable, writable.
 **/
class FileTableModel extends AbstractTableModel {
  protected File dir;
  protected String[] filenames;

  protected String[] columnNames = new String[] {
    "name", "size", "last modified", "directory?", "readable?", "writable?"
  };

  protected Class[] columnClasses = new Class[] { 
    String.class, Long.class, Date.class, 
      Boolean.class, Boolean.class, Boolean.class
  };

  // This table model works for any one given directory
  public FileTableModel(File dir) { 
    this.dir = dir; 
    this.filenames = dir.list();  // Store a list of files in the directory
  }

  // These are easy methods
  public int getColumnCount() { return 6; }  // A constant for this model
  public int getRowCount() { return filenames.length; }  // # of files in dir

  // Information about each column
  public String getColumnName(int col) { return columnNames[col]; }
  public Class getColumnClass(int col) { return columnClasses[col]; }

  // The method that must actually return the value of each cell
  public Object getValueAt(int row, int col) {
    File f = new File(dir, filenames[row]);
    switch(col) {
    case 0: return filenames[row];
    case 1: return new Long(f.length());
    case 2: return new Date(f.lastModified());
    case 3: return f.isDirectory() ? Boolean.TRUE : Boolean.FALSE;
    case 4: return f.canRead() ? Boolean.TRUE : Boolean.FALSE;
    case 5: return f.canWrite() ? Boolean.TRUE : Boolean.FALSE;
    default: return null;
    }
  }
}

3.21 JTextComponent and HTML Text Display

The most complex component in all of Swing is the JTextComponent, which is a powerful editor. It is part of the javax.swing.text package and generally is not used directly. Instead, you typically use one of its subclasses, such as JTextField, JPasswordField, JTextArea, or JEditorPane. The first three of these components are straightforward. They are for the entry of a single line of text, secret text such as a password, and simple, unformatted, multiline text, respectively.

It is the JEditorPane component that really makes use of the full power of JTextComponent. JEditorPane supports the display and editing of complex formatted text. In conjunction with the classes in the javax.swing.text.html and javax.swing.text.rtf packages, JEditorPane can display and edit HTML and RTF documents. The ability to display formatted text so easily is a very powerful feature. For example, the ability to display HTML documents makes it simple for a Swing application to add online help based on an HTML version of the application's user manual. Furthermore, formatted text is a professional-looking way for an application to display its output to the user.

Because HTML has become so ubiquitous, we'll focus on the display of HTML documents with JEditorPane, There are several different ways to get a JEditorPane to display an HTML document. If the desired document is available on the network, the easiest way to display it is simply to pass an appropriate java.net.URL object to the setPage() method of JEditorPane. setPage() determines the data type of the document and, assuming it is an HTML document, loads it and displays it as such. For example:

editor.setPage(new java.net.URL("http://www.my.com/product/help.htm"));

If the document you want to display is in a local file or is available from some kind of InputStream, you can display it by passing the appropriate stream to the read() method of JEditorPane. The second argument to this method should be null. For example:

InputStream in = new FileInputStream("help.htm");
editor.read(in, null);

Yet another way to display text in a JEditorPane is to pass the text to the setText() method. Before you do this, however, you must tell the editor what type of text to expect:

editor.setContentType("text/html");
editor.setText("<H1>Hello World!</H1>");
Calling setText() can be particularly useful when your application generates HTML text on the fly and wants to use a JEditorPane to display nicely formatted output to the user.

Example 3.4 shows one such use of the JEditorPane. This example is an alternative to Example 3.3: it displays the contents of a directory in tabular form but uses an HTML table instead of the JTable component. As a bonus, this example uses HTML hyperlinks to allow the user to browse from one directory to the next. (If you download and run the two examples, however, you'll probably notice that the JTable example is significantly faster, since it does not have to encode the directory contents into HTML and then parse that HTML into a table.) Figure 3.9 shows sample output from this example.

Figure 3.9: The JEditorPane component displaying an HTML table

Figure 3.9

Example 3.4: Dynamically Generated HTML in JEditorPane

import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import java.util.Date;

/**
 * This class implements a simple directory browser using the HTML
 * display capabilities of the JEditorPane component
 **/
public class FileTableHTML {
  public static void main(String[] args) throws IOException {
    // Get the name of the directory to display
    String dirname = (args.length>0)?args[0]:System.getProperty("user.home");

    // Create something to display it in
    final JEditorPane editor = new JEditorPane();
    editor.setEditable(false);               // we're browsing not editing
    editor.setContentType("text/html");      // must specify HTML text
    editor.setText(makeHTMLTable(dirname));  // specify the text to display
  
    // Set up the JEditorPane to handle clicks on hyperlinks
    editor.addHyperlinkListener(new HyperlinkListener() {
      public void hyperlinkUpdate(HyperlinkEvent e) {
	// Handle clicks; ignore mouseovers and other link-related events
	if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
	  // Get the HREF of the link and display it.
	  editor.setText(makeHTMLTable(e.getDescription()));
	}
      }
    });

    // Put the JEditorPane in a scrolling window and display it
    JFrame frame = new JFrame("FileTableHTML");
    frame.getContentPane().add(new JScrollPane(editor));
    frame.setSize(650, 500);
    frame.setVisible(true);
  }

  // This method returns an HTML table representing the specified directory
  public static String makeHTMLTable(String dirname) {
    // Look up the contents of the directory
    File dir = new File(dirname);
    String[] entries = dir.list();

    // Set up an output stream we can print the table to.
    // This is easier than concatenating strings all the time.
    StringWriter sout = new StringWriter();
    PrintWriter out = new PrintWriter(sout);
    
    // Print the directory name as the page title
    out.println("<H1>" + dirname + "</H1>");

    // Print an "up" link, unless we're already at the root
    String parent = dir.getParent();
    if ((parent != null) && (parent.length() > 0)) 
      out.println("<A HREF=\"" + parent + "\">Up to parent directory</A><P>");

    // Print out the table
    out.print("<TABLE BORDER=2 WIDTH=600><TR>");
    out.print("<TH>Name</TH><TH>Size</TH><TH>Modified</TH>");
    out.println("<TH>Readable?</TH><TH>Writable?</TH></TR>");
    for(int i=0; i < entries.length; i++) {
      File f = new File(dir, entries[i]);
      out.println("<TR><TD>" + 
		  (f.isDirectory() ?
		     "<a href=\""+f+"\">" + entries[i] + "</a>" : 
		     entries[i]) +
		  "</TD><TD>" + f.length() +
		  "</TD><TD>" + new Date(f.lastModified()) + 
		  "</TD><TD align=center>" + (f.canRead()?"x":" ") +
		  "</TD><TD align=center>" + (f.canWrite()?"x":" ") +
		  "</TD></TR>");
    }
    out.println("</TABLE>");
    out.close();

    // Get the string of HTML from the StringWriter and return it.
    return sout.toString();
  }
}

3.22 Pluggable Look-and-Feel

One of the unique features of Swing is its pluggable look-and-feel (PLAF) architecture, which allows a Swing application to change its entire appearance with one or two lines of code. The most common use of this feature is to give applications a choice between the native platform look-and-feel and a new platform-independent Java look-and-feel (also known as the Metal look-and-feel). Swing is distributed with three look-and-feels: Metal and two look-and-feels that mimic the appearance and behavior of the Windows and Motif (Unix/X) component toolkits. A look-and-feel that mimics the Macintosh platform is available as a separate download. While the Metal and Motif look-and-feels can be freely used, the Windows look-and-feel is restricted for use only on Windows platform - for copyright reasons, it does not run on any other operating system.

When a Swing application starts up, it reads the system property swing.defaultlaf to determine the classname of the default look-and-feel. In most Java installations, this property is set to the default Java look-and-feel, implemented by the class javax.swing.plaf.metal.MetalLookAndFeel. The end user can override this default by using the -D switch on the command line when invoking the Java interpreter. For example, to run a Swing application using the Motif look-and-feel, a user can type:

% java -Dswing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel app
If the user is using a Windows operating system, he can start the application using the Windows look-and-feel like this:
% java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel app

When you write a Swing application, you can explicitly set the look-and-feel that the application uses. To do this, simply call the static setLookAndFeel() method of the UIManager class and specify the classname of the desired look-and-feel implementation. To make this even easier, UIManager defines a static method that returns the classname of the default cross-platform look-and-feel (i.e., Metal) and another that returns the classname of the look-and-feel that mimics the native look-and-feel of the current platform. So, if you want your application to always look like a native application, you can simply include this line of code in your application, before it begins to create any GUI components:

UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Or, if you want to force the application to use the cross-platform look-and-feel, regardless of installation defaults and user preferences, you can use this line of code:
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

Note that calling setLookAndFeel() like this overrides the value of the swing.defaultlaf property, if the end user has set one. Of course, the command-line syntax for setting that property is quite awkward and may be beyond the capabilities of many end users. An alternative is to implement command-line options in your own application that give the user a choice of look-and-feels. You might set a native look-and-feel if the user specifies a -nativelook flag on the command line, for example.

The easiest time to call the setLookAndFeel() method is at application start-up, before any Swing components have been created. It is also possible to change the look-and-feel of a running application, however. This means that you can allow the user to change the current look-and-feel through a preferences dialog box, if you are so inclined. When the user selects a new look-and-feel, you first call setLookAndFeel() to install the new look-and-feel, and then you have to notify all of the Swing components that a new look-and-feel is in effect and ask them to use it. Fortunately, there is a convenience method to do this. Your code might look like this:

// Set the new look-and-feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName);
// Tell all components from main JFrame on down that LAF has changed
SwingUtilities.updateComponentTreeUI(myframe);

A dialog that allows the user to change the currently installed look-and-feel of a running application should probably let the user choose among all the look-and-feels that are installed on the system. An application can find out the classnames and human-readable names of all the installed look-and-feels on a given system by calling the static getInstalledLookAndFeels() method of UIManager. In the implementation from Sun, this method returns either a default list of installed look-and-feels or a list obtained from the swing.properties file of the installation.

3.22.1 Using Themes with the Metal Look-and-Feel

You can customize the colors and fonts of the default Java look-and-feel by subclassing the DefaultMetalTheme class that appears in the javax.swing.plaf.metal package. When you create a custom subclass, you can specify the six different fonts and six different colors used by the Metal look-and-feel. For example, you might implement a large font theme for users who have difficulty reading the default fonts used by Metal.

If you are feeling brave and want to second-guess the skilled designers who put the Metal look-and-feel together, you can subclass the abstract MetalTheme class directly. This class defines many methods that return colors and fonts. All of these methods, however, are implemented in terms of the six basic font methods and six basic color methods of the DefaultMetalTheme class.

If you look at the DefaultMetalTheme API, you'll notice that the font and color methods do not return java.awt.Font and java.awt.Color objects as you would expect. Instead, they return FontUIResource and ColorUIResource objects. Both of these classes are part of the javax.swing.plaf package and are trivial subclasses of the more familiar Font and Color classes. The only thing these subclasses do is implement the UIResource interface. But UIResource is a marker interface, with no methods of its own. Thus, a FontUIResource is a Font object that also happens to implement UIResource. Similarly, a ColorUIResource is both a Color object and a UIResource object.

The currently installed look-and-feel assigns default values for many properties of Swing components. A look-and-feel implementation needs to be able to distinguish between default values it has specified and programmer-supplied property values. For this reason, all look-and-feel defaults, such as colors and fonts, must implement the UIResource marker interface. For our purposes here, you can subclass DefaultMetalTheme and use the FontUIResource and ColorUIResource classes exactly as you would use normal Font and Color resources.

Once you have created your own theme by subclassing MetalTheme or DefaultMetalTheme, you can install it with code like this:

MetalLookAndFeel.setCurrentTheme(new MyCustomTheme());
If you are changing the current theme after having already created Swing components, you also have to reinstall the MetalLookAndFeel and notify all the components of the change:
UIManager.setLookAndFeel(new MetalLookAndFeel());
SwingUtilities.updateComponentTreeUI(myRootFrame);

3.22.2 Auxiliary Look-and-Feels

If you've browsed the list of Swing packages, you've probably noticed javax.swing.plaf.multi. This is the multiplexing look-and-feel. It allows one or more auxiliary look-and-feels to be used in conjunction with a single primary look-and-feel. The multiplexing look-and-feel is automatically used by a Swing application if an auxiliary look-and-feel has been requested. An application can request an auxiliary look-and-feel by calling the static UIManager method addAuxiliaryLookAndFeel(), while an end user can do this by setting the swing.auxiliarylaf property on a Java command line.

The primary purpose of auxiliary look-and-feels is for accessibility. For example, a person with impaired vision might start up a Java application using the -Dswing.auxiliarylaf= option to specify that the application should load a screen-reader look-and-feel. Auxiliary look-and-feels can be used for other purposes as well, of course. You might use an auxiliary look-and-feel to add audio feedback to a user interface. Such a look-and-feel might produce an audible click when the user clicks on a JButton, for example.

Swing is not shipped with any predefined auxiliary look-and-feels. You can implement your own, of course, although explaining how to do so is beyond the scope of this book.

3.23 Accessibility

The term accessibility refers to the architectural features of Swing that allow Swing applications to interact with assistive technologies, such as a visual macro recorder that allows users to automate repetitive point-and-click tasks or a screen reader.

To enable accessibility, every Swing component implements the Accessible interface, which, like all accessibility-related classes, is part of the javax.accessibility package. This interface defines a single getAccessibleContext() method that returns an AccessibleContext object for the component. The methods of AccessibleContext export salient information about the component, such as a list of its accessible children and its name, purpose, and description. An assistive technology can use the tree of AccessibleContext objects to gather information about a GUI and assist the user in interacting with that GUI.

A number of the AccessibleContext methods return objects that implement specialized interfaces to return specific types of accessibility information. For example, if an accessible component represents a numeric value of some sort (say a JSlider), the getAccessibleValue() method of its AccessibleContext object returns an AccessibleValue object that provides more information about that value and allows the assistive technology to query and set the value.

The interfaces and classes of the javax.accessibility package provide methods that allow an assistive technology to "read" a GUI. Many of the methods defined by these interfaces duplicate functionality already provided by Swing components. The point, however, is that java.accessibility defines a standard API for interaction between any assistive technology and any accessible application. In other words, the accessibility API is not Swing specific. You can write JavaBeans and other custom components so that they support accessibility. If you do, these components automatically work with assistive technologies.

The details of the javax.accessibility package are of interest to programmers who are creating assistive technologies and developing accessible components or JavaBeans. Unfortunately, the details of these tasks are beyond the scope of this book.

Most of us are not developing assistive technologies and only rarely do we have to create accessible components. What we all want to do, however, is create accessible applications. Since all Swing components support accessibility, it is quite simple to create an accessible application with Swing. The key to supporting accessibility is providing the necessary information that allows an assistive technology to interpret your GUI for a user. The most commonly used example of an assistive technology is a screen reader for the vision impaired. A screen reader needs to be able to verbally describe a GUI to a user who cannot see it. In order to do this, it needs to have names and descriptions for all the critical components in your GUI.

The easiest way to assign a description to a component is to give it a tooltip. This way, your accessibility information also serves as context-sensitive help for novice users:

continue.setToolTipText("Click here to continue");
If, for some reason, you want to assign an accessible description to a component without giving it a tooltip, you can use code like this:
continue.getAccessibleContext().setAccessibleDescription("Continue button");

It is also helpful to assistive technologies if you provide names for your various components. A name should be a short human-readable string that uniquely identifies the component, at least within the current window or dialog box. Buttons, labels, menu items, and other components that display labels simply use those labels as their accessible names. Other components need to have names assigned. Here is one way to do that:

JTextField zipcode = new JTextField();
zipcode.getAccessibleContext().setAccessibleName("zipcode");

In a GUI, important components that do not display their own labels are often associated with JLabel components that serve to identify them. When this is the case, you can use the setLabelFor() method of JLabel to set the accessible name of the other component. The code might look like this:

JLabel zipcodeLabel = new JLabel("Zipcode");
JTextField zipcode = new JTextField();
zipcodeLabel.setLabelFor(zipcode);

By taking the simple step of assigning names and descriptions to your GUI components, you ensure that your application can be interpreted by assistive technologies and successfully used by all users.

3.24 Custom Components

We'll conclude this survey of Swing features with a quick look at what it takes to write a custom Swing component. Creating a custom component is a matter of subclassing an existing component and adding the new functionality you desire. Sometimes this is a simple job of adding a minor new feature to an existing component. At other times, you may want to create an entirely new component from scratch. In this case, you'll probably be subclassing JComponent, which is a bit more complicated. The following sections briefly explain the various things you'll need to consider when creating such a custom component. The best way to learn to write your own Swing-style components is to study the source code of Swing components, and since Sun makes this source code freely available, I encourage you to examine it.

3.24.1 Properties

You need to decide what properties you want your component to export and define accessor methods that allow them to be set and queried. If your component represents or displays some kind of nontrivial data structure, consider representing the data in a separate model object. Define an interface for the model and a default implementation of the interface.

If you think that other objects may be interested in property changes on your component, have the set methods for those properties generate the events PropertyChangeEvent or ChangeEvent and include appropriate event listener registration methods in your component. This kind of notification is often important if you follow the Swing architecture and divide the functionality of your component among a component object, a model object, and a UI delegate object.

When a property is set on your component, the component may need to be redrawn or resized as a result. You must keep this in mind when you write the property accessor methods for your component. For example, if you define a setColor() method, this method should call repaint() to request that the component be repainted. (Painting the component is a separate topic that is discussed later.) If you define a setFont() method and a change in font size causes the component to require more (or less) space on the screen, you should call revalidate() to request a relayout of the GUI. Note that the repaint() and revalidate() methods add a repaint or relayout request to a queue and return right away. Therefore, you may call these methods freely without fear of inefficiency.

3.24.2 Events

You need to decide what kind of events your component generates. You can reuse existing event and listener classes, if they suit your purposes, or you can define your own. Add event listener registration and deregistration methods in your component. You need to keep track of the registered listeners, and you may find the javax.swing.event.EventListenerList helpful for this task. For each event listener registration method, it is common practice to define a protected method to generate and fire an appropriate event to all registered listeners. For example, if your component has a public addActionListener() method, you may find it useful to define a protected fireActionEvent() method as well. This method calls the actionPerformed() method of every registered ActionListener object.

3.24.3 Constructors

It is customary to provide a no-argument constructor for a component. This is helpful if you want your component to work with GUI builder tools, for example. In addition, think about how you expect programmers to use your component. If there are a few properties that are likely to be set in most cases, you should define a constructor that takes values for these properties as arguments, to make the component easier to use.

3.24.4 Drawing the Component

Almost every component has some visual appearance. When you define a custom component, you have to write the code that draws the component on the screen. There are several ways you can do this. If you are creating an AWT component, override the paint() method and use the Graphics object that is passed to it to do whatever drawing you need to do.

For Swing components, the paint() method is also responsible for drawing the border and the children of your component, so you should not override it directly. Instead, override the paintComponent() method. This method is passed a Graphics object, just as the paint() method is, and you use this Graphics object to do any drawing you want. As we'll see in Chapter 4, Graphics with AWT and Java 2D, you can cast this Graphics object to a Graphics2D object if you want to use Java 2D features when drawing your component. Keep in mind, however, that a Swing component can be assigned an arbitrary border. Your paintComponent() method should check the size of the border and take this value into account when drawing.

When you define a custom component, you typically have only one look-and-feel in mind, so you can hardcode this look-and-feel as part of the component class itself. If you want your component to support the Swing pluggable look-and-feel architecture, however, you need to separate the drawing and event-handling tasks out into a separate javax.swing.plaf.ComponentUI object. If you do this, you should not override your component's paintComponent() method. Instead, put the painting functionality in the paint() method of the ComponentUI implementation. In order to make this work, you have to override the getUIClassID(), getUI(), setUI(), and updateUI() methods of JComponent.

3.24.5 Handling Events

Most components have some kind of interactive behavior and respond to user-input events such as mouse clicks and drags and key presses. When you are creating a custom component, you must write the code that handles these events. The Swing event-handling model was discussed in Chapter 2. Recall that the high-level way to handle input events is to register appropriate event listeners, such as MouseListener, MouseMotionListener, KeyListener, and FocusListener on your component. If you are using a separate UI delegate object, this object should implement the appropriate listener interfaces, and it should register itself with the appropriate event registration methods on the component when its installUI() method is called.

If you are not using a UI delegate, your component class can handle events at the lower level discussed in Chapter 2. To do this, you override methods such as processMouseEvent(), processMouseMotionEvent(), processKeyEvent(), and processFocusEvent(). In this case, be sure to register your interest in receiving events of the appropriate type by calling enableEvents() in your component's initialization code.

3.24.6 Component Size

Most components have a natural or preferred size that often depends on the settings of various component properties. Many components also have a minimum size below which they cannot adequately display themselves. And some components have a maximum size they wish to enforce. You must write the methods that compute and return these sizes.

If you are using a UI delegate object, you should implement the getMinimumSize(), getPreferredSize(), and getMaximumSize() methods in the delegate. The default JComponent methods call the delegate methods to determine these sizes if the programmer using the component has not overridden the minimum, preferred, or maximum sizes with her own specifications.

If you are not using a UI delegate object, you should override these three methods in the component itself. Ideally, your methods should respect any sizes passed to setMinimumSize(), setPreferredSize() and setMaximumSize(). Unfortunately, the values set by these methods are stored in private fields of JComponent, so you typically have to override both the get and the set methods.

3.24.7 Accessibility

It is a good idea to make your component accessible. In order to do this, your component must implement the javax.accessibility.Accessible interface and its getAccessibleContext() method. This method must return an AccessibleContext object that is customized for your component. You typically implement AccessibleContext as an inner class of the component by extending JComponent.AccessibleJComponent or some subclass of that class. Depending on your component, you may need to implement various other accessibility interfaces on this inner class as well. Studying the accessibility code in existing Swing components can be very helpful in learning how to write your own accessible components. You might start, for example, with the source code for AbstractButton.AccessibleAbstractButton.

3.24.8 Miscellaneous Methods

JComponent defines a number of other methods that you can optionally override to change aspects of a component's behavior. If you take a look at the list of properties defined by the JComponent API, you'll notice that a number of these are read-only properties (i.e., they do not define set methods). The only way to set the value returned by one of these methods is to subclass the method. In general, when you see a read-only property, you should consider it a candidate for subclassing. Here are a few methods of particular interest:

isOpaque()

If the component always fills its entire background, this method should return true. If a component can guarantee that it completely paints itself, Swing can perform some drawing optimizations. JComponent actually does define a setOpaque() method for this property, but your custom component may choose to ignore setOpaque() and override isOpaque().

isOptimizedDrawingEnabled()

If your component has children and allows those children to overlap, it should override this method to return false. Otherwise, leave it as is.

isFocusTraversable()

If your component wants to be included in focus traversal, it should override this method to return true. If your component does not want to be included in the keyboard navigation system, this method should return false.

isFocusCycleRoot()

If your component has children and wants to cycle focus among them, override this method to return true.

isManagingFocus()

If your component needs to receive the Tab and Shift-Tab key events that are normally handled by the focus manager, override this method to return true. If you do, the focus manager uses Ctrl-Tab instead.

Back to: Java Foundation Classes in a Nutshell


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.