Le guide de développement d'application de bureau avec Qt Quick

traducteur : Grégoire Lothe

Le but de ce guide est de vous familiariser avec Qt Quick et QML en employant les meilleures pratiques de programmation. Notez qu'il est tout de même nécessaire de connaître les bases de QML. À travers ce guide, on verra quelques bonnes techniques à utiliser. On terminera par voir comment déployer son application sur les plateformes de bureau. N'hésitez pas à consulter le code source fourni avec ce guide pour mieux comprendre les exemples et plus généralement la programmation avec Qt Quick !

Commentez

- Titre : Le guide de développement d'application de bureau avec Qt Quick

- Auteur : traducteur : Grégoire Lothe

- Parution : 19 juin 2012

- Mise à jour : 21 novembre 2017

- Licence : Le contenu de cet article est rédigé par et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. À propos de ce guide

- I-A. Pourquoi lire ce guide ?

- I-B. Et ensuite ?

- II. Design de base et prototypes

- II-A. Aperçu de l'application NoteApp

- II-A-1. Éléments de l'interface

- II-A-2. Comportement de l'interface

- II-A-2-a. Et ensuite ?

- II-B. Création de composants QML pour l'interface utilisateur

- II-B-1. Création d'un projet UI Qt Quick dans Qt Creator

- II-B-2. Créer des composants QML à partir d'éléments plus simples

- II-B-2-a. Et ensuite ?

- II-C. Manipuler la disposition des items QML et créer ses propres composants

- II-C-1. Note et NoteToolbar

- II-C-2. Page

- II-C-3. Signets (Marker)

- II-C-3-a. Et ensuite ?

- II-D. Utilisation d'un Repeater et d'un délégué pour créer la liste de signets

- II-D-1. Et ensuite ?

- II-E. Finalisation du prototype

- II-E-1. Assembler le prototype

- II-E-2. Rendre les notes déplaçables avec l'élément QML MouseArea

- II-E-2-a. Et ensuite ?

- III. Implémentation de l'interface utilisateur et ajout de quelques fonctionnalités

- III-A. Création du composant PagePanel pour gérer les Pages

- III-A-1. Utilisation d'un élément QML Item comme élément de base

- III-A-2. Utilisation des états (states)

- III-A-2-a. Et ensuite ?

- III-B. Lier les items Marker avec leur page respective dans le Repeater

- III-B-1. Et ensuite ?

- III-C. Ajouter des graphismes : Image ou BorderImage?

- III-C-1. Images d'arrière-plan sur les composants QML

- III-C-2. Création du composant Tool

- III-C-2-a. Et ensuite ?

- IV. Création dynamique de notes et stockage local

- IV-A. Création et gestion dynamique des notes

- IV-A-1. Création dynamique des notes

- IV-A-2. Suppression des notes

- IV-A-3. Et ensuite ?

- IV-B. Stocker et charger les notes depuis une base de données

- IV-B-1. Champs de la base de données

- IV-B-2. Une bibliothèque « sans état »

- IV-B-3. Et ensuite ?

- V. Remerciements

I - À propos de ce guide

I-A - Pourquoi lire ce guide ?

Ce guide apporte un aperçu des fonctionnalités de Qt Quick et de QML concernant le développement d'applications de bureau.

On se concentrera sur Qt Quick et les façons de l'utiliser efficacement pour écrire des applications complètes sans aucune ligne de code en C++. Il vous guidera pas à pas, des réglages de l'environnement de développement à la création de projets, jusqu'à la phase finale : le déploiement. On créera une application de gestion des notes à l'aide de Post-it, « NoteApp ».

Chaque chapitre est divisé en plusieurs étapes. Chacune décrit une fonctionnalité particulière de l'application et le code QML nécessaire. Ce guide couvre plusieurs aspects d'une interface utilisateur moderne tels que les animations, les bases de données et l'utilisation de JavaScript pour la logique de l'application.

Cette application ne ressemblera pas à une application de bureau classique. On ne retrouvera pas les éléments typiques tels que des barres d'outils, des menus, des dialogues, etc. Elle est fortement inspirée des interfaces modernes et fluides que l'on pourrait retrouver sur tablette ou smartphone, alors qu'elle cible plus un environnement de bureau.

Pour faciliter la lecture, chaque chapitre propose une version de NoteApp. Vous devriez vous y référer au cours de la lecture de ce guide. Les sources sont disponibles.

À la fin de ce guide, vous devriez bien comprendre comment fonctionne la création d'applications avec QML et Qt Quick, tout en ayant déjà pris de bonnes habitudes.

Une capture d'écran de NoteApp.

I-B - Et ensuite ?

On commence par créer un prototype de NoteApp. On découvre ainsi comment QML peut aider pendant cette étape.

II - Design de base et prototypes

L'un des principaux avantages de Qt Quick et de QML est qu'ils permettent de rapidement parvenir à un prototype. La phase de prototypage sera donc la première étape, et ce pour deux raisons. Premièrement, les designers peuvent rapidement et facilement parvenir à un premier jet de l'interface comme expliqué plus haut. Deuxièmement, cette phase incite les développeurs à travailler main dans la main avec les designers. Cette relation développeur/graphiste permet de développer rapidement et sûrement.

Plus tard, ce prototype sera utilisé comme base lorsque le développement commencera réellement. Dans ce chapitre, on avancera pas à pas en commençant par voir quelques concepts faits à la main. On discutera des fonctionnalités désirées et de la façon dont doit réagir l'interface aux interactions de l'utilisateur.

On abordera quelques concepts de base de QML tels que la création de composants simples ainsi que la disposition de ces derniers.

Note : vous pouvez retrouver les codes relatifs à ce chapitre dans le dossier compressé disponible dans l'introduction.

Voici un bref résumé de l'objectif de ce chapitre :

- concepts de l'interface et discussion sur les fonctionnalités ;

- création de composants QML avec Qt Creator ;

- utilisation de la propriété anchor et des éléments Repeater pour disposer correctement les composants QML.

II-A - Aperçu de l'application NoteApp

L'application NoteApp utilise un design basé sur l'utilisation de Post-it pour la création de notes et leur enregistrement.

Pour simplifier l'organisation de ses notes, l'utilisateur devra pouvoir les ranger dans des catégories prédéfinies. On imagine donc avoir trois catégories auxquelles elles peuvent appartenir. Chaque catégorie sera représentée par une zone dans laquelle les notes seront placées. On y ajoute donc aussi l'élément Page, une Page étant une zone dans laquelle les notes seront créées et placées.

L'utilisateur pourra supprimer une note à la fois ou toutes en même temps et les déplacer librement sur la page. Les trois pages correspondront aux trois catégories et seront repérées par des signets. Chaque signet possèdera une couleur particulière.

