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