2010-01-12 383 views
9

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

我试着断开可检测QAction发出的信号,但没有运气。

任何想法?谢谢。

回答

13

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

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

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

+0

非常感谢。 随着plastique风格确实有一个余地增加。所以我把复选框放在一个带有布局的小部件中,并设置边距(也许有一个更简单的方法...) 最后一件事:复选框没有扩展到菜单的全部宽度,所以如果点击发生在盒子标签结束后菜单被关闭并且盒子未被检查。设置尺寸策略不起作用。 – gregseth 2010-01-13 10:02:47

+0

这不适用于Ubuntu Unity上的'QsystemTrayIcon.contextMenu()',因为Unity不会在'QWidgetAction'中显示小部件 – Germar 2016-03-24 02:11:56

+0

@gregseth有没有办法将复选框扩展到菜单的全部宽度? – bob 2017-07-17 12:39:31

1

这里有几个想法我已经......在所有他们将工作寿不知道;)

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

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

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

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

希望这有助于有点!

0

(我开始与安迪的答案,所以谢谢安迪!)

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键并单击菜单时,它会翻转一个开关,使菜单在尝试关闭时打开自身备份。该位置被缓存,因此它在同一位置打开。有一个快速闪烁,但它感觉很好,因为用户知道他们正在按下一个键来完成这项工作。

在一天结束时(字面上),我已经有整个菜单做正确的事情。我只是想添加这个功能,而且我绝对不想改用这个小部件。出于这个原因,我现在保持这个肮脏的补丁。

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 = showed_menu->addAction (new QAction (menu)); 
    menu->add_action_with_showed_menu (action); 
7

似乎有不被阻止菜单,关闭任何优雅的方式。但是,如果该操作可以实际触发,则该菜单才会关闭,即该菜单已启用。所以,我发现的最优雅的解决方案是通过在触发时立即禁用操作来欺骗菜单。

  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()中的复杂算法。

+0

这与我刚刚提出的解决方案完全相同,并且据我所知,它运行良好。猜猜我应该在自己试验之前阅读所有答案。 – mooware 2015-04-09 00:56:21

+2

您可以不调用'QMenu :: mouseReleaseEvent'来代替禁用和启用该操作。在这种情况下,它就像一种魅力。重写'keyPressEvent'并添加空间按钮的行为也很好。 – bcmpinc 2015-04-11 16:52:28