IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Commencer à programmer en QML

Bienvenue dans l'univers du QML, le langage déclaratif d'interfaces utilisateur. Dans ce tutoriel pour débutant, nous allons créer un simple éditeur de texte en QML. Une fois que vous aurez fini de le lire, vous devriez pouvoir développer vos propres applications en QML et Qt/C++.

  

QML pour créer des interfaces utilisateur

L'application que nous allons développer est un éditeur de texte basique qui permettra le chargement, la sauvegarde et la manipulation de texte. Ce tutoriel se présente en deux parties. La première partie est consacrée à l'agencement et au comportement des éléments de l'interface avec le langage déclaratif QML, la seconde abordera l'implémentation en Qt/C++ des méthodes de chargement et de sauvegarde de fichiers. Grâce au système de métaobjets de Qt, les éléments QML peuvent utiliser les méthodes C++ comme des propriétés. En combinant QML et Qt C++, nous pouvons séparer efficacement la logique de l'interface de celle de l'application.

image Pour exécuter le code QML de notre exemple, ajoutez simplement l'outil qmlviewer en argument du fichier QML. La présence de code C++ dans ce tutoriel suppose que le lecteur possède les connaissances de base pour les procédures de compilation avec Qt.

Chapitres du tutoriel :

  1. Création d'un bouton et d'un menu ;
  2. Implémentation d'une barre de menu ;
  3. Création d'un éditeur de texte ;
  4. Décoration de l'éditeur de texte ;
  5. Couplage du QML avec Qt/C++.

Création d'un bouton et d'un menu

Nous commençons notre éditeur de texte par la création d'un bouton. Un bouton peut être représenté par une zone de sensibilité à la souris et un label. Les boutons déclenchent des actions lorsqu'ils sont pressés.

En QML, l'élément graphique de base est le Rectangle qui possède des propriétés permettant de contrôler son apparence et sa position.

import Qt 4.7
Rectangle {
    id: simplebutton
    color: "grey"
    width: 150; height: 75
 
    Text{
        id: buttonLabel
        anchors.centerIn: parent
        text: "button label"
    }
}

Tout d'abord, l'import de Qt 4.7 permet à l'outil

qmlviewer

d'importer les éléments QML que nous allons utiliser. Cette ligne doit exister dans tout fichier QML. Remarquez que la version des modules Qt est précisée dans l'état d'importation.

Ce simple rectangle a un unique identifiant, simplebutton, qui est attribué à la propriété id. On associe des valeurs aux propriétés du Rectangle en faisant suivre la propriété de deux points puis de sa valeur. Dans le code, la couleur grey (gris) est attribuée à la propriété color du rectangle. De la même façon, nous associons des valeurs aux propriétés width (largeur) et height (hauteur) du rectangle.

L'élément Text n'est pas un champ de texte modifiable. Nous allons appeler cet élément Text buttonLabel. Pour changer le contenu du champ Text, nous attribuons une valeur à la propriété text. Le label est contenu dans le rectangle et, pour le centrer, nous attribuons la valeur parent (ici simplebutton) à la propriété anchors (ancrage) de l'élément Text. La propriété anchors peut être associée à la propriété anchors d'autres éléments, permettant ainsi une plus simple définition de l'agencement.

Nous devrions maintenant enregistrer ce code dans un fichier SimpleButton.qml. L'exécution du fichier avec le qmlviewer en argument affichera le rectangle gris avec le label.

image Pour implémenter l'action lors du clic du bouton, nous pouvons utiliser QML et sa gestion d'événements. La gestion d'événements en QML ressemble beaucoup au processus de signaux et de slots de Qt. Les signaux sont émis et le slot connecté est appelé.

 Rectangle{
     id:simplebutton
     ...
 
     MouseArea{
         id: buttonMouseArea
 
         anchors.fill: parent //anchor all sides of the mouse area to the rectangle's anchors
                 //onClicked handles valid mouse button clicks
         onClicked: console.log(buttonLabel.text + " clicked" )
     }
 }

Nous incluons un élément MouseArea dans notre simplebutton. L'élémentMouseArea décrit la zone interactive dans laquelle les mouvements de la souris sont détectés. Pour notre bouton, nous ancrons entièrement l'élément MouseArea à son parent simplebutton. La syntaxe anchors.fill est une façon d'accéder à la propriété spécifique appelée fill à l'intérieur d'un groupe de propriétés appelé anchors. QML utilise des agencements fondés sur les ancrages où les éléments sont liés à d'autres éléments, créant ainsi une architecture solide.