Ce serait aussi une bonne idée de stocker les notes localement et peut-être de les enregistrer automatiquement pour l'utilisateur.

Récapitulatif :

- création/suppression des Notes ;

- édition et positionnement libre sur la page ;

- stockage local ;

- séparation en trois pages indiquées par des signets.

II-A-1 - Éléments de l'interface

On commence par implémenter le design de l'image du dessous.

Elle donne une bonne idée de ce que pourrait imaginer l'utilisateur concernant une telle interface. Elle permet également d'identifier les différents éléments et interactions à implémenter plus tard.

II-A-2 - Comportement de l'interface

Comme dit précédemment, trois pages peuvent contenir des items Note. On peut aussi constater que les signets pour changer de page sont placés sur la droite. La barre d'outils de gauche ne contient que deux boutons : un pour l'ajout et l'autre pour la suppression de toutes les notes de la page. Chaque item Note possède lui-même une barre d'outils qui lui permet d'être déplacé à l'aide d'un système de glisser-déposer. Sur chaque note, un bouton permet de supprimer la note.

II-A-2-a - Et ensuite ?

On tente d'identifier les composants nécessaires à l'implémentation des fonctionnalités qu'on a choisies et apprendre à les créer.

II-B - Création de composants QML pour l'interface utilisateur

Une fois les fonctionnalités et le design d'interface utilisateur pour l'application NoteApp choisis, on peut commencer sans problème à implémenter un premier jet.

Le prototype sera très simpliste et ne contiendra aucune fonctionnalité, mais donnera une idée de la version finale de l'application.

Dans cette étape, on crée notre première interface avec Qt Quick en utilisant Qt Creator et on verra comment créer les premiers composants QML.

II-B-1 - Création d'un projet UI Qt Quick dans Qt Creator

L'utilisation de Qt Creator simplifie vraiment la vie pendant la phase de prototypage. Vous vous en rendrez sûrement compte rapidement. C'est la façon la plus efficace, surtout si vous n'arrêtez pas de changer, tester et bidouiller dans chaque composant QML. N'hésitez pas à regarder les effets d'une modification, même mineure, cela vous permettra d'identifier plus facilement les erreurs.

Pour comprendre comment créer un projet d'interface Qt Quick, la documentation propose quelques pistes.

Note : un unique fichier QML « main.qml » au début sert à charger et à lancer l'application. Pour NoteApp, le fichier main.qml a en réalité été généré automatiquement par Qt Creator.

II-B-2 - Créer des composants QML à partir d'éléments plus simples

Pour rapprocher la programmation orientée objet de QML, on pourrait comparer les composants QML à des classes utilisées pour instancier et déclarer des objets. On pourrait en fait écrire entièrement une application simple dans un gros fichier QML, mais cela augmenterait sûrement la complexité et empêcherait de réutiliser le code.

Un composant QML peut être vu comme un groupe d'éléments QML, bien qu'en général il n'en contienne qu'un seul.

Note : chaque composant QML est contenu dans son propre fichier QML (.qml) éponyme. Par exemple, le composant Note sera dans un fichier appelé Note.qml.

D'après le concept d'interface, voici une brève liste des composants qui s'imposent d'eux-mêmes. On en ajoutera d'autres plus tard, lorsque l'on reverra l'application.

- Note : représente un item note.

- Page : contient des notes.

- Marker : représente un signet de page, permet à l'utilisateur de changer de page.

- NoteToolbar : la barre d'outils présente sur chaque note qui permet les déplacements avec la souris.

Jetez un coup d'œil à la page de la documentation dédiée pour comprendre comment utiliser Qt Creator pour créer les composants dont on a parlé. On regardera en détail la création de chacun d'entre eux dans les chapitres à venir.

II-B-2-a - Et ensuite ?

On commence à créer le prototype de l'interface de l'utilisateur.

II-C - Manipuler la disposition des items QML et créer ses propres composants

L'élément graphique « Rectangle » est un choix qui paraît évident pour créer les composants de base du prototype. Ils sont très faciles à utiliser et à mettre en place.

Note : c'est toujours une bonne idée d'indiquer les dimensions de base d'un composant tout juste créé. Tester l'application devient alors beaucoup plus facile.

On commence par implémenter le composant Note.

II-C-1 - Note et NoteToolbar

On a normalement créé les fichiers QML nécessaires avec Qt Creator.

Le code de NoteToolbar devrait ressembler à ceci pour correspondre à nos attentes :

// NoteToolbar.qml

import QtQuick 1.1

// Élément Rectangle avec dimensions et couleurs prédéfinies.
Rectangle {
id: root
width: 100
height: 62
color: "#9e964a"
}

Le composant Note possédera un composant NoteToolbar. De plus, il disposera d'un élément de saisie pour récupérer le texte entré par l'utilisateur. On utilisera l'élément QML TextEdit à cet effet. Pour arranger ces items entre eux, on utilise la propriété anchor. Cette propriété provient de l'élément Item, dont toutes les classes QML dérivent.

Référez-vous à la documentation pour plus de détails sur l'utilisation de la propriété anchor.

// Note.qml
import QtQuick 1.1
Rectangle {
id: root
width: 200
height: 200
color: "#cabf1b"

// Création de l'item NoteToolbar qui sera positionné/attaché à son parent.
NoteToolbar {
id: toolbar
// La hauteur doit être spécifiée puisqu'il
// n'y a pas de positionnement relatif sur le bas.
height: 40

// " Liaison " au parent grâce aux
// positionnements relatifs sur le haut,
// la gauche et la droite.
anchors {
top: root.top
left: root.left
right: root.right
}
}

// Création de la zone de saisie.
TextEdit {
anchors {
top: toolbar.bottom
bottom: root.bottom
right: root.right
left: root.left
}
wrapMode: TextEdit.WrapAnywhere
}
}

Note : on ne peut pas combiner l'utilisation de la propriété anchor avec un positionnement absolu (ce qui semble logique).

Attention, pour des raisons de performances, vous ne devriez pas utiliser la propriété anchor avec des items qui n'ont pas de lien direct avec cet item (c'est-à-dire l'item parent et les items « frères », qui ont le même parent).

II-C-2 - Page

Une fois le composant Note prêt, on essaye d'en mettre quelques-uns dans un composant Page.

// Page.qml

import QtQuick 1.1

Rectangle {
id: root
width: 600
height: 400
color: "#222525"

// Création d'une Note.
Note {
id: note1
// Les propriétés x et y servent
// à définir un positionnement absolu.
x: 105; y: 144
}

Note {
id: note2
x: 344
y: 83
}
}

