M.E.
M.E.

Reputation: 5495

How to get a Key Event in a Java Swing JFrame instance which has many JTextFields?

I have a JFrame class which implements several JLabels and JTextFields and I would like to perform an action when the focus is on that JFrame and the user press F1.

I have created the following class to handle the KeyListener:

public class PropertiesKey implements KeyListener, ActionListener {

    private Properties propertiesWindow;

    public PropertiesKey(Properties p) {
        propertiesWindow = p;
        System.out.println("DEBUG PropertiesKey");
    }

    @Override
    public void keyPressed(KeyEvent event) {

        System.out.println("DEBUG keyPressed");
        // F1 - Display Attribute Window
        if (event.getKeyCode() == KeyEvent.VK_F1){ 
            System.out.println("F1");
            if(propertiesWindow.isVisible()) {
                propertiesWindow.setVisible(false);
            } else {
                propertiesWindow.setVisible(true);
            }
        }
        System.out.println("KEY PRESSED " + event);
    }

    @Override
    public void keyReleased(KeyEvent event) {
        System.out.println("KEY RELEASED " + event);
    }

    @Override
    public void keyTyped(KeyEvent event) {
        System.out.println("KEY TYPED " + event);
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        System.out.println("ACTION PERFORMED " + event);
    }

}

Which I attach to the main JFrame class using:

this.addKeyListener(new PropertiesKey(this));

I am sure that this is actually attached as I see the System.out message when the Key listener class is created.

While this strategy works in another area of the program where I have an AWT Frame, I can not manage to get the KeyEvent propagated to my listener in this specific case of a JFrame populated with JTextFields.

I suspect that the TextFields -the focus on the JFrame will be always captured by one TextField- might be intercepting the event and not propagating it.

If that is the case, how can I fix it? Which strategy/pattern is used to capture Key events at JFrame level?

It it is not the case, how could I further troubleshoot this issue?

Upvotes: 0

Views: 2296

Answers (2)

camickr
camickr

Reputation: 324147

You should not be using a KeyListener. Key events can only be generated for the component that has focus. The frame will not have direct focus, only a component added to the frame.

Instead you should be using Key Bindings. With key bindings you can map a Keystroke to an Action even when a component doesn't have focus. Make sure you use the appropriate InputMap.

In this case if you want to add a general handler for the frame you would add the key bindings to the "root pane" of the frame.

Read the section from the Swing tutorial on How to Use Key Bindings for more information.

Also read the section on How to Use Actions for the benefits of using an Action.

Upvotes: 1

Mikle Garin
Mikle Garin

Reputation: 10153

KeyListener only works (in most cases) for components that are currently focused. It also won't work if you add it to a container of the focused component in case when that component consumes key events. Registering a keyboard action would be a better way to approach window-wide hotkeys.

Here is a small example of how it could be done:

public class FrameHotkey
{
    public static void main ( final String[] args )
    {
        SwingUtilities.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                final JFrame frame = new JFrame ();

                frame.setLayout ( new FlowLayout ( FlowLayout.CENTER, 15, 15 ) );
                frame.add ( new JLabel ( "Field 1:" ) );
                frame.add ( new JTextField ( "Field 1", 15 ) );
                frame.add ( new JLabel ( "Field 2:" ) );
                frame.add ( new JTextField ( "Field 2", 15 ) );

                // Hotkey for the F1 in window
                frame.getRootPane ().registerKeyboardAction ( new ActionListener ()
                {
                    @Override
                    public void actionPerformed ( final ActionEvent e )
                    {
                        JOptionPane.showMessageDialog ( frame, "F1 have been pressed!" );
                    }
                }, KeyStroke.getKeyStroke ( KeyEvent.VK_F1, 0 ), JComponent.WHEN_IN_FOCUSED_WINDOW );

                frame.setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE );
                frame.pack ();
                frame.setLocationRelativeTo ( null );
                frame.setVisible ( true );
            }
        } );
    }
}

Note that I'm registering hotkey on JRootPane of the JFrame here, but it generally does't matter because condition is JComponent.WHEN_IN_FOCUSED_WINDOW - meaning that you can register it on any component in the window and as long as the window is focused in the system you will receive the action event.

Alternatively, if your application has a JMenuBar with menu items that would perform the actions you desire - you can specify accelerators for those menu items and they will handle the action upon specified hotkey on their own.

I also recommend reading the article from Swing tutorial camickr offered in the other answer.

Upvotes: 0

Related Questions