L'élément MouseArea possède de nombreux signaux qui sont appelés lorsque la souris est en mouvement à l'intérieur de la zone qu'il délimite. L'un d'entre eux est onClicked et est appelé à chaque fois que le bouton considéré est pressé, le clic gauche étant le choix par défaut. Nous pouvons associer une action à l'événement onClicked. Dans notre exemple, console.log() affiche du texte à chaque clic dans la zone. La fonction console.log() est très utile pour afficher du texte et déboguer un programme.

Le code de SimpleButton.qml est suffisant pour afficher un bouton à l'écran et du texte à chaque clic de la souris.

 Rectangle {
     id:Button
     ...
 
     property color buttonColor: "lightblue"
     property color onHoverColor: "gold"
     property color borderColor: "white"
 
     signal buttonClick()
     onButtonClick: {
         console.log(buttonLabel.text + " clicked" )
     }
 
     MouseArea{
         onClicked: buttonClick()
         hoverEnabled: true
         onEntered: parent.border.color = onHoverColor
         onExited:  parent.border.color = borderColor
     }
 
     //determines the color of the button by using the conditional operator
     color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
 }

Un bouton complètement fonctionnel est disponible dans Button.qml. Les bouts de code présents dans cet article peuvent être incomplets, elliptiques, car ils ont soit été introduits avant dans les sections précédentes, soit inutiles pour la discussion du code actuel.

Les propriétés personnalisées sont déclarées en utilisant la syntaxe property type name. Dans le code, la propriété buttonColor, de type color, est déclarée et associée à la valeur « lightblue ». La propriété buttonColor est utilisée plus tard dans une opération conditionnelle pour déterminer la couleur de fond du bouton. Remarquez que, en plus d'associer une valeur à une propriété en utilisant le caractère :, il est possible d'attribuer une valeur avec le signe égal =. Les propriétés personnalisées permettent d'accéder aux éléments en dehors même du champ de définition du rectangle. Similairement aux types int, string ou real, il existe des types basiques en QML comme variant.

En associant des couleurs aux signaux onEntered et onExited, la bordure du bouton deviendra jaune lorsque la souris le survolera et virera au blanc lorsque celle-ci quittera la zone.

Un signal buttonClick() est déclaré dans Button.qml en plaçant le mot clé signal juste avant le nom du signal. Tous les signaux ont leur événement créé automatiquement, leur nom commençant par on. De cette façon, onButtonClick correspond à l'événement de buttonClick. onButtonClick définit alors une action à réaliser. Dans notre exemple, l'événement souris onClicked appelera simplement onButtonClick, qui affichera un texte. onButtonClick permet aux objets extérieurs d'accéder plus facilement à la zone. Par exemple, un élément peut contenir plusieurs MouseArea et un signal buttonClick peut ainsi mieux faire la distinction entre les différents événements des MouseArea.

Nous avons maintenant les bases pour implémenter des éléments en langage QML qui déclenchent des événements souris basiques. Nous avons créé un label Text dans un Rectangle, personnalisé ses propriétés et implémenté les actions déclenchées par les mouvements de la souris. Cette idée d'imbriquer des éléments dans d'autres éléments est reproduite tout au long de l'éditeur de texte.

Ce bouton n'a pas une grande utilité si ce n'est de déclencher une action. Dans la prochaine section, nous allons créer un menu contenant plusieurs de ces boutons.

image 

Créer un menu

Jusqu'à maintenant, nous avons vu comment créer des éléments et leur assigner des comportements avec un unique fichier QML. Dans cette partie, nous verrons comment importer des éléments QML existants et comment les réutiliser pour en faire de nouveaux.

Les menus affichent les éléments d'une liste, chacun de ces éléments ayant un action assignée. En QML, nous pouvons créer un menu de différentes façons. Premièrement, nous créons un menu contenant des boutons qui effectueront différentes actions. Le code de ce menu est dans FileMenu.qml.

import Qt 4.7                \\import the main Qt QML module
import "folderName"            \\import the contents of the folder
import "script.js" as Script        \\import a Javascript file and name it as Script

La syntaxe ci-dessus montre comment utiliser le mot-clé import. Il permet d'utiliser des fichiers JavaScript ou des fichiers QML qui ne sont pas dans le même dossier. Puisque Button.qml est dans le même dossier que FileMenu.qml, nous n'avons pas besoin d'importer le fichier Button.qml pour l'utiliser. Nous pouvons directement créer un Button en déclarant Button{}, comme la déclaration Rectangle{}.

 In FileMenu.qml:
 
     Row{
         anchors.centerIn: parent
         spacing: parent.width/6
 
         Button{
             id: loadButton
             buttonColor: "lightgrey"
             label: "Load"
         }
         Button{
             buttonColor: "grey"
             id: saveButton
             label: "Save"
         }
         Button{
             id: exitButton
             label: "Exit"
             buttonColor: "darkgrey"
 
             onButtonClick: Qt.quit()
         }
     }

