Manage focus with Swing in Java
1. Introduction
The focus is the "selected" state of a component. The component who has the focus is the active component. It's possible to ask focus fror a specific component but normally this is perfectly managed by Swing.
With this tutorial, you will learn how to ask focus for a specific component and how to define an order of focus. We will also learn a bit about the validation of input fields, the focus listen and the utility of KeyboardFocusManager.
2. Ask focus
Normally, the focus is managed by a component on the mouse click or when we come to a component with keyboard. A component who is focused is often visually modified, with a special border or an other background color.
To manage the focus of the windows, it's a little bit different and depends of the operating system, but nothing can give you the guarantee to have focus. On Windows, you can obtain focus on a window using toFront but nothing is guraranteed.
For the components, you can use the requestFocusInWindow() method :
component.requestFocusInWindow();
To obtain the focus, you have to ask focus after the add of the component but before the display of the window.
Before Java 1.4, you needed to use the requestFocus() method, but now it is not a good idea to use it, because it give also focus to the window of the component, and that's not always possible.
3. Navigation keys
By default, we navigate in an application with the Tab and Shift+Tab keys. But we can edit these keys.
For that, we have to get the keys with getFocusTraversalKeys(int id) and add the new key. By example, here is how to add the Enter key for the forward focus navigation :
Set keys = textField.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); Set newKeys = new HashSet(keys); newKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); textField.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newKeys);
If you want to add a key for the backward navigation, you need to use BACKWARD_TRAVERSAL_KEYS instead of FORWARD_TRAVERSAL_KEYS.
4. Validate an input
You often need to validate to validate the inputs of the user in a field. The best way to do that is to test the content directly after the focus lost. Of course, there is still Formatter to do that, but it's possible that you must use the validation on a custom component.
To do that, we'lll use InputVerifier. This InputVerifier enable to get the value of a field when this field lost focus. If the value is not valid, the InputVerifier can execute a particular action by example set the old value in the field insted of the new invalid value or give focus again to the field.
To use it, we've to create a class extending InputVerifier and use the setInputVerifier() method on the component to test :
CustomInputVerifier verifier = new CustomInputVerifier(); ... monComposant.setInputVerifier(verifier)
The inputVerifier class has only two methods :
- verifiy(JComponent input) : Indicate if the value of the component is correct. It's must be overriden.
- shouldYieldFocus(JComponent input) : This method idnicate if the component can loose focus or not. If it's return true, the focus change normally to the next component else the focus remains in the
current component while the input is invalid. By default, it's only return the value of verify().
For lisibility reasons, it's better to only implement the test of the input value in the verify method and add the other functionalities by example a beep it it's invalid in the shouldYieldFocus method.
We will see a simple example of InputVerifier. This verifier verify that the value is a number greater than zero but lower than x. If the value is not valid, we simple emit a beep.
import java.awt.Toolkit; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JTextField; public class NumberInputVerifier extends InputVerifier { private int max = 0; public NumberInputVerifier(){ this(100); } public NumberInputVerifier(int maximum){ super(); this.max = maximum; } public boolean shouldYieldFocus(JComponent input) { boolean valid = verify(input); if (valid) { return true; } else { Toolkit.getDefaultToolkit().beep(); return false; } } public boolean verify(JComponent input) { JTextField field = (JTextField)input; String value = field.getText(); int intValue; try { intValue = Integer.parseInt(value); } catch (NumberFormatException pe) { return false; } if(intValue < 0 || intValue > max){ return false; } return true; } }
You can of course make a lot of things with this class. You can use it to change the format of a number adding " or , or modify everithing else in the input component.
You can also use the same InputVerifier for several components. You just have to test the concerned component with the parameter of the verify method. But don't make too complex InputVerifier.
5. Listen the focus
A first way to know which component has the focus is to use a focus listener. You just have to create a class implementing FocusListener and add this listerner to every components you want to manage. Then, you just have to make things in the methods of the listener : focusGained and focusLost. This way is quite simple, but if you have a lot of components, it will be really heavy to implement.
An other way is to use the KeyboardFocusManager class. You just have to add a PropertyChangeListener on this class and verify the property focusOwner. You can also listener some other things : the focus of the windows, the changes on the focus order, ... You can find here a complete liste of properties. This times, you'll directly see all the focus changes for all the components in your application. If you want to restrain the set of components, you have to use the first way with FocusListener.
Here is an example with KeyboardFocusManager : KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); focusManager.addPropertyChangeListener( new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { String properties = e.getPropertyName(); if (("focusOwner".equals(properties)) && (e.getNewValue() != null)) { Component component = (Component)e.getNewValue(); String name = component.getName(); System.out.println(name + " take focus"); } } } }
6. Manage the order of focus
Swing has a correct focus order by default and normally it's enough. But you can change this order, by example to navigate in a complex form. By default, Swing determinate the focus order using the order of adding the components to the content pane.
Before Java 1.4, you had to use setNextFocusableComponent() on every component to specify the next component to have the focus. This method is now deprecated. We'll use the LayoutFocusTraversalPolicy class to define the focus order in a Swing application.
For that, we have to create a new class extending FocusTraversalPolicy and define this set of methods :
- getComponentAfter : Indicate which component will have focus after the component in parameter.
- getComponentBefore : Indicate which component has the focus before the component in parameter.
- getDefaultComponent : Indicate which component is the default component of the container on which we've applied the policy. It's used when we swith to this focus cycle.
- getLastComponent : Indicate which component is the last of this continer. It's used when we swith to this focus cycle.
- getFirstComponent : Indicate which component is the first of this container. It's used when we swith to this focus cycle.
- getInitialComponent : Indicate which component must have the focus when the window is displayed.
Here is a little example :
public class TestFocusTraversalPolicy extends FocusTraversalPolicy { public Component getComponentAfter(Container focusCycleRoot, Component aComponent) { if (aComponent.equals(component1)) { return component2; } else if (aComponent.equals(component2)) { return component3; } else if (aComponent.equals(component3)) { return component4; } else if (aComponent.equals(component4)) { return component5; } else if (aComponent.equals(component5)) { return component1; } return component1; } public Component getComponentBefore(Container focusCycleRoot, Component aComponent) { if (aComponent.equals(component1)) { return component5; } else if (aComponent.equals(component2)) { return component1; } else if (aComponent.equals(component3)) { return component2; } else if (aComponent.equals(component4)) { return component3; } else if (aComponent.equals(component5)) { return component4; } return component1; } public Component getDefaultComponent(Container focusCycleRoot) { return component1; } public Component getLastComponent(Container focusCycleRoot) { return component5; } public Component getFirstComponent(Container focusCycleRoot) { return component1; } }
The disadvantage of this technique is that the policy must know all the components. That why we often use an inner class to give it access to the member of the current class. Here is a simple example :
public class View { private Component component1; private Component component2; private Component component3; private Component component4; private Component component5; class TestFocusTraversalPolicy extends FocusTraversalPolicy { //Traversal policy methods } }
You can also use an inner anonymous class, but with that kind of class, you cannot use it for several components.
To apply this policy, you have to use the setFocusTraversalPolicy() method of the Container class :
TestFocusTraversalPolicy newPolicy = new TestFocusTraversalPolicy(); myContainer.setFocusTraversalPolicy(newPo
Of course you can define several strategies for several panels or the same policy for several panels.
If you want to restore the default focus policy, you just have to call setFocusTraversalPolicy() with null as parameter.
You can also edit the default FocusTraversalPolicy with one of yours with the setDefaultTraversalPolicy() method from the KeyboardFocusManager class.
6.1. Simplification
But it's not very easy to do that and that can quickly be heavy. So here is a simple class to manage the positions of all the components with an integer :
import java.awt.Component; import java.awt.Container; import java.awt.FocusTraversalPolicy; import java.util.HashMap; /** * A focus traversal policy who manage the order of components in the focus cycle using a int. * The first component of the cycle has the position of 0 and all the others follows with an interval of 1. * The last component must have the position (size() -1). * * @author Baptiste Wicht * */ public class MapFocusTraversalPolicy extends FocusTraversalPolicy { private HashMap components = new HashMap(); private HashMap positions = new HashMap(); /** * Add the component to the order with the specfieid position. * * @param component The component to add. * @param position The position of the component in the focus cycle root. */ public void addComponent(Component component, int position){ components.put(position, component); positions.put(component, position); } @Override public Component getComponentAfter(Container parent, Component component) { int position = positions.get(component); if(position = positions.size() - 1){ position = 0; } return components.get(position + 1); } @Override public Component getComponentBefore(Container parent, Component component) { int position = positions.get(component); if(position = 0){ position = positions.size() - 1; } return components.get(position - 1); } @Override public Component getDefaultComponent(Container parent) { return components.get(0); } @Override public Component getFirstComponent(Container parent) { return components.get(0); } @Override public Component getLastComponent(Container parent) { return components.get(components.size() - 1); } }
Vous can use this class as every other focus traversal policy we saw before. Just add all the components before using the policy.
An other way that you can explore it's to make all your components implementing an interface like Focusable with a getFocusPosition() method and use a policy to make the order with that Focusable components.
7. The KeyboardFocusManager class
There is also other utilities to the KeyboardFocusManager class.
First, you can change the component who has the focus with the focusXXX methods. By example, you can use the focusNextComponent() method to give the focus to the next component. you can also give the focus to a specific component with the setFocusOwner method or to a window with with the setActiveWindows method.
Then, you can also get the active window, the component with the focux and the container who contains the active component. If the components are in an other thread, you have to use the getGlobalXXX methods to get it.
8. Conclusion
To conclude, you can do almost everithing with the focus system. You can modify the order of focus for the components, say which components must have the focus, make tests on the input fields, know who has the focus. But that's sometimes weird when we start. But once we've understand which classes to use, it is quite simple.
Comments
Comments powered by Disqus