Getting Started with QML: Core Concepts and Fundamentals
- Overview
QML (Qt Markup Language) represents a declarative approach to building user interfaces within the Qt framework. Having worked with Qt for several years, from version 4.7 through the current Qt 5.6 releases, exploring QML opens up possibilities for building modern embedded and mobile applications. This guide walks through essential QML concepts and provides practical examples to illustrate key functionality.
- Prerequisites
Before diving into QML development, familiarize yourself with these fundamental areas:
- Basic elements
- Components (composite elements)
- Positioners (layout containers)
- Anchor-based element positioning
- Input elements for single-line and multi-line text
- Quick Controls components
- Models and views
- Canvas drawing capabilities
- Basic Elements
QML elements divide into two categories: visual and non-visual.
Visual Elements
Item- Base container for visual elementsRectangle- Rectangular shape with customizable fill and borderText- Display text contentImage- Display raster graphics
Non-Visual Elements
MouseArea- Defines interactive mouse regions
An important distinction: MouseArea itself is invisible despite providing interaction capabilities. It simply defines a region that responds to mouse events.
Separation of Display and Interaction
QML follows a design principle where visual display elements remain separate from interaction handlers. This separation allows developers to build flexible, maintainable interfaces by combining visual components with interaction areas.
- Components
A component encapsulates reusable combinations of basic elements into a single file. This promotes code reuse and cleaner application architecture. Creating components involves grouping related elements and exposing necessary properties and signals.
- Positioners
Positioners automatically arrange child elements within a container:
Row- Arranges items horizontallyColumn- Arranges items verticallyGrid- Arranges items in a grid patternFlow- Arranges items like text wrapping
- Anchor-Based Layout
Beyond postiioners, elements can be positioned using anchors. Anchors define relationships between element edges and centers, enabling precise layout control through properties like anchors.left, anchors.right, anchors.centerIn, and anchors.horizontalCenter.
- Input Elements
TextInput- Single-line text input fieldTextEdit- Multi-line rich text editing control
- Quick Controls Components
Qt Quick Controls provides a comprehensive set of UI components for desktop and mobile applications:
Application Structure
| ApplicationWindow | Top-level application window (similar to QMainWindow) |
|---|---|
| MenuBar | Horizontal menu bar at window top |
| StatusBar | Status information display bar |
| ToolBar | Toolbar for containing tool buttons and controls |
| Action | Abstract action for binding to UI elements |
Navigation and Views
| ScrollView | Scrollable container view |
|---|---|
| SplitView | Resizable split panel layout |
| StackView | Stack-based navigation container |
| TabView | Tabbed panel container |
| TableView | Table display with headers and scrolling |
Controls
| BusyIndicator | Activity indication display |
|---|---|
| Button | Push button control |
| CheckBox | Checkbox selection control |
| ComboBox | Dropdown selection control |
| GroupBox | Container with title and border |
| Label | Text display label |
| ProgressBar | Progress indication bar |
| RadioButton | Radio button selection |
| Slider | Slider control for value selection |
| SpinBox | Numeric value spinner |
| Switch | Toggle switch control |
| TextArea | Multi-line text editing area |
| TextField | Single-line text input field |
| ToolButton | Toolbar button control |
Menu Components
| Menu | Menu, submenu, and popup menu |
|---|---|
| MenuSeparator | Menu separator line |
| MenuItem | Individual menu entry |
- Models and Views
QML provides sophisticated model-view architecture through components like ListView, GridView, and Repeater. These work together with delegate components to display data efficiently. The Repeater element creates multiple instances based on a model, while delegates define how each item renders.
- Canvas Element
The Canvas element, introduced in Qt5, enables custom drawing operations similar to HTML5 canvas. It supports:
- Drawing paths with custom pens
- Fill and gradient styles
- Text rendering
- Path-based drawing commands
- Practical Examples
Example 1: Reusable Rounded Rectangle Component
import QtQuick 2.5
// Rounded rectangle with click interaction support
Rectangle {
property alias displayText: label.text;
property alias displayColor: label.color;
id: root;
width: 120;
height: 120;
radius: 60;
antialiasing: true;
signal activated();
MouseArea {
width: root.width;
height: root.height;
onClicked: {
root.activated();
console.log("rectangle activated");
}
}
Text {
id: label;
text: "";
color: "black";
anchors.centerIn: parent;
}
}
Example 2: Rotating Pinwheel Animation
import QtQuick 2.0
import QtQuick.Window 2.0
import QtGraphicalEffects 1.0
import "../components"
Window {
id: mainWindow;
visible: true;
width: 600;
height: 400;
Rectangle {
id: background;
color: "lightsteelblue";
width: mainWindow.width;
height: mainWindow.height;
}
MouseArea {
width: background.width;
height: background.height;
onClicked: {
rotationController.pause();
}
}
RoundedBox {
id: rotatingItem;
anchors.centerIn: parent;
ConicalGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "lightsteelblue" }
GradientStop { position: 1.0; color: "blue" }
}
}
RotationAnimation {
id: rotationController;
target: rotatingItem;
loops: Animation.Infinite;
from: 0;
to: 360;
duration: 1000;
}
onActivated: {
if (rotationController.paused) {
rotationController.resume();
} else {
rotationController.start();
}
}
}
}
Example 3: Traffic Light with State-Based Colors
import QtQuick 2.0
import QtQuick.Window 2.0
import "../components"
Window {
id: root;
visible: true;
width: 370;
height: 130;
function executeAction() {
console.log("action executed");
}
Rectangle {
id: container;
width: root.width;
height: root.height;
anchors.centerIn: parent;
Row {
id: lightCluster;
spacing: 2;
states: [
State {
name: "stopSignal"
PropertyChanges { target: redLamp; color: "red"; }
PropertyChanges { target: greenLamp; color: "black"; }
PropertyChanges { target: amberLamp; color: "black"; }
},
State {
name: "goSignal"
PropertyChanges { target: redLamp; color: "black"; }
PropertyChanges { target: greenLamp; color: "green"; }
PropertyChanges { target: amberLamp; color: "black"; }
},
State {
name: "cautionSignal"
PropertyChanges { target: redLamp; color: "black"; }
PropertyChanges { target: greenLamp; color: "black"; }
PropertyChanges { target: amberLamp; color: "yellow"; }
}
]
anchors.centerIn: parent;
RoundedBox {
id: redLamp;
color: "black";
radius: width / 2;
}
RoundedBox {
id: greenLamp;
color: "black";
radius: width / 2;
}
RoundedBox {
id: amberLamp;
color: "black";
radius: width / 2;
}
transitions: [
Transition {
from: "stopSignal"
to: "cautionSignal"
ColorAnimation { target: redLamp; properties: "color"; duration: 1000; }
ColorAnimation { target: amberLamp; properties: "color"; duration: 1000; }
}
]
}
property bool isRedActive: false;
MouseArea {
anchors.fill: parent;
onClicked: {
if (lightCluster.state == "stopSignal" || lightCluster.state == "goSignal") {
lightCluster.state = "cautionSignal";
} else {
if (container.isRedActive == false) {
lightCluster.state = "stopSignal";
container.isRedActive = true;
} else {
lightCluster.state = "goSignal";
container.isRedActive = false;
}
}
}
}
}
}
Example 4: Timer-Controlled Traffic Light
property bool isStopLight: false;
property int elapsedCounter: 0;
Timer {
id: redTimer;
interval: 50;
repeat: true;
triggeredOnStart: true;
property int duration: 60;
onTriggered: {
if (lightCluster.state != "stopSignal") {
lightCluster.state = "stopSignal";
root.isStopLight = true;
}
++elapsedCounter;
redLamp.displayText = duration - elapsedCounter;
if (duration <= elapsedCounter) {
elapsedCounter = 0;
amberTimer.start();
redTimer.stop();
}
}
}
Timer {
id: amberTimer;
interval: 1000;
repeat: true;
triggeredOnStart: true;
property int duration: 3;
onTriggered: {
if (lightCluster.state != "cautionSignal") {
lightCluster.state = "cautionSignal";
}
++elapsedCounter;
amberLamp.displayText = duration - elapsedCounter;
if (duration <= elapsedCounter) {
elapsedCounter = 0;
if (isStopLight) {
greenTimer.start();
} else {
redTimer.start();
}
stop();
}
}
}
Timer {
id: greenTimer;
interval: 150;
repeat: true;
triggeredOnStart: true;
property int duration: 20;
onTriggered: {
if (lightCluster.state != "goSignal") {
lightCluster.state = "goSignal";
root.isStopLight = false;
}
++elapsedCounter;
greenLamp.displayText = duration - elapsedCounter;
if (duration <= elapsedCounter) {
elapsedCounter = 0;
amberTimer.start();
greenTimer.stop();
}
}
}
Example 5: Calendar View with GridView
import QtQuick 2.6
import QtQuick.Window 2.0
import "../components"
Window {
visible: true;
width: 300;
height: 300;
Rectangle {
id: mainContainer;
anchors.fill: parent;
width: mainContainer.width;
height: mainContainer.height;
color: "yellow";
Row {
id: dayHeader;
spacing: 2;
padding: 5;
Repeater {
model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
Rectangle {
width: (mainContainer.width - 6 * dayHeader.spacing - 10) / 7;
height: 30;
radius: 3;
color: "lightBlue"
Text {
anchors.centerIn: parent;
text: modelData;
}
}
}
}
GridView {
id: dateGrid;
boundsBehavior: Flickable.StopAtBounds;
anchors {
top: dayHeader.bottom;
left: mainContainer.left;
leftMargin: 5;
right: mainContainer.right;
rightMargin: 5;
bottom: mainContainer.bottom;
}
model: 42;
cellWidth: (mainContainer.width - 10) / 7;
cellHeight: (mainContainer.width - 10) / 7;
delegate: dateDelegate;
focus: true;
}
Component {
id: dateDelegate;
Rectangle {
width: dateGrid.cellWidth;
height: dateGrid.cellHeight;
color: GridView.isCurrentItem ? "green" : "lightGreen"
border.color: Qt.lighter("green");
Text {
anchors.centerIn: parent;
font.pixelSize: 10;
text: index + 1;
}
}
}
}
}
- Key Takeaways
- Element separation: QML encourages separating visual display from interaction handling (e.g., MouseArea paired with Rectangle)
- Component reuse: Group reusable element combinations into components for cleaner code
- State machines: The State and Transition elements provide powerful mechanisms for managing UI state changes with automatic animations
- Positioning flexibility: Choose between positioners (Row, Column, Grid) and anchor-based layouts based on your needs
- Delegate patterns: ListView, GridView, and Repeater use delegates to render model data efficiently
- Animation support: Built-in animation types (NumberAnimation, RotationAnimation, ColorAnimation) simplify creating smooth transitions
These foundational concepts provide the building blocks for creating sophisticated QML-based applications across desktop and mobile platforms.