Dans FileMenu.qml, nous déclarons trois Button. Ils sont déclarés dans un Row, un élément qui permet de positionner ses enfants dans une colonne verticale. La déclaration de Button est dans Button.qml, le même que nous avons utilisé précédemment. De nouvelles valeurs peuvent être assignées aux propriétés existantes dans les nouveaux boutons, remplaçant celles définies dans Button.qml. Le bouton exitButton quittera l'application et fermera la fenêtre quand il sera cliqué. Notez le gestionnaire de signaux onButtonClick dans Button.qml en plus de celui de exitButton.

image

L'élément Row est déclaré dans un Rectangle, créant un rectangle qui contient la colonne pour les boutons. Ce rectangle additionnel représente une façon indirecte d'organiser la colonne de boutons dans un menu.

La déclaration du menu d'édition est similaire. Le menu possède des boutons Copy, Paste et Select All.

image

Armé de notre connaissance sur l'importation et la modification d'éléments existants, nous pouvons maintenant combiner ces deux menus pour faire une barre de menus, soit deux boutons pour sélectionner le menu et regarder comment structurer nos données avec QML.

Implémenter une barre de menus

Notre éditeur de texte a besoin d'une façon d'afficher les menus avec une barre de menus. La barre de menus permettra de passer d'un menu à un autre et l'utilisateur pourra choisir celui qu'il veut afficher. Le changement de menus implique plus de structure qu'un simple affichage des différents menus dans une colonne. QML utilise des modèles et des vues pour structurer les données et les afficher.

Utiliser des modèles et vues de données

QML a différentes vues de données qui affichent des modèles de données. Notre barre de menus affichera les menus dans une liste, avec un en-tête qui affichera les noms des différents menus. La liste des menus est déclarée dans un VisualItemModel. Le modèle qml-visualitemmodel contient des éléments qui ont déjà des vues graphiques comme les Rectangle et autres éléments graphiques. D'autres modèles comme le modèle qml-listmodel ont besoin d'un autre composant pour afficher leurs données.

Nous déclarons deux éléments graphiques dans le menuListModel, le FileMenu et le EditMenu. Nous modifions les deux menus et les affichons avec une ListView. Le fichier Menubar.qml contient les déclarations QML et le fichier EditMenu.qml un menu d'édition basique.

     VisualItemModel{
         id: menuListModel
         FileMenu{
             width: menuListView.width
             height: menuBar.height
             color: fileColor
         }
         EditMenu{
             color: editColor
             width:  menuListView.width
             height: menuBar.height
         }
     }

Notre ListView affichera le modèle grâce à un délégué. Ce délégué déclarera les éléments du modèle dans une colonne Row ou les affichera dans une grille. Notre menuListModel a déjà des éléments graphiques, nous n'avons donc pas besoin de délégué.

     ListView{
         id: menuListView
 
         //Anchors are set to react to window anchors
         anchors.fill:parent
         anchors.bottom: parent.bottom
         width:parent.width
         height: parent.height
 
         //the model contains the data
         model: menuListModel
 
         //control the movement of the menu switching
         snapMode: ListView.SnapOneItem
         orientation: ListView.Horizontal
         boundsBehavior: Flickable.StopAtBounds
         flickDeceleration: 5000
         highlightFollowsCurrentItem: true
         highlightMoveDuration:240
         highlightRangeMode: ListView.StrictlyEnforceRange
     }

De plus, ListView hérite de qml-flickable et permet de faire réagir la liste aux déplacements (drag, c'est-à-dire avec le clic maintenu) de la souris. La dernière portion du code ci-dessus définit les propriétés Flickable pour créer le mouvement de changement de menu voulu. Particulièrement, la propriété highlightMoveDuration change la durée de ce mouvement. Une valeur plus grande rendra le changement de menu plus long.

La ListView garde les éléments du modèles avec un index et chaque élément est accessible avec son index, dans l'ordre de déclaration. Changer le currentIndex (l'index actuel) change l'élément mis en valeur dans la ListView. L'en-tête de notre menu rend compte de cet effet. Il y a deux boutons, qui change le menu affiché lorsqu'ils sont cliqués. Le bouton fileButton change le menu actuel au menu fichier, l'index étant   car FileMenu est déclaré en premier dans menuListModel. De la même façon, le bouton editButton change le menu actuel pour le menu d'édition EditMenu.

