Emanuele
Emanuele

Reputation: 2394

Qt5 - QML: How to properly connect a ProgressBar with Button for long-running loop calculation

I am connecting a ProgressBar to two Buttons. The ProgressBar will do a computation of a loop as soon as I push the button. The computation will be erased if I will push the other button. I still haven't implemented the pause button.

The print screen of what I am trying to achieve is below and in case needed the whole code of the minimal verifiable example is available here:

pbar

The problem is that as soon as I connect the ProgressBar with my main.cpp file I have a bunch of errors all that look like the following: class ProgressDialog has no member named...

error

code is below:

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include "progressbardialog.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQuickView view;
    ProgressBarDialog progressDialog;

    // One way but it does not work
    // engine.rootContext()->setContextProperty("control", QVariant::fromValue(progressDialog.refModel()));
    // Another way but this also does not work
    view.setSource(QUrl::fromLocalFile("main.qml"));

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

progressbardialog.h

#ifndef PROGRESSBARDIALOG_H
#define PROGRESSBARDIALOG_H

#include <QObject>
#include <QFutureWatcher>

class ProgressBarDialog : public QObject
{
    Q_OBJECT
    Q_PROPERTY(float progress READ progress WRITE setProgress NOTIFY progressChanged)
public:
    ProgressBarDialog();
    float progress(int &iterator);
    // I will use this as a reference to pass to main.cpp using setContextProperty()
    QObject &refModel()
    {
        return m_Model;
    }

public Q_SLOTS:
    void startComputation();
    void cancelComputation();
signals:
    void progressChanged();
private:
    int m_progressValue;
    QObject m_Model;
    QFutureWatcher<void> m_futureWatcher;
};

#endif // PROGRESSBARDIALOG_H

progressbardialog.cpp

#include "progressbardialog.h"
#include <QtConcurrent/QtConcurrentMap>


ProgressBarDialog::ProgressBarDialog()
{}

void spin(int &iteration)
{
    const int work = 1000 * 1000 * 40;
    volatile int v = 0;
    for(int j = 0; j < work; ++j)
        ++v;
}

float ProgressBarDialog::progress(int &iterator)
{
    (void) iterator;
    const int work = 1000 * 1000 * 40;
    volatile int v = 0;
    for(int j = 0; j < work; ++j)
        ++v;
    emit progressChanged();
}

void ProgressBarDialog::startComputation()
{
    // Prepare the vector
    QVector<int> vector;
    for(int i = 0; i < 40; ++i)
        vector.append(i);
    const QFuture<void> future = QtConcurrent::map(vector, spin);
    m_futureWatcher.setFuture(future);
}

void ProgressBarDialog::cancelComputation()
{
    m_futureWatcher.cancel();
}

and finally the main.qml

import QtQuick 2.12                    // for the Item
import QtQuick.Controls 2.12           // for ApplicationWindow
import QtQuick.Layouts 1.12

ApplicationWindow {
    visible: true
    width: 440
    height: 480
    title: qsTr("Progress Bar")
    ColumnLayout {
            spacing: 10
            width: parent.width
        GroupBox {
            id: box1
            title: "Start - Stop"
            font.pointSize: 20
            Layout.alignment: parent.width
            spacing: 10
            GridLayout {
                width: parent.width
                columns: 1
                RowLayout {
                    spacing: 200
                    Layout.fillWidth: true
                    Layout.fillHeight: false
                    Button {
                        id: buttonStart
                        text: "Start"
                        font.pointSize: 15
                        enabled: !progressDialog.active
                        onClicked: progressDialog.startComputation()
                    }
                    Button {
                        id: buttonFinish
                        text: "Finish"
                        font.pointSize: 15
                        enabled: progressDialog.cancelComputation()
                    }
                }
            }
        }
        GroupBox {
            id: boxprogress
            title: "Progressbar"
            font.pointSize: 20
            Layout.alignment: parent.width
            spacing: 10
            GridLayout {
                width: parent.width
                columns: 1
                RowLayout {
                    Layout.fillWidth: true
                    Layout.fillHeight: false
                    ProgressBar {
                        id: progressbar_id
                        Layout.fillWidth: true
                        Layout.fillHeight: true
                        width: parent.width

                        // These are hard-coded values to confirm it is working
                        from: 0
                        to: 100
                        value: 5
                        onValueChanged: {
                            console.log("Progressbar value changed: ", progressbar_id.value)
                        }
                        onVisibleChanged: {
                            console.log("Progressbar visibility changed: ", progressbar_id.visible)
                        }
                    }
                    Connections {
                        target: progressDialog
                        onProgressChanged: progressbar_id.value = progress;
                    }
                    // This is working if clicking on the progressbar
                    MouseArea {
                        anchors.fill: parent
                        onClicked: progressbar_id.value += 5;
                    }
                }
            }
    }
}
}

What I tried so far :

1) In order to prove myself that the ProgressBarDialog is correctly working I tried it before as a stand alone main.qml using hard-coded values (which I left in the code so you can see what I have done). But as soon as I started implementing it in the .cpp file and setting the Q_PROPERTY I received all the errors I posted on the second screenshot.

2) I am sure about the procedure taken so far because: a) According to the official documentation and from this post because I will need to check the process of a long running operations; b) According to QtConcurrent the process has a better possibility to be checked, and that is exactly what I did using QtConcurrent::map()

3) I am using the following statement in the progressbardialog.h to make sure that the object is correctly connected and listens to the QML file:

QObject &refModel()
{
    return m_Model;
}

But this did n't show and substancial improvements. Therefore digging more into the process I was thinking that it should have been the case to use setContextProperty(), whih cled me to the next point.

4) According to the official documentation and according to this post here I thought I would have fix the error, but it still remains.

I am runnign out of ideas and anyone who may have had this problem, please point to the right direction for a possible solution.

Upvotes: 1

Views: 1412

Answers (1)

bialy
bialy

Reputation: 192

Q_PROPERTY is designed to easier read/write/react to variable change (C++ side) on the QML side. To complete your example with variable read/write/notify add correct getter/setter (signal is already ok).

float progress();
void setProgress(float);

If you want to call a function from QML mark it Q_INVOKABLE instead. Also what is the point of volatile in your code?

So to sum up, mark your iteration function as a Q_INVOKABLE. It will increment some internal progress value and then emit progressChanged(). This should cause QML side to update.

Upvotes: 2

Related Questions