防止 QMenu 在其 QAction 之一被触发时关闭
Posted
技术标签:
【中文标题】防止 QMenu 在其 QAction 之一被触发时关闭【英文标题】:Prevent a QMenu from closing when one of its QAction is triggered 【发布时间】:2010-01-12 16:27:53 【问题描述】:我使用 QMenu 作为上下文菜单。这个菜单充满了QActions。其中一个 QAction 是可选中的,我希望能够在不关闭上下文菜单的情况下选中/取消选中它(并且必须再次重新打开它才能选择我想要的选项)。
我尝试断开可检查的 QAction 发出的信号,但没有成功。
有什么想法吗?谢谢。
【问题讨论】:
【参考方案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】:
似乎没有任何优雅的方法可以防止菜单关闭。但是,只有当操作可以实际触发时,菜单才会关闭,即它已启用。因此,我发现最优雅的解决方案是通过在触发时立即禁用操作来欺骗菜单。
-
QMenu 子类
重新实现相关的事件处理程序(如 mouseReleaseEvent())
在事件处理程序中,禁用该动作,然后调用基类的实现,然后再次启用该动作,并手动触发它
这是一个重新实现 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);
;
【讨论】:
以上是关于防止 QMenu 在其 QAction 之一被触发时关闭的主要内容,如果未能解决你的问题,请参考以下文章