Le labelList rectangle a un z de valeur 1, qui permet son affichage devant la barre de menu. Les éléments avec les plus grands z sont devant ceux avec les plus petits z. Le z par défaut est  .

     Rectangle{
         id: labelList
         ...
         z: 1
         Row{
             anchors.centerIn: parent
             spacing:40
             Button{
                 label: "File"
                 id: fileButton
                 ...
                 onButtonClick: menuListView.currentIndex = 0
             }
             Button{
                 id: editButton
                 label: "Edit"
                 ...
                 onButtonClick:    menuListView.currentIndex = 1
             }
         }
     }

La barre de menus que nous avons créée peut être tirée pour changer de menu (ou en cliquant sur le nom du menu en haut). Le changement de menu est intuitif et réactif.

image

Création d'un éditeur de texte

Déclarer un champ de texte

Notre éditeur de texte n'en est pas un s'il n'a pas de champ de texte éditable. L'élément QML TextEdit permet la déclaration d'un champ de texte multi-lignes éditable. Un TextEdit est différent d'un simple Text, qui ne permet pas à l'utilisateur d'éditer le texte.

     TextEdit{
         id: textEditor
         anchors.fill:parent
         width:parent.width; height:parent.height
         color:"midnightblue" 
         focus: true
 
         wrapMode: TextEdit.Wrap
 
         onCursorRectangleChanged: flickArea.ensureVisible(cursorRectangle)
     }

L'éditeur a sa propre propriété de couleur de police pour le texte. L'aire du TextEdit est à l'intérieur d'une aire avec une barre de défilement qui fera défiler le texte si le curseur d'écriture sort de l'aire visible. La fonction ensureVisible() regarde si le curseur de texte est en dehors de l'aire visible et déplace l'aire de texte pour qu'il soit à nouveau visible. QML utilise la syntaxe Javascript pour ses scripts et, comme dit plus haut, des fichiers Javascript peuvent être importés dans un fichier QML.

     function ensureVisible(r){
         if (contentX >= r.x)
             contentX = r.x;
         else if (contentX+width <= r.x+r.width)
             contentX = r.x+r.width-width;
         if (contentY >= r.y)
             contentY = r.y;
         else if (contentY+height <= r.y+r.height)
             contentY = r.y+r.height-height;
     }

Combiner les éléments pour l'éditeur de texte

Maintenant, nous sommes prêts à créer la disposition des éléments de notre éditeur de texte avec QML. L'éditeur de texte a deux éléments, la barre de menus que nous avons créée et le champ de texte. QML nous permet de réutiliser des éléments, ce qui rend notre code plus simple, en important des éléments et en les modifiant si nécessaire. Notre éditeur de texte est séparé en deux parties ; un tiers est utilisé pour afficher la barre de menus et les deux autres pour le champ de texte. La barre de menus est affichée devant tous les autres éléments.

     Rectangle{
 
         id: screen
         width: 1000; height: 1000
 
         //the screen is partitioned into the MenuBar and TextArea. 1/3 of the screen is assigned to the MenuBar
         property int partition: height/3
 
         MenuBar{
             id:menuBar
             height: partition
             width:parent.width
             z: 1
         }
 
         TextArea{
             id:textArea
             anchors.bottom:parent.bottom
             y: partition
             color: "white"
             height: partition*2
             width:parent.width
         }
     }

En important des éléments existants, notre code de TextEditor est plus simple. Nous pouvons modifier l'application sans nous préoccuper des propriétés aux comportements que nous avons déjà définis. Avec cette approche, l'organisation de l'application et les éléments graphiques sont réalisés plus facilement.

image

Décoration de l'éditeur de texte

Implémentation d'une interface "tiroir"

Notre éditeur de texte est graphiquement simple et nous devons le décorer. Avec QML, nous pouvons déclarer des transitions et animer notre éditeur. Notre barre de menus occupe un tiers de l'écran, il serait bien qu'elle n'apparaisse qu'en temps voulu.

Nous pouvons ajouter une interface « tiroir », qui réduira ou agrandira la barre de menus. Dans notre implémentation, nous avons un rectangle fin qui réagit au clic de souris. Le drawer, comme l'application, a deux états : l'état « tiroir ouvert » et l'état « tiroir fermé ». Le drawer est une bande rectangulaire avec une petite hauteur. Il y a un élément Image qui déclare qu'une icône de type flèche sera centré dans le « tiroir ». Le tiroir assigne un état à l'application, avec l'identifiant screen, lorsque l'on clique à cet endroit.

     Rectangle{
         id:drawer
         height:15
 
         Image{
             id: arrowIcon
             source: "images/arrow.png"
             anchors.horizontalCenter: parent.horizontalCenter
         }
 
         MouseArea{
             id: drawerMouseArea
             anchors.fill:parent
             onClicked:{
                 if (screen.state == "DRAWER_CLOSED"){
                     screen.state = "DRAWER_OPEN"
                 }
                 else if (screen.state == "DRAWER_OPEN"){
                     screen.state = "DRAWER_CLOSED"
                 }
             }
             ...
         }
     }

