c++ 防止QMenu在其QAction之一被触发时关闭

qmelpv7a  于 2023-02-01  发布在  其他
关注(0)|答案(9)|浏览(337)

我正在使用一个QMenu作为上下文菜单。这个菜单充满了QAction。其中一个QAction是可选中的,我希望能够选中/取消选中它,而无需关闭上下文菜单(并且必须重新打开它来选择我想要的选项)。
我尝试断开可检查QAction发出的信号,但没有成功。
有什么主意吗?谢谢。

f2uvfpb9

f2uvfpb91#

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

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

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

ep6jt1vc

ep6jt1vc2#

似乎没有任何优雅的方法来阻止菜单关闭。然而,只有当动作实际上可以触发时,菜单才会关闭,也就是说,它被启用了。所以,我发现的最优雅的解决方案是通过在动作被触发的那一刻短暂地禁用它来欺骗菜单。
1.子类QMenu
1.重新实现相关事件处理程序(如mouseReleaseEvent())
1.在事件处理程序中,禁用操作,然后调用基类的实现,然后再次启用操作,并手动触发它
这是一个重新实现的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()中重新实现复杂的算法。

rqdpfwrv

rqdpfwrv3#

这是我的解决方案:

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

vx6bjr1n4#

这里有几个我的想法......不确定它们是否会起作用;)
1)尝试使用QMenu的方法aboutToHide()捕获事件;也许你可以“取消”隐藏过程?
2)也许您可以考虑使用EventFilter?
试看一下:http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter
3)否则你可以重新实现QMenu来添加你自己的行为,但是对我来说这似乎是一项很大的工作...
希望这能帮上一点忙!

efzxgjgh

efzxgjgh5#

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

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

我说它脏是因为这里的小部件充当了打开菜单的按钮和菜单本身的事件过滤器。使用显式的事件过滤器类很容易添加,也会让事情更容易理解。
布尔值可能会被替换为检查鼠标是否在菜单上,如果没有,就不要弹出菜单。然而,对于我的用例来说,CTRL键仍然是一个因素,所以它可能离一个好的解决方案不远了。
当用户按住CTRL键并单击菜单时,它会翻转一个开关,这样当菜单试图关闭时,它会自动打开。位置被缓存,所以它会在同一位置打开。会有一个快速 Flink ,但感觉还不错,因为用户知道他们按住了一个键才能使其工作。
在一天结束的时候(字面上),我已经让整个菜单做了正确的事情。我只是想添加这个功能,我绝对不想改变使用一个小部件只是为了这个。出于这个原因,我现在甚至保留这个肮脏的补丁。

5fjcxozz

5fjcxozz6#

子类QMenu并覆盖setVisible。你可以使用activeAction()来知道一个动作是否被选择,使用visible 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);
    }
};
jhkqcmku

jhkqcmku7#

将www.example.com连接QMenu.show到action触发器,我知道这是Qt5的代码(用Python编写),但是原理应该是向后兼容的。

from PyQt5 import QtWidgets

class CheckableMenu(QtWidgets.QMenuBar):
    
    def __init__(self,parent=None):
        super().__init__(parent)
        
        self.menuObj=QtWidgets.QMenu("View")
        self.addMenu(self.menuObj)
        for i in ['Both','Even','Odd']: #my checkable items
            t=QtWidgets.QAction(i,self.menuObj,checkable=True)
            t.triggered.connect(self.menuObj.show)
            self.menuObj.addAction(t)
koaltpgm

koaltpgm8#

从Baysmith解决方案开始,复选框并没有像我预期的那样工作,因为我正在连接到action triggered(),而不是连接到checkbox 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));

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

zzlelutf

zzlelutf9#

我已经纠结了半天了。
网上有很多公认的答案建议覆盖QMenu的setVisible函数,但对我来说根本不起作用。我找到了一个基于this post的解决方案(OP的最后一个回复)
我对这个问题的C++实现如下:

bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::MouseButtonRelease)
    {
        auto action = static_cast<QMenu*>(watched)->activeAction();
        if (action && action->isCheckable())
        {
            action->trigger();
            return true;
        }
    }
    return QObject::eventFilter(watched, event);
}

相关问题