Published: April 29, 2003
By Richard G. Baldwin
Java Programming Notes # 1840
New features in SDK Version 1.4.0
JavaTM 2 SDK, Standard Edition V1.4 contains a large number of new features, including a completely new focus subsystem.
I became hooked on Java in April 1996. Since then, Java has progressed from V1.0 up to V1.4. Fortunately, during that period, few if any of the improvements included in new versions have been incompatible with code written for previous versions. Unfortunately, that history of upgrade success changed with the release of the new focus subsystem in V1.4. Several aspects of the new subsystem are incompatible with code written for previous versions.
Historical problems with the focus subsystem
According to Sun,
"Prior to Java 2 Standard Edition, JDK 1.4, the AWT focus subsystem was inadequate. It suffered from major design and API problems, as well as over a hundred open bugs. Many of these bugs were caused by platform inconsistencies, or incompatibilities between the native focus system for heavyweights and the Java focus system for lightweights."
What does Sun say about focus in V1.4?
Sun goes on to describe some of the specific problems and then states:
"To address these and other deficiencies, we have designed a new focus model for the AWT in JDK 1.4. The primary design changes were the construction of a new centralized KeyboardFocusManager class, and a lightweight focus architecture. The amount of focus-related, platform-dependent code has been minimized and replaced by fully pluggable and extensible public APIs in the AWT. While we have attempted to remain backward compatible with the existing implementation, we were forced to make minor incompatible changes in order to reach an elegant and workable conclusion. We anticipate that these incompatibilities will have only a trivial impact on existing applications."
A lot to learn
There is a lot to learn about the new focus subsystem. It is anything but trivial. This lesson will deal with only one aspect of the new subsystem, and will deal with that aspect from a relatively simple viewpoint. This lesson deals with focus traversal among the components in a single-level container.
(In this lesson, there are no containers that contain focusable components within that single container. Thus, I refer to it as a single-level container. In particular, there is no requirement to deal with the complexities of having the focus move from one container up or down a level to a parent or child container, known as up cycle and down cycle.)
Viewing tip
You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different listings and figures while you are reading about them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find those lessons published at Gamelan.com. However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there. You will find a consolidated index at www.DickBaldwin.com.
Focusability
A focusable Component is a component that can become the focus owner and participates in keyboard focus traversal with a FocusTraversalPolicy.
Focus traversal
Since focus traversal is the primary topic of this lesson, we should probably begin with a description of that term. Here is how Sun describes focus traversal:
"Focus traversal -- the user's ability to change the "focus owner" without moving the cursor. Typically, this is done using the keyboard (for example, by using the TAB key), or an equivalent device in an accessible environment. Client code can also initiate traversal programmatically. Normal focus traversal can be either "forward" to the "next" Component, or "backward" to the "previous" Component."
I imagine that everyone reading this lesson is familiar with the focus traversal process described above.
The focus cycle root
Focus traversal is governed by a focus traversal policy. Before getting into a discussion of the focus traversal policy, however, I need to introduce the concept of a focus cycle root along with some other related terms.
According to Sun,
"Focus cycle root -- Container that is the root of the Component hierarchy for a particular "focus traversal cycle". When the "focus owner" is a Component inside a particular cycle, normal forward and backward focus traversal cannot move the "focus owner" above the focus cycle root in the Component hierarchy. Instead, two additional traversal operations, "up cycle" and "down cycle", are defined to allow keyboard and programmatic navigation up and down the focus traversal cycle hierarchy."
Focus traversal cycle
Sun has this to say about the focus traversal cycle.
"Focus traversal cycle -- a portion of the Component hierarchy, such that normal focus traversal "forward" (or "backward") will traverse through all of the Components in the focus cycle, but no other Components. This cycle provides a mapping from an arbitrary Component in the cycle to its "next" (forward traversal) and "previous" (backward traversal) Components."
Focus owner
According to Sun, the focus owner is the Component that typically receives keyboard input.
Up cycle and down cycle traversal operations
I will deal with up cycle and down cycle traversal operations in a subsequent lesson.
Focus traversal policy
And finally, here is how Sun describes a FocusTraversalPolicy.
"A FocusTraversalPolicy defines the order in which Components with a particular focus cycle root are traversed."
I will show you how to establish and use a focus traversal policy in the sample program to be discussed later in this lesson.
Primary responsibility of a FocusTraversalPolicy
The primary responsibility of a FocusTraversalPolicy is to specify the next and previous Components to gain focus when traversing forward or backward in a user interface. Within a list of focusable components, this involves specifying the components to receive focus after and before the currently focused component.
Focus traversal wraparound
In order to support wraparound at each end of a list of focusable components, each FocusTraversalPolicy must also specify the first and last Components in a traversal cycle. According to Sun, these are the components that receive focus when the traversal wraps around the end of the list going in either direction.
The first Component is the Component that receives the focus when traversal wraps in the forward direction. The last Component is the Component that receives the focus when traversal wraps in the reverse direction.
(Note that I will raise, but will not answer, some interesting questions regarding the wraparound later.)
A default component and an initial component
The FocusTraversalPolicy must also specify a default Component, which is the first component to receive the focus when traversing down into a new focus traversal cycle.
A FocusTraversalPolicy can optionally specify an initial Component, which is the first to receive focus when a Window is first made visible.
(The sample program in this lesson doesn't specify the optional initial component.)
Five methods are involved
I will discuss and explain a sample program that satisfies the requirements listed above by defining the behavior of the following five methods:
(The sample program in this lesson doesn't make use of the optional getInitialComponent method.)
Establishing the FocusTraversalPolicy
The FocusTraversalPolicy for a container is established by invoking the container's setFocusTraversalPolicy method and passing a FocusTraversalPolicy object's reference as a parameter.
If a policy is not explicitly set on a container, then the container inherits its policy from its nearest focus-cycle-root ancestor.
In this lesson, I will show you how to establish a custom focus traversal policy, and how to modify that policy at runtime.
The program named FocusTraversal01
This program illustrates the new FocusTraversalPolicy that was released in Java SDK Version 1.4.
Description of the program
A single JFrame object appears on the screen, as shown in Figure 1.
Figure 1 The GUI at startup
Four JButton objects appear at the North, South, East, and West locations in the frame. The buttons display the captions 03, 06, 09, and 12.
(The positions of the buttons with their captions mimic four of the numbers on a clock.)
When the program first starts running, the button with the caption 12 has the focus, and the number sequence 09,03,12,06,09,... appears near the center of the frame.
Click to change the sequence
When you click on that sequence, the sequence changes to 09,06,12,03,09,... and the color of the characters changes to red, as shown in Figure 2.
Figure 2 The GUI after clicking the sequence
Successive clicking on the sequence causes the sequence to toggle back and forth between the two sequences given above, and causes the color of the characters to toggle between black and red.
Focus traversal in the forward direction
When the black sequence 09,03,12,06,09,... appears, successively pressing the tab key causes the focus to traverse the four buttons in the order given by the black sequence.
Similarly, when the red sequence 09,06,12,03,09,... appears, successively pressing the tab key causes the focus to traverse the four buttons in the order given by the red sequence.
Focus traversal in the reverse direction
In both cases, holding down the shift key while pressing the tab key reverses the order of focus traversal.
Focus is well behaved
When you click on the sequence to change it, the focus does not jump from its current button to a different button.
When you have clicked on a button, causing that button to have the focus, pressing the tab key causes the focus to move from that button to the next button defined by the sequence that is showing.
SDK Version 1.4 required
This program requires SDK V1.4 or later, because features used in this program were first released in V1.4.
The program was tested using SDK 1.4.1 under WinXP
Will discuss sample program in fragments
As usual, I will discuss the program in fragments. You can view a listing of the entire program in Listing 13 near the end of the lesson.
The main method
The program begins in Listing 1 where the main method simply
instantiates an object of the class named GUI.
public class FocusTraversal01 { public static void main(String[] args){ new GUI(); }//end main }//end class FocusTraversal01 Listing 1 |
It is the class named GUI that produces the user interface shown in Figures 1 and 2.
The class named GUI
The class named GUI, which extends JFrame, begins in Listing
2.
class GUI extends JFrame{ boolean policyIsA = true; JLabel seq = new JLabel( " 09,03,12,06,09,..."); JButton button12 = new JButton("12"); JButton button03 = new JButton("03"); JButton button06 = new JButton("06"); JButton button09 = new JButton("09"); JFrame frame = this; Listing 2 |
Listing 2 instantiates all four of the buttons and the label that you saw in Figure 1.
(The component in the center of the GUI containing the sequence of numbers is a JLabel whose reference is stored in a reference variable named seq.)
In addition, the code in Listing 2 saves a reference to the GUI object in the reference variable named frame. This is used later to gain access to the GUI object from within some inner classes.
The variable named policyIsA
This switches between two different focus traversal policies at runtime. The boolean variable named policyIsA in Listing 1 is used later to keep track of which policy is currently in force.
The constructor for the GUI class
The constructor for the GUI class begins in Listing 3.
public GUI(){//constructor setSize(250,100); setTitle("Copyright 2003, R.G.Baldwin"); setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); getContentPane().add(button12,"North"); getContentPane().add(button03,"East"); getContentPane().add(button06,"South"); getContentPane().add(button09,"West"); getContentPane().add(seq,"Center"); Listing 3 |
The code in Listing 3 is completely straightforward, so I won't discuss it further.
(In case you are unfamiliar with the construction of graphical user interfaces in Java, I have published numerous tutorial lessons on that topic on my web site.)
The class named TravPolicyA
Listing 4 shows the beginning of an inner class named TravPolicyA,
which extends the class named FocusTraversalPolicy, and overrides five
of the six methods defined in that class.
class TravPolicyA extends FocusTraversalPolicy{ public Component getDefaultComponent( Container focusCycleRoot){ return button12; }//end getDefaultComponent Listing 4 |
Background information on the FocusTraversalPolicy class
The FocusTraversalPolicy class is an abstract class, and five of the six methods defined in the class are declared abstract. Therefore, the FocusTraversalPolicy class exists for the sole purpose of being extended, a task often relegated to interfaces in Java.
However, the class defines one non-abstract method named getInitialComponent, so the FocusTraversalPolicy class cannot be replaced by an interface. The default implementation of the getInitialComponent method returns the default Component.
Overridden methods
The code in Listing 4 overrides the method named getDefaultComponent. Subsequent listings will override the remaining four abstract methods of the FocusTraversalPolicy class.
The overridden methods in the FocusTraversalPolicy object must specify the next component that is to receive the focus whenever focus traversal occurs in a forward or reverse direction.
The getDefaultComponent method
The code in the overridden getDefaultComponent method in Listing 4 returns a reference to the button at the 12-o'clock position in the GUI as the default component. According to Sun,
"This Component will be the first to receive focus when traversing down into a new focus traversal cycle rooted at focusCycleRoot."
Because this GUI has only one focus cycle root, this is the component that receives the focus when the program starts running.
(Note that even though this method receives a reference to the focus cycle root as an incoming parameter, that information is not used in this program.)
The first component
The overridden method named getFirstComponent in Listing 5 specifies
the first component, as the component in the 9-o'clock position in the
GUI.
public Component getFirstComponent( Container focusCycleRoot){ return button09; }//end getFirstComponent Listing 5 |
According to Sun,
"This method is used to determine the next Component to focus when traversal wraps in the forward direction."
The last component
Similarly, the overridden method named getLastComponent in Listing 6
identifies component in the 6-o'clock position as the "Component to focus
when traversal wraps in the reverse direction."
public Component getLastComponent( Container focusCycleRoot){ return button06; }//end getLastComponent Listing 6 |
The next component in forward traversal
Listing 7 shows the overridden version of the method named getComponentAfter. This method receives a reference to a component by the parameter name aComponent. According to Sun, this method
"Returns the Component that should receive the focus after aComponent."
In this program, this method identifies each component that is to receive
the focus in succession as focus is traversed in the forward direction.
public Component getComponentAfter( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button06; }else if(aComponent == button03){ return button12; }else if(aComponent == button06){ return button09; }else if(aComponent == button09){ return button03; }//end else return button12;//make compiler happy }//end getComponentAfter Listing 7 |
This method gets called by the focus subsystem, passing the currently focused component as an input parameter. The method returns the component that should be the next component to receive the focus for forward traversal.
A little confusion
The discussion up to this point, based on Sun's documentation, implies that the system should know how to traverse from the last component to the first component when focus traversal wraps at the end of the traversal cycle. This implies that, (based on the specification of the first and last components earlier), the system should know how to traverse from button06 to button09 without being told how to do that in the method named getComponentAfter.
I'm probably missing something here, but I don't find that to be the case. The only way that I have been able to cause the focus to traverse from button06 to button09 is to explicitly include the information that button09 is the component after button06 in the overridden version of the getComponentAfter method.
The next component in reverse traversal
Listing 8 shows the overridden version of the method named getComponentBefore. Again, this method receives an incoming parameter known locally as aComponent, and according to Sun, this method
"Returns the Component that should receive the focus before aComponent."
In this program, this method identifies each component that is to
receive the focus in succession as focus is traversed in the reverse
direction.
public Component getComponentBefore( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button03; }else if(aComponent == button03){ return button09; }else if(aComponent == button06){ return button12; }else if(aComponent == button09){ return button06; }//end else return button12;//make compiler happy }//end getComponentBefore }//end TravPolicyA Listing 8 |
The method named getComponentBefore is called by the focus subsystem to learn the order of focus traversal as focus is traversed in the reverse direction. This method simply identifies the button in the reverse order of the method shown in Listing 7.
The end of the class definition
Listing 8 also signals the end of the inner class named TravPolicyA. As you will see later, an object of this class provides the traversal policy when the black number sequence is showing on the GUI.
The TravPolicyB class
Listing 13 near the end of the lesson includes an inner class named TravPolicyB. An object of this class is used to provide the traversal policy when the red number sequence is showing on the GUI.
Because of the similarity between this class and the class named TravPolicyA discussed earlier, I won't discuss this class in detail. It differs from TravPolicyA only in the specified order of focus traversal among the buttons.
Two FocusTraversalPolicy objects
Recall that we are still discussing code in the constructor for the GUI class.
The code in Listing 9 instantiates one object from each of the inner
classes named TravPolicyA and TravPolicyB discussed earlier and
saves those object's references in the final local variables named
policyA and policyB.
final TravPolicyA policyA = new TravPolicyA(); final TravPolicyB policyB = new TravPolicyB(); Listing 9 |
Why are the variables final?
In case you are wondering why these two variables were declared final, that is a requirement of the compiler. When policyA is not declared final, the following compiler error is produced by the javac compiler, V1.4.1:
"local variable policyA is accessed from within inner class; needs to be declared final"
The inner class that is referred to by this error message is an anonymous inner class that I will discuss later. The anonymous inner class is used to register a mouse listener on the JLabel object.
Set startup traversal policy
The code in Listing 10 sets the startup traversal policy to be governed by
the object earlier instantiated from the class named TravPolicyA.
The startup policy is established by invoking the setFocusTraversalPolicy on the JFrame object
when the object is constructed.
(The code in Listing 10 also causes the GUI to become visible on the
screen.)
frame.setFocusTraversalPolicy(policyA); frame.setVisible(true); Listing 10 |
The setFocusTraversalPolicy method
Here is part of what Sun has to say about the setFocusTraversalPolicy, method, which the JFrame class inherits from the Container class.
"Sets the focus traversal policy that will manage keyboard traversal of this Container's children, if this Container is a focus cycle root. If the argument is null, this Container inherits its policy from its focus- cycle-root ancestor. If the argument is non-null, this policy will be inherited by all focus-cycle-root children that have no keyboard- traversal policy of their own (as will, recursively, their focus-cycle- root children)."
Thus, whenever the setFocusTraversalPolicy method is invoked on the JFrame object, (passing a valid FocusTraversalPolicy object's reference as a parameter), that object governs the traversal policy from that point forward until the method is invoked again passing a different FocusTraversalPolicy object's reference as a parameter.
A mouse listener on the JLabel object
Listing 11 shows the beginning of an anonymous inner class, which is used to register a mouse listener on the JLabel object that displays the sequences of numbers. The beginning of an overridden mousePressed method is shown in Listing 11.
(Listing 11 shows the if clause of an if-else statement. The else clause will be shown in Listing 12.)
seq.addMouseListener( new MouseAdapter(){ public void mousePressed(MouseEvent e){ //Switch traversal policy if(policyIsA){ policyIsA = false; seq.setText( " 09,06,12,03,09,..."); seq.setForeground(Color.RED); frame.setFocusTraversalPolicy( policyB); Listing 11 |
Toggle between two focus traversal policies
The purpose of the mouse listener is to toggle the focus subsystem between the two FocusTraversalPolicy objects when the user clicks the JLabel object in the center of the GUI.
The code in Listing 11 is straightforward, with the operative statement being the final statement in Listing 11, which invokes the setFocusTraversalPolicy method on the JFrame to switch the traversal policy object from policyA to policyB.
(Some other changes to the displayed sequence of numbers and the color of the sequence are also made in Listing 11.)
The else clause
The else clause of the if-else statement in the
mousePressed method is shown in Listing 12.
}else{ policyIsA = true; seq.setText( " 09,03,12,06,09,..."); seq.setForeground(Color.BLACK); frame.setFocusTraversalPolicy( policyA); }//end else }//end mousePressed }//end new MouseAdapter );//end addMouseListener }//end constructor }//end GUI Listing 12 |
The code in Listing 12 switches the traversal policy from policyB back to policyA, changing the displayed sequence of numbers and the color of the sequence in the process.
That's a wrap
Finally, the code in Listing 12 ends the definition of the anonymous class, ends the definition of the constructor, and ends the definition of the GUI class.
The bottom line
When the user clicks the JLabel in the center of the GUI, an overridden mousePressed method is executed. The code in that method determines which focus traversal policy object is currently governing focus traversal, and replaces that object with a different focus traversal policy object. This makes it possible to change the focus traversal order among the components at runtime.
Remember, however, that you must be running Java V1.4 or later to compile and execute this program.
In this lesson, I have taught you how to establish a custom focus traversal policy, and how to modify that policy at runtime.
As time goes on, I plan to publish additional lessons that will help you learn to use about other features of the new focus subsystem that was introduced in Java SDK Version 1.4. Stay tuned for more on this subject.
/*File FocusTraversal01.java Copyright 2003 R.G.Baldwin This program illustrates the new FocusTraversalPolicy that was released in SDK V1.4. The behavior of this program is as follows: A single JFrame object appears on the screen. Four JButton objects appear at the North, South, East, and West locations in the frame. The buttons display the captions 03, 06, 09, and 12. (The positions of the buttons with their captions mimic four of the numbers on a clock.) When the program first starts running, the button with the caption 12 has the focus, and the number sequence 09,03,12,06,09,... appears near the center of the frame. When you click on that sequence, the sequence changes to 09,06,12,03,09,... and the color of the characters changes to red to emphasize that the sequence has changed. Successive clicking on the sequence causes the sequence to toggle back and forth between the two sequences given above, and causes the color of the characters to toggle between black and red. When the sequence 09,03,12,06,09,... appears, successive pressing of the tab key causes the focus to traverse the four buttons in the order given by the sequence. Similarly, when the sequence 09,06,12,03,09,... appears, successive pressing of the tab key causes the focus to traverse the four buttons in the order given by that sequence. In both cases, holding down the shift key while pressing the tab key reverses the order of focus traversal. When you use the mouse to change the sequence, the focus does not jump from its current button to a different button. When you use the mouse to cause the focus to rest on a particular button and then press the tab key, the focus moves from that button to the next button as defined by the sequence that is showing in the center of the frame. Requires SDK V1.4 or later. Tested using SDK 1.4.1 under WinXP ************************************************/ import java.awt.*; import java.awt.event.*; import javax.swing.*; public class FocusTraversal01 { public static void main(String[] args){ new GUI(); }//end main }//end class FocusTraversal01 //---------------------------------------------// class GUI extends JFrame{ boolean policyIsA = true; JLabel seq = new JLabel( " 09,03,12,06,09,..."); JButton button12 = new JButton("12"); JButton button03 = new JButton("03"); JButton button06 = new JButton("06"); JButton button09 = new JButton("09"); JFrame frame = this; public GUI(){//constructor setSize(250,100); setTitle("Copyright 2003, R.G.Baldwin"); setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); getContentPane().add(button12,"North"); getContentPane().add(button03,"East"); getContentPane().add(button06,"South"); getContentPane().add(button09,"West"); getContentPane().add(seq,"Center"); //Inner class for traversal policy A class TravPolicyA extends FocusTraversalPolicy{ public Component getDefaultComponent( Container focusCycleRoot){ return button12; }//end getDefaultComponent //---------------------------------------// public Component getFirstComponent( Container focusCycleRoot){ return button09; }//end getFirstComponent //---------------------------------------// public Component getLastComponent( Container focusCycleRoot){ return button06; }//end getLastComponent //---------------------------------------// public Component getComponentAfter( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button06; }else if(aComponent == button03){ return button12; }else if(aComponent == button06){ return button09; }else if(aComponent == button09){ return button03; }//end else return button12;//make compiler happy }//end getComponentAfter //---------------------------------------// public Component getComponentBefore( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button03; }else if(aComponent == button03){ return button09; }else if(aComponent == button06){ return button12; }else if(aComponent == button09){ return button06; }//end else return button12;//make compiler happy }//end getComponentBefore }//end TravPolicyA //=========================================// //Inner class for traversal policy B class TravPolicyB extends FocusTraversalPolicy{ public Component getDefaultComponent( Container focusCycleRoot){ return button06; }//end getDefaultComponent //---------------------------------------// public Component getFirstComponent( Container focusCycleRoot){ return button09; }//end getFirstComponent //---------------------------------------// public Component getLastComponent( Container focusCycleRoot){ return button03; }//end getLastComponent //---------------------------------------// public Component getComponentBefore( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button06; }else if(aComponent == button03){ return button12; }else if(aComponent == button06){ return button09; }else if(aComponent == button09){ return button03; }//end else return button12;//make compiler happy }//end getComponentBefore //---------------------------------------// public Component getComponentAfter( Container focusCycleRoot, Component aComponent){ if(aComponent == button12){ return button03; }else if(aComponent == button03){ return button09; }else if(aComponent == button06){ return button12; }else if(aComponent == button09){ return button06; }//end else return button12;//make compiler happy }//end getComponentAfter }//end TravPolicyB //=========================================// //Local variables must be final for access // within inner class final TravPolicyA policyA = new TravPolicyA(); final TravPolicyB policyB = new TravPolicyB(); //Set startup traversal policy to Policy A frame.setFocusTraversalPolicy(policyA); frame.setVisible(true); //Register mouse listener on the JLabel seq.addMouseListener( new MouseAdapter(){ public void mousePressed(MouseEvent e){ //Switch traversal policy if(policyIsA){ policyIsA = false; seq.setText( " 09,06,12,03,09,..."); seq.setForeground(Color.RED); frame.setFocusTraversalPolicy( policyB); }else{ policyIsA = true; seq.setText( " 09,03,12,06,09,..."); seq.setForeground(Color.BLACK); frame.setFocusTraversalPolicy( policyA); }//end else }//end mousePressed }//end new MouseAdapter );//end addMouseListener }//end constructor }//end GUI Listing 13 |
Copyright 2003, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-