|
|
|
|
Java Foundation Classes in a NutshellBy David Flanagan1st Edition September 1999 1-56592-488-6, Order Number: 4886 748 pages, $29.95 |
Chapter 3 Swing Programming Topics
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 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, andJToolTipclasses. It is not supported (at least in Java 1.2.2) byJCheckBoxorJRadioButton, however. Formatted text display is particularly useful withJOptionPanedialog boxes (described later in this chapter), as they display text using internalJLabelobjects.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.Actioninterface that encapsulates information about such an operation. TheActioninterface extends theActionListenerinterface, so it contains theactionPerformed()method. It is this method that you implement to actually perform the desired action. EachActionobject 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, eachActionobject has anenabledproperty and asetEnabled()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
Actionobject directly to aJMenuorJToolBarcomponent. When you do this, the component automatically creates aJMenuItemorJButtonto 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, theJMenuItemorJButtoncomponent 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
JMenuBarorJToolBarto 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, bothJMenuBarandJToolBardisplay an action's textual name and its icon.The
Actioninterface helps you implement a clean separation between GUI code and application logic. Remember, however, that you cannot just instantiateActionobjects directly. SinceActionis a kind ofActionListener, you must define an individual subclass ofActionthat implements theactionPerformed()method for each of your desired actions. TheAbstractActionclass is helpful here; it implements everything except theactionPerformed()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. ThistoolTipTextproperty is inherited fromJComponent, 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
enabledproperty of theToolTipManagerobject. The code looks like this:ToolTipManager.sharedInstance().setEnabled(false);3.5 Timers
The
javax.swing.Timerobject generates single or multipleActionEventevents at time intervals that you specify. Thus, aTimeris 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 aTimerobject 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 aTimer.You use
Timerobjects just like regular components. ATimerhas property accessor methods and anaddActionListener()method that you can use to add event listeners. TheinitialDelayproperty specifies how many milliseconds theTimerwaits before firing its firstActionEvent. If therepeatsproperty istrue, theTimergenerates a newActionEventeach timedelaymilliseconds passes. When an application (or the system in general) is very busy or when thedelayproperty is very small, the timer may fire events faster than the application can process them. If thecoalesceproperty istrue, theTimercombines multiple pending events into a singleActionEvent, 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()andSwingUtilities.invokeAndWait(). You pass aRunnableobject to each method, and therun()method of this object is invoked from the event thread.invokeLater()returns right away, regardless of when therun()method is invoked, whileinvokeAndWait()does not return until therun()method has completed.The
invokeLater()andinvokeAndWait()methods do not run yourRunnableobject right away. Instead, each method encapsulates theRunnableobject within a special event object and places the event on the event queue. Then, when all pending events have been handled, theRunnableobject is extracted from the event queue and the event dispatch thread calls itsrun()method. This means thatinvokeLater()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,
JComponentincludes 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 theputClientProperty()andgetClientProperty()methods. Since these areJComponentmethods, 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 aStringobject.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
JMenuthat contains 10JMenuItemcomponents. Each component notifies the sameActionListenerobject 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 thesetActionCommand()method (inherited fromAbstractButton) to associate a string with each of theJMenuItemcomponents. 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 aStringin 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 eachJMenuItem.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
JInternalFrameis being used as a floating palette, set this client property toBoolean.TRUEto change the look of the border."JScrollBar.isFreeStanding"
JScrollPanesets this client property toBoolean.FALSEon theJScrollBarcomponents it creates."JSlider.isFilled"Setting this client property of a
JSlidertoBoolean.TRUEcauses the slider to display a different background color on either side of the slider thumb."JToolBar.isRollover"Setting this client property to
Boolean.TRUEon aJToolBarcauses the component to highlight the border of whatever child component the mouse is currently over."JTree.lineStyle"This client property specifies how the
JTreecomponent 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
JButtonis activated when it receives aKeyEventthat tells it that the user pressed the Spacebar or the Enter key. Similarly,JMenuandJListrespond 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
JLabelobjects, 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 thenextFocusableComponentproperty 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 returnstrue, 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 nosetFocusCycleRoot()method: the only way you can change this behavior is by subclassing a container and overriding theisFocusCycleRoot()method. Also note that multiline text components such asJTextAreaandJEditorPaneuse 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 callrequestFocus()themselves under certain circumstances, such as when they are clicked on. If you do not want a component to respond torequestFocus()calls, set itsrequestFocusEnabledproperty tofalse. For example, you might set this property on aJButtonso 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.FocusManagerobject. You can obtain this object withFocusManager.getCurrentFocusManager(). If you implement your own manager, you can install it withFocusManager.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 fromAbstractButton):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.KeyStrokeobject 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 callingsetAccelerator(). To obtain an appropriateKeyStrokeobject, call the staticKeyStroke.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
JScrollPaneto 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-ActionListenerbindings. When a particular keystroke is bound to anActionListener, the component will perform the action (i.e., invoke theactionPerformed()method) when the user types the keystroke. You can register a keyboard shortcut for a component withregisterKeyboardAction(). 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
scrollaction. When the user types one of these keystrokes, theactionPerformed()method is passed anActionEventobject. ThegetActionCommand()method of thisActionEventreturns one of the strings "lineup", "linedown", "pageup", or "pagedown". The hypotheticalscrollaction 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 ofWHEN_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 isWHEN_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 aJTree.3.8.4 Keymaps
Swing supports a general, yet powerful text-editing subsystem. The
javax.swing.text.JTextComponentis the base component in this system; it is the superclass ofJTextField,JTextEditor, andJEditorPane, among others.Because text editing typically involves many keyboard shortcuts, Swing defines the
javax.swing.text.Keymapinterface, which represents a set ofKeyStroke-to-Actionbindings. 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. AKeymapcan have a parentKeymapfrom 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 aKeymapthan to register each one individually withregisterKeyboardAction().
JTextComponentdefinesgetKeymap()andsetKeymap()methods you can use to query and set the current keymap of a text component. There are no public implementations of theKeymapinterface, so you cannot instantiate one directly. Instead, create a newKeymapby calling the staticJTextComponent.addKeymap()method. This method allows you to specify a name and parent for the newKeymap. Both arguments are optional, however, so you may pass innull.3.9 Serialization
The AWT
Componentclass implements thejava.io.Serializablemarker interface, andJComponentreimplements 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.ObjectOutputStreamclass and reconstruct a serialized component with thejava.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
borderproperty fromJComponent, so you can callsetBorder()to specify aBorderobject for a Swing component. ThisBorderobject displays some kind of decoration around the outside of the component. Thejavax.swing.borderpackage contains thisBorderinterface 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 BevelBorderGives the component a beveled edge that makes it appear raised or lowered.
CompoundBorderCombines two other
Bordertypes to create a compound border.EmptyBorderA border with no appearance. This is a useful way to place an empty margin around a component.
EtchedBorderDraws a line around the component, using a 3D effect that makes the line appear etched into or raised out of the surrounding container.
LineBorderDraws a line, with a color and thickness you specify, around the component.
MatteBorderDraws the border using a solid color or a tiled image. You specify the border dimensions for all four sides.
SoftBevelBorderLike
BevelBorder, but with somewhat more complex graphics that give the bevel a softer edge.TitledBorderA border that combines text with an
EtchedBorderor any other border you specify.Figure 3.2: Swing border styles
The
Borderimplementations defined injavax.swing.bordercover just about every possible border you are likely to want to display. But if you ever find yourself needing a specialized border, simply implement theBorderinterface yourself.Most of the
Borderimplementations injavax.swing.borderare immutable objects, designed to be shared. If two components have the same style of border, they can use the sameBorderimmutable object. Thejavax.swing.BorderFactoryclass contains static methods that return various commonly usedBorderobjects 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.Imageclass to represent these graphic elements. Instead, however, it usesjavax.swing.Icon. This interface represents a graphic element more generally. ItspaintIcon()method is called to display the graphic, and this method can do anything necessary to display it.Swing includes an
Iconimplementation calledImageIcon. This commonly used class is anImage-based implementation ofIcon.ImageIconalso simplifies the process of reading images from external files. One of the constructors forImageIconsimply 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 givenImage, which can be used to create anImageIconthat 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.Frameand they could be specified only for these top-levelFramecomponents. TheseFrameconstants and the correspondingsetCursor()method ofFrameare now deprecated.Java 1.1 included a new
java.awt.Cursorclass and defined a newsetCursor()method for allComponentobjects. Even though cursors had a class of their own in Java 1.1, theCursor()constructor and theCursor.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
Cursorclass has a newgetSystemCustomCursor()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 thecreateCustomCursor()method of theToolkitobject. First, however, the application should check whether custom cursors are supported, by calling thegetBestCursorSize()method of theToolkit. 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 fromJComponent) to set thedoubleBufferedproperty totruefor 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.Boxcontainer.[ 1]Boxarranges its components into a single row or a single column. You can then use nestedBoxcontainers to create a two-dimensional arrangement of components.[1] For some reason,
Boxdoes not begin with the letter J as other Swing components and containers do. Nevertheless, it is a very useful and commonly used container.The
Boxcontainer uses theBoxLayoutlayout manager, but this layout manager is automatically assigned, so you never need to work with it explicitly. The easiest way to create aBoxis with the staticBox.createHorizontalBox()orBox.createVerticalBox()method. Once you have created aBox, simply add children to it. They will be arranged from left to right or from top to bottom.The unique power of the
Boxactually comes from an inner class calledBox.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 createBox.Fillerobjects directly. Instead, you create them using the following static methods ofBox:Box.createHorizontalStrut(intwidth) Box.createVerticalStrut(intheight) 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, usecreateVerticalStrut()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
JButtoncomponent, and some more horizontal glue. Now, no matter how wide the row becomes, theJButtonis always centered in it. This is because the two glue components (and possibly theJButton) grow equally to take up the extra space. On the other hand, if the row consists of only one glue component followed by aJButton, theJButtonalways 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
Boxused 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
Boxcontainer 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 aJButton, the natural size is the space required to accommodate the button text and/orIcon, plus the space required for the button border. By default, aJButtonreports its natural size as its minimum size and as its preferred size. When asked for its maximum size, aJButtonreturns 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
JButtonto become arbitrarily large as its container grows larger, you can set a maximum size for it by callingsetMaximumSize(). Setting a preferred size for aJButtonis an uncommon thing to do, asJButtonhas a perfectly good natural size. But some components, such asJScrollPaneobjects, do not have a natural size. For components like these, it is usually important that you establish a default size withsetPreferredSize(). If you want to prevent aJScrollPaneor similar component from becoming arbitrarily small or arbitrarily large, you should also callsetMinimumSize()andsetMaximumSize().Now that you understand the concepts of minimum, preferred, and maximum sizes, we can return to the
Boxcontainer and its struts and glue. Both struts and glue are instances of theBox.Fillercomponent. When you create aBox.Filler, you are actually specifying minimum, preferred, and maximum sizes for the component. A horizontal strut is simply aBox.Fillerwith 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
Boxcontainer distributes excess space to its children. If a horizontalBoxbecomes 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 intersperseJButtonobjects 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.Fillercomponents 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
Boxcontainer has covered only how components are arranged horizontally in a horizontal box or vertically in a vertical box. What doesBoxdo in the other dimension? When laying out components in a row, theBoxmakes 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,Boxtries 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
Boxmust 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
Boxpositions such a component based on itsalignmentXoralignmentYproperty. Each is afloatproperty 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, theBoxuses thealignmentXproperty. 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 aBoxneeds to position a component vertically in a row, it uses the component'salignmentYproperty 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.JOptionPaneis a Swing component that is designed to serve as a highly configurable body of a dialog box. Instead of using theJOptionPanedirectly, however, most Swing programs use one or more of the many static methods defined byJOptionPane. 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 withshowand another whose names begin withshowInternal. Theshowmethods display simple dialog boxes withinJDialogwindows, while theshowInternalmethods display the same dialog boxes insideJInternalFramewindows. These static methods are further broken down by the type of dialog they display. There are several versions ofshowMessageDialog(),showConfirmDialog(), andshowInputDialog(), as well asshowInternalversions 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
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 specifynull, 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 themessageargument to this and otherJOptionPanemethods is defined as anObject, not aString. This means that you are not limited to textual messages. If you pass aComponentor anIcon, theJOptionPanedisplays it as the message. If you pass some other kind of object,JOptionPaneattempts to convert it to a string by calling itstoString()method. You can even pass an array of objects as themessageargument. 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 ofStringobjects, instead of a single longString.The
showMessageDialog()function has variants that take more arguments. Thetitleargument specifies the text to appear in the titlebar of the dialog. ThemessageTypeargument specifies the general type of the message. Legal values are theJOptionPaneconstants that end with_MESSAGE. The values you are most likely to use areINFORMATION_MESSAGE,WARNING_MESSAGE, andERROR_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 ofshowMessageDialog()that lets you specify your owniconto display.3.15.2 Confirm Dialogs
You can use
JOptionPane.showConfirmDialog()orJOptionPane.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
The arguments to
showConfirmDialog()are much like the arguments toshowMessageDialog(), with the addition of theoptionTypeargument. This argument specifies the set of buttons that appears at the bottom of the dialog. Legal values areOK_CANCEL_OPTION,YES_NO_OPTION, andYES_NO_CANCEL_OPTION.A confirm dialog asks the user a question. The return value of
showOptionDialog()orshowInternalOptionDialog()is an integer that represents the user's answer in terms of the button the user clicked to dismiss the dialog. The possible values areOK_OPTION,YES_OPTION,NO_OPTION,CANCEL_OPTION, andCLOSED_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 themessageargument):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()andshowInternalInputDialog()methods are designed to ask for input that is more complex than a yes-or-no answer. The simple versions ofshowInputDialog()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 toshowMessageDialog()andshowConfirmDialog(). 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 choice3.16 JFileChooser
javax.swing.JFileChooseris a specialized component that allows the user to browse the filesystem and select a file. The easiest way to use it is with theshowOpenDialog()andshowSaveDialog()methods. These methods differ only in the text that appears in the "Okay" button. You can also call theshowDialog()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 isAPPROVE_OPTION, the user actually selected a file, which you can obtain with thegetSelectedFile()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()andshowOpenDialog()are instance methods, not static methods like those used withJOptionPane. This means that you can customize the dialog by setting properties on yourJFileChooserobject. You may be interested in setting thecurrentDirectoryandfileSelectionModeproperties before you display aJFileChooser.fileSelectionModecan be set toFILES_ONLY,DIRECTORIES_ONLY, orFILES_AND_DIRECTORIES. Once you create aJFileChooserfor an application, you may want to reuse it, rather than creating a new one each time you need one. If you do so, theJFileChooserautomatically remembers thecurrentDirectorymost recently selected by the user.3.16.1 Using File Filters
The
javax.swing.filechooserpackage defines auxiliary classes that are used byJFileChooser. One of the most important of these isFileFilter. The abstractjavax.swing.filechooser.FileFilterclass is much like thejava.io.FileFilterinterface. Each defines anaccept()method that is passedFileobjects and returnstruefor each file that should be displayed. TheFileFilterclass used byJFileChooserhas an additionalgetDescription()method that returns a string that names the types of files accepted by the filter. For example, you might define aFileFiltersubclass 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 theFileFilterit is to use withsetFileFilter(). Alternately, you can specify an array ofFileFilterobjects withsetChoosableFileFilters(). In this case,JFileChooserdisplays the descriptions of the filters and allows the user to choose one.3.16.2 Customizing JFileChooser
The behavior of a
JFileChoosercan be customized by providing your own implementation ofFileViewandFileSystemView. Both of these abstract classes are defined in thejavax.swing.filechooserclass.FileViewdefines methods that affect the way individual files are displayed by theJFileChooser, whileFileSystemViewdefines methods that enable theJFileChooserto handle operating-system dependencies in the filesystem.FileSystemViewunderstands the notion of hidden files, and it can return a complete list of filesystem roots, a capability that was lacking from the basicjava.io.Fileclass prior to Java 1.2. The defaultFileViewandFileSystemViewclasses provided byJFileChooserare perfectly adequate for most purposes, so you typically don't have to implement these classes yourself.It is also possible to customize a
JFileChooserby providing an accessory component. If you pass aJComponentto thesetAccessory()method ofJFileChooser, 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 thePropertyChangeListenerinterface and listening for changes to theselectedFileproperty. In order for this to work, you have to pass the accessory object to theaddPropertyChangeListener()method of theJFileChooser, of course.3.17 JColorChooser
Just as
JFileChooserallows the user to choose a file,javax.swing.JColorChooserallows the user to choose a color. Figure 3.6 shows aJColorChooserdialog. You can embed aJColorChoosercomponent directly in your application or in a custom dialog box, but the most common way to use it is to simply call the staticshowDialog()method:Color c = JColorChooser.showDialog(contentpane, // Dialog appears over this "Pick a Color", // Dialog title Color.white); // Default color selectionFigure 3.6: A JColorChooser dialog
As you can see from Figure 3.6,
JColorChooserdisplays a color selection pane and a color preview pane. The selection pane is actually aJTabbedPanethat 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
JColorChooserwith the staticshowDialog()method, you can create your own instance of theJColorChooserclass. You can then set properties on the color chooser object and display it in any way you want. The staticJColorChooser.createDialog()method is useful here. It creates a dialog box to hold yourJColorChooserpane and allows you to specify twoActionListenerobjects that are invoked in response to the OK and Cancel buttons in the dialog box.You can customize a
JColorChooserby 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 ofAbstractColorChooserPanel(from thejavax.swing.colorchooserpackage) and pass it to theaddChooserPanel()method of yourJColorChooser. Your custom panel contains aColorSelectionModelthat serves as the interface between your pane and theJColorChooser. All your pane needs to do is update the selected color of itsColorSelectionModel(ColorSelectionModelis also part of thejavax.swing.colorchooserpackage).You can use any
JComponentas a custom preview panel for yourJColorChooser. Simply pass the component tosetPreviewPanel(). The preview component has to track the currently selected color by listening forChangeEventevents generated by theColorSelectionModelof theJColorChooser.3.18 Menus
In Swing, menu bars, menu panes, and menu items are components, just like all other Swing components.
JMenuBaris a container designed to holdJMenuobjects.JMenuis a container designed to holdJMenuItemobjects and otherJMenuobjects (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.JTreeclass is a powerful Swing component for displaying tree-structured data. Like all Swing components,JTreerelies 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. TheJTreecomponent, however, displays data that is much more complex than a typical Swing component. When you are working with aJTree, you must create a model object that implements thejavax.swing.tree.TreeModelinterface.One approach is to use the
DefaultTreeModelclass, which implements theTreeModelinterface using theTreeNodeandMutableTreeNodeinterfaces (all defined injavax.swing.tree). To useDefaultTreeModel, you must implement your hierarchical data structures so that each element of the tree implements theTreeNodeorMutableTreeNodeinterface. Now you can create aDefaultTreeModelobject simply by passing the rootTreeNodeof your tree to aDefaultTreeModelconstructor. Then you create aJTreecomponent to display your tree simply by passing theDefaultTreeModelto thesetModel()method of theJTree.Sometimes, however, you do not have the luxury of designing the data structures used to represent your tree, so implementing the
TreeNodeinterface is simply not an option. In this case, you can implement theTreeModelinterface directly. The resultingTreeModelobject serves as the interface between your data and theJTreecomponent that displays the data. YourTreeModelimplementation provides the methods that allow theJTreecomponent 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
TreeModelinterface to represent the hierarchical structure of the filesystem, thereby allowing the file and directory tree to be displayed in aJTreecomponent. Notice how a relatively simple implementation ofTreeModelenables the powerful tree- browsing capabilities shown in Figure 3.7.Figure 3.7: The JTree component
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.JTableis another powerful Swing component for displaying complex data structures. LikeJTree,JTablerelies 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 theTableModelinterface and its default implementations,AbstractTableModelandDefaultTableModel.If your table data is tidily organized, it is easy to use
JTablewithout worrying about theTableModel. If your data is an array of rows, where each row is an array of objects, you can just pass thisObject[][]directly to theJTableconstructor. If you want, you can also specify an optional array of column names. This is all you need to do: theJTabledoes the rest. This technique also works if your data is stored in aVectorof rows, where each row is itself aVector.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
TableModelinterface (or, more likely, subclass theAbstractTableModelclass). The job of thisTableModelimplementation is to serve as the interface between your data, which is not neatly organized into a table, and theJTableobject, which wants to display a table. In other words, yourTableModelpresents 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
Fileobject 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 simpleTableModelimplementation enables the use of the powerful table-display capabilities of theJTablecomponent.Figure 3.8: The JTable component
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 thejavax.swing.textpackage and generally is not used directly. Instead, you typically use one of its subclasses, such asJTextField,JPasswordField,JTextArea, orJEditorPane. 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
JEditorPanecomponent that really makes use of the full power ofJTextComponent.JEditorPanesupports the display and editing of complex formatted text. In conjunction with the classes in thejavax.swing.text.htmlandjavax.swing.text.rtfpackages,JEditorPanecan 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 aJEditorPaneto display an HTML document. If the desired document is available on the network, the easiest way to display it is simply to pass an appropriatejava.net.URLobject to thesetPage()method ofJEditorPane.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 theread()method ofJEditorPane. The second argument to this method should benull. For example:InputStream in = new FileInputStream("help.htm"); editor.read(in, null);Yet another way to display text in a
JEditorPaneis to pass the text to thesetText()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>");CallingsetText()can be particularly useful when your application generates HTML text on the fly and wants to use aJEditorPaneto 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 theJTablecomponent. 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 theJTableexample 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
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.defaultlafto 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 classjavax.swing.plaf.metal.MetalLookAndFeel. The end user can override this default by using the-Dswitch 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 appIf 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 appWhen 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 theUIManagerclass and specify the classname of the desired look-and-feel implementation. To make this even easier,UIManagerdefines 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 theswing.defaultlafproperty, 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-nativelookflag 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 callsetLookAndFeel()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 ofUIManager. In the implementation from Sun, this method returns either a default list of installed look-and-feels or a list obtained from theswing.propertiesfile 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
DefaultMetalThemeclass that appears in thejavax.swing.plaf.metalpackage. 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
MetalThemeclass 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 theDefaultMetalThemeclass.If you look at the
DefaultMetalThemeAPI, you'll notice that the font and color methods do not returnjava.awt.Fontandjava.awt.Colorobjects as you would expect. Instead, they returnFontUIResourceandColorUIResourceobjects. Both of these classes are part of thejavax.swing.plafpackage and are trivial subclasses of the more familiarFontandColorclasses. The only thing these subclasses do is implement theUIResourceinterface. ButUIResourceis a marker interface, with no methods of its own. Thus, aFontUIResourceis aFontobject that also happens to implementUIResource. Similarly, aColorUIResourceis both aColorobject and aUIResourceobject.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
UIResourcemarker interface. For our purposes here, you can subclassDefaultMetalThemeand use theFontUIResourceandColorUIResourceclasses exactly as you would use normalFontandColorresources.Once you have created your own theme by subclassing
MetalThemeorDefaultMetalTheme, 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 theMetalLookAndFeeland 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 staticUIManagermethodaddAuxiliaryLookAndFeel(), while an end user can do this by setting theswing.auxiliarylafproperty 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 aJButton, 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
Accessibleinterface, which, like all accessibility-related classes, is part of thejavax.accessibilitypackage. This interface defines a singlegetAccessibleContext()method that returns anAccessibleContextobject for the component. The methods ofAccessibleContextexport 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 ofAccessibleContextobjects to gather information about a GUI and assist the user in interacting with that GUI.A number of the
AccessibleContextmethods 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 aJSlider), thegetAccessibleValue()method of itsAccessibleContextobject returns anAccessibleValueobject 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.accessibilitypackage 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 thatjava.accessibilitydefines 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.accessibilitypackage 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
JLabelcomponents that serve to identify them. When this is the case, you can use thesetLabelFor()method ofJLabelto 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
setmethods for those properties generate the eventsPropertyChangeEventorChangeEventand 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 callrepaint()to request that the component be repainted. (Painting the component is a separate topic t