Commencer à programmer en QMLBienvenue 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 utilisateurL'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. 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 :
Création d'un bouton et d'un menuNous 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 qmlviewerd'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. 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.
Créer un menuJusqu'à 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. 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. 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 menusNotre é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éesQML 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. Création d'un éditeur de texteDéclarer un champ de texteNotre é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 texteMaintenant, 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. Décoration de l'éditeur de texteImplé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. 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 QMLNous 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 :
Compiler un plug-in QtPour 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 QMLIn 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 QMLL'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 fichierNotre 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.
Achèvement de cet éditeur de texteCette 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. RemerciementsMerci à 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 ! |