QT 5 Blueprints - Sample Chapter
QT 5 Blueprints - Sample Chapter
ee
P U B L I S H I N G
C o m m u n i t y
$ 49.99 US
32.99 UK
pl
Symeon Huang
Qt 5 Blueprints
Qt 5 Blueprints
Sa
m
D i s t i l l e d
Qt 5 Blueprints
Design, build, and deploy cross-platform GUI projects using the
amazingly powerful Qt 5 framework
E x p e r i e n c e
Symeon Huang
Qt 5 Blueprints
Qt has been developed as a cross-platform framework and has been provided free to the
public for years. It's mainly used to build GUI applications. It also provides thousands of
APIs for easier development.
Qt 5, the latest major version of Qt, has once again proven to be the most popular crossplatform toolkit. With all these platform-independent classes and functions, you only
need to code once, and then you can make it run everywhere.
In addition to the traditional and powerful C++, Qt Quick 2, which is more mature, can
help web developers to develop dynamic and reliable applications, since QML is very
similar to JavaScript.
Building a Beautiful
Cross-platform Clock
In this chapter, you will learn that Qt is a great tool to build cross-platform
applications. A Qt/C++ clock example is used as a demonstration here. The topics
covered in this chapter, which are listed here, are essential for any real-world
applications. These are as follows:
[ 25 ]
Now, change the window title to Fancy Clock or any other name that you like.
Then, the main window UI needs to be tailored because the clock is displayed at the
top of the desktop. The menu bar, status bar, and toolbar are all removed. After that,
we need to drag an LCD Number widget into centralWidget. Next, change the
layout of MainWindow to LayOut Horizontally in order to autoresize the subwidget.
The last thing that needs to be done to the UI file is to change frameShape to
NoFrame under the QFrame column in the property of lcdNumber. If you've done
this right, you'll get a prototype of a digital clock, as shown here:
In order to update the LCD number display repeatedly, we have to make use of the
QTimer class to set up a timer that emits a signal repetitively. In addition to this, we
need to create a slot to receive the signal and to update the LCD number display to
the current time. Thus, the QTime class is also needed. This is how the header file of
MainWindowmainwindow.h will look now:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
[ 26 ]
Chapter 2
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
private slots:
void updateTime();
};
#endif // MAINWINDOW_H
As you can see, the only modification made here is the declaration of a private
updateTime slot. As usual, we're supposed to define this slot in mainwindow.cpp,
whose content is pasted here. Note that we need to include QTimer and QTime.
#include
#include
#include
#include
<QTimer>
<QTime>
"mainwindow.h"
"ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateTime()
{
[ 27 ]
Inside the updateTime slot, the QTime class is used to deal with the time, that is,
the clock. This class can provide accuracy of up to 1 millisecond, if the underlying
operating system supports it. However, QTime has nothing to do with the time
zone or daylight saving time. It is, at least, sufficient for our little clock. The
currentTime() function is a static public function, which is used to create a QTime
object that contains the system's local time.
As for the second line of the updateTime function, we used the toString
function provided by QTime to convert the time to a string, and then saved it in
currentTimeText. The arguments that are passed to toString are in the format
of the time string. The full list of expressions can be obtained from Qt Reference
Documentation. The colon in the middle of the clock should be flashing, just as
in the case of a real digital clock. Hence, we used an if statement to control this.
The colon will vanish when the second's value is even, and it will reappear when
the second's value is odd. Here, inside the if block, we used the [2] operator to
get a modifiable reference of the third character because this is the only way to
do direct modifications to a character inside a string. Here, the counting of the
currentTimeText string starts from 0. Meanwhile, the at() function of QString
returns a constant character, which you have no right to change. At last, this function
will let lcdNumber display the time string. Now, let's get back to the constructor
of MainWindow. After the initialization of the UI, the first thing it does is to create
a QTimer object. Why can't we use a local variable? The answer to that question is
because the local variables will be destroyed after the construction of MainWindow. If
the timer has gone, there's no way to trigger updateTime repetitively. We don't use
a member variable because there is no need to perform the declaration work in the
header file, since we won't use this timer elsewhere.
The QTimer class is used to create a repetitive and single-shot timer. It will emit the
timeout signal at constant intervals after start is called. Here, we create one timer
and connect the timeout signal to the updateTime slot so that updateTime is called
every second.
[ 28 ]
Chapter 2
[ 29 ]
Here, the windowFlags function is used to retrieve the window flags. One thing you
may be interested to know is that setWindowFlags will result in the invisibility of
the widget after the show function. So, you can either call setWindowFlags before the
show function of the window or widget or call show again after setWindowFlags.
[ 30 ]
Chapter 2
After the modification to the constructor, this is how the clock is expected to look:
There is a useful trick that you can use to hide the clock from the taskbar. Of course,
a clock doesn't need to be displayed among the applications in a taskbar. You should
never set a flag such as Qt::Tool or Qt::ToolTip alone to achieve this because
this will cause the exit behavior of the application to be abnormal. This trick is even
simpler; here is the code of main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget wid;
MainWindow w(&wid);
w.show();
return a.exec();
}
[ 31 ]
The preceding code makes our MainWindow w object a child of QWidget wid. The
child widgets won't display on the taskbar because there should be only one top
parent widget. Meanwhile, our parent widget, wid, doesn't even show. It's tricky,
but it's the only one that does the trick without breaking any other logic.
Well, a new problem has just surfaced. The clock is unable to move and the only way
to close it is by stopping it through the Qt Creator's panel or through a keyboard
shortcut. This is because we declared it as a frameless window, which led to an
inability to control it via a window manager. Since there is no way to interact with it,
it's impossible to close it by itself. Hence, the solution to this problem is to write our
own functions to move and close the clock.
Closing this application may be more urgent. Let's see how to reimplement some
functions to achieve this goal. First, we need to declare a new showContextMenu slot
to display a context menu and reimplement mouseReleaseEvent. The following
code shows the content of mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
private slots:
void updateTime();
void showContextMenu(const QPoint &pos);
protected:
void mouseReleaseEvent(QMouseEvent *);
};
#endif // MAINWINDOW_H
[ 32 ]
Chapter 2
There are two new classes defined in the preceding code: QPoint and QMouseEvent.
The QPoint class defines a point in the plane by using integer precision. Relatively,
there is another class named QPointF, which provides float precision. Well, the
QMouseEvent class inherits QEvent and QInputEvent. It contains some parameters
that describe a mouse event. Let's see why we need them in mainwindow.cpp:
#include
#include
#include
#include
#include
#include
#include
<QTimer>
<QTime>
<QMouseEvent>
<QMenu>
<QAction>
"mainwindow.h"
"ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::WindowStaysOnTopHint |
Qt::FramelessWindowHint | windowFlags());
connect(this, &MainWindow::customContextMenuRequested, this,
&MainWindow::showContextMenu);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateTime()
{
QTime currentTime = QTime::currentTime();
QString currentTimeText = currentTime.toString("hh:mm");
if (currentTime.second() % 2 == 0) {
[ 33 ]
Note that you should include QMouseEvent, QMenu, and QAction in order to utilize
these classes. There is a predefined customContextMenuRequested signal, which is
coupled with the newly created showContextMenu slot. For the sake of consistency,
we will follow the rule that Qt defined, which means that the QPoint argument
in customContextMenuRequested should be a local position instead of a global
position. That's why we need a mapToGlobal function to translate pos to a global
position. As for the QMenu class, it provides a menu widget for a menu bar, context
menu, or other pop-up menus. So, we create the contextMenu object, and then add
a new action with the Exit text. This is coupled with a close slot of MainWindow.
The last statement is used to execute the contextMenu object at the specified global
position. In other words, this slot will display a pop-up menu at the given position.
The reimplementation of mouseReleaseEvent is done to check the triggered
button of the event. If it's the right button, emit the customContextMenuRequested
signal with the local position of the mouse. Otherwise, simply call the default
mouseReleaseEvent function of QMainWindow.
Make use of the default member functions of the base class when you reimplement it.
[ 34 ]
Chapter 2
Run the application again; you can quit by right-clicking on it and then selecting
Exit. Now, we should continue the reimplementation to make the clock movable.
This time, we need to rewrite two protected functions: mousePressEvent and
mouseMoveEvent. Therefore, this is how the header file looks:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QPoint m_mousePos;
private slots:
void updateTime();
void showContextMenu(const QPoint &pos);
protected:
void mouseReleaseEvent(QMouseEvent *);
void mousePressEvent(QMouseEvent *);
void mouseMoveEvent(QMouseEvent *);
};
#endif // MAINWINDOW_H
[ 35 ]
There is also a declaration of a new private member variable in the preceding code,
m_mousePos, which is a QPoint object used to store the local position of the mouse.
The following code defines mousePressEvent and mouseMoveEvent:
void MainWindow::mousePressEvent(QMouseEvent *e)
{
m_mousePos = e->pos();
}
void MainWindow::mouseMoveEvent(QMouseEvent *e)
{
this->move(e->globalPos() - m_mousePos);
}
It's easier than you thought. When a mouse button is pressed, the local position of
the mouse is stored as m_mousePos. When the mouse is moving, we call the move
function to move MainWindow to a new position. Because the position passed to move
is a global position, we need to use globalPos of the event minus the local position
of the mouse. Confused? The m_mousePos variable is the mouse's relative position
to the top-left point of the parent widget, which is MainWindow in our case. The move
function will move the top-left point of MainWindow to the given global position.
While the e->globalPos() function is the global position of the mouse and not
MainWindow, we need to subtract the relative position of m_mousePos to translate the
mouse's global position to the top-left point position of MainWindow. After all this
effort, the clock should look much more satisfying.
Here, Qt5 Blueprints is the organization's name and Fancy Clock is the
application's name.
[ 36 ]
Chapter 2
The settings are stored in the system registry on Windows, while they are stored in the
XML preferences files on Mac OS X and the INI text files on the other Unix operating
systems, such as Linux. However, we do not usually need to be concerned with this,
since QSettings provides high-level interfaces to manipulate the settings.
If we're going to read and/or write settings in multiple places, we'd better set
the organization and application in QCoreApplication, which is inherited by
QApplication. The main.cpp file's content is shown as follows:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setOrganizationName(QString("Qt5 Blueprints"));
a.setApplicationName(QString("Fancy Clock"));
QWidget wid;
MainWindow w(&wid);
w.show();
return a.exec();
}
This enables us to use the default QSettings constructor to access the same settings.
In order to save the geometry and state of MainWindow, we need to reimplement
closeEvent. First, we need to declare closeEvent to be a protected member
function, as follows:
void closeEvent(QCloseEvent *);
[ 37 ]
Here, toByteArray() can translate the stored value to a QByteArray object. How
do we test to see if this works? To do this, perform the following steps:
1. Rebuild this application.
2. Run it.
3. Move its position.
4. Close it.
5. Run it again.
You'll see that the clock will appear at exactly the same position as it was before it
closed. Now that you're pretty much familiar with widgets, layouts, settings, signals,
and slots, it's time to cook a preference dialog by performing the following steps:
1. Right-click on the Fancy_Clock project in the Projects panel.
2. Select Add New.
3. Select Qt in the Files and Classes panel.
4. Click on Qt Designer Form Class in the middle panel.
5. Select Dialog with Buttons Bottom.
6. Fill in Preference under Class name.
7. Click on Next, and then select Finish.
[ 38 ]
Chapter 2
Qt Creator will redirect you to the Design mode. First, let's change windowTitle to
Preference, and then do some UI work. Perform the following steps to do this:
1. Drag Label to QDialog and change its objectName property to colourLabel.
Next, change its text to Colour.
2. Add QComboBox and change its objectName property to colourBox.
3. Add the Black, White, Green, and Red items to colourBox.
4. Change the layout of Preference to Lay Out in a Form Lay Out.
Close this UI file. Go back to editing the preference.h add a private onAccepted
slot. The following code shows the content of this file:
#ifndef PREFERENCE_H
#define PREFERENCE_H
#include <QDialog>
namespace Ui {
class Preference;
}
class Preference : public QDialog
{
Q_OBJECT
public:
explicit Preference(QWidget *parent = 0);
~Preference();
private:
Ui::Preference *ui;
private slots:
void onAccepted();
};
#endif // PREFERENCE_H
[ 39 ]
As usual, we define this slot in the source file. Besides, we have to set up some
initializations in the constructor of Preference. Thus, preference.cpp becomes
similar to the following code:
#include <QSettings>
#include "preference.h"
#include "ui_preference.h"
Preference::Preference(QWidget *parent) :
QDialog(parent),
ui(new Ui::Preference)
{
ui->setupUi(this);
QSettings sts;
ui->colourBox->setCurrentIndex(sts.value("Colour").toInt());
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&Preference::onAccepted);
}
Preference::~Preference()
{
delete ui;
}
void Preference::onAccepted()
{
QSettings sts;
sts.setValue("Colour", ui->colourBox->currentIndex());
}
[ 40 ]
Chapter 2
Similarly, we load the settings and change the current item of colourBox. Then,
it's the signal and slot coupling that follow. Note that Qt Creator has automatically
generated the accept and reject connections between buttonBox and Preference
for us. The accepted signal of buttonBox is emitted when the OK button is clicked.
Likewise, the rejected signal is emitted if the user clicks on Cancel. You may want
to check Signals & Slots Editor in the Design mode to see which connections are
defined there. This is shown in the following screenshot:
[ 41 ]
The setColour function is used to change the color of lcdNumber, while the
showPreference slot will execute a Preference object. The definitions of these
two members are in the mainwindow.cpp file, which is displayed in the following
manner:
#include
#include
#include
#include
#include
#include
#include
#include
#include
<QTimer>
<QTime>
<QMouseEvent>
<QMenu>
<QAction>
<QSettings>
"mainwindow.h"
"preference.h"
"ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
[ 42 ]
Chapter 2
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::WindowStaysOnTopHint |
Qt::FramelessWindowHint | windowFlags());
QSettings sts;
restoreGeometry(sts.value("MainGeometry").toByteArray());
restoreState(sts.value("MainState").toByteArray());
setColour();
connect(this, &MainWindow::customContextMenuRequested, this,
&MainWindow::showContextMenu);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateTime()
{
QTime currentTime = QTime::currentTime();
QString currentTimeText = currentTime.toString("hh:mm");
if (currentTime.second() % 2 == 0) {
currentTimeText[2] = ' ';
}
ui->lcdNumber->display(currentTimeText);
}
void MainWindow::showContextMenu(const QPoint &pos)
{
QMenu contextMenu;
contextMenu.addAction(QString("Preference"), this,
SLOT(showPreference()));
contextMenu.addAction(QString("Exit"), this, SLOT(close()));
contextMenu.exec(mapToGlobal(pos));
}
[ 43 ]
[ 44 ]
Chapter 2
case 3://red
c.setColor(QPalette::Foreground, Qt::red);
break;
}
ui->lcdNumber->setPalette(c);
this->update();
}
void MainWindow::showPreference()
{
Preference *pre = new Preference(this);
pre->exec();
setColour();
}
We call setColour in the constructor in order to set the color of lcdNumber correctly.
Inside setColour, we first read the Colour value from the settings, and then use
a switch statement to get the correct QPalette class before calling setPalette to
change the color of lcdNumber. Since Qt doesn't provide a direct way to change the
foreground color of the QLCDNumber objects, we need to use this tedious method to
achieve this. At the end of this member function, we call update() to update the
MainWindow user interface.
Don't forget to add the Preference action to contextMenu inside
showContextMenu. Otherwise, there will be no way to open the dialog.
[ 45 ]
No matter which way you prefer, the clock should have a Preference dialog
now. Run it, select Preference from the pop-up menu, and change the color to
whatever you please. You should expect a result similar to what is shown in the
following screenshot:
[ 46 ]
Chapter 2
If you run the file again, Fancy Clock won't show up in the taskbar any more. Please
keep the MainWindow object, w, as a child of QWidget wid; otherwise, the application
won't terminate after you click on Close.
[ 47 ]
Note that the Preference dialog uses native UI controls, rather than bringing the
other platform's controls to this one. This is one of the most fascinating things that Qt
has provided. All the Qt applications will look and behave like native applications
across all platforms.
It's not a hustle but the truth is that once you code the Qt application, you can run
it everywhere. You don't need to write different GUIs for different platforms. That
dark age has long gone. However, you may want to write some functions for specific
platforms, either because of particular needs or workarounds. Firstly, I'd like to
introduce you to some Qt Add-On modules dedicated for several platforms.
Take Qt Windows Extras as an example. Some cool features that Windows provides,
such as Thumbnail Toolbar and Aero Peek, are supported by Qt through this
add-on module.
Well, adding this module to the project file directly, which in this case is Fancy_
Clock.pro file, will definitely upset other platforms. A better way to do this is to test
whether it's on Windows; if so, add this module to the project. Otherwise, skip this
step. The following code shows you the Fancy_Clock.pro file, which will add the
winextras module if it's built on Windows:
QT
+= core gui
win32: QT += winextras
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = Fancy_Clock
TEMPLATE = app
SOURCES += main.cpp\
mainwindow.cpp \
[ 48 ]
Chapter 2
preference.cpp
HEADERS += mainwindow.h \
preference.h
FORMS
+= mainwindow.ui \
preference.ui
As you can see, win32 is a conditional statement, which is true only if the host
machine is Windows. After a qmake rerun for this project, you'll be able to include
and utilize those extra classes.
Similarly, if you want to do something on the Unix platforms, simply use the
keyword unix, but unix will be true only on Linux/X11 or Mac OS X. To
distinguish Mac OS X from Linux, here's an example:
win32 {
message("Built on Windows")
}
else: unix: macx{
message("Built on Mac OS X")
}
else {
message("Built on Linux")
}
In fact, you can just use unix: !macx as the conditional statement to do some
platform-specific work on Linux. It's a common practice to have many
platform-specific statements in the project file(s), especially when your project
needs to be linked with other libraries. You have to specify different paths for
these libraries on different platforms, otherwise the compiler will complain about
missing libraries or unknown symbols.
In addition to this, you may want to know how to write platform-specific code
while keeping it from other platforms. Similar to C++, it's a predefined macro that
is handled by various compilers. However, these compiler macro lists may differ
from one compiler to another. So, it is better to use Global Qt Declarations
instead. I'll use a the following short example to explain this further:
void MainWindow::showContextMenu(const QPoint &pos)
{
QMenu contextMenu;
#ifdef Q_OS_WIN
contextMenu.addAction(QString("Options"), this,
SLOT(showPreference()));
[ 49 ]
The preceding code shows you the new version of showContextMenu. The Preference
menu entry will use different texts on different platforms, namely Windows, Linux,
and Mac OS X. Change your showContextMenu function and run it again. You'll see
Options on Windows, Profile on Linux, and Preference on Mac OS X. Below is a list
concerning the platform-specific macros. You can get a full description, including other
macros, functions, and types on the QtGlobal document.
Macro
Correspond Platform
Q_OS_ANDROID
Android
Q_OS_FREEBSD
FreeBSD
Q_OS_LINUX
Linux
Q_OS_IOS
iOS
Q_OS_MAC
Q_OS_WIN
Q_OS_WINPHONE
Windows Phone 8
Q_OS_WINRT
Summary
In this chapter, information, including some tricks, about UI designing is included.
Furthermore, there are basic yet useful cross-platform topics. Now, you're able to
write an elegant Qt application in your favorite, and possibly already mastered, C++.
In the next chapter, we are going to learn how to write an application in Qt Quick.
However, fear not; Qt Quick is even easier and, of course, quicker to develop.
[ 50 ]
www.PacktPub.com
Stay Connected: