1

I have been looking for an answer to attach keybinding to a JButton for many-many hours, and still didn't manage to do it. I have the following simple program consisting of two classes. I tried using getInputMap() and getActionMap() several ways, but without success. I want it to do the following: When I press key "1" on the keyboard, it would press JButton btn1, and when I press key "2", it would press JButton btn2 (and 1 or 2 would appear on JLabel consequently).

//class1://

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

public class event06 extends JFrame {

    event06b base = new event06b(this);

    JButton btn1 = new JButton("1");
    JButton btn2 = new JButton("2");
    JLabel label = new JLabel("");

    public event06() {
        super();
        setBounds(300,300, 200,150);
        setResizable(true);
        setTitle("Button with keybinding");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        btn1.addActionListener(base);
        btn2.addActionListener(base);

        FlowLayout flo = new FlowLayout(FlowLayout.CENTER);
        setLayout(flo);
        add(btn1);
        add(btn2);
        add(label);

        setVisible(true);
    }

    public static void main(String[] args) {
        event06 window = new event06(); 
    }
}


//class 2://
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

public class event06b implements ActionListener {
    event06 gui;

    public event06b (event06 in) {
        gui = in;
    }
        public void actionPerformed(ActionEvent pressed) {
            Object source = pressed.getSource();
            if (source == gui.btn1) {gui.label.setText("1");}
            else if (source == gui.btn2) {gui.label.setText("2");}
        }
}

Update: (I still don't have a reputation of 15 yet, so I cannot answer my own question, and as far as I know I cannot post codes or long answers in comments, so I'm modifying my question instead).

What I managed to find out is that using KeyListeners only work, if no button has the focus. See the following example:

 //in class 1:// 
FlowLayout flo = new FlowLayout(FlowLayout.CENTER);
    setLayout(flo);
    add(btn1);
    btn1.setEnabled(false);
    btn2.setEnabled(false);
    add(btn2);
    add(label);

    btn1.addActionListener(base);
    btn2.addActionListener(base);
    addKeyListener(base);

Here, the two buttons, btn1 and btn2 are disabled, so they don't have focus, instead the window is on focus. That's why the KeyListener can work:

public void keyPressed (KeyEvent evt) {
    int keycode = evt.getKeyCode();
    gui.label.setText(Integer.toString(keycode));
}

public void keyReleased(KeyEvent txt) {}
public void keyTyped(KeyEvent txt) {}

In this case, tha JLabel in class1 shows the keycode of the key that has been pressed. (Note, that you can get the keycode under the keyPressed method, and not under the keyTyped method - this latter is good for getting key character by using getKeyChar. It is also better to use getKeyCode instead of getKeyChar, since specific keys have a keycode, but not a keychar).

For the buttons I use the actionPerformed method:

public void actionPerformed(ActionEvent pressed) {
    Object source = pressed.getSource();
    if (source == gui.btn1) {gui.label.setText("1");}
    else if (source == gui.btn2) {gui.label.setText("2");}
}

Since the buttons are disabled here, this doesn't work. So far, I couldn't connect the keyCode to this actionPerformed method. In the example presented by Veluria, this actionPerformed method is a part of an AbstractAction, and InputMaps and ActionMaps were used there. That seems to be the correct answer here, though I get this error, when I'm trying to use that suggestion: error: identifier expected

YEAH, I FOUND THE SOLUTION!!!: (first I show you the code, then I will explain what I modified).

//class 1://

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class event06 extends JFrame {

    event06b base = new event06b(this);

    JButton btn1 = new JButton("1");
    JButton btn2 = new JButton("2");
    JLabel label = new JLabel("");


    public event06() {
        super();
        setBounds(300,300,250,75);
        setResizable(false);
        setTitle("Buttons with key");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        FlowLayout flo = new FlowLayout(FlowLayout.CENTER);
        setLayout(flo);
        add(btn1);
        btn1.setEnabled(true);
        btn2.setEnabled(true);
        add(btn2);
        add(label);

        btn1.addActionListener(base.act);       
        btn1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke('1'), "pressed");
        btn1.getActionMap().put("pressed", base.act);

        btn2.addActionListener(base.act);
        btn2.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke('2'), "pressed");
        btn2.getActionMap().put("pressed", base.act);


        setVisible(true);
    }

    public static void main(String[] args) {
        event06 window = new event06();
    }
}

