1

I need some advice to access the field(QString name) variable in QWizardPage from a QThread. I'm building some kind of an installer and I want to do the installing work in a separate Thread.

My purpose: When reached the commit/install page, I want to execute code to do the "installing" and update the QWizardPage with my progress, until its finished.

The install function is dependent on many field() variables from other QWizardPages. Therefore I tried to execute this install function from a QThread, which is defined in an inner class from my QWizardPage. The problem is, the field()-function i a non-static member and so it's not working. And so I'm out of ideas to run my install-function parallel to my WizardPage.

I tried something like this:

InstallPage.h

class InstallPage : public QWizardPage
{
    Q_OBJECT
    class WorkerThread : public QThread
    {
        Q_OBJECT
        void run() override;
    };

public:
    InstallPage(QWidget *parent = 0);

private: 
    QLabel *lProgress;
    WorkerThread *installer;
    void install(); 
};


InstallPage.c

InstallPage::InstallPage(QWidget *parent)
    : QWizardPage(parent)
{
...
    installer = new WorkerThread(this);
    installer->start();
}

void InstallPage::WorkerThread::run()
{
    if(field("checkBox1").ToBool()) 
    {
         doStuff();
    }
}

//QT-Creator says at field("checkBox1"): 
//error: call to non-static member function without an object argument

I'm also open for any other idea to make my installer work. Maybe someone knows something I haven't thought of.

Tiger69
  • 11
  • 2
  • Like eyllaesc suggests, invert the dependency and create a separate worker class, copy the necessary information. Makes synchronization much easier, too. – Frank Osterfeld Aug 11 '19 at 06:49

1 Answers1

1

Another approach is to create a worker (QObject) that lives in another thread that performs the heavy task and notifies the status of that task through signals:

#include <QtWidgets>

class InitialPage: public QWizardPage
{
public:
    InitialPage(QWidget *parent = nullptr): QWizardPage(parent)
    {
        QSpinBox *spinbox = new QSpinBox;
        QLineEdit *lineedit = new QLineEdit;
        QVBoxLayout *lay = new QVBoxLayout(this);
        lay->addWidget(spinbox);
        lay->addWidget(lineedit);

        registerField("value1", spinbox);
        registerField("value2", lineedit);
    }

};

class InstallWorker: public QObject
{
    Q_OBJECT
public:
    InstallWorker(QObject *parent=nullptr): QObject(parent)
    {

    }
public Q_SLOTS:
    void install(int param1, const QString & param2)
    {
        Q_EMIT started();
        for(int i=0; i < 100; i++){
            qDebug() << __PRETTY_FUNCTION__ << i << param1 << param2;
            QThread::msleep(100);
            Q_EMIT progressChanged(i);
        }
        qDebug()<< __PRETTY_FUNCTION__ << "finished";
        Q_EMIT finished();
    }
Q_SIGNALS:
    void started();
    void progressChanged(int value);
    void finished();
};

class InstallPage: public QWizardPage
{
    Q_OBJECT
public:
    InstallPage(QWidget *parent = nullptr): QWizardPage(parent),
        label(new QLabel), progressbar(new QProgressBar)
    {

        QVBoxLayout *lay = new QVBoxLayout(this);
        lay->addWidget(label);
        lay->addWidget(progressbar);

        progressbar->setMinimum(0);
        progressbar->setMaximum(100);

        thread = new QThread(this);
        worker.moveToThread(thread);
        connect(&worker, &InstallWorker::started, this, &InstallPage::onStarted);
        connect(&worker, &InstallWorker::finished, this, &InstallPage::onFinished);
        connect(&worker, &InstallWorker::progressChanged, this, &InstallPage::onProgressChanged);
        thread->start();
    }
    ~InstallPage(){
        thread->quit();
        thread->wait();
    }
    void initializePage(){
        start_install();
    }
private Q_SLOTS:
    void start_install(){
        int param1 = field("value1").toInt();; 
        QString param2 = field("value2").toString();
        QMetaObject::invokeMethod(&worker, "install", Qt::QueuedConnection, Q_ARG(int, param1), Q_ARG(QString, param2));
    }
    void onStarted(){
        for(QWizard::WizardButton which: {QWizard::BackButton, QWizard::NextButton, QWizard::CancelButton})
            if(QAbstractButton * button = wizard()->button(which))
                button->setEnabled(false);
    }
    void onFinished(){
        for(QWizard::WizardButton which: {QWizard::BackButton, QWizard::NextButton, QWizard::CancelButton})
            if(QAbstractButton * button = wizard()->button(which))
                button->setEnabled(true);
        wizard()->next();
    }
    void onProgressChanged(int value){
        progressbar->setValue(value);
        label->setNum(value);
    }
private:
    InstallWorker worker;
    QThread *thread;
    QLabel *label;
    QProgressBar *progressbar;
};

class FinalPage: public QWizardPage
{
public:
    FinalPage(QWidget *parent = nullptr): QWizardPage(parent)
    {

    }
};

#include "main.moc"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QWizard wizard;
    wizard.addPage(new InitialPage);
    wizard.addPage(new InstallPage);
    wizard.addPage(new FinalPage);
    wizard.show();
    return app.exec();
}
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I tested it with some fields and this approach works well. But the problem I have with this is, that I have many fields, that are needed for installing. In your solution you pass the field-values as arguments to the thread. But I would need about 20-30 arguments there and this doesn't seems to be an elegant way to do this. Any suggestions? – Tiger69 Aug 11 '19 at 15:03
  • @Tiger69 Are they the same type? If they are then package it in a container so you only have to pass one arguments. If they are different you can convert it to QVariant and use a container as indicated – eyllanesc Aug 11 '19 at 15:06