Qt - QML Quick Start

This quick start guide uses Qt Creator on Qt 6, running on Linux.

The scope is to create a real Qt app that uses both QML (for UI) and C++ (for logic).


1. Project Setup in Qt Creator

Create a New Project

  1. Open Qt Creator
  2. Go to FileNew File or Project
  3. Select ApplicationQt Quick Application - Empty
  4. Choose a name and location
  5. Choose the Qt 6 kit (e.g., Desktop Qt 6.x GCC 64bit)
  6. Finish

This creates a basic project with main.cpp, main.qml, and a .pro file.

Project Structure

YourProject/
├── main.cpp
├── main.qml
├── YourProject.pro

Qt Creator auto-generates a CMakeLists.txt if you chose CMake, or a .pro file if you chose qmake.


2. Hello Luci4!

Replace the contents of main.cpp with:

This example doesn't use QML, it's just a quick hello-world application.
snippet.cpp
#include <QApplication>
#include <QLabel>
 
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QLabel label("Hello Luci4!");
    label.show();
    return app.exec();
}

Build and Run

  1. Click the green triangle in the lower left corner
  2. You will see a window with “Hello World!”
Tip: If you chose qmake and it fails to compile, check that your `.pro` file includes `QT += widgets`

3. Introduction to QML

QML is a declarative UI language, readable, and easy to learn.

Modify `main.qml`

snippet.qml
import QtQuick
import QtQuick.Controls
 
ApplicationWindow {
    visible: true
    width: 360
    height: 640
    title: "Hello QML"
 
    Rectangle {
        anchors.fill: parent
        color: "#f0f0f0"
 
        Text {
            anchors.centerIn: parent
            text: "Hello from QML!"
            font.pixelSize: 24
        }
    }
}

Make Sure main.cpp Loads QML

If your project was created as a Qt Quick app, this is already done:

snippet.cpp
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
 
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
 
    // This line is auto-generated
    const QUrl url(QStringLiteral("qrc:/your-app/Main.qml"));
 
    // This block of code in main.cpp is auto-generated by Qt when 
    // you create a QML-based application using tools like Qt Creator. 
    // It sets up a safety mechanism to gracefully exit the 
    // application if the QML engine fails to load the main QML file.
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreated,
        &app,
        [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        },
        Qt::QueuedConnection);
 
    engine.load(url);
    return app.exec();
}

QGuiApplication

QQmlApplicationEngine


4. C++ <-> QML Communication

Step 1: Create a C++ Backend Class

controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>

class Controller : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged)

    public:
        QString status() const;
        void setStatus(const QString &status);

        Q_INVOKABLE void changeColour();

    signals:
        void statusChanged();

    private:
        QString m_status;
};

#endif // CONTROLLER_H

What is `#ifndef CONTROLLER_H`?

Step 2: C++ Implementation

controller.cpp

snippet.cpp
#include "controller.h"
#include <QDebug>
 
QString Controller::status() const {
    return m_status;
}
 
void Controller::setStatus(const QString &status) {
    if (m_status != status) {
        m_status = status;
        emit statusChanged();
    }
}
 
void Controller::changeColour() {
    qDebug() << "Color change triggered from QML";
    setStatus("Color changed");
}

Step 3: Register the Class in main.cpp

snippet.cpp
#include <QQmlContext>
#include "controller.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "controller.h"
 
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
 
    const QUrl url(QStringLiteral("qrc:/your-app/Main.qml"));
    Controller controller;
 
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreated,
        &app,
        [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        },
        Qt::QueuedConnection);
 
    engine.rootContext()->setContextProperty("controller", &controller);
    engine.load(url);
 
    return app.exec();
}

Step 4: Connect from QML

snippet.qml
import QtQuick
import QtQuick.Controls
 
ApplicationWindow {
    visible: true
    width: 360
    height: 640
    title: "Hello Luci4"
 
    Rectangle {
        id: box
        color: "skyblue"
        anchors.fill: parent
 
        Column {
            anchors.centerIn: parent
            spacing: 16
 
            Button {
                text: "Change Colour"
                onClicked: {
                    controller.changeColour()
                    box.color = "lightgreen"
                }
            }
 
            Label {
                text: controller.status
            }
        }
    }
}

Signals

Example:

snippet.cpp
signals:
    void dataReady(QString result);
