Application Types

This section is a run through of different application types one can write with Qt 6. It’s not limited to the selection presented here, but it will give you a better idea of what you can achieve with Qt 6 in general.

Console Application

A console application does not provide a graphical user interface, and is usually called as part of a system service or from the command line. Qt 6 comes with a series of ready-made components which help you create cross-platform console applications very efficiently. For example, the networking file APIs, string handling, and an efficient command line parser. As Qt is a high-level API on top of C++, you get programming speed paired with execution speed. Don’t think of Qt as being just a UI toolkit - it has so much more to offer!

String Handling

This first example demonstrates how you could add 2 constant strings. Admittedly, this is not a very useful application, but it gives you an idea of what a native C++ application without an event loop may look like.

  1. // module or class includes
  2. include <QtCore>
  3. // text stream is text-codec aware
  4. QTextStream cout(stdout, QIODevice::WriteOnly);
  5. int main(int argc, char** argv)
  6. {
  7. // avoid compiler warnings
  8. Q_UNUSED(argc)
  9. Q_UNUSED(argv)
  10. QString s1("Paris");
  11. QString s2("London");
  12. // string concatenation
  13. QString s = s1 + " " + s2 + "!";
  14. cout << s << endl;
  15. }

Container Classes

This example adds a list, and list iteration, to the application. Qt comes with a large collection of container classes that are easy to use, and has the same API paradigms as other Qt classes.

  1. QString s1("Hello");
  2. QString s2("Qt");
  3. QList<QString> list;
  4. // stream into containers
  5. list << s1 << s2;
  6. // Java and STL like iterators
  7. QListIterator<QString> iter(list);
  8. while(iter.hasNext()) {
  9. cout << iter.next();
  10. if(iter.hasNext()) {
  11. cout << " ";
  12. }
  13. }
  14. cout << "!" << endl;

Here is a more advanced list function, that allows you to join a list of strings into one string. This is very handy when you need to proceed line based text input. The inverse (string to string-list) is also possible using the QString::split() function.

  1. QString s1("Hello");
  2. QString s2("Qt");
  3. // convenient container classes
  4. QStringList list;
  5. list << s1 << s2;
  6. // join strings
  7. QString s = list.join(" ") + "!";
  8. cout << s << endl;

File IO

In the next snippet, we read a CSV file from the local directory and loop over the rows to extract the cells from each row. Doing this, we get the table data from the CSV file in ca. 20 lines of code. File reading gives us a byte stream, to be able to convert this into valid Unicode text, we need to use the text stream and pass in the file as a lower-level stream. For writing CSV files, you would just need to open the file in write mode, and pipe the lines into the text stream.

  1. QList<QStringList> data;
  2. // file operations
  3. QFile file("sample.csv");
  4. if(file.open(QIODevice::ReadOnly)) {
  5. QTextStream stream(&file);
  6. // loop forever macro
  7. forever {
  8. QString line = stream.readLine();
  9. // test for null string 'String()'
  10. if(line.isNull()) {
  11. break;
  12. }
  13. // test for empty string 'QString("")'
  14. if(line.isEmpty()) {
  15. continue;
  16. }
  17. QStringList row;
  18. // for each loop to iterate over containers
  19. foreach(const QString& cell, line.split(",")) {
  20. row.append(cell.trimmed());
  21. }
  22. data.append(row);
  23. }
  24. }
  25. // No cleanup necessary.

This concludes the section about console based applications with Qt.

C++ Widget Application

Console based applications are very handy, but sometimes you need to have a graphical user interface (GUI). In addition, GUI-based applications will likely need a back-end to read/write files, communicate over the network, or keep data in a container.

In this first snippet for widget-based applications, we do as little as needed to create a window and show it. In Qt, a widget without a parent is a window. We use a scoped pointer to ensure that the widget is deleted when the pointer goes out of scope. The application object encapsulates the Qt runtime, and we start the event loop with the exec() call. From there on, the application reacts only to events triggered by user input (such as mouse or keyboard), or other event providers, such as networking or file IO. The application only exits when the event loop is exited. This is done by calling quit() on the application or by closing the window.