class2:

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

public class event06b {
    event06 gui;

    public event06b (event06 in) {
        gui = in;
    }

    Action act = new AbstractAction() {

        @Override
        public void actionPerformed(ActionEvent pressed) {
            Object source = pressed.getSource();
            if (source == gui.btn1) {gui.label.setText("1");}
            else if (source == gui.btn2) {gui.label.setText("2");}
        }
    };
}

So, I did the following: - added an ActionListener in class 1 to btn1 and btn2:

btn1.addActionListener(base.act);
  • I also used the InputMap:

    btn1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke('1'), "pressed");

  • Two things to note here: You have to put that "JComponent.WHEN_IN_FOCUSED_WINDOW" text in round brackets, so that you could get the input from the key even when the button does not have the focus. The other thing is, that when you use getKeyStroke('1'), you have to give a character, so use ' ' instead of " ". (I saw "" being used in many examples).

Then I used the ActionMap, like so:

btn1.getActionMap().put("pressed", base.act);

Please note, that here, and also at the ActionListener, I was referring to the other class by using base.act (base refers to class2, and act refers to the Action inside)

In class2, I put the actionPerformed method inside the AbstractAction, and used @Override. I don't know, why @Override has to be used, because it also works without it.

I'm sorry, if I misinterpreted something and didn't give the right explanation. I'm just a hobby programmer, having no education in computer science. But I hope, that this question and the provided answer might bring help to many others being in the same boat.

CsJoe
  • 19
  • 1
  • 3
  • Look into keylistener https://docs.oracle.com/javase/7/docs/api/java/awt/event/KeyListener.html – TheQAGuy Feb 07 '16 at 21:28
  • Thanks for the suggestion. I tried that, and in another program managed to bind a KeyEvent to the same action as what the JButton does, but it was working only when the button had focus on it. In that case I didn't use getInputMap() and getActionMap(), but an ActionEvent and a KeyEvent seperately. What I really would like to do here is to activate the button via pressing a keyboard key when the window is on focus, and the button is not. – CsJoe Feb 07 '16 at 21:44
  • While @JackWilliams means well, do not consider his misguided recommendation. You're on the right track to use key bindings here and **not** a KeyListener. – Hovercraft Full Of Eels Feb 07 '16 at 21:54
  • In the future however, show your attempt to solve this with your question. Else how will we be able to know what you may be doing or assuming incorrectly. – Hovercraft Full Of Eels Feb 07 '16 at 21:55
  • @JackWilliams: [Java KeyListener vs Keybinding](http://stackoverflow.com/questions/23486827/java-keylistener-vs-keybinding) – Hovercraft Full Of Eels Feb 07 '16 at 22:00
  • Again, **show your best good faith attempt and show your errors.** Edit your question with the latest code, since how can we help you without seeing what you've tried and without seeing the errors you're getting? – Hovercraft Full Of Eels Feb 07 '16 at 22:36
  • Take a look at the [Calculator Panel](http://stackoverflow.com/questions/23375276/attaching-a-single-action-listener-to-all-buttons/23375436#23375436) example. It shows how you can use Key Bindings with a single shared Action instead of creating a new Action for every key binding. – camickr Feb 08 '16 at 01:24
  • Thank you very much @camickr. I will have a look at it! Actually, I was making a calculator, when I first faced this problem, so I'm really glad that you sent me this link! – CsJoe Feb 08 '16 at 07:32
  • @HovercraftFullOfEels: Finally found the answer. After investigating many posts and visiting the Oracle Java documentation, finally managed to make the program work. May it provide help to anyone who is facing the same problem. (...) – CsJoe Feb 08 '16 at 16:33

1 Answers1

4

Here's one way to do it:

Action action = new AbstractAction("1") {
    @Override
    public void actionPerformed(ActionEvent e) {
        label.setText("1");
    }
};
action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("1"));
btn1.setAction(action);
btn1.getActionMap().put("setOneAction", action);
btn1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    (KeyStroke) action.getValue(Action.ACCELERATOR_KEY), "setOneAction");
Steffen
  • 3,999
  • 1
  • 23
  • 30
  • Thank you @Veluria for your helpful post! Based on your recommendations, after modifying the program, finally I managed to make it work. – CsJoe Feb 08 '16 at 16:27