Motasem
Motasem

Reputation: 609

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);
        }
    }
}

Upvotes: 3

Views: 5789

Answers (2)

David Kroukamp
David Kroukamp

Reputation: 36423

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);
            }
        });
    }
}

Upvotes: 5

Guillaume Polet
Guillaume Polet

Reputation: 47608

Not sure this is the best way to do it, but it works decently enough and if it is not the best way, you get a free SSCCE out of this answer.

I override processKeyBindings() and if it returns true then I grab the focus to indicate that the action has been performed. If you want to do something else you just need to modify the code there.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class IButtonSave extends JButton {
    private static final long serialVersionUID = 1L;

    public IButtonSave() {
        super();
        setFocusPainted(true);
        setFocusable(true);
    }

    @Override
    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
        boolean processKeyBinding = super.processKeyBinding(ks, e, condition, pressed);
        if (processKeyBinding) {
            requestFocusInWindow();
        }
        return processKeyBinding;
    }

    @Override
    public void setAction(Action a) {
        super.setAction(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]");
        setToolTipText("[F1]");
    }

    protected void initUI() {
        JFrame frame = new JFrame(IButtonSave.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JTextArea textarea = new JTextArea(5, 30);
        JPanel buttonPanel = new JPanel();
        AbstractAction someAction = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.err.println("Action performed");
            }
        };
        IButtonSave button = new IButtonSave();
        button.setAction(someAction);
        buttonPanel.add(button);
        frame.add(new JScrollPane(textarea));
        frame.add(buttonPanel, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new IButtonSave().initUI();
            }
        });
    }

}

Upvotes: 3

Related Questions