Click here to Skip to main content
15,886,001 members
Articles / Desktop Programming / QT

Qt meta-object Programming

Rate me:
Please Sign up or sign in to vote.
4.52/5 (10 votes)
14 Jul 2016CPOL8 min read 19.3K   214   13  
Qt Metaobject Programming

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:

C++
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.

C++
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:

C++
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.

C++
QList<QWidget *> widgets = source->findChildren<QWidget *>("");
foreach (QWidget *w, widgets)
{
    const QMetaObject *p = w->metaObject();
    if (p->indexOfMethod(QMetaObject::normalizedSignature("stateChanged(int)")) != -1)
    {   
        // Don't need to know actual type, 
        // It means it has a Q_SLOT or Q_INVOKABLE marked call stateChanged(int) 
        // So I can connect to it even though it may not be derived from QCheckbox! 
        connect(w,  SIGNAL(stateChanged(int)), SLOT(onCheckBoxStateChanged(int))); 
    }
    if (p->indexOfMethod(QMetaObject::normalizedSignature("valueChanged(int)")) != -1)   
    {
        // Don't need to know actual type,
        // It means it has a Q_SLOT or Q_INVOKABLE marked call stateChanged(int)
        // So I can connect to it even though it may not be derived from QCheckbox!
        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:

C++
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:

C++
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:

C#
mpSpinBox.BeginInvoke( delegate { this.setValue(mValue); });

or:

C#
mpSpinBox.BeginInvoke( () => { mpSpinBox.setValue(mValue); });

or for blocking:

C#
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.

C++
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,      // Can't be blocking!
            Q_ARG(int, delay));
        QThread::yieldCurrentThread(); // Make it likely the work is done now.
    }
}

void className::_InternalImplimentation(intdelay) {
    // The below call only works if it is made on the same thread that made this timer.
    //
    // Things like Timers, Com ports, Script environments and most QWidgets have 
    // member functions that either malfunction or make run time checks.
    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(); // Make it likely the work is done now.
        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); // recursive is a must.

void className::crossThreadSafe(int delay) 
{    
    if (QThread::currentThread() != this->thread()) 
    {
        // Wrong thread, post a request.
        QMetaObject::invokeMethod(this, "crossThreadSafe", 
            Qt::QueuedConnection, Q_ARG(int, delay));       
        while (mMutex.tryLock() == false)
        {
            QThread::msleep(100); // Sleep 100 msec until the work is done in a loop.
        }
        // It completed, so release it for the next operation to begin.
        mMutex.unlock(); 
    }
    else
    {
        // Correct thread, do it (still works if recursive).
        myTimer.start(delay);
        mMutex.unlock();
    } 
 }

The above does two things:

  1. Many  threads can call crossThreadSafe simultaneously, and those events are coalessed into (typically) one call to myTimer.start(delay); 
  2. 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:

C#
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.

C++
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")
    {
        // Found it.
        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
Phil is a Principal Software developer focusing on weird yet practical algorithms that run the gamut of embedded and desktop (PID loops, Kalman filters, FFTs, client-server SOAP bindings, ASIC design, communication protocols, game engines, robotics).

In his personal life he is a part time mad scientist, full time dad, and studies small circle jujitsu, plays guitar and piano.

Comments and Discussions

 
-- There are no messages in this forum --