Why my ChangeListener reacts only for JMenu, and n

2019-01-26 18:54发布

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

2条回答
beautiful°
2楼-- · 2019-01-26 18:56

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)."

查看更多
走好不送
3楼-- · 2019-01-26 19:08

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;
}
查看更多
登录 后发表回答