Skip to content

How to use FocusManager

Mikle edited this page Aug 15, 2019 · 2 revisions

Available since: WebLaF v1.28 release
Required Java version: Java 6 update 30 or any later
Module: ui


What is it for?

One of the things lacking in Swing is ability to globally track various component states. One of such states is focused - it is a simple state informing that user has keyboard focus on the component that has the state.

There are various solutions to how focused state can be implemented for Swing components - most popular is simply registering FocusListener on the component. While FocusListener is sufficient for tracking a single component focused state in most common cases - it will not be of any help if you want to track it application-wide.

To provide such application-wide focus tracking and solve some other minor issues with default FocusListener I've created FocusManager that continuously focused component through AWTEventListener and makes sure that various listeners you can register in FocusManager are informed about every change they might be interested in.


How to use it?

The only way to use FocusManager is to register one of the available listeners, so first you need to decide what exactly you want to listen for - all focus change events or only ones for a particular component.

All listeners receive events from the same source, but they are filtered and delivered differently.

GlobalFocusListener

Can be registered to listen to all focus change events:

FocusManager.registerGlobalFocusListener ( new GlobalFocusListener ()
{
    @Override
    public void focusChanged ( @Nullable final Component oldFocus, @Nullable final Component newFocus )
    {
        ...
    }
} );

It can also be registered using an existing component:

FocusManager.registerGlobalFocusListener ( component, new GlobalFocusListener ()
{
    @Override
    public void focusChanged ( @Nullable final Component oldFocus, @Nullable final Component newFocus )
    {
        ...
    }
} );

That method is particularly useful if you have a component that will be listening to provided events. Events will not be limited to that component, but listener will be registered in FocusManager differently to help prevent possible memory leaks through the listener itself.

I recommend using second option or registering FocusTracker for particular component as described in the next section.

Here is a small practical example of GlobalFocusListener usage:

public final class GlobalFocusListenerExample
{
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                WebLookAndFeel.install ();

                final WebLabel label = new WebLabel ( "...", WebLabel.CENTER );
                TestFrame.show (
                        10, false, 10,
                        label,
                        new WebTextField ( "Sample", 20 ),
                        new WebButton ( "Sample" ),
                        new WebCheckBox ( "Sample" ),
                        new WebRadioButton ( "Sample" )
                );

                FocusManager.registerGlobalFocusListener ( label, new GlobalFocusListener ()
                {
                    @Override
                    public void focusChanged ( @Nullable final Component oldFocus, @Nullable final Component newFocus )
                    {
                        final String prev = oldFocus != null ? ReflectUtils.getClassName ( oldFocus ) : "none";
                        final String next = newFocus != null ? ReflectUtils.getClassName ( newFocus ) : "none";
                        label.setText ( prev + " -> " + next );
                    }
                } );
            }
        } );
    }
}

It will display a small frame with a few focusable components and label showing what component has the focus:

FocusTracker

DefaultFocusTracker implementation covers most common cases of focused state tracking - when component itself or one of it's children is focused. Additional related components can also be added to be considered sharing the focus with the tracked component which can be convenient for some cases.

It is generally recommended to be use DefaultFocusTracker instead of writing a custom FocusTracker implementation.

Any FocusTracker can be registered in FocusManager like this:

FocusManager.addFocusTracker ( component, new DefaultFocusTracker ( component, true )
{
    @Override
    public void focusChanged ( final boolean focused )
    {
        ...
    }
} );

Here is a small practical example of FocusTracker usage:

public final class FocusTrackerExample
{
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                WebLookAndFeel.install ();

                final WebLabel label = new WebLabel ( "Panel is not focused", WebLabel.CENTER );

                final WebCheckBox directFocusOnly = new WebCheckBox ( "Direct focus only", false );
                directFocusOnly.setHorizontalAlignment ( WebCheckBox.CENTER );

                final WebButton button = new WebButton ( "Sample button" );

                final WebPanel panel = new WebPanel ( StyleId.panelDecorated, new BorderLayout (), button );
                panel.setBackground ( Color.WHITE );
                panel.setPadding ( 20 );
                panel.setFocusable ( true );
                panel.onMousePress ( new MouseEventRunnable ()
                {
                    @Override
                    public void run ( final MouseEvent e )
                    {
                        panel.requestFocusInWindow ();
                    }
                } );

                TestFrame.show ( 20, false, 20, label, directFocusOnly, panel );

                final DefaultFocusTracker focusTracker = new DefaultFocusTracker ( panel, true )
                {
                    @Override
                    public void focusChanged ( final boolean focused )
                    {
                        label.setText ( focused ? "Panel is focused" : "Panel is not focused" );
                    }
                };
                FocusManager.addFocusTracker ( panel, focusTracker );

                directFocusOnly.addActionListener ( new ActionListener ()
                {
                    @Override
                    public void actionPerformed ( final ActionEvent e )
                    {
                        focusTracker.setUniteWithChildren ( !directFocusOnly.isSelected () );
                    }
                } );
            }
        } );
    }
}

It will display a small frame with label showing whether or not panel is currently focused:

You can also enable or disable uniteWithChildren setting in the DefaultFocusTracker that switches tracking mode, affecting whether or not focused children cause component to be counted as focused as well.

Now if you actually want to provide different set of conditions for deciding whether or not component is focused based on the actual focused component - you may actually want to write your own FocusTracker implementation and specify your conditions in isInvolved ( tracked, component ) method where tracked is your component and component is the actual focused component.


That's all you need to know to start using this feature.
I will update FocusManager with more options in the future, so stay tuned!