Presenta els controls bàsics de Qt Quick necessaris per crear interfícies d'usuari funcionals i interactives.
Qt Quick Controls proporciona un conjunt de components d'interfície d'usuari llests per emprar. Els més rellevants per al nostre projecte són:
Qt Quick empra un sistema d'anchors per posicionar elements de manera relativa:
Rectangle {
anchors.fill: parent // Omple tot l'espai del pare
anchors.centerIn: parent // Centra dins del pare
anchors.left: parent.left // Alinea a l'esquerra
anchors.margins: 10 // Marges a tots els costats
}
import QtQuick 2.15
import QtQuick.Controls 6.5
Window {
width: 400
height: 300
visible: true
title: "Exemple Botó"
Button {
text: "Prem-me"
anchors.centerIn: parent
onClicked: {
console.log("Botó clicat!")
}
}
}
Rectangle {
width: 100
height: 100
color: "lightblue"
Image {
source: "imatge.png"
anchors.fill: parent
anchors.margins: 10
fillMode: Image.PreserveAspectFit
MouseArea {
anchors.fill: parent
onClicked: {
console.log("Imatge clicada")
}
}
}
}
Cada component té propietats que en defineixen l'aspecte i comportament:
Text {
text: "Hola món"
color: "blue"
font.pixelSize: 24
font.bold: true
horizontalAlignment: Text.AlignHCenter
}
Rectangle {
width: 200
height: 100
color: "green"
radius: 10 // Arrodoniment cantonades
border.color: "black"
border.width: 2
}
Implementa navegació entre múltiples pantalles dins una aplicació mitjançant lliscament (swipe) o canvi programàtic.
import QtQuick 2.15
import QtQuick.Controls 6.5
Window {
width: 640
height: 480
visible: true
SwipeView {
id: vista
anchors.fill: parent
currentIndex: 0
Page {
id: pagina1
background: Rectangle { color: "lightblue" }
Label {
text: "Pàgina 1"
anchors.centerIn: parent
font.pixelSize: 36
}
}
Page {
id: pagina2
background: Rectangle { color: "lightgreen" }
Label {
text: "Pàgina 2"
anchors.centerIn: parent
font.pixelSize: 36
}
}
}
PageIndicator {
count: vista.count
currentIndex: vista.currentIndex
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
}
Canvia de pàgina des del codi sense necessitat de lliscar:
Button {
text: "Anar a configuració"
onClicked: {
vista.currentIndex = 1
}
}
Button {
text: "Tornar al joc"
onClicked: {
vista.currentIndex = 0
}
}
Pots personalitzar l'aspecte dels indicadors de pàgina:
PageIndicator {
id: indicator
count: vista.count
currentIndex: vista.currentIndex
anchors.bottom: vista.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
delegate: Rectangle {
width: 8
height: 8
radius: 4
color: index === indicator.currentIndex ? "blue" : "gray"
}
}
SwipeView permet afegir animacions en canviar de pàgina:
SwipeView {
id: vista
// Desactiva animació de swipe per fer-ne una custom
interactive: true
Behavior on currentIndex {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
}
Desa i recupera dades entre execucions de l'aplicació de manera senzilla i automàtica.
Qt Labs Settings proporciona un component per emmagatzemar preferències de l'aplicació:
import Qt.labs.settings 1.0
import QtQuick 2.15
import QtQuick.Controls 6.5
import Qt.labs.settings 1.0
Window {
width: 400
height: 300
visible: true
Settings {
id: configuracio
category: "estadistiques"
property int partidesGuanyades: 0
property int partidesPerdudes: 0
}
Column {
anchors.centerIn: parent
spacing: 10
Label {
text: "Guanyades: " + configuracio.partidesGuanyades
}
Label {
text: "Perdudes: " + configuracio.partidesPerdudes
}
Button {
text: "He guanyat!"
onClicked: {
configuracio.partidesGuanyades += 1
}
}
Button {
text: "He perdut"
onClicked: {
configuracio.partidesPerdudes += 1
}
}
Button {
text: "Reset"
onClicked: {
configuracio.partidesGuanyades = 0
configuracio.partidesPerdudes = 0
}
}
}
}
Pots organitzar configuracions en diferents grups:
Settings {
id: configJoc
category: "joc"
property string dificultat: "mitja"
property bool soActivat: true
}
Settings {
id: configVisual
category: "visual"
property bool modeFosc: false
property int midaFont: 14
}
Settings pot desar diversos tipus de dades:
Settings {
id: config
// Tipus bàsics
property int numero: 42
property real decimal: 3.14
property bool boolea: true
property string text: "Hola"
// Data i hora
property date data: new Date()
// Arrays (com a string JSON)
property string llistaJSON: "[]"
function desaLlista(array) {
llistaJSON = JSON.stringify(array)
}
function carregaLlista() {
return JSON.parse(llistaJSON)
}
}
Pots enllaçar propietats de Settings amb propietats d'altres components:
Settings {
id: config
property alias modeFosc: temaManager.darkMode
property alias volum: reproductor.volume
}
Item {
id: temaManager
property bool darkMode: false
}
Item {
id: reproductor
property real volume: 0.5
}
Les dades es desen en ubicacions específiques del sistema:
~/.config/organization/application.confHKEY_CURRENT_USER\Software\organization\application~/Library/Preferences/com.organization.application.plistQt.labs.settings 1.0 al començament del fitxerEmpaqueta recursos (imatges, fonts, fitxers QML) dins l'executable de l'aplicació per facilitar-ne la distribució i l'accés.
El sistema de recursos de Qt permet incloure fitxers dins l'aplicació:
Crea un fitxer resources.qrc:
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>GamePage.qml</file>
<file>ConfigPage.qml</file>
</qresource>
</RCC>
Pots organitzar recursos en grups lògics:
<RCC>
<qresource prefix="/">
<!-- Fitxers QML -->
<file>main.qml</file>
<file>GamePage.qml</file>
<file>ConfigPage.qml</file>
<!-- Imatges amb alias -->
<file alias="img/rock.png">imatges/pedra_gran.png</file>
<file alias="img/paper.png">imatges/paper_gran.png</file>
<file alias="img/scissors.png">imatges/tisora_gran.png</file>
<file alias="img/settings.png">icons/configuracio.png</file>
</qresource>
</RCC>
Pots accedir als recursos de diverses maneres:
// Amb prefix qrc:
Image {
source: "qrc:/img/rock.png"
}
// Sense prefix (també funciona):
Image {
source: "/img/paper.png"
}
// Ruta relativa si està al mateix prefix:
Image {
source: "img/scissors.png"
}
Pots crear diferents prefixos per organitzar millor:
<RCC>
<qresource prefix="/qml">
<file>main.qml</file>
<file>GamePage.qml</file>
</qresource>
<qresource prefix="/images">
<file>rock.png</file>
<file>paper.png</file>
</qresource>
<qresource prefix="/fonts">
<file>Roboto-Regular.ttf</file>
</qresource>
</RCC>
Accés amb prefixos múltiples:
Image {
source: "qrc:/images/rock.png"
}
FontLoader {
source: "qrc:/fonts/Roboto-Regular.ttf"
}
Afegeix el fitxer .qrc al projecte:
set(PROJECT_SOURCES
main.cpp
resources.qrc
)
qt_add_executable(appname
${PROJECT_SOURCES}
)
Genera valors aleatoris per implementar la lògica del joc on l'aplicació ha de triar una opció de manera impredictible.
JavaScript/QML proporciona funcions matemàtiques per generar aleatorietat:
// Valor aleatori entre 0 i 1
var aleatori = Math.random()
console.log(aleatori) // Ex: 0.7382...
// Valor aleatori entre 0 i N-1
var valor = Math.floor(Math.random() * 3)
console.log(valor) // 0, 1 o 2
function opcioAleatoria() {
var opcions = ["Pedra", "Paper", "Tisora"]
var index = Math.floor(Math.random() * opcions.length)
return opcions[index]
}
// Ús
var jugadaApp = opcioAleatoria()
console.log("Aplicació juga:", jugadaApp)
Item {
id: joc
function jugadaAleatoria() {
var opcions = ["R", "P", "S"]
return opcions[Math.floor(Math.random() * opcions.length)]
}
function imatgePerOpcio(opcio) {
if (opcio === "R") return "qrc:/img/rock.png"
if (opcio === "P") return "qrc:/img/paper.png"
if (opcio === "S") return "qrc:/img/scissors.png"
return ""
}
MouseArea {
onClicked: {
var jugadaUsuari = "R" // Exemple
var jugadaApp = joc.jugadaAleatoria()
console.log("Usuari:", jugadaUsuari)
console.log("App:", jugadaApp)
var imatgeApp = joc.imatgePerOpcio(jugadaApp)
console.log("Imatge:", imatgeApp)
}
}
}
Per generar valors en un rang específic:
// Valor entre min i max (inclosos)
function aleatoriBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
// Exemples
var dau = aleatoriBetween(1, 6) // 1-6
var percentatge = aleatoriBetween(0, 100) // 0-100
Si vols que certes opcions siguin més probables:
function opcioAmbPes() {
var aleatori = Math.random()
// Pedra: 50%, Paper: 30%, Tisora: 20%
if (aleatori < 0.5) return "R"
if (aleatori < 0.8) return "P"
return "S"
}
Per barrejar elements d'un array (algorisme Fisher-Yates):
function barrejar(array) {
var copia = array.slice() // Copia per no modificar l'original
for (var i = copia.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1))
var temp = copia[i]
copia[i] = copia[j]
copia[j] = temp
}
return copia
}
// Ús
var opcions = ["R", "P", "S"]
var barrejat = barrejar(opcions)
console.log(barrejat) // Ex: ["S", "R", "P"]
Math.random() retorna valors entre 0 i 1 (0 inclusiu, 1 exclusiu)Math.floor() per arrodonir cap avallMath.floor(Math.random() * N) genera valors de 0 a N-1Math.random() NO és criptogràficament segur (no l'empris per seguretat)Comunica dades i esdeveniments entre components QML de manera eficient i desacoblada.
QML permet crear propietats i signals personalitzats per facilitar la comunicació:
Item {
id: boto
// Propietats custom
property string texteBoto: "Clic aquí"
property color colorFons: "blue"
property int comptador: 0
Rectangle {
color: boto.colorFons
Text {
text: boto.texteBoto + " (" + boto.comptador + ")"
}
}
}
Item {
id: boto
// Signal sense paràmetres
signal clicat()
// Signal amb paràmetres
signal valorCanviat(int nouValor)
signal botoActivat(string missatge, bool estat)
MouseArea {
anchors.fill: parent
onClicked: {
boto.clicat() // Emet el signal
boto.valorCanviat(42)
boto.botoActivat("Hola", true)
}
}
}
// Component fill
Item {
id: botoPersonalitzat
signal botoPremut(string missatge)
MouseArea {
anchors.fill: parent
onClicked: {
botoPersonalitzat.botoPremut("S'ha clicat!")
}
}
}
// Component pare
Item {
BotoPersonalitzat {
id: elMeuBoto
Connections {
function onBotoPremut(missatge) {
console.log("Rebut:", missatge)
}
}
}
}
Els alias exposen propietats internes sense duplicar dades:
Rectangle {
id: contenidor
// Alias a propietats internes
property alias texteTitol: titol.text
property alias colorFons: fons.color
Rectangle {
id: fons
color: "white"
anchors.fill: parent
}
Text {
id: titol
text: "Títol"
}
}
// Ús
Contenidor {
texteTitol: "Nou títol" // Modifica directament titol.text
colorFons: "lightblue" // Modifica directament fons.color
}
// BotoRPS.qml
import QtQuick 2.15
Item {
id: botoRPS
// Propietats públiques
property string opcio: "R"
property string imatgeSource: ""
// Signal personalitzat
signal opcioSeleccionada(string opcio, string imatge)
width: 80
height: 80
Rectangle {
id: fons
anchors.fill: parent
color: "orange"
radius: 40
Image {
source: botoRPS.imatgeSource
anchors.fill: parent
anchors.margins: 10
fillMode: Image.PreserveAspectFit
}
MouseArea {
anchors.fill: parent
onClicked: {
botoRPS.opcioSeleccionada(botoRPS.opcio, botoRPS.imatgeSource)
}
}
}
}
Ús del component:
Row {
spacing: 20
BotoRPS {
opcio: "R"
imatgeSource: "qrc:/img/rock.png"
Connections {
function onOpcioSeleccionada(opcio, imatge) {
console.log("Seleccionat:", opcio)
// Lògica del joc...
}
}
}
BotoRPS {
opcio: "P"
imatgeSource: "qrc:/img/paper.png"
Connections {
function onOpcioSeleccionada(opcio, imatge) {
console.log("Seleccionat:", opcio)
}
}
}
}
Pots crear propietats que només es poden llegir externament:
Item {
readonly property int resultat: calcul()
function calcul() {
return 42
}
}
Item {
signal resultatsJoc(string guanyador, int puntsUsuari, int puntsApp, string jugadaUsuari, string jugadaApp)
function finalitzarJoc() {
resultatsJoc("usuari", 5, 3, "R", "S")
}
}
Gestiona diferents estats visuals d'un component i anima els canvis entre ells de manera suau i professional.
Qt Quick proporciona un sistema declaratiu per gestionar estats:
Rectangle {
id: semàfor
width: 100
height: 100
color: "red"
states: [
State {
name: "verd"
PropertyChanges {
target: semàfor
color: "green"
}
},
State {
name: "groc"
PropertyChanges {
target: semàfor
color: "yellow"
}
},
State {
name: "vermell"
PropertyChanges {
target: semàfor
color: "red"
}
}
]
MouseArea {
anchors.fill: parent
onClicked: {
if (semàfor.state === "vermell") semàfor.state = "verd"
else if (semàfor.state === "verd") semàfor.state = "groc"
else semàfor.state = "vermell"
}
}
}
Rectangle {
id: fonsJoc
color: "#AAAAAA"
anchors.fill: parent
states: [
State {
name: "usuariGuanya"
PropertyChanges {
target: fonsJoc
color: "lime"
}
},
State {
name: "usuariPerd"
PropertyChanges {
target: fonsJoc
color: "lightcoral"
}
},
State {
name: "empat"
PropertyChanges {
target: fonsJoc
color: "cyan"
}
}
]
}
// Canvi d'estat
function processarResultat(resultat) {
if (resultat === "guanya") fonsJoc.state = "usuariGuanya"
else if (resultat === "perd") fonsJoc.state = "usuariPerd"
else fonsJoc.state = "empat"
}
Rectangle {
id: quadrat
color: "blue"
states: [
State {
name: "vermell"
PropertyChanges { target: quadrat; color: "red" }
}
]
transitions: [
Transition {
from: "" // Estat per defecte
to: "vermell"
ColorAnimation {
duration: 500
easing.type: Easing.InOutQuad
}
},
Transition {
from: "vermell"
to: ""
ColorAnimation {
duration: 300
}
}
]
}
Per aplicar la mateixa animació a tots els canvis:
transitions: [
Transition {
from: "*" // Des de qualsevol estat
to: "*" // Cap a qualsevol estat
ColorAnimation {
duration: 400
}
}
]
transitions: [
Transition {
from: "*"
to: "usuariGuanya"
SequentialAnimation {
ColorAnimation {
to: "#AAAAAA"
duration: 500
}
ColorAnimation {
to: "lime"
duration: 500
}
}
}
]
State {
name: "expandit"
PropertyChanges {
target: rectangle
width: 300
height: 300
color: "blue"
}
}
Transition {
to: "expandit"
ParallelAnimation {
NumberAnimation {
properties: "width,height"
duration: 500
easing.type: Easing.OutBack
}
ColorAnimation {
duration: 500
}
}
}
// Animació de color
ColorAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
// Animació de números (width, height, opacity, rotation...)
NumberAnimation {
properties: "width,height"
duration: 500
easing.type: Easing.OutBounce
}
// Animació de posició
PropertyAnimation {
property: "x"
duration: 400
}
// Animació de rotació
RotationAnimation {
duration: 1000
direction: RotationAnimation.Clockwise
}
easing.type: Easing.Linear // Velocitat constant
easing.type: Easing.InOutQuad // Suau inici i final
easing.type: Easing.OutBounce // Efecte de bot
easing.type: Easing.InOutElastic // Efecte elàstic
easing.type: Easing.OutBack // Sobrepassa lleugerament
State {
name: "destacat"
PropertyChanges {
target: boto
width: 200
height: 80
color: "gold"
scale: 1.2
opacity: 1.0
}
PropertyChanges {
target: text
font.pixelSize: 24
color: "black"
}
}
L'estat buit ("") és l'estat per defecte:
Rectangle {
// Propietats per defecte
color: "gray"
width: 100
states: [
State {
name: "actiu"
PropertyChanges {
target: parent
color: "blue"
width: 150
}
}
]
// Tornar a l'estat per defecte
MouseArea {
onClicked: {
parent.state = "" // Torna a gray i width 100
}
}
}
from: "*" i to: "*" s'apliquen des de/cap a qualsevol estat"" és l'estat per defecte del componentImplementa un sistema de temes que permeti canviar entre mode clar i mode fosc a tota l'aplicació de manera centralitzada i persistent.
Un singleton és un patró de disseny que garanteix que només existeixi una única instància d'un objecte i que sigui accessible globalment des de qualsevol part de l'aplicació.
Avantatges en QML:
Crea el fitxer ThemeManager.qml:
// ThemeManager.qml
pragma Singleton
import QtQuick 2.15
QtObject {
id: themeManager
// Propietat principal del tema
property bool darkMode: false
// Colors per al mode clar
property color lightBackground: "#FFFFFF"
property color lightText: "#000000"
property color lightAccent: "#0066CC"
property color lightSecondary: "#F5F5F5"
// Colors per al mode fosc
property color darkBackground: "#1E1E1E"
property color darkText: "#FFFFFF"
property color darkAccent: "#3DAEE9"
property color darkSecondary: "#2D2D2D"
// Colors actius (calculats segons darkMode)
property color backgroundColor: darkMode ? darkBackground : lightBackground
property color textColor: darkMode ? darkText : lightText
property color accentColor: darkMode ? darkAccent : lightAccent
property color secondaryColor: darkMode ? darkSecondary : lightSecondary
// Funció per canviar el tema
function toggleTheme() {
darkMode = !darkMode
}
}
Crea el fitxer qmldir al directori arrel del projecte:
singleton ThemeManager 1.0 ThemeManager.qml
Aquest fitxer indica a Qt que ThemeManager és un singleton i com accedir-hi.
Assegura't que el qmldir s'inclogui als recursos:
set(PROJECT_SOURCES
main.cpp
resources.qrc
)
I al resources.qrc:
<RCC>
<qresource prefix="/">
<file>qmldir</file>
<file>ThemeManager.qml</file>
<file>main.qml</file>
<!-- altres fitxers -->
</qresource>
</RCC>
// main.qml
import QtQuick 2.15
import QtQuick.Controls 6.5
import "." // Importa el directori actual (on està qmldir)
Window {
width: 640
height: 480
visible: true
Rectangle {
anchors.fill: parent
color: ThemeManager.backgroundColor
Column {
anchors.centerIn: parent
spacing: 20
Label {
text: "Hola món"
color: ThemeManager.textColor
font.pixelSize: 32
}
Button {
text: darkMode ? "Mode clar" : "Mode fosc"
onClicked: {
ThemeManager.toggleTheme()
}
}
}
}
}
// GamePage.qml
import QtQuick 2.15
import "."
Rectangle {
color: ThemeManager.backgroundColor
Text {
text: "Pàgina de joc"
color: ThemeManager.textColor
}
Rectangle {
color: ThemeManager.secondaryColor
// ...
}
}
// ConfigPage.qml
import QtQuick 2.15
import QtQuick.Controls 6.5
import "."
Rectangle {
color: ThemeManager.backgroundColor
Label {
text: "Configuració"
color: ThemeManager.textColor
}
Switch {
text: "Mode fosc"
checked: ThemeManager.darkMode
onToggled: {
ThemeManager.toggleTheme()
}
}
}
Per recordar la preferència de l'usuari:
import Qt.labs.settings 1.0
Settings {
id: config
category: "visual"
// Enllaça directament amb ThemeManager
property alias darkMode: ThemeManager.darkMode
}
Amb això, quan l'usuari canviï el tema, es desarà automàticament i es recuperarà en obrir l'aplicació de nou.
Mode clar:
#FFFFFF (blanc)#000000 (negre)#0066CC (blau)#F5F5F5 (gris molt clar)Mode fosc:
#1E1E1E (gris molt fosc)#FFFFFF (blanc)#3DAEE9 (blau clar)#2D2D2D (gris fosc)Assegura't que:
Rectangle {
id: fons
color: ThemeManager.backgroundColor
// Anima el canvi de color
Behavior on color {
ColorAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
Text {
id: titol
color: ThemeManager.textColor
// Anima el canvi de color del text
Behavior on color {
ColorAnimation {
duration: 300
}
}
}
}
Pots afegir més propietats al ThemeManager:
pragma Singleton
import QtQuick 2.15
QtObject {
property bool darkMode: false
// Colors
property color backgroundColor: darkMode ? "#1E1E1E" : "#FFFFFF"
property color textColor: darkMode ? "#FFFFFF" : "#000000"
property color accentColor: darkMode ? "#3DAEE9" : "#0066CC"
// Mides de font
property int fontSizeSmall: 12
property int fontSizeNormal: 16
property int fontSizeLarge: 24
property int fontSizeTitle: 36
// Espaiat
property int spacing: 10
property int margins: 20
// Radis
property int radiusSmall: 5
property int radiusNormal: 10
property int radiusLarge: 20
// Durades d'animació
property int animationFast: 150
property int animationNormal: 300
property int animationSlow: 500
}
pragma Singleton marca el fitxer com a singletonimport "." al directori on està el qmldirThemeManager.darkMode actualitza automàticament tots els colorsQt Quick:
Components específics:
Recursos:
Oficials:
Comunitat:
qml i qt-quickQt Creator:
qmlscene:
Qt Design Studio:
Qt proporciona molts exemples de codi:
Qt/Examples/Qt-6.x/Forums:
Stack Overflow:
[qml]: https://stackoverflow.com/questions/tagged/qml[qtquick]: https://stackoverflow.com/questions/tagged/qtquickBlogs i articles:
YouTube:
Cursos online:
Qt Open Source:
Qt Commercial:
Bona sort amb el desenvolupament Qt Quick! 🚀