Introduzione Alla Programmazione Con QML

Benvenuti nel mondo di QML, il linguaggio dichiarativo per la realizzazione di interfacce utente (UI, user interface).
In questa guida introduttiva, costruiremo un semplice editor di testo utilizzando QML. Dopo aver letto questa guida, dovresti essere pronto per sviluppare le tue applicazioni usando QML e Qt C++.

QML per la creazione di UI

L’applicazione che svilupperemo sarà un semplice editor di testo e permetterà di caricare, salvare e modificare del testo. Questa introduzione è divisa in due parti. La prima parte si occupa dello sviluppo del layout e dei comportamenti dei componenti dell’applicazione tramite il linguaggio QML. La seconda, invece, tratta dell’implementazione delle funzioni di caricamento e salvataggio utilizzando Qt C++. Grazie al Qt’s Meta-Object System [doc.qt.nokia.com], possiamo esporre le funzioni C++ come proprietà usate da elementi del linguaggio QML. Usando QML e Qt C++, possiamo separare efficacemente la logica del layout dalla logica riguardante le funzionalità dell’applicazione.

Immagine editor di testi

Per eseguire il codice QML dell’esempio, è sufficiente passare come argomento allo strumento qmlviewer [doc.qt.nokia.com] i files QML. La seguente introduzione è pensata per quei lettori che hanno almeno una minima conoscenza della procedura per la compilazione di applicazioni in Qt.

Capitoli:

  1. Definizione di pulsanti e menu
  2. Implementare barra dei menu
  3. Costruire un editor di testo
  4. Decorare l’editor di testo
  5. Estendere QML attraverso Qt C++

Definizione di pulsanti e menu

Componenti base – Pulsante

Iniziamo il nostro editor di testi costruendo un pulsante. Un pulsante è costituito da un’area sensibile al mouse e da un’etichetta. I pulsanti eseguono un azione alla pressione da parte dell’utente.
In QML, l’oggetto visuale di base è il Rectangle [doc.qt.nokia.com]. Il Rectangle ha le proprietà per controllare l’aspetto e la posizione degli elementi.

  1. import Qt 4.7
  2.  
  3. Rectangle{
  4.   id:simplebutton
  5.        
  6.   color: "grey"
  7.   width: 400; height: 400
  8.  
  9.   Text {
  10.       id: buttonLabel
  11.       text: "button label"
  12.       anchors.centerIn:parent
  13.   }
  14. }

Innanzitutto, la riga import Qt 4.7 abilita il tool qmlviewer all’importazione di elementi QML. Questa linea deve essere presente in ogni file QML. Si noti che la versione dei moduli di Qt è inclusa nella dichiarazione.

Il Rectangle ha un identificatore univoco, simplebutton, che è associato alla proprietà id. Le properità del Rectangle sono specificate definendone prima il nome e, dopo il simbolo : (due punti), il valore. Nel codice di esempio, il colore grey è legato alla proprietà color del Rectangle. Stessa cosa per larghezza e altezza (width e height).

L’elemento Text [doc.qt.nokia.com] è un campo di testo non editabile. Lo chiameremo buttonLabel. Per impostare il contenuto del campo di testo, assoceremo un valore alla sua proprietà text. L’etichetta è contenuta all’interno del rectangle, e per centrarla in mezzo, colleghiamo gerarchicamente le anchors (ancore) dell’elemento Text al suo genitore, che nel nostro caso si chiama simplebutton.
Ogni ancora può essere collegata alle ancore di altri elementi; in questo modo si semplifica la definizione della disposizione reciproca degli elementi.

Salveremo il tutto come SimpleButton.qml. Lanciando qmlviewer con il nome del file creato come argomento, verrà mostrato a schermo un rettangolo grigio con un’etichetta di testo.

pulsante grigio con etichetta testo

Per implementare l’evento click sul pulsante, possiamo usare la gestione degli eventi di QML. La gestione eventi di QML è molto simile al meccanismo signal e slot [doc.qt.nokia.com] di Qt. Un segnale sarà emesso e il relativo slot connesso sarà chiamato.

  1.  
  2. Rectangle{
  3.      id:simplebutton
  4.      ...
  5.  
  6.      MouseArea{
  7.          id: buttonMouseArea
  8.  
  9.          anchors.fill: parent //ancora tutti i lati della MouseArea ai lati del rettangolo
  10.                  //onClicked gestisce click del mouse validi
  11.          onClicked: console.log(buttonLabel.text + " clicked" )
  12.      }
  13.  }
  14.  

Aggiungiamo un elemento MouseArea [doc.qt.nokia.com] nel nostro componente simplebutton. Gli elementi MouseArea descrivono l’area interattiva dove i movimenti del mouse sono rilevati. Per il nostro pulsante, ancoriamo l’intera MouseArea al suo genitore (parent), il simplebutton. La sintassi anchors.fill è un modo per accedere ad una specifica proprietà chiamata fill all’interno di un gruppo di proprietà denominate anchors. QML utilizza layouts basati su ancore [doc.qt.nokia.com], dove gli elementi possono ancorarsi ad altri elementi, creando layout robusti e flessibili.

