1

I have my QML UI alongside my C++ code. My loop which I need to show its progress is developed in C++ like this:

for(unsigned int j = 0; j < Count; ++j) {
    // Do stuff
}

On my QML code, I need to progress bar like this:

ProgressBar {
    value: j  // Should come from C++ loop
              // It is "j" on C++ loop
    from: 0   // C++ loop starts out with 0
    to: Count // C++ loop ends with "Count"
}

I don't know how my C++ loop and my QML progress bar need to be linked to each other. I couldn't find any relevant example. Can anybody give me a hint.


The communication between my C++ and QML is done by using Q_PROPERTY like this, but I'm not sure how to use it:

Q_PROPERTY(float j READ j WRITE setJ NOTIFY jChanged)
Megidd
  • 7,089
  • 6
  • 65
  • 142
  • you have to put you C++ object in the QML context by calling `setContextProperty` on the `rootContext` of your `QQmlEngine`. See http://doc.qt.io/qt-5/qtqml-cppintegration-contextproperties.html (the example is for `QQuickView` but it also works for `QQmlEngine`) – Amfasis Nov 13 '18 at 12:12

1 Answers1

3

It's easy! let's go step by step.

  1. Your C++ class should extend QObject and use Q_OBJECT macro inside. I call it Worker for example.

    class Worker : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
    public:
        Q_INVOKABLE void start() {
            // start the worker thread in which your loop spins
        }
        float progress(); // progress getter
    signals:
        void progressChanged(float progress);
    };
    
  2. Start a background thread and define its own progressChanged signal.

    for(unsigned int j = 0; j < Count; ++j) {
        // Do stuff
        emit progressChanged(/*current progress*/);
    }
    
  3. Connect background thread's progressChanged to the main thread living object Worker's progressChanged signal (queued connection). This step is required to relay signals from background thread to QML.

  4. Link the C++ class to QML:

    int main(int argc, char *argv[]) {
        QGuiApplication app(argc, argv);
    
        QQuickView view;
        Worker w;
        view.engine()->rootContext()->setContextProperty("worker", &w);
        view.setSource(QUrl::fromLocalFile("MyItem.qml"));
        view.show();
    
        return app.exec();
    }
    
  5. Listen to the signal at QML side:

    ProgressBar {
        id: prg
        from: 0   // C++ loop starts out with 0
        to: Count // C++ loop ends with "Count"
    
        Connections {
            target: worker
            onProgressChanged: prg.value = progress;
        }
    }
    
frogatto
  • 28,539
  • 11
  • 83
  • 129
  • 1
    Did you test that? Because it looks like this solution will block the main thread, so the GUI won't update until all the work is done. And QML cannot directly connect to objects living in other threads, any queued connections have to be handled on the C++ side. So at the very least, it would take a non-blocking worker. – dtech Nov 13 '18 at 18:49
  • @dtech In the `start` method I put a comment that a worker thread should be run in which progress signals should be emitted. Also you're right, an additional connection is needed to connect the main thread's `progressChanged` to the background's thread `progressChanged`. – frogatto Nov 14 '18 at 06:58
  • 1
    See https://stackoverflow.com/questions/53280700/progress-bar-implementation-in-qml-to-show-progress-of-a-loop-in-c/53282734?noredirect=1#comment93470271_53282734 and https://stackoverflow.com/questions/32952474/non-blocking-worker-interrupt-file-copy/32952945 , the latter can be implemented even in the main thread without blocking it, by fragmenting the work. – dtech Nov 14 '18 at 07:10
  • @dtech I think starting a background thread is a better solution to fragmenting the work and periodically block the main thread. – frogatto Nov 14 '18 at 07:16
  • 1
    It really depends, and there is nothing wrong with blocking the main thread, as long as it is not for more than 50 ms. There are many tasks for which the second thread is overkill. A non-blocking worker can also be implemented in plain QML, where you can't really use secondary threads, except for the exceptionally limited worker script. Different solutions for different use cases. – dtech Nov 14 '18 at 07:28