【问题标题】:highlighting custom QWidgetAction on hover在悬停时突出显示自定义 QWidgetAction
【发布时间】:2019-07-31 20:39:08
【问题描述】:

我的应用程序有一个QMenuBar 和多个QMenus,每个QActions 和子QMenus 的数量。 大多数QAction-items 是QWidgetAction 的派生类,并重新实现了QWidgetAction::createWidget 方法。

通常,QActions 和 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 方法在所有子菜单关闭之前不会被调用,这只会在鼠标离开子菜单或其操作后发生一些延迟(顺便说一句,我该如何更改延迟?) .与鼠标移动事件相同。

问题:如何正确地重新实现 highlight-on-hover?用户不会注意到自定义小部件和标准 QAction 的行为不同。 默认的QWidgetAction::createWidget 有什么作用,如何重现它?我已经看过Qt's source,但它很混乱。

Code to reproduce the animations

Actual production code

【问题讨论】:

  • MyWidget 是否已经覆盖 enterEventleaveEvent,或者只是作为您尝试解决问题的一部分?
  • 我只覆盖它们来解决问题。

标签: c++ qt user-interface qt5 qmenu


【解决方案1】:

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

我建议在 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;
};

那么您唯一需要做的就是在包含您的自定义WidgetAction 的每个菜单上安装一个event filter

menu->installEventFilter(new WidgetActionFilterObject(this));

您将获得相同的结果,而无需在每个 hovered 信号上循环。

【讨论】:

  • 我已经试过了。它不能解决问题。通常,接收鼠标移动、进入和离开事件,除非显示子菜单。仅供参考:我曾经在 MWE 中的 每个小部件 上启用了 mouseTracking,没有任何区别。
  • @pasbi ,我已经创建了一个项目来测试并且有效地,当子菜单打开时,鼠标回到QWidgetAction 时的行为是特殊的。我调查;)
  • @pasbi,我已经更新了我的答案,这不是一个漂亮的解决方案,但我已经测试并且它正在工作
  • 我对您的解决方案思考得越多,我就越觉得它已经是正确的解决方案。让QMenu 负责突出其行为应该有什么问题?我认为我希望让每个动作小部件负责自己的突出显示是不合适的......
  • @pasbi 我只是说这很丑,因为我们必须遍历菜单的所有MyWidgetAction 来判断突出显示是否打开,但我认为在这个例子中,我们可以做一个聪明的连接菜单和操作以避免 for 循环。但解决方案本身并不“坏”
【解决方案2】:

请注意,这不是一个完整的答案(我的目标不是那个赏金),而是 QMenu 的一些坚实背景以及它如何处理小部件。

你有两个问题:

1) QWidgetAction::create() 的默认实现什么都不做。你打算在你的实现中覆盖它,所以如果你的原始代码确实工作,那将是令人惊讶的。

2) 一旦你覆盖了QWidgetAction,你就会遇到更大的问题:QMenu 不包含带有QLayoutQWidgets。它是一个指向QActions 的指针容器和一个计算出的QRects 的向量。它维护状态,但它不是传统的 Qt 小部件容器。

“但是等等!”你说,QMenuPrivate::widgetItems 呢?好问题。 QMenu 确实跟踪添加到其中的小部件,但它没有紧密集成。 QMenu 真正所做的只是保留小部件的 sizeHint 并确保它不会根据其 sizeHint 绘制小部件可能所在的位置。

“我已经尝试过了。它并不能解决问题。通常,除非显示子菜单,否则会收到鼠标移动、进入和离开事件。仅供参考:我曾经在每个菜单上启用了鼠标跟踪MWE 中的小部件,没有任何区别。”

我在上面提到QMenu 并没有真正与添加到它的小部件集成?这是它开始真正说明的地方。子菜单是特定于平台的意大利面条代码,并与QMenu 的相关 QPA 代码紧密绑定。诸如内部边距、绘制菜单的偏移量(在某些平台上子菜单偏移几个像素)、当前具有焦点的子菜单,所有这些都没有传播到 QWidget 添加到菜单。扔进一个吸引您注意力的子菜单(创建自己的事件循环!),突然间您就进入了未知领域。

如果您想在单级菜单中添加一个按钮,QWidgetAction 可以做到。行为类似于操作并显示子菜单的操作?您正在进入“包含 qmenu_p.h”领域。

“我对您的解决方案思考得越多,我就越觉得它已经是正确的解决方案。让 QMenu 负责突出显示其操作应该有什么问题?我认为我希望让每个操作小部件负责其自己的突出显示有什么不合适的吗……”

正确。 QMenu 对您的小部件没有特别考虑。它只是为它腾出空间并主要通过其关联的QAction 进行通信。

【讨论】:

  • 好的,听起来不错。但是:原始代码确实有效。我的意思是createWidget 未被覆盖并因此返回nullptr 的那部分。动作看起来不是很令人兴奋,但突出显示效果很好(见第一个动画)。但也许我只是想接受“子菜单是特定于平台的意大利面条代码”并且不再关心为什么......谢谢您的洞察力,顺便说一句。
  • 原始代码的工作原理是为小部件保留空间并且成功地将操作添加到菜单中。它不起作用,因为那里实际上没有小部件。很高兴能提供帮助。
猜你喜欢
  • 2015-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多