Qt自定义带前后缀图标的PushButton

news2024/9/27 15:26:48

写在前面

Qt提供QPushButton不满足带前后缀图标的需求,因此考虑自定义实现带前后缀图标的PushButton,方便后续快速使用。

效果如下:
1

2

3

4
同时可设置前后缀图标和文本之间间隙:
5

代码实现

通过前文介绍的Qt样式表底层实现

可以得知通过setStyleSheet()设置的样式,最终都会到paintEvent中绘制实现。

因此本示例的原理就是:前后缀图标和文本的绘制通过重写paintEvent实现,其他样式设置调用默认的paintEvent()处理。

完整代码如下:

#ifndef MPUSHBUTTON_H
#define MPUSHBUTTON_H
#include <QPushButton>
#include <QIcon>

class MPushButton : public QPushButton
{
	Q_OBJECT
public:
	explicit MPushButton(QWidget* parent = nullptr);

	void setPrefixIcons(const QIcon(&icons)[4]);
	void setSuffixIcons(const QIcon(&icons)[4]);

	void setPrefixIconSize(const QSize& size);
	void setSuffixIconSize(const QSize& size);
	void setPrefixIconTextSpacing(int spacing);
	void setSuffixIconTextSpacing(int spacing);

	void hidePrefixIcon(bool bHide);
	void hideSuffixIcon(bool bHide);

	void setTextColor(const QColor(&colors)[4]);

signals:


protected:
	void paintEvent(QPaintEvent* event) override;

private:
	QIcon m_prefixIcons[4];
	QIcon m_suffixIcons[4];

	QSize m_prefixIconSize;
	QSize m_suffixIconSize;
	int m_prefixIconTextSpacing;
	int m_suffixIconTextSpacing;

	bool m_hide_prefix_icon;
	bool m_hide_suffix_icon;

	QColor m_textColor[4];
};

#endif // MPUSHBUTTON_H

#include "mpushbutton.h"

#include <QPainter>
#include <QStyleOptionButton>
#include <QStylePainter>

MPushButton::MPushButton(QWidget* parent)
	: QPushButton{ parent }
{
	setAttribute(Qt::WA_Hover);

	m_prefixIconSize = QSize(20, 20);
	m_suffixIconSize = QSize(20, 20);
	m_prefixIconTextSpacing = 2;
	m_suffixIconTextSpacing = 2;

	m_hide_prefix_icon = true;
	m_hide_suffix_icon = true;
}

void MPushButton::setPrefixIcons(const QIcon(&icons)[4])
{
	std::copy(std::begin(icons), std::end(icons), std::begin(m_prefixIcons));
	update();
}

void MPushButton::setSuffixIcons(const QIcon(&icons)[4])
{
	std::copy(std::begin(icons), std::end(icons), std::begin(m_suffixIcons));
	update();
}

void MPushButton::setPrefixIconSize(const QSize& size)
{
	m_prefixIconSize = size;
	update();
}

void MPushButton::setSuffixIconSize(const QSize& size)
{
	m_suffixIconSize = size;
	update();
}

void MPushButton::setPrefixIconTextSpacing(int spacing)
{
	m_prefixIconTextSpacing = spacing;
	update();
}

void MPushButton::setSuffixIconTextSpacing(int spacing)
{
	m_suffixIconTextSpacing = spacing;
	update();
}

void MPushButton::hidePrefixIcon(bool bHide)
{
	m_hide_prefix_icon = bHide;
	update();
}

void MPushButton::hideSuffixIcon(bool bHide)
{
	m_hide_suffix_icon = bHide;
	update();
}

void MPushButton::setTextColor(const QColor(&colors)[4])
{
	std::copy(std::begin(colors), std::end(colors), std::begin(m_textColor));
	update();
}


