I. L'article original

Cet article est une adaptation en langue française de Qt Quick Application Developer Guide for Desktop, Release 1.0.

II. À propos de ce guide

II-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.

Image non disponible
Une capture d'écran de NoteApp.

II-B. Et ensuite ?

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

III. 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.

III-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.

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

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

Image non disponible

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.

III-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.

III-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.

III-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.

III-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.

III-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.

III-B-2-a. Et ensuite ?

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

III-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.

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

 
Sélectionnez
// 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.

 
Sélectionnez
// 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).

III-C-2. Page

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

 
Sélectionnez
// 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 :

Image non disponible

III-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.

 
Sélectionnez
// Marker.qml

import QtQuick 1.1

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

III-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.

III-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.

 
Sélectionnez
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 :

 
Sélectionnez
// 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 :

Image non disponible

III-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.

III-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.

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

 
Sélectionnez
// 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 :

 
Sélectionnez
// 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 :

Image non disponible

III-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é :

 
Sélectionnez
// 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.

 
Sélectionnez
// 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.

III-E-2-a. Et ensuite ?

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

IV. 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.

IV-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.

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

 
Sélectionnez
// 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.

IV-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.

 
Sélectionnez
// 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 :

Image non disponible

Voici à quoi ressemble le code de PagePanel :

 
Sélectionnez
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.

IV-A-2-a. Et ensuite ?

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

IV-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) :

 
Sélectionnez
// 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.

Image non disponible

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.

 
Sélectionnez
// 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é :

 
Sélectionnez
// 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.

 
Sélectionnez
// 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.

IV-B-1. Et ensuite ?

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

IV-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.

IV-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.

 
Sélectionnez
// 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.

 
Sélectionnez
// 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.

 
Sélectionnez
// 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é :

 
Sélectionnez
// 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 :

Image non disponible

IV-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).

 
Sélectionnez
// 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.

 
Sélectionnez
// 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.

Image non disponible

IV-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.

V. Remerciements

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