Dokumen - Pub Getting Started With QT 5 Introduction To Programming QT 5 For Cross Platform Application Development 9781789955125 1789955122
Dokumen - Pub Getting Started With QT 5 Introduction To Programming QT 5 For Cross Platform Application Development 9781789955125 1789955122
Benjamin Baka
BIRMINGHAM - MUMBAI
Getting Started with Qt 5
Copyright © 2019 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form
or by any means, without the prior written permission of the publisher, except in the case of brief quotations
embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented.
However, the information contained in this book is sold without warranty, either express or implied. Neither the
author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to
have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products
mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy
of this information.
ISBN 978-1-78995-603-0
www.packtpub.com
mapt.io
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as
well as industry leading tools to help you plan your personal development and advance
your career. For more information, please visit our website.
Why subscribe?
Spend less time learning and more time coding with practical eBooks and Videos
from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Packt.com
Did you know that Packt offers eBook versions of every book published, with PDF and
ePub files available? You can upgrade to the eBook version at www.packt.com and as a print
book customer, you are entitled to a discount on the eBook copy. Get in touch with us at
[email protected] for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a
range of free newsletters, and receive exclusive discounts and offers on Packt books and
eBooks.
Contributors
I'd like to thank the Baka family for all of their support in my many endeavors. Another
thanks to Samuel Afari for encouraging me to go beyond my limits.
To the entire Packt team, it's been a pleasure working with you.
And I continue to stay forever indebted to Guido Sohne and Lorenzo Cabrini for their
amazing guidance and input in my life.
About the reviewer
Nibedit Dey is a techno-entrepreneur and innovator with over 8 years of experience in
building complex software-based products using Qt and C++. Before starting his
entrepreneurial journey, he worked for L&T and Tektronix in different research and
development roles. Additionally, he has reviewed The Modern C++ Challenge, Hands-on GUI
programming with C++ and Qt5, and Hands-On High Performance Programming with Qt 5 books
for Packt.
I would like to thank the online programming communities, bloggers, and my peers from
earlier organizations, from whom I have learned a lot over the years.
[ ii ]
Preface
There are many buzzwords in computing today, most of which revolve around various
software technologies and concepts. Browsers have become the preferred means of
accessing information and consuming all manner of data. But there is still a void that can
only be filled by standalone applications that must be installed and run on an operating
system. The browser itself as an application cannot be accessed through a browser and
bears witness to this assertion.
Applications such as VLC, Adobe Photoshop, Google Earth, and QGIS are a few examples
of applications that run directly on an operating system. Interestingly enough, these well-
known software brands are built with Qt.
The main aim of this book is to introduce Qt to the reader. Through the use of simple and
easy-to-understand examples, it will walk the user from one concept to the next without
focusing too much on theory. The size of the book requires us to be concise in our
presentation of materials. Coupled with the ample examples presented, we hope to shorten
the path to understanding and learning how to use Qt.
The book does, however, presume that you have a working knowledge in the use of C++. If
you can express your thoughts in developing algorithms and the use of object-oriented
programming, you will find the content easy to consume.
Expert or intermediate persons with Qt knowledge should seek more detailed materials
that are available out there. This book is not a reference guide, and should only be used as
introductory material.
Preface
Chapter 2, Creating Widgets and Layouts, covers GUI components that are usually present in
most applications and how they are created in Qt. The chapter then ends by detailing how
to use layouts with widgets.
Chapter 3, Working with Signals and Slots, introduces one of the most important concepts to
grasp in Qt, signals and slots. It demonstrates to the reader how to make an application
trigger and respond to actions.
Chapter 4, Implementing Windows and Dialog, brings the reader closer to how a real-world
Qt program should be written. It illustrates how to use and organize a program using
classes, windows, and dialog boxes.
Chapter 5, Managing Events, Custom Signals, and Slots, explores the writing of custom
signals and slots and introduces the topic of Events in Qt.
Chapter 6, Connecting Qt with Databases, deals with how to write applications that connect
to a database and how to also present the data in visual form.
This book also avoids continuing with examples from previous chapters. Each chapter's
examples are short and do not require the reader to have knowledge of previous chapters.
That way, you can pick any chapter you link and work through it.
Appropriate links to set up the environment on Windows have been provided. Linux and
macOS platforms have been catered for directly in this book.
[2]
Preface
Once the file is downloaded, please make sure that you unzip or extract the folder using the
latest version of:
The code bundle for the book is also hosted on GitHub at https://github.com/
PacktPublishing/Getting-Started-with-Qt-5. In case there's an update to the code, it will
be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available
at https://github.com/PacktPublishing/. Check them out!
[3]
Preface
Conventions used
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames,
file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an
example: "To set the password to the connection parameter, the code fragment,
db_conn.setPassword(""), is issued."
db_conn.setHostName("127.0.0.1");
db_conn.setDatabaseName("contact_db");
db_conn.setUserName("root");
db_conn.setPassword("");
db_conn.setPort(3306);
When we wish to draw your attention to a particular part of a code block, the relevant lines
or items are set in bold:
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)
Bold: Indicates a new term, an important word, or words that you see on screen. For
example, words in menus or dialog boxes appear in the text like this. Here is an example:
"It displays the text Hello world ! in a label."
[4]
Preface
Get in touch
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book
title in the subject of your message and email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes
do happen. If you have found a mistake in this book, we would be grateful if you would
report this to us. Please visit www.packt.com/submit-errata, selecting your book, clicking
on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the internet, we
would be grateful if you would provide us with the location address or website name.
Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in,
and you are interested in either writing or contributing to a book, please visit
authors.packtpub.com.
Reviews
Please leave a review. Once you have read and used this book, why not leave a review on
the site that you purchased it from? Potential readers can then see and use your unbiased
opinion to make purchase decisions, we at Packt can understand what you think about our
products, and our authors can see your feedback on their book. Thank you!
[5]
1
Introducing Qt 5
Qt gives developers a great toolbox with which to create fantastic and practical applications
with minimal stress, as you will soon discover. In this chapter, we will introduce Qt and
describe how to set it up on a machine. By the end of the chapter, you should be able to do
the following:
Install Qt
Write a simple program in Qt
Compile and run a Qt program
The objectives have been kept simple and straightforward. So let's get started!
Installing Qt on Linux
The Ubuntu operating system makes it reasonably easy to install Qt 5. Issue the following
commands to set up your box:
sudo apt-get install qt5-default
After the installation, Qt programs will be compiled and run from the command line. In
Chapter 6, Connecting Qt with Databases, we will illustrate how to connect to the database
using Qt. Issue the following command to ensure that the relevant libraries are installed for
Qt to work with. The database that will'll connect to is MySQL:
sudo apt-get install libqt5sql5-mysql
Introducing Qt 5 Chapter 1
Installing Qt on macOS
There are a variety of ways to get Qt installed on a Mac. To begin the process of installing
Qt 5 on your Mac, you need to get Xcode installed on your machine. Issue the following
commands on the Terminal:
xcode-select --install
If you get the following output, then you are ready for the next series of steps:
xcode-select: error: command line tools are already installed, use
"Software Update" to install updates
HomeBrew is a package management software tool that allows you to easily install Unix
tools that don't come shipped with the macOS.
If you don't already have it on your machine, you can install it by issuing the following
command in a Terminal:
/user/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/
Homebrew/install/master/install)"
After that, you should issue yet another set of commands to get Qt installed via the
Terminal:
curl -O
https://raw.githubusercontent.com/Homebrew/homebrew-core/fdfc724dd532345f5c
6cdf47dc43e99654e6a5fd/Formula/qt5.rb
In a few chapters' time, we will be working with the MySql database. To configure Qt 5
with MySql, issue the following command:
brew install ./qt5 --with-mysql
This command should take a while to complete and, assuming nothing goes wrong, you are
ready to write Qt programs.
Installation on Windows
For readers using Windows, installation remains simple, albeit a little less straightforward.
We can start by heading over to http://download.qt.io.
[7]
Introducing Qt 5 Chapter 1
Run the program and opt to create an account. Click through to select the installation folder
and don't forget to select the MinGW 5.3.0 32 bit option as the compiler when selecting the
components that need to be installed.
What is Qt?
Now that we have set up our boxes to start development, let's put together a hello world
example. First, however, let's take a brief detour.
The difficulty in trying to produce the same look, feel, and functionality across multiple
operating systems is one big hurdle you have to deal with when writing GUI applications.
Qt completely does away with this impediment by providing a means to write code only
once and ensuring that it runs on most operating systems without requiring much or any
change.
Qt makes use of some modules. These modules group related functionalities together. The
following lists some modules and what they do:
QtCore: As the name implies, these modules contains core and important classes
for the Qt framework. These include containers, events, and thread management,
among others.
QtWidgets and QtGui: This module contains classes for calling widgets.
Widgets are the components that make up the majority of a graphical interface.
These include buttons, textboxes, and labels.
QtWebkit: This module makes it possible to use web pages and apps within a Qt
application.
QtNetwork: This module provides classes to connect to and communicate with
network resources.
[8]
Introducing Qt 5 Chapter 1
QtXML: For parsing XML documents, this module contains useful classes.
QtSQL: This module is feature-rich with classes and drivers that allow for
connecting to databases, including My SQL, PostgreSQL, and SQLite.
Hello world in Qt
In this section, we will put together a very simple hello world program. The program will
show a simple button within a window. Create a file called hello.cpp in a newly created
folder called hello_world. Open the file and insert the code:
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel label("Hello world !");
label.show();
return app.exec();
}
This looks like a regular C++ program, with the exception of unfamiliar classes being used.
Like any regular program, the int main() function is the entry point of our application.
An instance of the QApplication class is created, called app, and the arguments passed to
the main() function. The app object is required because it sets off the Event loop that
continues to run until we close the application. Without the QApplication object, you
cannot really create a Qt GUI application.
Also, the constructor for QApplication requires that we pass the argc and argv to it.
We instantiate an object of the QLabel class, label. We pass the "Hello World!"
string to its constructor. A QLabel represents what we call a widget, which is a term used
to describe visual elements on the screen. Labels are used to hold text for display.
By default, created widgets are hidden. To display them, a call to the show() function has
to be made.
[9]
Introducing Qt 5 Chapter 1
To start the Event loop, the app.exec() line is executed. This passes control of the
application to Qt.
The return keyword will pass an integer back to the operating system, indicating the state
of the application when it was closed or exited.
To compile and run our program, navigate to the folder where hello.cpp is stored. Type
the following command in the Terminal:
% qmake -project
This will create the hello_world.pro file. The name hello_world is the name of the
folder where hello.cpp is located. The generated file should change, depending on the
location you stored the hello.cpp file.
Open the hello_world.pro file with any text editor of your choice. The following lines
deserve some explanation:
TEMPLATE = app
The value, app, here means that the final output of the project will be an application.
Alternatively, it could be a library or sub-directory:
TARGET = hello_world
The name, hello_world, here is the name of the application or (library) that will be
executed:
SOURCES += hello.cpp
Since hello.cpp is the only source file in our project, it is added to the SOURCES variable.
We need to generate a Makefile that will detail the steps needed to compile our hello
world program. The benefit of this autogenerated Makefile is that it takes away the need
for us to know the various nuances involved in compiling the program on the different
operating systems.
[ 10 ]
Introducing Qt 5 Chapter 1
The following error will be produced (along with further information) as the output from
running the make command:
#include <QApplication>
^~~~~~~~~~~~
Earlier on, we mentioned that various components and classes are packaged into modules.
The QApplication is being utilized in our application, but the correct module has not been
included. During compilation, this omission results in an error.
To fix this issue, open the hello_world.pro file and insert the following lines after the
line:
INCLUDEPATH += .
QT += widgets
This will add the QtWidget module, along with the QtCore modules, to the compiled
program. With the correct module added, run the make command again on the command
line:
% make
A hello_world file will be generated in the same folder. Run this file from the command
line as follows:
% ./hello_world
On a macOS, the full path to the executable will be specified with the following path from
the command line:
./hello_world.app/Contents/MacOS/hello_world
[ 11 ]
Introducing Qt 5 Chapter 1
Well, there is our first GUI program. It displays the Hello world ! in a label. To close the
application, click on the Close button of the window.
Let's add a dash of Qt Style Sheet (QSS) to give our label a little effect!
A QSS rule is passed as an argument to the setStyleSheet method on the label object.
The rule sets every label within our application to show the color green when the cursor
hovers over it.
Run the following commands to recompile the application and run it:
% make
% ./hello_world
The program should appear as in the following screenshot. The label turns green when the
mouse is placed over it:
[ 12 ]
Introducing Qt 5 Chapter 1
Summary
This chapter laid the foundation for getting to know Qt and what it can be used for.
Installing of Qt on macOS and Linux was outlined. A small hello world application was
written and compiled, all from the command line, without any need for an IDE. This meant
that we were also introduced to the various steps that lead to the final program.
Finally, the hello world application was modified to employ QSS in a bid to show what
other things can be done to a widget.
In Chapter 2, Creating Widgets and Layouts, we will explore more widgets in Qt and how to
organize and group them.
[ 13 ]
2
Creating Widgets and Layouts
In this chapter, we shall take a look at what widgets are and the various kinds that are
available for creating GUIs. For most GUI applications that you will write, Qt is laden with
sufficient widgets to implement it. Coupled with widgets are layout classes, which help us
to arrange and position the widgets for better appeal.
Widgets
Widgets are the graphical components with which we construct user interfaces. A familiar
example of such a component is a textbox. This is the component that is used to capture our
email address or last and first names on forms in a GUI application.
Qt organizes its classes with heavy use of inheritance, and it is very important to have a
good grasp of this. Consider the following diagram:
At the very top of the hierarchy is the QObject. A lot of classes inherit from the QObject
class. The QObject class also contains the mechanisms of signals and slots and event
management, among other things.
Furthermore, widgets that share common behavior are grouped together. QCheckBox,
QPushButton, and QRadioButton are all buttons of the same kind and thus inherit from
QAbstractButton, which holds properties and functions that are shared by all buttons. This
principle also applies to QAbstractScrollArea and its children, QGraphicsView and
QTextEdit.
To put into practice some of what we have just learned, let's create a simple Qt program
with only one widget.
This Qt application displays only one button. Open a text file and name it how you want
with the suffix .cpp.
Most of the examples will require that you create a new folder where the
source code will be stored. This will allow for easy compilation of the
program as a project.
[ 15 ]
Creating Widgets and Layouts Chapter 2
Insert the following lines of codes. Create a new folder and move the .cpp file into it:
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton myButton(QIcon("filesaveas.png"),"Push Me");
myButton.setToolTip("Click this to turn back the hands of time");
myButton.show();
return app.exec();
}
The purpose of this application is to show how a widget without a parent object becomes
the main window when executed. The button that will be created will include an icon and a
tooltip.
For starters, this application looks similar to the one we wrote at the tail end of Chapter 1,
Introducing Qt 5. In this application, a button named myButton is declared. An instance of
QIcon is passed as the first argument to the default constructor of QPushButton. This
reads the file named filesaveas.png (which, for now, should be in the same folder as the
source code file on GitHub). The text "Push Me" is passed as the second argument. This
text will be displayed on the button.
Lastly, we call the show() function on the myButton object to unhide it and draw it to the
screen. In this application, we only have one widget, QPushButton. What could be the
parent of this widget? Well, if unspecified, the parent defaults to NULL, which tells Qt that
the widget is without a parent. When displaying such a widget, it will be enclosed in a
window on account of this reasoning.
Save the file and run the following commands to compile your application. Change
directory to the new folder you created that houses the .cpp file created:
[ 16 ]
Creating Widgets and Layouts Chapter 2
From the name of the .pro file, it tells us that the name of the folder where the .cpp file is
located is called qbutton. This name should, therefore, change to whichever folder name
the .cpp file is located in when you issue the preceding command.
Now, remember to add the following line to the qbutton.pro beneath INCLUDEPATH +=
.:
QT += widgets
The preceding screenshot shows what you will see when the program is run for the first
time:
The tooltip that was specified within the code is displayed when we rest our cursor on the
button, as seen in the preceding screenshot.
The button also shows the image for those cases when you want to add an image to a
button in order to improve the intuitiveness of a UI.
[ 17 ]
Creating Widgets and Layouts Chapter 2
To cap off this section on widgets, let's customize a QLabel and display it. This time, an
instance of QLabel will have its font changed and shall display a longer text than usual.
Create a file named qlabel_long_text.cpp in a newly created folder and insert the
following code:
#include <QApplication>
#include <QString>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QString message = "'What do you know about this business?' the
King said to Alice.\n'Nothing,' said Alice.\n'Nothing whatever?' persisted
the King.\n'Nothing whatever,' said Alice.";
QLabel label(message);
label.setFont(QFont("Comic Sans MS", 18));
label.setAlignment(Qt::AlignCenter);
label.show();
return app.exec();
}
The structure of our Qt programs has not changed that much. The first three (3) lines have
the include directives adding the headers for the classes we will be using.
As usual, the arguments to the main() function are passed to app(). The
message variable is a QString object that holds a long string. QString is the class used
when working with strings. It has a host of functionalities not available in C++ string.
An instance of QLabel is created, label, and message is passed to this. To change the style
by which the label string is displayed, we pass an instance of QFont to the setFont
function. We select the font style Comic Sans MS, with a point size of 18, to the constructor
of QFont.
[ 18 ]
Creating Widgets and Layouts Chapter 2
To align all the text in the middle, we call the setAlignment function on the label object
and pass the Qt::AlignCenter constant.
Lastly, we display the widget by calling the show function on the label object.
As usual, we shall issue the following codes on the command line to compile and run this
program:
% qmake -project
% qmake
% ./qlabel_long_text
The output of the program appears as follows. All the text on the lines are centered in the
middle:
Once again, the only widget within the label application becomes the main window
because it has no parent object associated with it. Secondly, the widget becomes a window
because the show() method was called on label.
Layouts
Up to this point, we have been creating applications that only have one widget serving as
the main component and, by extension, a window too. However, GUI applications are
usually made up of several widgets that come together to communicate a process to the
user. One way in which we can make use of multiple widgets is to use layouts to serve as
the canvas into which we insert our widgets.
[ 19 ]
Creating Widgets and Layouts Chapter 2
It is important to consider the classes used in laying out widgets. As usual, the top class
from which the QLayout abstract class inherits is QObject. Also, QLayout makes use of
multiple inheritances by inheriting from QLayoutItem. The concrete classes here are
QBoxLayout, QFormLayout, QGridLayout, and QStackedLayout. QHBoxLayout and
QVBoxLayout further refine what the QBoxLayout class is by adding orientation to how
the widgets within a layout might be arranged.
The following table provides a brief description of what the major layouts do:
[ 20 ]
Creating Widgets and Layouts Chapter 2
Let's create a simple program to illustrate how to use some of the layout classes.
QGridLayout
The QGridLayout is used to arrange widgets by specifying the number of rows and
columns that will be filled up by multiple widgets. A grid-like structure mimics a table in
that it has rows and columns and widgets are inserted as cells where a row and column
meet.
Create a new folder and, using of any editor, create a file named main.cpp:
#include <QApplication>
#include <QPushButton>
#include <QGridLayout>
#include <QLineEdit>
#include <QDateTimeEdit>
#include <QSpinBox>
#include <QComboBox>
#include <QLabel>
#include <QStringList>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
QLabel *nameLabel = new QLabel("Open Happiness");
QLineEdit *firstNameLineEdit= new QLineEdit;
QLineEdit *lastNameLineEdit= new QLineEdit;
QSpinBox *ageSpinBox = new QSpinBox;
ageSpinBox->setRange(1, 100);
QComboBox *employmentStatusComboBox= new QComboBox;
QStringList employmentStatus = {"Unemployed", "Employed", "NA"};
employmentStatusComboBox->addItems(employmentStatus);
QGridLayout *layout = new QGridLayout;
layout->addWidget(nameLabel, 0, 0);
layout->addWidget(firstNameLineEdit, 0, 1);
layout->addWidget(lastNameLineEdit, 0, 2);
layout->addWidget(ageSpinBox, 1, 0);
layout->addWidget(employmentStatusComboBox, 1, 1,1,2);
window->setLayout(layout);
window->show();
return app.exec();
}
The aim of the program is to illustrate how to use a layout object. To fill up the layout, other
widgets will be discussed too.
[ 21 ]
Creating Widgets and Layouts Chapter 2
In the preceding code, *window is an instance of QWidget. For now, keep this object in to
see and how we shall turn it into a window.
The widgets that we are going to insert into our layout are created thereafter, namely name,
firstnameLineEdit, and lastNameLineEdit.
Some prefer to name their variables by appending the name of the class
that they are instantiating to it. The CamelCase naming scheme is being
used here too.
QLineEdit is essentially the class for creating textboxes. QSpinbox is a widget that allows
for the selection of a value between a given range. In this case,
ageSpinBox->setRange(1, 100) sets the range of possible values between 1 and 100.
Next, we instantiate the QComboBox class to create a widget with drop-down values
specified by a list of strings stored in QStringList. The list of strings,
employmentStatus, is then passed to employmentStatusComboBox by calling its
addItems() method. These will become the options that will be displayed when the
widget is clicked.
Since we want to layout our widgets in a grid layout, we create an object from the
QGridLayout, *layout. To add the widgets to the layout, the addWIdget() method is
called and each time, the widget, along with two (2) numbers that specify the row and
column where the widget is to be inserted is specified:
layout->addWidget(nameLabel, 0, 0);
layout->addWidget(firstNameLineEdit, 0, 1);
layout->addWidget(lastNameLineEdit, 0, 2);
layout->addWidget(ageSpinBox, 1, 0);
layout->addWidget(employmentStatusComboBox, 1, 1,1,2);
The first widget to be inserted into the layout object is the label, nameLabel. This occupies
the first row and first column of the grid. The first row is represented by the second
parameter 0 while the first column is represented by 0. This resolves to the selection of the
first cell of the grid to keep nameLabel.
The second widget that is added to the layout is firstNameLineEdit. This widget will be
inserted on the first row, marked by 0, and on the second column marked by 1. Next to this
widget is added the lastNameLineEdit widget, also sitting on the same row, 0.
The ageSpinBox widget will be fixed on the second row marked by 1 and in the first
column, marked by 0.
[ 22 ]
Creating Widgets and Layouts Chapter 2
The window object is without a layout. To set the layout of the widget, call setLayout and
pass in the layout object, which holds the other widgets.
Because window, which is basically a widget, has no parent object, it will become a window
when we call the show() method on it. Also, all the widgets that were added to the layout
object via the addWidget() method are children of the layout object.
Run the project by issuing the commands to create the project and compiling on the
command line.
Notice how the drop-down widget stretches to fill the third column. The placement of the
widgets conforms to how we laid out the widgets as we called addWidget(). Experiment
by clicking on the ageSpinBox to observe how it behaves.
In the next section, we shall take a look at a useful layout class called QFormLayout.
QFormLayout
For those instances when you simply need to place a number of widgets together in a two-
column layout, the QFormLayout is useful. You may choose to construct a form using
QGridLayout, but for form presentation, QFormLayout is most suited.
[ 23 ]
Creating Widgets and Layouts Chapter 2
Take, for instance, the following code. It illustrates a form that has labels in the first column
and the actual control for taking user input in the second column:
#include <QApplication>
#include <QFormLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QSpinBox>
#include <QComboBox>
#include <QStringList>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
QLineEdit *firstNameLineEdit= new QLineEdit;
QLineEdit *lastNameLineEdit= new QLineEdit;
QSpinBox *ageSpingBox = new QSpinBox;
QComboBox *employmentStatusComboBox= new QComboBox;
QStringList employmentStatus = {"Unemployed", "Employed", "NA"};
ageSpingBox->setRange(1, 100);
employmentStatusComboBox->addItems(employmentStatus);
QFormLayout *personalInfoformLayout = new QFormLayout;
personalInfoformLayout->addRow("First Name:", firstNameLineEdit);
personalInfoformLayout->addRow("Last Name:", lastNameLineEdit );
personalInfoformLayout->addRow("Age", ageSpingBox);
personalInfoformLayout->addRow("Employment Status",
employmentStatusComboBox);
window->setLayout(personalInfoformLayout);
window->show();
return app.exec();
}
The code should look familiar by now. We instantiate objects of the various widgets we
want to show in the form. Thereafter, the layout is created:
QFormLayout *personalInfoformLayout = new QFormLayout;
"First Name: " is the label and the widget here is firstNameLineEdit.
[ 24 ]
Creating Widgets and Layouts Chapter 2
Remember that the QWidget instance, window, will become the main window of the
application since its show() method is called.
QForm eliminates the need to specify columns and rows by giving us an easy way to add a
row to our layout, and each time we do so, we can specify the label and the widget we want
displayed.
You should see this output when you compile and run the project:
The preceding screenshot shows how widgets are aligned in those layouts. A form is
presented in a question-and-answer manner. The labels are usually on the left-hand side
while the widgets that take the user input are on the right-hand side.
[ 25 ]
Creating Widgets and Layouts Chapter 2
QVBoxLayout
In a QVBoxLayout layout, widgets are aligned vertically and they are packed in the layout
from top to bottom.
For QVBoxLayout, the arrow gives the direction of growth in which the widgets are added
to the layout. The first widget, widget 1, will occupy the top of the layout, while the last call
to addWidget() will make widget 5 occupy the bottom of the layout.
[ 26 ]
Creating Widgets and Layouts Chapter 2
In previous examples, we indicated the reason why we create an instance of QWidget. Two
labels are created with the strings "Username" and "Password". A textbox, QLineEdit
instance is also created to receive both username and password input. On the
passwordLineEdit object, the setEchoMode() method is passed the constant
QLineEdit::Password that masks the input of that textbox and replaces it with dots to
prevent the characters that are typed from being readable.
Two push buttons are also created, button1 and button2. An instance of QVBoxLayout is
created. To add widgets to the layout, the addWidget() method is called and passed the
specific widget. The very first widget passed to addWidget will appear on top when
displayed. Likewise, the last widget added will show on the bottom, which in this case is
button2.
The layout for the window widget instance is set by passing layout to setLayout().
Finally, the show() method is called on the window. Compile the project and run it to see
the output:
[ 27 ]
Creating Widgets and Layouts Chapter 2
In the preceding screenshot, we can see that the first widget that was added to the layout
was the label, label1, while button2 (with the text Register) was the last widget
occupying the bottom.
QHBoxLayout
The QHBoxLayout layout class is very similar in use to QVBoxLayout. Widgets are added
to the layout by calling the addWidget() method.
The arrow in the diagram shows the direction in which widgets grow in number as they are
added to a QHBoxLayout. The first widget added to this layout is widget 1, while widget 3
is the last widget to be added to the layout.
A small application to allow users to enter a URL makes use of this layout type:
#include <QApplication>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLineEdit>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
QLineEdit *urlLineEdit= new QLineEdit;
QPushButton *exportButton = new QPushButton("Export");
urlLineEdit->setPlaceholderText("Enter Url to export. Eg,
http://yourdomain.com/items");
urlLineEdit->setFixedWidth(400);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(urlLineEdit);
layout->addWidget(exportButton);
window->setLayout(layout);
window->show();
return app.exec();
}
[ 28 ]
Creating Widgets and Layouts Chapter 2
A textbox or QLineEdit and button are created. A placeholder is set on the QLineEdit
instance, urlLineEdit. To enable the placeholder to be seen, we stretch urlLineEdit by
setting setFixedWidth to 400.
An instance of QHBoxLayout is created and passed to the layout pointer. The two
widgets, urlLineEdit and exportButton, are added to the layout via the addWidget()
method.
The layout is set against window and the show() method of the window is called.
Compile the application and run it. You should see the following output:
Because the button was added to the layout after textbox, it appears accordingly, standing
next to the textbox. If another widget had been added to the layout, it would also appear
after the button, exportButton.
Summary
In this chapter, we have looked at a number of widgets that are useful in creating GUI
applications. The process of compilation remains the same. We also learned how to use
layouts to present and arrange multiple widgets.
Up to this point, our application does not do anything. The QPushButton instances, when
clicked, do nothing along with the other widgets that are action driven.
In the next chapter, we shall learn how to animate our application so that it responds to
actions, thus making them useful.
[ 29 ]
3
Working with Signals and Slots
Thus far, we have learned how to create applications and display various kinds of widgets.
If that were all that GUI applications were made of, that would be the end of the matter.
But there is more that we need to do in order to make our applications usable. In this
chapter, we will set about the following:
GUI toolkits usually provide a means to react to things that occur within an application.
Nothing is left to chance. Every tick that happens within the application is registered and
taken note of. For example, when you move a window or resize it, the action gets
registered, and provided ample code has been written, it will be executed as a reaction to
the moving or resizing of the window. For every action that occurs, a number of outcomes
may happen. Essentially, the questions we want to answer are as follows: what do we do
when a particular action or event has occurred? How do we handle it?
One way to implement the ability to react to an action that has occurred is by using the
design pattern called the Observer Pattern.
Working with Signals and Slots Chapter 3
In the Observer Pattern design, an observable object communicates its state change to other
objects that are observing it. For instance, any time an object (A) wants to be notified of a
state change of some other object (B), it first has to identify that object (B) and register itself
as one of the objects that should receive such notification of the state change. Sometime in
the future, when the state of an object (B) occurs, object (B) will go through a list of objects it
keeps that want to be informed regarding the state change. This will, at this point, include
object (A):
From the preceding diagram, the Subject circle is termed the observable object, while the
circles in the bounded box are the observers. They are being notified of the state change of
the Subject as its count variable is increased from 1 to 5.
Some events or actions that may occur within our application that we will be interested in
and would want to react to include the following:
[ 31 ]
Working with Signals and Slots Chapter 3
A signal is a message that is passed to communicate that the state of an object has changed.
This signal may carry information about the change that has occurred. For instance, when a
window has been resized, the signal will usually carry the coordinates of the new state (or
size) of the window. Sometimes, a signal may carry no extra information, such as that of a
button click.
A slot is a specific function of an object that is called whenever a certain signal has been
emitted. Since slots are functions, they will embody lines of code that perform an action,
such as closing a window, disabling a button, and sending an email, to mention but a few.
Signals and slots have to be connected (in code). Without writing code to connect a signal
and a slot, they will exist as independent entities.
Most of the widgets in Qt come with a number of signals and slots. However, it is possible
to write your own signals and slots too.
[ 32 ]
Working with Signals and Slots Chapter 3
Be sure to edit the .pro file to include the widget module during compilation.
The quit() method of the app object is called, which terminates the event loop.
To specify what should happen when quitButton has been clicked, we pass app and say
that the quit() method on the app object should be called. These four parameters are
connected by the static function, connect(), of the QObject class.
The second and final parameters are the signatures of the methods representing the signals
and the slots. The first and third parameters are pointers and should contain the address to
objects. Since quitButton is already a pointer, we simply pass it as it is. On the other
hand, &app would return the address of app.
[ 33 ]
Working with Signals and Slots Chapter 3
The example we have just illustrated is quite primitive. Let's write an application where a
change in the state of one widget is passed to another widget. Not only will the signal be
connected to a slot, but data will be carried along:
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QDial>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
QLabel *volumeLabel = new QLabel("0");
QDial *volumeDial= new QDial;
layout->addWidget(volumeDial);
layout->addWidget(volumeLabel);
QObject::connect(volumeDial, SIGNAL(valueChanged(int)), volumeLabel,
SLOT(setNum(int)));
window->setLayout(layout);
window->show();
return app.exec();
}
This is yet another simple program that illustrates how data is passed between the signal
and slot. An instance of QVBoxLayout is created, layout. A QLabel
instance, volumeLabel, is created and will be used to display changes that occur. It is
initialized with the string 0. Next, an instance of QDial is created with QDial
*volumeDial = new QDial. The QDial widget is a knob-like looking widget that is
graduated with a minimum and maximum range of numbers. With the aid of a mouse, the
knob can be turned, just like you would turn up the volume on a speaker or radio.
These two widgets, volumeLabel and volumeDial, are then added to the layout using the
addWidget() method.
Note how the connection between the signals and slots is established in the following code:
QObject::connect(volumeDial, SIGNAL(valueChanged(int)), volumeLabel,
SLOT(setNum(int)));
[ 34 ]
Working with Signals and Slots Chapter 3
This literally establishes a connection that reads "Anytime the QDial changes its value, call the
setNum() method of the volumeLabel object and pass it an int value." There can be a number
of state changes that may occur in QDial. The connection further makes it explicit that we
are only interested in the value that has changed when the knob (QDial) was moved,
which, in turn, emitted its current value through the valueChanged(int) signal.
To dry run the program, let's assume that the range of QDial is representing a radio
volume range between 0 and 100. If the knob of QDial is changed to half of the range,
the valueChanged(50) signal will be emitted. Now, the value 50 will be passed to the
setNum(50) function. This will be used to set the text of the label, volumeLabel in our
example, to display 50.
Compile the application and run it. The following output will be displayed on the first run:
As you can see, the initial state of QDial is zero. The following label shows that too. Move
the dial, and you will see that the label will have its value change accordingly. The
following screenshot shows the state of the application after the knob has been moved to
half of the range:
Move the knob around and observe how the label changes accordingly. This is all made
possible by means of the signals and slots mechanism.
[ 35 ]
Working with Signals and Slots Chapter 3
[ 36 ]
Working with Signals and Slots Chapter 3
We want to illustrate how one signal can be connected to two different slots, or more than
one slot for that matter. The widget that will be emitting the signal is an instance of QDial,
volumeDial. An instance of QLCDNumber, volumeLCD is created. This widget displays
information in an LCD-like digit form. Note volumeLabel is an instance of a QLabel.
These two widgets shall provide the two slots.
To make the text of volumeLCD stand out, we set the color of the display to red with
volumeLCD->setPalette(Qt::red);.
The fact that layout is an instance of QVBoxLayout means that widgets added to this
layout will flow from top to bottom. Each widget added to the layout will be centered
around the middle as we set setAlignment(Qt::AlignHCenter); on volumeLabel:
volumeDial->setNotchesVisible(true);
volumeDial->setMinimum(0);
volumeDial->setMaximum(100);
The three widgets are added accordingly with each call to the addWidget() method:
layout->addWidget(volumeDial);
layout->addWidget(volumeLabel);
layout->addWidget(volumeLCD);
The total effect of these two connections is that when there is a change in volumeDial, both
volumeLabel and volumeLCD will be updated with the current value of volumeDial. All
this happens at the same time without the application clogging up, all thanks to the
efficient design of signals and slots.
[ 37 ]
Working with Signals and Slots Chapter 3
Compile and run the project. A typical output of the program is as follows:
In the preceding screenshot, when the QDial widget (that is the round-looking object) was
moved to 32, both volumeLabel and volumeLCD were updated. As you move the
dial, volumeLabel and volumeLCD will receive the updates by way of signals and will
update themselves accordingly.
[ 38 ]
Working with Signals and Slots Chapter 3
lengthSlider->setMinimum(0);
lengthSlider->setMaximum(100);
volumeDial->setNotchesVisible(true);
volumeDial->setMinimum(0);
volumeDial->setMaximum(100);
layout->addWidget(volumeDial);
layout->addWidget(lengthSlider);
layout->addWidget(volumeLCD);
QObject::connect(volumeDial, SIGNAL(valueChanged(int)), volumeLCD ,
SLOT(display(int)));
QObject::connect(lengthSlider, SIGNAL(valueChanged(int)), volumeLCD
, SLOT(display(int)));
window->setLayout(layout);
window->show();
return app.exec();
}
The QSlider widget is instantiated and passed Qt::Horizontal, which is a constant that
changes the orientation of the widgets such that it is presented horizontally. Everything else
is the same as we saw in previous examples. The window and layout are instantiated,
together with the QDial and QSlider objects:
lengthSlider->setTickPosition(QSlider::TicksAbove);
lengthSlider->setTickInterval(10);
lengthSlider->setSingleStep(1);
lengthSlider->setMinimum(0);
The first widget that shall emit a signal in this example is the volumeDial object. But now,
the QSlider instance also emits a signal that allows us to get the state of the QSlider
whenever it has changed.
[ 39 ]
Working with Signals and Slots Chapter 3
The setMinimum() and setMaximum() variables are used to set the range of values for our
QSlider instance. The range here is between 0 and 100.
The QVBoxLayout object, layout, adds the lengthSlider widget object to the list of
widgets it will house with the line, layout->addWidget(lengthSlider);:
QObject::connect(volumeDial, SIGNAL(valueChanged(int)), volumeLCD ,
SLOT(display(int)));
QObject::connect(lengthSlider, SIGNAL(valueChanged(int)), volumeLCD ,
SLOT(display(int)));
There are two calls to the static method, connect(). The first call will establish a
connection between the valueChanged(int) signal of volumeDial with
the display(int) slot of volumeLCD. As a result, whenever the QDial object changes, the
value will be passed to the display(int) slot for display.
Compile and run the program from the command line as we have done for the previous
examples.
The first time the application is run, the output should be similar to the following:
[ 40 ]
Working with Signals and Slots Chapter 3
Both QDial and QSlider are at zero. Now, we will move the QDial to 48. See how the
QLCDNumber is updated accordingly:
With the way we have set up our signals and slots, it will also be possible for QSlider to
also update the same widget, volumeLCD. When we move QSlider, we will see that
volumeLCD is updated immediately by its value:
As can be seen, QSlider has been moved to the tail end of its range and the value has been
passed onto volumeLCD.
[ 41 ]
Working with Signals and Slots Chapter 3
Summary
In this chapter, we took a look at the core concept of signals and slots in Qt. After creating
our first application, we looked at the various ways in which signals and slots can be
connected.
We saw how to connect one signal from a widget to multiple slots. This is a typical way to
set up signals and slots, especially when a change in the state of a widget has to be
communicated to many other widgets.
To show how flexible signals and slots could be configured, we also looked at an example
where multiple signals were connected to one slot of a widget. This type of arrangement is
useful when different widgets can be used to achieve the same effect on a widget.
In Chapter 4, Implementing Windows and Dialog, we shall change our style of writing
applications and study how to make full-blown window applications.
[ 42 ]
4
Implementing Windows and
Dialog
In the previous chapter, we learned how to animate our application by using signals and
slots to trigger and respond to actions that occur within our application. So far, we have
been concentrating on examples that are contained in only one file and do not expressly
describe a full working application. To do so, we will need to change the style in which our
applications are written, and also adopt a number of new conventions.
In this chapter, we shall work with Windows in Qt, so that by the end of the chapter, you
should be able to do the following:
mainWindow.show();
return a.exec();
}
But this style of writing applications is not the best. Instead, from this point onward, we
shall define our own custom widget, in which we shall define child widgets and make
connections between signals and sockets.
We start off by creating a new folder and defining a header file. The name of our header file
here is mainwindow.h, but feel free to name it how you want and remember to add the .h
suffix. This file should basically contain the following:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
};
#endif
We include the Qt classes QMainWindow, and QLabel in our header file. Then, we subclass
QMainWindow and call it MainWindow. The constructor of this new class is declared with
the following:
public:
MainWindow();
The entire class definition is wrapped within an #ifndef ... #endif directive, which
tells the preprocessor to ignore its content if it is accidentally included multiple times in a
file.
[ 44 ]
Implementing Windows and Dialog Chapter 4
Take notice of the Q_OBJECT macro. This is what makes the signals and slots mechanism
possible. Remember that the C++ language does not know about the keywords used to set
up signals and slots. By including this macro, it becomes part of the C++ syntax.
What we have defined so far is just the header file. The body of the main program has to
live in some other .cpp file. For easy identification, we call it mainwindow.cpp. Create this
file within the same folder and add the following lines of code:
#include "mainwindow.h"
MainWindow::MainWindow()
{
setWindowTitle("Main Window");
resize(400, 700);
QLabel *mainLabel = new QLabel("Main Widget");
setCentralWidget(mainLabel);
mainLabel->setAlignment(Qt::AlignCenter);
}
We include the header file that we defined earlier with the first line of code. The default
constructor of our sub-classed widget, MainWindow, is defined.
Notice how we call the method that sets the title of the window. setWindowTitle() is
invoked and can be accessed from within the constructor since it is an inherited method
from QWindow. There is no need to use the this keyword. The size of the window is
specified by calling the resize() method and passing two integer values to be used as the
dimensions of the window.
An instance of a QLabel is created, mainLabel. The text within the label is aligned to the
center by calling mainLabel->setAlignment(Qt::AlignCenter).
[ 45 ]
Implementing Windows and Dialog Chapter 4
At the very top of every window is the Menu Bar. Elements such as the file, edit, and help
menus go there. Below that, are the Toolbars. Contained within the Toolbars are the Dock
Widgets, which are collapsible panels. Now, the main controls within the window must be
put in the Central Widget location. Since a UI is made up of several widgets, it will be good
to compose a widget that will contain child widgets. This parent widget is what you will
stick into the Central Widget area. To do this, we call setCentralWidget() and pass in
the parent widget. At the bottom of the window, is the Status Bar.
To run the application, we need to create an instance of our custom window class. Create a
file called main.cpp within the same folder where the header and .cpp files are located.
Add the following lines of code to main.cpp:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainwindow;
mainwindow.show();
return app.exec();
}
We include the header file mainwindow.h, which contains the declaration of our custom
class, MainWindow. Without this, the compiler wouldn't know where to find the definition
of the MainWindow class.
[ 46 ]
Implementing Windows and Dialog Chapter 4
An instance of MainWindow is created and the show() method is called on it. We still have
to call the show() method on mainwindow. MainWindow, which is a subclass of
QMainWindow, and behaves just like any widget out there. Furthermore, as we already
know, to cause a widget to appear, you have to call the show() method on it.
To run the program, move into the folder via the command line and issue the following
commands:
% qmake -project
Add QT += widgets to the .pro file that is generated. Now continue with the next set of
commands:
% qmake
% make
Examine the .pro file for a second. At the very bottom of the file, we have the following
lines:
HEADERS += mainwindow.h
SOURCES += main.cpp mainwindow.cpp
The headers are automatically collected and added to HEADERS. Similarly, the .cpp files are
collected and added to SOURCES. Always remember to check this file when there are
compilation errors to ensure that all required files have been added.
For those who work on the macOS, the correct command you will need to issue in order to
run the executable is as follows:
% ./classSimpleWindow.app/Contents/MacOS/classSimpleWindow
[ 47 ]
Implementing Windows and Dialog Chapter 4
Menu bar
Most applications hold a set of clickable(s) that reveal a list of another set of actions that
expose more functionality to the user. The most popular among these are the File, Edit, and
Help menus.
In Qt, menu bars occupy the very top of the window. We shall create a short program to
make use of the menu bar.
Three files must be created in a newly created folder. These are as follows:
main.cpp
mainwindow.h
mainwindow.cpp
The main.cpp file will remain as before in terms of content. Therefore, copy the main.cpp
file from the previous section. Let's examine the mainwindow.h file:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QApplication>
#include <QAction>
#include <QtGui>
#include <QAction>
#include <QMenuBar>
#include <QMenu>
#include <Qt>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
private slots:
private:
// Menus
QMenu *fileMenu;
QMenu *helpMenu;
// Actions
QAction *quitAction;
QAction *aboutAction;
QAction *saveAction;
QAction *cancelAction;
QAction *openAction;
QAction *newAction;
QAction *aboutQtAction;
[ 48 ]
Implementing Windows and Dialog Chapter 4
};
#endif
Once more, the header file is enclosed in an ifndef directive to prevent errors that may
occur as a result of multiple inclusions of this file.
To create a menu within the window, you need instances of QMenu. Each menu, such as the
File menu, will have sub-menus or items that make up the menu. The File menu usually
has the Open, New, and Close sub-menus.
A typical image of a Menu bar is as follows, with the File, Edit, and Help menus. The File
menu items under the File menu are New..., Open..., Save, Save As..., and Quit:
Our application will have only two menus, namely, fileMenu and helpMenu. The other
instances of QAction are the individual menu items: quitAction, saveAction,
cancelAction, and newAction.
Both the menu and sub-menu items are defined as members of the class in the header file.
Furthermore, this kind of declaration will allow users to modify their behavior and also to
easily access them when connecting them to sockets.
Now, let's switch to the mainwindow.cpp. Copy the following code into mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow()
{
setWindowTitle("SRM System");
setFixedSize(500, 500);
QPixmap newIcon("new.png");
QPixmap openIcon("open.png");
QPixmap closeIcon("close.png");
// Setup File Menu
fileMenu = menuBar()->addMenu("&File");
quitAction = new QAction(closeIcon, "Quit", this);
[ 49 ]
Implementing Windows and Dialog Chapter 4
quitAction->setShortcuts(QKeySequence::Quit);
newAction = new QAction(newIcon, "&New", this);
newAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
openAction = new QAction(openIcon, "&New", this);
openAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addSeparator();
fileMenu->addAction(quitAction);
helpMenu = menuBar()->addMenu("Help");
aboutAction = new QAction("About", this);
aboutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_H));
helpMenu->addAction(aboutAction);
// Setup Signals and Slots
connect(quitAction, &QAction::triggered, this, &QApplication::quit);
}
The header file, mainwindow.h, is included at the beginning of the file to make available
the class declaration and Qt classes that will be used in the program.
In the default constructor of our custom class, MainWindow, we start by setting the name of
our window by calling setWindowTitle() and giving it an appropriate name. The size of
our window is then established by calling setFixedSize(). This is demonstrated in the
following code block:
QPixmap newIcon("new.png");
QPixmap openIcon("open.png");
QPixmap closeIcon("close.png");
Menu items can be displayed with images beside them. To associate an image or icon with
a menu item, QAction, you need to first capture that image within an instance of QPixmap.
Three such images are captured in the newIcon, openIcon, and
closeIcon variables. These will be used further down the code.
To add a menu to the window, a call to menuBar() is made. This returns an instance of
QMenu, and we call addMenu on that object specifying the name of the menu we want to
add. Here, we call our first menu, File. The "&" sign in front of the F in File will make it
possible to press Alt + F on the keyboard.
[ 50 ]
Implementing Windows and Dialog Chapter 4
Now that we have our menu in fileMenu and the menu items in quitAction,
newAction, and openActions, we need to link them together:
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addSeparator();
fileMenu->addAction(quitAction);
To add a sub-menu item, we call the addAction() method on the QMenu instance,
fileMenu, and pass the required QAction instance. The addSeparator() is used to insert
a visual marker in our list of menu items. It also returns an instance of QAction, but we are
not interested in that object at this moment.
A second menu is added to the application along with its only sub-menu item:
helpMenu = menuBar()->addMenu("Help");
aboutAction = new QAction("About", this);
aboutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_H));
helpMenu->addAction(aboutAction);
QAction encapsulates a general idea of an action that can be inserted into widgets. Here,
we used QAction to insert actions into our menus.
These QAction instances emit the triggered signal, which can be connected to a socket to
cause the application to change, as follows:
connect(quitAction, &QAction::triggered, this, &QApplication::quit);
When connecting a signal to a slot within a class definition, simply call the connect()
method and pass in the parameters as you would do normally. The first parameter is the
object that is going to emit the signal we are interested in. &QAction::triggered is one
way of specifying the triggered signal. This is the same as writing SIGNAL(triggered()).
The this keyword refers to the MainWindow object that will be created in the future. The
quit slot is specified by &QApplication::quit.
[ 51 ]
Implementing Windows and Dialog Chapter 4
The signal and slot connected will create a situation where, when the File menu is opened
and the Close button is clicked, the application will close.
The last file needed to run this example is the main.cpp file. The previous main.cpp file
created should be copied over to this project.
On a Mac, press the key combination Command + Q and that will close the application. On
Linux and Windows, Alt + F4 should do the same. This is made possible by the following
line of code:
quitAction->setShortcuts(QKeySequence::Quit);
This line of code blurs out the difference by relying on Qt's QKeySequence::Quit,
depending on the OS in use.
Nothing happens. That is because we did not define what should happen when the user
clicks on that action. The last menu item, Quit, on the other hand, closes the application as
defined by the socket and slot we declared.
Also, take note of how each menu item has an appropriate icon or image in front of it.
Visit the Packt website to obtain the images for this book.
[ 52 ]
Implementing Windows and Dialog Chapter 4
Toolbar
Beneath the menu bar is a panel that is usually referred to as toolbar. It contains a set of
controls that could be widgets or instances of QAction, just as we saw in their use in
creating the menu bar. This also means that you may choose to replace the QAction with a
widget, such as a regular QPushButton or QComboBox.
Toolbars may be fixed to the top of the window (beneath the menu bar) and can be pinned
there or made to float around the dock widget.
Once again, we will need to create a new project or modify the one from the previous
section of this chapter. The files that we will be creating are main.cpp, mainwindow.h, and
mainwindow.cpp.
The main.cpp file remains the same, as follows. We only instantiate our custom class and
call show() on it:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); //
MainWindow mainwindow;
mainwindow.show();
return app.exec();
}
The mainwindow.h file will essentially contain the QAction members that will hold the
actions in our toolbar:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QApplication>
#include <QAction>
#include <QPushButton>
#include <QAction>
#include <QMenuBar>
#include <QMenu>
#include <QtGui>
#include <Qt>
#include <QToolBar>
#include <QTableView>
class MainWindow : public QMainWindow
{
[ 53 ]
Implementing Windows and Dialog Chapter 4
Q_OBJECT
public:
MainWindow();
private slots:
private:
// Menus
QMenu *fileMenu;
QMenu *helpMenu;
// Actions
QAction *quitAction;
QAction *aboutAction;
QAction *saveAction;
QAction *cancelAction;
QAction *openAction;
QAction *newAction;
QAction *aboutQtAction;
QToolBar *toolbar;
QAction *newToolBarAction;
QAction *openToolBarAction;
QAction *closeToolBarAction;
};
#endif
This header file appears the same as before. The only difference is the QToolbar
instance, *toolbar, and the QAction objects that will be shown within the toolbar. These
are newToolBarAction, openToolBarAction, and closeToolBarAction. The QAction
instances that are used in a menu are the same as the ones used for toolbars.
[ 54 ]
Implementing Windows and Dialog Chapter 4
openAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addSeparator();
fileMenu->addAction(quitAction);
helpMenu = menuBar()->addMenu("Help");
aboutAction = new QAction("About", this);
aboutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_H));
helpMenu->addAction(aboutAction);
// Setup Tool bar menu
toolbar = addToolBar("main toolbar");
// toolbar->setMovable( false );
newToolBarAction = toolbar->addAction(QIcon(newIcon), "New File");
openToolBarAction = toolbar->addAction(QIcon(openIcon), "Open File");
toolbar->addSeparator();
closeToolBarAction = toolbar->addAction(QIcon(closeIcon), "Quit
Application");
// Setup Signals and Slots
connect(quitAction, &QAction::triggered, this, &QApplication::quit);
connect(closeToolBarAction, &QAction::triggered, this,
&QApplication::quit);
}
The same set of icons used for the menu bar will be used for the toolbars too.
To obtain an instance of the Windows toolbar for further manipulation, call the
addTooBar() method, which will return an instance of a QToolBar. The method accepts
any text that is used as the title of the window. It also adds the toolbar to the window.
The toolbar at this point can be moved around within the window. To fix it to the top of the
window, call the toolbar->setMovable(false); function on the instance of the
QToolBar, toolbar:
Two QAction objects are created and passed to the newToolBarAction and
openToolBarAction objects. We pass the QIcon object that becomes the image on the
QAction and a name or text to be displayed as a tooltip. A separator is added to the toolbar
by calling the addSeparator() method. The last control, closeToolBarAction, contains
an image to be displayed on the toolbar.
[ 55 ]
Implementing Windows and Dialog Chapter 4
To link the trigger signal of closeToolBarAction to the quit slot of the window, we do
the following:
connect(closeToolBarAction, &QAction::triggered, this,
&QApplication::quit);
Add QT += widgets to the .pro file that is generated and make sure all three files are
listed in the bottom of the file:
The preceding screenshot shows the toolbar beneath the File and Help menus. Three icons
show three QAction objects that represent the New, Open, and Close actions. Only the last
button (to close the application) action works. That is because we only defined a single
signal-slot connection for the closeToolBarAction and QAction objects.
By hovering the mouse over the toolbar menu items, some text appears. This message is
called a tooltip. As can be seen in the preceding diagram, the Open File message is derived
from the last parameter of the following line:
openToolBarAction = toolbar->addAction(QIcon(openIcon), "Open File");
[ 56 ]
Implementing Windows and Dialog Chapter 4
As you can see, by clicking on the three vertical dots on the left-hand side of the toolbar and
moving it, you can detach the toolbar from the top to either the left, right, or bottom. To
display this kind of functionality, issue the following command:
toolbar->setMovable(false);
This will fix the toolbar to the top so that it can't be moved around.
There will be a form where the details of a number of contacts will be received. This detail
will then be added to a list on the window. As more contacts are added, the list will grow.
We shall base these on the previous section's code and continue to build on it.
As usual, you create a new folder with the three files, namely, main.cpp,
mainwindow.cpp, and mainwindow.h. The main.cpp file will remain as before from the
previous sections.
[ 57 ]
Implementing Windows and Dialog Chapter 4
#include <QLabel>
#include <QLineEdit>
#include <QDate>
#include <QDateEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QMessageBox>
#include <QAction>
#include <QMenuBar>
#include <QMenu>
#include <QtGui>
#include <Qt>
#include <QToolBar>
#include <QTableView>
#include <QHeaderView>
The file imports the classes that will be used in declaring the members within our custom
class. The whole file is wrapped with the #ifndef directive so that the header file can be
included multiple times without yielding errors.
Add the following lines of code to the same header file, mainwindow.h:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
private slots:
void saveButtonClicked();
There is only one slot in our application that will be used to move the content of a number
of widgets into a list.
Continue the code listing by adding the following lines of code that will add the members
of the class and define the prototype of some helper methods:
private:
// Widgets
QWidget *mainWidget;
QVBoxLayout *centralWidgetLayout;
QGridLayout *formLayout;
QHBoxLayout *buttonsLayout;
QLabel *nameLabel;
QLabel *dateOfBirthLabel;
[ 58 ]
Implementing Windows and Dialog Chapter 4
QLabel *phoneNumberLabel;
QPushButton *savePushButton;
QPushButton *newPushButton;
QLineEdit *nameLineEdit;
QDateEdit *dateOfBirthEdit;
QLineEdit *phoneNumberLineEdit;
QTableView *appTable;
QStandardItemModel *model;
// Menus
QMenu *fileMenu;
QMenu *helpMenu;
// Actions
QAction *quitAction;
QAction *aboutAction;
QAction *saveAction;
QAction *cancelAction;
QAction *openAction;
QAction *newAction;
QAction *aboutQtAction;
QAction *newToolBarAction;
QAction *openToolBarAction;
QAction *closeToolBarAction;
QAction *clearToolBarAction;
// Toolbar
QToolBar *toolbar;
// Icons
QPixmap newIcon;
QPixmap openIcon;
QPixmap closeIcon;
QPixmap clearIcon;
// init methods
void clearFields();
void createIcons();
void createMenuBar();
void createToolBar();
void setupSignalsAndSlot();
void setupCoreWidgets();
};
#endif
The members include layout and other widget classes, classes for our menu, toolbars, and
their associated QAction objects.
As you can see, the code is borrowed from the previous section with the exception of the
widgets being added.
[ 59 ]
Implementing Windows and Dialog Chapter 4
In the mainwindow.cpp file, we shall define our class with the following lines of code:
#include "mainwindow.h"
#include "mainwindow.h"
MainWindow::MainWindow()
{
setWindowTitle("Form in Window");
setFixedSize(500, 500);
createIcons();
setupCoreWidgets();
createMenuBar();
createToolBar();
centralWidgetLayout->addLayout(formLayout);
centralWidgetLayout->addWidget(appTable);
centralWidgetLayout->addLayout(buttonsLayout);
mainWidget->setLayout(centralWidgetLayout);
setCentralWidget(mainWidget);
setupSignalsAndSlots();
}
The default constructor has been refactored a great deal here. The building blocks of code
have been moved away into functions to help make the code readable.
Now, we only set the window title and size of the application window. Next, we call the
method that will create the icons that will be used by the various widgets. Another function
call is made to set up the core widgets by calling the setupCoreWidgets() method. The
menu and toolbars are created by calling the createMenuBar() and createToolBar()
methods.
The layout object, centralWidgetLayout, is the main layout of our application. We add
the formLayout object first, followed by the appTable object. As you can see, it is possible
to insert a layout into another layout. Lastly, we insert the buttonsLayout object, which
contains our buttons.
[ 60 ]
Implementing Windows and Dialog Chapter 4
Add the following lines of code to the mainwindow.cpp file that defines the
createIcons() method:
void MainWindow::createIcons() {
newIcon = QPixmap("new.png");
openIcon = QPixmap("open.png");
closeIcon = QPixmap("close.png");
clearIcon = QPixmap("clear.png");
}
The createIcons() method will pass instances of QPixmap to the members that were
declared in mainwindow.h.
[ 61 ]
Implementing Windows and Dialog Chapter 4
formLayout->addWidget(nameLineEdit, 0, 1);
formLayout->addWidget(dateOfBirthLabel, 1, 0);
formLayout->addWidget(dateOfBirthEdit, 1, 1);
formLayout->addWidget(phoneNumberLabel, 2, 0);
formLayout->addWidget(phoneNumberLineEdit, 2, 1);
buttonsLayout->addStretch();
buttonsLayout->addWidget(savePushButton);
buttonsLayout->addWidget(newPushButton);
}
Here, we are just instantiating objects to be used within the application. There is nothing
out of the ordinary here. nameLineEdit and phoneNumberLineEdit will be used to
collect the name and phone number of contacts about to be saved. dateOfBirthEdit is a
special kind of textbox that allows you to specify a date. savePushButton and
newPushButton are buttons that will be used to trigger the saving of the contact and the
clearing of the list.
The labels and line edit controls will be used in the formLayout object, which is a
QGridLayout instance. QGridLayout allows widgets to be specified using columns and
rows.
To save a contact, this means we will save it to a widget that can display a list of items. Qt
has a number of such widgets. These include QListView, QTableView, and QTreeView.
When the QListView is used in displaying information, it will typically appear as in the
following screenshot:
[ 62 ]
Implementing Windows and Dialog Chapter 4
QTableView will use columns and rows to display data or information in cells as follows:
This line is used to help us define a custom action that should happen when we raise a
context menu on the table:
appTable->horizontalHeader()->setSectionResizeMode(
QHeaderView::Stretch); /** Note **/
[ 63 ]
Implementing Windows and Dialog Chapter 4
The preceding line is important and enables the headers of our table to stretch out fully.
This is the result when we omit that line (as shown in an area bounded by the red box):
Ideally, we want our table to have the following header, so that it looks like this:
To set the header for the table, we can do so with the following lines of code:
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Name")));
The table for displaying the contacts needs headers. The setHorizontalHeaderItem()
method on the model object uses the first parameter to indicate the position where the new
QStandardItem(QString()) should be inserted. Because our table uses three columns,
the line is repeated three times for the headers, Name, Date of Birth, and Phone Number:
appTable->setModel(model);
QStandardItem *firstItem = new QStandardItem(QString("G. Shone"));
QDate dateOfBirth(1980, 1, 1);
QStandardItem *secondItem = new QStandardItem(dateOfBirth.toString());
QStandardItem *thirdItem = new QStandardItem(QString("05443394858"));
model->setItem(0,0,firstItem);
model->setItem(0,1,secondItem);
model->setItem(0,2,thirdItem);
We make model the model of our QTableView by calling setModel() on appTable and
passing model as a parameter.
To populate our model, which updates its view, QTableView, we shall create instances of
QStandardItem. Each cell in our table has to be encapsulated in this class. dateOfBirth is
of the QDate type, so we call toString() on it and pass it to new QStandardItem().
firstItem is inserted into our model by specifying the row and column as in the line
model->setItem(0, 0, firstItem);.
[ 64 ]
Implementing Windows and Dialog Chapter 4
Now, let's populate our formLayout object. This is of the QGridLayout type. To insert
widgets into our layout, use the following lines of code:
formLayout->addWidget(nameLabel, 0, 0);
formLayout->addWidget(nameLineEdit, 0, 1);
formLayout->addWidget(dateOfBirthLabel, 1, 0);
formLayout->addWidget(dateOfBirthEdit, 1, 1);
formLayout->addWidget(phoneNumberLabel, 2, 0);
formLayout->addWidget(phoneNumberLineEdit, 2, 1);
We add widgets to the layout by calling addWidget(), supplying the widget, and the row
and column it is supposed to fill. 0, 0 will fill the first cell, 0, 1 will fill the second cell on
the first row, and 1, 0 will fill the first cell on the second row.
To push savePushButton and newPushButton to the right, we first add a stretch that will
expand and fill the empty space by calling addStretch() before a call to add the widgets
is made by addWidget().
Before we come to the menus in the application, add the following code. To include menus
and a toolbar to our application, add the definition of createMenuBar() and
createToolBar() to the mainwindow.cpp file:
void MainWindow::createMenuBar() {
// Setup File Menu
fileMenu = menuBar()->addMenu("&File");
quitAction = new QAction(closeIcon, "Quit", this);
quitAction->setShortcuts(QKeySequence::Quit);
newAction = new QAction(newIcon, "&New", this);
newAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
openAction = new QAction(openIcon, "&New", this);
openAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addSeparator();
fileMenu->addAction(quitAction);
helpMenu = menuBar()->addMenu("Help");
aboutAction = new QAction("About", this);
aboutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_H));
helpMenu->addAction(aboutAction);
[ 65 ]
Implementing Windows and Dialog Chapter 4
}
void MainWindow::createToolBar() {
// Setup Tool bar menu
toolbar = addToolBar("main toolbar");
// toolbar->setMovable( false );
newToolBarAction = toolbar->addAction(QIcon(newIcon), "New File");
openToolBarAction = toolbar->addAction(QIcon(openIcon), "Open File");
toolbar->addSeparator();
clearToolBarAction = toolbar->addAction(QIcon(clearIcon), "Clear All");
closeToolBarAction = toolbar->addAction(QIcon(closeIcon), "Quit
Application");
}
The preceding code is familiar code that adds a toolbar and menus to our window. The
final lines of code define the setupSignalsAndSlots() method:
void MainWindow::setupSignalsAndSlots() {
// Setup Signals and Slots
connect(quitAction, &QAction::triggered, this, &QApplication::quit);
connect(closeToolBarAction, &QAction::triggered, this,
&QApplication::quit);
connect(savePushButton, SIGNAL(clicked()), this,
SLOT(saveButtonClicked()));
}
In the preceding code, we connect the triggered signal of quitAction to the quit slot of
QApplication. The triggered signal of closeToolBarAction is connected to the same, to
achieve the effect of closing the application.
The exact operation that ensures that the information input into the form is saved, is
defined by the saveButtonClicked() function that serves a slot.
[ 66 ]
Implementing Windows and Dialog Chapter 4
When saveButtonClicked() is invoked, we shall extract the values within the controls,
nameLinedEdit, dateOfBirthEdit, and phoneNumberLineEdit. We append them to
the model by calling appendRow() on the model object. We can access the model object
because it is a member point variable in our class definition.
After appending the new contact information into the list, all the fields are cleared and reset
with a call to clearFields().
The nameLineEdit object is reset to an empty string by calling the clear() method. This
method also doubles as a slot. Another way to set a QLineEdit object to an empty string is
by setting the text to "" by calling the setText(""):
Because QDateEdit accepts dates, we have to create an instance of date and pass it to
setDate() of dateOfBirthEdit.
[ 67 ]
Implementing Windows and Dialog Chapter 4
Compile and run the project. You should see the following output:
To add a new contact, complete the form and click on the Save button:
[ 68 ]
Implementing Windows and Dialog Chapter 4
After clicking on the Save button, you should see the following:
[ 69 ]
Implementing Windows and Dialog Chapter 4
The preceding code listing will yield an output such as the following:
This QMessageBox instance is being used to communicate to the user that an operation was
successful.
Let's complete the contact application being written to show how QMessageBox and
QInputDialog are used.
Choose to build upon the example in the previous section or create a new folder with the
three main files we have been working with so far, that is, main.cpp, mainwindow.cpp,
and mainwindow.h.
[ 70 ]
Implementing Windows and Dialog Chapter 4
#include <QMenuBar>
#include <QMenu>
#include <QtGui>
#include <Qt>
#include <QToolBar>
#include <QTableView>
#include <QHeaderView>
#include <QInputDialog>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
private slots:
void saveButtonClicked();
void aboutDialog();
void clearAllRecords();
void deleteSavedRecord();
private:
// Widgets
QWidget *mainWidget;
QVBoxLayout *centralWidgetLayout;
QGridLayout *formLayout;
QHBoxLayout *buttonsLayout;
QLabel *nameLabel;
QLabel *dateOfBirthLabel;
QLabel *phoneNumberLabel;
QPushButton *savePushButton;
QPushButton *clearPushButton;
QLineEdit *nameLineEdit;
QDateEdit *dateOfBirthEdit;
QLineEdit *phoneNumberLineEdit;
QTableView *appTable;
QStandardItemModel *model;
// Menus
QMenu *fileMenu;
QMenu *helpMenu;
// Actions
QAction *quitAction;
QAction *aboutAction;
QAction *saveAction;
QAction *cancelAction;
QAction *openAction;
QAction *newAction;
QAction *aboutQtAction;
QAction *newToolBarAction;
QAction *openToolBarAction;
QAction *closeToolBarAction;
[ 71 ]
Implementing Windows and Dialog Chapter 4
QAction *clearToolBarAction;
QAction *deleteOneEntryToolBarAction;
// Icons
QPixmap newIcon;
QPixmap openIcon;
QPixmap closeIcon;
QPixmap clearIcon;
QPixmap deleteIcon;
// Toolbar
QToolBar *toolbar;
void clearFields();
void createIcons();
void createMenuBar();
void createToolBar();
void setupSignalsAndSlots();
void setupCoreWidgets();
};
#endif
The only notable change is the increase in the number of slots. The saveButtonClicked()
slot will be reimplemented to pop up a message telling the user of a successful save action.
The aboutDialog() slot will be used to show an about message. This is usually a window
that conveys information about the program and usually contains copyright, help, and
contact information.
The clearAllRecords() slot will invoke a question message box that will prompt the
user of the destructive action about to be taken. deleteSavedRecord() will
use QInputDialog to accept input from the user as to which row to remove from our list of
saved contacts.
QAction *aboutQtAction will be used to invoke the slot to display the about page or
message. We shall also add a toolbar action, QAction *deleteOneEntryToolBarAction,
that will be used to invoke a dialog box that will receive input from the user. Observe these
three inputs, QPixmap deleteIcon, QPixmap clearIcon, and QPixmap deleteIcon, as
we add more actions to the window and, likewise, the QPushButton*clearPushButton,
which is replacing newPushButton in the previous example.
Everything else about the header file remains the same. The two extra classes imported are
the QMessageBox and QInputDialog classes.
In the mainwindow.cpp file, we define the default constructor of the MainWindow class as
follows:
#include "mainwindow.h"
MainWindow::MainWindow()
[ 72 ]
Implementing Windows and Dialog Chapter 4
{
setWindowTitle("RMS System");
setFixedSize(500, 500);
setWindowIcon(QIcon("window_logo.png"));
createIcons();
setupCoreWidgets();
createMenuBar();
createToolBar();
centralWidgetLayout->addLayout(formLayout);
centralWidgetLayout->addWidget(appTable);
//centralWidgetLayout->addStretch();
centralWidgetLayout->addLayout(buttonsLayout);
mainWidget->setLayout(centralWidgetLayout);
setCentralWidget(mainWidget);
setupSignalsAndSlots();
}
This time, we want to give the whole application an icon that will show up in a taskbar or
dock when it is running. To do this, we call the setWindowIcon() method and pass in an
instance of QIcon("window_logo.png").
The window_logo.png file is included in the project, along with the other
image files being used as an attachment on the Packt site for this book.
Everything remains the same as before in the previous example. The methods that are
setting up the various parts of the application have been modified slightly.
[ 73 ]
Implementing Windows and Dialog Chapter 4
void MainWindow::aboutDialog()
{
QMessageBox::about(this, "About RMS System","RMS System 2.0"
"<p>Copyright © 2005 Inc." "This is a simple application to
demonstrate the use of windows," "tool bars, menus and dialog boxes");
}
The static method, QMessageBox::about(), is called with this as its first argument. The
title of the window is the second argument, and a string that describes the application is
given as the third parameter.
At runtime, click on the Help menu and then click on About. You should see the following
output:
In the clearAllRecords() slot, we will first ask the user with the aid of a prompt if they
are sure they want all the items in a model to be removed. This can be achieved by the
following code:
int status = QMessageBox::question( this, tr("Delete Records ?"), tr("You
are about to delete all saved records "
"<p>Are you sure you want to delete all records "),
QMessageBox::No|QMessageBox::Default, QMessageBox::No|QMessageBox::Escape,
QMessageBox::NoButton);
if (status == QMessageBox::Yes)
return model->clear();
[ 74 ]
Implementing Windows and Dialog Chapter 4
QMessageBox::question is used to raise a dialog to ask the user a question. It has two
main buttons, Yes and No. QMessageBox::No|QMessageBox::Default sets the
No option as the default selection. QMessageBox::No|QMessageBox::Escape makes the
escape key have the same effect as clicking on the No option.
Whatever option the user chooses will be stored as int in the status variable. It will then be
compared to the QMessageBox::Yes constant. This way of asking the user a Yes or No
question is not informative enough, especially when a destructive operation will ensue
when the user clicks Yes. We shall use the alternative form as defined in
clearAllRecords():
void MainWindow::clearAllRecords()
{
*/
int status = QMessageBox::question( this, tr("Delete all Records ?"),
tr("This operation will delete all saved records. " "<p>Do you want to
remove all saved records ? "
), tr("Yes, Delete all records"), tr("No !"), QString(), 1, 1);
if (status == 0) {
int rowCount = model->rowCount();
model->removeRows(0, rowCount);
}
}
As usual, the parent object is pointed to by this. The second parameter is the title of the
dialog box and the string of the question follows. We shall make the first option verbose by
passing Yes, Delete all records. The user, upon reading, will know what effect the clicking
of the button will have. The No ! parameter will be displayed on the button that represents
the other answer to the question. QString() is being passed so that we don't display the
third button. When the first button is clicked, 0 will be returned to status. When the
second button or option is clicked, 1 will be returned. By specifying 1, we make the "No !"
button the default button of the dialog box. We select 1 again, as the last parameter
specifies that "No !" should be the button selected when the escape button is pressed.
If the user clicks on the Yes, Delete all records button, then status will store 0. In the body
of the if statement, we obtain the number of rows in our model object. A call to
removeRows is made and we specify that all the entries from the first, represented by 0, to
the rowCount, should be removed. However, if the user clicks on the No ! button, the
application will do nothing, as we don't specify that in the if statement.
[ 75 ]
Implementing Windows and Dialog Chapter 4
The dialog window should appear as follows when the Clear All button is clicked:
The saveButtonClicked() slot has also been modified to show a simple message to the
user that the operation has been successful, as demonstrated in the following block of code:
void MainWindow::saveButtonClicked()
{
QStandardItem *name = new QStandardItem(nameLineEdit->text());
QStandardItem *dob = new
QStandardItem(dateOfBirthEdit->date().toString());
QStandardItem *phoneNumber = new
QStandardItem(phoneNumberLineEdit->text());
model->appendRow({ name, dob, phoneNumber});
clearFields();
QMessageBox::information(this, tr("RMS System"), tr("Record saved
successfully!"),
QMessageBox::Ok|QMessageBox::Default,
QMessageBox::NoButton, QMessageBox::NoButton);
}
The two last parameters are constants that prevent buttons from showing in the message
box.
To allow the application to remove certain rows from the table, the
deleteSaveRecords() method is used to raise an input-based dialog box that receives the
rowId of the row we want to remove through the model:
void MainWindow::deleteSavedRecord()
{
bool ok;
int rowId = QInputDialog::getInt(this, tr("Select Row to delete"),
tr("Please enter Row ID of record (Eg. 1)"),
1, 1, model->rowCount(), 1, &ok );
if (ok)
{
model->removeRow(rowId-1);
}
}
[ 76 ]
Implementing Windows and Dialog Chapter 4
The this keyword refers to the parent object. The second parameter to the call of the static
method QInputDialog::getInt() is used as the title of the dialog window. The request
is captured in the second parameter. The third parameter here is used to specify the default
number of the input field. 1, and model->rowCount(), are the minimum and maximum
values that should be accepted.
The last but one parameter, 1, is the incremental step between the minimum and maximum
value. True or False will be stored in &ok. When the user clicks OK, True will be stored in
&ok and, based on that, the if statement will call the removeRow on the model object.
Whatever value that the user inputs will be passed to rowId. We pass rowId-1 to get the
actual index of the row in the model.
The following screenshot is what will appear when the user clicks on this action:
[ 77 ]
Implementing Windows and Dialog Chapter 4
All the other methods are borrowed from the previous section and can be obtained from
the source code attached to this book.
To recap, this is what you should see after compiling and running the project:
Remember that the reason we already have an entry in the model object is because we
created such an entry within the setupCoreWidgets() method.
Fill in the name, date of birth, and phone number fields and click on Save. This will add an
extra line to the table in the window. A dialog message will tell you if the operation was
successful.
To delete a row within the table, select the desired row and click on the recycle bin icon,
and confirm whether you really want to delete the entry.
Summary
In this chapter, we have seen how to create menus, toolbars, and how to use dialog boxes to
receive further input and display information to the user.
In Chapter 5, Managing Events, Custom Signals, and Slots, we will explore the use of events
and more on signals and slots.
[ 78 ]
5
Managing Events, Custom
Signals, and Slots
This chapter introduces the concept of events. To maintain a working state, messages are
passed around from the windowing system to the application, and within the application
itself. These messages may contain data that could be useful when delivered at some
destination. The messages being talked about here are referred to as events in Qt.
Events
Event handlers
Drag and drop
Custom signals
Events
In Qt, all events that occur are encapsulated in objects that inherit from the QEvent abstract
class. An example of an event that has occurred is when a window has been resized or
moved. The change in the state of the application will be noticed, and an appropriate
QEvent object will be created to represent it.
The application event loop delivers this object to certain objects that inherit from QObject.
This QEvent object is handled by means of a method call that will be invoked.
Managing Events, Custom Signals, and Slots Chapter 5
There are different types of events. When a mouse is clicked, a QMouseEvent object is
created to represent this. The object will contain additional information, such as the specific
mouse button that was clicked, together with the location where that event occurred.
Event handlers
All QObjects have an event() method that receives events. For QWidgets, this method
will relay the event object to more specific event handlers. It is possible to redefine what an
event handler should do by sub-classing the widget of interest and re-implementing that
event handler.
Create a folder containing the main.cpp, mainwindow.cpp, and mainwindow.h files. The
mainwindow.h file should contain the following code:
#include <QMainWindow>
#include <QMoveEvent>
#include <QMainWindow>
class MainWindow: public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
protected:
void moveEvent(QMoveEvent *event);
};
When a window is moved, the event() method of the QMainWindow object will be called.
The event will be further encapsulated in a QMoveEvent object and forwarded to the
moveEvent() event handler. Since we are interested in changing the behavior of the
window when it is moved, we define our own moveEvent().
[ 80 ]
Managing Events, Custom Signals, and Slots Chapter 5
In the default constructor, the title of the window is set. The event object carries the
coordinates of where the window currently is. Then event->pos().x() is called to obtain
the x coordinate, likewise the y coordinate is obtained by calling event->pos().y().
We convert yCord and xCord to text and store them in text. To access the status bar of the
window, statusBar() is called and text is passed to the showMessage() method of the
status bar object returned from the call to statusBar().
Compile and run the application. Note how the status bar changes when you move the
application window.
Here are two screenshots showing how the status bar, located at the bottom of the window,
changed when the window was moved.
[ 81 ]
Managing Events, Custom Signals, and Slots Chapter 5
When the window was moved, it later showed the output as shown in the following
screenshot:
Note the very bottom of the window and how it has changed. Continuously move the
window around and observe how the status bar changes.
Apart from the events generated by the windowing system, other events are generated by
Qt. The example here will illustrate how to tell Qt to send our application timer-based
events at certain intervals.
As usual, we shall start with the three main files we usually create, that is, main.cpp,
mainwindow.cpp, and mainwindow.h. The project builds upon the previous example.
[ 82 ]
Managing Events, Custom Signals, and Slots Chapter 5
To receive the timer events, we shall implement our own timerEvent method, which will
be the destination of the event that is given off when a timer expires. That is the essence of
adding the void timerEvent(QTimerEvent *event) signature. The QLabel
currentDateTimeLabel instance will be used to display the date and time.
In the mainwindow.cpp file, the default constructor is defined by the following code:
#include <QDateTime>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow (parent){
setWindowTitle("Locate Window");
currentDateTimeLabel = new QLabel("Current Date and Time");
currentDateTimeLabel->setAlignment(Qt::AlignCenter);
setCentralWidget(currentDateTimeLabel);
startTimer(1000);
}
The title for the window is set. An instance of QLabel is created and the call to
setAlignment ensures that its content says centered. Then currentDateTimeLabel is
passed to the setCentralWidget() method. The startTimer(1000) method starts a
timer and will trigger a QTimerEvent object every second, represented by 1000.
For each second, we now need to define what should happen by re-implementing the
timerEvent() method.
Every second, the timerEvent() will be called and passed an instance of QTimerEvent.
The Q_UNUSED (event) is used to keep the compiler from complaining that event() is
not being used in any way. A string representation of the current date and time is passed to
dateTime and set as the text for the currentDateTimeLabel instance variable.
The main.cpp file remains the same as before. As a reference it is presented once more, as
shown in the following code:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[]){
QApplication app(argc, argv);
MainWindow window;
[ 83 ]
Managing Events, Custom Signals, and Slots Chapter 5
window.resize(300, 300);
window.show();
return app.exec();
}
The application will initially show the text, current date, and time, but after a second it
should change and display the updated time. Every second that passes will cause text to be
updated too.
The application is a small text editor. When a text file is dropped into the text area, it will
open and insert the contents of that text file into the text area. The status of the window will
show the number of characters in the text area, which is an instance of a QTextEdit.
This example application also illustrates a very important point about events. To customize
a widget, one has to change the existing behavior of that widget by overriding its event
handlers. Signals and slots are not considered when trying to customize widgets (except
events).
[ 84 ]
Managing Events, Custom Signals, and Slots Chapter 5
The dragTextEdit.h and dragTextEdit.cpp files will contain the definition of our
custom widget. The mainwindow.cpp and mainwindow.h files will be used to construct
the application.
Let's start with the custom QTextEdit widget. Insert the following lines of code into
dragTextEdit.h:
#ifndef TEXTEDIT_H
#define TEXTEDIT_H
#include <QMoveEvent>
#include <QMouseEvent>
#include <QDebug>
#include <QDateTime>
#include <QTextEdit>
#include <QMimeData>
#include <QMimeDatabase>
#include <QMimeType>
class DragTextEdit: public QTextEdit
{
Q_OBJECT
public:
explicit DragTextEdit(QWidget *parent = nullptr);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
};
#endif
The DragTextEdit custom widget, inherits from QTextEdit. The default constructor is
declared. In order to accept a drop event, we need to override the following methods to
ensure proper behavior, as shown in the following code:
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override
[ 85 ]
Managing Events, Custom Signals, and Slots Chapter 5
Now that the header file has been created, open the dragTextEdit.cpp file and add the
definition of the default constructor, as shown in the following code:
#include "dragTextEdit.h"
DragTextEdit::DragTextEdit(QWidget *parent) : QTextEdit(parent)
{
setAcceptDrops(true);
}
The #include directive imports the header file, after which the default constructor is
defined. In order for our widget to accept a drop event, we need to say so by calling the
setAcceptDrops(true) method.
We now have to add the definition of the methods we want to override. Add the following
lines to dragTextEdit.cpp:
void DragTextEdit::dragMoveEvent(QDragMoveEvent *event)
{
event->acceptProposedAction();
}
void DragTextEdit::dragLeaveEvent(QDragLeaveEvent *event)
{
event->accept();
}
void DragTextEdit::dragEnterEvent(QDragEnterEvent *event)
{ event->acceptProposedAction();
}
These event handlers deal with the major steps involved when there is going to be a drop
action. The acceptProposedAction() method is called on the event object in the
dragEnterEvent() and dragMoveEvent() methods. These events are called when the
cursor in drag mode is on the boundary of the widget that calls the setAcceptDrops()
method. If you refuse to call the acceptProposedAction() method, the drop behavior
may misbehave.
The dragMoveEvent() event handler is called when the cursor is within the widget of
interest. But to define what happens when the drop event happens we need to define the
dropEvent() handler.
[ 86 ]
Managing Events, Custom Signals, and Slots Chapter 5
The mime data of the file, encapsulated within the event object, is obtained by calling
event->mimeData(). If it contains text data, we extract the contents of the file and call the
setText() method belonging to QTextEdit. This will populate the DragTextEdit
instance with that text. Note the fact that we continue to call
event->acceptProposedAction() to tell Qt that we have handled this event. If, on the
other hand, event->ignore() is called, it is taken as an unwanted event or action and is,
as such, propagated to a parent widget.
[ 87 ]
Managing Events, Custom Signals, and Slots Chapter 5
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
private slots:
void updateStatusBar();
private:
DragTextEdit *slateDragTextEdit;
};
#endif
The QMainWindow, QLabel class with the other usual classes are imported along with
the dragTextEdit.h header file, which allows the inclusion of our custom class. A slot
that will be called anytime text is added or removed from the DragTextEdit widget and is
declared. Lastly, an instance of DragTextEdit is created.
Create and open the mainwindow.cpp file and insert the following code:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow (parent)
{
QWidget *mainWidget = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
slateDragTextEdit = new DragTextEdit();
layout->addWidget(slateDragTextEdit);
mainWidget->setLayout(layout);
setCentralWidget(mainWidget);
statusBar()->showMessage(QString::number(0));
connect(slateDragTextEdit, SIGNAL(textChanged()), this,
SLOT(updateStatusBar()));
}
void MainWindow::updateStatusBar()
{ int charCount = slateDragTextEdit->toPlainText().count();
statusBar()->showMessage(QString::number(charCount));
}
In the constructor, QWidget and the QVBoxLayout objects are created to hold the main
widget and layout. This widget will then be inserted with the call to
setCentralWdiget(), as shown in the following code:
[ 88 ]
Managing Events, Custom Signals, and Slots Chapter 5
The main.cpp file will contain only the following few lines of code to instantiate the
window and display it:
#include <QApplication>
#include <Qt>
#include "mainwindow.h"
int main(int argc, char *argv[]){
QApplication app(argc, argv);
MainWindow window;
window.setWindowTitle("Drag Text Edit");
window.show();
return app.exec();
}
At the end of the project, you should have five (5) files in your folder. To compile the
project, issue the following commands within the folder on the command line:
% qmake -project
Don't forget to add QT += widgets to the generated .pro file. The .pro file should
contain the header files and program files. It should look like the following code:
# Input
HEADERS += dragTextEdit.h mainwindow.h
SOURCES += dragTextEdit.cpp main.cpp mainwindow.cpp
[ 89 ]
Managing Events, Custom Signals, and Slots Chapter 5
Since there are no characters when the program is executed, the status bar will read 0, as in
the preceding screenshot.
Type some input into the text area and find out how, with every keystroke, the status bar is
updated, as we have in the following screenshot:
The example in this section illustrates how the text area can accept items external to the
application. Drag and drop any text (.txt) file, or any file containing text, onto the text
area and see how its content is used to populate the textbox, as shown in the following
screenshot:
[ 90 ]
Managing Events, Custom Signals, and Slots Chapter 5
From the preceding screenshot, the content of the sometext.txt file, which contains text,
will be pasted into the text area as shown in the following screenshot:
The last section of this chapter will touch on the creation of a custom signal.
Custom signals
In previous chapters, we saw how to use slots and create custom slots to implement some
functionality in response to a signal being emitted. Now, in this section, we will look at
how to create custom signals that can be emitted and connected to other slots.
To create a custom signal, one needs to declare a method signature and mark it as a signal
with the aid of the Q_OBJECT macro. When declared, signals don't have a return type, but
they can accept parameters.
Let's get our feet wet with a project. As usual, a new folder should be created with the three
(3) files, namely, main.cpp, mainwindow.cpp, and mainwindow.h.
In this example, we shall override mousePressEvent and emit a custom signal that will be
connected to a slot to perform a number of updates on a window.
[ 91 ]
Managing Events, Custom Signals, and Slots Chapter 5
#include <QLabel>
class MainWindow: public QMainWindow
{
Q_OBJECT
public slots:
void updateMousePosition(QPoint pos);
signals:
void mouseMoved(QPoint pos);
public:
MainWindow(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
private:
QLabel *mousePosition;
QWidget *windowCentralWidget;
};
#endif
When this signal is emitted, it will pass an instance of QPoint as an argument. If we didn't
want our signal to pass any argument, it would have been written with as void
mouseMoved().
Custom signals should return nothing. The signal will be emitted when
we re-implement the mousePressEvent() handler.
The member pointer, mousePosition, will display the coordinates of the mouse when it is
clicked.
In the mainwindow.cpp file, we shall define three (3) methods. These are the default
constructor, the slot updateMousePosition(), and the mousePressEvent() override
methods, as shown in the following code:
#include "mainwindow.h"
void MainWindow::mousePressEvent(QMouseEvent *event){
emit mouseMoved(event->pos());
}
[ 92 ]
Managing Events, Custom Signals, and Slots Chapter 5
The include statement has to be at the very top of the file. In this override method, we
obtain the coordinate where the mouse press event was generated by calling
event->pos().
The points x and y coordinates are obtained by calling x() and y(), respectively.
The emit mouseMoved(event->pos()) line is used to emit the signal we declared in the
header file. Furthermore, event->pos() will return a QPoint object, which conforms with
the signature of the signal.
The following screenshot shows how the slot is defined in the mainwindow.cpp file:
void MainWindow::updateMousePosition(QPoint point){
int xCord = point.x();
int yCord = point.y();
QString text = QString::number(xCord) + ", " + QString::number(yCord);
mousePosition->setText(text);
statusBar()->showMessage(text);
}
The QPoint instance is received by the slot as a parameter. It's x and y coordinates are
obtained by calling point.x() and point.y(), respectively. A QString instance text is
used to concatenate the two values, xCord and yCord, into a longer string.
The QLabel instance, mousePosition, will be used to display this coordinate by calling its
setText() method. Similarly, the status bar of the window will be set by calling
statusBar()->showMessage(text).
To do the plumbing of connecting the custom signal to our slot, we need to define the
default constructor. Add the following lines to mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) : QMainWindow (parent){
windowCentralWidget = new QWidget();
mousePosition = new QLabel("Mouse Position");
QVBoxLayout *innerLayout = new QVBoxLayout();
innerLayout->addWidget(mousePosition);
windowCentralWidget->setLayout(innerLayout);
setCentralWidget(windowCentralWidget);
statusBar()->showMessage("Ready");
connect(this, SIGNAL(mouseMoved(QPoint)), this,
SLOT(updateMousePosition(QPoint)));
}
[ 93 ]
Managing Events, Custom Signals, and Slots Chapter 5
Like we have been doing, the windowCentralWidget is used as the main widget in our
application. QLabel is added to its layout, innerLayout. The status bar is given an initial
value of "Ready".
In the main.cpp file, we shall instantiate our window and start the main event loop, as
shown in the following code:
#include <QApplication>
#include <Qt>
#include "mainwindow.h"
int main(int argc, char *argv[]){
QApplication app(argc, argv);
MainWindow window;
window.resize(300, 300);
window.setWindowTitle("Hover Events");
window.show();
return app.exec();
}
The status bar reads Ready, while the QLabel making up the main widget in the window
reads Mouse Position. Now, click on any part within the window, and see the status bar
and label change to display the coordinates of the mouse where the click was generated.
[ 94 ]
Managing Events, Custom Signals, and Slots Chapter 5
The location of the cursor is 145, 157, where 145 is on the x axis and 157 is on the y axis.
When the cursor is moved, this value will not change. However, when the mouse is clicked,
the mouseMoved() signal will be emitted along with the coordinates to update the screen.
Summary
This chapter shed more light on how to use events in Qt. We understood the different
situations that call for the use of events instead of the signal-slot mechanism. The first
examples touched on how to override and implement custom event handlers. The events
we implemented captured the position of a window and also redefined what should
happen every second in an example application.
With the aid of events, we also implemented a simple drop event in the drag and drop
action, where a simple text editor was created to accept files that are dropped in the text
area. Lastly, the chapter illustrated how to create a custom signal that is emitted when an
event occurs.
In Chapter 6, Connecting Qt with Databases, we will focus on the various ways to store data
and retrieve it when building Qt applications.
[ 95 ]
6
Connecting Qt with Databases
In recent times, most applications integrate with some database for storing information for
further processing and future use.
Qt comes with modules and classes that make connecting to databases effortless. The
MySql database will be used to illustrate the examples in this chapter, but the same
principles will apply to other databases.
By the end of this chapter, you should be able to perform the following:
QtSql
The QtSql module comes equipped with classes and drivers for accessing databases. To
proceed beyond this point, you should have made the necessary configuration during the
installation of Qt on your system to enable database access.
For those on the macOS using Homebrew, remember to issue the command as previously
described in Chapter 1, Introducing Qt 5.
Linux users have to install the modules and enable the correct flags during compilation to
make the QtSql module work, but, for the most part, the instructions in Chapter
1, Introducing Qt 5, should suffice.
Connecting Qt with Databases Chapter 6
UI layer
SQL API layer
Driver layer
[ 97 ]
Connecting Qt with Databases Chapter 6
Let's create a database with the following tables by issuing the following statements:
use contact_db;
CREATE TABLE IF NOT EXISTS contacts (
id INT AUTO_INCREMENT,
last_name VARCHAR(255) NOT NULL,
first_name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
The name of the database is contact_db, and it is assumed that you have created it in the
MySql instance you installed.
The SQL statements create a table called contacts with an auto-increment id field along
with, last_name, first_name, and phone_number fields that store characters.
Now, create a new folder and add a file called main.cpp. Insert the following lines of code:
#include <QApplication>
#include <QtSql>
#include <QDebug>
/*
use contact_db;
CREATE TABLE IF NOT EXISTS contacts (
id INT AUTO_INCREMENT,
last_name VARCHAR(255) NOT NULL,
first_name VARCHAR(255) NOT NULL,
phone_number VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
*/
int main(int argc, char *argv[]) {
// Setup db connection
QSqlDatabase db_conn =
QSqlDatabase::addDatabase("QMYSQL", "contact_db");
db_conn.setHostName("127.0.0.1");
db_conn.setDatabaseName("contact_db");
db_conn.setUserName("root");
db_conn.setPassword("");
db_conn.setPort(3306);
[ 98 ]
Connecting Qt with Databases Chapter 6
// Error checks
if (!db_conn.open()) {
qDebug() << db_conn.lastError();
return 1;
} else {
qDebug() << "Database connection established !";
}
}
In the preceding code, the structure of the database table has been commented out, but
serves as a reminder in case you have not created it.
This connection, db_conn, is then initialized with the parameters to make the connection
work. The hostname, specific database we want to connect to, username, password, and
port number are set on the database connection object, db_conn:
db_conn.setHostName("127.0.0.1");
db_conn.setDatabaseName("contact_db");
db_conn.setUserName("root");
db_conn.setPassword("");
db_conn.setPort(3306);
Depending on a number of situations, you may need to specify more than these parameters
to gain access to a database, but, for the most part, this should work. Also, note that the
password is an empty string. It is only for illustration purposes. You have to change the
password as pertains to your database.
[ 99 ]
Connecting Qt with Databases Chapter 6
A call to open() will result in a bool being returned to determine whether the connection to
the database was successful. !db_conn.open() tests whether the return value is False.
Note the way in which we shall compile and run this program.
Issue the following on the command line while you are in the folder where the main.cpp
file is locate:
% qmake -project
Open the resulting .pro file, and add the following lines:
QT += widgets sql
We intend to use widgets in the course of this chapter, so it has been listed as the first
module to be included. Likewise, we include the SQL module. Proceed with the following
commands:
% qmake
% make
% ./program_executable
If you get the Database connection established! response, then it means your
program is able to connect to the database smoothly. On the other hand, you may get an
error, which will describe the reason why the connection is unable to be established. Go
through the following list to ensure you are on the right path when you encounter an error:
Now, let's update the program so that we can illustrate how to issue the various SQL
statements in Qt.
Listing records
In order to execute query statements against the database, we shall make use of the
QSqlQuery class. These statements include data-altering statements, such as INSERT,
SELECT, and UPDATE. Data definition statements such as CREATE TABLE can also be issued.
[ 100 ]
Connecting Qt with Databases Chapter 6
Consider the following snippet of code to list all entries within the contacts table:
QSqlQuery statement("SELECT * FROM contacts", db_conn);
QSqlRecord record = statement.record();
while (statement.next()){
QString firstName =
statement.value(record.indexOf("first_name")).toString();
QString lastName =
statement.value(record.indexOf("last_name")).toString();
QString phoneNumber =
statement.value(record.indexOf("phone_number")).toString();
qDebug() << firstName << " - " << lastName << " - " << phoneNumber;
}
The query statement and the database connection are passed as parameters to an instance
of the QSqlQuery statement. QSqlRecord is used to encapsulate a database row or view.
We shall use its instance, record, to get the index of a column in a row.
statement.record() returns field information for the current query.
If there are any rows that match the query in statement, statement.next() will allow
us to cycle through the returned rows. We can call previous(), first(), and last() to
enable us to move back and forth with the returned rows or data.
For each row that is returned and accessed by calling statement.next(), the statement
object is used to get its corresponding data according to the code,
statement.value(0).toString(). This should return the first column in the row
converted to string to be stored in firstName. Instead of this approach, we can use
record, to obtain the index of the column we are interested in. As such, to extract the first
name column, we write
statement.value(record.indexOf("first_name")).toString().
The qDebug() call helps to print out the data in firstName, lastName, and phoneNumber,
similar to what we would have done using cout.
[ 101 ]
Connecting Qt with Databases Chapter 6
phone_number)"
"VALUES (?, ?, ?)");
insert_statement.addBindValue("Sidle");
insert_statement.addBindValue("Sara");
insert_statement.addBindValue("+14495849555");
insert_statement.exec();
To change the order in which the data is inserted, we can use the
insert_statement.bindValue() function instead. The INSERT statement has three (3)
positional placeholders, which number from 0 up to 2. We can fill the last placeholder first
by specifying it as follows:
insert_statement.prepare("INSERT INTO contacts (last_name, first_name,
phone_number)"
"VALUES (?, ?, ?)");
insert_statement.bindValue(2, "+144758849555");
insert_statement.bindValue(1, "Brass");
insert_statement.bindValue(0, "Jim");
insert_statement.exec();
The placeholder for the phone number column is filled first by specifying bind(2,
"+144758849555"), where 2 is the index of the (phone_number) placeholder.
An alternative to using the positions of the placeholders would be to name them. Consider
the following INSERT statement:
insert_statement.prepare("INSERT INTO contacts (last_name, first_name,
phone_number)"
"VALUES (:last_name, :first_name, :phone_number)");
insert_statement.bindValue(":last_name", "Brown");
insert_statement.bindValue(":first_name", "Warrick");
insert_statement.bindValue(":phone_number", "+7494588594");
insert_statement.exec();
[ 102 ]
Connecting Qt with Databases Chapter 6
Instead of using the index of the position when completing the SQL statement, named
placeholders are used to reference the data in the VALUES part. That way, the name of the
placeholders are passed with a corresponding value to every call to bindValue().
numRowsAffected() is a method that is used to figure out how many records were
affected. One benefit of this method is that it helps determine whether our query has
changed the database. If it returns -1, it means that the query's operation produced
indeterminate results.
The statement here sets the first_name of the record with an ID of 1 to 'Jude'.
update_statement.numRowsAffected() will return nothing, especially in the case
where the first record in the table with id=1 is missing. Do take note of this.
[ 103 ]
Connecting Qt with Databases Chapter 6
[ 104 ]
Connecting Qt with Databases Chapter 6
Of particular importance is how the database table is created. From the preceding code
listing, the QString instance, table_definition, holds the structure of the table we are
about to create. The table is created when table_definition and the database connection
are passed to an instance of QSqlQuery. That's all it takes to create a table.
[ 105 ]
Connecting Qt with Databases Chapter 6
A typical output of the program run from the command is given as follows:
./dbBasics.app/Contents/MacOS/dbBasics
Database connection established !
"Jude" - "Sidle" - "+14495849555"
"Brass" - "Jim" - "+144758849555"
Number of rows affected: 1
Number of rows affected: 0
In application development, you are confronted with the challenge of how to present data
and to maintain a relationship between data and presentation (view) such that changes to
the data are reflected in the view.
In the early days of the PHP language, data, presentation, and business logic were all
jumbled up in one or more scripts. This made debugging and eventual code maintenance a
nightmare. This same dilemma does crop up from time to time in language and framework
design.
[ 106 ]
Connecting Qt with Databases Chapter 6
This data that has been obtained needs to displayed or presented to the user. The
component(s) through which the data is piped is called the view. For example, an HTML
page showing a list of student names can be called a view. In Qt, there are a number of
widgets that can be used to display data in a model. Some typical views for data
presentation are as follows:
These view classes are optimized for the displaying of information such that, when they are
associated with a model, a change in the model will cause the view to be automatically
updated. The view maintains its own state and gets informed when there are changes in the
model.
For instance, when a list of names is displayed in a QListView, a call to remove() on the
model will both remove the item from the model's list and also update the view by
reducing the number of items on display.
Instead of writing code to update the view, the view class does so on our behalf. Let's create
a sample project that will make use of a model to access data from a database:
Create a new folder and, within it, create a file named main.cpp. Copy over the following
lines of code into main.cpp:
#include <QtSql>
#include <QDebug>
/*
int main(int argc, char *argv[])
{
// Setup db connection
QSqlDatabase db_conn =
QSqlDatabase::addDatabase("QMYSQL", "contact_db");
db_conn.setHostName("127.0.0.1");
db_conn.setDatabaseName("contact_db");
db_conn.setUserName("root");
db_conn.setPassword("");
db_conn.setPort(3306);
// Error checks
[ 107 ]
Connecting Qt with Databases Chapter 6
if (!db_conn.open()) {
qDebug() << db_conn.lastError(); return 1;
}
// Use Database model
QSqlTableModel *contactsTableModel = new QSqlTableModel(0, db_conn);
contactsTableModel->setTable("contacts");
contactsTableModel->select();
for (int i = 0; i < contactsTableModel->rowCount(); ++i) {
QSqlRecord record = contactsTableModel->record(i);
QString id = record.value("id").toString();
QString last_name = record.value("last_name").toString();
QString first_name = record.value("first_name").toString();
QString phone_number = record.value("phone_number").toString();
qDebug() << id << " : " << first_name << " : " << last_name << " :
" << phone_number;
}
// Insert Row
int row = contactsTableModel->rowCount();
contactsTableModel->insertRows(row, 1);
contactsTableModel->setData(contactsTableModel->index(row, 1),
"Stokes");
contactsTableModel->setData(contactsTableModel->index(row, 2), "Nick");
contactsTableModel->setData(contactsTableModel->index(row, 3),
"+443569948");
contactsTableModel->submitAll();
// Custom filter
qDebug() << "\nCustom filter: \n";
contactsTableModel->setFilter("id=12 AND last_name like'Stokes'");
contactsTableModel->select();
for (int i = 0; i < contactsTableModel->rowCount(); ++i) {
QSqlRecord record = contactsTableModel->record(i);
QString id = record.value("id").toString();
QString last_name = record.value("last_name").toString();
QString first_name = record.value("first_name").toString();
QString phone_number = record.value("phone_number").toString();
qDebug() << id << " : " << first_name << " : " << last_name << " :
" << phone_number;
}
}
The purpose of this program is to connect to a database, list the rows in a particular table,
and issue a SELECT statement against it.
[ 108 ]
Connecting Qt with Databases Chapter 6
To select the table within the database we wish to manipulate, a call to the setTable()
method is called on contactsTableModel. The contacts string is passed as the name of
the table.
To populate the contactsTableModel model with the information in the table, a call to
select() is issued. A loop is now used to iterate over the data in the model:
Each row in the table is obtained by using an index. An index of 0 here refers to the first
item in the model. This index is not tied to the primary key in the table. It is instead a
simple way to reference the rows in the table.
The rowCount() method is useful as it helps in knowing the total row count associated
with the latest SELECT statement.
To obtain each row in the table, the index in the loop, i, is passed to
contactsTableModel->record(i). The QSqlRecord instance will hold a reference to a
row in the table, which was returned by calling record(i).
For each row, the value stored at the intersecting column is obtained by passing the name
of the column to value. As such, record.value("id") will return the value stored in the
column id of the contact table. toString() returns the output as a string. This same call is
issued to obtain the values for last_name, first_name, and phone_number for each row
(QSqlRecord record) in the table.
The qDebug() statement is then used to output all the values for each row.
[ 109 ]
Connecting Qt with Databases Chapter 6
Since QSqlTableModel allows for editing of the table, the following statement inserts a
new row with data:
// Insert Row
int row = contactsTableModel->rowCount();
contactsTableModel->insertRows(row, 1);
contactsTableModel->setData(contactsTableModel->index(row, 1), "Stokes");
contactsTableModel->setData(contactsTableModel->index(row, 2), "Nick");
contactsTableModel->setData(contactsTableModel->index(row, 3),
"+443569948");
contactsTableModel->submitAll();
The total items in the table are obtained by calling rowCount(). To insert a single row into
the table, a call to insertRows(row, 1) is made. The single row here is represented by 1
at position row.
At column 1, the last_name column of the new row gets the value "Stokes", after the call
to setData(). contactsTableModel->index(row,1) represents the index where
"Stokes" is to be inserted.
To persist the data, a call to submitAll() is issued. This will write off any changes that are
lingering on in memory to the database.
Note at this point that the model has become the interface for accessing the data in the
database. We also do not need to know the specific query that the statements map to for the
different kinds of database the application talks to. This is a huge advantage.
If this model were associated with a view, the newly inserted row would be populated onto
the screen without any code to perform such an operation.
The WHERE clause part of the SQL statement is what is passed to setFilter(). The WHERE
clause, in this case, is selecting rows from the table where the id is equal to 12 and the
last_name field is 'Stokes'.
To apply the filter, call the select() method on contactsTableModel. The loop is then
used to iterate over the results.
[ 110 ]
Connecting Qt with Databases Chapter 6
Because we want to display the model, the widgets classes have been included. The
database connections remain the same.
[ 111 ]
Connecting Qt with Databases Chapter 6
Instead of using magic numbers such as 0, 1, and so on, enumerators provide some context
for the constants 0, 1, and so on.
We said that the Model-View concept has an added benefit in that changes made to the
view can be made to reflect in the database without writing extra code:
contactsTableModel->setEditStrategy(
QSqlTableModel::OnManualSubmit);
The preceding lines stipulate that changes to the data displayed in the view should not be
propagated to the database. Instead, an independent process should trigger the syncing of
the view with the data in the database. In contrast to making the syncing process a manual
one, replace the code that has been commented out:
//contactsTableModel->setEditStrategy(
// QSqlTableModel::OnRowChange);
[ 112 ]
Connecting Qt with Databases Chapter 6
Since we have created the model, it is time to add the view. Add the following lines of code
to main.cpp:
//contactsTableModel->setEditStrategy(
// QSqlTableModel::OnRowChange);
// continue from here ...
QTableView *contactsTableView = new QTableView();
contactsTableView->setModel(contactsTableModel);
contactsTableView->setSelectionMode(QAbstractItemView::SingleSelection);
contactsTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
QHeaderView *header = contactsTableView->horizontalHeader();
header->setStretchLastSection(true);
To show the entries in the database table, the view class, QTableView, is used here. The
QTableView class is special in that it is a class with an implementation of a model and view
all-in-one. That means that internally, this class has an internal model where data can be
inserted for display. For our purposes, we shall replace this model.
QTableView presents data in a tabular form with rows and columns. We are choosing to
use this view since it resembles how data is organized in a relational database.
The selection of items in the table is restricted to a single item when the
setSelectionMode() method is called. If we want to allow multiple selections in the
table, then the QAbstractItemView::MultiSelection constant should be passed to
setSelectionMode(). The selection, in this case, is made by clicking and dragging the
mouse over the items in the table in which you have an interest.
When QTableView is rendered, there is unused space to the right of the widget.
[ 113 ]
Connecting Qt with Databases Chapter 6
Consider how the space marked Empty space presents a gaping hole in the interface.
In order to make the last column stretch to fill the containing widget, we need to obtain an
instance of the header object of QTableView and set the desired property,
setStretchLastSection(), to true, as in the following code:
At this point, we need to construct a simple window and layout for the application. Add
the following lines to main.cpp:
QWidget window;
QVBoxLayout *layout = new QVBoxLayout();
QPushButton *saveToDbPushButton = new QPushButton("Save Changes");
layout->addWidget(contactsTableView);
layout->addWidget(saveToDbPushButton);
A QVBoxLayout instance will serve as the main layout for the application window.
Changes made to the entries in the table will not be persisted to the database. We have
intentionally made it thus, in order to use a button to manually write changes to the
database. As such, a QPushButton instance is created. The table and button are added to
the layout object.
[ 114 ]
Connecting Qt with Databases Chapter 6
Make sure the QT variable in the .pro file has the following line:
QT += widgets sql
The output of the application will populate a list in the table, assuming the contacts table is
not empty:
[ 115 ]
Connecting Qt with Databases Chapter 6
Note how the last column has extended all the way to the edge of the window. From the
preceding screenshot, you can see data that has already been persisted in the database.
Double-click on any of the cells and edit its content. Click on the Save Changes button.
When you visit the database, you will see that the changes in the application have been
reflected in the app.
Summary
This chapter illustrated how to connect to databases when developing Qt applications. We
learned how to use models to serve as an abstraction for manipulating data in a database.
Finally, the information in the database table was displayed with the aid of Model-View
classes. These classes make it easy to extract data for display, while allowing changes made
in the view to be propagated to the database.
[ 116 ]
Other Books You May Enjoy
If you enjoyed this book, you may be interested in these other books by Packt:
Qt 5 Projects
Marco Piccolino
ISBN: 9781788293884
Learn the basics of modern Qt application development
Develop solid and maintainable applications with BDD, TDD, and Qt Test
Master the latest UI technologies and know when to use them: Qt Quick,
Controls 2, Qt 3D and Charts
Build a desktop UI with Widgets and the Designer
Translate your user interfaces with QTranslator and Linguist
Get familiar with multimedia components to handle visual input and output
Explore data manipulation and transfer: the model/view framework, JSON,
Bluetooth, and network I/O
Take advantage of existing web technologies and UI components with
WebEngine
Other Books You May Enjoy
Mastering Qt 5
Guillaume Lazar
ISBN: 9781786467126
Create stunning UIs with Qt Widget and Qt Quick
Develop powerful, cross-platform applications with the Qt framework
Design GUIs with the Qt Designer and build a library in it for UI preview
Handle user interaction with the Qt signal/slot mechanism in C++
Prepare a cross-platform project to host a third-party library
Build a Qt application using the OpenCV API
Use the Qt Animation framework to display stunning effects
Deploy mobile apps with Qt and embedded platforms
[ 118 ]
Other Books You May Enjoy
[ 119 ]
Index
Linux
C Qt, installing on 6
custom signals 91, 92, 93
custom window M
creating 43, 44, 45, 46, 47
macOS
dialog boxes, adding 69, 70, 72, 74, 75, 76, 78
Qt, installing on 7
menu bar 48, 49, 50, 52
modules, Qt
toolbar 53, 54, 56, 57
QtCore 8
widgets, adding 57, 58, 60, 61, 62, 63, 64, 66,
QtGui 8
68, 69
QtNetwork 8
QtSQL 9
D QtWebkit 8
data model QtWidgets 8
displaying 111, 112, 113, 114, 116 QtXML 9
using, for database access 106, 107, 108, 110
drag and drop operation 84, 86, 88, 89, 90 Q
QFormLayout class
E about 23, 25
event handlers 80, 81, 82, 83 reference 20
events 79 QGridLayout class
about 21, 22
G reference 20
Graphical User Interfaces (GUI) 8 QHBoxLayout class
about 28, 29
H reference 20
hello world program 9, 10, 12 QStackedLayout class
reference 20
L Qt program
creating, with widget 15
layout, classes
Qt Style Sheet (QSS) 12
QFormLayout 23, 25
Qt
QGridLayout 21, 22
about 8
QHBoxLayout 28, 29
hello world program 9, 10, 12
QVBoxLayout 26, 27
installing, on Linux 6
layouts
installing, on macOS 7
about 19
installing, on Windows 7
with direction 25
modules 8
QtSql module multiple signals, connecting to single slot 38, 39,
about 96 41
layers 97 single signal, connecting to multiple slots 36, 37,
QtSql 38
connection, making 97, 98, 99, 100 slots 32, 33
DELETE operation 103
INSERT operation 101, 102 W
records, listing 100, 101 widgets
UPDATE operation 103, 105 about 14, 16, 18
QVBoxLayout class key points 14
about 26, 27 laying out 20
reference 20 Windows
Qt, installing on 8
S
signals X
about 32, 33 XAMPP
connecting, with slots 34 download link 97