Teague Teague - 3 months ago 11
C++ Question

How are Qt signals/slots actually coupled to the elements in a .ui file?

I'm currently working on a Qt project and am somewhat confused with the signals and slots mechanism. I feel however that I have a somewhat good understanding between the differences between a QObject and user interface form.

A user interface form (described by a .ui file) is fed into the user interface compiler(uic) and produces an associated header file. This header file doesn't just contain interface information, but instead contains the implementation details on a QObject should be formatted. A QObject on the other hand is the base class in which a lot of the Qt framework is built upon. The signals and slot systems is based completely on the QObject.

When extending the QObject class (or from a derived class), you are actually defining an object in which can produce signals and slots. To format this object to look like the user interface you just designed in Qt Designer, you create an instance of the ui class (via the ui header generated by the uic). You call the setup method on this class and feed it the new QObject you're creating.

This all seems to make sense.

However, what doesn't make sense is when you start actually defining signals. From with Qt Designer, I have the option to right click and select a 'signal'. When I select a signal (whether that be a mouse click on a button or a change in a progress bar), it ends up adding that to my QObjects signals section. My question is: why and how? How exactly are my actions in Qt Designer (a program that generates an XML for the uic) getting coupled to my QObject in Qt Creator? More specifically, how does it know which QObject to add this slot to?

This question may be somewhat unclear, so I'll throw an example here.

Say for example I want to create a new interactive display of some kind. Let's call it 'MyScreen'. In order to create this interface, I would likely have 3 files: 'myscreen.h', 'myscreen.cpp', and 'myscreen.ui'. 'myscreen.h' is responsible for declaring the QObjects properties and methods as well as signals and slots. 'myscreen.cpp' is responsible for defining the methods, signals, and slots. 'myscreen.ui' is repsonible for creating the user interface layout that will be used to format an instance of MyScreen.

But due to what I had stated previously saying that the ui is just used for generating a header, why exactly is it coupled to 'myscreen.cpp'? That is, I can right click on the .ui layout, create a new signal type, and that signal type will be added to the myscreen.h and myscreen.cpp. How does this happen? How is it coupled? Does Qt operate such that there should always be 3 files (xxx.h, xxx.cpp, xxx.ui) that should exist?

So hopefully that gives you some context in what I'm confused about. There doesn't seem to be a well written document (that I've found at least), that thoroughly describes the underlying relationship between all these items.

TLDR - How does the signal/slot from the .h and .cpp actually link to the elements defined in the .ui file?

Answer

My question is: why and how? How exactly are my actions in Qt Designer (a program that generates an XML for the uic) getting coupled to my QObject in Qt Creator? More specifically, how does it know which QObject to add this slot to?

The short answer is: magic.

The real answer is, it actually has nothing to do with your actions in Designer, at least not directly. It's not actually even a UI-specific thing, it's just that designer makes good use of it. What happens is this:

First of all, any time you compile a file that makes use of the Q_OBJECT macro, this triggers Qt's moc tool to run during compilation ("triggers" isn't really the most accurate description: More precisely, when qmake is run to generate makefiles, it adds build rules to run moc when Q_OBJECT is detected in a header, but that's beside the point). Details are a bit outside the scope of this answer but long story short is it ultimately generates those moc_*.cpp files you'll see after compilation, which contain a whole bunch of compiled meta info. The important part of this is where it generates run-time accessible information about the various signals and slots you have declared. You'll see how that's important here below.

The other important thing is all QObjects have a name. This name is accessible at runtime. The name you give your widgets in designer is set as the widget's object name. The reason this is important will also become clear below.

And the final important thing is QObjects have a hierarchical structure; when you create a QObject you can set its parent. All of the widgets on your form ultimately have the form itself (e.g. your QMainWindow-derived class) as their parent.

Ok, so...

You'll notice in source code Qt generates for your windows you always have that ui->setupUi(this) call in the constructor. The implementation of that function is generated by Qt's uic tool during compilation, in e.g. ui_mainwindow.h. That function is where all the properties you set up in designer, stored in the .ui file, are actually set, including the widgets' object names. Now, the last line of the generated setupUI() implementation is the key:

void setupUi(QMainWindow *MainWindow) {
    ...
    // Object properties, including names, are set here. This is 
    // generated from the .ui file. For example:
    pushButton = new QPushButton(centralWidget);
    pushButton->setObjectName(QString::fromUtf8("pushButton"));
    pushButton->setGeometry(QRect(110, 70, 75, 23));
    ...
    // This is the important part for the signal/slot binding:
    QMetaObject::connectSlotsByName(MainWindow);
}

That function, connectSlotsByName, is where the magic happens for all those widget signals.

Basically that function does the following:

  1. Iterate through all your declared slot names, which it can do because this was all packed into runtime-accessible strings by moc in the meta object info.
  2. When it finds a slot whose name matches the pattern on_<objectname>_<signalname>, it recursively searches the object hierarchy for an object whose name is <objectname>, e.g. one of your named widgets, or whatever other named objects you may have created. It then connects that objects signal, <signalname> to the slot it found (it basically automates calls to QObject::connect(...)).

And that's it.

So what this means is, nothing special actually happens in designer when you go to a slot there. All that happens is designer inserts a slot named on_widgetName_signalName, with the appropriate parameters, into your header and generates an empty function body for it. (thuga has added a great explanation of this). This is enough for QMetaObject::connectSlotsByName to find it at run time and set up the connection.

The implications here are very convenient:

  • You can remove a widget's signal handler without doing anything special in designer. You just remove the slot and its definition from your source file, it's all completely self contained.
  • You can add widget signal handlers by hand without having to go through the designer interface, which can save you some tedium sometimes. All you have to do is create a slot in your window named e.g. on_lineEdit1_returnPressed() and voila, it works. You just have to be careful, as hyde reminds us in a comment: If you make a mistake here you won't get a compile-time error, you will only get a run-time warning printed to stderr and the connection won't be created; so it's important to pay attention to console output if you make any changes here.

The other implication here is that this functionality isn't actually limited to UI components. moc runs for all your QObjects, and connectSlotsByName is always available. So any object hierarchy you create, if you name the objects, then declare slots with appropriate names, you can call connectSlotsByName to automatically set up connections; it just so happens that uic sticks that call at the end of setupUi, because that's a convenient place to put it. But the magic isn't UI-specific, and doesn't rely on uic, only on the meta info. It's a generally available mechanism.

It's pretty neat.