QWidgetAction实现鼠标滑过菜单项图标高亮显示

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhushentian/article/details/82014760

需求是鼠标滑过菜单项时,菜单项的文字、icon以及子菜单的小箭头都要高亮显示,qss中只能设置item背景色、文字颜色以及子菜单小箭头的样式,icon的图片不能切换,另外曾经想过用indicator(对action setCheckable(true)后,此子控件在qss中会生效)代替icon,因为indicator可以在qss中定制,但是这样一来所有的action的图标都是一致的了,这明显不符合需求。于是想着用QWidgetAction,自定义一个QWidget,在上面加入icon,菜单文字等,先看下效果:

设计的菜单项的模型如下:

其中,1为放置icon图片的widget,2为显示菜单项文字的Label,3为放置子菜单指示器的widget。

新建一个继承QWidget的类,用作QWidgetAction的defaultWidget:

QMenuWidget.h

#pragma once

#include <QWidget>
#include <QMenu>

namespace Ui {
class QMenuWidget;
}
class QMenuWidget : public QWidget
{
	Q_OBJECT

public:
	QMenuWidget(QWidget *parent = Q_NULLPTR);
	~QMenuWidget();

private:
    Ui::QMenuWidget *ui;
	QWidget *m_icon;
	QWidget *m_text;
	QWidget *m_submenu_indicator;

public:
	void resizeEvent(QResizeEvent *event);
	void paintEvent(QPaintEvent *event);
	void mouseEnterEvent(QEvent *event);
	void mouseLeaveEvent(QEvent *event);
	

	void SetIconWidget(QWidget *widget_);
	void SetTextWidget(QWidget *text_);
	void SetSubMenuIndicatorWidget(QWidget *indicator_);
	void initWidgets();

};

重载resizeEvent是为了安放三个widget的位置;

QMenuWidget.cpp:

#include "QMenuWidget.h"
#include <QPainter>
#include <QStyleOption>
#include <QEnterEvent>
#include <QDebug>
#include <QMouseEvent>
#include "ui_QMenuWidget.h"
QMenuWidget::QMenuWidget(QWidget *parent)
	: QWidget(parent)
	,m_submenu_indicator(NULL)
	,m_icon(NULL)
	,m_text(NULL)
        ,ui(new Ui::QMenuWidget)
{
    ui->setupUi(this);
    setMouseTracking(true);
}

QMenuWidget::~QMenuWidget()
{
}

void QMenuWidget::resizeEvent(QResizeEvent * event)
{
    int icon_w = m_icon->width();
    int icon_h = m_icon->height();
	int left_margin = 2;
	int top_margin = rect().height() - icon_h;
	top_margin /= 2;
	m_icon->setGeometry(left_margin, top_margin, icon_w, icon_h);

	int text_margin_left = 2;
	int indicator_w = 0;
	if (m_submenu_indicator)
		indicator_w = m_submenu_indicator->width();
	QRect text_rc;
	text_rc.setTop(0);
	text_rc.setLeft(rect().left() + left_margin  + text_margin_left + icon_w);
	text_rc.setRight(rect().right() - indicator_w);
	text_rc.setBottom(rect().bottom());
	m_text->setGeometry(text_rc);

	if (!m_submenu_indicator)
		return;
	int indicator_h = m_submenu_indicator->height();

	top_margin = rect().height() - indicator_h;
	top_margin /= 2;
	m_submenu_indicator->setGeometry(rect().right() - indicator_w, top_margin, indicator_w, indicator_h);

}

