![]() ![]() ![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Book Excerpt Index
John Zukowski's Definitive Guide to Swing for Java 2
|
![]() |
Figure
2-7 shows what the PrintHelloAction might look like on a
JToolBar and a JMenu . Selectable buttons are provided
to enable or disable the Action as well as change its
name. |
Figure 2-7: The PrintHelloAction
in use
The complete source code for this example follows. Don't worry just yet about the specifics of creating toolbars or menu bars. They'll be discussed in more detail later.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ActionTester { public static void main( String args[]) { JFrame frame = new ExitableJFrame( "Action Sample"); final Action printAction = new PrintHelloAction(); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); menu.add(printAction); JToolBar toolbar = new JToolBar(); toolbar.add(printAction); JButton enableButton = new JButton( "Enable"); ActionListener enableActionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { printAction.setEnabled(true); } }; enableButton.addActionListener( enableActionListener); JButton disableButton = new JButton( "Disable"); ActionListener disableActionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { printAction.setEnabled(false); } }; disableButton.addActionListener( disableActionListener); JButton relabelButton = new JButton( "Relabel"); ActionListener relabelActionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { printAction.putValue( Action.NAME, "Hello World"); } }; relabelButton.addActionListener( relabelActionListener); JPanel buttonPanel = new JPanel(); buttonPanel.add(enableButton); buttonPanel.add(disableButton); buttonPanel.add(relabelButton); frame.setJMenuBar(menuBar); Container contentPane = frame.getContentPane(); contentPane.add( toolbar, BorderLayout.SOUTH); contentPane.add( buttonPanel, BorderLayout.NORTH); frame.setSize(300, 200); frame.setVisible(true); } }AbstractAction Properties
As Table 2-1 shows, the AbstractAction
class has only a single
bound property available from setter/getter methods.
PROPERTY NAME | DATA TYPE | ACCESS |
enabled | boolean | read-write bound |
The remainder of the bound properties are placed in the lookup table with putValue(String key, Object value). Table 2-2 describes the predefined set of Action constants that can be used as the key. You're not limited to using only this set, though.
Constant | Description |
NAME | Action name, used as button label. |
SMALL_ICON | Icon for action, used as button label. |
SHORT_DESCRIPTION | Short description of action; could be used as tooltip text, but not by default |
LONG_DESCRIPTION | Long description of action; could be used by accessibility |
DEFAULT | Unused constant that could be used for your own property |
Table 2-2: AbstractAction Lookup Property Keys
Once a property has been placed in the lookup table, you can get it with
public Object getValue(String key)
. It works similarly to the
java.util.Hashtable class
, with one distinction: If you try to put
a key-value
pair into the table with a null value, the table removes the key, if it's
present.
NOTE:There are many public subclasses of this class. Although you might never need to worry about any of them, this list is provided for those present in Swing 1.1:
TextAction
In some cases, you might find it useful to associate the preexisting Action with your own event trigger.
Creating an Action JButton
To let you see what happens behind the scenes when using an Action with a
JMenuBar
or a JToolBar
, we'll create a JButton that
accepts an Action for configuration. Surprisingly, this isn't available by
default. To make this example even more helpful, we'll provide built-in support
for tooltip textsomething that's not provided with the built-in Action support
for JMenu, JPopupMenu, or JToolBar
.
When extending a component to support an Action, you need to provide a constructor that accepts the Action as its sole argument. The constructor then maps all the Action attributes to the appropriate property of the component. We'll add a helper method to do the mapping, making the Action a full-fledged property for the button that can be changed later.
For a JButton
, the Action.NAME
and
Action.SMALL_ICON
are included for the label,
and they get mapped to the text and icon properties. The initial state of the
JButton must also match the state of the Action, so you need to ask for the
enabled property value of the Action. The tooltip text will be provided by the
Action.SHORT_DESCRIPTION
. Lastly, you'll need to associate the
Action as the ActionListener
for the JButton
.
To ensure that the button property values change when the Action changes, we
need to add our own PropertyChangeListener
to the Action. In
addition, if you associate a new Action with the button, you need to remember to
remove the old one. The source for the constructor and
setAction(Action)
method follow.
private Action action; PropertyChangeListener propertyChangeListener; public ActionButton(Action action) { setAction(action); } public void setAction(Action newValue) { // Disconnect current action; if ((action != null) && ( propertyChangeListener != null)) { action.removePropertyChangeListener( propertyChangeListener); removeActionListener(action); } action = newValue; if (action == null) { setText(""); setIcon(null); } else { setText((String)action.getValue( Action.NAME)); setIcon((Icon)action.getValue( Action.SMALL_ICON)); setEnabled(action.isEnabled()); String toolTipText = ( String)action.getValue( Action.SHORT_DESCRIPTION); if (toolTipText != null) { setToolTipText(toolTipText); } addActionListener(action); if (propertyChangeListener == null) { propertyChangeListener = new OurPropertyChangeListener(); } action.addPropertyChangeListener( propertyChangeListener); } }The real workhorse here is the
PropertyChangeListener
. It directs
specific Action property changes in order to modify the appropriate property setting of the JButton. The source for the listener follows.
class OurPropertyChangeListener implements PropertyChangeListener { public void propertyChange( PropertyChangeEvent propertyChangeEvent) { String propertyName = propertyChangeEvent.getPropertyName(); if (propertyName.equals( Action.NAME)) { String text = ( String)propertyChangeEvent.getNewValue( ); setText(text); } else if (propertyName.equals( Action.SMALL_ICON)) { Icon icon = ( Icon)propertyChangeEvent.getNewValue( ); setIcon(icon); } else if (propertyName.equals( Action.SHORT_DESCRIPTION)) { String text = ( String)propertyChangeEvent.getNewValue( ); setToolTipText(text); } else if (propertyName.equals( "enabled")) { Boolean enabledState = ( Boolean)propertyChangeEvent.getNewValue(); setEnabled(enabledState.booleanValue()); } } }
Figure 2-8: The ActionButton in use
By adding the following line of code to the ActionTester
example
shown in the section Class AbstractAction
, the three components in
the example share the same Action. Figure 2-8 shows the updated display with the
new component.
contentPane.add(new ActionButton(printAction), BorderLayout.CENTER);
The complete source for the ActionButton follows.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.beans.*; public class ActionButton extends JButton { class OurPropertyChangeListener implements PropertyChangeListener { public void propertyChange( PropertyChangeEvent propertyChangeEvent) { String propertyName = propertyChangeEvent.getPropertyName(); if (propertyName.equals(Action.NAME)) { String text = ( String)propertyChangeEvent.getNewValue(); setText(text); } else if (propertyName.equals( Action.SMALL_ICON)) { Icon icon = ( Icon)propertyChangeEvent.getNewValue( ); setIcon(icon); } else if (propertyName.equals( Action.SHORT_DESCRIPTION)) { String text = ( String)propertyChangeEvent.getNewValue( ); setToolTipText(text); } else if ( propertyName.equals("enabled")) { Boolean enabledState = ( Boolean)propertyChangeEvent.getNewValue( ); setEnabled(enabledState.booleanValue( )); } } } private Action action; PropertyChangeListener propertyChangeListener; public ActionButton() { } public ActionButton(Action action) { setAction(action); } public void setAction(Action newValue) { // Disconnect current action; if ((action != null) && ( propertyChangeListener != null)) { removeActionListener(action); } action = newValue; if (action == null) { setText(""); setIcon(null); } else { setText((String)action.getValue( Action.NAME)); setIcon((Icon)action.getValue( Action.SMALL_ICON)); setEnabled(action.isEnabled()); String toolTipText = (String)action.getValue( Action.SHORT_DESCRIPTION); if (toolTipText != null) { setToolTipText(toolTipText); } addActionListener(action); if (propertyChangeListener == null) { propertyChangeListener = new OurPropertyChangeListener(); } action.addPropertyChangeListener( propertyChangeListener); } } }NOTE: Notice that the
PropertyChangeListener
is
attached to the Action, and not to the JButton.
The KeyStroke class
and the public void
registerKeyboardAction(ActionListener
actionListener
, KeyStroke keyStroke,
int condition)
method of JComponent
provide a simple replacement for registering
KeyListener
objects to components and watching for specific keys to
be pressed. The KeyStroke class allows you to define a single combination of key
strokes, such as Shift-Ctrl-P or f4. You can then activate the keystroke by
registering it with a component and telling the keystroke what to do when the
component recognizes it, causing the ActionListener
to be notified.
Before finding out how to create keystrokes, let's look at the different
conditions that can be activated. Three conditions can activate a registered
keystroke, and there are four constants in JComponent
to help. The
fourth is for an undefined state. If you want the keystroke to activate when the
actual component has the input focus, you would use the
JComponent.WHEN_FOCUSED
constant. If you want the keystroke to
activate when the window that the component is in has the input focus,
you would use JComponent.WHEN_IN_FOCUSED_WINDOW
. The remaining
constant, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
, is used
when the keystroke is pressed in the component or a container of the component.
The four available constants are listed in Table 2-3.
Constants WHEN_FOCUSED WHEN_IN_FOCUSED_WINDOW
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
Table 2-3: Keystroke registration conditions
Creating a KeyStroke
The KeyStroke
class has no public constructor. You create a
keystroke by using
one of the static getKeyStroke() methods
plus the one
getKeyStrokeForEvent()
method, which follow:
public static KeyStroke getKeyStroke( char keyChar) public static KeyStroke getKeyStroke( String representation) public static KeyStroke getKeyStroke( int keyCode, int modifiers) public static KeyStroke getKeyStroke( int keyCode, int modifiers, boolean onKeyRelease) public static KeyStroke getKeyStrokeForEvent( KeyEvent keyEvent)The first version in this list,
public static KeyStroke getKeyStroke(
charkeyChar),
allows you to create a keystroke from a char variable,such a:
KeyStrokespace=KeyStroke.getKeyStroke('Z');
NOTE: I prefer to avoid the first method because as you don't
know whether to specify an uppercase or lowercase letter. There is also an
outdated, or deprecated, version of this constructor that adds a boolean
onKeyRelease
argument. This, too, should be avoided.
The public static KeyStroke getKeyStroke
(String representation)
version is the most interesting of the lot. It allows you to specify a keystroke
as a text string, such as control F4. The set of modifiers to the string are
shift | control | meta | alt | button1 | button2 | button3, and multiple
modifiers can be specified. The remainder of the string comes from one of the
many VK_* constants of the KeyEvent class
.
KeyStrokecontrolAlt7= KeyStroke.getKeyStroke("controlalt7");NOTE:The method I just listed didn't work prior to Swing 1.1.1.
The public static KeyStroke getKeyStroke(int keyCode, int
modifiers
) and public static KeyStroke
getKeyStroke(int keyCode,
int
modifiers, boolean onKeyRelease)
methods are the most clear-cut. They
allow you to directly specify the VK_* key constant and the InputEvent masks for
the modifiers (or zero for no modifiers). When not specified, onKeyRelease
is false.
KeyStroke enter = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0, true); KeyStroke shiftF4 = KeyStroke.getKeyStroke( KeyEvent.VK_F4, InputEvent.SHIFT_MASK );The last version listed,
public static KeyStroke
getKeyStrokeForEvent(KeyEvent
keyEvent)
, maps a specific KeyEvent directly to a KeyStroke. This is useful when you want to allow a user to supply the keystroke to activate an event. You ask the user to press a key for the event and then register the KeyEvent so that the next time it happens the event is activated.
KeyStroke fromKeyEvent = KeyStroke.getKeyStrokeForEvent(keyEvent);Registering a
KeyStroke
After you've created the keystroke, you need to register it with a component. When you register a keystroke with a component, you provide an ActionListener to call when pressed. The following example creates four buttons, each with a different keystroke registered to it and a possibly different focus activation condition, as listed in Table 2-3. The button label signifies the keystroke activation conditions. The ActionListener simply prints out a message.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class KeyStrokeSample { public static void main(String args[]) { JFrame frame = new ExitableJFrame("KeyStroke Sample"); JButton buttonA = new JButton( "<html><center>FOCUSED<br>control alt 7"); JButton buttonB = new JButton( "<html><center>FOCUS/RELEASE<br>VK_ENTER"); JButton buttonC = new JButton( "<html><center>ANCESTOR<br>VK_F4+SHIFT_MASK"); JButton buttonD = new JButton( "<html><center>WINDOW<br>' '"); // Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { System.out.println( "Activated"); } }; KeyStroke controlAlt7 = KeyStroke.getKeyStroke( "control alt 7"); buttonA.registerKeyboardAction( actionListener, controlAlt7, JComponent.WHEN_ FOCUSED); KeyStroke enter = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0, true); buttonB.registerKeyboardAction( actionListener, enter, JComponent.WHEN_FOCUSED); KeyStroke shiftF4 = KeyStroke.getKeyStroke( KeyEvent.VK_F4, InputEvent.SHIFT_MASK); buttonC.registerKeyboardAction( actionListener, shiftF4, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); KeyStroke space = KeyStroke.getKeyStroke(' '); buttonD.registerKeyboardAction( actionListener, space, JComponent.WHEN_IN_FOCUSED_WINDOW); Container contentPane = frame.getContentPane(); contentPane.setLayout(new GridLayout(2,2)); contentPane.add(buttonA); contentPane.add(buttonB); contentPane.add(buttonC); contentPane.add(buttonD); frame.setSize(400, 200); frame.setVisible(true); } }NOTE: If you try to run the previous program with JFC/Swing release 1.1, a
NullPointerException
will be generated.
Besides your creating KeyStroke
objects yourself, the Swing
libraries use KeyStroke objects
for several internal functions.
Two such instances of this are component mnemonics and accelerators. In a
component mnemonic, one character in a label appears underlined. When that
character is pressed using a platform-specific hot-key combination, the
component is activated. For instance, pressing Alt-A in Figure 2-9 would select
the About button on a Windows 98 platform.
Menu accelerators activate menu items when they're not visible. For instance, pressing Ctrl-P would select the Print menu item in Figure 2-9 when the File menu isn't visible. You'll learn more about mnemonics and accelerators in later chapters of this book.
Figure 2-9: Mnemonics and menu shortcuts
The term focus refers to when a component acquires the input focus. When a component has the input focus, it serves as the source for all key events, such as text input. In addition, certain components have some visual markings to indicate that they have the input focus, as shown in Figure 2-10. When certain components have the input focus, you can trigger selection with a keyboard key (usually the spacebar or Enter key), in addition to selection with a mouse. For instance, with a button, pressing the spacebar activates it.
Figure 2-10: A showing it has input focus
You can find out when the Swing component gets the input focus by registering a
FocusListener
, just as you'd do with all AWT 1.1 components.
Although this
doesn't change when you move from the AWT component set to the Swing component
set, what does differ is the set of methods used programatically to move the
focus around.
With AWT components, you can explicitly set the focus to a component with the
public void requestFocus() method
. In addition, you can transfer focus to the
next component with the public void transferFocus() method.
In the
AWT world, the next component is defined by the order in which components are
added to a container, as shown in Figure 2-11. By default, this focus traversal
starts at the top left of the figure and goes across each row and down to the
bottom right. When all the components are in the same container, this traversal
order is called a focus cycle and can be limited to remain within the
container.
Figure 2-11: Default focus ordering
NOTE: Besides your programmatically using
transferFocus()
to move the input focus, a user can press Tab or
Shift-Tab to move forward or backward around the components in a container.
To demonstrate the use of the two methods I mentioned earlier in this section,
we'll create a MouseListener
that moves the input focus to a
component when the mouse enters its space and an ActionListener that transfers
the input focus to the next component. The MouseListener
merely
needs to call requestFocus()
when the mouse enters the component.
import java.awt.*; import java.awt.event.*; public class MouseEnterFocusMover extends MouseAdapter { public void mouseEntered( MouseEvent mouseEvent) { Component component = mouseEvent.getComponent(); if (!component.hasFocus()) { component.requestFocus(); } } }For the
ActionListener
, you need to call
the transferFocus()
method for the event source.
import java.awt.*; import java.awt.event.*; public class ActionFocusMover implements ActionListener { public void actionPerformed( ActionEvent actionEvent) { Object source = actionEvent.getSource(); if (source instanceof Component) { Component component = ( Component)source; component.transferFocus(); } } }Because Swing components are built on top of AWT, these event handlers would work for Swing components, too. However, there's one difference with
requestFocus()
in Swing components: It can be turned off. Swing's
JComponent has a public void setRequestFocusEnabled(boolean newValue)
method
that allows you to give components the power to accept focus
requests. This differs from the read-only focusTraversable
property
of Component, which removes a component from the Tab focus cycle. When you set
the requestFocusEnabled property
to false for a Swing component and
someone then calls requestFocus()
on the component, it won't accept
the input focus. If you really want a component to get the input focus, Swing
provides a grabFocus()
method that can't be disabled.
We'll now use the two event handlers with some components that deny requesting a focus. For example, we'll create a 3-by-3 grid of buttons in which each button has an attached mouse listener and a focus listener. The even-numbered components permit requesting focus, whereas the odd-numbered ones don't. Because the odd buttons don't permit requesting focus, pressing an even button doesn't move the focus to the next component, nor does moving the mouse over an odd button. The only way an odd button can get the input focus is if the user presses the Tab/Shift-Tab keys as part of the focus cycle. Figure 2-12 shows the main window of the program.
Figure 2-12: Focus management example
The source code follows: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class FocusSample { public static void main(String args[]) { JFrame frame = new ExitableJFrame( "Focus Sample"); ActionListener actionListener = new ActionFocusMover(); MouseListener mouseListener = new MouseEnterFocusMover(); Container contentPane = frame.getContentPane(); contentPane.setLayout( new GridLayout(3,3)); for (int i=1; i<10; i++) { JButton button = new JButton("" + i); button.addActionListener( actionListener); button.addMouseListener( mouseListener); if ((i%2) != 0) { // odd - enabled //by default button.setRequestFocusEnabled(false); } contentPane.add(button); } frame.setSize(300, 200); frame.setVisible(true); } }In addition to the
requestFocusEnabled
property of
JComponent
and FocusTraversable
property of Component
that were just discussed, three additional focus-oriented properties of the
JComponent class exist.
NOTE: To change any of the read-only properties in Table 2-4, you must subclass the specific component you want to customize.
One customization option available at the Swing container level is the focus
cycle. Remember that the focus cycle for a container is a map of the focus
traversal order for the closed set of components. You can limit the focus cycle
to stay within the bounds of a container by overriding the public boolean
isFocusCycleRoot()
method to return true, thus restricting the focus traversal
from going beyond an inner container. Then, when a Tab key is pressed within the
last component of the container, the focus cycle will wrap back to the first
component in the container, instead of moving the input focus to the first
component outside the container. When Shift-Tab is pressed in the first
component, it wraps to the last component of the container, instead of to the
prior component in the outer container. Figure 2-13 illustrates how it would
look if you placed the middle three buttons from Figure 2-11 within a container
restricted in this way. Notice that there is no way of getting to the first
component on the third row by just pressing the Tab key to move forward.
WARNING: There seems to be a bug in the Swing 1.1 release with Shift-Tab in a focus-cycle- constrained container. Sometimes, it lets you out of the inner container.
Instead of having to recreate a focus-cycle-restricted container every time you need one, the following class definition is one you can keep handy for that use. Feel free to shorten the name.
import javax.swing.*; public class FocusCycleConstrainedJPanel extends JPanel { public boolean isFocusCycleRoot() { return true; } }Using the new FocusCycleConstrainedJPanel class, the following program demonstrates the behavior illustrated in Figure 2-13. The on-screen program will look just like Figure 2-12; it just behaves differently.
Figure 2-13: Restrictive focus cycle
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class FocusCycleSample { public static void main(String args[]) { JFrame frame = new ExitableJFrame( "Focus Cycle Sample"); Container contentPane = frame.getContentPane(); contentPane.setLayout( new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.weightx = 1.0; constraints.weighty = 1.0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.fill = GridBagConstraints.BOTH; // Row One constraints.gridy=0; for (int i=0; i<3; i++) { JButton button = new JButton("" + i); constraints.gridx=i; contentPane.add(button, constraints); } // Row Two JPanel panel = new FocusCycleConstrainedJPanel(); panel.setLayout(new GridLayout(1,3)); for (int i=0; i<3; i++) { JButton button = new JButton("" + (i+3)); panel.add(button); } constraints.gridx=0; constraints.gridy=1; constraints.gridwidth=3; contentPane.add(panel, constraints); // Row Three constraints.gridy=2; constraints.gridwidth=1; for (int i=0; i<3; i++) { JButton button = new JButton("" + (i+6)); constraints.gridx=i; contentPane.add(button, constraints); } frame.setSize(300, 200); frame.setVisible(true); } }
One other thing you can do at the component level is to subclass a Swing
component and override the public boolean isManagingFocus()
method
to return true. This allows you to manage focus traversal yourself by listening
for Tab key presses in a KeyListener
. Then when the Tab key is
pressed, you can consume()
the KeyEvent if you don't want the input
focus to move on, for example, because of an invalid data state. When consumed,
the FocusManager
would check the consumption status of the
KeyEvent
and not transfer the input focus if already consumed. The
FocusManager
class is described later in this chapter.
TIP Because the managingFocus property
of
JComponent is read-only, you must subclass a Swing component to internally
manage focus.
The following source demonstrates how you would abort the transferal of the
input focus in the keyPressed()
method of a KeyEvent
.
public void keyPressed( KeyEvent keyEvent) { if ((keyEvent.getKeyCode == KeyEvent.VK_TAB) || (keyEvent.getKeyChar( ) == '\t' )) { if (!(data is valid)) { // Define validation condition here keyEvent.consume(); } } }
The JComponent class allows you to programmatically reorder the forward
focus-traversal order by setting the nextFocusableComponent
property. Any AWT component and not just Swing components can be the next
focusable component. When this property is set for a component, it defines where
the input focus moves when the Tab key is pressed. No way exists to explicitly
set the response for Shift-Tab. The response is determined by the system's
finding the first component that has the current component as its
next component.
To demonstrate, the following program reverses the functionality of Tab and Shift-Tab. The screen for the program is shown in Figure 2-12, with the 3-by-3 set of buttons.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class NextComponentSample { public static void main(String args[]) { JFrame frame = new ExitableJFrame( "Next Component Sample"); Container contentPane = frame.getContentPane(); contentPane.setLayout(new GridLayout(3,3)); int COUNT = 9; JButton components[] = new JButton[COUNT]; for (int i=0; i<COUNT; i++) { JButton button = new JButton("" + (i+1)); components[i] = button; contentPane.add(button); } // Reverse tab order for (int i=0; i<COUNT; i++) { System.out.println(components[i].getText( ) + ":" + components[(i+COUNT-1) % COUNT].getText()); components[i].setNextFocusableComponent( components[(i+COUNT-1) % COUNT]); } frame.setSize(300, 200); frame.setVisible(true); } }
The abstract FocusManager
class in the Swing library serves as the
control mechanism framework for input focus behavior of Swing components. The
Swing FocusManager
is the basic control mechanism, but because
Swing components live within the AWT world, the AWT focus manager has overall
control. The abstract class defines a series of static methods and three
abstract methods for the concrete focus manager to implement.
public abstract class FocusManager { // Constructors public FocusManager(); // Constants public final static String FOCUS_MANAGER_CLASS_PROPERTY; // Class Methods public static void disableSwingFocusManager( ); public static FocusManager getCurrentManager( ); public static boolean isFocusManagerEnabled( ); public static void setCurrentManager( FocusManager focusManager); // Other Methods public abstract void focusNextComponent( Component component); public abstract void focusPreviousComponent( Component component); public abstract void processKeyEvent( Component focusedComponent, KeyEvent keyEvent); }NOTE: Although the class definition may imply that at most one
FocusManager
is working at a time, in
reality each ThreadGroup manages its own FocusManager
.
You can disable the Swing focus manager for an application by calling the
public static void disableSwingFocusManager()
method of
FocusManager
. This gives the AWT focus manager complete control.
To enable it again, call the public static void setCurrentManager
(FocusManager newValue)
method with a new manager as the argument.
To get the default manager for the look and feel, ask the UIManager
the following:
FocusManagerfocusManager=( FocusManager)UIManager.get( FocusManager.FOCUS_MANAGER_CLASS_PROPERTY)
NOTE:It is necessary to disable the focus manager when mixing Swing and AWT components within the same window.
The FocusManager
doesn't remember what has the input focus when
it's asked to find the next component to get the input focus. When asked to
adjust the input focus, all three of the abstract methods take the last
component, with the input focus as an argument.
The default implementation of the FocusManager interface is the
DefaultFocusManager
class, which provides the predefined behavior
for the five properties listed back in Table 2-4.
public class DefaultFocusManager extends FocusManager { // Constructors public DefaultFocusManager(); // Other Methods public boolean compareTabOrder( Component a, Component b); public Component getComponentAfter( Container aContainer, Component component); public Component getComponentBefore( Container aContainer, Component component); public Component getFirstComponent( Container container); public Component getLastComponent( Container container); public void focusNextComponent( Component component); public void focusPreviousComponent( Component component); public void processKeyEvent( Component focusedComponent, KeyEvent keyEvent); }TIP If you don't like the default left-to-right, top-to-bottom tab ordering, simply subclass
DefaultFocusManager
and override
compareTabOrder()
.
Although the behavior of the DefaultFocusManager
might suffice for
most needs, it won't for all. There might come a time when you need to customize
the focus behavior. Before wandering down this path, be sure the behavior you
desire can't first be accomplished by using the properties listed in Table 2-4.
After you define your custom focus manager, you need to install it. Once
installed, it takes over the duties originally performed by the
DefaultFocusManager.
FocusManager focusManager = new MyFocusManager();
FocusManager.setCurrentManager( focusManager);
Whenever you need to get or set a property value of a Swing component, it must
be done from the event-dispatch thread. Again, not to worry; it isn't that
difficult a task. Once you've created a program's screen probably in an init()
method of an applet or a main() method
of an applicationyou show
it. The
components on the screen are then responsive to user input. When a component is
activated, the component might look at another component for its state and pass
the state data along to something else. All this happens in the event-dispatch
thread by default while the event handler for the activated component executes
in the event-dispatch thread. Even the paint()
method executes in the
event-dispatch thread in response to paint events
(java.awt.PaintEvent)
.
NOTE: The addition and removal of event listeners can be done from any thread.
If an event-handling task is going to take too much time, the handler needs to get the appropriate information from the Swing components first, then create another Thread to do the task. If a secondary thread isn't created, the user interface becomes nonresponsive.
If a nonevent-handling task needs to access a Swing component, it must create a task to be run on the event-dispatch thread and then process the results when done.
The Swing libraries provide two ways of creating tasks to be run on the
event-dispatch thread. If you need to execute a task on the event-dispatch
thread, but you don't need any results and don't care exactly when the task
finishes, you can use the public static void invokeLater(Runnable runnable)
method of SwingUtilities
. If, on the other hand, you can't continue with what
you're doing until the task completes and returns you a value, you can use the
public static void invokeAndWait(Runnable runnable)
method of SwingUtilities.
The code to get the value is left up to you and not the return value to
the invokeAndWait()
method.
WARNING The invokeAndWait(Runnable)
method can throw an
InterrutpedException
or an InvocationTargetException
.
To demonstrate, let's first look at invokeLater()
. The method
requires a Runnable object as its argument, so you only need to create a
Runnable object and pass it along to the invokeLater() method
. Some
time after the current event dispatching is done, this runnable object will
execute.
Runnable runnable = new Runnable() { public void run() { // do work to be done } }SwingUtilities.invokeLater(runnable);
Similarly, the invokeAndWait() method also accepts a Runnable object. However, the line after the call to invokeAndWait() isn't executed until the Runnable object finishes.
try { Runnable runnable = new Runnable() { public void run() { // do work to be done // save/return results } } SwingUtilities.invokeAndWait( runnable); } catch ( InterruptedException interruptedException) { // Do something special } catch (InvocationTargetException invocationTargetException) { // Do something special }Here are some tips about using the two methods I just described and Swing component thread safety:
invokeLater()
and invokeAndWait()
wait until all pending AWT events
finish before they execute.
invokeAndWait()
from the event-dispatch thread within an AWT event
handler, because the event processing will never finish in order for
invokeAndWait()
to execute.
nvokeAndWait()
, be sure the object being blocked
doesn't retain
any system locks, otherwise the method may never return because of a deadlock
situation.
repaint()
, revalidate()
, and
invalidate()
don't have to be done from
the event-dispatch thread.
NOTE: In the Java 2 SDK, AWT's EventQueue
class
has its own pair of invokeAndWait()
and invokeLater()
methods.
They function in the same way as the SwingUtilities
methods;
however, their usage should be limited to interactions with AWT components.
In addition to the invokeAndWait()
and invokeLater()
methods of SwingUtilities
,
you can use the Timer class to create actions to be executed on the event-
dispatch thread. A Timer provides a way of notifying an
ActionListener
after a predefined number of milliseconds. The timer
can repeatedly notify the listeners, or just call them once.
Following is the single constructor for creating a Timer that specifies the
millisecond delay time between calls to the ActionListener
:
public Timer( int delay, ActionListener actionListener); // 1 second interval Timer timer = new Timer(1000, anActionListener); Using Timer ObjectsAfter a Timer object has been created, you need to start() it. Once the Timer is started, the ActionListener will be notified after the given number of milliseconds. If the system is busy, the delay could be longer but it won't be shorter.
If there comes a time when you want to stop a Timer, call its stop() method. The Timer also has a restart() method, which calls stop() and start(), restarting the delay period.
To demonstrate, the following defines an ActionListener that simply prints a message. You then create Timer to call this listener every half-second. After creating the timer, you need to start it.
import javax.swing.*; import java.awt.event.*; public class TimerSample { public static void main( String args[]) { new ExitableJFrame( ).setVisible(true); // To create //dummy screen ActionListener actionListener = new ActionListener() { public void actionPerformed( ActionEvent actionEvent) { System.out.println("Hello World Timer"); } }; Timer timer = new Timer( 500, actionListener); timer.start(); } }TIP A Timer doesn't start up the AWT event-dispatch thread. If the event-dispatch thread isn't running, your program might end relatively quickly. That's why the previous program creates and displays a dummy frame.
Table 2-5 lists the five properties of Timer. Four allow you to customize the behavior of the timer, and the fifth (running) tells you if a timer has been started but not stopped. The delay property is the same as the constructor argument. If you change the delay of a running timer, the new delay won't be used until the existing delay runs out.
The initialDelay property allows you to have another startup delay besides the
periodic delay after the first execution. For instance, if you don't want to do
a task for an hour, but then want to do it every 15 minutes thereafter, you need
to change the initialDelay
setting before you start the timer. By
default, the initialDelay and delay properties are set to the same setting in
the constructor.
The repeats property is true by default, which results in a repeating timer.
When false, the timer notifies action listeners only once. You then need to
restart()
the timer to trigger the listener again. Nonrepeating timers are good
for one-time notifications that need to happen after a triggering event.
The coalesce property allows for a busy system to throw away notifications that haven't happened yet when a new event needs to be fired to the registered ActionListener objects. By default, the coalesce value is true. This means if a timer runs every 500 milliseconds, but its system is bogged down and doesn't respond for a whole 2 seconds, the timer only needs to send one message without all the missing ones, too. If the setting were false, four messages would still need to be sent.
In addition to the properties just listed, you can turn on log messages with the following line of source. Log messages are good for actions that lack a visual element, allowing you to see when something happens.
Timer.setLogTimers(true);
In this chapter, we looked at the many ways of dealing with event handling when using Swing components. Because the components are built on top of AWT components, you can continue to use the delegation-based event mechanism first introduced into the Java class libraries with JDK 1.1. We also explored how the Swing components use the JavaBeans PropertyChangeListener approach for notification of bound property changes. With the description of some enhancements, such as simplified mouse button identification, we learned how the transition to the Swing component set from the AWT component set can be relatively painless.
Besides exploring the similarities between the Swing components and AWT
components, we also looked at several of the new features the Swing library
offers. We explored the Action interface and how it can simplify complex
user-interface development by completely separating the event-handling task from
the visual component. We looked at the technique for registering KeyStroke
objects to components to simplify listening for key events. We explored Swing's
focus management capabilities and how to customize the focus cycle with and
without the FocusManager
. Lastly, we looked at the
multithreading limitations of
the Swing components and how to get around them with the
invokeAndWait()
and
invokeLater()
methods of SwingUtilities
.
In Chapter 3, we'll cover the Model-View-Controller (MVC) architecture of the Swing component set. You'll learn how MVC can make your user interface development efforts much easier to maintain.
John Zukowski, a long-time contributer to the JDC, is a columnist for JavaWorld Magazine, a member of the JavaWorld Senior Advisory Board, and he is the Guide for Java at About.com. He is a faculty member of the Magelang Institute, a leading provider of advanced Java training and also contributors to the JDC. Zukowski has written many other popular titles on Java technology, including Java AWT Reference, Mastering Java 2, and Borland's JBuilder: No Experience Required.