28

I work in Qt and when I press the button GO I need to continuously send packages to the network and modify the interface with the information I receive.

The problem is that I have a while(1) in the button so the button never finishes so the interface is never updated. I thought to create a thread in the button and put the while(){} code there.

My question is how can I modify the interface from the thread? (For example how can I modify a textBox from the thread ?

Lukas Knuth
  • 25,449
  • 15
  • 83
  • 111
Emil Grigore
  • 929
  • 3
  • 13
  • 28

4 Answers4

52

Important thing about Qt is that you must work with Qt GUI only from GUI thread, that is main thread.

That's why the proper way to do this is to notify main thread from worker, and the code in main thread will actually update text box, progress bar or something else.

The best way to do this, I think, is use QThread instead of posix thread, and use Qt signals for communicating between threads. This will be your worker, a replacer of thread_func:

class WorkerThread : public QThread {
    void run() {
        while(1) {
             // ... hard work
             // Now want to notify main thread:
             emit progressChanged("Some info");
        }
    }
    // Define signal:
    signals:
    void progressChanged(QString info);
};

In your widget, define a slot with same prototype as signal in .h:

class MyWidget : public QWidget {
    // Your gui code

    // Define slot:
    public slots:
    void onProgressChanged(QString info);
};

In .cpp implement this function:

void MyWidget::onProgressChanged(QString info) {
    // Processing code
    textBox->setText("Latest info: " + info);
}

Now in that place where you want to spawn a thread (on button click):

void MyWidget::startWorkInAThread() {
    // Create an instance of your woker
    WorkerThread *workerThread = new WorkerThread;
    // Connect our signal and slot
    connect(workerThread, SIGNAL(progressChanged(QString)),
                          SLOT(onProgressChanged(QString)));
    // Setup callback for cleanup when it finishes
    connect(workerThread, SIGNAL(finished()),
            workerThread, SLOT(deleteLater()));
    // Run, Forest, run!
    workerThread->start(); // This invokes WorkerThread::run in a new thread
}

After you connect signal and slot, emiting slot with emit progressChanged(...) in worker thread will send message to main thread and main thread will call the slot that is connected to that signal, onProgressChanged here.

P.s. I haven't tested the code yet so feel free to suggest an edit if I'm wrong somewhere

NIA
  • 2,523
  • 22
  • 32
  • 2
    Thanks for this wonderful write up, it really helped me to understand a lot of this stuff that is new to me in QT! I'm having the problem of the UI locking up still. Is the first block of code supposed to be in a .h or .cpp file? – David Dec 11 '13 at 21:30
  • 3
    @David, well, .h vs .cpp actually should not matter. Are you sure that you are starting a thread with `workerThread->start()`, **not** with `workerThread->run()`? – NIA Dec 12 '13 at 09:03
  • 1
    Ann I see, thanks! Changed a few things up and now I have it. Thanks so much. – David Dec 12 '13 at 19:06
  • Users of this code note: Changed is misspelled "Chagned" in this code. While done consistently, make sure to change it or continue reversing the n and the g. – user2503170 Aug 21 '15 at 05:16
  • Ugh. Thanks @user2503170 , I fixed the typo – NIA Nov 12 '15 at 09:32
1

So the mechanism is that you cannot modify widgets from inside of a thread otherwise the application will crash with errors like:

QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
Segmentation fault

To get around this, you need to encapsulate the threaded work in a class, like:

class RunThread:public QThread{
  Q_OBJECT
 public:
  void run();

 signals:
  void resultReady(QString Input);
};

Where run() contains all the work you want to do.

In your parent class you will have a calling function generating data and a QT widget updating function:

class DevTab:public QWidget{
public:
  void ThreadedRunCommand();
  void DisplayData(QString Input);
...
}

Then to call into the thread you'll connect some slots, this

void DevTab::ThreadedRunCommand(){
  RunThread *workerThread = new RunThread();
  connect(workerThread, &RunThread::resultReady, this, &DevTab::UpdateScreen);
  connect(workerThread, &RunThread::finished, workerThread, &QObject::deleteLater);
  workerThread->start();  
}

The connection function takes 4 parameters, parameter 1 is cause class, parameter 2 is signal within that class. Parameter 3 is class of callback function, parameter 4 is callback function within the class.

Then you'd have a function in your child thread to generate data:

void RunThread::run(){
  QString Output="Hello world";
  while(1){
    emit resultReady(Output);
    sleep(5);
  }
}

Then you'd have a callback in your parent function to update the widget:

void DevTab::UpdateScreen(QString Input){
  DevTab::OutputLogs->append(Input);
}

Then when you run it, the widget in the parent will update each time the emit macro is called in the thread. If the connect functions are configured properly, it will automatically take the parameter emitted, and stash it into the input parameter of your callback function.

How this works:

  1. We initialise the class
  2. We setup the slots to handle what happens with the thread finishes and what to do with the "returned" aka emitted data because we can't return data from a thread in the usual way
  3. we then we run the thread with a ->start() call (which is hard coded into QThread), and QT looks for the hard coded name .run() memberfunction in the class
  4. Each time the emit resultReady macro is called in the child thread, it's stashed the QString data into some shared data area stuck in limbo between threads
  5. QT detects that resultReady has triggered and it signals your function, UpdateScreen(QString ) to accept the QString emitted from run() as an actual function parameter in the parent thread.
  6. This repeats every time the emit keyword is triggered.

Essentially the connect() functions are an interface between the child and parent threads so that data can travel back and forth.

Note: resultReady() does not need to be defined. Think of it as like a macro existing within QT internals.

Owl
  • 1,446
  • 14
  • 20
0

you can use invokeMethod() or Signals and slots mechanism ,Basically there are lot of examples like how to emit a signal and how to receive that in a SLOT .But ,InvokeMethod seems interesting .

Below is example ,where it shows How to change the text of a label from a thread:

//file1.cpp

QObject *obj = NULL; //global 
QLabel *label = new QLabel("test");
obj = label;   //Keep this as global and assign this once in constructor.

Next in your WorkerThread you can do as below:

//file2.cpp (ie.,thread)

extern QObject *obj;
void workerThread::run()
{
     for(int i = 0; i<10 ;i++
     {
         QMetaObject::invokeMethod(obj, "setText",
                                Q_ARG(QString,QString::number(i)));
     }
     emit finished();
}
pra7
  • 834
  • 2
  • 21
  • 50
  • Your solution is incomplete, but more importantly, uses a potentially uninitialized (or invalid) global pointer. – jonspaceharper May 28 '16 at 02:15
  • Thanks @JonHarper for pointing out .I have changed it . i think anyone can understand this simple example ,So i didn't give full example. Did u find any memory related issues in the above code ? if yes pls post it.. – pra7 May 31 '16 at 04:10
-4

you start thread passing some pointer to thread function (in posix the thread function have the signature void* (thread_func)(void*), something equal under windows too) - and you are completely free to send the pointer to your own data (struct or something) and use this from the thread function (casting pointer to proper type). well, memory management should be though out (so you neither leak memory nor use already freed memory from the thread), but this is a different issue

VB9-UANIC
  • 330
  • 1
  • 5