Avec Qt Creator, vous pouvez simplement exécuter ce fichier. Il lancera alors qmlviewer qui se chargera d'afficher le fichier Page.qml. Le résultat devrait ressembler à ceci :

II-C-3 - Signets (Marker)

Comme pour chaque autre composant, Marker sera simplement un rectangle avec des dimensions prédéfinies. Son comportement sera défini plus tard.

// Marker.qml

import QtQuick 1.1

Rectangle {
id: root
width: 50
height: 90
color: "#0a7bfb"
}

II-C-3-a - Et ensuite ?

Dans la prochaine partie, on verra comment utiliser les éléments Repeater et Column pour gérer une liste statique de signets.

II-D - Utilisation d'un Repeater et d'un délégué pour créer la liste de signets

Précédemment, on a vu comment créer les composants Note, NoteToolbar, Page et Marker et comment les positionner à l'aide de la propriété anchor.

Pourtant, si on regarde le concept, on remarque la présence de trois éléments Marker (signets) arrangés verticalement. On pourrait de nouveau utiliser la propriété anchor, mais cela augmenterait largement la complexité du code. QML dispose de moyens bien plus efficaces et propres tels que les dispositions (layout) et autres éléments de positionnement comme Column.

Comme on veut trois signets presque identiques dans un unique élément Column, on utilise l'élément Repeater, qui les dupliquera.

Column {
id: layout
// La propriété spacing peut être utilisée pour ajouter de l'espace entre les items.
spacing: 10

// Un Repeater pour générer trois items Marker.
Repeater {
model: 3
delegate:
// Utilisation de Marker comme délégué.
Marker { id: marker }
}
}

Dans ce bout de code, l'élément Repeater génère trois items (model: 3) d'après le délégué delegate, l'élément Marker.

Vous pouvez trouver d'autres informations sur l'utilisation conjointe des éléments Column et Repeater.

Vous vous demandez sûrement maintenant où placer le code du dessus. Eh bien, on doit le mettre dans un autre composant : MarkerPanel. En fait, il s'agit simplement d'une liste d'items Marker. Chacun d'entre eux peut être utilisé comme un élément de l'interface. On verra comment plus tard.

Voici à quoi ressemble le composant MarkerPanel :

// MarkerPanel.qml

import QtQuick 1.1

Rectangle {
id: root
width: 50
height: 300
// Élément Column qui gère le positionnement relatif.
Column {
id: layout
anchors.fill: parent
spacing: 10
Repeater {
// Trois items Marker.
model: 3
delegate: Marker { id: marker }
}
}
}

Note : tester chaque composant individuellement pendant la phase de prototypage est toujours une bonne idée. Cela permet de repérer les erreurs dès que possible.

Si on lance le fichier MarkerPanel.qml avec Qt Creator et son qmlviewer, on devrait obtenir ceci :

II-D-1 - Et ensuite ?

Dans la prochaine partie, on verra comment assembler les composants créés jusqu'à présent et on en finira avec le prototype.

II-E - Finalisation du prototype

On a enfin nos composants prêts à servir dans l'interface ! Voici la liste de ceux que nous avons implémentés pour l'instant :

- Note ;

- NoteToolbar ;

- Marker ;

- MarkerPanel ;

- Page.

On aura besoin de bien d'autres en avançant, comme vous pourrez le constater.

Comme dit précédemment, Qt Creator génère automatiquement un fichier main.qml considéré comme le fichier principal lors du lancement de l'application. On assemble donc ces composants dans ce fichier.

II-E-1 - Assembler le prototype

Si on regarde de nouveau le concept d'interface, on remarque que la zone des signets est située sur la droite. La zone qui contiendra les notes (Page) se situe au centre. Il manque encore la barre d'outils.

Implémentons-la donc maintenant !

La barre d'outils contient deux boutons : un pour créer une nouvelle note et l'autre pour les supprimer toutes. Pour rendre le tout plus simple, on ne les sépare pas dans un nouveau fichier QML, on les déclare à l'intérieur même du fichier main.qml.

Le code ressemble à ceci :

// Utilisation de l'élément Rectangle pour notre barre d'outils.
// Il aide à aligner le Column avec le reste des items.
Rectangle {
id: toolbar

// On définit une largeur puisque toolbar ne sera
// pas attaché sur la droite.
width: 50

color: "#444a4b"
anchors {
left: window.left
top: window.top; bottom: window.bottom
topMargin: 100; bottomMargin: 100
}

// Utilisation de Column pour gérer nos deux outils.
Column {
anchors { anchors.fill: parent; topMargin: 30 }
spacing: 20

// Juste pour le prototype, on utilise
// un repeater pour générer deux outils.
Repeater {
model: 2
// Le rectangle symbolisera l'outil
// pendant la phase de prototypage.
Rectangle { width: 50; height: 50; color: "red" }
}
}
}

Maintenant, nous sommes fins prêts à assembler le prototype. Voici le fichier main.qml :

// main.qml

import QtQuick 1.1

Rectangle {
// Utilisation de window comme id.
id: window
width: 800
height: 600

// Création de MarkerPane.
MarkerPanel {
id: markerPanel
width: 50
anchors.topMargin: 20
anchors {
right: window.right
top: window.top
bottom: window.bottom
}
}

// Barre d'outils.
Rectangle {
id: toolbar
width: 50
color: "#444a4b"
anchors {
left: window.left
top: window.top
bottom: window.bottom
topMargin: 100
bottomMargin: 100
}
}
Column {
anchors { fill: parent; topMargin: 30 }
spacing: 20
Repeater {
model: 2
Rectangle { width: 50;
height: 50;
color: "red"
}
}
}
}

Voici à quoi ressemble le tout assemblé lorsqu'il est lancé avec Qt Creator :

II-E-2 - Rendre les notes déplaçables avec l'élément QML MouseArea

Pour l'instant, on a un prototype très simpliste qui servira de base pour plus tard. On peut cependant ajouter une fonctionnalité très simple qui permettra de déplacer les notes sur la page dès maintenant. L'élément QML MouseArea possède un ensemble de propriétés drag. La propriété drag.target contiendra l'identificateur de notre note.

Comme l'utilisateur doit utiliser la barre d'outils des notes pour les déplacer, cet élément (le MouseArea) devra se trouver dans le composant NoteToolbar. Ce composant gère les déplacements avec la souris.

