1

I have a custom dialog that imitates and embellishes QProgressDialog with some additional application-specific information and enables pausing/resuming the worker thread running in the background. Ultimately what I'm looking for is the ability to change the appearance of the QProgressBar to reflect the paused state as described in this question.

pause progress

Oddly enough QWinTaskbarProgress seems to support this exact functionality as of Qt 5.2, but a plain QProgressBar does not, in Qt4 or Qt5. I'm okay with fiddling around in the Qt source code, but I've dug around in the Qt source code and I can't figure out where the actual state of the form control is queried/provided, so I can't figure out what I need to change. Maybe it's not there because this is strictly a Windows thing, but maybe not?

Using CSS to override the StyleSheet as recommended in the docs (and here) results in a very ugly progress bar, completely different in appearance from the stock Windows 7 progress bar.

Normal:

normal appearance

Stylesheet:

stylesheet appearance

I don't want to use this option.

Phlucious
  • 3,704
  • 28
  • 61
  • 2
    Yup, the reason you can't find the code that draws the task bar progress bars on Windows 7+ is because it's a feature of Windows, not Qt -- it's part of the `ITaskBarList3` interface. – MrEricSir Apr 16 '18 at 23:29
  • I did see that. I guess I assumed that something similar was happening on the Windows/vc++ control objects being used deep in the Qt src. – Phlucious Apr 16 '18 at 23:32

1 Answers1

4

The simple way is setting QGraphicsColorizeEffect to the progress bar.

Like this:

QProgressBar* progress = new QProgressBar;
progress->setGraphicsEffect(new QGraphicsColorizeEffect);

Result on Win7:

enter image description here

Umm...the result looks like fine, but we can make it better, only change the chunk colour.

Here is the final result:

enter image description here

Reimplement QGraphicsEffect::draw to specific & customize the colourize effect area:

class Colorize : public QGraphicsEffect {
public:
    explicit Colorize(QObject *parent = Q_NULLPTR) :
        QGraphicsEffect(parent),
        strength(1),
        color(Qt::red),
        effectRect()
    { }
    quint32 strength;
    QColor color;
    QRectF effectRect;

protected:
    void draw(QPainter* painter) {
        QPoint offset;
        const QPixmap pixmap = sourcePixmap(Qt::LogicalCoordinates, &offset);
        draw(painter, offset, pixmap, QRect());
    }

    void draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect) const
    {
        if (src.isNull())
            return;

        QImage srcImage;
        QImage destImage;

        if (srcRect.isNull()) {
            srcImage = src.toImage();
            srcImage = srcImage.convertToFormat(srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32);
            destImage = QImage(srcImage.size(), srcImage.format());
        } else {
            QRect rect = srcRect.toAlignedRect().intersected(src.rect());

            srcImage = src.copy(rect).toImage();
            srcImage = srcImage.convertToFormat(srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32);
            destImage = QImage(rect.size(), srcImage.format());
        }
        destImage.setDevicePixelRatio(src.devicePixelRatioF());

        // do colorizing
        QPainter destPainter(&destImage);
        grayscale(srcImage, destImage, srcImage.rect());
        destPainter.setCompositionMode(QPainter::CompositionMode_Screen);
        destPainter.fillRect(effectRect, color);
        destPainter.end();

        // alpha blending srcImage and destImage
        if(0 < strength && strength < 1){
            QImage buffer = srcImage;
            QPainter bufPainter(&buffer);
            bufPainter.setOpacity(strength);
            bufPainter.drawImage(0, 0, destImage);
            bufPainter.end();
            destImage = buffer;
        }

        if (srcImage.hasAlphaChannel())
            destImage.setAlphaChannel(srcImage.alphaChannel());

        painter->drawImage(dest, destImage);
    }
}; 

Calculate the grove rect of the progress bar:

QRectF getGrooveRect() const {
    StyleOptionProgressBar option;
    option.initFrom(this); // this ⇒ progress bar
    return style()->subElementRect(QStyle::SE_ProgressBarGroove, &option, this);
}

...

class StyleOptionProgressBar : public QStyleOptionProgressBar {
public:
    using QStyleOptionProgressBar::QStyleOptionProgressBar;

    void initFrom(const ColorizeProgressBar* w) {
        init(w);
        minimum = w->minimum();
        maximum = w->maximum();
        progress = w->value();
        text = w->text();
        textAlignment = w->alignment();
        textVisible = w->isTextVisible();
        orientation = w->orientation();
        invertedAppearance = w->invertedAppearance();
    }
};

Complete source on Github.

BTW, Referred Qt source code

JustWe
  • 4,250
  • 3
  • 39
  • 90
  • Wouldn't it have been easier to just apply the GraphicsEffect to the progress bar, hide its text and add a new label (unaffected by the GraphicsEffect) to show the percentage? Not that this isn't a nice example for the draw() function. – TheSHEEEP Apr 17 '18 at 07:19
  • @TheSHEEEP I thought about removing text, but GraphicsEffect changes the whole colour of the progress bar(chunk + background). – JustWe Apr 17 '18 at 07:24
  • True... isn't there some way to only get the filled part of the bar? Might be a private property, though... – TheSHEEEP Apr 17 '18 at 07:55
  • The first option using `QGraphicsColorizeEffect` with `Qt::darkGray` will accomplish the desired end goal for now (i.e., giving the appearance of stopping), unless a simpler option for recoloring it yellow appears. This is the only way to change the color that I could find since the progressbar color is governed by an (uneditable?) animation. – Phlucious Apr 17 '18 at 19:07
  • @Jiu I think you can use the built-in functions to calculate the chunk size using `progressBar->style()->subElementRect(SE_ProgressBarContents, opt, progressBar)`. Only catch is it must be done from a QProgressBar subclass to get access to `initStyleOption()`. – Phlucious Apr 17 '18 at 23:51
  • @Phlucious Thanks! I tried `SE_ProgressBarContents` with `initFrom` but still got the whole size, `initStyleOption() ` didn't used because that for setting the style. BTW, `SE_ProgressBarLabel` can get the correct label pos & size, so I think this shall be the right way to process. – JustWe Apr 18 '18 at 01:34
  • @Phlucious I updated the answer & Github, Please take a look. Really thanks for helping me improve it. – JustWe Apr 18 '18 at 03:09