La MouseArea ha molti gestori di segnali che possono essere chiamati durante i movimenti del mouse all’interno dei limiti specificati. Uno di questi è onClicked ed è invocato ogni qualvolta viene cliccato un pulsante valido del mouse; il pulsante di default è quello sinistro. Possiamo legare azioni al gestore onClicked. Nel nostro esempio, console.log() visualizza testo ogni volta che viene cliccata la MouseArea. La funzione console.log() è uno strumento utile per scopi di debugging e, in generale, per visualizzare del testo in output.

Il codice in SimpleButton.qml è sufficiente per mostrare un bottone sullo schermo e visualizzare del testo ogni volta che viene cliccato con il mouse.

  1. Rectangle {
  2.      id:Button
  3.      ...
  4.  
  5.      property color buttonColor: "lightblue"
  6.      property color onHoverColor: "gold"
  7.      property color borderColor: "white"
  8.  
  9.      signal buttonClick()
  10.      onButtonClick: {
  11.          console.log(buttonLabel.text + " clicked" )
  12.      }
  13.  
  14.      MouseArea{
  15.          onClicked: buttonClick()
  16.          hoverEnabled: true
  17.          onEntered: parent.border.color = onHoverColor
  18.          onExited:  parent.border.color = borderColor
  19.      }
  20.  
  21.      // determina il colore del bottone utilizando l'operatore condizionale
  22.      color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
  23. }

Un pulsante completamente funzionante è Button.qml. Il codice visualizzato in questo articolo omette alcune parti, denotate da “…”, poiché sono già state introdotte nella sezione precedente o comunque sono irrilevanti ai fini della discussione di questo specifico codice.

Per dichiarare proprietà personalizzate si utilizza la sintassi: proprietà tipo nome. Nel codice, viene dichiarata la proprietà buttonColor, di tipo color, e vincolata al valore “lightblue”. La proprietà buttonColor è utilizzata successivamente in un’operazione condizionale per determinare il colore di riempimento del bottone. Si noti che per assegnare dei valori alle proprietà è possibile utilizzare il segno uguale "="oppure il segno due punti ":". Le proprietà personalizzate permettono di rendere accessibili elementi interni all’elemento Rectangle, anche dall’esterno del suo campo di visibiltà. Esistono diversi tipi QML [doc.qt.nokia.com] di base come int, string, real, ma anche un tipo generico chiamato variant.

Legando i gestori dei segnali onEntered ed onExited ai colori, il colore del bordo del pulsante diventa giallo quando il mouse si sposta sopra di esso e ritorna del colore pecedente quando il mouse esce dalla MouseArea.

Un segnale buttonClick() è dichiarato in Button.qml inserendo la parola chiave signal davanti al nome del segnale. Per ogni segnale vengono create automaticamente le relative funzioni di gestione, i cui nomi iniziano con il prefisso on. Come risultato si ha che il gestore di buttonClick è onButtonClick. All’onButtonClick è successivamente assegnata un’azione da svolgere. Nel nostro esempio, il gestore del mouse onClicked chiamerà semplicemente onButtonClick, il quale ha il compito di visualizzare del testo. onButtonClick permette ad oggetti esterni di accedere facilmente alla MouseArea del Button. Inoltre, gli elementi possono avere dichiarate più di una MouseArea, ed avere il segnale buttonClick permette di distinguere meglio i diversi gestori di segnali delle varie MouseArea.

Ora abbiamo le conoscenze essenziali per implementare in QML oggetti che possono gestire i principali movimenti del mouse. Abbiamo creato un’etichetta Text all’interno di un Rectangle, personalizzato le sue proprietà ed implementato dei comportamenti che rispondano ai movimenti del mouse. Questa idea di base della creazione di elementi all’interno di altri elementi, si ripresenterà nello sviluppo di tutta l’applicazione di esempio.

Il pulsante realizzato non è molto utile se non viene usato come componente per eseguire una qualche azione. Nella prossima sezione creeremo un menu contenente alcuni di questi pulsanti.

Pulsante grigio chiaro con all'interno un'etichetta

Creazione della pagina del menu

Fino ad ora abbiamo visto come creare elementi ed assegnare comportamenti all’interno di file QML. In questa sezione, scopriremo come importare elementi QML e come riutilizzare alcuni componenti creati per costruire altri componenti.

Un menu mostra il contenuto di una lista i cui elementi hanno la capacità di compiere un’azione. In QML possiamo ottnere un menu in vari modi. Prima di tutto creeremo un menu contenente dei pulsanti a cui faremo eseguire azioni differenti. Il codice del menu è in FileMenu.qml.

  1. import Qt 4.7                //Importa il modulo Qt QML principale
  2. import "folderName"            //Importa il contenuto della cartella
  3. import "script.js" as Script   //Importa un file javascript e lo chiama Script

La sintassi riportata mostra come utilizzare la parola chiave import. Ciò è richiesto per utilizzare file JavaScript [developer.mozilla.org] o QML che non sono nella stessa cartella. Siccome Button.qml si trova nella stessa cartella di FileMenu.qml, non è necessario importare esplicitamente il file Button.qml per poterlo usare, ma possiamo creare direttamente un elemento Button dichiarando Button{}, come è stato fatto per Rectangle{}.

  1. In FileMenu.qml:
  2.  
  3.      Row{
  4.          anchors.centerIn: parent
  5.          spacing: parent.width/6
  6.  
  7.          Button{
  8.              id: loadButton
  9.              buttonColor: "lightgrey"
  10.              label: "Load"
  11.          }
  12.          Button{
  13.              buttonColor: "grey"
  14.              id: saveButton
  15.              label: "Save"
  16.          }
  17.          Button{
  18.              id: exitButton
  19.              label: "Exit"
  20.              buttonColor: "darkgrey"
  21.  
  22.              onButtonClick: Qt.quit()
  23.          }
  24.      }