Un état est simplement une collection de configurations et est déclaré dans un State. Une liste d'états peut être faite et liée à la propriété states. Dans notre application, les deux états sont DRAWER_CLOSED (fermé) et DRAWER_OPEN (ouvert). Les configurations des éléments sont déclarés dans un élément PropertyChanges. Dans l'état DRAWER_OPEN, il y a quatre éléments qui seront modifiés. Le premier est menuBar, qui changera sa propriété y à  . De la même façon, la textArea aura une position plus basse lorsque l'état sera DRAWER_OPEN. La textArea, le drawer et l'icône du « tiroir » annulerons les modifications faites à leurs propriétés pour aller à l'état actuel.

     states:[
         State {
             name: "DRAWER_OPEN"
             PropertyChanges { target: menuBar; y: 0}
             PropertyChanges { target: textArea; y: partition + drawer.height}
             PropertyChanges { target: drawer; y: partition}
             PropertyChanges { target: arrowIcon; rotation: 180}
         },
         State {
             name: "DRAWER_CLOSED"
             PropertyChanges { target: menuBar; y:-height; }
             PropertyChanges { target: textArea; y: drawer.height; height: screen.height - drawer.height }
             PropertyChanges { target: drawer; y: 0 }
             PropertyChanges { target: arrowIcon; rotation: 0 }
         }
     ]

Le changement d'état est direct et a besoin de transitions. Les transitions entre les états sont définis avec l'élément Transition, qui lui même sera lié aux propriétés transitions de l'élément. Notre éditeur de texte a un changement d'état chaque fois que l'état est changé en DRAWER_OPEN ou en DRAWER_CLOSE. La transition doit avoir un état d'origine from et un état d'arrivée to mais, dans notre cas, nous utiliserons * pour dire que la transition s'applique à n'importe quel changement d'état.

