Ús d'arxius vectorials SVG al QML (Qt6)
Explicació de main.qml de ampMeter00_00.pdf
El fitxer main.qml de ampMeter00_00.pdf estableix la base d'una aplicació Qt Quick que simula un amperímetre simple [1].
Estructura Bàsica de la Finestra
import QtQuick: Importa el mòdul Qt Quick necessari per als elements de la interfície d'usuari [1].Window: Defineix la finestra principal de l'aplicació [1].width: 640,height: 480: Estableix les dimensions de la finestra [1].visible: true: Fa que la finestra sigui visible quan s'inicia l'aplicació [1].title: qsTr("ampMeter00"): Defineix el títol de la finestra com "ampMeter00" [1].
Element Arrel (Rectangle)
Rectangle { id: root ... }: Dins la finestra, hi ha unRectangleanomenatrootque serveix com a contenidor principal [1].anchors.fill: parent: Fa que aquest rectangle ompli completament la finestra pare [1].color: "white": Estableix el color de fons del rectangle com a blanc [1].
Imatge de Fons de l'Amperímetre
Image { id: fonsSvg ... }: S'afegeix un elementImageper al fons de l'amperímetre [1].source: "qrc:/material/AmpMeterFons_c.svg": Carrega la imatge de fons des dels recursos de l'aplicació [1].fillMode: Image.PreserveAspectFit: Aquesta propietat assegura que la imatge mantingui les seves proporcions originals mentre s'ajusta dins de l'espai disponible [1].anchors.centerIn: parent: Centra la imatge de fons dins del seu element pare (rootrectangle) [1].
Imatge de l'Agulla de l'Amperímetre
Image { id: agullaSvg ... }: S'afegeix un altre elementImageper representar l'agulla de l'amperímetre [2].source: "qrc:/material/AmpMeterAgulla_c.svg": Carrega la imatge de l'agulla [2].fillMode: Image.PreserveAspectFit: Igual que amb el fons, manté les proporcions de l'agulla [2].anchors.centerIn: parent: Centra l'agulla dins del seu pare (rootrectangle) [2].rotation: 45: Estableix una rotació inicial de 45 graus per a l'agulla [2]. Aquest és el punt clau de control en aquesta primera versió, ja que la rotació es defineix de manera estàtica [2].
En resum, ampMeter00_00.pdf presenta una interfície d'usuari bàsica amb un fons i una agulla d'amperímetre, on l'agulla es mostra en una posició fixa de 45 graus [1, 2].
// main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00")
Rectangle {
id: root
anchors.fill: parent
color: "white"
Image {
id: fonsSvg
source: "qrc:/material/AmpMeterFons_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: "qrc:/material/AmpMeterAgulla_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
rotation: 45
}
}
}
Canvis Progressius en el Codi
A continuació, s'expliquen els canvis introduïts en cada iteració del projecte:
De ampMeter00_00.pdf a ampMeter00_01.pdf
La versió ampMeter00_01.pdf introdueix la interactivitat i la animació per a l'agulla de l'amperímetre.
• Rotació Inicial i Opacitat de l'Agulla:
- La rotation inicial de l'agulla (agullaSvg) es canvia a 0.
- S'afegeix una propietat opacity: 0.8 a l'agulla.
• Animació de Rotació (Behavior): ** S'afegeix un Behavior on rotation a l'agulla. Això significa que cada vegada que la propietat rotation de l'agulla canvia, s'executarà una animació. ** NumberAnimation { duration: 300; easing.type: Easing.InOutBack }: L'animació durarà 300 mil·lisegons i utilitzarà una funció d'acceleració Easing.InOutBack per a un efecte més dinàmic.
• Botons de Control: ** S'afegeix una fila de botons (Row) a la part inferior de la pantalla, anomenada controlButtons. ** Aquests botons es centren horitzontalment (anchors.horizontalCenter: parent.horizontalCenter) i es col·loquen a 20 píxels de la part inferior (anchors.bottomMargin: 20). ** Hi ha un spacing: 20 entre els botons. ** Cada botó és un Rectangle gris amb un width i height de 60 píxels i un radius de 30 (creant cercles). ** Cada Rectangle conté un Text blanc amb la seva etiqueta (per exemple, "-60", "0", "60"). ** Cada botó té una MouseArea que detecta clics. Quan es clica, la rotation de l'agulla (agullaSvg.rotation) es defineix a un valor específic (-45, -20, 0, 20, 45). ** El cursorShape de la MouseArea es defineix com Qt.PointingHandCursor per indicar que és un element clicable.
Aquesta versió permet a l'usuari controlar la rotació de l'agulla mitjançant botons, amb una animació suau cada vegada que l'agulla canvia de posició.
// main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00_01")
Rectangle {
id: root
anchors.fill: parent
color: "white"
Image {
id: fonsSvg
source: "qrc:/material/AmpMeterFons_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: "qrc:/material/AmpMeterAgulla_c.svg"
fillMode: Image.PreserveAspectFit
opacity: 0.8
anchors.centerIn: parent
rotation: 0
Behavior on rotation {
NumberAnimation {
duration: 300
easing.type: Easing.InOutBack
}
}
}
Row {
id: controlButtons
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
spacing: 20
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "-60"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = -45
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "-30"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = -20
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "0"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 0
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "30"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 20
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "60"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 45
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
De ampMeter00_01.pdf a ampMeter00_02.pdf
La versió ampMeter00_02.pdf afegeix una animació automàtica i contínua per a l'agulla.
• Canvi de Funció d'Acceleració: ** La funció d'acceleració (easing.type) del NumberAnimation de l'agulla es canvia de Easing.InOutBack a Easing.InOutQuad.
• Animació Seqüencial: ** S'afegeix una SequentialAnimation on rotation a l'agulla (agullaSvg). ** loops: Animation.Infinite: Aquesta animació es repetirà indefinidament. ** Consisteix en dues RotationAnimations: *** Una que fa girar l'agulla fins a 45 graus durant 1000 mil·lisegons. *** Una segona que la fa girar fins a -45 graus durant 500 mil·lisegons. ** Aquesta animació fa que l'agulla es mogui contínuament entre 45 i -45 graus quan no es controla manualment.
• Els botons de control manual romanen igual, permetent a l'usuari sobrescriure l'animació automàtica.
main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00_02")
Rectangle {
id: root
anchors.fill: parent
color: "white"
Image {
id: fonsSvg
source: "qrc:/material/AmpMeterFons_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: "qrc:/material/AmpMeterAgulla_c.svg"
fillMode: Image.PreserveAspectFit
opacity: 0.8
anchors.centerIn: parent
rotation: 0
Behavior on rotation {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
SequentialAnimation on rotation {
loops: Animation.Infinite
RotationAnimation {
to: 45
duration: 1000
}
RotationAnimation {
to: -45
duration: 500
}
}
}
Row {
id: controlButtons
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
spacing: 20
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "-60"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = -45
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "-30"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = -20
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "0"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 0
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "30"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 20
cursorShape: Qt.PointingHandCursor
}
}
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: "60"
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = 45
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
De ampMeter00_02.pdf a ampMeter00_03.pdf
La versió ampMeter00_03.pdf refactoritza el codi dels botons utilitzant un model de dades (ListModel) i un Repeater, millorant la modularitat i la facilitat de manteniment.
• Eliminació de l'Animació Seqüencial: ** La SequentialAnimation on rotation s'elimina.
• Model de Dades per a Botons (ListModel): ** Es defineix un ListModel amb id: buttonModel. ** Conté ListElements, cadascun amb una label (el text del botó) i un value (el valor de rotació corresponent). Per exemple, { label: "-60"; value: -45 }.
• Generació de Botons amb Repeater: ** En lloc de declarar cada botó individualment, s'utilitza un Repeater amb model: buttonModel dins del Row de controlButtons. ** El Repeater crea un Rectangle per a cada element del model. ** El Text de cada botó utilitza model.label i l'esdeveniment onClicked de la MouseArea utilitza agullaSvg.rotation = model.value.
Aquest canvi redueix la duplicitat de codi i facilita l'addició o modificació de botons en el futur.
main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00_03")
Rectangle {
id: root
anchors.fill: parent
color: "white"
Image {
id: fonsSvg
source: "qrc:/material/AmpMeterFons_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: "qrc:/material/AmpMeterAgulla_c.svg"
fillMode: Image.PreserveAspectFit
opacity: 0.8
anchors.centerIn: parent
rotation: 0
Behavior on rotation {
NumberAnimation {
duration: 300
easing.type: Easing.InOutBack
}
}
}
ListModel {
id: buttonModel
ListElement { label: "-60"; value: -45 }
ListElement { label: "-30"; value: -20 }
ListElement { label: "0"; value: 0 }
ListElement { label: "30"; value: 20 }
ListElement { label: "60"; value: 45 }
}
Row {
id: controlButtons
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
spacing: 20
Repeater {
model: buttonModel
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: model.label
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: agullaSvg.rotation = model.value
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
}
De ampMeter00_03.pdf a ampMeter00_04.pdf
La versió ampMeter00_04.pdf millora la modularitat extraient la lògica dels botons en un component QML reutilitzable anomenat Botonera.qml.
• Creació del Component Botonera.qml: ** Es crea un nou fitxer Botonera.qml que conté la implementació del Row i el Repeater per als botons. ** Aquest component té una propietat property alias model: repeater.model que permet al component pare passar-li el ListModel. ** Defineix un signal buttonClicked(real value) que s'emet quan es prem un botó, enviant el value de rotació. ** Dins del MouseArea de cada botó, onClicked ara crida botonera.buttonClicked(model.value).
• Ús del Component Botonera a main.qml: ** A main.qml, el Row original de botons es reemplaça per una instància del nou component Botonera. ** controlButtons (la instància de Botonera) rep el buttonModel mitjançant model: buttonModel. ** Captura el senyal onButtonClicked del component Botonera i actualitza la rotació de l'agulla amb agullaSvg.rotation = value.
Aquesta refactorització permet reutilitzar el bloc de botons en altres parts de l'aplicació o en altres projectes, fent el codi de main.qml més net i centrat en la composició.
main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00_04")
Rectangle {
id: root
anchors.fill: parent
color: "white"
Image {
id: fonsSvg
source: "qrc:/material/AmpMeterFons_c.svg"
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: "qrc:/material/AmpMeterAgulla_c.svg"
fillMode: Image.PreserveAspectFit
opacity: 0.8
anchors.centerIn: parent
rotation: 0
Behavior on rotation {
NumberAnimation {
duration: 300
easing.type: Easing.InOutBack
}
}
}
ListModel {
id: buttonModel
ListElement { label: "-60"; value: -45 }
ListElement { label: "-30"; value: -20 }
ListElement { label: "0"; value: 0 }
ListElement { label: "30"; value: 20 }
ListElement { label: "60"; value: 45 }
}
Botonera {
id: controlButtons
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
model: buttonModel
onButtonClicked: function(value) {
agullaSvg.rotation = value
}
}
}
}
Botonera.qml
import QtQuick
Row {
id: botonera
spacing: 20
property alias model: repeater.model
signal buttonClicked(real value)
Repeater {
id: repeater
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: model.label
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: botonera.buttonClicked(model.value)
cursorShape: Qt.PointingHandCursor
}
}
}
}
De ampMeter00_04.pdf a ampMeter00_05.pdf
La versió ampMeter00_05.pdf continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un component QML propi anomenat AmpMeter.qml.
• Creació del Component AmpMeter.qml: ** Es crea un nou fitxer AmpMeter.qml que conté el fons (fonsSvg) i l'agulla (agullaSvg) de l'amperímetre. ** Exposa una propietat property alias rotation: agullaSvg.rotation per permetre al component pare controlar directament la rotació de l'agulla interna. ** També defineix propietats per personalitzar les imatges de fons i agulla (fonsSource, agullaSource) i l'opacitat de l'agulla (agullaOpacity). ** El Behavior on rotation per a l'agulla es troba ara dins d'aquest component.
• Ús del Component AmpMeter a main.qml: ** A main.qml, les dues imatges originals (fonsSvg i agullaSvg) es reemplacen per una instància del nou component AmpMeter. ** L'esdeveniment onButtonClicked del Botonera ara actualitza la rotació de l'amperímetre amb ampMeter.rotation = value, utilitzant la propietat exposada pel component AmpMeter.
Aquesta última refactorització crea un component autònom (AmpMeter) que conté tota la lògica i la presentació de l'amperímetre, fent que main.qml sigui extremadament simple i es basi en la composició de components.
main.qml
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("ampMeter00_05")
Rectangle {
id: root
anchors.fill: parent
color: "white"
AmpMeter {
id: ampMeter
anchors.centerIn: parent
width: parent.width
height: parent.height
}
ListModel {
id: buttonModel
ListElement { label: "-60"; value: -45 }
ListElement { label: "-30"; value: -20 }
ListElement { label: "0"; value: 0 }
ListElement { label: "30"; value: 20 }
ListElement { label: "60"; value: 45 }
}
Botonera {
id: controlButtons
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
model: buttonModel
onButtonClicked: function(value) {
ampMeter.rotation = value
}
}
}
}
Botonera.qml
import QtQuick
Row {
id: botonera
spacing: 20
property alias model: repeater.model
signal buttonClicked(real value)
Repeater {
id: repeater
Rectangle {
width: 60
height: 60
radius: 30
color: "gray"
Text {
anchors.centerIn: parent
text: model.label
color: "white"
font.pixelSize: 14
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: botonera.buttonClicked(model.value)
cursorShape: Qt.PointingHandCursor
}
}
}
}
AmpMeter.qml
import QtQuick
Item {
id: ampMeter
property alias rotation: agullaSvg.rotation
property string fonsSource: "qrc:/material/AmpMeterFons_c.svg"
property string agullaSource: "qrc:/material/AmpMeterAgulla_c.svg"
property real agullaOpacity: 0.8
Image {
id: fonsSvg
source: ampMeter.fonsSource
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
}
Image {
id: agullaSvg
source: ampMeter.agullaSource
fillMode: Image.PreserveAspectFit
opacity: ampMeter.agullaOpacity
anchors.centerIn: parent
rotation: 0
Behavior on rotation {
NumberAnimation {
duration: 300
easing.type: Easing.InOutBack
}
}
}
}