In FileMenu.qml abbiamo dichiarato tre elementi Button. Essi sono definiti all’interno di un elemento di tipo Row, un elemento di posizionamento che ha lo scopo di collocare i suoi figli lungo una riga orizzontale. La dichiarazione di Button si trova nel file Button.qml, lo stesso che abbiamo utilizzato nella sezione precedente. Le impostazioni delle proprietà possono essere dichiarate all’interno della dichiarazione del pulsante; in questo modo si sovrascrivono le proprietà impostate in Button.qml. Quando cliccato, il pulsante chiamato exitButton chiuderà l’applicazione. Si noti che il gestore di segnale onButtonClick in Button.qml sarà chiamato in aggiunta al gestore onButtonClick in exitButton.

Immagine menu File

La dichiarazione dell’elemento di tipo Row è fatta all’interno di un Rectangle, creando un contenitore a forma di rettangolo per la riga di pulsanti. Il rettangolo aggiuntivo permette di aver un modo indiretto di organizzare la riga di pulsanti all’interno del menu.

La dichiarazione del menu di editing è molto simile a quella precedente. Questo menù è composto da pulsanti con le etichette Copy, Paste e Select All.

Immagine menu Edit

Forti della conoscenza acquisita sull’importazione e sulla personalizzazione dei componenti, possiamo ora combinare queste pagine di menu per creare una singola barra dei menu, composta da pulsanti per selezionare il menu, e vedere come strutturare i dati usando QML.

Implementare la barra dei menu

Il nostro editor di testi necessità di un modo per visualizzare i menu tramite una barra dei menu. La barra dei menu proporrà i diversi menu attraverso cui l’utente potrà navigare. Per il cambio del menu occorrerà una struttura più complessa della semplice visualizzazione su una riga. QML utilizza modelli e viste per gestire e visualizzare i dati strutturati.

Usare la struttura modello-vista

QML dispone di diverse viste [doc.qt.nokia.com] per visualizzare i modelli [doc.qt.nokia.com]. La nostra barra dei menu visualizzerà i menu in una lista, con un’intestazione che mostrerà i nomi dei menu su una riga. L’elenco dei menu è dichiarato all’interno di un VisualItemModel. L’elemento VisualItemModel [doc.qt.nokia.com] contiene gli oggetti che hanno già delle viste, come l’oggetto Rectangle e gli altri elementi dell’interfaccia. Altri tipi di modelli, come l’elemento ListModel [doc.qt.nokia.com], necessitano di un delegato per visualizzare i propri dati.

Dichiariamo quindi due elementi visuali nel menuListModel, il FileMenu e EditMenu, li personalizziamo e li visualizziamo usando il ListView [doc.qt.nokia.com]. Il file MenuBar.qml contiene le dichiarazioni QML e un semplice menu di modifica è definito nel file EditMenu.qml.

  1.  VisualItemModel{
  2.          id: menuListModel
  3.          FileMenu{
  4.              width: menuListView.width
  5.              height: menuBar.height
  6.              color: fileColor
  7.          }
  8.          EditMenu{
  9.              color: editColor
  10.              width:  menuListView.width
  11.              height: menuBar.height
  12.          }
  13.      }

L’elemento ListView visualizza un modello attraverso il suo elemento delegato. Il delegato può definire che gli elementi del modello da visualizzare siano disposti in una riga o in una griglia. Il nostro menuListModel ha già degli oggetti visibili, quindi non abbiamo bisogno di dichiarare un delegato.

  1.  ListView{
  2.          id: menuListView
  3.  
  4.          //Le anchors sono settate per adattarsi alla finestra
  5.          anchors.fill:parent
  6.          anchors.bottom: parent.bottom
  7.          width:parent.width
  8.          height: parent.height
  9.  
  10.          //Il model contiene i dati
  11.          model: menuListModel
  12.  
  13.          //Controlla il movimento del passaggio tra i menu
  14.          snapMode: ListView.SnapOneItem
  15.          orientation: ListView.Horizontal
  16.          boundsBehavior: Flickable.StopAtBounds
  17.          flickDeceleration: 5000
  18.          highlightFollowsCurrentItem: true
  19.          highlightMoveDuration:240
  20.          highlightRangeMode: ListView.StrictlyEnforceRange
  21.      }

Inoltre, ListView eredita da Flickable [doc.qt.nokia.com], rendendo la lista reattiva al trascinamento del mouse ed a altre gestures. L’ultima parte di codice imposta la proprietà Flickable per creare dei movimenti di ribaltamento alla nostra vista. In particolare, la proprietà highlightMoveDuration cambia la durata della transizione: un valore più alto di highlightMoveDuration renderà il passaggio tra i menu più lento.

Il ListView gestisce gli elementi del modello attraverso un index (indice) e ogni elemento visuale nel modello è accessibile tramite questo indice, secondo l’ordine di dichiarazione. Cambiando il currentIndex cambieremo l’elemento evidenziato nella Listview. L’intestazione della barra dei menu semplifica questo effetto. Ci sono due pulsanti sulla stessa riga, quando cliccati entrambi cambiano il menu corrente. Il pulsante fileButton quando cliccato cambia il menu corrente con FileMenu; l’indice diventa 0 perchè FileMenu è il primo elemento dichiarato nel menuListModel. Analogamente il pulsante editButton sostituisce il menu corrente con EditMenu.

