1

In a simple calculator app, I use abstract actions to handle the buttons being clicked with the mouse and the respective number pad keys (with key bindings) being pressed. I wanted certain cosmetic changes to occur upon using the keyboard, such as changing the border of JButton number 1 when I press the number 1 key. And changing it back upon release. This all works. Then I went and started pressing the JButtons with my mouse again and realized that the key released action never gets invoked, obviously because I'm not using the keybinding. So my question is, is there a way to invoke the appropriate released abstract action when the mouse release/button raises?

When I discovered this, I initially tried this:

Abstract action(){
set border to this..
code..
code..
code..
set border to this..
}

So no matter if the key or the mouse, it would change. However it doesn't change, or probably goes so fast it's undetectable.

It doesn't make sense to register a mouse listener in this instance. I tried this anyway and I cannot seem to register the abstract action as a mouse listener.

Thanks for your input and ideas.

I register actionlistener:

    btnMultiplication.addActionListener( operatorAction );
    btnDivision.addActionListener( operatorAction );
    btnAddition.addActionListener( operatorAction );
    btnSubtraction.addActionListener( operatorAction );
    btnSix.addActionListener( numberAction );
    btnSeven.addActionListener( numberAction );
    btnEight.addActionListener( numberAction );

the *Action's are abstract actions

I use this for keyboard input

im.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD0, 0, false ), "Number" );
im.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD0, 0, true ), "Number Released" );
am.put( "Number", numberAction );
am.put( "Number Released", numberActionR );

I used the Number action to change the border of the respective jbutton. Then I use Number Released to change the border again.

Obviously, when I click with the mouse, the border highlights. But the Number Released doesn't invoke. Like I stated, eliminating the released aspect all together and putting the first border change at the start of the abstract action and then the final border change at the end of the abstract action evidently goes so fast you cannot see the border change.

KiloJKilo
  • 465
  • 1
  • 6
  • 16
  • How are you physically registering the key actions against the buttons? Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses – MadProgrammer Mar 03 '15 at 23:33
  • 1
    Change you focus. The key bindings should be focused on modifying the state of the button and updating its state via it's model (armed and pressed). You should then use a separate `Action`, set to the `JButton` which performs the action desired operation. If you change the state of the button model right (pressed/armed, unarmed/un-pressed), this will trigger the buttons action event and call the associated `Action`... – MadProgrammer Mar 03 '15 at 23:57
  • Ok, I think I understand what you're saying. Looking into it. – KiloJKilo Mar 04 '15 at 00:05

2 Answers2

3

One thing that many people miss when dealing with key bindings, is the fact that you can register for either a "press" or "release" event (press been the default). So in your case, you need to do both. The "press" event should "arm" AND "press" the button, the "release" should "unpress" and "unarm" the button (the order is important), for example...

I would also change your focus. Instead of having the key bindings trigger the desired action, have the JButton Action do this, this will allow you to focus on having the key bindings change the state of the button, preferably through the use of the button model, which will allow the button to be triggered and the associated Action called.

Buttons

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import static javax.swing.Action.NAME;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridLayout(4, 3));
            add(createButton(7, KeyEvent.VK_7, KeyEvent.VK_NUMPAD7));
            add(createButton(8, KeyEvent.VK_8, KeyEvent.VK_NUMPAD8));
            add(createButton(9, KeyEvent.VK_9, KeyEvent.VK_NUMPAD9));
            add(createButton(4, KeyEvent.VK_4, KeyEvent.VK_NUMPAD4));
            add(createButton(5, KeyEvent.VK_5, KeyEvent.VK_NUMPAD5));
            add(createButton(6, KeyEvent.VK_6, KeyEvent.VK_NUMPAD6));
            add(createButton(1, KeyEvent.VK_1, KeyEvent.VK_NUMPAD1));
            add(createButton(2, KeyEvent.VK_2, KeyEvent.VK_NUMPAD2));
            add(createButton(3, KeyEvent.VK_3, KeyEvent.VK_NUMPAD3));
            add(createButton(0, KeyEvent.VK_0, KeyEvent.VK_NUMPAD0));
        }

        protected JButton createButton(int number, int... virtualKeys) {

            NumberAction na = new NumberAction(Integer.toString(number));
            JButton btn = new JButton(na);

            InputMap im = btn.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
            ActionMap am = btn.getActionMap();

            for (int virtualKey : virtualKeys) {

                im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), "number-pressed");
                im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), "number-released");

            }

            am.put("number-pressed", new NumberKeyPressedAction(btn, true));
            am.put("number-released", new NumberKeyPressedAction(btn, false));

            return btn;

        }

        public class NumberAction extends AbstractAction {

            public NumberAction(String name) {
                super(name);
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(getValue(NAME) + " was clicked");
            }

        }

        public class NumberKeyPressedAction extends AbstractAction {

            private final JButton btn;
            private final boolean pressed;

            public NumberKeyPressedAction(JButton btn, boolean pressed) {
                // You could just pass the button model, but this was easier...
                this.btn = btn;
                this.pressed = pressed;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                if (pressed) {
                    btn.getModel().setArmed(pressed);
                    btn.getModel().setPressed(pressed);
                } else {
                    btn.getModel().setPressed(pressed);
                    btn.getModel().setArmed(pressed);
                }
            }

        }

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I'm studying this and I think I understand all except how you are dynamically creating your buttons. Can you explain what is happening when you're creating your button using the NumberAction action? – KiloJKilo Mar 04 '15 at 17:47
  • 1
    Basically, I create a NumberAction, this uses the passed int value and converts it to a Strong, which is used by the button to set the text of the button. You could, instead, pass it the int value and internally, convert it to String for the NAME property and use the int value as part of the action. The method then creates a pressed and released key event for each virtual key code (I use both the number keys and num pad keys), it then creates the "pressed" action which changes the state of the button based on if it should be pressed or released. These key bindings are attached to the buttons – MadProgrammer Mar 04 '15 at 20:12
  • I made the view of this project using a window builder. You create buttons on the fly. I did not. At this point, based upon your examples, I'd essentially need to scrap the entire view class and start over. I've torn this thing up a couple of times and rebuilt it already so I'm going to stop working on this calculator project for now and revisit it sometime down the road. – KiloJKilo Mar 04 '15 at 20:46
  • Well, rather then creating the buttons, you could retrofit the method to configure them instead. – MadProgrammer Mar 04 '15 at 20:52
  • I tried to implement it in that manner. However, it seems all my button presses show i've clicked number nine. here is my implementation: http://github.com/KiloJKilo/Calculator/blob/new_controller/Calculator/src/com/kevinjkahl/calculator/view/View.java – KiloJKilo Mar 04 '15 at 21:07
  • You're using the same action name for all your buttons. Beware, I applied the key bindings to each individual button, not the component. The names have to be unique for each group (so all key strokes you want to perform a single action, need to use the same name) – MadProgrammer Mar 04 '15 at 21:17
  • Ok I stuck with it and got it figured out. I posted an answer showing what I did. I appreciate you helping me with this. This project has been very beneficial. – KiloJKilo Mar 05 '15 at 00:56
