【问题标题】:Prevent a QMenu from closing when one of its QAction is triggered防止 QMenu 在其 QAction 之一被触发时关闭
【发布时间】:2010-01-12 16:27:53
【问题描述】:

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

我尝试断开可检查的 QAction 发出的信号,但没有成功。

有什么想法吗?谢谢。

【问题讨论】:

    标签: c++ qt qt4


    【解决方案1】:

    将 QWidgetAction 和 QCheckBox 用于不会导致菜单关闭的“可检查操作”。

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

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

    【讨论】:

    • 非常感谢。对于塑料风格,确实有一个余量可以添加。所以我把复选框放在一个带有布局的小部件中,并设置它的边距(也许有更简单的方法......)最后一件事:复选框不会扩展到菜单的整个宽度,所以如果点击发生在之后框标签的末尾关闭菜单并且不选中框。设置尺寸政策无效。
    • 这不适用于 Ubuntu Unity 上的 QsystemTrayIcon.contextMenu(),因为 Unity 不会从 QWidgetAction 内部显示小部件
    • @gregseth 有没有办法将复选框扩展到菜单的整个宽度?
    【解决方案2】:

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

    1. QMenu 子类
    2. 重新实现相关的事件处理程序(如 mouseReleaseEvent())
    3. 在事件处理程序中,禁用该动作,然后调用基类的实现,然后再次启用该动作,并手动触发它

    这是一个重新实现 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() 中的复杂算法。

    【讨论】:

    • 这与我刚刚提出的解决方案完全相同,据我所知,它运行良好。我想我应该在自己尝试之前阅读这里的所有答案。
    • 您也可以不调用QMenu::mouseReleaseEvent,而不是禁用和启用该操作。在这种情况下,它就像一个魅力。覆盖 keyPressEvent 并为空格按钮添加行为也可以正常工作。
    • 这种方法的问题是当菜单不适合屏幕时,菜单的顶部/底部会有箭头向上/向下滚动。触发操作后,菜单会显示其总高度(没有滚动条),因此您无法看到整个菜单。
    【解决方案3】:

    这是我的解决方案:

        // 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】:

      以下是我的一些想法......完全不确定它们是否会起作用;)

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

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

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

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

      希望这会有所帮助!

      【讨论】:

        【解决方案5】:

        (我从安迪的回答开始,所以谢谢安迪!)

        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
        

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

        bools 可能会被替换为检查鼠标是否在菜单上,如果没有,请不要将其弹出。但是,对于我的用例,仍然需要考虑 CTRL 键,因此它可能离一个不错的解决方案不远了。

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

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

        【讨论】:

          【解决方案6】:

          从baysmith解决方案开始,复选框没有按我的预期工作,因为我连接到动作触发(),而不是连接到复选框切换(布尔)。当我按下按钮时,我正在使用代码打开一个带有多个复选框的菜单:

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

          对于我的用例,这就像一个魅力

          【讨论】:

            【解决方案7】:

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

            class ComponentMenu : public QMenu
            {
            public:
                using QMenu::QMenu;
            
                void setVisible(bool visible) override
                {
                    // Don't hide the menu when holding Shift down
                    if (!visible && activeAction())
                        if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
                            return;
            
                    QMenu::setVisible(visible);
                }
            };
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-07-08
              • 2018-07-04
              相关资源
              最近更新 更多