Java Swing : Custom Traversal Order

Recently I have had the opportunity to dust off my Java Swing skills and work on a thick client application for my customer. Coming back to Swing after several years of web applications and big data back ends, I have been surprised by how much complexity there is under the covers of Swing! There is a lot of expressiveness and power that I think is often overlooked due to the many bad examples of Swing most developers have encountered.

I want to write a series of blogs detailing my adventures with Swing and hopefully leave behind a trail of code and interesting examples that will help those who are trying sharpen their Swing skills (or at least survive coding a desktop application!) make better use of Swing.

So my very first Swing challenge was to impose a left to right, top to bottom "traversal order" on a three different forms in the desktop application I was working on. (A "traversal order" is the order that components receive focus in an application. Focus is granted when a component is clicked on, but can also be cycled manually via the keyboard such as by using the Tab key to navigate fields in a form.)

By default Swing traverses every eligible (meaning every visible, enabled, viewable, etc.) Component in a Container in the order it was added to the Container. Now it is often the case that coding practices and UI design conflict when it comes to adding components to a Container! Components are seldom instantiated and added in your code in the same order you want your users to navigate them. Hence the need arises for imposing acustom traversal order.

Enter the FocusTraversalPolicy object. You can extend this class and imposes a ordering of your own design onto the components in a container.

A custom FocusTraversalPolicy is added to a container via the setFocusTraversalPolicy() method on the Container object.

JPanel mainPanel = new Panel();  
...
CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy();  
mainPanel.setFocusTraversalPolicy(policy);  

The interface defined by FocusTraversalPolicy is simple but expressive. There are five methods:

  • getDefaultComponent()
  • getFirstComponent()
  • getLastComponent()
  • getComponentBefore()
  • getComponentAfter()

The traversal order is defined in relationship to each component. You do not have to impose a linear order. Each component can make a decision about what component should receive focus next (possibly dynamically based on other conditions such as the state of other Components).

Below is a simple implementation of FocusTraversalPolicy as an example:

import java.awt.Component;  
import java.awt.Container;  
import java.awt.FocusTraversalPolicy;  
import java.util.ArrayList;

public class CustomFocusTraversalPolicy extends FocusTraversalPolicy {  
    private ArrayList<Component> order;
    public CustomFocusTraversalPolicy(ArrayList<Component> order) {
        this.order = order;
    }

    @Override
    public Component getComponentAfter(Container container, Component component) {
        int index = (order.indexOf(component) + 1) % order.size();
        Component after = order.get(index);
        while (index < order.size() && !(after.isEnabled() && after.isVisible())) {
            index++;
            after = order.get(index);
        }
        return after;
    }

    @Override
    public Component getComponentBefore(Container container, Component component) {
        int index = (order.indexOf(component) - 1);
        if (index < 0) {
            index = order.size() - 1;
        }
        Component before = order.get(index);
        while (index >= 0 && !(before.isEnabled() && before.isVisible())) {
            index --;
            before = order.get(index);
        }
        return before;
    }

    @Override
    public Component getFirstComponent(Container container) {
        int index = 0;
        Component first = order.get(index);
        while (index < order.size() && !(first.isEnabled() && first.isVisible())) {
            index++;
            first = order.get(index);
        }
        return first;
    }

    @Override
    public Component getLastComponent(Container container) {
        int index = order.size() - 1;
        Component last = order.get(index);
        while (index >= 0 && !(last.isEnabled() && last.isVisible())) {
            index--;
            last = order.get(index);
        }
        return last;
    }

    @Override
    public Component getDefaultComponent(Container container) {
        return getFirstComponent(container);
    }
}

Code is available at: https://gist.github.com/marlhammer/6348987#file-customfocustraversalpolicy-java

This is simple focus traversal policy takes a predefined list of components and simply traverses back and forth through the list accounting for disabled or invisible components along the way. (Note: Yes. There is the possibility of an infinite loop if all components in the list are disabled or invisible... So please do not do that.)

For a simple case where there is not a significant amount of dynamic Component manipulation or a complex hierarchy of Containers this policy works extremely well and is easy to maintain. More advanced techniques are possible and I recommend you start here: http://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html#customFocusTraversal if your needs exceed what can be accomplished with the simple code above.

Questions? Comments? Email me at: [email protected]

Happy coding!