Il rettangolo dell’etichetta labelList ha un valore di z settato a 1, e sarà quindi visualizzata davanti alla barra dei menu. Gli oggetti con un valore di z maggiori saranno visualizzati in primo piano rispetto a quelli con un valore di z inferiore. Il valore di default di z è 0.

  1.  Rectangle{
  2.          id: labelList
  3.          ...
  4.          z: 1
  5.          Row{
  6.              anchors.centerIn: parent
  7.              spacing:40
  8.              Button{
  9.                  label: "File"
  10.                  id: fileButton
  11.                  ...
  12.                  onButtonClick: menuListView.currentIndex = 0
  13.              }
  14.              Button{
  15.                  id: editButton
  16.                  label: "Edit"
  17.                  ...
  18.                  onButtonClick:    menuListView.currentIndex = 1
  19.              }
  20.          }
  21.      }

La barra dei menu appena creata potrà essere sfogliata per accedere ai menu oppure si può semplicemente cliccare sui nomi dei menu. Il cambiamento dei menu è intuitivo e reattivo.

barra dei menu

Costruire un editor di testo

Dichiarare un’area di testo

Il nostro editor di testo non potrà mai essere un vero editor di testo finché non inseriremo un area di testo modificabile. L’elemento TextEdit [doc.qt.nokia.com] permette la dichiarazione di un componente per la modifica di testo multilinea. TextEdit è diverso dall’elemento Text [doc.qt.nokia.com], che non consente all’utente di modificare direttamente il testo.

  1. TextEdit{
  2.          id: textEditor
  3.          anchors.fill:parent
  4.          width:parent.width; height:parent.height
  5.          color:"midnightblue"
  6.          focus: true
  7.  
  8.          wrapMode: TextEdit.Wrap
  9.  
  10.          onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
  11.      }

L’editor contiene proprietà per impostare il colore del testo e per farlo andare a capo. L’area di testo TextEdit è contenuta all’interno di un’area scorrevole che farà scorrere il testo se il cursore si trova al di fuori dell’area visibile. La funzione ensureVisible() controlla se il rettangolo del cursore è fuori dai confini visibili e, nel caso, sposta opportunamente l’area di testo. QML usa la sintassi Javascript per i suoi scripts, e, come accennato in precedenza, i file Javascript possono essere importati e utilizzati all’interno di un file QML.

  1.  function ensureVisible(r){
  2.          if (contentX >= r.x)
  3.              contentX = r.x;
  4.          else if (contentX+width <= r.x+r.width)
  5.              contentX = r.x+r.width-width;
  6.          if (contentY >= r.y)
  7.              contentY = r.y;
  8.          else if (contentY+height <= r.y+r.height)
  9.              contentY = r.y+r.height-height;
  10.      }

Combinare i componenti per l’editor di testi

Ora siamo pronti per creare il layout del nostro editor di testo mediante QML. L’editor di testo ha due componenti: la barra dei menu e l’area di testo. QML ci permette di riutilizzare i componenti, rendendo il codice più semplice, attraverso l’importazione e la personalizzazione degli stessi quando necessario. Il nostro editor di testo divide la finestra in due parti, un terzo dello schermo è dedicato alla barra dei menu ed i rimanenti due terzi della schermata sono destinati alla visualizzazione dell’area di testo. La barra dei menu viene visualizzata in primo piano rispetto a qualsiasi altro elemento.

  1.   Rectangle{
  2.  
  3.          id: screen
  4.          width: 1000; height: 1000
  5.  
  6.          //Lo schermo è diviso in MenuBar e TextArea. La MenuBar occupa 1/3 dello schermo
  7.          property int partition: height/3
  8.  
  9.          MenuBar{
  10.              id:menuBar
  11.              height: partition
  12.              width:parent.width
  13.              z: 1
  14.          }
  15.  
  16.          TextArea{
  17.              id:textArea
  18.              anchors.bottom:parent.bottom
  19.              y: partition
  20.              color: "white"
  21.              height: partition*2
  22.              width:parent.width
  23.          }
  24.      }

Tramite l’importazione di componenti riutilizzabili, il codice del nostro editor di testo appare molto più semplice. Possiamo quindi personalizzare l’applicazione principale, senza preoccuparci delle proprietà che abbiamo già definito prima. Usando questo approccio, i layout delll’applicazione ed i componenti dell’interfaccia grafica possono essere creati ed assemblati più facilmente.

editor di testi

Decorare l’editor di testo

Implementare un interfaccia a scomparsa

Il nostro editor di testo appare semplice, ma possiamo abbellirlo. Utilizzando QML possiamo dichiarare delle transizioni per animarlo. La nostra barra del menu occupa un terzo dello schermo, sarebbe carino se apparisse solo quando lo vogliamo.