0

So, I wanted to post my implementation that worked, based on the chosen solution given by MadProgrammer.

I removed explicit creation of the jbuttons. I now create them like this:

createButton( 3, 5, 1, 1, ".", "btnDecimal", KeyEvent.VK_DECIMAL );
    createButton( 1, 5, 1, 2, "0", "btnZero", KeyEvent.VK_0, KeyEvent.VK_NUMPAD0 );
    createButton( 1, 4, 1, 1, "1", "btnOne", KeyEvent.VK_1, KeyEvent.VK_NUMPAD1 );
    createButton( 2, 4, 1, 1, "2", "btnTwo", KeyEvent.VK_2, KeyEvent.VK_NUMPAD2 );
    createButton( 3, 4, 1, 1, "3", "btnThree", KeyEvent.VK_3, KeyEvent.VK_NUMPAD3 );
    createButton( 1, 3, 1, 1, "4", "btnFour", KeyEvent.VK_4, KeyEvent.VK_NUMPAD4 );
    createButton( 2, 3, 1, 1, "5", "btnFive", KeyEvent.VK_5, KeyEvent.VK_NUMPAD5 );
    createButton( 3, 3, 1, 1, "6", "btnSix", KeyEvent.VK_6, KeyEvent.VK_NUMPAD6 );
    createButton( 1, 2, 1, 1, "7", "btnSeven", KeyEvent.VK_7, KeyEvent.VK_NUMPAD7 );
    createButton( 2, 2, 1, 1, "8", "btnEight", KeyEvent.VK_8, KeyEvent.VK_NUMPAD8 );
    createButton( 3, 2, 1, 1, "9", "btnNine", KeyEvent.VK_9, KeyEvent.VK_NUMPAD9 );

The createButton method does this:

private void createButton( int x, int y, int h, int w, String actionCommand, String name, int... keys ) {

    nAction na = new nAction( actionCommand );
    JButton btn = new JButton( na );
    btn.setName( name );
    InputMap im = btn.getInputMap( WHEN_IN_FOCUSED_WINDOW );
    ActionMap am = btn.getActionMap();

    for ( int virtualKey : keys ) {

        im.put( KeyStroke.getKeyStroke( virtualKey, 0, false ), "number-pressed" );
        im.put( KeyStroke.getKeyStroke( virtualKey, 0, true ), "number-released" );

    }

    am.put( "number-pressed", new NumberKeyPressedAction( btn, true ) );
    am.put( "number-released", new NumberKeyPressedAction( btn, false ) );

    GridBagConstraints gbc_btn = new GridBagConstraints();
    // gbc_btnEquals.anchor = GridBagConstraints.WEST;
    gbc_btn.fill = GridBagConstraints.BOTH;
    gbc_btn.insets = new Insets( 0, 0, 5, 5 );
    gbc_btn.gridheight = h;
    gbc_btn.gridwidth = w;
    gbc_btn.gridx = x;
    gbc_btn.gridy = y;
    frame.getContentPane().add( btn, gbc_btn );
    btn.setBackground( new Color( 225, 225, 225 ) );
    btn.setBorder( BorderFactory.createLineBorder( Color.BLACK ) );

As you can see, I create my instances as MadProgrammer has shown in his example and created references to the AbstractActions. I then set the properties for the various swing attributes and then the border and background. This reduces the code and variable usage greatly. A side note. The parameter name in createButton and its use to name the button are no longer used and will be removed.

KiloJKilo
  • 465
  • 1
  • 6
  • 16