My application has a QMenuBar
with a number of QMenu
s, each of which having a number of QAction
s and sub-QMenu
s.
Most of the QAction
-items are derivatives of QWidgetAction
with re-implemented QWidgetAction::createWidget
methods.
Usually, both QAction
s and QMenu
become highlighted on mouse hover.
Even a QWidgetAction
doesn't make trouble until here:
But as soon as I override QWidgetAction::createWidget
to return a custom QWidget
QWidget* MyWidgetAction::createWidget(QWidget* parent) { return new MyWidget(parent); }
the highlighting does not work anymore. So I implemented it myself:
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); }
However, it does not behave as expected:
I already figured out that the enterEvent
method is not called until all sub-menus are closed, which only happens with some delay after mouse leaves the sub menu or its action (btw, how can I change the delay?). Same with mouse-move events.
Question: How can I re-implement highlight-on-hover properly? The user shall not notice that custom widget and standard QAction
behave differently.
What does the default QWidgetAction::createWidget
do and how can I reproduce it? I've already looked at Qt's source but it's quite confusing.
Code to reproduce the animations
Actual production code
I think that the reason is you don't enable the mouse tracking on your widget, so the parent menu can't be notify that the mouse cursor change his position.
I suggest to add in the constructor of your MyWidget
class this line:
setMousetracking(true);
Edit #1:
I found an ugly trick but it seems to be working:
// 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);
}
});
I saw that the hovered
signal is always send correctly, so I connect this to a lambda to check for each custom WidgetAction
if it's the current hovered item and manually highlight in this case.
Edit #2:
To avoid the for loop in the lambda in my first edit, you can also create an eventfilter to manage the highlight on mouse move:
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;
};
Then the only thing you have to do is to install an event filter on each menu that contain your custom WidgetAction
:
menu->installEventFilter(new WidgetActionFilterObject(this));
And you will obtain the same result without a loop on each hovered
signal.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With