Frage

I'm trying to make my JMenu disabled when all of its subitems are disabled. I have a menu "Add new" and in this menu two menu items: "File" and "Directory".

The menu items are bound to particular actions whose states I change, so menu items change their state as well.

What I'm trying to achieve is that the "Add new" menu gets disabled when both of "File" and "Directory" actions, thus items as well, are disabled.

I tried to override isSelected() method od JMenu and it partially works - it doesn't display the items. However, the menu is still painted as active (black font instead of gray).

Any thoughts on how to achieve this?

Here's a code sample that replicates the situation:

public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() { 

            JFrame frame = new JFrame();
            JPopupMenu popup = new JPopupMenu();

            final Action actionBeep = new DefaultEditorKit.BeepAction();
            final Action actionPaste = new DefaultEditorKit.PasteAction();

            JMenu menu = new JMenu("Add");
            menu.add(new JMenuItem(actionBeep));
            menu.add(new JMenuItem(actionPaste));
            popup.add(menu);

            JTable table = new JTable(3, 3);

            table.setComponentPopupMenu(popup);
            table.addMouseListener(new MouseAdapter() {
               @Override
               public void mouseReleased(MouseEvent e) {
                   if(e.getClickCount() == 2) {
                       actionBeep.setEnabled(!actionBeep.isEnabled());
                       actionPaste.setEnabled(!actionPaste.isEnabled());
                   }
               }
            });

            frame.add(table);
            frame.pack();
            frame.setVisible(true);

        }
    });
}
War es hilfreich?

Lösung

An easily forgotten fact is that a JMenu is-a AbstractButton, as such you can set an Action to it. While that Action's actionPerformed is never called, its properties are used to keep the menu's corresponding properties in sync.

So assuming all your menus are driven by (groups of) Actions, you can define a wrapper Action that syncs its own enabled state to such a group and then set that wrapper to the menu. Advantages of this approach are that you can

  • re-use such a wrapper f.i. if the same grouping applies in a mainMenu and a popup
  • build trees of groups

The wrapper could be something like:

/**
 * Empty Action with enabled state that's the OR'ed enabled of all contained actions.
 */
public static class OrEnabledEmptyAction extends AbstractAction {

    private List<Action> actions;

    public OrEnabledEmptyAction(Collection<Action> actions, String name) {
        super(name);
        this.actions = new ArrayList<>(actions);
        installEnabledListener();
        updateEnabled();
    }

    /**
     * Updates this Action's enabled state dependent on enabled of
     * contained actions.
     */
    private void updateEnabled() {
        boolean enabled = false;
        for (Action action : actions) {
            enabled |= action.isEnabled();
        }
        setEnabled(enabled);
    }

    /**
     * Installs a PropertyChangeListener which updates this Action's 
     * enabled state on notification of enabled of contained actions.
     */
    private void installEnabledListener() {
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("enabled".equals(evt.getPropertyName()))
                    updateEnabled();
            }

        };
        for (Action action : actions) {
            action.addPropertyChangeListener(l);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // does nothing, never called for a JMenu anyway
    }

}

Its usage (your example with an additional mainMenu to highlight the re-use):

JPopupMenu popup = new JPopupMenu();

final Action actionBeep = new DefaultEditorKit.BeepAction();
final Action actionPaste = new DefaultEditorKit.PasteAction();

final List<Action> actions = new ArrayList<>();
actions.add(actionBeep);
actions.add(actionPaste);
JMenu menu = new JMenu();
// add actions to menu in popup
for (Action action : actions) {
    menu.add(action);
}
// sets the menu's action to the OR-Enabled
menu.setAction(new OrEnabledEmptyAction(actions, "Add"));
popup.add(menu);

JMenuBar bar = new JMenuBar();
JMenu mainMenu = new JMenu();
// add actions to menu in menuBar
for (Action action : actions) {
    mainMenu.add(action);
}
// re-use or-action
mainMenu.setAction(menu.getAction());
bar.add(mainMenu);
frame.setJMenuBar(bar);

JTable table = new JTable(3, 3);

table.setComponentPopupMenu(popup);
// for seeing the effect, change enabled state of only one action
// per released
table.addMouseListener(new MouseAdapter() {
    int index;
    @Override
    public void mouseReleased(MouseEvent e) {
        if (!SwingUtilities.isLeftMouseButton(e))
            return;
        actions.get(index).setEnabled(!actions.get(index).isEnabled());
        index = (index +1) % actions.size();
    }
});
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top