Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Qt: Access GUI From Another Thread?

Credits go to CGamesPlay for helping out!
This thread is locked; no one can reply to it. rss feed Print
Qt: Access GUI From Another Thread?
Billybob
Member #3,136
January 2003

Apparently in Qt you can only access GUI elements from the GUI (main) thread. I need to use multiple threads to do work while keeping the GUI responsive, and yet the worker threads need to modify the GUI. Qt does provide an Event system that's thread-safe, but writing all those different events is not something I look forward to. I could macro/template something together for events, but still it seems silly.

Does anyone have any ideas for a solution to this problem? Particularly one that makes writing my program easy, and not painful (that's why I'm using Qt in the first place). If only C++ let me do anonymous local classes like Java :(

EDIT: An old documentation reference says I can use QApplication::lock to do it, but I can't find references to lock in the 4.3 documentation.

CGamesPlay
Member #2,559
July 2002
avatar

"All those different events"? How many could there be? Provide more information about your application, it sounds like you might be doing something wrong.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

Well, for example, one of my worker threads needs to update the status bar and add a message to the console (a text box on my window) as it performs its tasks.

CGamesPlay
Member #2,559
July 2002
avatar

So you need one signal that accepts a QString.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

Is there no way to do the same thing without signal->slot? All I want to do is call a function of my MainWindow class in the context of the GUI thread. It seems silly to have to write another class with signals that match all the slots of the MainWindow, and then inherit that class so that all my workers can throw those signal.

CGamesPlay
Member #2,559
July 2002
avatar

You don't need a second class:

1class MainWindow: public QMainWindow
2{
3 public slots:
4 void statusUpdate(QString status);
5};
6 
7class WorkerThread: public QObject
8{
9 public:
10 void doWork();
11 
12 signals:
13 void statusChanged(QString newStatus);
14};
15 
16void WorkerThread::doWork()
17{
18 connect(this, SIGNAL(statusChanged(QString))), qMainWindow, SLOT(statusUpdate(QString)));
19 
20 emit statusChanged("Beginning work...");
21}

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

Well, no I don't need a second class, but the second class is necessary if multiple classes (working in multiple threads) need access to the GUI. Otherwise I'd be duplicating signal code. The second class would have all the signals, as well as the connect's in its constructor. So that way I wouldn't have to duplicate all the work.

In any case, I found the solution. I stumbled upon it in Qt Assistant. It's good ole QMetaObject::invokeMethod :) It does exactly what I need, invoke a method in the other thread. As I was poking around I also found some other useful features like auto-connect and foreach. :D

There's just one last issue. invokeMethod is async when it's used across threads. This is fine for methods that set things on the GUI, but methods that return a value could be an issue. Any idea on how I would tell my thread to wait until the event has been processed (and a value returned)? I have no immediate need for this, but in case it comes up ...

By the way, thanks for the help. you gave me ideas of what to look for in Qt Assistant :)

CGamesPlay
Member #2,559
July 2002
avatar

I'm more and more confident that you have a fundamental misunderstanding of what's going on.

Quote:

Well, no I don't need a second class, but the second class is necessary if multiple classes (working in multiple threads) need access to the GUI.

Multiple slots can connect to a single signal, and a single slot can connect to multiple signals.

Quote:

It's good ole QMetaObject::invokeMethod :) It does exactly what I need, invoke a method in the other thread.

This is the method that emit uses. You are using Qt's signals, but incorrectly.

Quote:

There's just one last issue. invokeMethod is async when it's used across threads. This is fine for methods that set things on the GUI, but methods that return a value could be an issue. Any idea on how I would tell my thread to wait until the event has been processed (and a value returned)? I have no immediate need for this, but in case it comes up ...

Not possible. That's why signals return void, always. Besides, what if multiple slots were connected to that signal? Which value would it return?

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

Quote:

Multiple slots can connect to a single signal, and a single slot can connect to multiple signals.

??? How is that relevant? The signal needs to be defined in the class that's emitting it, so if I have multiple classes that need to modify the GUI, then each and every one of them will need to define the signal. (or, use a second class and inherit from it).

Quote:

You are using Qt's signals, but incorrectly.

Perhaps, but trying to use them "properly" would involve massive amounts of code. Why go through all that when I can just do this?
QMetaObject::invokeMethod(MainWindow::instance->txtConsole, "append", Q_ARG(text));

I appreciate the help though. If I'm using it wrong, I would like to know. It's always good to have a better understanding of the library you're using :)

axilmar
Member #1,204
April 2001

Quote:

Apparently in Qt you can only access GUI elements from the GUI (main) thread. I need to use multiple threads to do work while keeping the GUI responsive, and yet the worker threads need to modify the GUI. Qt does provide an Event system that's thread-safe, but writing all those different events is not something I look forward to. I could macro/template something together for events, but still it seems silly.

Does anyone have any ideas for a solution to this problem? Particularly one that makes writing my program easy, and not painful (that's why I'm using Qt in the first place). If only C++ let me do anonymous local classes like Java :(

You need to post QEvent-derived instances to the main thread.

CGamesPlay
Member #2,559
July 2002
avatar

Quote:

The signal needs to be defined in the class that's emitting it, so if I have multiple classes that need to modify the GUI, then each and every one of them will need to define the signal. (or, use a second class and inherit from it).

Wait, you know that you don't define the signal for the class, right?

class Object: public QObject
{
    Q_OBJECT

    Object();

    signals:
        /// You do not write the definition for this function.
        void statusChanged(QString);
};

Object::Object()
{
    connect(this, SIGNAL(statusChanged(QString))), qMainWindow, SLOT(statusUpdate(QString)));
}

