我的应用程序有一个带有多个QMenuBar的QMenu,每个QMenu都有一些QActions和子QMenus,大多数QAction-items都是带有重新实现的QWidgetAction::createWidget方法的QWidgetAction的衍生工具。
通常,QAction和QMenu都会在鼠标悬停时突出显示。即使是QWidgetAction也不会给您带来麻烦,直到这里:

但一旦我重写QWidgetAction::createWidget以返回自定义QWidget
QWidget* MyWidgetAction::createWidget(QWidget* parent) { return new MyWidget(parent); }高亮不再起作用了。所以我自己实现了:
void MyWidget::set_highlighted(bool h)
{
setBackgroundRole(h ? QPalette::Highlight : QPalette::Window);
setAutoFillBackground(h);
}
void MyWidget::enterEvent(QEvent*) override { set_highlighted(true); }
void MyWidget::leaveEvent(QEvent*) override { set_highlighted(false); }然而,它的行为并不像预期的那样:

我已经知道,直到所有子菜单关闭后才调用enterEvent方法,只有在鼠标离开子菜单或其操作之后才会发生延迟(顺便说一句,如何更改延迟?)鼠标移动事件也是如此。
问:我怎样才能正确地重新实施高亮悬停?用户不会注意到自定义小部件和标准QAction的行为不同。默认的QWidgetAction::createWidget是做什么的,我如何复制它?我已经看过Qt源了,但这很让人困惑。
发布于 2019-03-12 13:35:48
我认为原因是您没有在您的小部件上启用鼠标跟踪,因此不能通知父菜单鼠标光标更改了他的位置。
我建议在MyWidget类的构造函数中添加这一行:
setMousetracking(true);编辑#1:
我发现了一个丑陋的诡计,但它似乎奏效了:
// You WidgetAction class
class MyWidgetAction : public QWidgetAction
{
public:
MyWidgetAction(QObject *parent = nullptr);
QWidget* createWidget(QWidget* parent) override {
w = new MyWidget(parent);
return w;
}
void highlight(bool hl) { w->set_highlighted(hl); }
private:
MyWidget *w;
};
// In your code
QMenu *menu = ui->menuBar->addMenu("The Menu");
menu->addAction("Standard QAction 1");
menu->addAction("Standard QAction 2");
menu->addMenu("submenu")->addAction("subaction1");
QWidgetAction *a = new MyWidgetAction();
a->setText("My action 1");
a->setParent(menu); // Needed for the trick
menu->addAction(a);
menu->addAction("Standard QAction 3");
menu->addAction("Standard QAction 4");
// The ugly trick
connect(menu, &QMenu::hovered, this, [menu](QAction *act){
QList<MyWidgetAction*> lCustomActions = menu->findChildren<MyWidgetAction*>();
for (MyWidgetAction *mwa : lCustomActions){
mwa->highlight(mwa == act);
}
});我看到hovered信号总是正确发送的,所以我将其连接到lambda,以检查每个自定义WidgetAction是否是当前的悬停项,并在本例中手动突出显示。
编辑#2:
为了避免在我的第一个编辑中在lambda中使用for循环,您还可以创建一个事件过滤器来管理鼠标移动时的突出显示:
class WidgetActionFilterObject : public QObject
{
Q_OBJECT
public:
explicit WidgetActionFilterObject(QObject *parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *evt) override {
if (evt->type() == QEvent::Type::MouseMove){
QMouseEvent *mouse_evt = static_cast<QMouseEvent*>(evt);
QAction *a = static_cast<QMenu*>(obj)->actionAt(mouse_evt->pos());
MyWidgetAction *mwa = dynamic_cast<MyWidgetAction*>(a);
if (mwa){
if (last_wa && mwa != last_wa){
last_wa->highlight(false);
}
mwa->highlight(true);
last_wa = mwa;
} else {
if (last_wa){
last_wa->highlight(false);
last_wa = nullptr;
}
}
}
return QObject::eventFilter(obj, evt);
}
private:
MyWidgetAction *last_wa = nullptr;
};然后,您唯一需要做的就是在每个包含自定义https://doc.qt.io/qt-5/eventsandfilters.html#event-filters的菜单上安装一个WidgetAction。
menu->installEventFilter(new WidgetActionFilterObject(this));在没有循环的情况下,每个hovered信号都会得到相同的结果。
发布于 2019-03-12 21:11:46
Note表示,这不是一个完整的答案(我并不是为了获得这样的回报),而是关于QMenu及其如何处理小部件的一些坚实的背景知识。
你有两个问题:
1) QWidgetAction::create()的默认实现不起任何作用。您的目的是在您的实现中覆盖这一点,所以如果您的原始代码确实有效的话,这将是令人惊讶的。
2)一旦您超越QWidgetAction,您就会遇到一个更大的问题:QMenu不包含带有QLayout的QWidgets。它是指向QActions的指针容器,也是计算出的QRects的向量,它维护状态,但它不是传统的Qt小部件容器。
“但是等等!”你说,QMenuPrivate::widgetItems呢?问得好。QMenu确实跟踪了添加到它的小部件,但是它并没有被紧密集成。QMenu真正做的就是保留小部件的sizeHint,并确保它不会根据小部件的sizeHint来绘制小部件的位置。
“我已经试过了。它解决不了这个问题。一般来说,除非显示一个子菜单,否则会收到鼠标移动、输入和离开事件。FYI:我曾经在MWE中的每个小部件上启用了mouseTracking,没有任何区别。”
我在上面提到过,QMenu并没有真正地与添加到其中的小部件集成?这才是真正说明问题的地方。子菜单是特定于平台的意大利面代码,并紧密绑定到QMenu的相关QPA代码中。比如内部边距、绘制菜单的偏移量(在某些平台上,子菜单被几个像素抵消),子菜单当前具有焦点,所有这些都没有传播到添加到菜单中的QWidget。抛出一个子菜单,该子菜单占用了您的焦点(和创建了自己的事件循环!),突然你就进入了未知的水域。
如果您想将一个按钮添加到一个单层菜单中,QWidgetAction将执行此操作。行为像动作和显示子菜单的动作?你进入了“包括qmenu_p.h”的领域。
“我越多地思考你的解决方案,我就越觉得它已经是正确的解决方案了。让QMenu负责突出自己的行为应该有什么错呢?我认为我想让每个动作部件对自己的突出显示负责的愿望是不恰当的……”
对,是这样。QMenu对您的小部件没有特别的考虑。它只是为它创造了空间,并且主要通过它相关的QAction进行通信。
https://stackoverflow.com/questions/55086498
复制相似问题