[Qt]自定义QStyle——实现QProgressBar自定义样式

摘要:
[Qt]定制QStyle--QProgressBar定制风格的实现实现效果预览前言我们都知道,Qt作为一个跨平台桌面程序开发框架,对风格匹配非常友好。因此,对于开发应用程序来说,使用自定义样式来开发您觉得舒适的样式也很重要。我们都知道Qt支持QSS来修改程序中的控制样式。虽然使用QSS修改程序样式非常方便,但大多数人都会选择使用它,但随着时间的推移,你会发现

[Qt]自定义QStyle——实现QProgressBar自定义样式

实现效果预览

[Qt]自定义QStyle——实现QProgressBar自定义样式第1张

前言

​ 我们都知道Qt作为一个跨平台的桌面程序开发框架,其对样式的匹配度非常的友好。正因为如此,使用自定义style开发出自己觉得看起来比较舒服的样式对开发应用程序也是比较重要的。
​ 我们都知道Qt支持QSS来实现对程序中控件样式的修改,虽然使用QSS修改程序样式非常的方便,大多数人也会选择使用他,但是久而久之,你就会发现使用QSS也会有一些弊端,比如:QSS语言单一古板,使用一种方式定义出来的QSS样式表只有一种表现,另外程序中大量使用QSS就会显得程序臃肿。因此,这里我们使用QStyle的方式修改程序样式。
QStyle是Qt样式的抽象基类,其衍生出来QCommonStyleQProxyStyle都部分效果的实现,但是具体效果并没有做过多的定义。我们在程序中可以继承QCommonStyle或者QProxyStyle实现自定义样式,但是千万不要使用QStyle继承实现 样式,当然你也可以不停我的劝,自己去实现,这样的代码量非常的庞大,基本上是从零开始。

一、介绍

​ 这里简单介绍一下什么是QStyleQCommonStyleQProxyStyle

QStyle: 抽象基类,封装了GUI的外观,Qt中几乎所有的部件都是用QStyle完成绘图工作

QCommonStyle: 继承自QStyle,封装了GUI常见的外观,实现了控件的部分外观

QProxyStyle: 简化了动态覆盖QStyle元素的便利类,封装了QStyle,可以动态覆盖绘制或者其他行为

  • 以上摘自Qt官方文档

​ 具体是什么意思呢?大家看了肯定云里雾里,这里我解释一下,QStyleQCommonStyle都是抽象类,需要用户自己实现,当然既然你选择使用这个类,你就要做好重新实现大量虚函数的准备,这些函数到底是什么,在哪里调用的,后面会说到。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不会过多帮助实现

下面来看看属性值,列举了进度条的属性值如下

  • PrimitiveElementdrawPrimitive函数的参数,其包含的进度条子元素为
    • PE_IndicatorProgressChunk:此元素表示进度覆盖区域的元素,windows样式是一小节一小节设定的
  • ControlElementdrawControl函数的参数,包含的进度条子元素为:
    • CE_ProgressBarContents:进度条内容部分,区别于文本部分,只包含进度区域
    • CE_ProgressBar:整个进度条部分,整个绘制QProgressBar的开始
    • CE_ProgressBarGroove:这个元素查看Qt源码发现这个部分宽度为固定值1,而且从效果上看是介于内容和文本之间的部分
    • CE_ProgressBarLabel:进度条文本部分
  • SubElementsubElementRect函数的参数,包含进度条的子元素为:
    • SE_ProgressBarContents:返回进度条内容区域的QRect
    • SE_ProgressBarLabel:返回进度条文本区域的QRect
    • SE_ProgressBarGroove:返回介于文本和内容之间的部分,默认宽度为1

绘制进度条所需要的内容就是这些。下面列出进度条各区域的位置图:

[Qt]自定义QStyle——实现QProgressBar自定义样式第2张

三、实现

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;
}
  • 绘制labelcontent之间的部分,由于labelcontent区域一致,这里就直接不管就行
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自定义样式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇uboot完全手册---14大小端模式转换函数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

Ubuntu下安装Latex [转]

Ubuntu下LaTex中文环境配置LaTeX是一个强大的排版软件,但是其最初只是为英文排版而设计的。第一步,安装基本的LaTeX环境:sudoapt-getinstalltexlive第二步,安装中文字体包。sudoapt-getinstalllatex-cjk-all至此,基本的配置就完成了。正常情况下,编译这个tex文件就能在文档中显示中文了。\doc...

Hibernate 数据的批量插入、更新和删除

对于这个批处理场景,Hibernate提供了一个批处理解决方案。接下来,我们将从批插入、批更新和批删除三个方面介绍如何处理此批处理场景。为了避免这种情况,Hibernate为批量更新和批量删除提供了类似于SQL的HQL语法。...

微信支付服务商模式支付与普通微信支付的配置区别

chapter=7_7&index=5注:与普通微信支付相比,源代码是上述7/8之间的区别,其他可以看作是服务提供商自己的微信支付配置;...

WritableWorkbook 详细用例 (转)

1WritableWorkbookworkbook=工作簿.createWorkbook(newFile(“d:\test.xls”));1Workbookwb=工作簿.getWorkbook(newFile(“src\test\test.xls”));...

微信小程序生成带参数的二维码(小程序码)独家asp.net的服务端c#完整代码

1) 我第一次使用wx。小程序端请求调用API,发现这是一个坑!@-_~Page:'pages/index/index',//在此处填写要跳转到的小程序页面。你不能在它前面添加/oh。发布后必须为1024页//小程序代码的边长,以像素为单位,范围[2801280]},标头:{'content-type':“application/json;charset=U...

element-ui表格el-table回显时默认全选数据

1、html代码˂el-table-columntype="selection"width="45"...