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

2023-11-17

需求是鼠标滑过菜单项时,菜单项的文字、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鼠标滑过菜单图标高亮

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QWidgetAction实现鼠标滑过菜单项图标高亮显示 的相关文章

  • 有没有快速创建集合的方法?

    目前我正在创建一个像这样的新集 std set a s s insert a1 s insert a2 s insert a3 s insert a10 有没有办法创建s在一行 int myints 10 20 30 40 50 std s
  • 何时使用 =default 使析构函数默认?

    尽管对构造函数使用 default 对我来说很清楚 即强制编译器在其他构造函数存在时创建默认构造函数 但我仍然无法理解这两种类型的析构函数之间的区别 那些使用 default 的 那些没有显式定义并由编译器自动生成的 我唯一想到的是 gro
  • 根据 N 个值中最小的一个返回不同的结果

    不确定如何使标题更具描述性 所以我只是从一个例子开始 我使用下面的代码位 它从枚举中选择一个方向 具体取决于四个轴中哪一个与给定方向相比形成最小角度 static Direction VectorToDirection Vector2 di
  • 找不到 assimp-vc140-mt.dll ASSIMP

    我已经从以下位置下载了 Assimp 项目http assimp sourceforge net main downloads html http assimp sourceforge net main downloads html Ass
  • ASP.Net Core 内容配置附件/内联

    我正在从 WebAPI 控制器返回一个文件 Content Disposition 标头值自动设置为 附件 例如 处置 附件 文件名 30956 pdf 文件名 UTF 8 30956 pdf 当它设置为附件时 浏览器将要求保存文件而不是打
  • vs2008 c#:Facebook.rest.api如何使用它来获取好友列表?

    如何在此基础上取得进一步的进步 获取好友列表的下一步是什么 string APIKey ConfigurationManager AppSettings API Key string APISecret ConfigurationManag
  • 单例模式和 std::unique_ptr

    std unique ptr唯一地控制它指向的对象 因此不使用引用计数 单例确保利用引用计数只能创建一个对象 那么会std unique ptr与单例执行相同 单例确保只有一个实例属于一种类型 A unique ptr确保只有一个智能指针到
  • Visual Studio Code:如何配置 includePath 以获得更好的 IntelliSense 结果

    我是使用 Visual Studio Code 的完全初学者 我不知道我在做什么 我已经四处搜索 也许还不够 但我找不到像我这样的人如何配置的简单解释c cpp properties json每当我单击带有绿色波浪线下划线的行旁边的黄色灯泡
  • std::forward_as_tuple 将参数传递给 2 个构造函数

    我想传递多个参数以便在函数内构造两个对象 以同样的方式std pair
  • 如何从文本文件读取整数到数组

    这就是我想做的 我对此有些不满 但我希望你能容忍我 这对我来说是一个非常新的概念 1 在我的程序中 我希望创建一个包含 50 个整数的数组来保存来自文件的数据 我的程序必须获取用户的文档文件夹的路径 2 文件的名称为 grades txt
  • 将二进制数据从 C# 上传到 PHP

    我想将文件从 Windows C 应用程序上传到运行 PHP 的 Web 服务器 我知道 WebClient UploadFile 方法 但我希望能够分块上传文件 以便我可以监控进度并能够暂停 恢复 因此 我正在读取文件的一部分并使用 We
  • 无法在内存位置找到异常源:cudaError_enum

    我正在尝试确定 Microsoft C 异常的来源 test fft exe 中 0x770ab9bc 处的第一次机会异常 Microsoft C 异常 内存位置 0x016cf234 处的 cudaError enum 我的构建环境是 I
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • 是否有相当于 Clang/LLVM 的 .spec 文件,在哪里可以找到参考?

    The gcc驱动程序可以配置为使用特定的链接器 特定的选项和其他细节 例如覆盖系统头 specs files 当前 截至撰写本文时 GCC 版本 4 9 0 的手册此处描述了规范文件 https gcc gnu org onlinedoc
  • ASP.NET MailMessage.BodyEncoding 和 MailMessage.SubjectEncoding 默认值

    很简单的问题 但我在 MSDN 上找不到答案 查找 ASP NET 将用于的默认值 MailMessage BodyEncoding and MailMessage SubjectEncoding 如果你不在代码中设置它们 Thanks F
  • cout 和字符串连接

    我刚刚复习了我的 C 我尝试这样做 include
  • 跨多个域的 ASP.NET 会话

    是否有合适的 NET 解决方案来在多个域上提供持久服务器会话 即 如果该网站的用户在 www site1 com 下登录 他们也将在 www site2 com 下登录 安全是我们正在开发的程序的一个问题 Thanks 它是否需要在会话中
  • 更改 Windows Phone 系统托盘颜色

    有没有办法将 Windows Phone 上的系统托盘颜色从黑色更改为白色 我的应用程序有白色背景 所以我希望系统托盘也是白色的 您可以在页面 XAML 中执行此操作
  • 将 char[][] 转换为 char** 会导致段错误吗?

    好吧 我的 C 有点生疏了 但我想我应该用 C 来做我的下一个 小 项目 这样我就可以对其进行抛光 并且我已经有不到 20 行的段错误了 这是我的完整代码 define ROWS 4 define COLS 4 char main map
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo

