I have a JMenuItem
and I want to receive user input. The user has to be able to start the item's functionality by mouse or keyboard.
The item's functionality contains a JDialog
to be opened. This dialog listens to released ENTER keys and starts its own functionality when ENTER is released.
When the user hits the JMenuItem
by usage of ENTER (key pressed), he will cause the dialog to open. When he lets go of ENTER, he will - and that is the problem - cause the dialog's functionality to start. (When the user lets go of the ENTER-key he will fire an event to the now opened JDialog
. I don't want that. I want two separate steps: 1st: Choose the JMenuItem
by usage of ENTER (complete stroke or ENTER released), 2nd: Start new functionality in JDialog by usage of ENTER (complete stroke or ENTER released).
I have tried several things so far but I can't get the JMenuItem
to receive key released events. This seems to be some focus issue I can't get around so far.
How can this problem be tackled?
If the explanation was a little confusing it can be summarized like this:
I want a
JMenuItem
to receive ENTER-key-released-events and react to it, I do not want it to react to ENTER-key-pressed-events.
A small example of code might show my problem:
Edit
import java.awt.Dimension;
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.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
public class MenuItemProblem {
public static void openDialog(JFrame frame){
Action enterReleasedAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// This action shall be triggered by releasing
// the enter key in the JDialog.
// (There are two times events of the released enter key shall be
// evaluated during the application runs, of which this is the second one.)
// But: It is triggered by the first release of enter in the application,
// which is the one to choose the menu item.
System.out.println("Dialog: Enter was released!");
}
};
JDialog dialog = new JDialog(frame, "My dialog", true);
JRootPane rootPane = dialog.getRootPane();
ActionMap actionMap = rootPane.getActionMap();
String enterReleased = "my_enter_released_function";
actionMap.put(enterReleased, enterReleasedAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
// Keystroke for releasing the enter key
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), enterReleased);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setMinimumSize(new Dimension(260, 300));
dialog.setResizable(true);
dialog.pack();
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
}
public static void main(String[] args) {
JFrame frame = new JFrame("My App");
JMenuItem item = new JMenuItem("My item");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// By releasing enter (and mouse clicks, space and so on)
// this action shall be triggered.
// It shall not be triggered by enter key pressed,
// for this would cause the action of the called dialog
// do be triggered.
// But: It is triggered by enter pressed!
// (There are two times events of the released enter key shall be
// evaluated during the application runs, of which this is the first one.)
System.out.println("Choosing the menu item");
openDialog(frame);
}
});
JMenu menu = new JMenu("My menu");
menu.add(item);
JMenuBar bar = new JMenuBar();
bar.add(menu);
// Register alt key to be caught
Action altAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MenuElement[] elements = MenuSelectionManager.defaultManager().getSelectedPath();
if(elements.length > 0){
MenuSelectionManager.defaultManager().clearSelectedPath();
} else{
menu.doClick();
}
}
};
JRootPane rootPane = frame.getRootPane();
ActionMap actionMap = rootPane.getActionMap();
String altActionKey = "alt_key_for_menu";
actionMap.put(altActionKey, altAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true);
inputMap.put(ks, altActionKey);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(bar);
frame.setSize(350, 250);
frame.setVisible(true);
}
}
Yeah, I would be happy if you guys could point me into the right direction. Thanks in advance and appreciation for all your effort!
Ok, problem solved. I did not try to adapt the API but instead adapted the way the dialog's functionality is triggered. In detail I changed the trigger from ENTER released to ENTER typed. Some sample code to understand what I did:
import java.awt.Dimension;
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.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
public class MenuItemSolution {
public static void openDialog(JFrame frame){
Action enterTypedAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Dialog: Enter was typed!");
}
};
JDialog dialog = new JDialog(frame, "My dialog", true);
JRootPane rootPane = dialog.getRootPane();
ActionMap actionMap = rootPane.getActionMap();
String enterTyped = "my_enter_typed_function";
actionMap.put(enterTyped, enterTypedAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
// This is the point of change: I just created a KeyStroke to
// deal with ENTER key typed instead of ENTER key released.
// Only problem left (which doesn't matter in my use case):
// Key typed events can occur several time when holding down the ENTER key.
inputMap.put(KeyStroke.getKeyStroke('\n'), enterTyped);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setMinimumSize(new Dimension(260, 300));
dialog.setResizable(true);
dialog.pack();
dialog.setLocationRelativeTo(frame);
dialog.setVisible(true);
}
public static void main(String[] args) {
JFrame frame = new JFrame("My App");
JMenuItem item = new JMenuItem("My item");
item.addActionListener((ActionEvent e) -> {
openDialog(frame);
});
JMenu menu = new JMenu("My menu");
menu.add(item);
JMenuBar bar = new JMenuBar();
bar.add(menu);
// Register alt key to be caught
Action altAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MenuElement[] elements = MenuSelectionManager.defaultManager().getSelectedPath();
if(elements.length > 0){
MenuSelectionManager.defaultManager().clearSelectedPath();
} else{
menu.doClick();
}
}
};
JRootPane rootPane = frame.getRootPane();
ActionMap actionMap = rootPane.getActionMap();
String altActionKey = "alt_key_for_menu";
actionMap.put(altActionKey, altAction);
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true);
inputMap.put(ks, altActionKey);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(bar);
frame.setSize(350, 250);
frame.setVisible(true);
}
}
Thank you all for your attention, especially @MadProgrammer and @mKorbel!
EDIT
There are some even better ways for that as I know recognized.
1st approach:
Set a timer to react to ENTER
key released events on the newly opened dialog.
2nd approach:
Enforce an ENTER
key pressed event in the newly opened dialog before a ENTER
key released event is processed.