Short Answer
You should use Mnemonics. They don't run into the problem you ran into because, with Mnemonics, holding Alt+V is the same thing as holding the button with your computer mouse. The button remains pressed until you let go of V, regardless of if you let go of Alt before or after V.
That solves the multiple click problem, but please note that that means that input only happens when you let go of the button, not when you push it down. I think that I am solving your actual issue here (sounds like an XY Problem), but please note that the button event will not occur on push, but on release.
Long Answer
So, what you are trying to do is to have your button be triggered only once upon key press. If that is your goal, then we can accomplish that more easily using Mnemonics and javax.swing.Action
. Following the suggestion from @camickr
above, I would also encourage you to look at the "How to Use Actions" tutorial provided by Oracle themselves. Their Swing tutorials are excellent.
So to begin, Swing provides developers the ability to attach a Mnemonic to many of the key Swing components. Mnemonics are an accessibility feature that allows the user to perform more complex actions through key combinations. In Swing, that is commonly done via Alt+SomeKey. Mnemonics can be activated by attaching an Action
to the component that you want to make easily accessible.
According to the Action supported Swing Components diagram, all buttons that subtype JComponent
have the ability to use Mnemonics (PLEASE NOTE that not all Swing components can use Mnemonics). They each have a method on them called setAction(Action action)
. The method definition for it is AbstractButton::setAction
. And since JButton
is a child of AbstractButton
, that means that JButton
also has this method too.
So, we know how to achieve what we want (using an Action
on a JButton
) and we know that it is possible for buttons (according to the diagram I listed above). Let's build the Action
.
In order to make a button get triggered by an Action
, we must first create an Action
, set the Mnemonic on the Action, then attach it to the button via JButton::setAction
. Here is a quick and dirty demo that shows that off.
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.AbstractAction;
import javax.swing.Action;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
public class Example1
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
JButton button = new JButton();
Action action =
new AbstractAction()
{
public void actionPerformed(ActionEvent event)
{
System.out.println("You pressed Alt-V");
}
};
action.putValue(Action.NAME, "This is the button text -- press Alt-V");
action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_V);
button.setAction(action);
frame.getContentPane().add(button);
frame.pack();
frame.setVisible(true);
}
}
As you can see if you run the above, it creates a tiny little JFrame
holding nothing but a button. If you press Alt-V when the window is in focus, you can trigger the button presses. And, by default, the keyboard combination doesn't run into any of the potholes that InputMap and ActionMap brought you into.
The code is mostly self-explanatory, but the 4 lines that matter most are the 2 Action::putValue
lines, the AbstractButton::setAction
line, and the line inside of the actionPerformed(ActionEvent event)
method.
The actionPerformed(ActionEvent event)
method is what happens whenever the button is pressed. The Action::putValue
lines configure the action. The first one specifies the text that should show up on the JButton
. The second one attaches the keyboard combination to the Action
itself. Then, we attach this Action
to the JButton
using the setAction(Action action)
method. Once that is done, then the JButton
can be triggered by using Alt-V
.
Here is a more complicated example that shows how this might look when nested inside of multiple JPanel
instances.
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
public class KeyboardControls
{
public static void main(String[] args)
{
new KeyboardControls();
}
public KeyboardControls()
{
final JFrame frame = makeBasicJFrame();
final JPanel parentPanel = makeParentPanel();
frame.add(parentPanel);
frame.setVisible(true);
}
private JFrame makeBasicJFrame()
{
final JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setLocationByPlatform(true);
frame.setTitle("Keyboard Controls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
private JPanel makeParentPanel()
{
final JPanel parentPanel = new JPanel();
parentPanel.setLayout(new BorderLayout());
final CardLayout cardLayout = new CardLayout();
final JPanel cardLayoutPanel = makeCardLayoutPanel(cardLayout);
final Action nextAction =
new AbstractAction()
{
public void actionPerformed(final ActionEvent event)
{
cardLayout.next(cardLayoutPanel);
}
};
final JButton nextButton = makeNextButton(nextAction);
parentPanel.add(cardLayoutPanel, BorderLayout.CENTER);
parentPanel.add(nextButton, BorderLayout.SOUTH);
return parentPanel;
}
private void addPageToGivenPanel(final JPanel givenPanel, final String pageName)
{
final JPanel pagePanel = new JPanel();
final JLabel pageLabel = new JLabel();
pageLabel.setText(pageName);
pagePanel.add(pageLabel);
givenPanel.add(pagePanel, pageName);
}
private JPanel makeCardLayoutPanel(final CardLayout cardLayout)
{
final JPanel cardLayoutPanel = new JPanel();
cardLayoutPanel.setLayout(cardLayout);
addPageToGivenPanel(cardLayoutPanel, "PAGE_1");
addPageToGivenPanel(cardLayoutPanel, "PAGE_2");
addPageToGivenPanel(cardLayoutPanel, "PAGE_3");
addPageToGivenPanel(cardLayoutPanel, "PAGE_4");
addPageToGivenPanel(cardLayoutPanel, "PAGE_5");
return cardLayoutPanel;
}
private JButton makeNextButton(final Action nextAction)
{
final JButton nextButton = new JButton();
nextButton.setAction(nextAction);
final String buttonText = "NEXT -- Press Alt-V on your keyboard";
nextAction.putValue(Action.NAME, buttonText);
nextAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_V);
return nextButton;
}
}