A tale scopo possiamo aggiungere un elemento di interfaccia (drawer) che si occuperà di contrarre o espandere la barra dei menu quando viene cliccata. Nella nostra implementazione abbiamo un sottile rettangolo che risponde ai click del mouse. Il drawer, così come l’applicazione, ha due stati: aperto (DRAWER_OPEN) e chiuso (DRAWER_CLOSED). L’elemento drawer è una sottile striscia rettangolare al cui interno è presente un elemento immagine [doc.qt.nokia.com] che definisce un’icona a forma di freccia centrata all’interno dell’area. Il drawer assegna uno stato, con identificativo screen, all’intera applicazione ogni volta che l’utente clicca la MouseArea.

  1. Rectangle{
  2.          id:drawer
  3.          height:15
  4.  
  5.          Image{
  6.              id: arrowIcon
  7.              source: "images/arrow.png"
  8.              anchors.horizontalCenter: parent.horizontalCenter
  9.          }
  10.  
  11.          MouseArea{
  12.              id: drawerMouseArea
  13.              anchors.fill:parent
  14.              onClicked:{
  15.                  if (screen.state == "DRAWER_CLOSED"){
  16.                      screen.state = "DRAWER_OPEN"
  17.                  }
  18.                  else if (screen.state == "DRAWER_OPEN"){
  19.                      screen.state = "DRAWER_CLOSED"
  20.                  }
  21.              }
  22.              ...
  23.          }
  24.      }

Uno stato è semplicemente un’insieme di configurazioni predefinite e si dichiara tramite un elemento State [doc.qt.nokia.com]. Una lista di stati può essere collegata alla proprietà states. Nella nostra applicazione i due stati sono chiamati DRAWER_CLOSED e DRAWER_OPEN. La configurazione dei componenti è definita negli elementi PropertyChanges [doc.qt.nokia.com]. Nello stato DRAWER_OPEN quattro elementi riceveranno modifiche alle loro proprietà. Il primo, la menuBar, cambia la proprietà y in 0. Similmente, la textArea si abbasserà in una nuova posizione quando lo stato è DRAWER_OPEN. La textArea, il drawer e la sua icona subiranno cambiamenti delle loro proprietà per adeguarsi allo stato corrente.

  1. states:[
  2.          State {
  3.              name: "DRAWER_OPEN"
  4.              PropertyChanges { target: menuBar; y: 0}
  5.              PropertyChanges { target: textArea; y: partition + drawer.height}
  6.              PropertyChanges { target: drawer; y: partition}
  7.              PropertyChanges { target: arrowIcon; rotation: 180}
  8.          },
  9.          State {
  10.              name: "DRAWER_CLOSED"
  11.              PropertyChanges { target: menuBar; y:-height; }
  12.              PropertyChanges { target: textArea; y: drawer.height; height: screen.height - drawer.height }
  13.              PropertyChanges { target: drawer; y: 0 }
  14.              PropertyChanges { target: arrowIcon; rotation: 0 }
  15.          }
  16.      ]

Le variazioni tra uno stato e l’altro sono brusche, perciò necessitano di transizioni più dolci. Le transizioni tra gli stati sono definite utilizzando l’elemento Transition [doc.qt.nokia.com], che può essere legato alla proprietà transitions di un elemento. il nostra editor di testo effettua transizioni quando lo stato diventa DRAWER_OPEN oppure DRAWER_CLOSED. È importante sottolineare che le transizioni necessitano di uno stato di partenza (from) e di uno di arrivo (to), tuttavia, nel nostro caso, possiamo utilizzare il simbolo jolly * per indicare che la transizione si applica a tutti i cambiamenti di stato.

Durante le transizioni possiamo assegnare delle animazioni ai cambiamenti di proprietà. La nostra menuBar cambia posizione da y:0 a y:-partition: è possibile animare questa transizione utilizzando l’elemento NumberAnimation [doc.qt.nokia.com]. Definiamo che le proprietà dell’oggetto si animino per un certo periodo con un andamento definito da una certa curva. Una curva di andamento controlla la progressione dell’animazione, interpolando i comportamenti durante la variazione di stato. L’evoluzione della curva scelta è Easing.Out.Quint [doc.qt.nokia.com] che rallenta i movimenti verso la fine dell’animazione. E’ possibile approfondire l’argomento leggendo l’articolo QML’s Animation [doc.qt.nokia.com].

  1. transitions: [
  2.          Transition {
  3.              to: "*"
  4.              NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
  5.              NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
  6.              NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
  7.          }
  8.      ]

Un altro modo per animare variazioni dei valori delle proprietà è dichiarando un elemento Behaviour [doc.qt.nokia.com]. Una transizione avviene solo durante cambi di stato e Behaviour può impostare un’animazione per una modifica generica della proprietà. Nell’editor di testo, l’icona freccia ha un NumberAnimation che anima la sua proprietà rotation ad ogni cambiamento.

  1. In TextEditor.qml:
  2.  
  3.      Behavior{
  4.          NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
  5.      }

Grazie alla conoscenza di stati ed animazioni, siamo ora in grado di migliorare l’aspetto dei nostri componenti. In Button.qml possiamo aggiungere modifiche alle proprietà scale e color quando il bottone viene premuto. I tipi colore si animano utilizzando ColorAnimation, mentre i tipi numerici con NumberAnimation. La sintassi on nomeProprietà mostrata sotto semplifica il lavoro quando si lavora su di una singola proprietà.

  1. In Button.qml:
  2.      ...
  3.  
  4.      color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
  5.      Behavior on color { ColorAnimation{ duration: 55} }
  6.  
  7.      scale: buttonMouseArea.pressed ? 1.1 : 1.00
  8.      Behavior on scale { NumberAnimation{ duration: 55} }

