Introduction
This article exposes some of the underlying things that Qt uses for object model manipulation and threading. For beginners, it provides direct insight as to what really goes on in signals and slots. For an advanced user, it's a good reference for situations where you have a large semi-dynamically constructed interface or you need reflection style programming but are in C++.
Background
In Qt, every QObject
derived instance has a name. You can set it programmatically, or set it in the IDE using Qt Designer, or have it be auto-assigned by Qt Designer. For most cases, that makes it (typically) unique.
That means that every QObject
in the entire application (or at least, the top most window) has (almost always) a unique ... path from top most application node to the bottom:
grandParent/parent/child
That means:
- You can find it using Qt's framework and get a pointer to it (search by name, or name and type/subtype).
- You can use Qt's meta-object data to ask about its type (as a
string
), or ask which calls it supports.
That means you can then dynamically find, and get call signatures and through Qt's signals/slots, invoke via text an object.
- You can call functions that are
Q_SLOTS
but the code was invented years after you wrote your code, having no real knowledge of the type (just the call signature). - You can invoke across threads (similar to C# invoking to jump threads).
- You can reuse pieces of existing
Q_Widgets
by reparenting the pieces and edit the object tree, even on future versions of widgets if the names are the same. - You can adapt your code's behavior for what the future size, shape, and object tree of some other
QWidget
that hasn't even been written yet. - You can link many conceptually similar objects into the same behavior even though they derive from different base classes (all
checkedChanged
calls and valueChanged
calls can be detected by function signature and rewired to handlers that do the same or similar things).
Using the Code
Connect & Sender
Suppose you have a UI file that defined a large number of UI elements, and you got the layout perfect and they all display a lot of data, but, programmatically you want to link up all the signals and slots.
Typically, that would mean dozens or more lines looking like:
connect(mpUi->mpCheckBox1, SIGNAL(stateChanged(int)), SLOT(checkBox1_OnStateChanged(int)));
connect(mpUi->mpCheckBox1, SIGNAL(stateChanged(int)), SLOT(checkBox2_OnStateChanged(int)));
...
connect(mpUi->mpSpinBox1, SIGNAL(valueChanged(int)), SLOT(spinBox1_OnValueChanged(int)));
...
connect(mpUi->mpDSpinBox1, SIGNAL(valueChanged(double)), SLOT(dspinBox1_OnValueChanged(double)));
...
Normally, of course, the names would reflect meaningful information about what the checkbox or spinbox or what ever is used for, but it's just an example.
The point is, if you had 20 checkboxes, 20 spinboxes, 20 double spin boxes, you'd have 60 connects, AND 60 function handlers. That is a lot of typing.
Suppose you fold down by type, AND folded the SLOTS down, so there are 9 lines of code doing the connects (3 per type) and only one slot per type.
QList<QCheckBox *> checkBoxes = source->findChildren<QCheckBox *>("");
foreach (QCheckBox *cb, checkBoxes)
connect(cb, SIGNAL(stateChanged(int)), SLOT(onCheckBoxStateChanged(int)));
This maps ALL checkboxes to the same slot. Inside the slot, we can recover the pointer to the sender:
void myClass::onCheckBoxStateChanged(int) {
QCheckBox * whoSentThisSignal = sender();
if (whoSentThisSignal == NULL) { return; }
}
We could have mapped some of the checkboxes to this slot or that slot based on a regular expression on the name.
Type / Signature Discovery
Suppose the application is a bit more advanced. Suppose I'm writing part of it, a broker of the data between a cloud of data and the UI, and I'm not sure what the exact types are going to be. That's still ok and I can still link into them.
QList<QWidget *> widgets = source->findChildren<QWidget *>("");
foreach (QWidget *w, widgets)
{
const QMetaObject *p = w->metaObject();
if (p->indexOfMethod(QMetaObject::normalizedSignature("stateChanged(int)")) != -1)
{
connect(w, SIGNAL(stateChanged(int)), SLOT(onCheckBoxStateChanged(int)));
}
if (p->indexOfMethod(QMetaObject::normalizedSignature("valueChanged(int)")) != -1)
{
connect(w, SIGNAL(valueChanged(int)), SLOT(onValueChanged(int)));
}
}
In this case, my code only knows and relies on the code signature. Connect isn't looking for an entry in a vtable
, it is linking the text generated by the SIGNAL
and SLOT
macros to match against the string
table generated by the Qt moc tool. This means all the binding is NOT type safe or even type aware and it is all done at run time.
Invoking
I often asks people to "Explain Qt Threading" when I interview them. The reality is there are dozens of articles each proclaiming the "best" or "proper" way of doing threading, and they are really advocating style. The problem is Qt threading is kinda left to the developer to do and it's flexible and thus confusing. I'll briefly try to explain the how and provide a quick trick to make life easy.
In Qt, objects are not thread safe (unless you spun your own) and they all run on the main Qt UI thread by default. All timers, everything is all in the same thread by default. When you try to use more threads, you immediately have run time problems because the worker thread isn't the same thread as the UI and you can't call the UI objects directly. This is the same problem with the .NET framework and it has a similar solution (I'll get to that).
Suppose you have two worker threads, "A
" and "B
". In Qt, QThreads
are QObjects
and you "move" other QObjects
onto that thread. That means "A
" owns some objects and "B
" owns some objects. It also means that all the UI objects can't be called by the worker threads (crash) but other objects on "A
" and "B
" can call each other (but asynchronously so you'd typically need mutexes).
The general Qt paradigm is to "move" an object onto a worker thread and then create a signal / slot connection so that objects in worker thread "A
" signal the UI objects and vice versa. Qt detects that they are owned by different threads and queues the connection so that when the signal occurs, it is converted to text, put on the application event queue and then when the other thread awakes, it gets the string
off the event queue, converts it by matching the text into a set of args and call signature, finds (text match) the object and the function pointer and makes the call.
This is slow, but what Qt requires. You can add your own threadsafe calls by adding a mutex but with anything related to the UI, you at some point must change things inside Qt to effect painting and that will typically mean a queued connection (somewhere).
It also means you typically need to subclass some things in Qt, add extra slots, and add signals and connections through out your code.
Or, you can do this:
QTimer::singleShot(0, mpSpinBox, SLOT(functionName()));
The QTimer::singleShot
behind the scenes takes the pointer, and the text generated by the SLOT macro, puts it on the application event queue (with a delay of zero) and calls the function. Which is awesome if the function has no arguments.
Or, if it has arguments:
QMetaObject::invokeMethod(mpSpinBox, "setValue", Qt::QueuedConnection, Q_ARG(int, mValue));
This posts a request to eventually do mpSpinBox->setValue(mValue);
If I wanted to block this thread till done, I could use Qt::BlockingQueuedConnection
.
This works on anything that's a slot (Q_INVOKABLE
, Q_SLOT
, Q_PROPERTY
).
Behind the scenes, it creates a QMetaCallEvent
and posts that as an event to the mpSpinBox
via the application event pump that then gets serviced by the correct thread.
Which is akin to C# BeginInvoke
which would look like:
mpSpinBox.BeginInvoke( delegate { this.setValue(mValue); });
or:
mpSpinBox.BeginInvoke( () => { mpSpinBox.setValue(mValue); });
or for blocking:
mpSpinBox.Invoke( () => { mpSpinBox.setValue(mValue); });
Invoke required, but in Qt
Another good trick is making functions that are not thread safe, or have parent/child thread issues work across threads and even scripted things.
class className : public QObject {
...
public:
Q_INVOKABLE void crossThreadSafe(int delay);
protected:
void _InternalImplimentation(int delay);
...
QTimer myTimer;
};
Then in the body, we check that the thead we're being called on is the thread we need to be on and either directly call the worker function or invoke it. Note that it can't be Qt::BlockingQueuedConnection because Qt will assume it's a dead lock (even though it isn't) and not make the call. If _InternalImplimentation(delay) returned something we'd need to spin wait for the result instead of doing a blocking connection.
void className::crossThreadSafe(int delay) {
if (QThread::currentThread() == this->thread())
{
_InternalImplimentation(delay);
}
else
{
QMetaObject::invokeMethod(
this,
"_InternalImplimentation",
Qt::QueuedConnection,
Q_ARG(int, delay));
QThread::yieldCurrentThread();
}
}
void className::_InternalImplimentation(intdelay) {
myTimer.start(delay);
}
Another way that is much crisper, but ... a little harder for people new to Qt to read is:
void className::crossThreadSafe(int delay) {
if (QThread::currentThread() != this->thread())
{
QMetaObject::invokeMethod(this, "crossThreadSafe",
Qt::QueuedConnection, Q_ARG(int, delay));
QThread::yieldCurrentThread();
return;
}
myTimer.start(delay);
}
Done this way, the function calls itself but on the correct thread (if it's the wrong thread) otherwise it does the work. This style is a bit more common in C# applications but it's conceptually the same as checking if invokeRequired is true in C#.
In either case the developer must be aware that the call may activate almost imediately or ... when ever the OS eventually (msec typically, seconds extremely rarely) gets around to it. Techniques using mutex tryLock() etc. can be usefull if multiple things might try to start the same operation. They try to lock, and if fail it's already been kicked off, otherwise they gain the lock and are the initiator, then when the code actually completes the lock is released.
QMutex mMutex(true);
void className::crossThreadSafe(int delay)
{
if (QThread::currentThread() != this->thread())
{
QMetaObject::invokeMethod(this, "crossThreadSafe",
Qt::QueuedConnection, Q_ARG(int, delay));
while (mMutex.tryLock() == false)
{
QThread::msleep(100);
}
mMutex.unlock();
}
else
{
myTimer.start(delay);
mMutex.unlock();
}
}
The above does two things:
- Many threads can call crossThreadSafe simultaneously, and those events are coalessed into (typically) one call to myTimer.start(delay);
- All calls that are simultaneous block until the action completes.
Reparenting
Suppose you have a legacy control and it has a banner with the old company logo on it, and a border on the bottom and you want to reuse it. You could wrap the control and hide the banner and the border but that may not work if the control has private
functions that monitor visibility. You can recycle the control at run time instead.
First, a little helper function:
QString uniqueName(QObject * w, int depth = 20)
{
if (depth < 0) return "";
if (w == NULL) return "";
return uniqueName(w->parent(), depth - 1) + "/" + w->objectName();
}
Now I create a widget, find a button called mpCommandButton
BUT I make sure it's the one I want by checking its "unique" name.
QWidget *source = new oldWidgetToRecycle(this);
QList<QWidget *> possibleParts = source->findChildren<QWidget *>("mpCommandButton");
foreach (QWidget *w, possibleParts)
{
if (uniqueName(w) ==
"/MainWindow/swapOut/SourceWidget/frame/frame_2/mpCommandButton")
{
mpUi->mpDestination->addWidget(w);
break;
}
}
At this point, this may sound like a very limited technique, but it has some nice applications:
- You can edit existing Qt controls and modify them without subclassing.
- You can edit old controls and adjust layout styling.
- You can combine two old controls interlacing their layouts for a new experience.
- You can make old controls that normally layout short and wide to be tall and thin if the window resizes.
- You can make the application layout editable by the user (reparent movable things to panels, save panel ID with
uniqueName
, size
, location
).
History
- 29th June, 2016: Initial version