1

I'm working in a project where I need to add a key shortcut for each JRadioButton, while looking on another similar question and as I'm using some other custom Actions I decided to use the method setAction on each of my JRadioButtons, however it requires me to press ALT + 1 - ALT + 5 to "trigger" the actionPerformed method of my CustomAction class.

How can I modify this class in order to just press 1 - 5 and get the same behaviour?

This is the code I made that demonstrates this issue:

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

import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class RadioButtonSelectableByNumbers {
    private JFrame frame;
    private JRadioButton buttons[];
    private ButtonGroup group;

    public RadioButtonSelectableByNumbers() {

    }

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

    public void createAndShowGui() {
        frame = new JFrame("frame");
        buttons = new JRadioButton[5];
        group = new ButtonGroup();

        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));

        for (int i = 0; i < buttons.length; i++) {
            buttons[i] = new JRadioButton();
            switch (i) {
                case 0:
                    buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_1));
                    break;
                case 1:
                    buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_2));
                    break;
                case 2:
                    buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_3));
                    break;
                case 3:
                    buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_4));
                    break;
                default:
                    buttons[i].setAction(new CustomAction(String.valueOf(i + 1), KeyEvent.VK_5));
                    break;
            }
            group.add(buttons[i]);
            frame.getContentPane().add(buttons[i]);
        }

        frame.pack();
        frame.setVisible(true);
    }

    class CustomAction extends AbstractAction {
        public CustomAction(String name, Integer mnemonic, Integer modifier) {
            super(name);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(mnemonic, modifier));
        }

        public CustomAction(String name, Integer mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("radio clicked");
        }
    }
}
Community
  • 1
  • 1
Frakcool
  • 10,915
  • 9
  • 50
  • 89

1 Answers1

2

How do you tie any key to a component in Swing? Key Bindings: Key Bindings Tutorial.

Hang on while I look at your code...

For example

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

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

public class RadioButtonSelectableByNumbers {
    private JFrame frame;
    private JRadioButton buttons[];
    private ButtonGroup group;

    public RadioButtonSelectableByNumbers() {

    }

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

    public void createAndShowGui() {
        frame = new JFrame("frame");
        frame.setDefaultCloseOperation(JFrame);
        buttons = new JRadioButton[5];
        group = new ButtonGroup();

        frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS));

        for (int i = 0; i < buttons.length; i++) {
            JRadioButton rbtn = createButton(i);
            buttons[i] = rbtn;
            frame.getContentPane().add(rbtn);
        }

        frame.pack();
        frame.setVisible(true);
    }

    private JRadioButton createButton(int i) {
        String name = String.valueOf(i + 1);
        int stdMnemonic = KeyEvent.VK_1 + i;  // for standard number keys
        int numpadMnemonic = KeyEvent.VK_NUMPAD1 + i;  // for numpad number keys
        Action action = new CustomAction(name, stdMnemonic);
        JRadioButton rBtn = new JRadioButton(action);
        group.add(rBtn);

        // bindings active if window is focused. Component doesn't have to be focused
        int condition = JComponent.WHEN_IN_FOCUSED_WINDOW; 
        InputMap inputMap = rBtn.getInputMap(condition);
        ActionMap actionMap = rBtn.getActionMap();
        KeyStroke keyStroke = KeyStroke.getKeyStroke(stdMnemonic, 0);
        KeyStroke keyStroke2 = KeyStroke.getKeyStroke(numpadMnemonic, 0);
        inputMap.put(keyStroke, keyStroke.toString());
        actionMap.put(keyStroke.toString(), action);
        inputMap.put(keyStroke2, keyStroke2.toString());
        actionMap.put(keyStroke2.toString(), action);
        return rBtn;
    }

    class CustomAction extends AbstractAction {
        public CustomAction(String name, Integer mnemonic, Integer modifier) {
            super(name);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(mnemonic, modifier));
        }

        public CustomAction(String name, Integer mnemonic) {
            super(name);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("radio clicked: " + e.getActionCommand());
        }
    }
}

Another solution, that may be better, since usually JRadioButtons don't use ActionListeners, is to create an AbstractAction that simply clicks the button. In the example below, MyAction does this, as well as gives the active JRadioButton the focus:

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class NumberActions extends JPanel {
    private ButtonGroup buttonGroup = new ButtonGroup();

    public NumberActions() {
        ItemListener itemListener = new MyItemListener();
        setLayout(new GridLayout(1, 0));
        for (int i = 0; i < 10; i++) {
            JRadioButton rBtn = createRadioBtn(i);
            rBtn.addItemListener(itemListener);
            buttonGroup.add(rBtn);
            add(rBtn);
        }
    }

    private JRadioButton createRadioBtn(int i) {
        String text = String.valueOf(i);
        JRadioButton rBtn = new JRadioButton(text);
        rBtn.setActionCommand(text);

        int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
        InputMap inputMap = rBtn.getInputMap(condition);
        ActionMap actionMap = rBtn.getActionMap();
        Action action = new MyAction(rBtn);

        bindAction(inputMap, actionMap, action, KeyEvent.VK_0 + i);
        bindAction(inputMap, actionMap, action, KeyEvent.VK_NUMPAD0 + i);

        return rBtn;
    }

    private void bindAction(InputMap inputMap, ActionMap actionMap, Action action, int mnemonic) {
        KeyStroke keyStroke = KeyStroke.getKeyStroke(mnemonic, 0);
        inputMap.put(keyStroke, keyStroke.toString());
        actionMap.put(keyStroke.toString(), action);
    }

    private class MyItemListener implements ItemListener {
        @Override
        public void itemStateChanged(ItemEvent iEvt) {
            if (iEvt.getStateChange() == ItemEvent.SELECTED) {
                AbstractButton btn = (AbstractButton) iEvt.getSource();
                System.out.println("Button: " + btn.getActionCommand());
            }
        }
    }

    private class MyAction extends AbstractAction {
        private AbstractButton btn;

        public MyAction(AbstractButton btn) {
            this.btn = btn;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            btn.requestFocusInWindow();
            btn.doClick();
        }
    }

    private static void createAndShowGui() {
        NumberActions mainPanel = new NumberActions();

        JFrame frame = new JFrame("NumberActions");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

edit: code fixed

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • I understand better the Key Bindings now, when I read the tutorial (before you suggested it) it was kind of confusing to me, now it became clearer. I think that's all I was missing, however can you explain why you're adding `i` on these lines? `KeyEvent.VK_1 + i;` and the numpad line? – Frakcool Dec 03 '16 at 04:43
  • The mnemonic numbers are monotonically increasing. Rather than the unnecessary ugly and brittle switch/case statement, let the numbers calculate there own mnemonic values. I need to account though for both standard keys and number pad keys which use different mnemonics. – Hovercraft Full Of Eels Dec 03 '16 at 04:46
  • Oh, it's the enum values increasing, I was looking for that too, thanks I'll accept once I go back to my pc and try it – Frakcool Dec 03 '16 at 04:48
  • @Frakcool: no enums involved here, just int constants. Please see edit to answer regarding what I think is a better solution, one that will work if you wire your JRadioButtons with ItemListeners or what not. – Hovercraft Full Of Eels Dec 03 '16 at 05:04
  • 1
    (1+), I always forget about handling the entry of numbers using the numpad. – camickr Dec 03 '16 at 05:11