Inoltre, possiamo migliorare l’aspetto dei componenti QML aggiungendo effetti di colore, come sfumature ed effetti di trasparenza. Dichiarando un elemento Gradient [doc.qt.nokia.com] si sovrascrive la proprietà color dell’elemento. E’ possibile definire un colore all’interno della sfumatura utilizzando l’elemento GradientStop [doc.qt.nokia.com]. La sfumatura è valorrizata utilizzando una scala tra 0,0 e 1,0.

  1. In MenuBar.qml
  2.      gradient: Gradient {
  3.          GradientStop { position: 0.0; color: "#8C8F8C" }
  4.          GradientStop { position: 0.17; color: "#6A6D6A" }
  5.          GradientStop { position: 0.98;color: "#3F3F3F" }
  6.          GradientStop { position: 1.0; color: "#0e1B20" }
  7.      }

Il gradiente è usato dalla barra del menu per simulare l’effetto di profondità. Il primo colore inizia a 0.0 e l’ultimo termina a 1.0.

Prossimi passi

Abbiamo terminato di costruire l’interfaccia utente di un semplice editor di testo. Proseguendo, completata l’interfaccia utente, possiamo implementare la logica dell’applicazione utilizzando Qt e C++ standard. QML funziona bene come strumento di prototipazione, separando efficacemente la logica dell’applicazione dal progetto dell’interfaccia utente.

Interfaccia editor quasi conclusa

Estendere QML attraverso Qt C++

Ora che abbiamo completato il layout del nostro editor di testi, possiamo implementare le funzionalità in C++. Usando il QML con C++ abbiamo la possibilità di creare la logica della nostra applicazione attraverso Qt. Possiamo creare dei contenuti QML in un applicazione C++ usando le classi dichiarative Qt [doc.qt.nokia.com] e visualizzando gli elementi QML usando la Graphic Scene. Altrimenti, possiamo esportare il nostro codice C++ in un plugin che potrà essere letto dal tool qmlviewer [doc.qt.nokia.com]. Per la nostra applicazione implementiamo le funzioni di caricamento e salvataggio per poi esportarle come un plugin. In questo modo, dobbiamo solo caricare direttamente il file QML invece di eseguire un file eseguibile.

Esporre classi C++ in QML

Implementeremo il caricamento e salvataggio dei file attraverso Qt e C++. Le classi e le funzioni C++ possono essere usare in QML registrandole. Le classi hanno bisogno di essere compilate come plugin Qt e i file QML devo conoscere dove sono localizzati i plugin.

Per la nostra applicazione, dobbiamo creare i seguenti elementi:

  1. Una classe Directory che gestirà le operazioni legate alle directory
  2. Una classe File che sarà un QObject [doc.qt.nokia.com], che simulerà la lista di files nella directory
  3. Una classe plugin che registrerà la classe per il contenuto QML
  4. Un progetto Qt che compilerà il plugin
  5. Un file qmldir che informerà il tool qmlviewerfile sulla locazione del plugin

Costruire il plugin Qt

Per costruire il plugin, abbiamo bisogno di specificarlo nel file di progetto Qt. Innanzitutto, i sorgenti,gli headers e i moduli devono essere aggiunti al nostro file di progetto. Tutti i file C++ e di progetto devono essere nella directory filedialog.

  1.  In cppPlugins.pro:
  2.  
  3.      TEMPLATE = lib
  4.      CONFIG += qt plugin
  5.      QT += declarative
  6.  
  7.      DESTDIR +=  ../plugins
  8.      OBJECTS_DIR = tmp
  9.      MOC_DIR = tmp
  10.  
  11.      TARGET = FileDialog
  12.  
  13.      HEADERS +=     directory.h \
  14.              file.h \
  15.              dialogPlugin.h
  16.  
  17.      SOURCES +=    directory.cpp \
  18.              file.cpp \
  19.              dialogPlugin.cpp

In particolare, compiliamo Qt con i moduli dichiarativi e settiamolo come plugin, avremo bisogno di un lib template. Metteremo poi il plugin compilato nella sua directory padre.

Registrare una classe in QML

  1.  In dialogPlugin.h:
  2.  
  3.      #include <QtDeclarative/QDeclarativeExtensionPlugin>
  4.  
  5.      class DialogPlugin : public QDeclarativeExtensionPlugin
  6.      {
  7.          Q_OBJECT
  8.  
  9.          public:
  10.          void registerTypes(const char *uri);
  11.  
  12.      };

La nostra classe plugin, DialogPlugin è una sottoclasse di QDeclarativeExtensionPlugin [doc.qt.nokia.com]. Dobbiamo implementare la funzione ereditata, registerTypes() [doc.qt.nokia.com]. Il file dialogPlugin.cpp si prenta cosi:

  1.  DialogPlugin.cpp:
  2.  
  3.      #include "dialogPlugin.h"
  4.      #include "directory.h"
  5.      #include "file.h"
  6.      #include <QtDeclarative/qdeclarative.h>
  7.  
  8.      void DialogPlugin::registerTypes(const char *uri){
  9.  
  10.          qmlRegisterType<Directory>(uri, 1, 0, "Directory");
  11.          qmlRegisterType<File>(uri, 1, 0,"File");
  12.      }
  13.  
  14.      Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

La funzione registerTypes() [doc.qt.nokia.com] registra le nostre classi File e Directory in QML. Questa funzione necessità di un nome per la classe per il suo template, un numero di versione maggiore e minore e un nome per la nostre classi.

