In this lecture we will try to cover the theoretical aspects of the QObject class, which makes Qt what Qt is. Understanding this is key to the rest of the lectures. The first half of the lecture will be devoted to the QObject class, meta-information and the possibilities given by these classes. The second half of the lecture will be devoted to signals and slots which is the single most important concept of Qt. Recommended reading before the lecture: http://doc.trolltech.com/4.6/object.html
One of the key factors of Qt is the signals and slots mechanism. It is a mechanism to dynamically and loosely tie together events and changes with reactions Dynamically = at run-time Loosely = sender and receiver do not know each other events = timer event, clicks, etc. Not to be confused with actual events (QEvent). state changes = size changed, text changed, value changed, etc reactions = source code that actually does something This is what makes a Qt application tick Continues
Looking at an example from the first lecture. The three buttons all emit the clicked signal when they are clicked by the user (or activated by other means). They do this regardless whether something is connected to them or not, and regardless of what is connected to them. Continues
We choose to connect the buttons to the list widget's clear slot, and two custom slots in our custom code. As said earlier, the buttons do not care what they are connected to, and the slots are equally independent of what triggers them. You can even call them programatically as ordinary functions. Continues
So, the add button emits clicked, ending up in the on_addButton_clicked slot resulting in the following code being run. It simply asks for input and then adds it to the list. The delete button emits clicked, ending up in the on_deleteButton_clicked slot, where all selected items are deleted. The clear button emits clicked, which ends up in the clear slot of the QListWidget, which to us is a black box that does what its documentation says.
Signals and slots implement a pattern that is quite common. Usually, the mechanism is implemented as callback functions. It is important to recognize that signals/slots not are callbacks. For instance, a callback is simply a pointer. There is not actual check of signature compatibility. They also always work as direct calls, making them tricky to use across threads, etc. Signals and slots are more dynamic. For instance, the mechanism is 100% generic, so any QObject can be connected to any other QObject. The sender and receiver do not have to know of each others' implementations. Signature compatibility checks provide safety, but also flexibility, as they allow you to ignore arguments. All in all, signals and slots are easier, safer and more flexible. (for the advanced: compare to functors, which are clumsier from an implementation point and less flexible when it comes to skipping arguments, etc). Continues
A slot is an ordinary function, just that it can be connected to signals. They do not have to be connected, you can call a slot like any other function, and you implement it as usual. Slots are declared in one of the sections public, protected and private slots. These access restrictions work as intended when calling the function, but a private or protected slot can be connected to any other signal, so they can be triggered from outside the class. Slots can return values, but connections cannot carry return arguments. Any number of signals can be connected to a single slot. This means that a single slot can serve several sources of events – think keyboard shortcut, button, etc. Continues
Signals re defined in the signals section. This section can be considered protected, as a signal can only be emitted from within a class or its decendants. Signals always return void, and must not be implemented. Instead, moc provides function bodies that trigger the actual slot-activation-code. A signal can be connected to any number of slots, so a single event can trigger multiple reactions. It is fully possible to connect signals and slots across threads. Third party libraries such as Qxt ( http://doc.libqxt.org/0.5.0/classQxtRPCPeer.html ). Inside a signal emitting class, you use the emit keyword to emit a signal. Continues
You can make signals to slots connections between any two QObjects. Qt verifies that the signatures of the signal and slot match. The signature consists of the name of the signal or slot followed by the argument types . There must be no values nor variable names in the signature. It is also recommended to stick to using standard types, e.g. the ItemClass custom type reduces the reusability and should thus be avoided. Continues
When matching signatures, Qt is very forgiving. The basic rule is that Qt cannot create or convert values, but apart from that anything is allowed (i.e. skipping arguments). The examples on the slide demonstrate this. The errors are (from the top): missing the last int (cannot create) QString does not match int (cannot convert) missing the only int (cannot create) Continues
When making connections from Designer to your own source, Qt uses the automatic connection mechanism. It lets signals automatically connect to slots with the corresponding name (structure and examples on the slide). The automatic connections are made when connectSlotsByName is called. That is done at the end of the setupUi function generated by Designer. When using this mechanism, think about reusability. Sometimes handwriting a couple of connect statements can greatly improve readability of the code. Continues
A common scenario for signals and slots is to synchronize a pair of widgets. This is implemented by interconnecting two objects. (Refer to the example). If the value of dial1 changes, it emits valueChanged, which changes the value of dial2, that emits valueChanged, which changes the value of dial1, that emits... To avoid an infinite loop (which results in endless recursion, which will make the stack grow until you run out of memory and then crash miserably) the setValue function ignores attempts to set the current value.
Adding custom signals and slots to a class is really quite easy. You can add slots for numerous reasons. For instance, for each and every possible user action (file open, save, close, copy, cut, paste, help, about). Setter functions also make natural slots. Adding slots is just a matter of putting your functions in the right section of the declaration. Adding signals is just as easy, simply declare them in the signals section. If you have properties, it is convention to inform Qt of signals using the Q_PROPERTY macro. Continues
Implementing slots is just as implementing common methods. However, you must not forget the infinite loop protection. Emitting signals is as easy as calling emit signalName(arguments). When emitting signals, make sure to update the internal state first, so that your object is updated before it is queried.
The QObject class is essential to Qt It is the base class of most “active” classes in Qt, including all widgets (the image shows only a subset of the classes derived from QObject, it also shows that those subclasses are themselves inherited by other classes) The QObject implements many of the features that makes Qt what it is, such as: signals slots properties simplified memory management
The QObject is the base class of many of Qt's classes, but there are some exceptions. Graphics view items are not QObjects, as they have been designed to be lean (there can be tens of thousands of them, so every byte is important). QString, QList, QChar, all represent values, and cannot be QObjects because of that (continues)
So, why cannot QChar be a QObject. QObjects are individuals! This means that you cannot write obj1 = obj2, copy is not a valid operation. Why? QObjects have names, for instance addButton, deleteButton, etc (from the first lecture's demo). How do you copy this? You don't want two addButtons? QObjects are structured in hierarchies. How do you copy that context? Do you want them at the same level? Leaf level? Root level? QObjects can be interconnected (add calls a slot) How do you copy this? Do you want to duplicate connections?
QObjects carry meta-data, that is data about the object itself. This makes it possible to add introspection to Qt, for instance to ask a class which methods it has Every QObject has a meta object which can be retreived using the metaObject method. The meta object knows about the class, its name, base class, properties, methods, slots and signals. Continues
While providing C++ developers with meta data, Qt is still based 100% on C++. There is no other language involved. Instead, moc, the meta object compiler, parses the C++ code and generates even more C++ code. The figure shows an ordinary C++ build process, headers are included, sources compiled, object files linked and the end result is executable code. (Even libraries are, more or less, executable code, so this holds true in all cases.) Continues
So, Qt adds a new step to the build process. Moc parses class declarations and generates C++ code that implements the specific meta object. Notice that it is possible to have the moc work on a source file directly, and then include the result into the same source file, but that is only useful in the special case of a QObject derived class only used from within one single file. All this is handled by QtCreator, so you do not need to worry about it. There are solutions taking care of this step for all other build environments as well (command line builds, Visual Studio, Eclipse, Xcode, etc) Continues
What does moc look for in your class declarations? First of all, you need to inherit QObject directly or indirectly. If you are inheriting multiple classes, your QObject (decendant) must appear first. You cannot inherit from QObject twice (directly or indirectly). You then need to put the Q_OBJECT macro in your class declaration, in the private section. By convention, and historical limitations, this is usually placed first. You can then add class info, and more, through special macros. For instance, in this case the key “author” is given the value “John Doe”. Qt also adds some special keywords (just macros, from the compiler's point of view) which will discuss later on.
One commonly used feature is the Qt property system. You have probably used it without noticing if you have looked at the Designer part of QtCreator. There you have a list of properties for each class easily available. Properties are implemented through getter and setter methods, so the actual value is stored as a private member. Convention is that: Getters are named after the property (no get-prefix) or with the is-prefix if a boolean. Setters are named with a set-prefix. The pair of functions is then made into a Qt property using the Q_PROPERTY macro. (arguments: type, name, “READ”, getter, “WRITE”, setter) Continues
Why do you want to use setters instead of public variables? Setters makes it possible to validate settings before you store them. Asserting all input is a good habit and improves code quality. Setters also make it possible to react to changes. For instance, if someone alters the text property of a label, the label makes sure that it is repainted as needed. Continues
Getter methods are also useful, even though not as useful as setters. (reading public variables is somewhat ok) What they do is that they separate the storage of the data from the reading. The read is indirect. Example: size contains both width and height. Width contains the width of the size. Example: a set of flags can be stored in a 32-bit word instead of in separate booleans. Continues
Properties as specified using the Q_PROPERTY macro. What can we read from it? All properties must have a type, a name and be readable. WRITE: They can be written to (optional) RESET: They can be reset (optional), this means to write “no value” to it NOTIFY: A notify signal is sent when the property is changed (more on signals later). An example would be, textChanged, in the previous class (text/setText) DESIGNABLE/SCRIPTABLE: Properties can be made available in designer and through scripts. STORED: Most properties are stored, meaning that they are a part of the object's state. However, some properties take their values from other properties (e.g. width is a part of size) and are not stored. USER: A user property is the property modified by the user, e.g. isChecked of a QCheckBox) CONSTANT: Constant properties do not change for an instance of a class. (i.e. it can change between instances) FINAL: Final properties are not overridden. This is not enforced, but relied upon for performance optimizations. Continues
What does a property look like when using a class? You can use it directly, simply call the getter and setter. You can use the property/setProperty calls. They work with QVariants (a type that contains “all” types), so you have to cast it to strings using toString, etc. You can also ask the meta object about properties. Whether they can be read/written, their name, etc. This can then be used when calling property/setProperty. Continues
Using the property/setProperty approach, you can also create dynamic properties. This makes it possible to tag objects. For instance, to add a “required” boolean property to certain QLineEdits. The line editors do not need to know about this, but they can still keep track of it as a dynamic property. When validating the form, you can check this property and see if they are empty or not. Continues
When adding custom properties, it is good to follow the standard template. This makes it more intuitive to use your code with Qt code. The parts that you need are a getter and a setter. In addition, you need to store the state somewhere (private state). Optionally, you can allow the initialization of common properties (i.e. text of a label, but not window title of it – as it isn't a top level widget most of the time). When all parts are there, you can use the Q_PROPERTY macro. From left to right: type, name, getter, setter. Continues
When implementing the custom properties, you simply implement the functions as always. Ensure that the constructor calls QObject with the parent pointer. In the getter, you could have returned a calculated value. For instance, with width, as a part of the size (shown earlier). In the setter, first update the state, then react to the change. This way, you can use the getter throughout the class. This makes it easier to make changes later on. Continues
QObjects can be used in a way that takes care of all the dirty parts of memory management. It becomes almost as easy as working with a garbage collector, but you are still in full control. The trick is that all QObjects have a parent, or an owner. When the parent is deleted, it deletes all its children. This reduces the number of objects that you need to keep track of to one, in the ideal case. Continues
The very same parent-child relationship is used to represent visual hierarchies. Refer to the tree structure, the box contains the radio buttons (option1/2). The parent contains the box and the button. Compare to the previous slide. Continues
So, how does this make memory management easy. I still need to keep track of an object and make sure that I delete it? No, not if you use the stack cleverly. First of all, the example from the previous slide would probably have been implemented in the parent's constructor, i.e. this is the top-level parent. Second, when using the dialog, you allocate it on the stack. This means that the dialog, along with all its children, will be deleted when the scope ends. Continues
These slides intend to jog the students' memory, not explain the stack vs heap decision in full. The heap is used when you allocate memory dynamically . In C++, that means new/delete. In C you have used malloc and free. Heap memory must be explicitly freed, i.e. you must call delete on everything that you allocate. If you do not do so, you will leak memory. This will, eventually, lead to memory shortage and a crash. Dynamically allocated objects live until you delete them, so you have full control of when something is constructed or destructed. Continues
The stack is used for automatic memory allocations (as opposed to dynamic memory). The stack grows and shrinks when you make function calls. It is used for local variables, function arguments and return addresses. Objects allocated on the stack are destructed when they go out of scope. The scope ends with a }, or return, or for single-line scopes, at the end of the line. Continues
To get almost automatic memory management using Qt, the trick is to allocate the outermost parents on the stack, and the rest on the heap. For instance, the main function scope will be valid for as long as the application is running, so we allocate the application and window on the stack. The window, in turn, creates a bunch of child widgets in its constructor. To avoid destruction when the scope of the constructor ends, they are allocated dynamically. But, they are given the window as their parent object and are thus also destructed when the main function scope ends.
As QObjects all have parents, most QObject-related constructors take a parent pointer. Exceptions: QCoreApplication (QApplication) are QObjects w/o parents The parent usually appears as the left-most argument with a default value. However, as there can be multiple constructors, this can be “compensated”. It is common to use the explicit keyword to avoid unwanted automatic conversion of types. Example: QPushButton, the parent ends up last in all c'tor versions as there are no other default arguments Example: QLabel, the window flags and parent have default values. This means that the parent is the first of them, but after the text as the text property has no default value. Continues
As these are recommended guidelines and making exceptions is always an option. However, following them, makes your code more “Qt-esque”. Allow parent to be null (take that into consideration in your code). Have one constructor taking only parent (lets you use your widgets from Designer). This forces you to handle the case where all properties are set to default values or not set at all. Placing parent in the right place helps reuse. Multiple constructors avoids the need to pass dummy settings to unused properties.
Let's look at a real example that adds slightly more complexity to the situation. We will use two dial – LCD pairs and interconnect them using our custom TempConverter class that converts between Celsius and Fahrenheit. It does not only convert, it monitors and emits signals when changes take place. Continues
The dialog contains a TempConverter object and the user interface. The user interface consists of two halves – one for Celsius and one for Fahrenheit. Each consisting of a QGroupBox. The group boxes each contains a QDial and a QLCDNumber. Continues
The TempConverter class declaration. QObject, parent and Q_OBJECT macro are needed before we can add signals and slots. The setters are slots. There are signals for changes in either temperature. To avoid infinite loops we must have a “current” temperature. In this example we have decided to keep it in celsius. As we use integers throughout, this will not be very accurate, from a temperature conversion point of view. Continues
Looking at the slot implementations, the set temp Celsius slot contains the recursion lock, as the “current” temperature is kept in Celsius. It then updates the internal state and emits both signals. Notice that the argument of the Fahrenheit signal is retrieved from the getter function, which does the actual conversion C to F. The set Fahrenheit slot converts the temperature (F to C) and then uses the set Celsius method. Continues
The window making up the application then contains four widgets (dial + LCD for Celsius and Fahrenheit) and a TempConverter object. The connections between the dials are set up to go through the temperature converter object, while the LCDs are directly driven by the dials. Continues
The next slides will show how a signal propagates through the system. Everything starts with a user event – the celsiusDial is moved. This results in it emitting the valueChanged signal Continues
The valueChanged signal is connected to display of the celsiusLcd and setTempCelsius of the temperature converter. The display call simply changes the value shown in the LCD, while the setTempCelsius call changes the value of the temperature converter. Continues
Changing the temperature of the temp converter results in two signals being emitted: tempCelsiusChanged and tempFahrenheitChanged Continues
The tempCelsiusChanged signal is connected to the celsiusDial's setValue slot. This slot detects that the value of the dial isn't changed – thus halting there. The tempFahrenheitChanged signal connects to setValue of the fahrenheitDial, causing the dial's value to change Continues
As the dial's value changes, it emits the valueChanged signal with the new value Continues
This causes the fahrenheitLcd to be updated (through the display slot) The setTempFahrenheit slot is also called. This slot detects that the temperature isn't changed and stops there. Continues
At this point, all signals have propagated through the system and the TempConverter objects, and all the widgets are again in sync. Notice how the slots take responsibility to stop infinite propagation through the system. Also notice the importance of picking a scale to use for the “current” temperature. A rounding error causing a mismatch between the Celsius and Fahrenheit values could have caused the system to swing forth and back indefinitely.