Pour réussir ce tour de force, on doit permettre à l'item NoteToolbar de lier sa propriété drag.target à l'id du composant Note. On utilise à cette fin les alias de propriétés (ce sont en fait des références à d'autres propriétés).

Voici donc le code de NoteToolbar modifié :

// NoteToolbar.qml

import QtQuick 1.1

Rectangle {
id: root
width: 100
height: 62
color: "#9e964a"

// Alias de propriété " drag " de cet item sur la propriété drag de mouseArea.
property alias drag: mousearea.drag

// Création de l'item MouseArea.
MouseArea {
id: mousearea
anchors.fill: parent
}
}

Dans le code du dessus, on peut voir que l'alias de propriété drag a été lié à la propriété drag de MouseArea. On verra comment l'utiliser dans le composant Note.

// Note.qml

import QtQuick 1.1

Rectangle {
id: root
width: 200
height: 200
color: "#cabf1b"

// Création d'un item NoteToolbar
// qui sera positionné en fonction de ses parents.
NoteToolbar {
id: toolbar
height: 40
anchors {
top: root.top
left: root.left
right: root.right
}

// Utilisation de l'alias de propriété défini plus haut
// pour affecter à drag.target notre item Note.
drag.target: root
}

// Création du TextEdit (zone de saisie).
TextEdit {
anchors {
top: toolbar.bottom
bottom: root.bottom
right: root.right
left: root.left
}
wrapMode: TextEdit.WrapAnywhere
}
}

Vous pouvez trouver des informations plus détaillées sur l'utilisation de propriétés liées à cette la page.

II-E-2-a - Et ensuite ?

On commence la « vraie interface ». On ajoute quelques fonctionnalités de base.

III - Implémentation de l'interface utilisateur et ajout de quelques fonctionnalités

Le prototype de base a permis de définir la base de l'interface et des fonctionnalités requises et d'identifier les composants QML requis.

En se basant sur le travail déjà accompli, on tente de construire une interface plus complète et « sophistiquée ». On ajoutera aussi quelques nouvelles fonctionnalités.

Ce chapitre décrit les étapes détaillées de l'utilisation de graphismes avec QML, quelques améliorations de l'interface et l'ajout de fonctionnalités plus avancées avec JavaScript. On approfondira quelques éléments QML et on en découvrira de nouveaux au fur et à mesure que la complexité du code augmentera et que les fonctionnalités viendront s'ajouter.

Voici la liste des points abordés pendant ce chapitre :

- gestion des éléments Page avec l'aide de Repeater et d'un nouveau composant : PagePanel ;

- graphismes plus avancés avec QML ;

- fonctions JavaScript à même le code pour ajouter des fonctionnalités plus complexes ;

- approfondissement des éléments QML que l'on a pu voir jusqu'à présent.

III-A - Création du composant PagePanel pour gérer les Pages

Avec le composant Page, on n'a créé qu'une seule et unique page. On l'a attachée à ses parents, comme quasiment tous les autres items avec la propriété anchor. Cependant, dans le cahier des charges, on devait disposer de plusieurs pages, sélectionnables avec les signets Marker. On a vu comment l'élément MarkerPanel pouvait en aligner trois verticalement. On reprend la même logique dans l'implémentation de PagePanel.

III-A-1 - Utilisation d'un élément QML Item comme élément de base

Avant d'aller plus loin, il est nécessaire que vous compreniez pourquoi utiliser l'élément Rectangle comme élément principal d'un composant devrait être évité dans la mesure du possible. Jusqu'à maintenant, on l'a utilisé parce qu'il fournissait rapidement un résultat valable (visuellement parlant) et c'est généralement ce qu'on attend d'un prototype.

Une fois, cependant, que le prototype est terminé, c'est mieux de remplacer le Rectangle par un Item, surtout si on veut personnaliser l'arrière-plan un peu plus qu'en changeant la couleur de fond (c'est d'ailleurs ce qu'on verra dans une prochaine partie).

Le composant Page ressemble désormais à ceci :

// Page.qml

import QtQuick 1.1
Item {
id: root
...
}

Attention : à partir de maintenant, on considérera que l'élément de base de chacun des composants est un Item. Jetez donc un coup d'œil au code source de ce chapitre.

III-A-2 - Utilisation des états (states)

À première vue, la différence majeure entre PagePanel et MarkerPanel est que les Marker sont toujours visibles, alors qu'on est supposé ne voir qu'une seule page à la fois dans avec PagePanel.

On pourrait obtenir le résultat attendu de plusieurs façons. Notamment, on pourrait utiliser une fonction JavaScript qui modifierait la visibilité de chaque Page en fonction du Marker pressé par l'utilisateur.

Avec NoteApp, on utilisera l'élément State à cette fin. Le composant PagePanel possédera trois états différents et chacun sera lié à une seule page. Ainsi, une seule d'entre elles sera visible à la fois.

D'abord, on doit un peu modifier le composant Page en affectant à la propriété opacity la valeur 0.0 par défaut. L'état ne fera en fait que modifier l'opacité de la page qui lui est liée.

// Page.qml

import QtQuick 1.1
Item {
id: root
opacity: 0.0
...
}

Une fois le fichier PagePanel.qml créé, on définit les différents états et leur page associée. On a besoin de ces états :

- personnel (personal) ;

- loisirs (fun) ;

- travail (work).

Ils vont modifier respectivement l'opacité de ces pages :

- personalpage ;

- funpage ;

- workpage.

Voici une représentation de ce qui a été décrit plus haut :

Voici à quoi ressemble le code de PagePanel :

import QtQuick 1.1

// PagePane.qml

Item {
id: root
// Création de la liste d'états (states).
states: [
// Création d'un item state avec son nom.
State {
name: "personal"
// Les propriétés qui vont être modifiées.
PropertyChanges {
target: personalpage
opacity:1.0
restoreEntryValues: true
}
},
State {
name: "fun"
PropertyChanges {
target: funpage
opacity:1.0
restoreEntryValues: true
}
},
State {
name: "work"
PropertyChanges {
target: workpage
opacity:1.0
restoreEntryValues: true
}
}
]

// Création des trois pages.
Page { id: personalpage; anchors.fill: parent }
Page { id: funpage; anchors.fill: parent }
Page { id: workpage; anchors.fill: parent }
}

Note : en mettant la propriété restoreEntryValue à true, on précise que, quand on change d'état, on veut que la propriété soit remise à sa valeur par défaut.

En regardant le code du dessus, on peut voir que l'on a créé trois items Page et que les trois états associés modifient l'opacité. Aucune surprise, donc.

Dans cette étape, on a réussi à créer le composant PagePanel qui permettra à l'utilisateur de changer de page et donc de mieux gérer ses notes.

III-A-2-a - Et ensuite ?

Dans la prochaine étape, les items Marker pourront changer l'état de PagePanel.

III-B - Lier les items Marker avec leur page respective dans le Repeater