void QMenuWidget::paintEvent(QPaintEvent * event)
{
	
	QStyleOption opt;
	opt.init(this);
	QPainter p(this);
	style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

void QMenuWidget::mouseEnterEvent(QEvent * event)
{
	bool ishover = this->property("hover").toBool();
	if (ishover)
		return;
	qDebug() << __FUNCTION__;
	this->setProperty("hover", true);
	m_icon->setProperty("hover", true);
	m_text->setProperty("hover", true);

	m_icon->style()->unpolish(m_icon);
	m_text->style()->unpolish(m_text);
	this->style()->unpolish(this);

	m_icon->style()->polish(m_icon);
	m_text->style()->polish(m_text);
	this->style()->polish(this);


	m_icon->update();
	m_text->update();
	this->update();


	if (m_submenu_indicator)
	{
		
		m_submenu_indicator->setProperty("hover", true);
		m_submenu_indicator->style()->unpolish(m_submenu_indicator);
		m_submenu_indicator->style()->polish(m_submenu_indicator);
		m_submenu_indicator->update();
	}
	__super::enterEvent(event);
}

void QMenuWidget::mouseLeaveEvent(QEvent * event)
{
	bool ishover = this->property("hover").toBool();
	if (!ishover)
		return;
	qDebug() << __FUNCTION__;
	this->setProperty("hover", false);
	m_icon->setProperty("hover", false);
	m_text->setProperty("hover", false);

	this->style()->unpolish(this);
	m_icon->style()->unpolish(m_icon);
	m_text->style()->unpolish(m_text);

	this->style()->polish(this);
	m_icon->style()->polish(m_icon);
	m_text->style()->polish(m_text);

	this->update();
	m_icon->update();
	m_text->update();

	if (m_submenu_indicator)
	{
		m_submenu_indicator->setProperty("hover", false);
		m_submenu_indicator->style()->unpolish(m_submenu_indicator);
		m_submenu_indicator->style()->polish(m_submenu_indicator);
		m_submenu_indicator->update();
	}

	__super::leaveEvent(event);
}


void QMenuWidget::SetIconWidget(QWidget * widget_)
{
	m_icon = widget_;
	m_icon->setMouseTracking(true);
}

void QMenuWidget::SetTextWidget(QWidget * text_)
{
	m_text = text_;
	m_text->setMouseTracking(true);
}

void QMenuWidget::SetSubMenuIndicatorWidget(QWidget * indicator_)
{
    m_submenu_indicator = indicator_;
    if(m_submenu_indicator)
    {
        m_submenu_indicator->setMouseTracking(true);
    }
}

void QMenuWidget::initWidgets()
{
	this->setProperty("hover", false);
	m_icon->setProperty("hover", false);
	m_text->setProperty("hover", false);
	if(m_submenu_indicator)
	    m_submenu_indicator->setProperty("hover", false);
}

这里提供了接口来设置icon、label、以及indicator的widget,也可以由QMenuWidget自己来生成这三个widget。这里记录一下经验,最开始的时候我是重载QWidget的enterEvent、leaveEvent来捕捉鼠标进入以及离开事件,从而改变样式,后来发现这样做有几个问题:

第一个问题,当有子菜单时,鼠标放在菜单项上时,不能自动弹出子菜单,要点击一下才会弹出;

第二个问题,当有子菜单时,点击弹出子菜单后,鼠标移开菜单项,leaveEvent不能触发,从而菜单项一直保留hover=true的状态,样式也是hover=true的样式;

第三个问题,当QWidgetAction以及一个QAction并排放在菜单上时,从QAction移动到QWidgetAction时,QAction的状态仍为‘selected’状态;

尝试了各种方法均无法解决,后来把QMenu的源码看了下,发现触发QAction激活是在QMenu::mouseMoveEvent()里触发的,子菜单的延时弹出也是在这里触发的,于是便做个测试,我在代码中为QMenu安装了事件过滤器,发现鼠标在QMenuWidget上移动时,QMenu并没有触发到mouseMoveEvent,便断定以上的几个问题,均是因为鼠标在我自定义的QMenuWidget上移动时,QMenu无法捕捉到mouseMoveEvent。

想要让QMenu捕捉到QMenuWidget的mouseMove事件,那要怎么办呢?上网查了下,子QWidget窗口的事件如果未处理,即没有重载父类QWidget的事件处理函数,那么事件就会传播至父窗口,仔细看了下代码,发现QMenuWidget并没有重写mouseMoveEvent啊,为什么QMenu不能捕捉到呢,经过调试,终于发现要对QMenuWidget以及icon、label、indicator都设置

setMouseTracking(true);

才能捕捉到mouseMoveEvent并传播给父窗口QMenu;

这样之后,第一个问题解决了,第二个、第三个问题仍然存在;经调试,第二个问题是由于子菜单弹出后,鼠标移开,leaveEvent并没有触发;又仔细看了下QMenu的实现代码,发现每个QAction的状态都是由QMenu判断鼠标事件来决定的,于是决定不在QMenuWidget中判断各种鼠标事件,统一由QMenu执行,把装有QMenuWidget的QWidgetAction当做一般的QAction来处理,最终的QMenuWidget类的代码就如上述所示。样式上使用了qss来设置,用到了动态属性,当鼠标进入时,设置自定义属性hover为true,当鼠标离开时,设置hover为false。主窗口的代码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QMenuWidget.h"
#include <QFile>
#include <QLabel>
#include <QWidgetAction>
#include <QDebug>
#include <QMouseEvent>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setupMenu();
    QFile file_(":/qss/res/menu.qss");
    file_.open(QFile::ReadOnly);
    m_menu->setStyleSheet(file_.readAll());

    ui->pushButton->setMenu(m_menu);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::setupMenu()
{
    m_menu = new QMenu(this);
    m_menu->setObjectName("menu_1");
    QStringList menu_name;
    menu_name << "clock" << "map" << "home";
    QStringList menu_text;
    menu_text << QStringLiteral("时钟") << QStringLiteral("定位服务") << QStringLiteral("主页");
    for (int i = 0; i < 3;i++)
    {
        QMenuWidget *mw = new QMenuWidget(this);
        mw->setObjectName("menu_widget_" + menu_name.at(i));
        mw->setFixedSize(120, 40);

        QWidget *icon = new QWidget(mw);
        icon->setObjectName("menu_icon_"+menu_name.at(i));
        icon->setFixedSize(32, 32);
        QLabel *text = new QLabel(mw);
        text->setObjectName("menu_text_"+menu_name.at(i));
        text->setText(menu_text.at(i));
        QWidget* indicator = NULL;
        if(menu_name.at(i) == "home")
        {
            indicator = new QWidget(mw);
            indicator->setObjectName("menu_sub_"+menu_name.at(i));
            indicator->setFixedSize(8, 12);
        }
        mw->SetIconWidget(icon);
        mw->SetTextWidget(text);
        mw->SetSubMenuIndicatorWidget(indicator);
        mw->initWidgets();

        QWidgetAction *wa = new QWidgetAction(m_menu);
        wa->setObjectName("action_" + menu_name.at(i));
        wa->setText(menu_name.at(i));
        wa->setDefaultWidget(mw);
        //mw->SetAction(wa);
        m_widget_acts.append(wa);
    }

    QAction *act3 = new QAction(this);
    act3->setText(QStringLiteral("个人主页"));

    QMenu *submenu = new QMenu(this);
    submenu->addAction(act3);
    m_widget_acts.at(2)->setMenu(submenu);

    m_menu->addActions(m_widget_acts);
    m_menu->installEventFilter(this);
    connect(m_menu, &QMenu::triggered, this, &MainWindow::onMenuTriggered);

}

void MainWindow::onMenuTriggered(QAction *action)
{
    qDebug() << __FUNCTION__ << action->text();
}

bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_menu)
    {
        if (event->type() == QEvent::MouseMove)
        {

            //QMenuWidget *wid = (QMenuWidget *)m_widget_act2->defaultWidget();
            QPointF lp = ((QMouseEvent*)event)->localPos();
            QList<QAction*>::iterator it = m_widget_acts.begin();
            for (it;it != m_widget_acts.end();it++)
            {
                QWidgetAction *wa = (QWidgetAction*)(*it);
                QMenuWidget *wid = (QMenuWidget *)(wa)->defaultWidget();
                QRect rc = m_menu->actionGeometry(wa);
                if (rc.contains(lp.toPoint()))
                {
                    //qDebug() << rc << lp.toPoint() << wid->geometry();
                    wid->mouseEnterEvent(event);
                }
                else
                    wid->mouseLeaveEvent(event);

            }
        }
    }
    return false;
}

这里为m_menu添加了事件过滤器,鼠标移动时,判断鼠标在哪个action上,然后调用其对应的接口。菜单的qss文件可参考下方源码:

Qt鼠标滑过菜单图标高亮

猜你喜欢

转载自blog.csdn.net/zhushentian/article/details/82014760
今日推荐