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.
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();
}
Related
I'm using the MVC pattern and I'm trying to connect a signal from my view class with my controller class that inherits QObject
class View : public QWidget
{
Q_OBJECT
private:
Controller* controller;
QPushButton* startButton;
void addControls(QVBoxLayout* mainLayout);
public:
explicit View(QWidget *parent = nullptr);
void setController(Controller* c);
};
#endif // VIEW_H
This are the methods
void View::addControls(QVBoxLayout *mainLayout)
{
//I'm adding the button
}
View::View(QWidget *parent) : QWidget(parent)
{
//Layout
}
void View::setController(Controller *c){
controller = c;
connect(startButton, SIGNAL(clicked()), controller, SLOT(begin()));
//ERROR controller is a Controller* and it can't be converted it to const QObject*
}
And this is the Controller class
class Controller : public QObject
{
Q_OBJECT
private:
QTimer* timer;
View* view;
Model* model;
public:
explicit Controller(QObject *parent = nullptr);
~Controller();
void setModel(Model* m);
void setView(View* v);
public slots:
void begin() const;
};
#endif // CONTROLLER_H
And the methods
Controller::Controller(QObject *parent):
QObject(parent), timer(new QTimer)
{
connect(timer, SIGNAL(timeout()), this, SLOT(next()));
}
Controller::~Controller() { delete timer; }
void Controller::setModel(Model* m) { model = m; }
void Controller::setView(View* v) { view = v; }
void Controller::begin() const {
timer->start(200);
}
For good measure, here is the main where I set each component
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
View w;
Controller c;
Model m;
c.setModel(&m);
c.setView(&w);
w.setController(&c);
w.show();
return a.exec();
}
I've tried everything I could think of, can't make it work..
Stop using the terrible text-based slot connection interface.
Use the modern one that gives nice compile errors:
QObject::connect(startButton, &QPushButton::clicked, controller, &Controller::begin);
I want to update UI from the second thread, I have created slots which are going to be called from other thread by signaling, but somehow it is not being called from the other thread. Below is the code:
WorkerThread.h
class WorkerThread: public QObject
{
Q_OBJECT
public:
WorkerThread();
~WorkerThread();
public slots:
void onStart();
signals:
void sendMessage(const QString& msg, const int& code);
};
WorkerThread.cpp
#include "workerwhread.h"
WorkerThread::WorkerThread(){}
void WorkerThread::onStart(){
emit sendMessage("start", 100);
}
Usage:
MyWidget.h
namespace Ui {
class MyWidget;
}
class MyWidget: public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
private slots:
void onGetMessage(const QString &msg, const int& code);
private:
Ui::MyWidget *ui;
QThread *thread = nullptr;
WorkerThread *wt = nullptr;
};
MyWidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MyWidget)
{
ui->setupUi(this);
wt = new WorkerThread;
thread = new QThread;
connect(thread, &QThread::finished, wt, &QObject::deleteLater);
connect(ui->btStart, &QPushButton::clicked, wt, &WorkerThread::onStart);
connect(wt, &WorkerThread::sendMessage, this, &MyWidget::onGetMessage);
wt->moveToThread(thread);
thread->start();
}
void MyWidget::onGetMessage(const QString &msg, const int& code)
{
qDebug() << "message" << msg; // this never being called ????
}
Note: When I pass the connection type Qt::DirectConnectoin, then it is working, but the problem is it is not the GUI thread.
connect(wt, &WorkerThread::sendMessage, this, &MyWidget::onGetMessage, Qt::DirectConnection);
Main
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.setWindowIcon(QIcon(":/icons/system.png"));
return a.exec();
}
After a lot of trying and checking the code line by line, I finally found the problem. The reason was with overriding the event() function of QWidget, the return value of event() function is bool, so if you return a true it is OK and working well without throwing any runtime or compile-time error. But it will prevent signal-slot events to happen.
So NOT return true, but return QWidget::event(event); then it will slove the problem.
I trying to display some text to a textbrowser via: FindChild and it never displays it in the textbrowswer any help would be helpfull..
Here is my mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
namespace Ui
{
class MainWindow;
class TestWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
static MainWindow* GetInstance(QWidget* parent = 0);
signals:
public slots:
void on_pushButton_3_clicked();
void MainWindow_TextBrowser_String(const QString & newText);
private:
Ui::MainWindow *ui;
static MainWindow* mainInstance;
};
Here is my mainwindow.cpp
// Constructor
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
// Destructor
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_3_clicked()
{
TestWindow mwindow;
mwindow.start();
}
MainWindow* MainWindow::mainInstance = 0;
MainWindow* MainWindow::GetInstance(QWidget *parent)
{
if (mainInstance == NULL)
{
mainInstance = new MainWindow(parent);
}
return mainInstance;
}
void MainWindow::MainWindow_TextBrowser_String(const QString & newText)
{
QString TextBrowser_String = QString(newText);
ui->textBrowser->append(TextBrowser_String);
}
I create the testwindow object in the pushbutton send the start function to call the findchild window to send a string to the textbrowser window
Here is my testwindow.cpp
void testwindow::start()
{
// Create a new mainwindow on the heap.
MainWindow* instance = MainWindow::GetInstance();
// Or I can call
// MainWindow instance; then point to findchild
QString Test_Window_String = QStringLiteral("Test Window String");
instance->findChild<QTextBrowser*>("textBrowser")->append(Test_Window_String);
}
I understand that you can use a singal and slot and simply just create a signal that sends the string to the append textbrowser
void testwindow::singalandslot()
{
MainWindow* instance = MainWindow::GetInstance();
connect(this, SIGNAL(TextBrowswer_String(const QString &)), instance , SLOT(MainWindow_TextBrowser_String(QString &)));
}
void testwindow::fireSignal()
{
emit TextBrowswer_String("sender is sending to receiver.");
}
Even with a signal or FindChild it seems that the object is already deleted or i'm doing something wrong.
Can you please share your Ui::MainWindow Class and setupUi implementation to get a clear view?
Hope you have created the instance for QTextBrowser* inside setupUi or in the constructor.
With the below setupUi implementation, both ur usecases are working.
namespace Ui
{
class MainWindow: public QWidget
{
Q_OBJECT
public:
QTextBrowser* textBrowser;
void setupUi(QWidget* parent)
{
setParent(parent);
textBrowser = new QTextBrowser(parent);
textBrowser->setObjectName("textBrowser");
textBrowser->setText("Hello");
}
};
}
I have two subclasses of QObject:
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0);
signals:
public slots:
void process();
};
And
class ThreadController : public QObject
{
Q_OBJECT
public:
explicit ThreadController(QObject *parent = 0);
public slots:
void sleep();
void wakeUp();
private:
QMutex *mutex;
QWaitCondition *wc;
};
void ThreadController::sleep(){
mutex->lock();
wc->wait(mutex);
mutex->unlock();
}
void ThreadController::wakeUp(){
mutex->lock();
wc->wakeOne();
mutex->unlock();
}
******************************************************
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
QThread* thread = new QThread;
Worker* worker = new Worker;
ThreadController*controller=new ThreadController();
controller->moveToThread(thread);
worker->moveToThread(thread);
QObject::connect(thread,&QThread::started,worker,&Worker::process);
QObject::connect(&w,&MainWindow::wakeClicked,controller,&ThreadController::wakeUp);
QObject::connect(&w,&MainWindow::sleepClicked,controller,&ThreadController::sleep);
thread->start();
return a.exec();
}
I move the worker and controller to the same QThread. I want to control the thread (put to sleep or wake up) by the ThreadController and the worker just doing its work, now the idea can not work, the worker can doing process but the controller can not work, I don't know what's wrong with my code.
Thanks #thuga yes,just as you say. Worker::process is a infinite loop like that:
void Worker::process(){
int a=(int)QThread::currentThreadId();
emit sendThreadId(a);
int b=0;
while (b<1000000) {
b++;
emit processResult(b);
QThread::msleep(1);// signal emit too quickly will block the main thread,
//so i just let the loop slow down
}
}
Why ThreadController::sleep is never executed if the process is infinite loop,but the ThreadController::wakeup can be executed i had tested.
QObject::connect(&w,&MainWindow::wakeClicked,controller,&ThreadController::wakeUp,Qt::DirectConnection);
yes the directconnection let it work.thanks!And i havs some questions:when a QThread uses QWaitCondition::wait() will stop the event loop in it? So any signal sends to the thread will not be processed anymore?
i want to reuse the QThread in my program.When the worker finishs its work will send a signal to the controller,and the controller will execute the sleep()function.Next time the data comes,controller wakeup() the thread and the worker goes to work again. Has any other solution to suit this case ?Reuse QThread or Delete it and new it(which Performance loss more)?
#thuga I need the signal&slot in the workerthread.But QtConcurrent::run could not provide such a mechanism.You should be correct that we don't have to put the thread to sleep when there is nothing to do. And i just finished the job with your help. Although my solution is not good,i posted all the code as shown below:
Worker Class for doing heavy jobs.
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0);
signals:
void processResult(int);
void sendThreadId(int);
public slots:
void process();
};
void Worker::process(){
QEventLoop eventloop;
int a=(int)QThread::currentThreadId();
emit sendThreadId(a);
int b=0;
while (b<1000000) {
b++;
emit processResult(b);
QThread::msleep(1);
eventloop.processEvents();
}
qDebug()<<"Worker::process() called";
qDebug()<<QThread::currentThreadId();
eventloop.exec();
}
ThreadController class for controling the worker QThread to sleep or wakeup.
class ThreadController : public QObject
{
Q_OBJECT
public:
explicit ThreadController(QObject *parent = 0);
~ThreadController();
signals:
void statusChanged(QString);
public slots:
void sleep();
void wakeUp();
private:
QMutex *mutex;
QWaitCondition *wc;
};
ThreadController::ThreadController(QObject *parent) : QObject(parent)
{
mutex=new QMutex();
wc=new QWaitCondition();
}
ThreadController::~ThreadController(){
delete mutex;
delete wc;
}
void ThreadController::sleep(){
qDebug()<<"ThreadController::sleep() called";
qDebug()<<QThread::currentThreadId();
emit statusChanged("WorkerThread sleeping");
mutex->lock();
wc->wait(mutex);
mutex->unlock();
}
void ThreadController::wakeUp(){
qDebug()<<"ThreadController::wakeUp() called";
qDebug()<<QThread::currentThreadId();
emit statusChanged("WorkerThread wakening");
mutex->lock();
wc->wakeAll();
mutex->unlock();
}
MainWindow Class for making a simple GUI(two QPushButtons and several QLabels)
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void wakeClicked();
void sleepClicked();
public slots:
void setMainId(int);
void setThreadId(int);
void setThreadStatus(QString);
void setThreadCount(int);
private:
QLabel*l_mainThreadId;
QLabel*l_workerThreadId;
QLabel*l_workerThreadStatus;
QLabel*l_workerThreadCount;
QString count;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("Threading Test");
QWidget *window=new QWidget;
QVBoxLayout *mainLayout=new QVBoxLayout;
QHBoxLayout *mainLayout1=new QHBoxLayout;
QHBoxLayout *mainLayout2=new QHBoxLayout;
QLabel*label1=new QLabel("Main Thread ID:");
l_mainThreadId=new QLabel;
int a=(int)QThread::currentThreadId();
QString idlabel=QString::number(a);
l_mainThreadId->setText(idlabel);
mainLayout1->addWidget(label1);
mainLayout1->addWidget(l_mainThreadId);
QPushButton* wakeButton=new QPushButton("WakeUp");
QPushButton*sleepButton=new QPushButton("Sleep");
mainLayout2->addWidget(wakeButton);
mainLayout2->addWidget(sleepButton);
mainLayout->addLayout(mainLayout1);
mainLayout->addLayout(mainLayout2);
QVBoxLayout *threadLayout=new QVBoxLayout;
QHBoxLayout *threadLayout1=new QHBoxLayout;
QHBoxLayout *threadLayout2=new QHBoxLayout;
QHBoxLayout *threadLayout3=new QHBoxLayout;
QLabel *label2=new QLabel("Worker Thread ID:");
l_workerThreadId=new QLabel;
threadLayout1->addWidget(label2);
threadLayout1->addWidget(l_workerThreadId);
QLabel *label3=new QLabel("Worker Thread Status:");
l_workerThreadStatus=new QLabel;
threadLayout2->addWidget(label3);
threadLayout2->addWidget(l_workerThreadStatus);
QLabel *label4=new QLabel("Counting Result:");
l_workerThreadCount=new QLabel;
threadLayout3->addWidget(label4);
threadLayout3->addWidget(l_workerThreadCount);
threadLayout->addLayout(threadLayout1);
threadLayout->addLayout(threadLayout2);
threadLayout->addLayout(threadLayout3);
QHBoxLayout *layout=new QHBoxLayout;
layout->addLayout(mainLayout);
layout->addLayout(threadLayout);
window->setLayout(layout);
setCentralWidget(window);
setThreadStatus("WorkerThread wakening");
QObject::connect(wakeButton,&QPushButton::clicked,this,&MainWindow::wakeClicked);
QObject::connect(sleepButton,&QPushButton::clicked,this,&MainWindow::sleepClicked);
}
MainWindow::~MainWindow()
{
}
void MainWindow::setMainId(int id){
QString idlabel=QString::number(id);
l_mainThreadId->setText(idlabel);
}
void MainWindow::setThreadId(int id){
QString idlabel=QString::number(id);
l_workerThreadId->setText(idlabel);
}
void MainWindow::setThreadStatus(QString s){
l_workerThreadStatus->setText(s);
}
void MainWindow::setThreadCount(int i){
count = QString::number (i) ;
l_workerThreadCount->setText(count);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
QThread* thread = new QThread;
Worker* worker = new Worker;
ThreadController*controller=new ThreadController();
controller->moveToThread(thread);
worker->moveToThread(thread);
QObject::connect(thread,&QThread::started,worker,&Worker::process); QObject::connect(worker,&Worker::processResult,&w,&MainWindow::setThreadCount);
QObject::connect(worker,&Worker::sendThreadId,&w,&MainWindow::setThreadId);
QObject::connect(&w,&MainWindow::wakeClicked,controller,&ThreadController::wakeUp,Qt::DirectConnection);
QObject::connect(&w,&MainWindow::sleepClicked,controller,&ThreadController::sleep);
QObject::connect(controller,&ThreadController::statusChanged,&w,&MainWindow::setThreadStatus);
thread->start();
return a.exec();
}
The Program:
I have two classes, MyClass and Widget. Below is the MyClass class and from my Widget class i want to get the str variable. How is that done?
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass();
void fetch();
public slots:
void replyFinished(QNetworkReply*);
private:
QNetworkAccessManager* m_manager;
};
MyClass::MyClass()
{
m_manager = new QNetworkAccessManager(this);
connect( m_manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
}
void MyClass::fetch()
{
m_manager->get(QNetworkRequest(QUrl("http://stackoverflow.com")));
}
void MyClass::replyFinished(QNetworkReply* pReply)
{
QByteArray data=pReply->readAll();
QString str(data);
//this str should be available in my widget class
}
EDIT: Here is a the important part of the widget
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
};
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//here str should be accessed
}
If you want the str variable from your function available to classes or other functions, here are two choices:
Return it from the function.
Declare a variable in MyClass to hold the string and set the
variable to the value.
Case 1: Returning from a function
QString MyClass::replyFinished(...)
{
QString str(data);
return data;
}
Case 2: Declare a class member variable
class MyClass
{
public:
QString m_replyStr;
};
//...
void MyClass::replyFinished(...)
{
QByteArray data = pReply->readAll();
m_replyStr = data;
}
Modifying your question with an example of what you want to do would be very helpful.
You can emit a signal with str as argument and connect it to a slot in your widget. Then you can do what you want with it.
This way you will preserve the event oriented design and you have not need to control if str exists. Simply when it's ready the slot will handle it.
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass();
void fetch();
public slots:
void replyFinished(QNetworkReply*);
signals:
void strReplyReady(QString str);
private:
QNetworkAccessManager* m_manager;
};
...
void MyClass::replyFinished(QNetworkReply* pReply)
{
QByteArray data=pReply->readAll();
QString str(data);
emit strReplyRead(str);
}
your Widget
class MyWidget : public QWidget
{
//public members
...
public slots:
void readReply(QString str);
}
void MyWidget::readReply(QString str){
//do what you want with str
}
in the main.cpp you do the connect with the static member of QObject
QObject::connect(myClassPointer,SIGNAL(strReplyReay(QString)) ,
myWidgetPointer,SLOT(readReply(QString)));