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).
File → New File or ProjectApplication → Qt Quick Application - Empty
This creates a basic project with main.cpp, main.qml, and a .pro file.
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.
Replace the contents of main.cpp with:
This example doesn't use QML, it's just a quick hello-world application.
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello Luci4!"); label.show(); return app.exec(); }
Tip: If you chose qmake and it fails to compile, check that your `.pro` file includes `QT += widgets`
QML is a declarative UI language, readable, and easy to learn.
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 } } }
If your project was created as a Qt Quick app, this is already done:
#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 is a class that manages application-level resources for GUI applications using Qt Quick.QApplication instead.QQmlApplicationEngine loads and manages your QML UI.engine.load(QUrl(...)) to point it to your QML entry point.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
#ifndef CONTROLLER_H means “if not defined CONTROLLER_H”#define CONTROLLER_H defines the symbol#endif closes the blockcontroller.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"); }
#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(); }
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: void dataReady(QString result);
emit dataReady("Download complete");
You can connect it like this:
connect(worker, &Worker::dataReady, this, &MainWindow::onDataReady);
Or expose it to QML and bind to it:
Connections { target: controller onDataReady: console.log("Received:", result) }
Signals must be declared inside the `signals:` section and must be emitted manually with `emit`.
QStringListModel model; model.setStringList({"Item 1", "Item 2", "Item 3"}); engine.rootContext()->setContextProperty("myModel", &model);
ListView { width: parent.width height: parent.height model: myModel delegate: Rectangle { width: parent.width height: 40 color: "#dddddd" Text { anchors.centerIn: parent text: modelData } } }
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;
};
Qt Quick Controls provides platform-aware UI widgets like Button, Slider, TextField, etc.
QT += quick quickcontrols2
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
QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, [](QNetworkReply *reply) { qDebug() << reply->readAll(); }); manager->get(QNetworkRequest(QUrl("https://api.example.com")));
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() } }
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')"); }
QSqlQueryModel *sqlModel = new QSqlQueryModel(); sqlModel->setQuery("SELECT name FROM items"); engine.rootContext()->setContextProperty("sqlModel", sqlModel);
ListView { model: sqlModel delegate: Text { text: model.name } }
Use `QSqlTableModel` or a custom proxy model for sorting/filtering
This combines:
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) } } }
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
ItemCard.qml, HeaderBar.qmlQt.labs.settings.Settings for persisting UI or state preferencesSingletons in QML for shared/global state across componentsbroken links, find alternatives:
by: ▖ ▘▖▖ ▌ ▌▌▛▘▌▙▌ ▙▖▙▌▙▖▌ ▌ written: long time ago, edited: June 2025