Search code examples
javaswingfocusjbuttonkey-bindings

JButton & Action & KeyBinding


I have created a JButton class that recieving Action, the JButton class includes keystrokes & mouse listener so i can use the same class in multiple frames as needed.

My problems is that: JButton not getting the focus when pressing the key, but it doing the action. i need to make a new background or something that tell the user that the button did the action.

Any ideas??

Here is my code:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;
import swtdesigner.SwingResourceManager;

public class IButtonSave extends JButton{
    private static final long serialVersionUID = 1L;
    private Action action = null;
    public IButtonSave() {
        super();
        setFocusPainted(true);
        setFocusable(true);

        try {
            jbInit();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void jbInit() throws Exception {
        setMargin(new Insets(0, 0, 0, 0));
        setBorder(new LineBorder(Color.black, 1, true));
        setIconTextGap(0);
        setHorizontalTextPosition(SwingConstants.CENTER);
        setVerticalTextPosition(SwingConstants.TOP);
        setPreferredSize(new Dimension(50, 43));
        setMinimumSize(new Dimension(50, 43));
        setMaximumSize(new Dimension(50, 43));
        addMouseListener(new ThisMouseListener());
        setVerifyInputWhenFocusTarget(true);
    }

    public void setAction(Action a){
        action = a;
        KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F1,0,true);
        KeyStroke ks2 = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0);
        getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, "Save");
        getInputMap(JComponent.WHEN_FOCUSED).put(ks2, "Save");

        getActionMap().put("Save", a);
        setText("Save [F1]");
        setIcon(SwingResourceManager.getIcon(SwingResourceManager.class, "/images/small/save.png"));
        setToolTipText("[F1]");
    }

    private class ThisMouseListener extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {
            this_mouseClicked(e);
        }
    }
    protected void this_mouseClicked(MouseEvent e) {
        if(e.getClickCount() >= 1){
            action.actionPerformed(null);
        }
    }
}

Solution

  • Why extend JButton class when you can simply add KeyBindings to its instance?

    Not to sure what you want but this works fine for me:

    Basically a JButton which can be activated by mouse click, or pressing F1 (as long as focus is in window and if pressed will shift focus to JButton) or ENTER (only when in focus of JButton).

    When the AbstractAction is called it will call requestFocusInWindow() on JButton (thus pressing F1 will make button gain focus which is what I think you wanted):

    enter image description here

    import java.awt.BorderLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.AbstractAction;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JTextField;
    import javax.swing.KeyStroke;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                    final JButton btn = new JButton("Button");
    
                    AbstractAction aa = new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent ae) {
                            System.out.println("Here");
    
                            btn.requestFocusInWindow();//request that the button has focus
                        }
                    };
    
                    //so button can be pressed using F1 and ENTER
                    btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter");
                    btn.getActionMap().put("Enter", aa);
                    btn.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "F1");
                    btn.getActionMap().put("F1", aa);
    
                    btn.addActionListener(aa);//so button can be clicked
    
                    JTextField tf = new JTextField("added to show ENTER wont work unless button in focus");
    
                    frame.add(tf);
                    frame.add(btn, BorderLayout.SOUTH);
    
                    frame.pack();
                    frame.setVisible(true);
                }
            });
        }
    }
    

    UPADTE:

    alternatively as @GuillaumePolet suggested (+1 to him) override processKeyBinding of JButton and check for appropriate key and than call the method:

    import java.awt.BorderLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.AbstractAction;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JTextField;
    import javax.swing.KeyStroke;
    import javax.swing.SwingUtilities;
    
    public class Test {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    final JFrame frame = new JFrame();
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                    final JButton btn = new JButton("Button") {
                        @Override
                        protected boolean processKeyBinding(KeyStroke ks, KeyEvent ke, int i, boolean bln) {
                            boolean b = super.processKeyBinding(ks, ke, i, bln);
    
                            if (b && ks.getKeyCode() == KeyEvent.VK_F1) {
                                requestFocusInWindow();
                            }
    
                            return b;
                        }
                    };
    
                    AbstractAction aa = new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent ae) {
                            System.out.println("Here");
                        }
                    };
    
                    //so button can be pressed using F1 and ENTER
                    btn.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter");
                    btn.getActionMap().put("Enter", aa);
                    btn.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "F1");
                    btn.getActionMap().put("F1", aa);
    
                    btn.addActionListener(aa);//so button can be clicked
    
                    JTextField tf = new JTextField("added to show ENTER wont work unless button in focus");
    
                    frame.add(tf);
                    frame.add(btn, BorderLayout.SOUTH);
    
                    frame.pack();
                    frame.setVisible(true);
                }
            });
        }
    }