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
- Open Qt Creator
- Go to
FileβNew File or Project - Select
ApplicationβQt Quick Application - Empty - Choose a name and location
- Choose the Qt 6 kit (e.g., Desktop Qt 6.x GCC 64bit)
- 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
- Click the green triangle in the lower left corner
- 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
QGuiApplicationis a class that manages application-level resources for GUI applications using Qt Quick.- It handles events, keyboard input, and the event loop.
- If youβre using QWidget-based UIs, use
QApplicationinstead.
QQmlApplicationEngine
QQmlApplicationEngineloads and manages your QML UI.- It parses your QML files and creates the UI at runtime.
- You use
engine.load(QUrl(...))to point it to your QML entry point.
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`?
- This is an include guard, used to prevent the header from being included more than once.
#ifndef CONTROLLER_Hmeans βif not defined CONTROLLER_Hβ#define CONTROLLER_Hdefines the symbol#endifcloses the block- This avoids compiler errors due to multiple definitions.
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
- Signals are Qtβs messaging mechanism.
- Think of them like events: a button click emits a signal.
- You can connect signals to slots (functions), either in C++ or QML.
- They enable decoupled communication between objects.
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:
- Main screen with a list of items from SQLite
- Button to fetch data from an API and update DB
- Color-changing background on button click
- Time picker to schedule a daily task with toggle switch
This combines:
- C++ logic (network, SQLite, scheduled tasks)
- QML UI (views, buttons, time picker)
- Signal-slot communication between QML and C++
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
- Break your QML into reusable components like
ItemCard.qml,HeaderBar.qml - Use
Qt.labs.settings.Settingsfor persisting UI or state preferences - Explore
Singletonsin QML for shared/global state across components - Implement background task scheduler using QTimer + stored time preferences
Resources
broken links, find alternatives:
- snippet.cpp
by: β βββ β βββββββ βββββββ β written: long time ago, edited: June 2025
