2

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!

vern
  • 103
  • 2
  • 10
  • Isn't this how it works by default? – MadProgrammer Mar 30 '15 at 07:05
  • for JMenuItem (for JMenu is there another listener(s)) are defaults implemented an correctly (as is mentioned by MadProgrammer), you can to use MenuListener, MenuKeyListener and events from ButtonModel, missing there... look at mnemonics and accelator, – mKorbel Mar 30 '15 at 07:13
  • The item's functionality contains a JDialog to be opened. This dialog listens to released enter keys. == I'm hope that there is only one JDialog with CardLayout – mKorbel Mar 30 '15 at 07:14
  • @MadProgrammer: No, the item receives the pressed-event but not the released-event. – vern Mar 30 '15 at 08:40
  • No point is, using key board to open a `JMenu`, select a `JMenuItem` and pressing `Enter` will trigger the `JMenuItem`'s associated `ActionListener`, all this extra `KeyListener` stuff is just adding complexity which isn't required – MadProgrammer Mar 30 '15 at 08:43
  • @mins: Thanks for the edit! And yes I do talk about the ENTER key :) – vern Mar 30 '15 at 08:43
  • @MadProgrammer: Ah, that is interesting. I should probably try to overwrite somehow delete this actionlistener and do everything necessary in the keylistener's methods, right? This is to be done I think, for the enter-pressed-event should be ignored. – vern Mar 30 '15 at 08:46
  • No, you should use the `ActionListener` and get rid of the `KeyListener` - IMHO – MadProgrammer Mar 30 '15 at 08:49
  • @MadProgrammer Well, the problem is, I can't differentiate what caused the event to be fired. If an ENTER-pressed is the cause I do not want to treat the event by opening the `JDialog`. If I can't treat that difference appropriately my problem isn't solved :( I will add more specific details of the problem. – vern Mar 30 '15 at 08:52
  • Why would you want two different actions for the same menu item? That's just frustrating the user – MadProgrammer Mar 30 '15 at 08:55
  • @MadProgrammer I only want one action for the menu item, that is open a `JDialog` that contains a listener to ENTER-released events. The problem I have is: If I choose that menu item by usage of ENTER-pressed I open this `JDialog`. If I let go of ENTER (which will eventually have to be done after pressing ENTER to choose the menu item), I will fire an ENTER released event to the `JDialog` that was previously opened by Enter pressed. Can you follow? – vern Mar 30 '15 at 09:05
  • Not really, but what happens when the user uses the mouse? I think mkobel's suggestion of using a ButtonModel might be better – MadProgrammer Mar 30 '15 at 09:54
  • @MadProgrammer: Before I dive into that maybe one more approach to understand the problem in a simple way. Is it possible to reduce the actions that fire events at a `JMenuItem`? I do not want action events caused by enter key pressed. That's basically it. – vern Mar 30 '15 at 11:41
  • I edited my question to give a little more insight into the problem. I hope it is helpful. – vern Mar 30 '15 at 12:03
  • Instead of all this horrible code for the "ALT"+xxx Key, why not just use `setMnemonic(char)`. – Guillaume Polet Mar 30 '15 at 12:28
  • @GuillaumePolet Ah, that's just some fast way to make alt work, so the menu can be activated via keyboard. Thanks for the hint though, I will look into that. **Edit** btw.: It doesn't do it for me, for it does not deactivate the menu when pressed the second time using _menu.setMnemonic(KeyEvent.VK_ALT);_ . – vern Mar 30 '15 at 12:53
  • @mKorbel Ok, I am trying to play around with the ButtonModel but still can't really figure it out and I am starting to get realy frustrated. Where are the events actually dealt with? What happens when I strike enter on an armed JMenuItem? I am on that for several hours now and I can hardly think clear any more. Any ideas of you guys? Did the edit of the question help you understand it? – vern Mar 30 '15 at 14:32
  • ENTER andc TAB keys are in KeyBinding as accelators – mKorbel Mar 30 '15 at 15:34
  • The major problem you seem to have, is you have an entire api which has been designed to work in a particular way, which now you are trying to change (for some reason). Forgive me for not understanding, but generally speaking, the intention is, you shouldn't care how the menu item is triggered (mouse, enter, keyboard short cut, etc), you should just respond to it. The functionality is provided by the look and feel, which can respond to key pressed or released or typed events, so even if you can figure out a solution, the moment you change look and feel, it might stop working – MadProgrammer Mar 30 '15 at 20:17
  • @MadProgrammer I wish I wouldn't have to deal with that. The problem is I need two separate strokes of the ENTER key: One to trigger the menu item and one in the newly opened dialog. If I can really change the API somehow I might be able to make it run permanently, not only look and feel dependent, for I only need to disable the treatment of key pressed - that would be it. But you are right I don't linke messing with that. I found an interesting interface `MenuElement` that might lead me on... @mKorbel I will look into that, thanks for the hint. – vern Mar 31 '15 at 06:13

1 Answers1

1

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.

vern
  • 103
  • 2
  • 10