Qt Quick Controls 2 - Chat Tutorial
This tutorial shows how to write a basic chat application using Qt Quick Controls 2. It will also explain how to integrate an SQL database into a Qt application.
Chapter 1: Setting Up
When setting up a new project, it's easiest to use Qt Creator. For this project, we chose the Qt Quick application template, which creates a basic "Hello World" application with the following files:
MainForm.ui.qml
- Defines the default UImain.qml
- Embeds the default UI in a Windowqml.qrc
- Lists the.qml
files that are built into the binarymain.cpp
- Loadsmain.qml
chattutorial.pro
- Provides the qmake configuration
Note: Delete the MainForm.ui.qml
from the project as we will not use it in this tutorial.
main.cpp
The default code in main.cpp
has two includes:
#include <QGuiApplication> #include <QQmlApplicationEngine>
The first gives us access to QGuiApplication. All Qt applications require an application object, but the precise type depends on what the application does. QCoreApplication is sufficient for non-graphical applications. QGuiApplication is sufficient for graphical applications that do not use Qt Widgets, while QApplication is required for those that do.
The second include makes QQmlApplicationEngine available, along with some useful functions required for making C++ types accessible from QML.
Within main()
, we set up the application object and QML engine:
int main(int argc, char *argv[]) { QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
It begins with enabling high DPI scaling, which is not part of the default code. It is necessary to do so before the application object is constructed.
After that's done, we construct the application object, passing any application arguments provided by the user.
Next, the QML engine is created. QQmlApplicationEngine is a convenient wrapper over QQmlEngine, providing the load() function to easily load QML for an application. It also adds some convenience for using file selectors.
Once we've set up things in C++, we can move on to the user interface in QML.
main.qml
Let's modify the default QML code to suit our needs.
import QtQuick 2.6 import QtQuick.Controls 2.1
First, import the Qt Quick module. This gives us access to graphical primitives such as Item, Rectangle, Text, and so on. For the full list of types, see the Qt Quick QML Types documentation.
Next, import the Qt Quick Controls 2 module. Amongst other things, this provides access to ApplicationWindow, which will replace the existing root type, Window
:
ApplicationWindow { width: 540 height: 960 visible: true ... }
ApplicationWindow is a Window with some added convenience for creating a header and a footer. It also provides the foundation for popups and supports some basic styling, such as the background color.
There are three properties that are almost always set when using ApplicationWindow: width, height, and visible. Once we've set these, we have a properly sized, empty window ready to be filled with content.
Note: The title
property from the default code is removed.
The first "screen" in our application will be a list of contacts. It would be nice to have some text at the top of each screen that describes its purpose. The header and footer properties of ApplicationWindow could work in this situation. They have some characteristics that make them ideal for items that should be displayed on every screen of an application:
- They are anchored to the top and bottom of the window, respectively.
- They fill the width of the window.
However, when the contents of the header and footer varies depending on which screen the user is viewing, it is much easier to use Page. For now, we'll just add one page, but in the next chapter, we'll demonstrate how to navigate between several pages.
Page { anchors.fill: parent header: Label { padding: 10 text: qsTr("Contacts") font.pixelSize: 20 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } }
We replace the default MainForm {...}
code block with a Page, which is sized to occupy all the space on the window using the anchors.fill property.
Then, we assign a Label to its header property. Label extends the primitive Text item from the Qt Quick module by adding styling and font inheritance. This means that a Label can look different depending on which style is in use, and can also propagate its pixel size to its children.
We want some distance between the top of the application window and the text, so we set the padding property. This allocates extra space on each side of the label (within its bounds). We can also explicitly set the topPadding and bottomPadding properties instead.
We set the text of the label using the qsTr()
function, which ensures that the text can be translated by Qt's translation system. It's a good practice to follow for text that is visible to the end users of your application.
By default, text is vertically aligned to the top of its bounds, while the horizontal alignment depends on the natural direction of the text; for example, text that is read from left to right will be aligned to the left. If we used these defaults, our text would be at the top-left corner of the window. This is not desirable for a header, so we align the text to the center of its bounds, both horizontally and vertically.
The Project File
The .pro
or project file contains all of the information needed by qmake to generate a Makefile, which is then used to compile and link the application.
TEMPLATE = app
The first line tells qmake
which kind of project this is. We're building an application, so we use the app
template.
QT += qml quick
The next line declares the Qt libraries that we want to use from C++.
CONFIG += c++11
This line states that a C++11 compatible compiler is required to build the project.
SOURCES += main.cpp
The SOURCES
variable lists all of the source files that should be compiled. A similar variable, HEADERS
, is available for header files.
RESOURCES += qml.qrc
The next line tells qmake
that we have a collection of resources that should be built into the executable.
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/chattutorial/chapter1-settingup
This line replaces deployment settings that come with the default project file. It determines where the example is copied, on running "make install
".
Now we can build and run the application:
s2-chattutorial-chapter1.png
qtquickcontrols2-chattutorial-chapter1.png
Chapter 2: Lists
In this chapter, we'll explain how to create a list of interactive items using ListView and ItemDelegate.
ListView comes from the Qt Quick module, and displays a list of items populated from a model. ItemDelegate comes from the Qt Quick Controls 2 module, and provides a standard view item for use in views and controls such as ListView and ComboBox. For example, each ItemDelegate can display text, be checked on and off, and react to mouse clicks.
Here is our ListView:
... ListView { id: listView anchors.fill: parent topMargin: 48 leftMargin: 48 bottomMargin: 48 rightMargin: 48 spacing: 20 model: ["Albert Einstein", "Ernest Hemingway", "Hans Gude"] delegate: ItemDelegate { text: modelData width: listView.width - listView.leftMargin - listView.rightMargin height: avatar.implicitHeight leftPadding: avatar.implicitWidth + 32 Image { id: avatar source: "qrc:/" + modelData.replace(" ", "_") + ".png" } } } ...
Sizing and Positioning
The first thing we do is set a size for the view. It should fill the available space on the page, so we use anchors.fill. Note that Page ensures that its header and footer have enough of their own space reserved, so the view in this case will sit below the header, for example.
Next, we set margins around the ListView to put some distance between it and the edges of the window. The margin properties reserve space within the bounds of the view, which means that the empty areas can still be "flicked" by the user.
The items should be nicely spaced out within the view, so the spacing property is set to 20
.
Model
In order to quickly populate the view with some items, we've used a JavaScript array as the model. One of the greatest strengths of QML is its ability to make prototyping an application extremely quick, and this is an example of that. It's also possible to simply assign a number to the model property to indicate how many items you need. For example, if you assign 10
to the model
property, each item's display text will be a number from 0
to 9
.
However, once the application gets past the prototype stage, it quickly becomes necessary to use some real data. For this, it's best to use a proper C++ model by subclassing QAbstractItemModel.
Delegate
On to the delegate. We assign the corresponding text from the model to the text property of ItemDelegate. The exact manner in which the data from the model is made available to each delegate depends on the type of model used. See Models and Views in Qt Quick for more information.
In our application, the width of each item in the view should be the same as the width of the view. This ensures that the user has a lot of room with which to select a contact from the list, which is an important factor on devices with small touch screens, like mobile phones. However, the width of the view includes our 48
pixel margins, so we must account for that in our assignment to the width property.
Next, we define an Image. This will display a picture of the user's contact. The image will be 40
pixels wide and 40
pixels high. We'll base the height of the delegate on the image's height, so that we don't have any empty vertical space.
s2-chattutorial-chapter2.png
qtquickcontrols2-chattutorial-chapter2.png
Chapter 3: Navigation
In this chapter, you'll learn how to use StackView to navigate between pages in an application. Here's the revised main.qml
:
import QtQuick 2.6 import QtQuick.Controls 2.1 ApplicationWindow { id: window width: 540 height: 960 visible: true StackView { id: stackView anchors.fill: parent initialItem: ContactPage {} } }
StackView
As its name suggests, StackView provides stack-based navigation. The last item to be "pushed" onto the stack is the first one to be removed, and the top-most item is always the one that is visible.
In the same manner as we did with Page, we tell the StackView to fill the application window. The only thing left to do after that is to give it an item to display, via initialItem. StackView accepts items, components and URLs.
You'll notice that we moved the code for the contact list into ContactPage.qml
. It's a good idea to do this as soon as you have a general idea of which screens your application will contain. Doing so not only makes your code easier to read, but ensures that items are only instantiated from a given component when completely necessary, reducing memory usage.
Note: Qt Creator provides several convenient refactoring options for QML, one of which allows you to move a block of code into a separate file (Alt + Enter > Move Component into Separate File
).
Another thing to consider when using ListView is whether to refer to it by id
, or use the attached ListView.view property. The best approach depends on a few different factors. Giving the view an id will result in shorter and more efficient binding expressions, as the attached property has a very small amount of overhead. However, if you plan on reusing the delegate in other views, it is better to use the attached properties to avoid tying the delegate to a particular view. For example, using the attached properties, the width
assignment in our delegate becomes:
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
In chapter 2, we added a ListView below the header. If you run the application for that chapter, you'll see that the contents of the view can be scrolled over the top of the header:
chapter2-listview-header.gif
qtquickcontrols2-chattutorial-chapter2-listview-header.gif
This is not that nice, especially if the text in the delegates is long enough that it reaches the text in the header. What we ideally want to do is to have a solid block of color under the header text, but above the view. This ensures that the listview contents can't visually interfere with the header contents. Note that it's also possible to achieve this by setting the clip property of the view to true
, but doing so can affect performance.
ToolBar is the right tool for this job. It is a container of both application-wide and context-sensitive actions and controls, such as navigation buttons and search fields. Best of all, it has a background color that, as usual, comes from the application style. Here it is in action:
header: ToolBar { Label { text: qsTr("Contacts") font.pixelSize: 20 anchors.centerIn: parent } }
chapter3-listview-header.gif