When you run the code, you will see a window with the size of 240 x 120 pixels. That’s all.

  1. include <QtGui>
  2. int main(int argc, char** argv)
  3. {
  4. QApplication app(argc, argv);
  5. QScopedPointer<QWidget> widget(new CustomWidget());
  6. widget->resize(240, 120);
  7. widget->show();
  8. return app.exec();
  9. }

Custom Widgets

When you work on user interfaces, you may need to create custom-made widgets. Typically, a widget is a window area filled with painting calls. Additionally, the widget has internal knowledge of how to handle keyboard and mouse input, as well as how to react to external triggers. To do this in Qt, we need to derive from QWidget and overwrite several functions for painting and event handling.

  1. #pragma once
  2. include <QtWidgets>
  3. class CustomWidget : public QWidget
  4. {
  5. Q_OBJECT
  6. public:
  7. explicit CustomWidget(QWidget *parent = 0);
  8. void paintEvent(QPaintEvent *event);
  9. void mousePressEvent(QMouseEvent *event);
  10. void mouseMoveEvent(QMouseEvent *event);
  11. private:
  12. QPoint m_lastPos;
  13. };

In the implementation, we draw a small border on our widget and a small rectangle on the last mouse position. This is very typical for a low-level custom widget. Mouse and keyboard events change the internal state of the widget and trigger a painting update. We won’t go into too much detail about this code, but it is good to know that you have the possibility. Qt comes with a large set of ready-made desktop widgets, so it’s likely that you don’t have to do this.

  1. include "customwidget.h"
  2. CustomWidget::CustomWidget(QWidget *parent) :
  3. QWidget(parent)
  4. {
  5. }
  6. void CustomWidget::paintEvent(QPaintEvent *)
  7. {
  8. QPainter painter(this);
  9. QRect r1 = rect().adjusted(10,10,-10,-10);
  10. painter.setPen(QColor("#33B5E5"));
  11. painter.drawRect(r1);
  12. QRect r2(QPoint(0,0),QSize(40,40));
  13. if(m_lastPos.isNull()) {
  14. r2.moveCenter(r1.center());
  15. } else {
  16. r2.moveCenter(m_lastPos);
  17. }
  18. painter.fillRect(r2, QColor("#FFBB33"));
  19. }
  20. void CustomWidget::mousePressEvent(QMouseEvent *event)
  21. {
  22. m_lastPos = event->pos();
  23. update();
  24. }
  25. void CustomWidget::mouseMoveEvent(QMouseEvent *event)
  26. {
  27. m_lastPos = event->pos();
  28. update();
  29. }

Desktop Widgets

The Qt developers have done all of this for you already and provide a set of desktop widgets, with a native look on different operating systems. Your job, then, is to arrange these different widgets in a widget container into larger panels. A widget in Qt can also be a container for other widgets. This is accomplished through the parent-child relationship. This means we need to make our ready-made widgets, such as buttons, checkboxes, radio buttons, lists, and grids, children of other widgets. One way to accomplish this is displayed below.

Here is the header file for a so-called widget container.

  1. class CustomWidget : public QWidget
  2. {
  3. Q_OBJECT
  4. public:
  5. explicit CustomWidget(QWidget *parent = 0);
  6. private slots:
  7. void itemClicked(QListWidgetItem* item);
  8. void updateItem();
  9. private:
  10. QListWidget *m_widget;
  11. QLineEdit *m_edit;
  12. QPushButton *m_button;
  13. };

