Interactive Qt applications with GUI have a different control flow from console applications and filter applications[2] because they are event-based, and often multithreaded. Objects are frequently sending messages to each other, making a linear hand-trace through the code rather difficult.
[2] A filter application is not interactive. It simply reads from standard input and writes to standard output.
The Qt class QEvent encapsulates the notion of an event. QEvent is the base class for several specific event classes such as QActionEvent, QFileOpenEvent, QHoverEvent, QInputEvent, QMouseEvent, and so forth. QEvent objects can be created by the window system in response to actions of the user (e.g., QMouseEvent) at specified time intervals (QTimerEvent) or explicitly by an application program. The type() member function returns an enum that has nearly a hundred specific values that can identify the particular kind of event.
A typical Qt program creates objects, connects them, and then tells the application to exec(). At that point, the objects can send information to each other in a variety of ways. QWidgets send QEvents to other QObjects in response to user actions such as mouse clicks and keyboard events. A widget can also respond to events from the window manager such as repaints, resizes, or close events. Furthermore, QObjects can transmit information to one another by means of signals and slots.
Each QWidget can be specialized to handle keyboard and mouse events in its own way. Some widgets will emit a signal in response to receiving an event.
An event loop is a program structure that permits events to be prioritized, enqueued, and dispatched to objects. Writing an event-based application means implementing a passive interface of functions that only get called in response to certain events. The event loop generally continues running until a terminating event occurs (e.g., the user clicks on the QUIT button).
Example 9.5 shows a simple application that initiates the event loop by calling exec().
Example 9.5. src/eventloop/main.cpp
[ . . . . ] int main(int argc, char * argv[]) { QApplication myapp(argc, argv); <-- 1 QWidget rootWidget; setGui(&rootWidget); rootWidget.show(); <-- 2 return myapp.exec(); <-- 3 };
|
When we run this app, we first see a widget on the screen as shown in the following figure.
We can type in the QTextEdit on the screen, or click on the Shout button. When Shout is clicked, a widget is superimposed on our original widget as shown in the next figure.
This message dialog knows how to self-destruct, because it has its own buttons and actions.
9.3.1. Layouts: A First Look
Whenever more than a single widget needs to be displayed, they must be arranged in some form of a layout (see Section 11.5). Layouts are derived from the abstract base class, QLayout, which is derived from QObject. Layouts are geometry managers that fit into the composition hierarchy of a graphical interface. Typically, we start with a widget that will contain all of the parts of our graphical construction. We select one or more suitable layouts to be children of our main widget (or of one another) and then we add widgets to the layouts.
It is important to understand that widgets in a layout are not children of the layoutthey are children of the widget that owns the layout. Only a widget can be the parent of another widget. It may be useful to think of the layout as an older sibling acting as the nanny of its widgets. |
In Example 9.6, we are laying out widgets in a vertical fashion with QVBoxLayout.
Example 9.6. src/eventloop/main.cpp
[ . . . . ] QWidget* setGui(QWidget *box) { QLayout* layout = new QVBoxLayout; box->setLayout(layout); <-- 1 QTextEdit *te = new QTextEdit; <-- 2 layout->addWidget(te); <-- 3 te->setHtml("Some text in the QTextEdit" "edit window please?"); QPushButton *quitButton=new QPushButton("Quit"); layout->addWidget(quitButton); QPushButton *shoutButton = new QPushButton("Shout"); layout->addWidget(shoutButton); Messager *msgr = new Messager("This dialog will self- destruct.", box); QObject::connect(quitButton, SIGNAL(clicked()), qApp, SLOT(quit())); <-- 4 qApp->connect(shoutButton, SIGNAL(clicked()), msgr, SLOT(shout())); return box; }
|
The widgets are arranged vertically in this layout, from top to bottom, in the order that they were added to the layout.
9.3.2. Connecting to Slots
In Example 9.7, we saw the following connections established:
QObject::connect(quitButton, SIGNAL(clicked()), qApp, SLOT(quit())); qApp->connect(shoutButton, SIGNAL(clicked()), msgr, SLOT(shout()));
connect() is actually a static member of QObject and can be called with any QObject or, as we showed, by means of its class scope resolution operator. qApp is a global pointer that points to the currently running QApplication.
The second connect goes to a slot that we declare in Example 9.7.
Example 9.7. src/eventloop/messager.h
#ifndef MESSAGER_H #define MESSAGER_H #include #include #include class Messager : public QObject { Q_OBJECT public: Messager (QString msg, QWidget* parent=0); public slots: void shout(); private: QWidget* m_Parent; QErrorMessage* message; }; #endif |
Declaring a member function to be a slot enables it to be connected to a signal so that it can be called passively in response to some event. For its definition, shown in Example 9.8, we have kept things quite simple: the shout() function simply pops up a message box on the screen.
Example 9.8. src/eventloop/messager.cpp
#include "messager.h" Messager::Messager(QString msg, QWidget* parent) : m_Parent(parent) { message = new QErrorMessage(parent); setObjectName(msg); } void Messager::shout() { message->showMessage(objectName()); } |
9.3.3. Signals and Slots
When the main thread of a C++ program calls qApp->exec(), it enters into an event loop, where messages are handled and dispatched. While qApp is executing its event loop, it is possible for QObjects to send messages to one another.
A signal is a message that is presented in a class definition like a void function declaration. It has a parameter list but no function body. A signal is part of the interface of a class. It looks like a function but it cannot be calledit must be emitted by an object of that class. A signal is implicitly protected, and so are all the identifiers that follow it in the class definition until another access specifier appears.
A slot is a void member function. It can be called as a normal member function.
A signal of one object can be connected to the slots of one or more[3] other objects, provided the objects exist and the parameter lists are assignment compatible[4] from the signal to the slot. The syntax of the connect statement is:
[3] Multiple signals can be connected to the same slot also.
[4] Same number of parameters, each one being assignment compatible.
bool QObject::connect(senderqobjptr, SIGNAL(signalname(argtypelist)), receiverqobjptr, SLOT(slotname(argtypelist)) optionalConnectionType);
Any QObject that has a signal can emit that signal. This will result in an indirect call to all connected slots.
QWidgets already emit signals in response to events, so you only need to make the proper connections to receive those signals. Arguments passed in the emit statement are accessible as parameters in the slot function, similar to a function call, except that the call is indirect. The argument list is a way to transmit information from one object to another.
Example 9.9 defines a class that uses signals and slots to transmit a single int parameter.
Example 9.9. src/widgets/sliderlcd/sliderlcd.h
[ . . . . ] class QSlider; class QLCDNumber; class LogWindow; class QErrorMessage; class SliderLCD : public QMainWindow { Q_OBJECT public: SliderLCD(int minval = -273, int maxval = 360); void initSliderLCD(); public slots: void checkValue(int newValue); void showMessage(); signals: void toomuch(); private: int m_Minval, m_Maxval; LogWindow* m_LogWin; QErrorMessage *m_ErrorMessage; QLCDNumber* m_LCD; QSlider* m_Slider; }; #endif [ . . . . ] |
In Example 9.10, we can see how the widgets are initially created and connected.
Example 9.10. src/widgets/sliderlcd/sliderlcd.cpp
[ . . . . ] SliderLCD::SliderLCD(int min, int max) : m_Minval(min), m_Maxval(max) { initSliderLCD(); } void SliderLCD::initSliderLCD() { m_LogWin = new LogWindow(); <-- 1 QDockWidget *logDock = new QDockWidget("Debug Log"); logDock->setWidget(m_LogWin); logDock->setFeatures(0); <-- 2 setCentralWidget(logDock); m_LCD = new QLCDNumber(); m_LCD->setSegmentStyle(QLCDNumber::Filled); QDockWidget *lcdDock = new QDockWidget("LCD"); lcdDock->setFeatures(QDockWidget::DockWidgetClosable); <-- 3 lcdDock->setWidget(m_LCD); addDockWidget(Qt::LeftDockWidgetArea, lcdDock); m_Slider = new QSlider( Qt::Horizontal); QDockWidget* sliderDock = new QDockWidget("How cold is it today?"); sliderDock->setWidget(m_Slider); sliderDock->setFeatures(QDockWidget::DockWidgetMovable); /* Can be moved between doc areas */ addDockWidget(Qt::BottomDockWidgetArea, sliderDock); m_Slider->setRange(m_Minval, m_Maxval); m_Slider->setValue(0); m_Slider->setFocusPolicy(Qt::StrongFocus); m_Slider->setSingleStep(1); <-- 4 m_Slider->setPageStep(20); <-- 5 m_Slider->setFocus(); <-- 6 connect(m_Slider, SIGNAL(valueChanged(int)), /*SliderLCD is a QObject so connect does not need scope resolution. */ this, SLOT(checkValue(int))); connect(m_Slider, SIGNAL(valueChanged(int)), m_LCD, SLOT(display(int))); connect(this, SIGNAL(toomuch()), this, SLOT(showMessage())); <-- 7 m_ErrorMessage = NULL; }
|
Only the argument types belong in the connect statement; for example, the following is not legal:
connect( button, SIGNAL(valueChanged(int)), lcd, SLOT(setValue(3)))
Example 9.11 defines the two slots, one of which conditionally emits another signal.
Example 9.11. src/widgets/sliderlcd/sliderlcd.cpp
[ . . . . ] void SliderLCD::checkValue(int newValue) { if (newValue> 120) { emit toomuch(); <-- 1 } } /* This slot is called indirectly via emit because of the connect */ void SliderLCD::showMessage() { if (m_ErrorMessage == NULL) { m_ErrorMessage = new QErrorMessage(this); } if (!m_ErrorMessage->isVisible()) { QString message("Too hot outside! Stay in. "); m_ErrorMessage->showMessage(message); <-- 2 } }
|
Example 9.12 contains client code to test this class.
Example 9.12. src/widgets/sliderlcd/sliderlcd-demo.cpp
#include "sliderlcd.h" #include #include int main(int argc, char ** argv) { QApplication app(argc, argv); SliderLCD slcd; slcd.show(); qDebug() << QString("This is a debug message."); return app.exec(); } |
Whenever the slider produces a new value, that value is transmitted as an argument from the valueChanged(int) signal to the display(int) slot of the lcd.
Exercises: Signals and Slots
1. |
Modify the sliderlcd program as follows:
|
2. |
Write an application, similar to the one in Section 9.3, but that has four buttons. The first one, labeled Advice, should be connected to a slot that randomly selects a piece of text (such as a fortune cookie) and displays it in the QTextEdit window. The second one, labeled Weather, randomly selects a sentence about the weather and displays it in the QTextEdit window. The third one, labeled Next Meeting, pops up a message dialog with a randomly generated (fictitious) meeting time and descriptive message in it. The fourth one, labeled Quit, terminates the program. Use signals and slots to connect the button clicks with the appropriate functions. |
Part I: Introduction to C++ and Qt 4
C++ Introduction
Classes
Introduction to Qt
Lists
Functions
Inheritance and Polymorphism
Part II: Higher-Level Programming
Libraries
Introduction to Design Patterns
QObject
Generics and Containers
Qt GUI Widgets
Concurrency
Validation and Regular Expressions
Parsing XML
Meta Objects, Properties, and Reflective Programming
More Design Patterns
Models and Views
Qt SQL Classes
Part III: C++ Language Reference
Types and Expressions
Scope and Storage Class
Statements and Control Structures
Memory Access
Chapter Summary
Inheritance in Detail
Miscellaneous Topics
Part IV: Programming Assignments
MP3 Jukebox Assignments
Part V: Appendices
MP3 Jukebox Assignments
Bibliography
MP3 Jukebox Assignments