void MPushButton::paintEvent(QPaintEvent* event)
{
	//通过父类QPushButton绘制样式表
	//注意:通过text-align设置文本对齐会影响图标设置效果
	QString qsText = text();
	setText("");	//设置文本为空,即不绘制文本,以便后面自己绘制
	QPushButton::paintEvent(event);	// 保留样式表的样式
	setText(qsText);

	QStylePainter painter(this);

	QStyleOptionButton option;
	initStyleOption(&option);
	//option.initFrom(this);

	QFontMetrics fm = painter.fontMetrics();
	QRect contentRect = style()->subElementRect(QStyle::SE_PushButtonContents, &option, this);

	int idx;
	if (!isEnabled())
	{
		idx = 3;
	}
	else if (isDown() || isChecked())
	{
		idx = 2;
	}
	else if (underMouse())
	{
		idx = 1;
	}
	else
	{
		idx = 0;
	}

	QPixmap prefixPixmap = m_prefixIcons[idx].pixmap(m_prefixIconSize);
	QPixmap suffixPixmap = m_suffixIcons[idx].pixmap(m_suffixIconSize);


	//自定义图标高度和文本间距。注意:前缀图标、文本、后缀图标要当成一个整体处理,否则无法应用样式表属性(如padding)
	int totalWidth = 0;
	int prefixStartX = 0;
	int contentStartX = 0;
	int suffixStartX = 0;
	if (m_hide_prefix_icon && m_hide_suffix_icon)
	{
		//不显示前后缀图标
		totalWidth = fm.width(option.text);
		contentStartX = contentRect.left() + (contentRect.width() - totalWidth) / 2;
	}
	else if (!m_hide_prefix_icon && !m_hide_suffix_icon)
	{
		//显示前后缀图标
		totalWidth = m_prefixIconSize.width() + fm.width(option.text) + m_suffixIconSize.width() + m_prefixIconTextSpacing + m_suffixIconTextSpacing;
		prefixStartX = contentRect.left() + (contentRect.width() - totalWidth) / 2;
		contentStartX = prefixStartX + m_prefixIconSize.width() + m_prefixIconTextSpacing;
		suffixStartX = prefixStartX + m_prefixIconSize.width() + m_prefixIconTextSpacing + fm.width(option.text) + m_suffixIconTextSpacing;
	}
	else if (m_hide_prefix_icon && !m_hide_suffix_icon)
	{
		//只显示后缀图标
		totalWidth = fm.width(option.text) + m_suffixIconSize.width() + m_suffixIconTextSpacing;
		contentStartX = contentRect.left() + (contentRect.width() - totalWidth) / 2;
		suffixStartX = contentStartX + fm.width(option.text) + m_suffixIconTextSpacing;
	}
	else
	{
		//只显示前缀图标
		totalWidth = m_prefixIconSize.width() + fm.width(option.text) + m_prefixIconTextSpacing;
		prefixStartX = contentRect.left() + (contentRect.width() - totalWidth) / 2;
		contentStartX = prefixStartX + m_prefixIconSize.width() + m_prefixIconTextSpacing;
	}



	int startY = contentRect.top() + (contentRect.height() - fm.height()) / 2;
	int startPreY = contentRect.top() + (contentRect.height() - m_prefixIconSize.height()) / 2;
	int startSufY = contentRect.top() + (contentRect.height() - m_suffixIconSize.height()) / 2;

	if (!m_hide_prefix_icon)
	{
		//前面已有当前状态判断,后续可扩展维护hover、presse状态的前缀图标
		painter.drawPixmap(prefixStartX, startPreY, m_prefixIconSize.width(), m_prefixIconSize.height(), prefixPixmap);
	}

	//方式一、通过drawText绘制文本
	// 优点:可自己指定绘制位置,
	// 缺点:无法应用样式表中字体相关的设置
	// 处理:自己维护正常、悬浮、点击、禁用时的文本颜色,自己绘制
	QColor textColor = m_textColor[idx];
	painter.setPen(textColor);
	painter.drawText(contentStartX, startY, fm.width(option.text), fm.height(), Qt::AlignCenter, option.text);

	//方式二、通过drawControl绘制文本
	//可通过QStylePainter绘制保留样式的文本
	//优点:可以应用样式表中字体相关的设置
	//缺点:无法指定绘制位置,遇到text-align或者padding样式属性值时会绘制两次文本,导致文本错位,同时与前后缀图标错位
	//style()->drawControl(QStyle::CE_PushButtonLabel, &option, &painter, this);

	if (!m_hide_suffix_icon)
	{
		//前面已有当前状态判断,后续可扩展维护hover、presse状态的前缀图标
		painter.drawPixmap(suffixStartX, startSufY, m_suffixIconSize.width(), m_suffixIconSize.height(), suffixPixmap);
	}
}

使用示例:

