写在前面
Qt提供QPushButton不满足带前后缀图标的需求,因此考虑自定义实现带前后缀图标的PushButton,方便后续快速使用。
效果如下:
同时可设置前后缀图标和文本之间间隙:
代码实现
通过前文介绍的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状态的前后缀图标。