随机推荐

  • 安全开发-JS应用&NodeJS指南&原型链污染&Express框架&功能实现&审计&WebPack打包器&第三方库JQuery&安装使用&安全检测

    文章内容 环境搭建 NodeJS 解析安装 库安装 安全问题 NodeJS 注入 RCE 原型链 案例分析 NodeJS CTF题目 源码审计 打包器 WebPack 使用 安全 第三方库 JQuery 使用 安全 环境搭建 NodeJS
  • TestComplete数据驱动测试教程(三)——修改记录测试

    TestComplete是一款具有人工智能的自动UI测试工具 利用自动化测试工具和人工智能支持的混合对象识别引擎 轻松检测和测试每个桌面 Web和移动应用程序 本文中我们将讲解如何进行数据驱动的测试 方便大家更快更直接的学习TestComp
  • c++的常用库

    c 的常用库 C 资源大全 关于 C 框架 库和资源的一些汇总列表 内容包括 标准库 Web应用框架 人工智能 数据库 图片处理 机器学习 日志 代码分析等 标准库 C 标准库 包括了STL容器 算法和函数等 C Standard Libr
  • 用C++封装一个Ocr文字识别程序,离线识别,完全免费

    程序封装了RapidOcr模块 源文件路径Tree信息如下 文件夹 PATH 列表 卷序列号为 43EE 6931 D build windows bat CMakeLists txt CMakeSettings json Ocr cpp
  • Centos7版本的安装超级详细

    Centos7的安装超级详细 关于Centos版本下载地址 https archive kernel org centos vault CentOS 7 0 1406 x86 64 DVD iso 标准安装版 一般下载这个就可以了 Cent
  • 线程创建的三种方法

    继承Thread类 1 继承Thread类 2 重写run 方法 3 调用start 方法开启线程 public class testThread1 extends Thread Override public void run run 方
  • Qt浅谈之一:内存泄露(总结)

    一 简介 Qt内存管理机制 Qt 在内部能够维护对象的层次结构 对于可视元素 这种层次结构就是子组件与父组件的关系 对于非可视元素 则是一个对象与另一个对象的从属关系 在 Qt 中 在 Qt 中 删除父对象会将其子对象一起删除 C 中del
  • 谷歌chrome浏览器的源码分析(一)

    随着网络技术的发展 越来越多应用都已经离不开网络 特别像人类大脑一样的知识库的搜索引擎 更加是离不开功能强大的云计算 不过 即便云计算非常强大 但它还不能直接地把结果呈现给用户 这样就需要一个客户端来呈现出来 这个客户端就是浏览器 现在越来
  • 归并排序(分析与模板)

    归并排序 思路 1 确定分界元素mid left right 2 2 递归分解数组 两两组合组成两个有序数组 3 归并 合二为一 int temp 100010 merge sort int num int l int r if l gt
  • std::thread线程命名

    也可以参考我另外一篇文章 另外一篇更详细些 为线程设置名字的最大的好处是在程序出错时 它会出现在 GDB 的出错信息里 可以更快地定位问题 有两种方法可以给线程设置名字 一种在线程的调用函数内部设置 还有一种是在外部对指定线程变量做设置 i
  • 【软件测试】未来软件测试必备的8大技能,你缺少哪几个?

    软件测试工程师是个神奇的职业 他是开发人员与老板之间的传话筒 三夹板 也是开发人员与老板的好帮手 他不仅需要有销售的沟通能力 也需要具备编辑人员的文档撰写技巧 如此一个面面俱到的岗位 他需要具备的技能到底有哪些呢 有逆向思维的能力 曾经采访
  • 算法:两个有序数组合并成一个有序数组 java语言

    题目 有两个有序数组a 和b 将它们合并成数组c 需要c 也是有序数组 思路 新建一个以两个集合长度之和为长度的新数组 从两数组最左边开始比起 把小的放入新集合 并用变量标记后一位置 每次比较都是比较的最左边未比较过的元素 通过变量 循环比
  • 分享一个可交互的小场景(二)

    先看效果 可互动的小场景 再看代码 JS部分
  • 正点原子I.MX6ULL开发板车牌识别项目实战 1

    1 项目总体概述 下图为 车牌识别项目 的系统框图 借助这个框图 简要介绍项目的总体思路和所需要做的准备工作 1 1 总体思路 通过摄像头采集图像信息 并将图像信息传递开发板 这里使用的是OpenCv 开发板收到图像信息之后 通过定时器 周
  • Python解决ModuleNotFoundError: No module named 'Queue'的问题

    我们知道Python2和Python3两个版本之间 有些不兼容的地方 Python3中引入Queue会报出这个问题 Python3中要这样引入 1 import queue Python2中要这样引入 1 import Queue 为了兼容
  • 第十六课,面剔除

    使用OpenGL的面剔除选项 它默认是禁用状态 glEnable GL CULL FACE 直接运行后 我们发现正方体的部分面确实被剔除了 但是却不是背向面 这是因为我们定义的正方体并不是严格遵循逆时针顺序定义的 原理详见教程 这里就不过多
  • python输出文本 去掉引号,如何从导出的python列表中删除逗号,引号和括号?

    You guys were super helpful with my last newbie question so I figured I would give it another shot Right now my Python 3
  • 基于范围的for循环

    一 基于范围的for循环 C 11 1 范围for的语法 2 范围for的使用条件 二 指针空值nullptr 一 基于范围的for循环 C 11 1 范围for的语法 对于一个有范围的集合而言 由程序员来说明循环的范围是多余的 有时候还会
  • 智能聊天机器人实现(源码+解析)

    前言 之前写了一篇 美女图片采集器 源码 解析 得到了众多朋友的支持 发现这样系列的教程还是挺受欢迎的 也激励我继续写下去 也在那一篇文章中提过 美女图片采集只是我先前那个完整APP中的一个功能罢了 还有其他几个比较好玩的尚未开源 之后有时
  • QWidgetAction实现鼠标滑过菜单项图标高亮显示

    需求是鼠标滑过菜单项时 菜单项的文字 icon以及子菜单的小箭头都要高亮显示 qss中只能设置item背景色 文字颜色以及子菜单小箭头的样式 icon的图片不能切换 另外曾经想过用indicator 对action setCheckable