#include "mpushbutton.h"
void MyWidget::mpushbutton_test()
{
	MPushButton* btn = new MPushButton(this);
	QIcon prefixIcons[4] = {
		QIcon(":/Image/avatar_normal.svg")
		, QIcon(":/Image/avatar_hover_pressed.svg")
		, QIcon(":/Image/avatar_hover_pressed.svg")
		, QIcon(":/Image/avatar_normal.svg")
	};
	btn->setPrefixIcons(prefixIcons);
	btn->setPrefixIconSize(QSize(16, 16));

	QIcon suffixIcons[4] = {
		QIcon(":/Image/avatar_normal.svg")
		, QIcon(":/Image/avatar_hover_pressed.svg")
		, QIcon(":/Image/avatar_hover_pressed.svg")
		, QIcon(":/Image/avatar_normal.svg")
	};
	btn->setSuffixIcons(suffixIcons);
	btn->setSuffixIconSize(QSize(16, 16));

	//可通过样式表设置背景、边框等样式
	QString qsBtnCSS = "QPushButton{font-family: Microsoft YaHei; font-size: 14px; font-weight: normal; color: #333333; border: 1px solid #DBDBDB;border-radius: 4px;text-align: left; padding-left: 20px;background: #FAFBFC; }"
		"QPushButton:hover{ background: #EBEBEB;  }"
		"QPushButton:pressed{background: #EBEBEB;  }"
		"QPushButton:disabled{background: #EBEBEB;  }";
	btn->setStyleSheet(qsBtnCSS);

	//btn->setPrefixIconTextSpacing(10);		//可设置前后缀图标和文本之间的间隙
	//btn->setSuffixIconTextSpacing(4);

	QColor textColor[4] = {
		QColor("#333333")
		, QColor("#2982FF")
		, QColor("#0053D9")
		, QColor("#BDBDBD")
	};
	btn->setTextColor(textColor);

	btn->hidePrefixIcon(false);
	btn->hideSuffixIcon(false);

	btn->resize(96, 30);
	btn->setText("Admin");
	btn->move(100, 100);
	//btn->setEnabled(false);	//验证禁用效果
}

总结

通过自定义QPushButton,重写paintEvent,同时保留setStyleSheet()设置的样式,来实现带前后缀图标的MPushButton,以满足特殊场景使用。

这样实现的问题上面也有提到,自己绘制文本,需要考虑文本相关的样式(如text-align、padding)的影响。

后续也可按需扩展维护hover、pressed、disabled状态的前后缀图标。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1949354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux ftp操作记录

一.ftp 创建用户 passwd: user ftpuser does not exist 如果你遇到 passwd: user ftpuser does not exist 的错误&#xff0c;这意味着系统中不存在名为 ftpuser 的用户。你需要首先确认FTP用户是否是系统用户&#xff0c;还是FTP服务器软件&#xff08;如Pure-FTPd&#xff…

类和对象:完结

1.再深构造函数 • 之前我们实现构造函数时&#xff0c;初始化成员变量主要使⽤函数体内赋值&#xff0c;构造函数初始化还有⼀种⽅ 式&#xff0c;就是初始化列表&#xff0c;初始化列表的使⽤⽅式是以⼀个冒号开始&#xff0c;接着是⼀个以逗号分隔的数据成 员列表&#xf…

redis的使用场景

1. redis的使用场景 redis使用场景的案例&#xff1a;[1]热点数据的缓存[2]分布式锁[3]短信业务&#xff08;登录注册时&#xff09;2. redis实现注册登录功能 代码 在发送验证码时&#xff0c;先判断数据库是否有该手机号&#xff0c;有则发送验证码&#xff08;此时redis缓存…

基于微信小程序+SpringBoot+Vue的自习室选座与门禁系统(带1w+文档)

基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 基于微信小程序SpringBootVue的自习室选座与门禁系统(带1w文档) 本课题研究的研学自习室选座与门禁系统让用户在小程序端查看座位&#xff0c;预定座位&#xff0c;支付座位价格&#xff0c;该系统让用户预定座位…

Jmeter三种方式获取数组中多个数据并将其当做下个接口参数入参【附带JSON提取器和CSV格式化】

目录 一、传统方式-JOSN提取器获取接口返回值 1、接口调用获取返回值 2、添加JSON提取器 3、调试程序查看结果 4、添加循环控制器 5、设置count计数器 6、添加请求 7、执行请求 二、CSV参数化 1、将结果写入后置处理程序 2、设置循环处理器 3、添加CSV文件 4、设置…

【机器学习】用Jupyter Notebook实现并探索单变量线性回归的代价函数以及遇到的一些问题

引言 在机器学习中&#xff0c;代价函数&#xff08;Cost Function&#xff09;是一个用于衡量模型预测值与实际值之间差异的函数。在监督学习中&#xff0c;代价函数是评估模型性能的关键工具&#xff0c;它可以帮助我们了解模型在训练数据上的表现&#xff0c;并通过优化过程…

IPD推行成功的核心要素(十五)项目管理提升IPD相关项目交付效率和用户体验

研发项目往往包含很多复杂的流程和具体的细节。因此&#xff0c;一套完整且标准的研发项目管理制度和流程对项目的推进至关重要。研发项目管理是成功推动创新和技术发展的关键因素。然而在实际管理中&#xff0c;研发项目管理常常面临着需求不确定、技术风险、人员素质、成本和…

PyTorch安装CUDA标准流程(可解决大部分GPU无法使用问题)

最近一段时间在研究PyTorch中的GPU的使用方法&#xff0c;之前曾经安装过CUDA&#xff0c;不过在PyTorch中调用CUDA时无法使用。考虑到是版本不兼容问题&#xff0c;卸载后尝试了其他的版本&#xff0c;依旧没有能解决问题&#xff0c;指导查阅了很多资料后才找到了解决方案。 …

