A Very Simple Project
(From https://wiki.qt.io/Qt_for_Beginners)
We shall write two files:
- main.cpp, and
- myFirst, which is transformed by
qmakeinto a Makefile.
The Profile (.pro) File
This is the contents of myFirst.pro:
TEMPLATE = app TARGET = helloWorld SOURCES += main.cpp QT = core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
Building my Application
I resort to building my application on the command line:
clear && qmake -o Makefile myFirst.pro && make helloWorld
as the IDE fails to find whatever.
Then I run my executable:
./helloWorld
Changing the Font
We can also change the font. In Qt, a font is represented with the QFont class. The documentation provides a lot of information. We are especially concerned here with one of the constructors of QFont.
QFont(const QString & family, int pointSize = –1, int weight = -1, bool italic = false)
In order to change the font, we have to instantiate a QFont instance, and pass it to the QPushButton using setFont. The following snippet will change the font to Courier.
QFont font ("Courier");
button.setFont(font);
QObject and the Parenting System
QObject is the most basic class in Qt. Most of classes in Qt inherit from this class. QObject provides some very powerful capabilities like:
- object name: you can set a name, as a string, to an object and search for objects by names.
- parenting system (described in the following subsection)
- signals and slots (described in the next section)
- event management
Widgets are able to respond to events and use parenting system and signals and slots mechanism. All widgets inherit from QObject. The most basic widget is the QWidget. QWidget contains most properties that are used to describe a window, or a widget, like position and size, mouse cursor, tooltips, etc.
Note: in Qt, a widget can also be a window. In the previous section, we displayed a button that is a widget, but it appears directly as a window. There is no need for a QWindow
class.
Parenting System
Parenting system is a convenient way of dealing with objects in Qt, especially widgets. Any object that inherits from QObject can have a parent and children. This hierarchy tree makes many things convenient:
- When an object is destroyed, all of its children are destroyed as well. So, calling delete becomes optional in certain cases.
- All
QObjectshavefindChildandfindChildrenmethods that can be used to search for children of a given object. - Child widgets in a
QWidgetautomatically appear inside the parent widget.
The following snippet that creates a QPushButton inside a QPushButton:
#include <QApplication>
#include <QPushButton>
int main(int argc, char **argv)
{
QApplication app (argc, argv);
QPushButton button1 ("parent");
QPushButton button2 ("child", &button1);
button1.show();
return app.exec();
}
Lastly, we shall place a button inside a window (a generic QWidget) and will place and resize the containing window:
QWidget window;
window.setFixedSize(100, 50);
QPushButton *button = new QPushButton("Hello World", &window);
button->setGeometry(10, 10, 80, 30);
window.show();
Subclassing QWidget
Until now, we have put all of our code in the main function. This was not a problem for our simple examples, but for more and more complex applications we might want to split our code into different classes. What is often done is to create a class that is used to display a window, and implement all the widgets that are contained in this window as attributes of this class.
Two useful members in QWidget:
void QWidget::setFixedSize(int width, int height);void QWidget::setGeometry(int x, int y, int width, int height);
The header (Window.h)
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
signals:
public slots:
};
#endif // WINDOW_H
The source (Window.cpp)
#include "window.h"
Window::Window(QWidget *parent) :
QWidget(parent) {};
You can see that Qt Creator automatically generates a class template. Notice that there are some new elements in the header:
- The
Q_OBJECTmacro. - A new category of methods : signals
- A new category of methods : public slots
The Observer Pattern: Signals and Slots
Nearly all UI toolkits have a mechanism to detect a user action, and respond to this action. Some of them use callbacks, others use listeners, but basically, all of them are inspired by the observer pattern.
The observer pattern is used when an observable object wants to notify other observer objects about a state change. Here are some concrete examples:
- A user has clicked on a button, and a menu should be displayed.
- A web page just finished loading, and a process should extract some information from this loaded page.
- A user is scrolling through a list of items (in an app store for example), and has reached the end, so more items should be loaded.
The observer pattern is used everywhere in GUI applications, and often leads to some boilerplate code. Qt was created with the idea of removing this boilerplate code and providing a nice clean syntax. The signal and slots mechanism makes this possible.
Signals and Slots
Instead of having observable objects and observers, and registering them, Qt provides two high level concepts: signals and slots.
- A signal is a message that an object can send, usually to report a status change.
- A slot is a function that accepts and responds to a signal.
Here are some examples of signals and slots from our well known QPushButton class:
- clicked
- pressed
- released
As you can see, their names are quite explicit. These signals are sent when the user clicks (presses, then releases), presses or releases the button.
Here are some slots, from different classes:
QApplication::quitQWidget::setEnabledQPushButton::setText
In order to respond to a signal, a slot must be connected to a signal. Qt provides the method QObject::connect. It is used this way, with the two macros SIGNAL and SLOT.
FooObjectA *fooA = new FooObjectA(); FooObjectB *fooB = new FooObjectB(); QObject::connect(fooA, SIGNAL (bared()), fooB, SLOT (baz()));
This example assumes that FooObjectA has a bared signal, and FooObjectB has a baz slot.
You have to write the signature of the signal and the slot inside the two macros SIGNAL and SLOT. If you want more information about what these macros do, please read further down.
Note: Basically, signals and slots are methods, that might or might not have arguments, but that never return anything. While the notion of a signal as a method is unusual, a slot is actually a real method, and can be called as usual by other methods, or while responding to a signal.
Transmitting information
The signals and slots mechanism is useful to respond to buttons clicks, but it can do much more than that. For example, It can also be used to communicate information. Let's say while playing a song, a progress bar is needed to show how much time remains before the song is over. A media player might have a class that is used to check the progress of the media. An instance of this class might periodically send a tick signal, with the progress value. This signal can be connected to a QProgressBar, that can be used to display the progress.
The hypothetical class used to check the progress might have a signal that have this signature:
void MediaProgressManager::tick(int miliseconds);
and we know from the documentation, that the QProgressBar has this slot:
void QProgressBar::setValue(int value);
You can see that the signal and the slot have the same kind of parameters, especially the type. If you connect a signal to a slot that does not share the same kind of parameters, when the connection is done (at run-time) you will get a warning like:
QObject::connect: Incompatible sender/receiver arguments
This is because the signal transmits the information to the slot using the parameters. The first parameter of the signal is passed to the first one of the slot, and the same for second, third, and so forth.
The code for the connection will look like this:
MediaProgressManager *manager = new MediaProgressManager(); QProgressBar *progress = new QProgressBar(window); QObject::connect(manager, SIGNAL (tick(int)), progress, SLOT (setValue(int)));
You can see that you have to provide a signature inside the SIGNAL and SLOT macro, providing the type of the values that are passed through the signals. You may also provide the name of the variable if you want. (It is actually even better).
Features of signals and slots
- A signal can be connected to several slots
- Many signals can be connected to a slot
- A signal can be connected to a signal: it is signal relaying. The second signal is sent if the first signal is sent.
Responding to an Event
Remember our button app? Let's try to actually make something with this app, like being able to close it by clicking the button. We already know that QPushButton provides the clicked signal. We also have to know that QApplication provides the quit slot, which closes the application.
In order to make a click on a button close the app, we must connect the signal clicked emitted by the button to the quit slot of this QApplication instance. We can modify the code from the previous section to do this, but before we do that, you might wonder how to gain access to the QApplication instance while you are in another class. Actually, it is pretty simple, since there exists a static function in QApplication, with the following signature, that is used to get it:
QApplication * QApplication::instance()
This leads to the following modification of our previous code (window.cpp), in the constructor for user-defined Window:
#include "window.h"
#include <QApplication>
#include <QPushButton>
Window::Window(QWidget *parent) :
QWidget(parent)
{
// Set size of the window:
setFixedSize(100, 50);
// Create and position the button:
m_button = new QPushButton("Quit", this);
m_button->setGeometry(10, 10, 80, 30);
// NEW : Make the connection:
connect(m_button, SIGNAL (clicked()), QApplication::instance(), SLOT (quit()));
}
Transmitting information with signals and slots
Here is a simpler example for information transmission. It only displays a progress bar and a slider (created by QSlider) inside a window, and while the slider is moved, the value of the progress bar is synced with a very simple connection.
The interesting signals and slots are:
void QSlider::valueChanged(int value); void QProgressBar::setValue(int value);
QSlider automatically emits the signal valueChanged with the new value passed as a parameter when the value is changed, and the method setValue of QProgressBar is used, as we have seen, to set the value of the progress bar.
This leads to the following code:
#include <QApplication>
#include <QProgressBar>
#include <QSlider>
int main(int argc, char **argv)
{
QApplication app (argc, argv);
// Create a container window
QWidget window;
window.setFixedSize(200, 80);
// Create a progress bar
// with the range between 0 and 100, and a starting value of 0
QProgressBar *progressBar = new QProgressBar(&window);
progressBar->setRange(0, 100);
progressBar->setValue(0);
progressBar->setGeometry(10, 10, 180, 30);
// Create a horizontal slider
// with the range between 0 and 100, and a starting value of 0
QSlider *slider = new QSlider(&window);
slider->setOrientation(Qt::Horizontal);
slider->setRange(0, 100);
slider->setValue(0);
slider->setGeometry(10, 40, 180, 30);
window.show();
// Connection
// This connection set the value of the progress bar
// while the slider's value changes
QObject::connect(slider, SIGNAL (valueChanged(int)), progressBar, SLOT (setValue(int)));
return app.exec();
}
Creating Signals and Slots with the Framework's Macros
The most important macro is Q_OBJECT. Signal-Slot connections and their syntax cannot be interpreted by a regular C++ compiler. The moc is provided to translate the QT syntax like "connect", "signals", "slots", etc into regular C++ syntax. This is done by specifying the Q_OBJECT macro in the header containing class definitions that use such syntax.
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = 0);
}
Other marker macros for moc are:
- signals
- public / protected / private slots
which mark the different methods that need to be extended.
SIGNAL and SLOT are also two very important and useful macros. When a signal is emitted, the meta-object system is used to compare the signature of the signal, to check the connection, and to find the slot using it's signature. These macros are actually used to convert the provided method signature into a string that matches the one stored in the meta-object.
Creating custom slots
In order to implement a slot, we first need to make the class be able to send signals and have slots (as shown before). This is done by setting the Q_OBJECT macro in the class declaration (often in the header).
After that, a slot should be declared in the corresponding section, and implemented as a normal method.
Finally, slots are connected to signals.
Creating signals
As for slots, we first need to add the Q_OBJECT macro. Signals should then be declared in the signals section, and there is no need for them to be implemented.
Signals are issued using the emit keyword:
emit mySignal();
Note that in order to send signals that have parameters, you have to pass them in the signal emission:
emit mySignal(firstParameter, secondParameter...);
We'll walk through an example.
First, we'll create custom slots. We declare our window with a button in window.h:
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
class QPushButton;
class Window : public QWidget
{
public:
explicit Window(QWidget *parent = 0);
private:
QPushButton *m_button;
};
#endif
We implement the Window constructor in a separate file (Window.cpp):
#include "window.h"
#include <QPushButton>
Window::Window(QWidget *parent) :
QWidget(parent)
{
// Set size of the window
setFixedSize(100, 50);
// Create and position the button
m_button = new QPushButton("Hello World", this);
m_button->setGeometry(10, 10, 80, 30);
}
(We might want to remove our previous connection that makes the application quit while clicking the button.) Now, we want that, when clicking on the button, the text is changed. More precisely, we want that the button can be checked, and that, when checked, it displays "checked", and when unchecked, it restores "Hello World".
QPushButton does not implement such a specific slot, so we have to implement it on our own. As stated previously, we have to add the Q_OBJECT macro.
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
private:
QPushButton *m_button;
};