The JFC Swing Tutorial: A Guide to Constructing GUIs (2nd Edition)
< Day Day Up > |
Many components even those operated primarily with the mouse, such as buttons can be operated with the keyboard. For a key press to affect a component, the component must have the keyboard focus. From the user 's point of view, the component with the keyboard focus is generally prominentwith a dotted or black border, for exampleand the window containing the component is more prominent than other windows onscreen. These visual cues tell the user to which component any typing will go. At most, one component in the window system can have the keyboard focus. Exactly how a window gains the focus depends on the windowing system. There's no foolproof way, across all platforms, to ensure that a window gains the focus. On some systems, such as Microsoft Windows, the frontmost window becomes the focused window; in this case, the method Window.toFront moves the window to the front, thereby giving it the focus. However, on a system such as Solaris, some window managers choose the focused window based on cursor position; in this case, Window.toFront doesn't result in the same behavior. A component generally gains the focus by the user clicking it, tabbing between components, or otherwise interacting with it. A component can also be given the focus programmatically, such as when its containing frame or dialog is made visible. This code snippet shows how to give a particular component the focus every time the window is activated: //Make textField get the focus whenever frame is activated. frame.addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { textField.requestFocusInWindow(); } }); If you want to ensure that a particular component gains the focus the first time a window is activated, you can call requestFocusInWindow on the component after the component has been realized but before the frame is displayed. Here's some sample code showing how this can be done: JFrame frame = new JFrame("Test"); JPanel = new JPanel(); //...Create a variety of components here... //Create the component that will have the initial focus. JButton button = new JButton("I'm first"); panel.add(button); frame.getContentPane().add(panel); //Add it to the panel frame.pack(); //Realize the components. //This button will have the initial focus. button.requestFocusInWindow(); frame.setVisible(true); //Display the window.
Version Note: This section describes the focus architecture implemented in release 1.4. Prior to 1.4, JComponent methods such as setNextFocusableComponent , getNextFocusableComponent , requestDefaultFocus , and isManagingFocus were used to manage the keyboard focus. These methods are now deprecated. Another method, requestFocus , is discouraged because it tries to give the focus to the component's window, which isn't always possible. As of v1.4, you should use requestFocusInWindow , which does not attempt to make the component's window focused and returns a boolean value indicating whether the method succeeded.
Introduction to the Focus Subsystem
The focus subsystem is designed to do the right thing as invisibly as possible. In most cases it behaves in a reasonable manner, and if it doesn't you can tweak it in various ways. Here are some common scenarios:
The FocusConceptsDemo example shown in Figure 25 illustrates a few concepts. Figure 25. A screenshot of the FocusConceptsDemo example in which the second JButton has the focus.
Try This:
At the heart of the focus subsystem is the KeyboardFocusManager , which manages state and initiates changes. The keyboard manager tracks the focus owner the component that receives typing from the keyboard. The focused window is the window that contains the focus owner.
Note: If you happen to use a JWindow in your GUI, you should know that JWindow 's owning frame must be visible for any components in the window to get the focus. By default, if you don't specify an owning frame for a JWindow , an invisible owning frame is created for it. The result is that components in JWindow s might not be able to get the focus. The solution is to either specify a visible owning frame when creating the JWindow , or use an undecorated JFrame instead of JWindow .
A focus cycle (or focus traversal cycle ) is a set of components that typically share a common ancestor in the containment hierarchy. The focus cycle root is the container that is the root for a particular focus traversal cycle. By default, every Window and JInternalFrame is a focus cycle root. Any Container (remember that all Swing components are containers) can be a focus cycle root as well; a focus cycle root can itself contain one or more focus cycle roots. The following Swing objects are focus cycle roots: JApplet , JDesktopPane , JDialog , JEditorPane , JFrame , JInternalFrame , and JWindow . While it might appear that JTable and JTree are focus cycle roots, they aren't. A focus traversal policy determines the order in which a group of components are navigated. Swing provides the LayoutFocusTraversalPolicy [45] class, which decides the order of navigation based on layout manager-dependent factors such as size , location, and orientation of components. Within a focus cycle, components can be navigated in a forward or backward direction. In a hierarchy of focus cycle roots, upward traversal takes the focus out of the current cycle into the parent cycle. [45] LayoutFocusTraversalPolicy API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/LayoutFocusTraversalPolicy.html. In most look and feels, components are navigated with the Tab and Shift-Tab keys. These are the default focus traversal keys and can be changed programmatically. You can, for example, add Enter as a forward focus traversal key with the following four lines of code: Set forwardKeys = getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); Set newForwardKeys = new HashSet(forwardKeys); newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys); Tab shifts the focus forward. Shift-Tab moves it backward. For example, in FocusConceptsDemo the first button has the initial focus. Tabbing moves the focus through the buttons into the text area. Additional tabbing moves the cursor within the text area but not out of it because inside a text area Tab is not a focus traversal key. However, Control-Tab moves the focus out of the text area and into the first text field. Likewise, Control-Shift-Tab moves the focus out of the text area and into the previous component. The Control key is used by convention to move the focus out of any component that treats Tab as special, such as JTable . We've just given you a brief introduction to the focus architecture. If you want more details, see the specification for the Focus Subsystem at: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html. Validating Input
A common requirement of GUI design is a component that restricts the user's inputfor example, a text field that allows only numeric input in decimal format (e.g., money) or a text field that allows only 5 digits for a zip code. Release 1.4 provides an easy-to-use formatted text field component that allows input to be restricted to a variety of localizable formats. [46] You can also specify a custom formatter for the text field that can perform special checking such as determining whether values are not just formatted correctly but also whether they are reasonable. [46] See also How to Use Formatted Text Fields (page 221) in Chapter 7. When you have a component that isn't a text field, or as an alternative to a custom formatter, you can use an input verifier. An input verifier allows you to reject specific values, such as a properly formatted but invalid zip code, or values outside of a desired range, such as a body temperature higher than 110F. To use it, create a subclass of InputVerifier [47] (a class introduced in release 1.3), create an instance of your subclass, and set the instance as the input verifier for one or more components. [47] InputVerifier API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/InputVerifier.html. A component's input verifier is consulted whenever the component is about to lose the focus. If the component's value isn't acceptable, the input verifier can take appropriate action, such as refusing to yield the focus on the component or replacing the user's input with the last valid value and then allowing the focus to transfer to the next component. Figures 26 and 27 show mortgage calculators . One uses input verification with standard text fields, and the other uses formatted text fields. Figure 26. A screenshot of the InputVerificationDemo example.
Figure 27. A screenshot of the FormattedTextFieldDemo example.
Try This:
You can find the code in InputVerificationDemo.java . Here's the code for the Input-Verifier subclass, MyVerifier : class MyVerifier extends InputVerifier implements ActionListener { double MIN_AMOUNT = 10000.0; double MAX_AMOUNT = 10000000.0; double MIN_RATE = 0.0; int MIN_PERIOD = 1; int MAX_PERIOD = 40; public boolean shouldYieldFocus(JComponent input) { boolean inputOK = verify(input); makeItPretty(input); updatePayment(); if (inputOK) { return true; } else { Toolkit.getDefaultToolkit().beep(); return false; } } protected void updatePayment() { double amount = DEFAULT_AMOUNT; double rate = DEFAULT_RATE; int numPeriods = DEFAULT_PERIOD; double payment = 0.0; //Parse the values. try { amount = moneyFormat.parse(amountField.getText()). doubleValue(); } catch (ParseException pe) {} try { rate = percentFormat.parse(rateField.getText()). doubleValue(); } catch (ParseException pe) {} try { numPeriods =decimalFormat.parse(numPeriodsField.getText()).intValue(); } catch (ParseException pe) {} //Calculate the result and update the GUI. payment = computePayment(amount, rate, numPeriods); paymentField.setText(paymentFormat.format(payment)); } //This method checks input, but should cause no side effects. public boolean verify(JComponent input) { return checkField(input, false); } protected void makeItPretty(JComponent input) { checkField(input, true); } protected boolean checkField(JComponent input, boolean changeIt) { if (input == amountField) { return checkAmountField(changeIt); } else if (input == rateField) { return checkRateField(changeIt); } else if (input == numPeriodsField) { return checkNumPeriodsField(changeIt); } else { return true; //shouldn't happen } } //Checks that the amount field is valid. If it is valid, //it returns true; otherwise, returns false. If the //change argument is true, this method reigns in the //value if necessary and (even if not) sets it to the //parsed number so that it looks good--no letters, //for example. protected boolean checkAmountField(boolean change) { boolean wasValid = true; double amount = DEFAULT_AMOUNT; //Parse the value. try { amount = moneyFormat.parse(amountField.getText()). doubleValue(); } catch (ParseException pe) { wasValid = false; } //Value was invalid. if ((amount < MIN_AMOUNT) (amount > MAX_AMOUNT)) { wasValid = false; if (change) { if (amount < MIN_AMOUNT) { amount = MIN_AMOUNT; } else { // amount is greater than MAX_AMOUNT amount = MAX_AMOUNT; } } } //Whether value was valid or not, format it nicely. if (change) { amountField.setText(moneyFormat.format(amount)); amountField.selectAll(); } return wasValid; } //Checks that the rate field is valid. If it is valid, //it returns true; otherwise, returns false. If the //change argument is true, this method reigns in the //value if necessary and (even if not) sets it to the //parsed number so that it looks good--no letters, //for example. protected boolean checkRateField(boolean change) { ...//Similar to checkAmountField... } //Checks that the numPeriods field is valid. If it is valid, //it returns true; otherwise, returns false. If the //change argument is true, this method reigns in the //value if necessary and (even if not) sets it to the //parsed number so that it looks good--no letters, //for example. protected boolean checkNumPeriodsField(boolean change) { ...//Similar to checkAmountField... } public void actionPerformed(ActionEvent e) { JTextField source = (JTextField)e.getSource(); shouldYieldFocus(source); //ignore return value source.selectAll(); } }
The input verifier is installed using the JComponent setInputVerifier method. For example, InputVerificationDemo has this code: private MyVerifier verifier = new MyVerifier(); .. amountField.setInputVerifier(verifier); Making a Custom Component Focusable
To gain the focus, a component must satisfy three requirements: It must be visible, enabled, and focusable. It's also likely that you'll want to give it an input map. If you don't know what an input map is, please read How to Use Key Bindings (page 623). The TrackFocusDemo example defines the simple component Picture . The following is its constructor: public Picture(Image image) { this.image = image; setFocusable(true); addMouseListener(this); addFocusListener(this); } The call to setFocusable(true) makes the component focusable. If you explicitly give your component key bindings in its WHEN_FOCUSED input map, you don't need to call set-Focusable . To visually show changes in the focus (by painting a red border only when the component has the focus), Picture has a focus listener. To gain the focus when the user clicks on the picture, the component has a mouse listener. The listener's mouseClicked method requests that the focus be transferred to the picture. Here's the code: public void mouseClicked(MouseEvent e) { //Since the user clicked on us, now get the focus! requestFocusInWindow(); } See Tracking Focus Changes to Multiple Components (page 595) for more discussion of the TrackFocusDemo [51] example. [51] You can find the TrackFocusDemo source files here: JavaTutorial/uiswing/misc/example-1dot4/index.html#TrackFocusDemo . Customizing Focus Traversal
The focus subsystem determines a default order that's used when the focus traversal keys (such as Tab) are used to navigate. A Swing application has its policy determined by LayoutFocusTraversalPolicy . You can set a focus traversal policy on any Container , though if the container isn't a focus cycle root it may have no apparent effect. The FocusTraversalDemo example shown in Figure 28 demonstrates how to customize focus behavior. Figure 28. A screenshot of the FocusTraversalDemo example.
Try This:
The check box was removed from the focus cycle with this line of code: togglePolicy.setFocusable(false); Here's the application's custom FocusTraversalPolicy : ... JTextField tf1, tf2, tf3, tf4, tf5, tf6; JTable table; ... public class MyOwnFocusTraversalPolicy extends FocusTraversalPolicy { public Component getComponentAfter(Container focusCycleRoot, Component aComponent) { if (aComponent.equals(tf1)) { return tf2; } else if (aComponent.equals(tf2)) { return tf3; } else if (aComponent.equals(tf3)) { return tf4; } else if (aComponent.equals(tf4)) { return tf5; } else if (aComponent.equals(tf5)) { return tf6; } else if (aComponent.equals(tf6)) { return table; } else if (aComponent.equals(table)) { return tf1; } return tf1; } public Component getComponentBefore(Container focusCycleRoot, Component aComponent) { if (aComponent.equals(tf1)) { return table; } else if (aComponent.equals(tf2)) { return tf1; } else if (aComponent.equals(tf3)) { return tf2; } else if (aComponent.equals(tf4)) { return tf3; } else if (aComponent.equals(tf5)) { return tf4; } else if (aComponent.equals(tf6)) { return tf5; } else if (aComponent.equals(table)) { return tf6; } return tf1; } public Component getDefaultComponent(Container focusCycleRoot) { return tf1; } public Component getLastComponent(Container focusCycleRoot) { return table; } public Component getFirstComponent(Container focusCycleRoot) { return tf1; } } To use a custom FocusTraversalPolicy , use code like the following on any focus cycle root. MyOwnFocusTraversalPolicy newPolicy = new MyOwnFocusTraversalPolicy(); frame.setFocusTraversalPolicy(newPolicy); You can remove the policy by setting FocusTraversalPolicy to null. This restores the default policy. Tracking Focus Changes to Multiple Components
In some situations an application may need to track which component has the focus. This information might be used to update menus or perhaps a status bar dynamically. If you need to track the focus only on specific components, it may make sense to implement a focus event listener. (See How to Write a Focus Listener (page 665) in Chapter 10.) If a focus listener isn't appropriate, you can register a PropertyChangeListener on the KeyboardFocusManager . The property change listener is notified of every change involving the focus, including changes to the focus owner, the focused window, and the default focus traversal policy. For a complete list, see Table 17 (page 601). Figure 29 shows TrackFocusDemo , which tracks the focus owner by installing a property change listener on the keyboard focus manager. Figure 29. A screenshot of the TrackFocusDemo example.
Try This:
You can view the demo's code in TrackFocusDemo.java . The custom component used for painting the images is in Picture.java . Here's the code that defines and installs the property change listener: KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); focusManager.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); if (("focusOwner".equals(prop)) && (e.getNewValue() != null) && ((e.getNewValue()) instanceof Picture)) { Component comp = (Component)e.getNewValue(); String name = comp.getName(); Integer num = new Integer(name); int index = num.intValue(); if (index < 0 index > comments.length) { index = 0; } info.setText(comments[index]); } } }); The custom component, Picture , is responsible for painting the image. All six components are defined in the following manner: pic1 = new Picture(createImageIcon("images/" + mayaString + ".gif", mayaString).getImage()); pic1.setName("1"); Timing Focus Transfers
Focus transfers are asynchronous, which can lead to some odd timing- related problems and assumptions, especially during automatic transfers of the focus. Imagine an application with a window containing a Start button, a Cancel button, and a text field. The components are added in this order:
When an application is launched, the LayoutFocusTraversalPolicy determines the focus traversal policyin this case, it's the order in which the components were added to their container. In this example, the desired behavior is that the Start button have the initial focus; when it's clicked, it's disabled and the Cancel button gets the focus. The correct way to implement this is to add the components to the container in the desired order or to create a custom focus traversal policy. If, for some reason, this isn't possible, the way to implement this is with the following code snippet: public void actionPerformed(ActionEvent e) { //This works. start.setEnabled(false); cancel.requestFocusInWindow(); } As desired, the focus goes from the Start button to the Cancel button rather than to the text field. But a different result occurs if the same methods are called in the opposite order, like this: public void actionPerformed(ActionEvent e) { //This doesn't work. cancel.requestFocusInWindow(); start.setEnabled(false); } In this case, the focus is requested on the Cancel button before it has left the Start button. The call to requestFocusInWindow initiates the transfer, but doesn't immediately move the focus to the Cancel button. When the Start button is disabled, the focus is transferred to the next component (so there's always a component with the focus) and, in this case, it then moves the focus to the text field, not the Cancel button. The need to make focus requests after all other changes that might affect the focus applies to
The Focus API
Tables 15 and 16 list the commonly used constructors and methods related to focus. Table 17 defines the bound properties for KeyboardFocusManager . A listener can be registered for these properties by calling addPropertyChangeListener . For more detailed information about the focus architecture, see the specification for the Focus Subsystem online at: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html. In addition, you may find How to Write a Focus Listener (page 665) in Chapter 10 useful. You should also refer to the API documentation for LayoutFocusTraversalPolicy , SortingFocusTraversalPolicy , InputVerifier , and KeyboardFocusManager at: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/LayoutFocus-TraversalPolicy.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/SortingFocus-TraversalPolicy.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/InputVerifier.html http://java.sun.com/j2se/1.4.2/docs/api/java/awt/KeyboardFocus-Manager.html Table 15. Useful Methods for Components (This API was introduced in release 1.4.)
Table 16. Creating and Using a Custom FocusTraversalPolicy (This API was introduced in release 1.4. Unless otherwise specified, each method is defined in the FocusTraversalPolicy interface.)
Table 17. KeyboardFocusManager Properties This API was introduced in release 1.4.
Examples That Use Focus
The following table lists examples that manipulate the focus.
|
< Day Day Up > |