uni-app声生命周期

应用的生命周期函数在App.vue页面 onLaunch:当uni-app初始化完成时触发&#xff08;全局触发一次&#xff09; onShow:当uni-app启动&#xff0c;或从后台进入前台时显示 onHide:当uni-app从前台进入后台 onError:当uni-app报错时触发,异常信息为err 页面的生命周期 onLoad…

数据治理之“财务一张表”

前言 信息技术的发展&#xff0c;伴随企业业务系统的纷纷建设&#xff0c;提升业务处理效率的同时&#xff0c;也将企业的整体主价值链流程分成了一段一段的业务子流程&#xff0c;很多情况下存在数据上报延迟、业务协作不顺畅、计划反馈不及时、库存积压占资多……都可以从数据…

20240725java的Controller、DAO、DO、Mapper、Service层、反射、AOP注解等内容的学习

在Java开发中&#xff0c;‌controller、‌dao、‌do、‌mapper等概念通常与MVC&#xff08;‌Model-View-Controller&#xff09;‌架构和分层设计相关。‌这些概念各自承担着不同的职责&#xff0c;‌共同协作以构建和运行一个应用程序。‌以下是这些概念的解释&#xff1a;‌…

深度学习趋同性的量化探索:以多模态学习与联合嵌入为例

深度学习趋同性的量化探索&#xff1a;以多模态学习与联合嵌入为例 参考文献 据说是2024年最好的人工智能论文&#xff0c;是否有划时代的意义&#xff1f; [2405.07987] The Platonic Representation Hypothesis (arxiv.org) ​arxiv.org/abs/2405.07987 趋同性的量化表达 …

OAK相机支持的图像传感器有哪些?

相机支持的传感器 在 RVC2 上&#xff0c;固件必须具有传感器配置才能支持给定的相机传感器。目前&#xff0c;我们支持下面列出的相机传感器的开箱即用&#xff08;固件中&#xff09;传感器配置。 名称 分辨率 传感器类型 尺寸 最大 帧率 IMX378 40563040 彩色 1/2.…

产品经理-简历的筛选标准(22)

什么是简历 简要地描述过往的经历—一份简历的核心要素就是介绍你所经历过的事情 因此准备简历的关键是“简”“要”二字&#xff1a; 一方面是你挑选出来的事情&#xff0c;一定是重要的、能给予你所谋求的位置提供竞争力的事情&#xff1b;另一方面是在描述这件事情的时候 要…

《Java初阶数据结构》----6.<优先级队列之PriorityQueue底层:堆>

前言 大家好&#xff0c;我目前在学习java。之前也学了一段时间&#xff0c;但是没有发布博客。时间过的真的很快。我会利用好这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区…

Golang | Leetcode Golang题解之第290题单词规律

题目&#xff1a; 题解&#xff1a; func wordPattern(pattern string, s string) bool {word2ch : map[string]byte{}ch2word : map[byte]string{}words : strings.Split(s, " ")if len(pattern) ! len(words) {return false}for i, word : range words {ch : patt…

【Python实战因果推断】56_因果推理概论6

目录 Causal Quantities: An Example Bias Causal Quantities: An Example 让我们看看在我们的商业问题中&#xff0c;你如何定义这些量。首先&#xff0c;你要注意到&#xff0c;你永远无法知道价格削减&#xff08;即促销活动&#xff09;对某个特定商家的确切影响&#xf…

算法 定长按组翻转链表

一、题目 已知一个链表的头部head&#xff0c;每k个结点为一组&#xff0c;按组翻转。要求返回翻转后的头部 k是一个正整数&#xff0c;它的值小于等于链表长度。如果节点总数不是k的整数倍&#xff0c;则剩余的结点保留原来的顺序。示例如下&#xff1a; &#xff08;要求不…

数据集成工具之kettle

Kettle 是一个用于数据集成的开源工具&#xff0c;由 Pentaho 开发&#xff0c;现已由 Hitachi Vantara 维护。Kettle 的全名是 Pentaho Data Integration (PDI)&#xff0c;主要用于数据提取、转换和加载&#xff08;ETL&#xff09;过程。 1. 核心组件 Spoon: 图形化的设计工…

【MetaGPT系列】【MetaGPT完全实践宝典——多智能体实践】

目录 前言一、智能体1-1、Agent概述1-2、Agent与ChatGPT的区别 二、多智能体框架MetaGPT2-1、安装&配置2-2、使用已有的Agent&#xff08;ProductManager&#xff09;2-3、多智能体系统介绍2-4、多智能体案例分析2-4-1、构建智能体团队2-4-2、动作/行为 定义2-4-3、角色/智…