4

Now, I have a JMenu, and some JMenuItems in it. I want my program to perform some action when JMenu's and JMenuItem's state is changed to "selected". I don't use MouseLitener's MouseOver, because I want user to be able to navigate in menu using keyboards too. Now, I wrote this listener:

class MenuItemListener implements ChangeListener {
    @Override
    public void stateChanged(ChangeEvent arg0) {
        JMenuItem item = (JMenuItem) arg0.getSource();
        if(item.isSelected())
            System.out.println(item.getText()+" pressed!");
    }
}

When I add this listener to JMenu, it works properly, but when I add it to JMenuItem, nothing happens... When I delete if statement so that listener reacts both, when menu is selected and diselected I works fine for JMenu as well as for JMenuItem. So, as I see, JMenuItem can't "pass" isSelected() test... But what can be a problem? :S

nicks
  • 2,161
  • 8
  • 49
  • 101
  • evolution of questions (and the one linked from there): http://stackoverflow.com/questions/5821701/what-listener-should-i-use-java (just to put this into perspective, so don't need to start all over again :-) – kleopatra May 12 '11 at 07:39
  • 1
    still think that you should have edited your very first question to formally include the keyboard requirement (instead of just mentioning in some comments) - probably would have been solved right away ;-) – kleopatra May 12 '11 at 10:48

2 Answers2

7

No offense intended in any direction, this is just one of those questions with a history

  • initial requirement: do-something when a mouse is over JMenuItem
  • initial everybody's darling: MouseListener
  • initial deviating suggestion (kudos to @mKorbel!): ChangeListener on the buttonModel, checking the rollover property

  • refined requirement: doSomething when JMenuItem just highlighted, by both keyboard and mouse over.

  • refined darling: ChangeListener on the buttonModel, property not specified
  • refined deviation: ActionListener

  • current requirement: doSomething when JMenu or JMenuItem "selected" property changed.

  • current darling: can't be done with a listener, override ...
  • current deviations: Action, MenuListener ...

The correct and complete (in hindsight, though, as the keyboard wasn't yet mentioned) answer was available in the first round already: some semantic listener which is "low-level enough" to capture state changes (candidates are rollover, armed, selected, pressed on the buttonModel level) which make the menuItems change their highlighted state. Unfortunately, the exact relation is not well known (to me, at least), undocumented (read: lazy me couldn't find anything on a quick look) and even confusing (again, to me) as rollover is false always (?) for menuItems

The experimentalist's reaction is to .. try: below is a code snippet which listens and logs the state changes on some menu tree (simply throw into an arbitrary menuBar and move the mouse around and navigate by keyboard).

And the winner is: - use a ChangeListener and check if the source is either selected or armed.

    ChangeListener ch = new ChangeListener() {

        @Override
        public void stateChanged(ChangeEvent e) {
            if (e.getSource() instanceof JMenuItem) {
                JMenuItem item = (JMenuItem) e.getSource();
                if (item.isSelected() || item.isArmed()) {
                    System.out.println("Highlighted: " + item.getActionCommand());
                }
            }
        }
    };

works for both keyboard and mouse, both JMenu and JMenuItem

//----------- code snippet to track property changes in menuItem/buttonModel

    // test menu
    JMenu menu = new JMenu("Sample menu");
    menu.setMnemonic('s');
    installListeners(menu);

    // first menuitem
    JMenuItem other = menu.add("content1");
    installListeners(other);
    // second menuitem
    other = menu.add("again + ");
    installListeners(other);

    // sub
    JMenu sub = new JMenu("subMenu");
    installListeners(sub);
    menu.add(sub);

    // menus in sub
    other = sub.add("first in sub");
    installListeners(other);
    other = sub.add("second in sub");
    installListeners(other);

    getJMenuBar().add(menu);

private void installListeners(JMenuItem menu) {
    menu.getModel().addChangeListener(getChangeListener());
    menu.addChangeListener(getChangeListener());
}

private ChangeListener getChangeListener() {
    ChangeListener ch = new ChangeListener() {

        @Override
        public void stateChanged(ChangeEvent e) {
            if (e.getSource() instanceof ButtonModel) {
                ButtonModel model = (ButtonModel) e.getSource();
                System.out.println("from model: " + createStateText(model));
            } else if (e.getSource() instanceof JMenuItem) {
                JMenuItem item = (JMenuItem) e.getSource();
                System.out.println("  from item: " + createStateText(item));
            }
        }

        private String createStateText(ButtonModel model) {
            String text = model.getActionCommand() + " armed: " + model.isArmed();
            text += " selected: " + model.isSelected();
            text += " rollover " + model.isRollover();
            text += " pressed: " + model.isPressed();
            return text;
        }

        private String createStateText(JMenuItem model) {
            String text = model.getActionCommand() + " armed: " + model.isArmed();
            text += " selected: " + model.isSelected();
            // not supported on JMenuItem nor on AbstractButton
           // text += " rollover " + model.isRollover();
           // text += " pressed: " + model.isPressed();
            return text;
        }
    };
    return ch;
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
4

This is the expected polymorphic behavior. The isSelected() method of JMenuItem is inherited from AbstractButton, while the same method in Jmenu is overridden so that it "Returns true if the menu is currently selected (highlighted)."

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 2
    @trashgod so from what you are saying and what @Nika Gamkrelidze want I think the OP should set Actions on the JMenuItems to detect when they are selected, right? BTW +1. – Boro May 12 '11 at 06:30
  • It depends on what you want to do and how much you want to do it. `JMenu` has a private `ChangeListener` that invokes both `fireMenuSelected()` and `fireMenuDeselected()`; you could override them and see what turns up. – trashgod May 12 '11 at 06:56
  • @Boro: Thanks! [`Action`](http://download.oracle.com/javase/6/docs/api/javax/swing/Action.html) would be preferred, but menus don't honor the `selected` property. I can see wanting to do something as the user rolls over each `JMenuItem`; I'm less sanguine about `JMenu`. – trashgod May 12 '11 at 07:04
  • Oops, I spoke too soon: A `MenuListener` on your `JMenu` should work. – trashgod May 12 '11 at 07:25
  • @Boro no, action is not the answer in this context - but nobody could know without the previous questions :-) – kleopatra May 12 '11 at 10:02