On vient tout juste de voir l'implémentation du composant PagePanel, qui en fait utilise trois états différents pour modifier chacun l'opacité d'un composant Page. Dans cette étape, on verra comment utiliser les items Marker et MarkerPanel pour permettre à l'utilisateur de changer de page.

Pendant la phase de prototypage, le composant MarkerPanel était très simple et ne possédait aucune fonctionnalité. Il utilisait un élément Repeater qui générait trois items Marker (on le lui avait précisé avec les délégués).

MarkerPanel devrait s'occuper de stocker le signet actif, celui cliqué par l'utilisateur. En se basant sur ce fait, PagePanel mettrait à jour son état courant. On doit donc lier l'état de PagePanel à une nouvelle propriété de MarkerPanel qui contiendrait le signet actif.

On ajoute donc une propriété activeMarker (de type string) :

// MarkerPanel.qml

import QtQuick 1.1

Item {
id: root
width: 150; height: 450

// Une propriété activeMarker de type string pour stocker le signet courant.
property string activeMarker: "personal"
...

On pourrait stocker une valeur markerid, utile pour identifier de façon unique chaque signet. De cette manière, activeMarker prendrait alors la valeur markerid du signet activé par l'utilisateur.

L'élément Repeater génère les trois signets d'après un modèle. On peut donc modifier les valeurs de la propriété markerid.

// MarkerPanel.qml

import QtQuick 1.1

Item {
id: root
width: 150; height: 450

// Une propriété activeMarker de type string pour stocker le signet courant.
property string activeMarker: "personal"

// Une liste de données pour les trois signets (le modèle).
property variant markerData: [
{ markerid: "personal" },
{ markerid: "fun" },
{ markerid: "work" }
]

Column {
id: layout
anchors.fill: parent
spacing: 5

Repeater {
// Application du modèle
model: markerData
delegate:
Marker {
id: marker
// Gestion du signal " clicked() " de l'item Marker. Affecte la valeur markerid
// du signet cliqué à la propriété activeMarker de MarkerPanel.
onClicked: root.activeMarker = modelData.markerid
}
}
}
}

Dans ce code, on modifie la propriété activeMarker quand l'événement onClicked est actionné. Ceci veut dire que le signal clicked() est préalablement défini dans le composant Marker ; il est utilisé si l'utilisateur clique sur le signet.

Voici donc le composant Marker modifié :

// Marker.qml

Item {
id: root
width: 50; height: 90
signal clicked()

MouseArea {
id: mouseArea
anchors.fill: parent
// On émet le signal "clicked()".
onClicked: root.clicked()
}
}

Pour l'instant, le composant PagePanel est capable de changer de page (en réalité, en mettant l'opacité à 0 ou à 1). MarkerPanel est lui capable d'identifier le signet courant et d'envoyer un signal quand il change.

Comment utiliser la propriété activeMarker de MarkerPanel pour mettre jour l'état de PagePanel ?

Dans le fichier main.qml, où on a déjà un item MarkerPanel et une page, on doit d'abord remplacer l'item Page avec PagePanel.

// main.qml

// Création d'un MarkerPanel.

MarkerPanel {
id: markerPanel
width: 50
anchors.topMargin: 20
anchors {
right: window.right
top: window.top
bottom: window.bottom
}
}

...

// Création d'un PagePanel.
PagePanel {
id: pagePanel
// On lie l'état de PagePanel à la propriété
// activeMarker de MarkerPanel.
state: markerPanel.activeMarker
anchors {
right: markerPanel.left
left: toolbar.right
top: parent.top
bottom: parent.bottom
leftMargin: 1
rightMargin: -50
topMargin: 3
bottomMargin: 15
}
}

Dans le code du dessus, on peut voir comment le property binding peut aider à lier la propriété state à activeMarker. Cela veut dire que, dès que activeMarker sera modifié, state réagira en conséquence et sera modifié, changeant ainsi la page courante.

III-B-1 - Et ensuite ?

On modifiera un peu l'application pour la rendre plus attrayante, à l'aide de graphismes.

III-C - Ajouter des graphismes : Image ou BorderImage?

Grâce à la nature de QML, les développeurs et les designers peuvent travailler ensemble tout le long de la phase de développement. De nos jours, avoir une interface propre et classe fait une grosse différence quant à la façon dont l'utilisateur perçoit l'application.

N'hésitez surtout pas à utiliser autant de graphismes que vous le souhaitez dans votre interface. La collaboration entre les développeurs et les graphistes est très efficace avec QML, puisque ces derniers peuvent directement tester leurs graphismes sur des éléments de l'interface, mais aussi comprendre d'un point de vue technique ce dont les développeurs ont réellement besoin. L'application n'en devient que plus attrayante et plus facile à maintenir.

Ajoutons donc des graphismes à nos composants.

III-C-1 - Images d'arrière-plan sur les composants QML

L'élément BorderImage est conseillé dans les cas où la taille de l'image doit être ajustée en fonction du contexte, tout en conservant les bords à leur taille initiale. Les effets d'ombre sur les composants sont un bon exemple : en général, on préférerait qu'elles conservent la même taille.

Regardons ensemble comment utiliser les BorderImage comme arrière-plan pour les composants.

On ajoute l'élément BorderImage dans PagePanel.qml.

// PagePanel.qml

...

BorderImage {
id: background
// BorderImage prend toute la place possible.
anchors.fill: parent
source: "images/page.png"
// On indique la marge.
// Cette information devrait venir du graphiste.
border.left: 68; border.top: 69
border.right: 40; border.bottom: 80
}

...

On fait la même chose à l'intérieur de Note dans Note.qml.

// Note.qml

...

BorderImage {
id: noteImage
anchors { fill: parent}
source: "images/personal_note.png"
border.left: 20; border.top: 20
border.right: 20; border.bottom: 20
}