The only "extra code" you have to write is the connect line and the prototype for the function.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

1class MainWindow : blah
2{
3blah
4 
5slots:
6 setStatusBarText(QString);
7 appendToConsole(QString);
8 enable_btnStart(bool);
9 enable_btnStop(bool);
10 enable_btnForce(bool);
11 blah, blah, blah
12};
13 
14// Long-o implementions of all the slots go here
15// And keep going
16// and going
17// and going
18// and going
19// and going
20// and going
21// and going
22// and this'll probably fill up a couple pages
23// so just imagine spending a couple hours of your life here
24// Oh and don't forget that you made a typo in the first function
25// which you've been copying and pasting
26// so now all your work is wasted.
27 
28class Object1 : public QObject
29{
30Q_OBJECT
31private signals:
32 setStatusBarText(QString);
33 appendToConsole(QString);
34 enable_btnStart(bool);
35 enable_btnStop(bool);
36 enable_btnForce(bool);
37 blah, blah, blah
38};
39 
40// Constructor with connect
41 
42class Object2 : public QObject
43{
44Q_OBJECT
45private signals:
46 setStatusBarText(QString);
47 appendToConsole(QString);
48 enable_btnStart(bool);
49 enable_btnStop(bool);
50 enable_btnForce(bool);
51 blah, blah, blah
52};
53 
54// Constructor with connect
55 
56class Object3 : public QObject
57{
58Q_OBJECT
59private signals:
60 setStatusBarText(QString);
61 appendToConsole(QString);
62 enable_btnStart(bool);
63 enable_btnStop(bool);
64 enable_btnForce(bool);
65 blah, blah, blah
66};
67 
68// Constructor with connect
69 
70class Object4 : public QObject
71{
72Q_OBJECT
73private signals:
74 setStatusBarText(QString);
75 appendToConsole(QString);
76 enable_btnStart(bool);
77 enable_btnStop(bool);
78 enable_btnForce(bool);
79 blah, blah, blah
80};
81 
82// Constructor with connect

Or I could just use invokeMethod :P

CGamesPlay
Member #2,559
July 2002
avatar

How many slots would you actually have to write? SO far, it still looks like you're doing it wrong. For one, adhere to MVC: your model (worker thread) doesn't need to know anything about the view (MainWindow's GUI). You should construct a message type that you use to pass. This can, most likely, be done in one signal, but I can't envision a viable case with more than 5.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Billybob
Member #3,136
January 2003

I see. Okie dokie, I know the MVC but I'm not using it here. This is a smaller project that doesn't need to adhere to such a standard.

Thanks for the help. :)

HoHo
Member #4,534
April 2004
avatar

Can't you create a baseclass with all those signals and derieve Object1-4 from it? Should save quite some time.

Also if slots like "enable_btnStart" and "setStatusBarText" do just that, set the button enambled or write a new statusbar text then why don't you connect the changing class signals directly to those buttons or with statusbar? No need for intermediate class that woudl do it. Though you'd need to make those connections within the MainWindow class then.

__________
In theory, there is no difference between theory and practice. But, in practice, there is - Jan L.A. van de Snepscheut
MMORPG's...Many Men Online Role Playing Girls - Radagar
"Is Java REALLY slower? Does STL really bloat your exes? Find out with your friendly host, HoHo, and his benchmarking machine!" - Jakub Wasilewski

CGamesPlay
Member #2,559
July 2002
avatar

Quote:

Can't you create a baseclass with all those signals and derieve Object1-4 from it? Should save quite some time.

Billybob said:

It seems silly to have to write another class with signals that match all the slots of the MainWindow, and then inherit that class so that all my workers can throw those signal.

Quote:

Also if slots like "enable_btnStart" and "setStatusBarText" do just that, set the button enambled or write a new statusbar text then why don't you connect the changing class signals directly to those buttons or with statusbar?

Bad idea; breaks OO ;)

No, the best way would be to create a custom event type and send that with all the necessary parameter information, then let the MainWindow decide how to represent the information. But He's made it clear that he doesn't care about the right way :)

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

HoHo
Member #4,534
April 2004
avatar

Quote:

Bad idea; breaks OO

And I thought this is exactly what MVC is all about. In this case the mainwindow would be the controller that connects the model (signals from other classes) with view (sets button statuses and statusbar text). Have I really misunderstood the concept that badly?

__________
In theory, there is no difference between theory and practice. But, in practice, there is - Jan L.A. van de Snepscheut
MMORPG's...Many Men Online Role Playing Girls - Radagar
"Is Java REALLY slower? Does STL really bloat your exes? Find out with your friendly host, HoHo, and his benchmarking machine!" - Jakub Wasilewski

CGamesPlay
Member #2,559
July 2002
avatar

Quote:

And I thought this is exactly what MVC is all about. In this case the mainwindow would be the controller that connects the model (signals from other classes) with view (sets button statuses and statusbar text).

No, you have it right, but you're misdirected: The model does not know that it is changing the status bar text, or enabling a button. That is the responsibility of the view.

As a general rule, the same model code should be able to work, without changes, from the GUI, or from the command-line, or from a web page, with just different controllers and views.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

axilmar
Member #1,204
April 2001

Billybob, why don't you do the sane thing and create QEvent-derived instances? you can do any modification to the GUI from inside the event, when it is executed by the main thread.

Go to: