当其中一个QAction被触发时,防止QMenu关闭

时间:2010-01-12 16:27:53

标签: c++ qt qt4

我正在使用QMenu作为上下文菜单。此菜单包含QActions。其中一个QAction是可检查的,我希望能够在不关闭上下文菜单的情况下检查/取消选中它(并且必须再次重新打开它以选择我想要的选项)。

我已经尝试断开可检查QAction发出的信号而没有运气。

有什么想法吗?感谢。

7 个答案:

答案 0 :(得分:17)

使用QWidgetAction和QCheckBox进行“可检查操作”,这不会导致菜单关闭。

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

在某些样式中,这与可检查的操作看起来不完全相同。例如,对于Plastique样式,复选框需要缩进一点。

答案 1 :(得分:7)

似乎没有任何优雅的方法来阻止菜单关闭。但是,菜单只有在动作可以实际触发时才会关闭,即它已启用。因此,我找到的最优雅的解决方案是通过在触发操作时立即禁用该操作来欺骗菜单。

  1. 子类QMenu
  2. 重新实现相关的事件处理程序(如mouseReleaseEvent())
  3. 在事件处理程序中,禁用操作,然后调用基类的实现,然后再次启用操作,并手动触发
  4. 这是重新实现的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);
    }
    

    为了使解决方案更加完美,应该在可能触发操作的所有事件处理程序中进行类似操作,例如keyPressEvent()等...

    问题在于,要确定您的重新实现是否应该实际触发操作,或者甚至应该触发哪个操作并不总是很容易。最困难的可能是由助记符触发的动作:您需要自己在QMenu :: keyPressEvent()中重新实现复杂的算法。

答案 2 :(得分:1)

以下是我所拥有的几个想法......根本不确定它们会起作用;)

1)尝试使用QMenu的方法aboutToHide()来捕获事件;也许你可以“取消”隐藏过程?

2)也许您可以考虑使用EventFilter?

尝试查看:http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter

3)否则你可以重新实现QMenu来添加你自己的行为,但对我来说似乎有很多工作......

希望这有点帮助!

答案 3 :(得分:1)

这是我的解决方案:

    // 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);

答案 4 :(得分:0)

(我从Andy的回答开始,谢谢Andy!)

1)aboutToHide()通过在缓存位置重新弹出菜单来工作,但它也可以进入无限循环。测试是否在菜单外单击鼠标以忽略重新打开应该可以解决问题。

2)我尝试了一个事件过滤器,但它阻止了对菜单项的实际点击。

3)同时使用两者。

这是一个证明它有效的脏模式。当用户在单击时按住CTRL时,这会使菜单保持打开状态:

    # in __init__ ...
    self.options_button.installEventFilter(self)
    self.options_menu.installEventFilter(self)
    self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu)

    self.__options_menu_pos_cache = None
    self.__options_menu_open = False

def onAboutToHideOptionsMenu(self):
    if self.__options_menu_open:          # Option + avoid an infinite loop
        self.__options_menu_open = False  # Turn it off to "reset"
        self.options_menu.popup(self.__options_menu_pos_cache)

def eventFilter(self, obj, event):
    if event.type() == QtCore.QEvent.MouseButtonRelease:
        if obj is self.options_menu:
            if event.modifiers() == QtCore.Qt.ControlModifier:
                self.__options_menu_open = True

            return False

        self.__options_menu_pos_cache = event.globalPos()
        self.options_menu.popup(event.globalPos())
        return True

    return False

我说它很脏,因为这里的小部件充当了打开菜单按钮和菜单本身的事件过滤器。使用显式事件过滤器类很容易添加,它会使事情更容易理解。

bool可能会被替换为检查以查看鼠标是否在菜单上,如果没有,则不要将其弹出。但是,我的用例仍然需要考虑CTRL键,所以它可能并不是一个很好的解决方案。

当用户按住CTRL并单击菜单时,它会翻转一个开关,以便菜单在尝试关闭时自动打开。该位置被缓存,因此它在相同的位置打开。有一个快速的闪烁,但感觉还不错,因为用户知道他们正在按住一个键来完成这项工作。

在一天结束时(字面意思)我已经让整个菜单做了正确的事情。我只想添加此功能,我绝对不想改为使用小部件。出于这个原因,我现在要保持这个肮脏的补丁。

答案 5 :(得分:0)

从贝史密斯解决方案开始,该复选框没有按我预期的那样工作,因为我正在连接到动作Trigger(),而不是连接到toggled(bool)复选框。按下按钮时,我正在使用代码打开带有多个复选框的菜单:

           QMenu menu;

            QCheckBox *checkBox = new QCheckBox("Show Grass", &menu);
            checkBox->setChecked(m_showGrass);
            QWidgetAction *action = new QWidgetAction(&menu);
            action->setDefaultWidget(checkBox);
            menu.addAction(action);
            //connect(action, SIGNAL(triggered()), this, SLOT(ToggleShowHardscape_Grass()));
            connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(ToggleShowHardscape_Grass()));

            menu.exec(QCursor::pos() + QPoint(-300, 20));

在我的用例中,它就像一个魅力

答案 6 :(得分:0)

子类 QMenu 并覆盖 setVisible。您可以使用 activeAction() 来了解是否选择了某个操作,并使用可见参数来查看 QMenu 是否正在尝试关闭,然后您可以覆盖并使用您想要的值调用 QMenu::setVisible(...)。