Dovremmo esportare il plugin usando la macro Q_EXPORT_PLUGIN2 [doc.qt.nokia.com]. Notate nel nostro file dialogPlugin.h, abbiamo messo la macro Q_OBJECT [doc.qt.nokia.com] macro all’inizo della classe. Dobbiamo quindi lanciare qmake sul progetto per generare i necessari codici meta-oggetto di Qt.

Creare proprietà QML nella classi C++

Possiamo creare degli elementi e delle proprietà QML usando C++ e il sistema meta-oggetto di Qt. Possiamo implementare delle proprietà usando gli slot e signal, per farli conoscere a Qt. Queste proprieta possono poi essere usate in QML.

Per l’editor di testi, abbiamo bisogno di abilitare il caricamento e salvataggio dei files. Tipicamente, queste funzionalità sono contenute in un file dialog. Fortunatamente, possiamo usare QDir [doc.qt.nokia.com], QFile [doc.qt.nokia.com] e QTextStream [doc.qt.nokia.com] per implementare la lettura delle cartelle e i flussi di input/output.

  1. class Directory : public QObject{
  2.  
  3.          Q_OBJECT
  4.  
  5.          Q_PROPERTY(int filesCount READ filesCount CONSTANT)
  6.          Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
  7.          Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
  8.          Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
  9.  
  10.          ...

La classe Directory usa il sistema meta-oggetto di Qt per registrare le proprietà di cui ha bisogno per realizzare la gestione dei file. La classe Directory è esportata come plugin e usabile in QML come un elemento Directory. Ciascuna delle proprietà elencate che utilizza la macro Q_PROPERTY [doc.qt.nokia.com] è una proprietà QML.

Q_PROPERTY dichiara una proprietà, così come le sue funzioni di lettura e scrittura nel sistema meta-oggetto di Qt. Per esempio la proprietà filename, di tipo QString [doc.qt.nokia.com], è leggibile usando la funzione filename() e scrivibile usando la funzione setFilename(). Inoltre. c’è un segnale associato alla proprietà filename chiamato filenameChanged(), che è emesso quando la proprietà cambia. Le funzioni di lettura e scrittura sono dichiarate pubbliche nel file header.

Allo stesso modo, abbiamo le altre proprietà dichiarate ai loro usi. La proprietà filesCount indica il numero di file in una directory. La proprietà filename è settata sul nome del file corrente selezionato e il caricamento/salvattaggio del contenuto del dile è memorizzato nella proprietà fileContent.

  1.  Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

La proprietà files list è una lista di tutti i file filtrati nella directory. La classe Directory è implementata per filtrare i file non validi; solo i file con estensione .txt sono validi. Inoltre, QLists [doc.qt.nokia.com] può essere utilizzata nei file QML dichiarandoli come QDeclarativeListProperty [doc.qt.nokia.com] in C++. L’oggetto template deve ereditare da un QObject [doc.qt.nokia.com], quindi, la classe File deve anche ereditare da QObject. Nella classe Directory, l’elenco di oggetti File che è memorizzato in un Qlist chiamato m_fileList.

  1.  class File : public QObject{
  2.  
  3.          Q_OBJECT
  4.          Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
  5.  
  6.          ...
  7.      };

Le proprietà possono quindi essere utilizzate in QML come parte del elemento della proprietà Directory. Notate che non abbiamo bisogno di creare un identificatore id nel nostro codice C++.

  1.  Directory{
  2.          id: directory
  3.  
  4.          filesCount
  5.          filename
  6.          fileContent
  7.          files
  8.  
  9.          files[0].name
  10.      }

Siccome QML usa la sintassi e la struttura Javascript, possiamo scorrere l’elenco dei file e recuperare le sue proprietà. Per recuperare il nome del primo file, chiameremo files0.name.

Le regolari funzioni C++ sono accessibili anche da QML. Il caricamento e salvataggio implementati in C++ sono dichiarati usando la macro Q_INVOKABLE [doc.qt.nokia.com]. Altrimenti, possiamo dichiarare le funzioni cme slot e saranno accessibili da QML.

  1. In Directory.h:
  2.  
  3.      Q_INVOKABLE void saveFile();
  4.      Q_INVOKABLE void loadFile();

La classe Directory deve inoltre notificare agli altri oggetti ogni volta che cambia il contenuto della directory. Questa funzione viene eseguita utilizzando un segnale. Come accennato in precedenza, i segnali QML devono avere un gestore corrispondente con prefisso i loro nomi. Il segnale è chiamato directoryChanged e viene emesso quando c’è un aggiornamento delal directory. L’aggiornamento semplicemente ricarica il contenuto della directory e aggiorna la lista dei files validi nella directory. Gli oggetti QML possono essere notificati collegando un azione al segnale onDirectoryChanged.

La lista di proprietà deve essere ulteriormente esplorata. Questo perché le proprietà list utilizzano delle chiamate per accedere e modificare il contenuto dell’elenco. La proprietà list è di tipo QDeclarativeListProperty<File>. Ogni volta che si accede alla lista, la funzione di accesso deve restituire un QDeclarativeListProperty<File>. Il template File, ha bisogno di derivare da un QObject. Inoltre, per creare QDeclarativeListProperty [doc.qt.nokia.com], la lista di accesso e modifica devono essere passati al construttore come puntatori a funzione. La lista, in questo caso QList, deve anche essere una lista di puntatori a files.

Il costruttore QDeclarativeListProperty [doc.qt.nokia.com] e l’implementazione della Directory:

  1.      QDeclarativeListProperty  ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )
  2.      QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt,  &clearFilesPtr );