Pendant les transitions, nous pouvons assigner des animations aux changements des propriétés. Notre menuBar change de position de y:0 à y:-partition et nous pouvons animer cette transition avec un élément NumberAnimation. Nous déclarons que les propriétés de la cible s'animeront pendant un certain temps et suivant une certaine courbe. Une courbe permet de contrôler le rythme de l'animation et le comportement d'interpolation pendant le changement d'état. Nous choisissons la courbe Easing.OutQuint, qui ralentit le mouvement vers la fin. Veuillez lire l'article sur l'animation avec QML pour plus d'informations.

     transitions: [
         Transition {
             to: "*"
             NumberAnimation { target: textArea; properties: "y, height"; duration: 100; easing.type:Easing.OutExpo }
             NumberAnimation { target: menuBar; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
             NumberAnimation { target: drawer; properties: "y"; duration: 100; easing.type: Easing.OutExpo }
         }
     ]

Un autre moyen d'animer un changement de valeur de propriété est d'utiliser un élément Behavior (comportement). Une transition ne marche que pendant un changement d'état et un Behavior permet de créer une animation pour un changement de valeur de propriété général. Dans notre éditeur de texte, la flèche a une NumberAnimation qui anime sa rotation à chaque fois que la valeur change.

 In TextEditor.qml:
 
     Behavior{
         NumberAnimation{property: "rotation";easing.type: Easing.OutExpo }
     }

Revenons à notre éditeur avec ces connaissances sur l'animation et les états. Nous pouvons améliorer l'apparence des ses composants. Dans le fichier Button.qml, nous pouvons ajouter un changement de valeur des propriétés color et scale lorsque le bouton est cliqué. Les couleurs sont animées avec ColorAnimation et les nombres avec NumberAnimation. La syntaxe on propertyName ci-dessous est utile pour cibler une seule propriété.

 In Button.qml:
     ...
 
     color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
     Behavior on color { ColorAnimation{ duration: 55} }
 
     scale: buttonMouseArea.pressed ? 1.1 : 1.00
     Behavior on scale { NumberAnimation{ duration: 55} }

De plus, nous pouvons améliorer l'apparence de nos composants QML en ajoutant des effets de couleurs comme les effets de dégradé et d'opacité. Déclarer une élément Gradient modifiera la propriété color. Vous pouvez déclarer une couleur dans un dégradé avec l'élément GradientStop. Le dégradé est positionné avec une échelle de 0.0 à 1.0.

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

Le dégradé est utilisé par la barre de menus pour afficher un dégradé simulant la profondeur. La première couleur commence à 0.0 et la dernière est à 1.0.

Et maintenant quoi ?

Nous avons finalisé l'interface utilisateur d'un éditeur de texte simple. À partir de maintenant, l'interface est complète et nous pouvons implémenter les fonctionnalités de l'application avec Qt et le C++. QML fonctionne très bien en tant qu'outil de prototypage, en séparant l'interface de la logique de l'application.

image

Couplage du QML avec Qt C++

Maintenant que nous avons l'interface graphique de notre éditeur de texte, nous pouvons implémenter ses fonctionnalités en C++. L'utilisation de QML avec le C++ permet de créer la logique de l'application avec Qt. Nous pouvons créer un contexte QML dans une application en C++ avec les classes déclaratives de Qt et afficher des éléments QML avec une zone graphique. Alternativement, nous pouvons exporter notre code C++ dans un plug-in que qmlviewer peut lire. Pour notre application, nous devrions implémenter les fonctions de sauvegarde et chargement de fichiers en C++ et les exporter en tant que plug-in. De cette façon, nous devrons juste charger le fichier QML au lieu de lancer un exécutable.

Utiliser les classes C++ avec QML

Nous allons implémenter les fonctions de sauvegarde et de chargement de fichier avec Qt en C++. Les classes et fonctions de C++ peuvent être utilisées en QML en les enregistrant. La classe doit aussi être compilée en tant que plug-in Qt et le fichier QML doit savoir où est ce plug-in.

Pour notre application, nous devrons créer les éléments suivants :

  1. La classe Directory qui s'occupera des opérations relatives aux dossiers ;
  2. La classe File, qui sera un QObject, simulant la liste des fichiers dans un dossier ;
  3. La classe du plug-in, qui enregistrera les classes auprès de QML ;
  4. Un fichier projet Qt, qui compilera le plug-in ;
  5. Un fichier qmldir, qui dira au qmlviewer où trouver le plug-in.

Compiler un plug-in Qt

Pour compiler un plug-in, nous avons besoin de définir plusieurs choses dans le fichier projet. Premièrement, les sources, en-têtes et modules Qt requis pour le projet. Tout le code C++ et les fichiers du projet sont dans le dossier filedialog.

 In cppPlugins.pro:
 
     TEMPLATE = lib
     CONFIG += qt plugin
     QT += declarative
 
     DESTDIR +=  ../plugins
     OBJECTS_DIR = tmp
     MOC_DIR = tmp
 
     TARGET = FileDialog
 
     HEADERS +=     directory.h \
             file.h \
             dialogPlugin.h
 
     SOURCES +=    directory.cpp \
             file.cpp \
             dialogPlugin.cpp

En particulier, nous compilons Qt avec le module declarative et configurons le projet en tant que plugin, ayant besoin d'un template lib. Le fichier compilé sera placé dans le dossier parent à plugins.

Enregistrer une classe dans QML

 In dialogPlugin.h:
 
     #include <QtDeclarative/QDeclarativeExtensionPlugin>
 
     class DialogPlugin : public QDeclarativeExtensionPlugin
     {
         Q_OBJECT
 
         public:
         void registerTypes(const char *uri);
 
     };

Notre classe de plug-in, DialogPlugin, est une classe fille de QDeclarativeExtensionPlugin. Nous devons implémenter la fonction héritée registerTypes(). Le fichier dialogPlugin.cpp est le suivant :

     #include "dialogPlugin.h"
     #include "directory.h"
     #include "file.h"
     #include <QtDeclarative/qdeclarative.h>
 
     void DialogPlugin::registerTypes(const char *uri){
 
         qmlRegisterType<Directory>(uri, 1, 0, "Directory");
         qmlRegisterType<File>(uri, 1, 0,"File");
     }
 
     Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);

La fonction registerTypes() enregistre nos classes File et Directory dans QML. Cette fonction doit avoir le nom de la classe pour son template, une version majeure, une version mineure et un nom pour nos classes.

Nous devons exporter le plug-in avec la macro Q_EXPORT_PLUGIN2. Notez que dans notre fichier dialogPlugin.h, nous avons la macro Q_OBJECT au début de notre classe. Du coup, nous devons utiliser qmake sur le projet pour générer le code méta-objet nécessaire.

Création des propriétés QML dans une classe C++

Nous pouvons créer des éléments et propriétés QML en C++ avec le système de méta-objets de Qt. Nous pouvons implémenter les propriétés avec des slots et des signaux, rendant Qt « au courant » de ces propriétés. Ces propriétés peuvent ensuite être utilisées en QML.

Pour l'éditeur de texte, nous devons être capable de charger et de sauvegarder des fichiers. D'habitude, ces fonctionnalités sont utilisées dans des boîtes de dialogue de fichiers. Heureusement, nous pouvons utiliser QDir, QFile et QTextStream pour implémenter la lecture de dossiers et la lecture/écriture des fichiers.

     class Directory : public QObject{
 
         Q_OBJECT
 
         Q_PROPERTY(int filesCount READ filesCount CONSTANT)
         Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
         Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
         Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )
 
         ...