In the implementation, we use layouts to better arrange our widgets. Layout managers re-layout the widgets according to some size policies when the container widget is re-sized. In this example, we have a list, a line edit, and a button, which are arranged vertically and allow the user to edit a list of cities. We use Qt’s signal and slots to connect sender and receiver objects.

  1. CustomWidget::CustomWidget(QWidget *parent) :
  2. QWidget(parent)
  3. {
  4. QVBoxLayout *layout = new QVBoxLayout(this);
  5. m_widget = new QListWidget(this);
  6. layout->addWidget(m_widget);
  7. m_edit = new QLineEdit(this);
  8. layout->addWidget(m_edit);
  9. m_button = new QPushButton("Quit", this);
  10. layout->addWidget(m_button);
  11. setLayout(layout);
  12. QStringList cities;
  13. cities << "Paris" << "London" << "Munich";
  14. foreach(const QString& city, cities) {
  15. m_widget->addItem(city);
  16. }
  17. connect(m_widget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(itemClicked(QListWidgetItem*)));
  18. connect(m_edit, SIGNAL(editingFinished()), this, SLOT(updateItem()));
  19. connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
  20. }
  21. void CustomWidget::itemClicked(QListWidgetItem *item)
  22. {
  23. Q_ASSERT(item);
  24. m_edit->setText(item->text());
  25. }
  26. void CustomWidget::updateItem()
  27. {
  28. QListWidgetItem* item = m_widget->currentItem();
  29. if(item) {
  30. item->setText(m_edit->text());
  31. }
  32. }

Drawing Shapes

Some problems are better visualized. If the problem at hand looks remotely like geometrical objects, Qt graphics view is a good candidate. A graphics view arranges simple geometrical shapes in a scene. The user can interact with these shapes, or they are positioned using an algorithm. To populate a graphics view, you need a graphics view and a graphics scene. The scene is attached to the view and is populated with graphics items.

Here is a short example. First the header file with the declaration of the view and scene.

  1. class CustomWidgetV2 : public QWidget
  2. {
  3. Q_OBJECT
  4. public:
  5. explicit CustomWidgetV2(QWidget *parent = 0);
  6. private:
  7. QGraphicsView *m_view;
  8. QGraphicsScene *m_scene;
  9. };

In the implementation, the scene gets attached to the view first. The view is a widget and gets arranged in our container widget. In the end, we add a small rectangle to the scene, which is then rendered on the view.

  1. include "customwidgetv2.h"
  2. CustomWidget::CustomWidget(QWidget *parent) :
  3. QWidget(parent)
  4. {
  5. m_view = new QGraphicsView(this);
  6. m_scene = new QGraphicsScene(this);
  7. m_view->setScene(m_scene);
  8. QVBoxLayout *layout = new QVBoxLayout(this);
  9. layout->setMargin(0);
  10. layout->addWidget(m_view);
  11. setLayout(layout);
  12. QGraphicsItem* rect1 = m_scene->addRect(0,0, 40, 40, Qt::NoPen, QColor("#FFBB33"));
  13. rect1->setFlags(QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsMovable);
  14. }

Adapting Data

Up to now, we have mostly covered basic data types and how to use widgets and graphics views. In your applications, you will often need a larger amount of structured data, which may also need to be stored persistently. Finally, the data also needs to be displayed. For this, Qt uses models. A simple model is the string list model, which gets filled with strings and then attached to a list view.

  1. m_view = new QListView(this);
  2. m_model = new QStringListModel(this);
  3. view->setModel(m_model);
  4. QList<QString> cities;
  5. cities << "Munich" << "Paris" << "London";
  6. m_model->setStringList(cities);

Another popular way to store and retrieve data is SQL. Qt comes with SQLite embedded, and also has support for other database engines (e.g. MySQL and PostgreSQL). First, you need to create your database using a schema, like this:

  1. CREATE TABLE city (name TEXT, country TEXT);
  2. INSERT INTO city value ("Munich", "Germany");
  3. INSERT INTO city value ("Paris", "France");
  4. INSERT INTO city value ("London", "United Kingdom");

To use SQL, we need to add the SQL module to our .pro file

  1. QT += sql