snippet.cpp
emit dataReady("Download complete");

You can connect it like this:

snippet.cpp
connect(worker, &Worker::dataReady, this, &MainWindow::onDataReady);

Or expose it to QML and bind to it:

snippet.qml
Connections {
    target: controller
    onDataReady: console.log("Received:", result)
}
Signals must be declared inside the `signals:` section and must be emitted manually with `emit`.

5. Using Models in QML (Model-View)

C++ Model

snippet.cpp
QStringListModel model;
model.setStringList({"Item 1", "Item 2", "Item 3"});
engine.rootContext()->setContextProperty("myModel", &model);

QML View

snippet.qml
ListView {
    width: parent.width
    height: parent.height
    model: myModel
    delegate: Rectangle {
        width: parent.width
        height: 40
        color: "#dddddd"
 
        Text {
            anchors.centerIn: parent
            text: modelData
        }
    }
}

Custom QAbstractListModel (Advanced)

Subclass QAbstractListModel if you want to expose multiple roles:

class ItemModel : public QAbstractListModel {
    Q_OBJECT
public:
    enum Roles { NameRole = Qt::UserRole + 1 };

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QHash<int, QByteArray> roleNames() const override;

private:
    QList<QString> items;
};

6. Using Qt Quick Controls 2

Qt Quick Controls provides platform-aware UI widgets like Button, Slider, TextField, etc.

Update `.pro` File

snippet.pro
QT += quick quickcontrols2

Use in QML

snippet.qml
import QtQuick.Controls
 
ApplicationWindow {
    visible: true
    width: 400
    height: 600
 
    Column {
        anchors.centerIn: parent
        spacing: 10
 
        TextField {
            placeholderText: "Type something"
        }
 
        Slider {
            from: 0; to: 100
        }
 
        Button {
            text: "Click me"
            onClicked: console.log("Clicked")
        }
    }
}
Use Layouts (`ColumnLayout`, `RowLayout`) for adaptive UIs

7. Networking

C++: Fetching Data

snippet.cpp
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, [](QNetworkReply *reply) {
    qDebug() << reply->readAll();
});
manager->get(QNetworkRequest(QUrl("https://api.example.com")));

QML: Lightweight Networking

snippet.qml
Button {
    text: "Fetch"
    onClicked: {
        var xhr = new XMLHttpRequest()
        xhr.open("GET", "https://api.example.com")
        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.DONE)
                console.log(xhr.responseText)
        }
        xhr.send()
    }
}

8. SQLite Database Integration

C++: Connecting to SQLite

snippet.cpp
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("mydata.db");
if (db.open()) {
    QSqlQuery query;
    query.exec("CREATE TABLE IF NOT EXISTS items (name TEXT)");
    query.exec("INSERT INTO items VALUES ('Apple')");
}

Displaying Data in QML

snippet.cpp
QSqlQueryModel *sqlModel = new QSqlQueryModel();
sqlModel->setQuery("SELECT name FROM items");
engine.rootContext()->setContextProperty("sqlModel", sqlModel);
snippet.qml
ListView {
    model: sqlModel
    delegate: Text {
        text: model.name
    }
}
Use `QSqlTableModel` or a custom proxy model for sorting/filtering

9. Homework

Implement the following:

This combines:

Persistent Time Picker Example (QML + SharedPreferences)

snippet.qml
ApplicationWindow {
    width: 400
    height: 600
    visible: true
 
    QtObject {
        id: settings
        property int hour: 8
        property int minute: 0
    }
 
    Column {
        anchors.centerIn: parent
        spacing: 12
 
        TimePicker {
            id: picker
            from: 0; to: 23
            value: settings.hour
            onValueChanged: settings.hour = value
        }
 
        Switch {
            id: toggle
            checked: true
            onCheckedChanged: backend.setEnabled(checked)
        }
    }
}

C++ Worker Timer Callback Example

snippet.cpp
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, []() {
    qDebug() << "Timer triggered at scheduled time!";
});
timer->start(60000); // Check every minute
Combine with `QTime::currentTime()` and stored preferences to trigger the event when desired

Next Steps


Resources

broken links, find alternatives:


snippet.cpp
by:
▖     ▘▖▖
▌ ▌▌▛▘▌▙▌
▙▖▙▌▙▖▌ ▌
 
written: long time ago, edited: June 2025