[Qt]自定义QStyle——实现QProgressBar自定义样式
Posted during
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Qt]自定义QStyle——实现QProgressBar自定义样式相关的知识,希望对你有一定的参考价值。
[Qt]自定义QStyle——实现QProgressBar自定义样式
实现效果预览
前言
? 我们都知道Qt
作为一个跨平台的桌面程序开发框架,其对样式的匹配度非常的友好。正因为如此,使用自定义style开发出自己觉得看起来比较舒服的样式对开发应用程序也是比较重要的。
? 我们都知道Qt
支持QSS
来实现对程序中控件样式的修改,虽然使用QSS
修改程序样式非常的方便,大多数人也会选择使用他,但是久而久之,你就会发现使用QSS
也会有一些弊端,比如:QSS
语言单一古板,使用一种方式定义出来的QSS
样式表只有一种表现,另外程序中大量使用QSS
就会显得程序臃肿。因此,这里我们使用QStyle
的方式修改程序样式。
? QStyle
是Qt样式的抽象基类,其衍生出来QCommonStyle
和QProxyStyle
都部分效果的实现,但是具体效果并没有做过多的定义。我们在程序中可以继承QCommonStyle
或者QProxyStyle
实现自定义样式,但是千万不要使用QStyle
继承实现 样式,当然你也可以不停我的劝,自己去实现,这样的代码量非常的庞大,基本上是从零开始。
一、介绍
? 这里简单介绍一下什么是QStyle
、QCommonStyle
和QProxyStyle
QStyle
: 抽象基类,封装了GUI的外观,Qt中几乎所有的部件都是用QStyle
完成绘图工作
QCommonStyle
: 继承自QStyle
,封装了GUI常见的外观,实现了控件的部分外观
QProxyStyle
: 简化了动态覆盖QStyle
元素的便利类,封装了QStyle
,可以动态覆盖绘制或者其他行为
- 以上摘自Qt官方文档
? 具体是什么意思呢?大家看了肯定云里雾里,这里我解释一下,QStyle
和QCommonStyle
都是抽象类,需要用户自己实现,当然既然你选择使用这个类,你就要做好重新实现大量虚函数的准备,这些函数到底是什么,在哪里调用的,后面会说到。QProxyStyle
是什么意思呢?为什么会有QProxyStyle
这个类呢?我到底是继承QCommonStyle
还是QProxyStyle
呢?相信大家肯定会有这样的疑问,当初我刚接触的时候也会有这样的疑问,现在我告诉大家,QProxyStyle
从名字中可以看到proxy
代理,即代理样式,它会预设出所有的代理样式出来,如果你继承QProxyStyle
类实现自己的样式,并且在使用的时候指定了代理样式(构造函数中指定),那么除了自己定义的部分之外,其他的样式都是代理样式的,QStyle
中有一个成员函数是proxy
,返回代理样式指针,一般会返回this
指针,即如果继承QCommonStyle
自定义样式,返回自身但不包括预设样式,继承QProxyStyle
返回自身但是当控件样式自定义未实现时,使用代理样式。
二、分析
? 由于我们只是实现QProgressBar
的样式,因此只需要继承QCommonStyle
即可。下面介绍一下Qt在实现时是怎么进行的。
1. QProgressBar中paintEvent的源码
void QProgressBar::paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionProgressBar opt;
initStyleOption(&opt);
paint.drawControl(QStyle::CE_ProgressBar, opt);
d_func()->lastPaintedValue = d_func()->value;
}
? 细看源码发现,首先调用style().drawControl()
函数,并且传递的是QStyle::CE_ProgtressBar
的参数。追根溯源,查看文档发现CE_ProgressBar
参数意思是一个QProgressBar,绘制CE ProgressBarGroove, CE ProgressBarContents和CE ProgressBarLabel。
,还是没法理解的话,查看QCommonStyle
的源码。
2. QCommonStyle中drawControl()函数的部分源码
case CE_ProgressBar:
if (const QStyleOptionProgressBar *pb
= qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
QStyleOptionProgressBar subopt = *pb;
subopt.rect = subElementRect(SE_ProgressBarGroove, pb, widget);
proxy()->drawControl(CE_ProgressBarGroove, &subopt, p, widget);
subopt.rect = subElementRect(SE_ProgressBarContents, pb, widget);
proxy()->drawControl(CE_ProgressBarContents, &subopt, p, widget);
if (pb->textVisible) {
subopt.rect = subElementRect(SE_ProgressBarLabel, pb, widget);
proxy()->drawControl(CE_ProgressBarLabel, &subopt, p, widget);
}
}
break;
case CE_ProgressBarGroove:
if (opt->rect.isValid())
qDrawShadePanel(p, opt->rect, opt->palette, true, 1,
&opt->palette.brush(QPalette::Window));
break;
case CE_ProgressBarLabel:
if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
const bool vertical = pb->orientation == Qt::Vertical;
if (!vertical) {
QPalette::ColorRole textRole = QPalette::NoRole;
if ((pb->textAlignment & Qt::AlignCenter) && pb->textVisible
&& ((qint64(pb->progress) - qint64(pb->minimum)) * 2 >= (qint64(pb->maximum) - qint64(pb->minimum)))) {
textRole = QPalette::HighlightedText;
//Draw text shadow, This will increase readability when the background of same color
QRect shadowRect(pb->rect);
shadowRect.translate(1,1);
QColor shadowColor = (pb->palette.color(textRole).value() <= 128)
? QColor(255,255,255,160) : QColor(0,0,0,160);
QPalette shadowPalette = pb->palette;
shadowPalette.setColor(textRole, shadowColor);
proxy()->drawItemText(p, shadowRect, Qt::AlignCenter | Qt::TextSingleLine, shadowPalette,
pb->state & State_Enabled, pb->text, textRole);
}
proxy()->drawItemText(p, pb->rect, Qt::AlignCenter | Qt::TextSingleLine, pb->palette,
pb->state & State_Enabled, pb->text, textRole);
}
}
break;
case CE_ProgressBarContents:
if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
QRect rect = pb->rect;
const bool vertical = pb->orientation == Qt::Vertical;
const bool inverted = pb->invertedAppearance;
qint64 minimum = qint64(pb->minimum);
qint64 maximum = qint64(pb->maximum);
qint64 progress = qint64(pb->progress);
QTransform m;
if (vertical) {
rect = QRect(rect.y(), rect.x(), rect.height(), rect.width()); // flip width and height
m.rotate(90);
m.translate(0, -(rect.height() + rect.y()*2));
}
QPalette pal2 = pb->palette;
// Correct the highlight color if it is the same as the background
if (pal2.highlight() == pal2.window())
pal2.setColor(QPalette::Highlight, pb->palette.color(QPalette::Active,
QPalette::Highlight));
bool reverse = ((!vertical && (pb->direction == Qt::RightToLeft)) || vertical);
if (inverted)
reverse = !reverse;
int w = rect.width();
if (pb->minimum == 0 && pb->maximum == 0) {
// draw busy indicator
int x = (progress - minimum) % (w * 2);
if (x > w)
x = 2 * w - x;
x = reverse ? rect.right() - x : x + rect.x();
p->setPen(QPen(pal2.highlight().color(), 4));
p->drawLine(x, rect.y(), x, rect.height());
} else {
const int unit_width = proxy()->pixelMetric(PM_ProgressBarChunkWidth, pb, widget);
if (!unit_width)
return;
int u;
if (unit_width > 1)
u = ((rect.width() + unit_width) / unit_width);
else
u = w / unit_width;
qint64 p_v = progress - minimum;
qint64 t_s = (maximum - minimum) ? (maximum - minimum) : qint64(1);
if (u > 0 && p_v >= INT_MAX / u && t_s >= u) {
// scale down to something usable.
p_v /= u;
t_s /= u;
}
// nu < tnu, if last chunk is only a partial chunk
int tnu, nu;
tnu = nu = p_v * u / t_s;
if (nu * unit_width > w)
--nu;
// Draw nu units out of a possible u of unit_width
// width, each a rectangle bordered by background
// color, all in a sunken panel with a percentage text
// display at the end.
int x = 0;
int x0 = reverse ? rect.right() - ((unit_width > 1) ? unit_width : 0)
: rect.x();
QStyleOptionProgressBar pbBits = *pb;
pbBits.rect = rect;
pbBits.palette = pal2;
int myY = pbBits.rect.y();
int myHeight = pbBits.rect.height();
pbBits.state = State_None;
for (int i = 0; i < nu; ++i) {
pbBits.rect.setRect(x0 + x, myY, unit_width, myHeight);
pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect();
proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget);
x += reverse ? -unit_width : unit_width;
}
// Draw the last partial chunk to fill up the
// progress bar entirely
if (nu < tnu) {
int pixels_left = w - (nu * unit_width);
int offset = reverse ? x0 + x + unit_width-pixels_left : x0 + x;
pbBits.rect.setRect(offset, myY, pixels_left, myHeight);
pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect();
proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget);
}
}
}
break;
函数实现很长,有性质可以看完,这里我总结一下,总的来说还是围绕着几个函数执行:
drawControl
函数,一个绘制函数,具体绘制什么需要从参数属性中获取drawPrimitive
函数,同样是绘制函数,根据指定参数绘制内容subElementRect
函数,返回子元素的QRect
同样的QCommenStyle
不会过多帮助实现pixelMetric
函数,像素值,返回指定元素的像素值,QCommenStyle
不会过多帮助实现
下面来看看属性值,列举了进度条的属性值如下
PrimitiveElement
:drawPrimitive
函数的参数,其包含的进度条子元素为PE_IndicatorProgressChunk
:此元素表示进度覆盖区域的元素,windows样式是一小节一小节设定的
ControlElement
:drawControl
函数的参数,包含的进度条子元素为:CE_ProgressBarContents
:进度条内容部分,区别于文本部分,只包含进度区域CE_ProgressBar
:整个进度条部分,整个绘制QProgressBar
的开始CE_ProgressBarGroove
:这个元素查看Qt源码发现这个部分宽度为固定值1,而且从效果上看是介于内容和文本之间的部分CE_ProgressBarLabel
:进度条文本部分
SubElement
:subElementRect
函数的参数,包含进度条的子元素为:SE_ProgressBarContents
:返回进度条内容区域的QRect
SE_ProgressBarLabel
:返回进度条文本区域的QRect
SE_ProgressBarGroove
:返回介于文本和内容之间的部分,默认宽度为1
绘制进度条所需要的内容就是这些。下面列出进度条各区域的位置图:
三、实现
1. 重新实现drawControl()函数部分内容
注意:由于QCommonStyle
中已经实现了CE_ProgressBar
的内容,如上面源码所示,这里就不作实现
- 绘制进度条整个内容部分,通过设置内容区域的
rect
为整个QProgressBar
的区域,可以将Label
区域与它重合实现字体在进度条上的效果
case CE_ProgressBarContents: {
const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt);
const bool vertial = pb->orientation == Qt::Vertical;
QRect rect = pb->rect;
int minimum = pb->minimum;
int maximum = pb->maximum;
int progress = pb->progress;
QStyleOptionProgressBar pbBits = *pb;
if (vertial) {
pbBits.rect = QRect(rect.x(), rect.height() - int(rect.height() * double(progress) / (maximum-minimum)), rect.width(), int(rect.height() * double(progress) / (maximum-minimum)));
} else {
pbBits.rect = QRect(rect.x(), rect.y(), int(rect.width() * double(progress) / (maximum-minimum)), rect.height());
}
p->setBrush(QColor("#D3D3D3"));
p->drawRoundedRect(rect, 8, 8);
proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget);
return;
}
- 绘制
label
和content
之间的部分,由于label
与content
区域一致,这里就直接不管就行
case CE_ProgressBarGroove: {
// 从源码分析 这里宽度只有1
p->setPen(Qt::transparent);
p->setBrush(Qt::NoBrush);
p->drawRect(opt->rect);
return;
}
-
绘制文本区域,这里的
Rect
是整个QProgressBar
的区域,以便实现居中和字体渐变的效果这个效果主要是为了实现,进度条覆盖文字时变色,通过观察
fusion style
的源码发现它实现这一效果的方法时painter.setClipRect()
这个函数,大家可以试试。
case CE_ProgressBarLabel: {
const QStyleOptionProgressBar *pBarOpt = qstyleoption_cast<const QStyleOptionProgressBar *>(opt);
QString text = QString("已完成").append(QString::number(double(pBarOpt->progress) / (pBarOpt->maximum-pBarOpt->minimum) * 100)).append("%");
QFont font = p->font();
bool vertical = pBarOpt->orientation == Qt::Vertical;
font.setLetterSpacing(QFont::AbsoluteSpacing, 2);
p->setFont(font);
/* 字体矩形渐变色 */
double mid = (double(pBarOpt->progress) / (pBarOpt->maximum-pBarOpt->minimum) > 0) ? double(pBarOpt->progress) / (pBarOpt->maximum-pBarOpt->minimum) : 0.001;
mid = mid >= 1 ? 0.999 : mid;
if (!vertical) {
QLinearGradient textGradient(QPointF(pBarOpt->rect.left(), pBarOpt->rect.top()), QPointF(pBarOpt->rect.width(), pBarOpt->rect.top()));
textGradient.setColorAt(0, Qt::white);
textGradient.setColorAt(mid, Qt::white);
textGradient.setColorAt(mid + 0.001, Qt::darkGray);
textGradient.setColorAt(1, Qt::darkGray);
p->setPen(QPen(QBrush(textGradient), 1));
p->drawText(pBarOpt->rect, Qt::AlignCenter | Qt::TextSingleLine, text);
} else {
QLinearGradient textGradient(QPointF(pBarOpt->rect.left(), pBarOpt->rect.height()), QPointF(pBarOpt->rect.left(), pBarOpt->rect.top()));
textGradient.setColorAt(0, Qt::white);
textGradient.setColorAt(mid, Qt::white);
textGradient.setColorAt(mid + 0.001, Qt::darkGray);
textGradient.setColorAt(1, Qt::darkGray);
p->setPen(QPen(QBrush(textGradient), 1));
p->drawText(QRectF((pBarOpt->rect.width()-QFontMetricsF(p->font()).width("字"))/2, pBarOpt->rect.top(), QFontMetricsF(p->font()).width("字"), pBarOpt->rect.height()), Qt::AlignCenter | Qt::Tex
}
return;
}
2. 重新实现subElementRect()函数部分内容
- 进度条内容区域
rect
,这里直接返回的整个区域的rect
case SE_ProgressBarContents: {
r = widget->rect();
break;
}
- 进度条文本区域rect,同样返回整个区域rect
case SE_ProgressBarLabel:
r = subElementRect(QStyle::SE_ProgressBarContents, opt, widget);
break;
3. 重新实现drawPrimitive()函数部分内容
- 绘制当前进度区域,使用渐变方式进行
case PE_IndicatorProgressChunk: {
QLinearGradient linear;
linear.setStart(0, 0);
linear.setFinalStop(widget->width(), widget->height());
linear.setColorAt(0, QColor(255,182,193));
linear.setColorAt(0.5, QColor(100,149,237));
linear.setColorAt(1, QColor(255,222,173));
painter->setPen(Qt::NoPen);
painter->setBrush(linear);
painter->drawRoundedRect(option->rect, 8, 8);
return;
}
最后就能实现自定义QProgressBar
的效果,同样的方式,我们可以实现多种其他控件的样式。本次只分享QProgressBar
的样式,感兴趣的可以自己试试其他控件
以上是关于[Qt]自定义QStyle——实现QProgressBar自定义样式的主要内容,如果未能解决你的问题,请参考以下文章