// Création de NoteToolbar qui sera positionné en fonction de son parent.
NoteToolbar {
id: toolbar
...

Attention : soyez sûr que l'élément BorderImage soit utilisé au bon « endroit », parce que l'ordre dans lequel sont définis les éléments d'un composant affecte le rendu final. Les items avec la même valeur « Z » (la profondeur) apparaîtront ainsi dans l'ordre dans lequel ils sont déclarés. Référez-vous à la page de la documentation pour plus d'informations à ce sujet.

Vous vous demandez sûrement quelle est la meilleure approche pour utiliser un arrière-plan sur les items Marker alors qu'ils sont créés à l'intérieur du composant MarkerPanel. Eh bien, la solution se trouve déjà dans le code du composant MarkerPanel !

La liste markerData, le modèle du Repeater, existe déjà. On peut agrandir markerData pour qu'elle contienne les chemins relatifs des images et utiliser Image comme élément de base de Marker.

// Marker.qml

import QtQuick 1.1

// Image est très pratique comme item de base puisque Marker est simplement
// un élément graphique avec un signal clicked().
Image {
id: root

// Déclaration du signal " clicked() ".
signal clicked()

// Création de MouseArea pour détecter les clics de souris.
MouseArea {
id: mouseArea
anchors.fill: parent

// On émet le signal " clicked() ".
onClicked: root.clicked()
}
}

Voyons maintenant comment le composant MarkerPanel doit être modifié :

// MarkerPanel.qml
...
// Dans markerData, on ajoute les chemins des images à utiliser.
property variant markerData: [
{ img: "images/personalmarker.png", markerid: "personal" },
{ img: "images/funmarker.png", markerid: "fun" },
{ img: "images/workmarker.png", markerid: "work" }
]

Column {
id: layout
anchors.fill: parent
spacing: 5

Repeater {
// On applique le modèle.
model: markerData
delegate: Marker {
id: marker

// On lie la valeur img du modèle
// à la valeur source du signet.
source: modelData.img

onClicked: root.activeMarker = modelData.markerid
}
}
}

Dans ce code, on peut voir comment la propriété source de Marker est liée à la valeur img de markerData.

On utilise aussi l'élément BorderImage pour mettre un arrière-plan sur le composant NoteToolbar ainsi que pour l'élément principal dans main.qml.

Note : demandez toujours aux graphistes quelles devraient être les marges sur les contours des éléments BorderImage et comment ces éléments devraient être alignés et positionnés.

En lançant MarkerPanel.qml dans Qt Creator, vous devriez obtenir ceci :

III-C-2 - Création du composant Tool

Ce serait plus pratique de créer un unique composant qui pourrait être utilisé pour les boutons New Note (nouvelle note) et Clear All (tout supprimer). C'est pourquoi on implémente à présent un composant Tool, qui aura comme élément de base Image qui gérera les clics de souris de l'utilisateur.

L'élément Image est souvent utilisé comme élément de l'interface de base, quelle que soit sa nature (image statique ou animée).

// Tool.qml

import QtQuick 1.1

// Utilisation de Image comme élément de base.
Image {
id: root

// On ajoute le signal clicked().
signal clicked()

// Utilisation de MouseArea
// pour intercepter les clics de souris.
MouseArea {
anchors.fill: parent
// Émission du signal clicked.
onClicked: root.clicked()
}
}

On utilise le composant Tool pour créer une barre d'outils, puisqu'il est déjà disponible. On doit modifier le code du prototype pour utiliser des items Tool au lieu de simples rectangles.

// main.qml

...
// Arrière plan de la barre d'outils
Rectangle {
anchors.fill: toolbar
color: "white"
opacity: 0.15
radius: 16
border { color: "#600"; width: 4 }
}

// Utilisation de Column pour aligner
// les Tool horizontalement.
Column { // Barre d'outils de gauche
id: toolbar
spacing: 16
anchors {
top: window.top
left: window.left
bottom: window.bottom
topMargin: 50
bottomMargin: 50
leftMargin: 8
}

// Outil " nouvelle note "
Tool {
id: newNoteTool
source: "images/add.png"
}

// Outil " tout supprimer "
Tool {
id: clearAllTool
source: "images/clear.png"
}
}
...

Maintenant que tous les graphismes sont en place, l'application est normalement beaucoup plus attrayante.

III-C-2-a - Et ensuite ?

Dans le prochain chapitre, on regardera en détail comment créer et gérer les notes dynamiquement et comment les stocker localement.

IV - Création dynamique de notes et stockage local

On a pu constater jusqu'à présent que QML était un langage déclaratif très efficace et assez puissant. On a pu aussi voir que l'on pouvait le combiner avec quelques lignes de code JavaScript dans un fichier QML pour implémenter des fonctionnalités supplémentaires, ce qui le rendait encore meilleur. QML ne s'arrête pas là : vous pouvez importer des fichiers ou des modules entièrement en JavaScript et les utiliser dans vos composants. C'est en partie ce qu'on verra au cours de ce chapitre.

Le but de l'application NoteApp est de fournir à l'utilisateur un moyen simple de gérer ses notes (c'est-à-dire les créer, les déplacer, les classer, les éditer, les supprimer, etc.). Mais quelle utilité aurait-elle vraiment si elle n'était pas capable de les sauvegarder ? On avait auparavant décidé que cette opération devait s'effectuer « discrètement », sans intervention de l'utilisateur. On parlera donc aussi de tout cela au cours de ce chapitre.

Les domaines abordés seront donc :

- l'utilisation de JavaScript pour implémenter des fonctions dynamiques (telles que l'ajout, la suppression…) ;

- l'utilisation d'une API Qt Quick pour la gestion des bases de données.

IV-A - Création et gestion dynamique des notes

L'application doit être capable d'ajouter ou de supprimer des notes dynamiquement. Il y a plusieurs façons de parvenir à nos fins. En fait, on en a déjà vu une : l'élément Repeater.

La création dynamique d'un composant nécessite qu'il soit parfaitement défini et chargé avant d'être instancié. On peut donc ensuite appeler la fonction JavaScript createObject(Item parent, object properties) de l'élément Component pour le créer. Pour plus d'information à ce sujet, rendez-vous sur la page de la documentation à ce sujet.

Voyons comment mettre ceci en pratique.

IV-A-1 - Création dynamique des notes

On sait que les items Page sont normalement parents des items Note : ils sont par conséquent responsable de leur création (ainsi que du chargement de ces dernières depuis la base de données dans laquelle elles seront stockées, mais on verra tout ça plus tard).

Comme dit plus haut, on charge Note dans Page :

// Page.qml

...
// Chargement du composant Note
Component {
id: noteComponent
Note { }
}
...

Note : createObject() appartient à l'item Component, on doit donc placer la note à l'intérieur d'un tel item avant de pouvoir continuer.

Maintenant, on ajoute une petite fonction JavaScript qui s'occupera de l'ajout des Note, sans oublier que la fonction createObject() prend en argument le parent ainsi qu'une liste d'argument pour la création du composant. On doit, de plus, encapsuler Note dans un autre item Item à l'intérieur même de Page (l'utilité réelle de cette opération vous apparaîtra plus tard qu'on s'occupera de la sauvegarde).

// Page.qml

...

// Création d'un item Item qui servira de container.
Item { id: container }

...

// Une fonction JavaScript pour créer une note.
function newNoteObject(args) {
// Appel de createObject() sur les items noteComponent.
// L'item container sera le parent.
var note = noteComponent.createObject(container, args)
if(note == null) {
console.log("impossible de créer l'objet note !")
}
}