La classe Directory utilise le système de méta-objets de Qt pour enregistrer les propriétés requises pour faire la gestion des fichiers. La classe Directory est exportée en tant que plug-in et est utilisable dans QML en tant que l'élément Directory. Chacune des propriétés listées avec la macro Q_PROPERTY est une propriété QML.

La macro Q_PROPERTY déclare une propriété ainsi que ses fonctions de lecture et écriture dans le système de méta-objets. Par exemple, la propriété filename, de type QString, est lisible avec la fonction filename() et éditable avec la fonction setFilename(). De plus, il y a un signal assigné à cette propriété nommé filenameChanged(), qui est émis lorsque la valeur est changée. Les fonctions de lecture et d'écriture sont déclarées dans le fichier en-tête en tant que fonctions public.

De la même façon, nous avons d'autres propriétés déclarées selon leurs utilisations. La propriété filesCount indique le nombre de fichiers dans un dossier. La propriété filename vaut le nom du fichier sélectionné et le contenu sauvegardé/chargé est sauvegardé dans la propriété fileContent.

     Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

La propriété liste files est une liste de tous les fichiers filtrés dans un dossier. La classe Directory est implémentée pour filtrer les fichiers textes invalides ; seuls les fichiers ayant l'extension .txt sont valides. Plus loin, des QList peuvent être utilisées dans des fichiers QML, en les déclarant en tant que QDeclarativeListProperty en C++. Dans la classe Directory, la liste d'objets File est mise dans une QList nommée m_fileList.

     class File : public QObject{
 
         Q_OBJECT
         Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
 
         ...
     };

Les propriétés peuvent ensuite être utilisées en QML comme les propriétés de l'élément Directory. Notez que nous n'avons pas d'identifiant id dans le code C++.

     Directory{
         id: directory
 
         filesCount
         filename
         fileContent
         files
 
         files[0].name
     }

Puisque QML utilise la syntaxe et la structure de Javascript, nous pouvons utiliser la liste des fichiers avec un itérateur. Pour retrouver le nom du premier fichier, il suffit de faire files[0].name.

Les fonctions normales en C++ sont aussi accessible depuis QML. Les fonctions de chargement et de sauvegarde sont implémentés en C++ et déclarées avec la macro Q_INVOKABLE. Alternativement, nous pouvons déclarer les fonctions en tant que slot et elles seront accessibles depuis QML.

 In Directory.h:
 
     Q_INVOKABLE void saveFile();
     Q_INVOKABLE void loadFile();

La classes Directory doit aussi notifier les autres objets lorsque le contenu du dossier change. Cette fonctionnalité est implémentée par un signal. Comme mentionné précédemment, les signaux QML ont un gestionnaire correspondant avec le préfixe on. Le signal directoryChanged est appelé et est émis lorsque le dossier est rafraîchi. Le rafraîchissement recharge simplement le contenu du dossier et met à jour la liste de fichiers valides dans le dossier. Les éléments QML peuvent ensuite être notifiés en liant une action au gestionnaire de signal onDirectoryChanged.

Les propriétés list doivent être approfondies, car les propriétés de listes utilisent des callbacks pour accéder et modifier le contenu de la liste. La propriété liste est de type QDeclarativeListProperty<File>. Lorsqu'on accède à la liste, l'accesseur retourne une QDeclarativeListProperty<File>. Le type de template, File, doit dériver de QObject. De plus, pour créer une QDeclarativeListProperty, l'accesseur et la fonction modifiant la liste doivent être passés au constructeur en tant que pointeurs de fonction. La liste, une QList ici, doit aussi être une liste de pointeurs vers File.

Le constructeur de QDeclarativeListProperty et l'implémentation de Directory :

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

Le constructeur passe des pointeurs vers toutes les fonctions qui vont ajouter des éléments à la liste, compter le nombre d'éléments, retrouver un élément avec un index et vider la liste. Seule la fonction qui ajoute des éléments est obligatoire. Notez que les pointeurs de fonctions doivent correspondre aux définitions de AppendFunction, CountFunction, AtFunction ou ClearFunction.

     void appendFiles(QDeclarativeListProperty<File> * property, File * file)
     File* fileAt(QDeclarativeListProperty<File> * property, int index)
     int filesSize(QDeclarativeListProperty<File> * property)
     void clearFilesPtr(QDeclarativeListProperty<File> *property)

