最近写一个Ribbon控件,Ribbon控件上的编辑控件都是图标加文字加控件模式,如Word的的这个combobox控件:
它是由图标和文件以及控件本身组成,但普通的QComboBox
只是一个combobox,当然也可以使用QLabel
+QComboBox
组合控件来实现,但加入了QLabel
有点多余,于是就想重写一个QComboBox
,在绘制控件时把图标和文本绘制出来
QWidget
会保存一个icon和一个windowTitle,因此不需要给类新加任何变量保存图标和文字,只需在绘制时,把图标和文字绘制好后在旁边绘制combobox。
QStylePainter
对标准控件进行重绘重绘控件只要继承paintEvent
函数即可,在paintEvent
函数里可以对控件进行任意绘制,但是为了可以使用同一的绘制风格和能应用QSS,正确的绘制应该通过样式进行重绘,QComboBox
的绘制信息都保存在QStyleOptionComboBox
中,Qt大部分标准控件都会有一个函数initStyleOption
,此函数根据当前的状态初始化QStyleOptionComboBox
的内容。
QStyleOptionComboBox
继承于QStyleOption
,QStyleOption
这个类是负责保存窗口状态信息的类,里面有窗口的大小rect
,调色板palette
,字体相关fontMetrics
,已经状态state
,因此想让combobox绘制时右移一点,只需要改动QStyleOptionComboBox::rect
的大小即可
Qt源码QComboBox
的paintEvent
如下(Qt5.7 源码):
/*!
\reimp
*/
void QComboBox::paintEvent(QPaintEvent *)
{
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
绘制控件主要通过painter.drawComplexControl(QStyle::CC_ComboBox, opt);
此句进行绘制,需要改变绘制的结果,只需要改变initStyleOption
的输出即可。
修正的QComboBox代码如下:
头文件
class SARibbonComboBox : public QComboBox
{
public:
SARibbonComboBox(QWidget *parent = Q_NULLPTR);
protected:
void paintEvent(QPaintEvent *e);
void initStyleOption(QStyleOptionComboBox *option) const;
private:
mutable int m_iconEnd;
mutable int m_textWidth;
};
这里m_iconEnd个m_textWidth使用mutable修饰是为了在const函数里使用,其实不用这两个变量记录也行,就是在paintEvent
绘制时麻烦一点。
cpp文件:
void SARibbonComboBox::initStyleOption(QStyleOptionComboBox *option) const
{
QComboBox::initStyleOption(option);
//修正图标
int w = 0;
QIcon icon = windowIcon();
const int widgetHeight = height();
if(!icon.isNull())
{
QSize iconSize = icon.actualSize(QSize(widgetHeight,widgetHeight),
(option->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
(option->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off );
w += iconSize.width();
w += 4;
m_iconEnd = w;
}
//绘制文字
QString text = windowTitle();
if(!text.isEmpty())
{
int textWidth = option->fontMetrics.width(text);
if(textWidth > (option->rect.width() - widgetHeight - w))
{
textWidth = option->rect.width() - widgetHeight - w;
text = option->fontMetrics.elidedText(text,Qt::ElideRight,textWidth);
}
if(textWidth > 0)
{
w += textWidth;
w += 2;
}
m_textWidth = textWidth;
}
option->rect.adjust(w,0,0,0);
}
void SARibbonComboBox::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
//绘制图标
QIcon icon = windowIcon();
const int widgetHeight = height();
if(!icon.isNull())
{
//绘制图标
icon.paint(&painter,0,0,widgetHeight,widgetHeight,Qt::AlignCenter
,(opt.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled,
(opt.state & QStyle::State_Selected) ? QIcon::On : QIcon::Off);
}
//绘制文字
QString text = windowTitle();
if(!text.isEmpty())
{
painter.drawItemText(QRect(m_iconEnd,0,m_textWidth,opt.rect.height())
,Qt::AlignLeft|Qt::AlignVCenter
,opt.palette
,opt.state & QStyle::State_Enabled
,text
);
}
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
思路很简单,就是计算好图标和文字的长度,把QStyleOption
的rect
进行右偏移
显示的结果:
一切看起来很美好
但是等设置为编辑模式就出了问题
发现编辑框并没有根据QStyleOption
的rect进行绘制,查找QComboBox
的源代码发现,编辑模式会生成一个QLineEdit
作为编辑框,尺寸是由initStyleOption
获取
void QComboBox::setEditable(bool editable)
{
Q_D(QComboBox);
if (isEditable() == editable)
return;
QStyleOptionComboBox opt;
initStyleOption(&opt);
if (editable) {
if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
d->viewContainer()->updateScrollers();
view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
}
QLineEdit *le = new QLineEdit(this);
setLineEdit(le);
} else {
if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
d->viewContainer()->updateScrollers();
view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
setAttribute(Qt::WA_InputMethodEnabled, false);
d->lineEdit->hide();
d->lineEdit->deleteLater();
d->lineEdit = 0;
}
d->updateDelegate();
d->updateFocusPolicy();
d->viewContainer()->updateTopBottomMargin();
if (!testAttribute(Qt::WA_Resized))
adjustSize();
}
非常可惜的是,initStyleOption
不是虚函数!!!,这样,setEditable函数并不会调用重载版本的initStyleOption
函数