Prevent a QMenu from closing when one of its QAction is triggered

This is my solution:

    // this menu don't hide, if action in actions_with_showed_menu is chosen.
    class showed_menu : public QMenu
    {
      Q_OBJECT
    public:
      showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; }
      showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; }
      void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); }

      virtual void setVisible (bool visible)
      {
        if (is_ignore_hide)
          {
            is_ignore_hide = false;
            return;
          }
        QMenu::setVisible (visible);
      }

      virtual void mouseReleaseEvent (QMouseEvent *e)
      {
        const QAction *action = actionAt (e->pos ());
        if (action)
          if (actions_with_showed_menu.contains (action))
            is_ignore_hide = true;
        QMenu::mouseReleaseEvent (e);
      }
    private:
      // clicking on this actions don't close menu 
      QSet <const QAction *> actions_with_showed_menu;
      bool is_ignore_hide;
    };

    showed_menu *menu = new showed_menu ();
    QAction *action = menu->addAction (new QAction (menu));
    menu->add_action_with_showed_menu (action);

Use a QWidgetAction and QCheckBox for a "checkable action" which doesn't cause the menu to close.

QCheckBox *checkBox = new QCheckBox(menu);
QWidgetAction *checkableAction = new QWidgetAction(menu);
checkableAction->setDefaultWidget(checkBox);
menu->addAction(checkableAction);

In some styles, this won't appear exactly the same as a checkable action. For example, for the Plastique style, the check box needs to be indented a bit.


Here are couple ideas I've had... Not sure at all they will work tho ;)

1) Try to catch the Event by using the QMenu's method aboutToHide(); Maybe you can "Cancel" the hide process ?

2) Maybe you could consider using an EventFilter ?

Try to have a look at : http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter

3) Otherwise you could reimplement QMenu to add your own behavior, but it seems a lot of work to me...

Hope this helps a bit !


There doesn't seem to be any elegant way to prevent the menu from closing. However, the menu will only close if the action can actually trigger, i.e. it is enabled. So, the most elegant solution I found is to trick the menu by shortly disabling the action at the moment when it would be triggered.

  1. Subclass QMenu
  2. Reimplement relevant event handlers (like mouseReleaseEvent())
  3. In the event handler, disable the action, then call base class' implementation, then enable the action again, and trigger it manually

This is an example of reimplemented mouseReleaseEvent():

void mouseReleaseEvent(QMouseEvent *e)
{
    QAction *action = activeAction();
    if (action && action->isEnabled()) {
        action->setEnabled(false);
        QMenu::mouseReleaseEvent(e);
        action->setEnabled(true);
        action->trigger();
    }
    else
        QMenu::mouseReleaseEvent(e);
}

To make the solution perfect, similar should be done in all event handlers that may trigger the action, like keyPressEvent(), etc...

The trouble is that it is not always easy to know whether your reimplementation should actually trigger the action, or even which action should be triggered. The most difficult is probably action triggering by mnemonics: you would need to reimplement the complex algorithm in QMenu::keyPressEvent() yourself.

Tags:

C++

Qt

Qt4