Some of the lingering issues with Swing-based GUIs include how to manage focus (which component has priority to receive keyboard input), determining what component has it, and how to traverse from one component to the next. Because Swing is built on top of the Abstract Window Toolkit (AWT), the management of component focus relies on the underlying focus management in the AWT. Past releases of the Java platform relied on the native window manager to assist with focus management, so while developers might have thought focus control was within their Java application, that wasn't always the case. Because of the reliance on the underlying native focus system, numerous platform "inconsistencies" appeared.
With Merlin, you get a whole new focus subsystem at the AWT level. The subsystem has its good and bad points. The new model is meant to create a workable cross-platform system, with a centralized
KeyboardFocusManager to manage the active and focused windows,
as well as the current focus owner. The downside is that there are some
incompatibilities with prior releases, causing some programs not to work
well with the newer release. As a developer, you need to be aware of
the new focus traversal behaviors when you create any new programs.
The new focus subsystem is quite large, and in this tip, we'll highlight just one
of the new features -- the FocusTraversalPolicy -- and show you how
to manage focus traversal within a single container. For information on the remaining
features, see Resources for links to Sun documentation
and other important guides.
We'll start by looking at the FocusTraversalPolicy class. Yes, it is a class,
not an interface. It is abstract, though, so it is meant to be subclassed. The
FocusTraversalPolicy class controls the order of traversal within a particular
focus cycle root. A focus cycle root is a container whose focusCycleRoot
property is set to true. By default, windows and frames are set to true and remaining containers are set to false, but can be made true. Setting the property to true means that when focus moves back and forth, the focus will
always stay within a component of the cycle within its focus cycle root.
The FocusTraversalPolicy class consists of six methods:
getDefaultComponent(Container focusCycleRoot)getInitialComponent(Window window)getComponentBefore(Container focusCycleRoot, Component aComponent)getComponentAfter(Container focusCycleRoot, Component aComponent)getFirstComponent(Container focusCycleRoot)getLastComponent(Container focusCycleRoot)
All six methods return a Component object. Of the six methods, five are
abstract, with getInitialComponent() being the only concrete method.
As the name suggests, the getDefaultComponent() method returns the default
component that should get focus when the associated focus cycle root gets
focus. Imagine tabbing around a container and tabbing into a container within
that container. This is called a down focus cycle. When focus enters that
subcontainer, getDefaultComponent() needs to return the initial component
that should get focus.
The getInitialComponent() method returns the initial component that should
get focus when a window is first displayed. By default, this method just returns
the default component for the window -- the result of calling getDefaultComponent().
The getComponentBefore() and getComponentAfter() methods work as a pair.
Given a particular component in a specific focus cycle root (think container), the methods return which component would be before it or after. Typically, you press Shift+Tab to move backward to a previous component and press Tab to advance forward to the next component, but circumstances may warrant having different key sequences to move focus
with the keyboard. Typically, these methods are written as one large if-else block or a Map lookup.
The getFirstComponent() and getLastComponent() methods
are also a pair. While logically you should write getComponentBefore() and
getComponentAfter() with a first/last component in mind, the initial
component doesn't have to be first and these methods let you explicitly set
which components are first and last.
The system includes five built-in focus traversal policies, the latter three of which are specific to Swing:
ContainerOrderFocusTraversalPolicyDefaultFocusTraversalPolicyInternalFrameFocusTraversalPolicySortingFocusTraversalPolicyLayoutFocusTraversalPolicy
ContainerOrderFocusTraversalPolicy works by using the container's
implicit component ordering (its getComponents() array), which is typically the order in which components are added to the container -- unless you add() components at a specific position. In addition to the six methods of
FocusTraversalPolicy, ContainerOrderFocusTraversalPolicy
also supports implicitly transferring focus down into a container and offering
an accept() method that you can override to define what is an acceptable
choice for a component getting focus.
DefaultFocusTraversalPolicy works very much like
ContainerOrderFocusTraversalPolicy, with one exception -- it relies on the
AWT's peer component to check on the focusability of a component. The
focusability of a peer is implementation dependent and returns you to the
problems of the existing focus management subsystem. When you work with
Swing GUI components, you don't have to worry about a peer's focusability.
InternalFrameFocusTraversalPolicy is the policy for JInternalFrame. Given that a JInternalFrame is a not a heavyweight window, it needs special behavior
to deal with when it is first shown.
SortingFocusTraversalPolicy allows you to define a Comparator to control
the focus traversal policy. Instead of using the deep if-else block or
lookup Map, create a comparator to deal with the ordering.
There is also a specialized SortingFocusTraversalPolicy that provides a
default Comparator: LayoutFocusTraversalPolicy. Here, components are sorted
by size, position, and orientation, instead of the order in the getComponents() array.
A sixth policy is also available, but it isn't public:
LegacyGlueFocusTraversalPolicy. This policy is used when old code uses something
like JComponent.setNextFocusableComponent().
Next, we'll create a simple example that demonstrates the use of our own
focus traversal policy. We'll create a FocusTraversalPolicy implementation
that takes an array and relies on the component order in the array to
determine traversal order. The sample window will be the typical
BorderLayout example where the components show off where North, South, East,
West, and Center are located. The traversal order will go clockwise around
the outside before visiting the middle. Figure 1 shows the program:
Figure 1. A BorderLayout window
Listing 1 shows the full clockwise traversal listing:
Listing 1. Clockwise traversal
import java.awt.*;
import javax.swing.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class BorderFocus {
public static void main(String args[]) {
JFrame frame = new JFrame("Focus Cycling");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
JButton north = new JButton("North");
contentPane.add(north, BorderLayout.NORTH);
JButton south = new JButton("South");
contentPane.add(south, BorderLayout.SOUTH);
JButton east = new JButton("East");
contentPane.add(east, BorderLayout.EAST);
JButton west = new JButton("West");
contentPane.add(west, BorderLayout.WEST);
JButton center = new JButton("Center");
contentPane.add(center, BorderLayout.CENTER);
contentPane.setFocusable(false);
final Component order[] =
new Component[] {north, east, south, west, center};
FocusTraversalPolicy policy = new FocusTraversalPolicy() {
List list = Arrays.asList(order);
public Component getFirstComponent(Container focusCycleRoot) {
return order[0];
}
public Component getLastComponent(Container focusCycleRoot) {
return order[order.length-1];
}
public Component getComponentAfter(Container focusCycleRoot,
Component aComponent) {
int index = list.indexOf(aComponent);
return order[(index + 1) % order.length];
}
public Component getComponentBefore(Container focusCycleRoot,
Component aComponent) {
int index = list.indexOf(aComponent);
return order[(index - 1 + order.length) % order.length];
}
public Component getDefaultComponent(Container focusCycleRoot) {
return order[0];
}
};
frame.setFocusTraversalPolicy(policy);
frame.pack();
frame.show();
}
}
|
As an additional exercise, you might want to rewrite the example to use
SortingFocusTraversalPolicy. Be sure to try out both forward and reverse
traversal through the container.
The focus subsystem in the 1.4 release of the Java platform has remedied a number of focus-related issues in prior releases. The FocusTraversalPolicy is just one of the improvements. Be sure to read the Focus specification referenced in the
Resources section to find out about the other
parts of the specification, including the new
KeyboardFocusManager, the differences between
requestFocus() and requestFocusInWindow,
and adjusting a component's focus traversal keys.
- The AWT Focus Subsystem
specification and learn all the gory details.
- Bertrand Portier examines the focus subsystem in more detail in his article "Java 2 gets a new focus subsystem"
(developerWorks, October 2001).
- Read the
FocusTraversalPolicyclass javadoc. - Learn the art of "Developing accessible GUIs with Swing" (developerWorks, December 2002) and be sure to deal with focus issues there, too.
- Learn more about event handling techniques in Paul Monday's tutorial,
"Java event
delivery techniques" (developerWorks, February 2002).
- Peruse John Zukowski's complete collection of Magic with Merlin tips.
- You might also want to check out the developerWorks IBM developer solutions for more topics related to focus.
- You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Java Collections (APress, April 2001) and Definitive Guide to Swing for Java 2, Second Edition (Apress, January 2000). Reach him at jaz@zukowski.net.