...

On peut voir comment une nouvelle note peut être créée dans avec la fonction createObject(). Les notes créées de la sorte appartiendront à l'item container.

On veut désormais appeler cette fonction quand on appuie sur le bouton « New Note » de la barre d'outils (définie dans main.qml). Le problème reste de savoir dans quelle page créer la note lorsque l'on clique sur ce bouton. PagePanel s'occupe de la gestion des Page : de ce fait, il devrait être capable de dire quelle est la page affichée. On peut créer une nouvelle propriété qui stockera cette information. On y accédera dans main.qml et on appellera la fonction newNoteObject().

// PagePanel.qml

...

// Page affichée
property Page currentPage: personalpage

// Création de la liste des états
states: [
// Les états sont crées avec leur propre nom.
State {
name: "personal"
PropertyChanges {
target: personalpage
opacity:1.0
restoreEntryValues: true
}
PropertyChanges {
target: root
currentPage: personalpage
explicit: true
}
},
State {
name: "fun" PropertyChanges {
target: funpage
opacity:1.0
restoreEntryValues: true
}
PropertyChanges {
target: root
currentPage: funpage
explicit: true
}
},
State {
name: "work"
PropertyChanges {
target: workpage
opacity:1.0
restoreEntryValues: true
}
PropertyChanges {
target: root
currentPage: workpage
explicit: true
}
}
]

...

Vous l'aurez remarqué, on doit modifier un peu chaque état (state) pour qu'il modifie la propriété currentPage comme convenu.

Voyons finalement comment appeler la fonction que l'on a définie plus haut dans main.qml.

// main.qml

...
// Utilisation d'une colonne pour les aligner verticalement.
Column {
id: toolbar
spacing: 16
anchors {
top: window.top
left: window.left
bottom: window.bottom
topMargin: 50
bottomMargin: 50
leftMargin: 8
}
// Outil « new note » ou icône « + »
Tool {
id: newNoteTool
source: "images/add.png"
// Utilisation de la propriété currentPage pour appeler
// newNoteObject() sans argument.
onClicked: pagePanel.currentPage.newNoteObject()
}
}
...

IV-A-2 - Suppression des notes

C'est beaucoup plus facile de supprimer une note : en fait, on fait simplement appel à la fonction destroy() du composant que l'on veut supprimer. Vous vous rappelez que l'on avait créé un item container qui contenait toutes les notes ? Eh bien, on peut simplement itérer toute la liste des enfants de cet item et appeler destroy() sur chacun d'entre eux pour tous les supprimer.

On ajoute donc une fonction dans Page qui effectuera cette opération.

// Page.qml

...
// Une fonction JavaScript qui itère à travers les enfants
// et les supprime un par un.
function clear() {
for(var i = 0; i<container.children.length; ++i) {
container.children[i].destroy()
}
}
...

On appelle donc cette fonction lorsque l'utilisateur clique sur le bouton « Clear All ».

// main.qml

...
// Outil « clear »
Tool {
id: clearAllTool
source: "images/clear.png"
onClicked: pagePanel.currentPage.clear()
}
...