Il costruttore passa dei puntatori alla funzione che li accoda alla lista, conta la lista, recupera l’oggetto usando un indice e svuota la lista. Solo la funzione di accodamento è obbligatoria. Si noti che il puntatore a funzione deve corrispondere alla definizione di AppendFunction [doc.qt.nokia.com], CountFunction [doc.qt.nokia.com], AtFunction [doc.qt.nokia.com], o ClearFunction [doc.qt.nokia.com].

  1.      void appendFiles(QDeclarativeListProperty<File> * property, File * file)
  2.      File* fileAt(QDeclarativeListProperty<File> * property, int index)
  3.      int filesSize(QDeclarativeListProperty<File> * property)
  4.      void clearFilesPtr(QDeclarativeListProperty<File> *property)

Per semplificare la nostra file dialog, la classe Directory filtra i files di testo non validi, che non hanno estensione .txt. Se un file non ha estensione .txt, non sarà visibile nella nostra file dialog. Inoltre, l’applicazione consente di verificare che i file salvati hanno estensione. Directory utilizza QTextStream [doc.qt.nokia.com] per leggere il file e per esportare il contenuto in un file.

Con il nostro elemento Directory, possiamo recuperare i file come lista, conoscere quanti file ci sono nella directory dell’applicazione, ottenere il nome del file e il suo contenuto come stringhe, e notificare se ci sono cambiamenti nel contenuto della directory.

Per compilare il plugin, lanciamo qmake sul file di progetto cppPlugins.pro, e lanciamo un make per compilare e trasferire il plugin nella directory dei plugins.

Integrare il File Dialog nel file Menu

Il nostro FileMenu deve visualizzare l’elemento FileDialog, contenente la lista di files in una directory, consentendo all’utente di selezionarne uno cliccando sulla lista. Abbiamo bisogno di assegnare i pulsanti del salvataggio e caricamento alle loro rispettive azioni. Il file FileMenu contiene un input di testo editabile per consentire all’utente di inserire il nome di un file attraverso la tastiera.

L’elemento Directory è usato nel file FileMenu.qml e notifica all’elemento FileDialog che la directory ha aggiornato il suo contenuto. Questa notifica è eseguita dal gestore del signal onDirectoryChanged.

  1.  In FileMenu.qml:
  2.  
  3.      Directory{
  4.          id:directory
  5.          filename: textInput.text
  6.          onDirectoryChanged: fileDialog.notifyRefresh()
  7.      }

Mantenendo con la semplicità della nostra applicazione, il filedialog sarà sempre visibile e non visualizzaremo file di testo non validi, che non hanno un estensione .txt.

  1. In FileDialog.qml:
  2.  
  3.      signal notifyRefresh()
  4.      onNotifyRefresh: dirView.model = directory.files

L’elemento FileDialog visualizza il contenuto della directory leggendo le sue proprietà chiamate files. I files sono usati come modelli dell’elemento GridView [doc.qt.nokia.com], che visualizza gli oggetti in una griglia in accordo con il suo delegato. Il delegato gestisce l’aspetto del modello e il nostro file dialog semplicemente crea una griglia con un testo centrato nel mezzo. Cliccando sul nome del file si visualizzerà un rettangolo per evidenziare il nome del file. Il FileDialog è notificato ogni volta che il segnale notifyRefresh è emesso, ricaricando i files nella directory.

  1.  In FileMenu.qml:
  2.  
  3.      Button{
  4.          id: newButton
  5.          label: "New"
  6.          onButtonClick:{
  7.              textArea.textContent = ""
  8.          }
  9.      }
  10.      Button{
  11.          id: loadButton
  12.          label: "Load"
  13.          onButtonClick:{
  14.              directory.filename = textInput.text
  15.              directory.loadFile()
  16.              textArea.textContent = directory.fileContent
  17.          }
  18.      }
  19.      Button{
  20.          id: saveButton
  21.          label: "Save"
  22.          onButtonClick:{
  23.              directory.fileContent = textArea.textContent
  24.              directory.filename = textInput.text
  25.              directory.saveFile()
  26.          }
  27.      }
  28.      Button{
  29.          id: exitButton
  30.          label: "Exit"
  31.          onButtonClick:{
  32.              Qt.quit()
  33.          }
  34.      }

Il nostro FileMenu può ora connette le sue rispettive azioni. Il saveButton trasferirà il testo dal TextEdit sulla proprietà fileContent della directory, poi copierà il nome dall’ input textedit. Finalmente, il pulsante chiamerà la funzione saveFile(), salvando il file. Il pulsante loadButton ha una simile esecuzione. Inoltre, l’azione New svuoterà il contenuto del TextEdit.

Inoltre, i pulsanti EditMenu sono collegati alle funzioni di TextEdit per copiare, incollare e selezionare tutto il testo nell’editor di testo.

editor di testo quasi completo

Completamento Editor di testi

editor di testi finito

L’applicazione può funzionare come un semplice editor di testi, in grado di accettare il testo e salvarlo in un file. L’editor di testo può anche caricare un file e eseguire la manipolazione del testo.