在悬停时突出显示自定义QWidgetAction

pas*_*sbi 5 c++ user-interface qt qmenu qt5

我的应用程序有一个QMenuBar带有多个QMenus的,每个都有多个QActions和sub QMenu。大多数QAction-item是QWidgetAction使用重新实现的QWidgetAction::createWidget方法的派生。

通常,QActions和都会QMenu在鼠标悬停时突出显示。即使QWidgetAction不闹事,直到在这里:

有关突出显示如何工作的动画图形

但是,一旦我覆盖QWidgetAction::createWidget以返回自定义QWidget

QWidget* MyWidgetAction::createWidget(QWidget* parent) { return new MyWidget(parent); }
Run Code Online (Sandbox Code Playgroud)

突出显示不再起作用。所以我自己实现了:

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); }
Run Code Online (Sandbox Code Playgroud)

但是,它的行为不符合预期:

突出显示出问题的动画图形

我已经弄清楚,enterEvent直到所有子菜单都关闭后才调用该方法,只有在鼠标离开子菜单或其动作后才会发生延迟(顺便说一句,如何更改延迟?)。与鼠标移动事件相同。

问题:如何正确重新实现悬停式高亮显示?用户将不会注意到自定义窗口小部件和标准的QAction行为有所不同。默认值QWidgetAction::createWidget有什么作用,我该如何重现?我已经看过Qt的来源,但这很令人困惑。

再现动画的代码

实际生产代码

Thi*_* B. 5

我认为原因是您没有在小部件上启用鼠标跟踪,因此无法通知父菜单鼠标光标更改其位置。

我建议在类的构造函数中添加MyWidget以下行:

setMousetracking(true);
Run Code Online (Sandbox Code Playgroud)

编辑#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);
    }
});
Run Code Online (Sandbox Code Playgroud)

我看到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;
};
Run Code Online (Sandbox Code Playgroud)

然后您唯一要做的就是在包含您的自定义的每个菜单上安装事件过滤器WidgetAction

menu->installEventFilter(new WidgetActionFilterObject(this));
Run Code Online (Sandbox Code Playgroud)

您将获得相同的结果,而无需对每个信号进行循环hovered