Library Zone Articles
External Articles
Byte Size

Discovery Zone Catalogue
Diary
Links
Bookstore
Interactive Zone Ask the Gurus
Discussion Groups
Newsletters
Feedback
Etc Cartoons
Humour
COMpetition
Advertising ASP Web Ring ASP Web Ring
iDevJobs.com - Jobs for Professional Developers
The Developer's Resource & Community Site
COM XML ASP Java & Misc. NEW: VS.NET
International This Week Forums Author Central Find a Job
Buy the book: Java Foundation Classes in a Nutshell

Java Foundation Classes in a Nutshell: Swing Programming Topics

Reproduced with kind permision of O'Reilly & Associates: www.oreilly.com

Contents:
Versions of Swing
Labels and HTML
Actions
Tooltips
Timers
The Event Dispatch Thread
Client Properties
Keyboard Shortcuts
Serialization
Borders
Icons
Cursors
Double-Buffering
The Box Container
Simple Dialogs
JFileChooser
JColorChooser
Menus
JTree and TreeModel
JTable and TableModel
JTextComponent and HTML Text Display
Pluggable Look-and-Feel
Accessibility
Custom Components

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 https://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

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

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

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();
      

Next Page....


Click here

Contribute to IDR:

To contribute an article to IDR, a click here.

To contact us at IDevResource.com, use our feedback form, or email us.

To comment on the site contact our webmaster.

Promoted by CyberSavvy UK - website promotion experts

All content © Copyright 2000 IDevResource.com, Disclaimer notice

WTL Introduction

Visit the IDR Forums

Java COM integration

Learn C#

WTL Architecture by Richard Grimes