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

24 篇文章 3 订阅

写在前面

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状态的前后缀图标。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值