Pour supprimer chaque note indépendamment des autres, on doit ajouter un élément Tool qui servira de bouton dans NoteToolbar (on peut réutiliser le même élément qu'avant).

// Note.qml

...
// Création de NoteToolbar
NoteToolbar {
id: toolbar
height: 40
anchors {
top: root.top
left: root.left
right: root.right
}
drag.target: root

// Création du bouton « Delete »
Tool {
id: deleteItem
source: "images/delete.png"
onClicked: root.destroy()
}
}
...

IV-A-3 - Et ensuite ?

On verra comment stocker les notes.

IV-B - Stocker et charger les notes depuis une base de données

On a pu voir jusqu'à présent comment implémenter des fonctionnalités de base pour créer et stocker des notes à la volée.

Dans cette étape, on verra comment les stocker dans une base de données locale. QML propose une API à cette fin : Qt Quick Database, basée sur SQLite.

Rappel : on avait auparavant décidé que l'application chargerait les notes dès son démarrage et les enregistrerait à sa fermeture.

IV-B-1 - Champs de la base de données

La base de données de NoteApp est très simple. Il s'agit en fait d'une seule table, qu'on appellera note et qui contiendra toutes les informations nécessaires.

Champ

Type

Description

noteId

INTEGER (PRIMARY KEY AUTOINCREMENT)

Identifiant unique d'une note

x

INTEGER

Abscisse d'une note

y

INTEGER

Ordonnée d'une note

noteText

TEXT

Texte de la note

markerId

TEXT

Page censée contenir la note.

Regardons ce dont on a besoin et ce que l'on a déjà.

- x et y sont des propriétés que chaque item QML possède. C'est donc bon pour les coordonnées.

- noteText est le texte de la note. On peut le récupérer à partir de l'élément Text de Note. On pourrait créer un autre alias de propriété appelé noteText pour accéder encore plus facilement à cette information ;

- noteId et markerId sont des identifiants. noteId sera unique à chaque note. markerId nous aidera à savoir à quelle page appartient une note.

On définit donc les propriétés manquantes.

// Note.qml

Item {
id: root
width: 200
height: 200

...
property string markerId
property int noteId
property alias noteText: editArea.text
...
}

Page est responsable de la création des notes : de ce fait, il devrait attribuer sa valeur à markeId quand une note est crée par l'utilisateur.

// Page.qml

Item {
id: root

...

// Cette propriété aide à sauvegarder les note dans la base de
// données.
property string markerId ...

// Cette fonction JavaScript sert à créer les notes
// qui ne sont pas chargées depuis la base de données.
// Elle attribue sa valeur à markerId.
function newNote() {
// Appel de la function newNoteObject()
newNoteObject( { "markerId": root.markerId } )
}

...
}

Avant, dans le fichier main.qml, on utilisait la fonction newNoteObject(), mais, comme expliqué plus haut, cette technique ne convient plus totalement. On doit la remplacer avec la fonction newNote() (celle de l'extrait de code ci-dessus).

On doit aussi s'assurer que l'on a attribué une valeur correcte à markerId de Page dans PagePanel.

// PagePanel.qml

Item {
id: root

...
// Création des trois pages.
Page {
id: personalpage
anchors.fill: parent
markerId: "personal"
}
Page {
id: funpage
anchors.fill: parent
markerId: "fun"
} Page { id: workpage; anchors.fill: parent; markerId: "work" } ... }

Voyons maintenant comment charger et stocker les notes.

IV-B-2 - Une bibliothèque « sans état »

On veut créer un unique fichier JavaScript qui contient toutes les fonctions chargées de la sauvegarde et du chargement des notes. Ce fichier agirait comme une interface entre la base de données et l'application QML. Les bibliothèques sans état (« stateless library », en anglais) peuvent être utiles.

Pour créer une bibliothèque JavaScript sans état dans Qt Creator, on commence par créer un nouveau fichier JavaScript (noteDB.js, ici) et on s'assure de cocher « bibliothèque sans état ».

Note : une bibliothèque sans état est une bibliothèque qui contient un ensemble de fonctions prenant une ou plusieurs entrées et renvoyant une sortie, sans jamais directement toucher à un composant QML.

Le fichier noteDB.js devrait apporter ces fonctionnalités :

- ouvrir/créer une instance d'une base de données locale ;

- créer les tables nécessaires de la base de données ;

- lire les notes depuis la base de données ;

- supprimer toutes les notes de la base de données.

On verra au fur et à mesure comment implémenter ces fonctionnalités. Regardons maintenant les fonctions que l'on doit écrire :

- openDB() : crée la base de donnée si elle n'existe pas encore ; sinon, l'ouvre ;

- createNoteTable() : crée la table note si elle n'existe pas encore (cette fonction sera appelée uniquement dans openDB()) ;

- clearNoteTable() : supprime toutes les lignes de la table note ;

- readNotesFromPage(markerId) : lit toutes les notes de la base de données dont la colonne markerId correspond à l'argument markerId et renvoie un dictionnaire ;

- saveNotes(noteItems, markerId) : sert à sauvegarder les notes dans la base de données. noteItems est une liste d'items Note.

On considère que le fichier JavaScript est maintenant écrit et on regarde comment l'utiliser dans les fichiers QML (on l'implémentera plus tard).

Ouvrir la connexion à la base de données dans le fichier main.qml est presque toujours une bonne habitude à prendre. On peut accéder à la fonction openDB() de là : ainsi, la connexion ne sera effectuée qu'une seule fois.

Importons noteDB.js dans main.qml. La question reste de savoir quand appeler openDB(). QML propose les signaux onCompleted() et onDestruction() qui sont émis quand un composant est complètement chargé ou lors de sa destruction (respectivement).

// main.qml

import QtQuick 1.1
import "noteDB.js" as NoteDB

...

// Ce signal est émis quand le composant est complètement chargé.
Component.onCompleted: {
NoteDB.openDB()
}

...

Voici l'implémentation de la fonction openDB(). Elle appelle une autre fonction : openDatabaseSyn() pour créer la base de données. Ensuite, elle appelle la fonction createNoteTable(), évoquée plus haut.

//noteDB.js

...

function openDB() {
print("noteDB.createDB()")
_db = openDatabaseSync("StickyNotesDB","1.0", "The stickynotes Database", 1000000);
createNoteTable();
}

function createNoteTable() {
print("noteDB.createTable()")
_db.transaction( function(tx) {
tx.executeSql(
"CREATE TABLE IF NOT EXISTS note
(noteId INTEGER PRIMARY KEY AUTOINCREMENT,
x INTEGER,
y INTEGER,
noteText TEXT,
markerId TEXT)")
})
}

...

La base de données est maintenant initialisée. On peut charger les items Note dans Page (puisque Page est responsable de leur création). Plus haut, on avait précisé que readNotesFromPage(markerId) devait renvoyer un dictionnaire des notes d'une page spécifiée.

//noteDB.js

...
function readNotesFromPage(markerId) {
print("noteDB.readNotesFromPage() " + markerId)
var noteItems = {}
_db.readTransaction( function(tx) {
var rs = tx.executeSql(
"SELECT * FROM note WHERE markerId=?
ORDER BY markerid DESC", [markerId] )
var item
for (var i = 0; i < rs.rows.length; i++) {
item = rs.rows.item(i)
noteItems[item.noteId] = item
}
})
return noteItems
}

Voici donc le code du fichier Page.qml.

// Page.qml

...

// Appel à loadNotes() quand le composant est chargé.
Component.onCompleted: loadNotes()

// Une fonction qui lit les notes de la base de données function
loadNotes() {
var noteItems = NoteDB.readNotesFromPage(markerId)
for (var i in noteItems) {
newNoteObject(noteItems[i])
}
}

...

On peut voir que la fonction newNoteObject() définie un peu plus haut dans Page.qml prend en argument les valeurs qui correspondent à une ligne de la table (c'est-à-dire x, y, noteText, markerId et noteId).

Remarque : les champs de la table note sont les même que les noms des propriétés de Note. Ceci nous fait gagner du temps en passant directement en argument une ligne du dictionnaire.

Maintenant que le chargement est fait, passons à la suite. On sait que PagePanel crée chacune des pages (qui contiennent les notes). PagePanel devrait donc savoir tout ce qu'il nous faut au moment de la sauvegarde.

//noteDB.js

...

function saveNotes(noteItems, markerId) {
for (var i = 0; i < noteItems.length; ++i) {
var noteItem = noteItems[i]
_db.transaction( function(tx) {
tx.executeSql( "INSERT INTO note
(markerId, x, y, noteText) VALUES(?,?,?,?)",
[markerId, noteItem.x, noteItem.y, noteItem.noteText]);
})
}
}

Pour le coté QML, on doit définir un alias de propriété dans Page pour accéder facilement à toutes les notes (qui sont en fait les enfants de container).

// PagePanel.qml

...

Component.onDestruction: saveNotesToDB()

// Une autre fonction JavaScript qui sauvegarde les notes dans la
// base de données
function saveNotesToDB() {
// Suppression des données précédentes
NoteDB.clearNoteTable();

// Stockage des notes par page
NoteDB.saveNotes(personalpage.notes, personalpage.markerId)
NoteDB.saveNotes(funpage.notes, funpage.markerId)
NoteDB.saveNotes(workpage.notes, workpage.markerId)
}

...

On ne veut pas s'embêter, on supprime donc toutes les données sans se poser de question avant de les récrire une par une. On s'évite ainsi d'ajouter du code qui mettrait la base de données à jour.

Nous voici enfin à la fin de ce chapitre ! L'utilisateur est maintenant capable de créer comme il le souhaite ses notes, tandis que l'application se charge pour lui de les sauvegarder et les charger sans même qu'il s'en rende compte.

IV-B-3 - Et ensuite ?

Dans le prochain chapitre, on verra comment rajouter quelques animations pour rendre l'interface encore plus attrayante.

V - Remerciements

Merci à Digia pour son autorisation à traduire cet article. Il s'agit d'une adaptation en langue française de Qt Quick Application Developer Guide for Desktop, Release 1.0.

Merci à Thibaut Cuvelier, Gurdil le nain et Claude Leloup pour leur relecture !