And then we can open our database using C++. First, we need to retrieve a new database object for the specified database engine. With this database object, we open the database. For SQLite, it’s enough to specify the path to the database file. Qt provides some high-level database models, one of which is the table model. The table model uses a table identifier and an optional where clause to select the data. The resulting model can be attached to a list view as with the other model before.

  1. QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
  2. db.setDatabaseName("cities.db");
  3. if(!db.open()) {
  4. qFatal("unable to open database");
  5. }
  6. m_model = QSqlTableModel(this);
  7. m_model->setTable("city");
  8. m_model->setHeaderData(0, Qt::Horizontal, "City");
  9. m_model->setHeaderData(1, Qt::Horizontal, "Country");
  10. view->setModel(m_model);
  11. m_model->select();

For a higher level model operations, Qt provides a sorting file proxy model that allows you sort, filter, and transform models.

  1. QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
  2. proxy->setSourceModel(m_model);
  3. view->setModel(proxy);
  4. view->setSortingEnabled(true);

Filtering is done based on the column that is to be filters, and a string as filter argument.

  1. proxy->setFilterKeyColumn(0);
  2. proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
  3. proxy->setFilterFixedString(QString)

The filter proxy model is much more powerful than demonstrated here. For now, it is enough to remember it exists.

!!! note

  1. This has been an overview of the different kind of classic applications you can develop with Qt 5. The desktop is moving, and soon the mobile devices will be our desktop of tomorrow. Mobile devices have a different user interface design. They are much more simplistic than desktop applications. They do one thing and they do it with simplicity and focus. Animations are an important part of the mobile experience. A user interface needs to feel alive and fluent. The traditional Qt technologies are not well suited for this market.

Coming next: Qt Quick to the rescue.

Qt Quick Application

There is an inherent conflict in modern software development. The user interface is moving much faster than our back-end services. In a traditional technology, you develop the so-called front-end at the same pace as the back-end. This results in conflicts when customers want to change the user interface during a project, or develop the idea of a user interface during the project. Agile projects, require agile methods.

Qt Quick provides a declarative environment where your user interface (the front-end) is declared like HTML and your back-end is in native C++ code. This allows you to get the best of both worlds.

This is a simple Qt Quick UI below

  1. import QtQuick
  2. Rectangle {
  3. width: 240; height: 240
  4. Rectangle {
  5. width: 40; height: 40
  6. anchors.centerIn: parent
  7. color: '#FFBB33'
  8. }
  9. }

The declaration language is called QML and it needs a runtime to execute it. Qt provides a standard runtime called qml. You can also write a custom runtime. For this, we need a quick view and set the main QML document as a source from C++. Then you can show the user interface.

  1. #include <QtGui>
  2. #include <QtQml>
  3. int main(int argc, char *argv[])
  4. {
  5. QGuiApplication app(argc, argv);
  6. QQmlApplicationEngine engine("main.qml");
  7. return app.exec();
  8. }

Let’s come back to our earlier examples. In one example, we used a C++ city model. It would be great if we could use this model inside our declarative QML code.

To enable this, we first code our front-end to see how we would want to use a city model. In this case, the front-end expects an object named cityModel which we can use inside a list view.

  1. import QtQuick
  2. Rectangle {
  3. width: 240; height: 120
  4. ListView {
  5. width: 180; height: 120
  6. anchors.centerIn: parent
  7. model: cityModel
  8. delegate: Text { text: model.city }
  9. }
  10. }

To enable the cityModel, we can mostly re-use our previous model, and add a context property to our root context. The root context is the other root-element in the main document.

  1. m_model = QSqlTableModel(this);
  2. ... // some magic code
  3. QHash<int, QByteArray> roles;
  4. roles[Qt::UserRole+1] = "city";
  5. roles[Qt::UserRole+2] = "country";
  6. m_model->setRoleNames(roles);
  7. engine.rootContext()->setContextProperty("cityModel", m_model);