Pour simplifier notre boîte de dialogue de fichiers, la classe Directory filtre les fichiers textes invalides, qui sont ceux n'ayant pas pour extension .txt. Si un fichier n'a pas cette extension, il ne sera pas affiché. De plus, l'implémentation fait en sorte que les fichiers sauvegardés aient pour extension .txt. Directory utilise QTextStream pour lire le fichier et écrire le contenu dans le fichier.

Avec notre élément Directory, nous pouvons retrouver les fichiers en tant que liste, savoir combien de fichiers textes sont dans le répertoire de l'application, récupérer le nom du fichier et son contenu dans une chaîne et être notifié quand le contenu du dossier change.

Pour compiler le plug-in, lancer qmake avec le fichier projet cppPlugins.pro, puis lancer make ou nmake pour compiler et transférer le plug-in dans le dossier plugins.

Importer un plug-in dans QML

L'outil qmlviewer importe les fichiers qui sont dans le même dossier que l'application. Nous pouvons aussi créer un fichier qmldir contenant les chemins des fichiers QML que nous souhaitons importer. Le fichier qmldir peut aussi contenir les chemins vers les plug-ins et autres ressources.

 In qmldir:
 
     Button ./Button.qml
     FileDialog ./FileDialog.qml
     TextArea ./TextArea.qml
     TextEditor ./TextEditor.qml
     EditMenu ./EditMenu.qml
 
     plugin FileDialog plugins

Le plug-in que nous venons de créer est appelé FileDialog, comme indiqué dans le champ TARGET du fichier projet. Le plug-in compilé est dans le dossier plugins.

Intégrer une boîte de dialogue de fichiers dans le menu fichier

Notre FileMenu a besoin d'afficher l'élément FileDialog, qui contient une liste de fichiers textes dans un dossier permettant à l'utilisateur d'en sélectionner un en cliquant dessus. Nous devons aussi assigner les boutons save, load et new à leur action respective. Le FileMenu contient un champ de texte éditable qui permet à l'utilisateur d'écrire le nom du fichier avec son clavier.

L'élément Directory est utilisé dans le fichier FileMenu.qml et informe l'élément FileDialog lorsque le contenu du dossier change. Cette notification est faite avec le gestionnaire de signal onDirectoryChanged.

 In FileMenu.qml:
 
     Directory{
         id:directory
         filename: textInput.text
         onDirectoryChanged: fileDialog.notifyRefresh()
     }

Pour garder notre application simple, la fenêtre des fichiers sera toujours visible et n'affichera pas les fichiers textes invalides, qui n'ont pas pour extension .txt.

 In FileDialog.qml:
 
     signal notifyRefresh()
     onNotifyRefresh: dirView.model = directory.files

L'élément FileDialog affichera le contenu du dossier en lisant sa propriété liste nommée files. Les fichiers sont affichés avec le modèle GridView, qui affiche les éléments dans une grille. Son délégué gérera l'apparence du modèle et notre fenêtre de fichiers créera simplement une grille avec du texte centré. Cliquer sur un nom de fichier produira l'affichage d'un rectangle pour mettre en valeur le nom du fichier. Le FileDialog est notifié à chaque fois que le signal notifyRefresh est émis, rechargeant les fichiers du dossier.

 In FileMenu.qml:
 
     Button{
         id: newButton
         label: "New"
         onButtonClick:{
             textArea.textContent = ""
         }
     }
     Button{
         id: loadButton
         label: "Load"
         onButtonClick:{
             directory.filename = textInput.text
             directory.loadFile()
             textArea.textContent = directory.fileContent
         }
     }
     Button{
         id: saveButton
         label: "Save"
         onButtonClick:{
             directory.fileContent = textArea.textContent
             directory.filename = textInput.text
             directory.saveFile()
         }
     }
     Button{
         id: exitButton
         label: "Exit"
         onButtonClick:{
             Qt.quit()
         }
     }

Notre FileMenu peut maintenant connecter les actions respectives. Le bouton saveButton transfèrera le texte depuis le TextEdit dans la propriété fileContent du dossier, puis copiera le nom du fichier depuis le champ de texte éditable. Enfin, le bouton appellera la fonction saveFile(), sauvegardant le fichier. Le bouton loadButton a un exécution similaire. De plus, l'action New videra de son contenu le TextEdit.

Ci-après, les boutons du menu EditMenu sont connectés aux fonctions Copier, Coller et Sélectionner tout du TextEdit.

image 

Achèvement de cet éditeur de texte

image

Cette application peut servir d'éditeur de texte simple, capable d'accepter du texte et de le sauvegarder dans un fichier. L'éditeur de texte peut aussi charger un fichier et faire des manipulations de texte.

Remerciements

Merci à Maxime Spriet et Paul Musti?re pour la traduction, ainsi qu'à Thibaut Cuvelier et Jacques Thery pour leur relecture !

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2024 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !