Diferència entre revisions de la pàgina «Ús d'arxius vectorials SVG al QML (Qt6)»

De binefa.com
Salta a la navegació Salta a la cerca
m
(Hi ha 28 revisions intermèdies del mateix usuari que no es mostren)
Línia 1: Línia 1:
 
[[Image:AmpMeter00.png|center|thumb|Exemple d'ús d'arxius SVG]]
 
[[Image:AmpMeter00.png|center|thumb|Exemple d'ús d'arxius SVG]]
  
== Explicació de <code>main.qml</code> de <code>ampMeter00_00</code> ==
+
[https://github.com/jordibinefa/Qt/tree/master/QML/svg Codis s'aquesta pàgina al GitHub]
 +
== Explicació de <code>main.qml</code> d'<code>ampMeter00_00</code> ==
  
 
El fitxer <code>main.qml</code> de <code>ampMeter00_00</code> estableix la base d'una aplicació Qt Quick que simula un amperímetre simple [1].
 
El fitxer <code>main.qml</code> de <code>ampMeter00_00</code> estableix la base d'una aplicació Qt Quick que simula un amperímetre simple [1].
  
=== Estructura Bàsica de la Finestra ===
+
=== Estructura bàsica de la finestra ===
 
*  <code>import QtQuick</code>: Importa el mòdul Qt Quick necessari per als elements de la interfície d'usuari [1].
 
*  <code>import QtQuick</code>: Importa el mòdul Qt Quick necessari per als elements de la interfície d'usuari [1].
 
*  <code>Window</code>: Defineix la finestra principal de l'aplicació [1].
 
*  <code>Window</code>: Defineix la finestra principal de l'aplicació [1].
Línia 12: Línia 13:
 
** <code>title: qsTr("ampMeter00")</code>: Defineix el títol de la finestra com "ampMeter00" [1].
 
** <code>title: qsTr("ampMeter00")</code>: Defineix el títol de la finestra com "ampMeter00" [1].
  
=== Element Arrel (Rectangle) ===
+
=== Element arrel (Rectangle) ===
 
*  <code>Rectangle { id: root ... }</code>: Dins la finestra, hi ha un <code>Rectangle</code> anomenat <code>root</code> que serveix com a contenidor principal [1].
 
*  <code>Rectangle { id: root ... }</code>: Dins la finestra, hi ha un <code>Rectangle</code> anomenat <code>root</code> que serveix com a contenidor principal [1].
 
*  <code>anchors.fill: parent</code>: Fa que aquest rectangle ompli completament la finestra pare [1].
 
*  <code>anchors.fill: parent</code>: Fa que aquest rectangle ompli completament la finestra pare [1].
 
*  <code>color: "white"</code>: Estableix el color de fons del rectangle com a blanc [1].
 
*  <code>color: "white"</code>: Estableix el color de fons del rectangle com a blanc [1].
  
=== Imatge de Fons de l'Amperímetre ===
+
=== Imatge de fons de l'amperímetre ===
 
*  <code>Image { id: fonsSvg ... }</code>: S'afegeix un element <code>Image</code> per al fons de l'amperímetre [1].
 
*  <code>Image { id: fonsSvg ... }</code>: S'afegeix un element <code>Image</code> per al fons de l'amperímetre [1].
 
*  <code>source: "qrc:/material/AmpMeterFons_c.svg"</code>: Carrega la imatge de fons des dels recursos de l'aplicació [1].
 
*  <code>source: "qrc:/material/AmpMeterFons_c.svg"</code>: Carrega la imatge de fons des dels recursos de l'aplicació [1].
Línia 23: Línia 24:
 
*  <code>anchors.centerIn: parent</code>: Centra la imatge de fons dins del seu element pare (<code>root</code> rectangle) [1].
 
*  <code>anchors.centerIn: parent</code>: Centra la imatge de fons dins del seu element pare (<code>root</code> rectangle) [1].
  
=== Imatge de l'Agulla de l'Amperímetre ===
+
=== Imatge de l'agulla de l'amperímetre ===
 
*  <code>Image { id: agullaSvg ... }</code>: S'afegeix un altre element <code>Image</code> per representar l'agulla de l'amperímetre [2].
 
*  <code>Image { id: agullaSvg ... }</code>: S'afegeix un altre element <code>Image</code> per representar l'agulla de l'amperímetre [2].
 
*  <code>source: "qrc:/material/AmpMeterAgulla_c.svg"</code>: Carrega la imatge de l'agulla [2].
 
*  <code>source: "qrc:/material/AmpMeterAgulla_c.svg"</code>: Carrega la imatge de l'agulla [2].
Línia 66: Línia 67:
 
== Canvis Progressius en el Codi ==
 
== Canvis Progressius en el Codi ==
 
A continuació, s'expliquen els canvis introduïts en cada iteració del projecte:
 
A continuació, s'expliquen els canvis introduïts en cada iteració del projecte:
=== De <code>ampMeter00_00</code> a <code>ampMeter00_01</code> ===
+
=== Interactivitat i animació (d'<code>ampMeter00_00</code> a <code>ampMeter00_01</code>) ===
La versió <code>ampMeter00_01</code> introdueix la '''interactivitat''' i la '''animació''' per a l'agulla de l'amperímetre.
+
La versió <code>ampMeter00_01</code> introdueix la '''interactivitat''' i l''''animació''' per a l'agulla de l'amperímetre.
  
 
* '''Rotació Inicial i Opacitat de l'Agulla:'''  
 
* '''Rotació Inicial i Opacitat de l'Agulla:'''  
Línia 237: Línia 238:
 
</nowiki></pre>
 
</nowiki></pre>
  
=== De <code>ampMeter00_01</code> a <code>ampMeter00_02</code> ===
+
=== Animació automàtica i contínua (d'<code>ampMeter00_01</code> a <code>ampMeter00_02</code>) ===
 
La versió <code>ampMeter00_02</code> afegeix una '''animació automàtica i contínua''' per a l'agulla.
 
La versió <code>ampMeter00_02</code> afegeix una '''animació automàtica i contínua''' per a l'agulla.
 
* '''Canvi de Funció d'Acceleració:'''  
 
* '''Canvi de Funció d'Acceleració:'''  
Línia 413: Línia 414:
 
</nowiki></pre>
 
</nowiki></pre>
  
=== De <code>ampMeter00_02</code> a <code>ampMeter00_03</code> ===
+
=== Model de dades ''ListModel'' i repetició ''Repeater'' (d'<code>ampMeter00_02</code> a <code>ampMeter00_03</code>) ===
 
La versió <code>ampMeter00_03</code> refactoritza el codi dels botons utilitzant un '''model de dades (<code>ListModel</code>) i un <code>Repeater</code>''', millorant la modularitat i la facilitat de manteniment.
 
La versió <code>ampMeter00_03</code> refactoritza el codi dels botons utilitzant un '''model de dades (<code>ListModel</code>) i un <code>Repeater</code>''', millorant la modularitat i la facilitat de manteniment.
 
* '''Eliminació de l'Animació Seqüencial:'''  
 
* '''Eliminació de l'Animació Seqüencial:'''  
Línia 508: Línia 509:
 
</nowiki></pre>
 
</nowiki></pre>
  
=== De <code>ampMeter00_03</code> a <code>ampMeter00_04</code> ===
+
=== Component '''Botonera'''. QML reutilitzable (d'<code>ampMeter00_03</code> a <code>ampMeter00_04</code>) ===
 
La versió <code>ampMeter00_04</code> millora la modularitat extraient la lògica dels botons en un '''component QML reutilitzable''' anomenat <code>Botonera.qml</code>.
 
La versió <code>ampMeter00_04</code> millora la modularitat extraient la lògica dels botons en un '''component QML reutilitzable''' anomenat <code>Botonera.qml</code>.
 
* '''Creació del Component <code>Botonera.qml</code>:'''  
 
* '''Creació del Component <code>Botonera.qml</code>:'''  
Línia 620: Línia 621:
 
</nowiki></pre>
 
</nowiki></pre>
  
=== De <code>ampMeter00_04</code> a <code>ampMeter00_05</code> ===
+
=== Component '''AmpMeter''' (d' <code>ampMeter00_04</code> a <code>ampMeter00_05</code>) ===
 
La versió <code>ampMeter00_05</code> continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un '''component QML propi''' anomenat <code>AmpMeter.qml</code>.
 
La versió <code>ampMeter00_05</code> continua amb la refactorització, encapsulant la lògica de l'amperímetre (fons i agulla) en un '''component QML propi''' anomenat <code>AmpMeter.qml</code>.
 
* '''Creació del Component <code>AmpMeter.qml</code>:'''  
 
* '''Creació del Component <code>AmpMeter.qml</code>:'''  
Línia 753: Línia 754:
 
}
 
}
 
</nowiki></pre>
 
</nowiki></pre>
 +
 +
= Modularitat i separació de responsabilitats =
 +
L'evolució dels codis, prenent com a referència inicial '''ampMeter00 _05''', mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.
 +
 +
=== Punt de referència inicial per a la modularitat i separació de responsabilitats - ampMeter00 _05 ===
 +
En aquesta versió, el fitxer  <code>main.qml </code> configura una finestra amb un  <code>Rectangle </code> blanc. Inclou dos components QML personalitzats:  <code>AmpMeter </code> i  <code>Botonera </code>.
 +
 +
* El component ''' <code>AmpMeter </code>''' es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat  <code>rotation </code> exposada i animada amb una  <code>NumberAnimation </code> que utilitza  <code>Easing.InOutBack </code>.
 +
* El component ''' <code>Botonera </code>''' es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un  <code>ListModel </code> anomenat  <code>buttonModel </code> que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal  <code>buttonClicked </code> amb el seu valor, i aquest valor s'assigna a la propietat  <code>rotation </code> del component  <code>ampMeter </code>.
 +
 +
=== Introducció de Múltiples AmpMeters - ampMeter00 _06 ===
 +
Aquesta versió es titula "ampMeter00 _06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar '''tres components  <code>AmpMeter </code> simultàniament'''.
 +
 +
[[Image:AmpMeter06.png|center|thumb|Exemple d'ús d'arxius SVG]]
 +
 +
* Els tres  <code>AmpMeter </code> ( <code>ampMeter1 </code>,  <code>ampMeter2 </code>,  <code>ampMeter3 </code>) estan organitzats en una  <code>Row </code> (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple,  <code>ampMeter1 </code> i  <code>ampMeter3 </code> amb  <code>scale: 0.5 </code>, i  <code>ampMeter2 </code> amb  <code>scale: 0.8 </code>).
 +
* El  <code>ListModel </code> dels botons es manté, però el gestor  <code>onButtonClicked </code> a  <code>main.qml </code> ara actualitza la propietat  <code>rotation </code> dels '''tres''' components  <code>AmpMeter </code> de forma idèntica ( <code>ampMeter1.rotation = value </code>,  <code>ampMeter2.rotation = value </code>,  <code>ampMeter3.rotation = value </code>).
 +
* Els components  <code>Botonera.qml </code> i  <code>AmpMeter.qml </code> conserven la seva estructura anterior.
 +
 +
=== Incorporació d'un lliscador o ''slider'' - ampMeter00 _07 ===
 +
Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).
 +
 +
[[Image:AmpMeter07.png|center|thumb|Exemple d'ús d'arxius SVG]]
 +
 +
 +
* Es mantenen els tres components  <code>AmpMeter </code> en fila.
 +
* S'introdueix un  <code>Column </code> ( <code>sliderContainer </code>) que conté la lògica del slider, situat just a sobre dels botons de control.
 +
* Aquest slider consisteix en un  <code>Rectangle </code> que serveix de pista i un altre  <code>Rectangle </code> ( <code>sliderHandle </code>) que fa de mànec lliscant.
 +
* Un  <code>MouseArea </code> permet arrossegar el  <code>sliderHandle </code> només en l'eix X ( <code>drag.axis: Drag.XAxis </code>).
 +
* El gestor  <code>onPositionChanged </code> calcula un valor ( <code>value </code>) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica '''exclusivament a  <code>ampMeter2 </code>''' ( <code>ampMeter2.rotation = rotation </code>).
 +
* Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
 +
* La  <code>Botonera </code> ara només controla  <code>ampMeter1 </code> i  <code>ampMeter3 </code>, ja que  <code>ampMeter2 </code> és controlat pel nou slider.
 +
 +
=== Modularització del lliscador amb  <code>BarraLliscant.qml </code> - ampMeter00 _08 ===
 +
En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat ''' <code>BarraLliscant.qml </code>'''.
 +
 +
[[Image:AmpMeter08.png|center|thumb|Exemple d'ús d'arxius SVG]]
 +
 +
* A  <code>main.qml </code>, el  <code>sliderContainer </code> es reemplaça per una instància del component  <code>BarraLliscant </code>.
 +
*  <code>BarraLliscant.qml </code> encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
 +
* Defineix propietats configurables com  <code>minValue </code>,  <code>maxValue </code>,  <code>sliderWidth </code> i  <code>sliderHeight </code>.
 +
* Emet un senyal  <code>valueChanged(real value) </code> quan el valor del lliscador canvia.
 +
* A  <code>main.qml </code>, el senyal  <code>onValueChanged </code> de la  <code>BarraLliscant </code> s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a  <code>ampMeter2 </code>.
 +
* El component  <code>AmpMeter.qml </code> introdueix propietats  <code>fondoSource </code> i  <code>agullaSource </code> per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
 +
* La  <code>Botonera </code> continua controlant  <code>ampMeter1 </code> i  <code>ampMeter3 </code>.
 +
 +
=== Modularització completa amb  <code>Vista.qml </code> i  <code>Controlador.qml </code> - ampMeter00 _09 ===
 +
Aquesta és la versió més modularitzada, amb el títol "ampMeter00 _09 - Modular".  <code>main.qml </code> se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: ''' <code>Vista </code>''' i ''' <code>Controlador </code>'''.
 +
 +
[[Image:AmpMeter09.png|center|thumb|Exemple d'ús d'arxius SVG]]
 +
 +
* ''' <code>Vista.qml </code>''': Aquest component agrupa els tres components  <code>AmpMeter </code> en una  <code>Row </code>.
 +
** Exposa les propietats  <code>ampMeter1Rotation </code>,  <code>ampMeter2Rotation </code>,  <code>ampMeter3Rotation </code> com àlies per controlar la rotació de cada  <code>AmpMeter </code> des del seu pare.
 +
** Permet personalitzar l'escala de cada  <code>AmpMeter </code> ( <code>ampMeter1Scale </code>,  <code>ampMeter2Scale </code>,  <code>ampMeter3Scale </code>).
 +
** Una novetat important és que cada  <code>AmpMeter </code> dins de la  <code>Vista </code> pot tenir un tipus d'animació d'''easing''' diferent ( <code>Easing.InOutQuad </code>,  <code>Easing.InOutBack </code>,  <code>Easing.InOutCubic </code>), cosa que requereix una nova propietat  <code>easingType </code> a  <code>AmpMeter.qml </code>.
 +
* ''' <code>Controlador.qml </code>''': Aquest component agrupa la  <code>BarraLliscant </code> i la  <code>Botonera </code> en una  <code>Column </code>.
 +
** Rep el  <code>buttonModel </code> per a la  <code>Botonera </code>.
 +
** Emet senyals  <code>sliderValueChanged(real value) </code> i  <code>buttonClicked(real value) </code> per comunicar els esdeveniments dels seus components interns al seu pare.
 +
** Connecta el senyal  <code>onValueChanged </code> de la seva  <code>BarraLliscant </code> interna al seu propi  <code>sliderValueChanged </code>.
 +
** Connecta el senyal  <code>onButtonClicked </code> de la seva  <code>Botonera </code> interna al seu propi  <code>buttonClicked </code>.
 +
* A  <code>main.qml </code>, el gestor  <code>onSliderValueChanged </code> del  <code>Controlador </code> ara actualitza  <code>vista.ampMeter2Rotation </code>.
 +
* El gestor  <code>onButtonClicked </code> del  <code>Controlador </code> actualitza  <code>vista.ampMeter1Rotation </code> i  <code>vista.ampMeter3Rotation </code>.
 +
 +
En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer  <code>main.qml </code>, cap a una estructura modular on components reutilitzables ( <code>AmpMeter </code>,  <code>Botonera </code>,  <code>BarraLliscant </code>) s'organitzen en components de més alt nivell ( <code>Vista </code>,  <code>Controlador </code>), millorant la mantenibilitat i la claredat del codi.

Revisió del 14:22, 10 set 2025

Exemple d'ús d'arxius SVG

Codis s'aquesta pàgina al GitHub

Explicació de main.qml d'ampMeter00_00

El fitxer main.qml de ampMeter00_00 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 un Rectangle anomenat root que 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 element Image per 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 (root rectangle) [1].

Imatge de l'agulla de l'amperímetre

  • Image { id: agullaSvg ... }: S'afegeix un altre element Image per 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 (root rectangle) [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 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:

// main.qml
import QtQuick // [1]
Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("ampMeter00_00")

    Rectangle {
        id: root
        anchors.fill: parent
        color: "white"

        Image {
            id: fonsSvg
            source: "qrc:/material/AmpMeterFons_c.svg"
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: parent
        }
        Image { // [2]
            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:

Interactivitat i animació (d'ampMeter00_00 a ampMeter00_01)

La versió ampMeter00_01 introdueix la interactivitat i l'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:

// 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
                }
            }
        }
    }
}

Animació automàtica i contínua (d'ampMeter00_01 a ampMeter00_02)

La versió ampMeter00_02 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:

// 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
                }
            }
        }
    }
}

Model de dades ListModel i repetició Repeater (d'ampMeter00_02 a ampMeter00_03)

La versió ampMeter00_03 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:

// 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
                    }
                }
            }
        }
    }
}

Component Botonera. QML reutilitzable (d'ampMeter00_03 a ampMeter00_04)

La versió ampMeter00_04 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:

// 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:

// 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
            }
        }
    }
}

Component AmpMeter (d' ampMeter00_04 a ampMeter00_05)

La versió ampMeter00_05 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:

// 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:

// 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:

// 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
            }
        }
    }
}

Modularitat i separació de responsabilitats

L'evolució dels codis, prenent com a referència inicial ampMeter00 _05, mostra una progressió cap a una major modularitat i control de la interfície d'usuari a través de la introducció de nous components QML i la separació de responsabilitats.

Punt de referència inicial per a la modularitat i separació de responsabilitats - ampMeter00 _05

En aquesta versió, el fitxer main.qml configura una finestra amb un Rectangle blanc. Inclou dos components QML personalitzats: AmpMeter i Botonera .

  • El component AmpMeter es centra a la pantalla i gestiona la visualització del fons i l'agulla d'un amperímetre. La rotació de l'agulla es controla mitjançant una propietat rotation exposada i animada amb una NumberAnimation que utilitza Easing.InOutBack .
  • El component Botonera es col·loca a la part inferior i es centra horitzontalment. S'alimenta d'un ListModel anomenat buttonModel que conté les etiquetes i els valors de rotació per als botons. Quan un botó és clicat, emet un senyal buttonClicked amb el seu valor, i aquest valor s'assigna a la propietat rotation del component ampMeter .

Introducció de Múltiples AmpMeters - ampMeter00 _06

Aquesta versió es titula "ampMeter00 _06 - Triple" i incrementa l'amplada de la finestra a 960 píxels. La millora principal és la capacitat de mostrar i controlar tres components AmpMeter simultàniament.

Exemple d'ús d'arxius SVG
  • Els tres AmpMeter ( ampMeter1 , ampMeter2 , ampMeter3 ) estan organitzats en una Row (fila) centrada, amb un desplaçament vertical per deixar espai als controls. Cadascun té dimensions i escala definides de manera individual (per exemple, ampMeter1 i ampMeter3 amb scale: 0.5 , i ampMeter2 amb scale: 0.8 ).
  • El ListModel dels botons es manté, però el gestor onButtonClicked a main.qml ara actualitza la propietat rotation dels tres components AmpMeter de forma idèntica ( ampMeter1.rotation = value , ampMeter2.rotation = value , ampMeter3.rotation = value ).
  • Els components Botonera.qml i AmpMeter.qml conserven la seva estructura anterior.

Incorporació d'un lliscador o slider - ampMeter00 _07

Continuant amb la configuració "Triple", aquesta versió afegeix un nou element de control: un slider (lliscador).

Exemple d'ús d'arxius SVG


  • Es mantenen els tres components AmpMeter en fila.
  • S'introdueix un Column ( sliderContainer ) que conté la lògica del slider, situat just a sobre dels botons de control.
  • Aquest slider consisteix en un Rectangle que serveix de pista i un altre Rectangle ( sliderHandle ) que fa de mànec lliscant.
  • Un MouseArea permet arrossegar el sliderHandle només en l'eix X ( drag.axis: Drag.XAxis ).
  • El gestor onPositionChanged calcula un valor ( value ) entre -60 i +60, el converteix en un angle de rotació entre -45 i +45, i l'aplica exclusivament a ampMeter2 ( ampMeter2.rotation = rotation ).
  • Es mostren etiquetes per als valors extrems (-60 i +60) i el valor actual del slider.
  • La Botonera ara només controla ampMeter1 i ampMeter3 , ja que ampMeter2 és controlat pel nou slider.

Modularització del lliscador amb BarraLliscant.qml - ampMeter00 _08

En aquesta etapa, la lògica del lliscador es refactoritza en un component QML independent anomenat BarraLliscant.qml .

Exemple d'ús d'arxius SVG
  • A main.qml , el sliderContainer es reemplaça per una instància del component BarraLliscant .
  • BarraLliscant.qml encapsula tota la interfície i la lògica del lliscador, incloent les etiquetes, la pista, el mànec, el càlcul del valor i la visualització del valor actual.
  • Defineix propietats configurables com minValue , maxValue , sliderWidth i sliderHeight .
  • Emet un senyal valueChanged(real value) quan el valor del lliscador canvia.
  • A main.qml , el senyal onValueChanged de la BarraLliscant s'utilitza per convertir el valor del lliscador en rotació i aplicar-lo a ampMeter2 .
  • El component AmpMeter.qml introdueix propietats fondoSource i agullaSource per personalitzar les imatges, tot i que inicialment mantenen els mateixos valors per defecte.
  • La Botonera continua controlant ampMeter1 i ampMeter3 .

Modularització completa amb Vista.qml i Controlador.qml - ampMeter00 _09

Aquesta és la versió més modularitzada, amb el títol "ampMeter00 _09 - Modular". main.qml se simplifica enormement delegant les responsabilitats a dos nous components de nivell superior: Vista i Controlador .

Exemple d'ús d'arxius SVG
  • Vista.qml : Aquest component agrupa els tres components AmpMeter en una Row .
    • Exposa les propietats ampMeter1Rotation , ampMeter2Rotation , ampMeter3Rotation com àlies per controlar la rotació de cada AmpMeter des del seu pare.
    • Permet personalitzar l'escala de cada AmpMeter ( ampMeter1Scale , ampMeter2Scale , ampMeter3Scale ).
    • Una novetat important és que cada AmpMeter dins de la Vista pot tenir un tipus d'animació deasing diferent ( Easing.InOutQuad , Easing.InOutBack , Easing.InOutCubic ), cosa que requereix una nova propietat easingType a AmpMeter.qml .
  • Controlador.qml : Aquest component agrupa la BarraLliscant i la Botonera en una Column .
    • Rep el buttonModel per a la Botonera .
    • Emet senyals sliderValueChanged(real value) i buttonClicked(real value) per comunicar els esdeveniments dels seus components interns al seu pare.
    • Connecta el senyal onValueChanged de la seva BarraLliscant interna al seu propi sliderValueChanged .
    • Connecta el senyal onButtonClicked de la seva Botonera interna al seu propi buttonClicked .
  • A main.qml , el gestor onSliderValueChanged del Controlador ara actualitza vista.ampMeter2Rotation .
  • El gestor onButtonClicked del Controlador actualitza vista.ampMeter1Rotation i vista.ampMeter3Rotation .

En resum, l'evolució del codi il·lustra una transició des d'una aplicació monolítica amb la lògica de control i vista al fitxer main.qml , cap a una estructura modular on components reutilitzables ( AmpMeter , Botonera , BarraLliscant ) s'organitzen en components de més alt nivell ( Vista , Controlador ), millorant la mantenibilitat i la claredat del codi.