Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

Vous n'avez pas encore de compte Developpez.com ? L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Developpez.com

Qt

Choisissez la catégorie, puis la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Écrire des composants QML : propriétés, méthodes et signaux

Un des concepts-clés en QML est la possibilité de définir vos propres composants QML convenant à votre application. Les éléments QML standard fournissent les composants essentiels à la création d?une application QML ; de plus, vous pouvez écrire vos propres composants personnalisés, pouvant être créés et réutilisés sans utiliser de C++.

Les composants sont les blocs de construction d?un projet QML. Lors de la rédaction d?un projet QML, qu?il soit grand ou petit, il est préférable de séparer le code QML en composants plus petits qui exécutent des ensembles spécifiques d?opérations, plutôt que de créer d'énormes fichiers QML avec de vastes fonctionnalités entremêlées, plus difficiles à gérer et pouvant contenir du code dupliqué.

Définir de nouveaux composants

Un composant est un type réutilisable avec une interface bien définie, construit entièrement en QML. Chaque portion de code QML peut devenir un composant, en plaçant son code dans le fichier  »<Name>.qml », où <Nom> est le nom du nouveau composant, commençant par une lettre majuscule. Ces fichiers QML deviennent automatiquement disponibles en tant que nouveau type d?élément QML pour les autres composants et applications QML dans le même répertoire.

Par exemple, l?un des composants les plus simples et courants que l?on peut construire en QML est un composant de type bouton. Ci-dessous, nous créons ce composant en tant que Rectangle, avec une MouseArea cliquable, dans un fichier nommé Button.qml :

 // Button.qml
 import QtQuick 1.0
 
 Rectangle {
     width: 100; height: 100
     color: "red"
 
     MouseArea {
         anchors.fill: parent
         onClicked: console.log("Button clicked!")
     }
 }

À présent, ce composant peut être réutilisé par n?importe quel fichier du même répertoire. Le fichier étant nommé Button.qml, le composant est appelé Button :

 // application.qml
 import QtQuick 1.0
 
 Column {
     Button { width: 50; height: 50 }
     Button { x: 50; width: 100; height: 50; color: "blue" }
     Button { width: 50; height: 50; radius: 8 }
 }
image

L?objet racine dans Button.qml définit les attributs disponibles pour les utilisateurs du composant Button. Dans ce cas, l?objet racine est un Rectangle, donc toutes les propriétés, méthodes et signaux du Rectangle sont disponibles, autorisant application.qml à personnaliser les propriétés width, height, radius et color des objets Button.

Si Button.qml n?était pas dans le même répertoire, application.qml devrait le charger en tant que module depuis un chemin spécifique du système de fichiers, ou en tant que plug-in. On notera également que la casse du nom du fichier du composant est importante sur certains systèmes de fichiers (en particulier UNIX). La casse du nom du fichier doit de préférence correspondre exactement à la casse du composant QML ? par exemple Box.qml et non BoX.qml - indépendamment de la plateforme sur laquelle QML est déployé.

Pour écrire un composant utile, il est généralement nécessaire de lui adjoindre des attributs personnalisés qui stockent et communiquent des données spécifiques. Ce résultat est obtenu en ajoutant les attributs suivants aux composants :

  • des propriétés accessibles depuis l?extérieur pour modifier un objet (par exemple, Item possède les propriétés width et height), et utilisées par la liaison de propriété ;
  • des méthodes en code JavaScript qui peuvent être appelées depuis l?intérieur ou l?extérieur (par exemple, Animation possède la méthode start()) ;
  • des signaux pour prévenir les autres objets qu?un évènement s?est produit (par exemple, MouseArea possède un signal clicked).

Les sections suivantes montrent comment ces attributs peuvent être ajoutés au composant QML.

Ajouter des propriétés

Une propriété est une valeur d?un composant QML qui peut être lue et modifiée par d?autres objets. Par exemple, le composant Rectangle possède les propriétés width, height et color. Les propriétés utilisent fréquemment la liaison de propriété, et dans ce cas la valeur d?une propriété est automatiquement mise à jour en utilisant la valeur d?une autre propriété.

La syntaxe pour définir une nouvelle propriété est :

 [default] property <type> <name>[: defaultValeur]

La déclaration d?une propriété peut apparaître n?importe où à l?intérieur de la définition du composant QML, mais il est d?usage de la placer au début. Un composant ne peut pas déclarer plus d?une propriété avec le même nom. Il est possible que le nom d?une propriété soit le même que celui d?une propriété existante dans un type, mais ce n?est pas recommandé car la propriété existante serait alors invisible et inaccessible.

Voici un exemple : le composant ImageViewer définit la propriété currentImage, de type string, et de valeur initiale « default-image.png ». Cette propriété est utilisée pour spécifier l?image affichée dans l?objet enfant Image. Un autre fichier, application.qml, peut créer un objet ImageViewer et peut lire ou modifier la valeur de currentImage :

 // ImageViewer.qml
 import QtQuick 1.0
 
 Item {
     id: item
     width: 200; height: 200
 
     property string currentImage: "default-image.png"
 
     Image { source: item.currentImage }
 }
 import QtQuick 1.0
 
 ImageViewer {
     id: viewer
 
     currentImage: "http://qt.nokia.com/logo.png"
 
     Text { text: viewer.currentImage }
 }

La valeur par défaut d?une propriété est facultative. La valeur par défaut est un raccourci pratique, au comportement identique à cette méthode en deux étapes :

 // Utilisation d'une valeur par défaut
 property int myProperty: 10
 
 // Méthode plus longue mais au comportement identique
 property int myProperty
 myProperty: 10

Les types de propriétés pris en charge

Toutes les propriétés QML sont typées. Les exemples précédents montrent des propriétés de types int et string ; il est à noter que le type d?une propriété doit être déclaré. Le type est utilisé pour déterminer le fonctionnement de la propriété, et sa définition en C++.

Un certain nombre de types de propriétés sont pris en charge par défaut. Elles sont listées ci-dessous, avec leurs valeurs par défaut et les types C++ correspondants.

Nom du type QML Valeur par défaut Nom du type C++
int 0 int
bool false bool
double 0.0 double
real 0.0 double
string  » » (empty string) QString
url  » » (empty url) QUrl
color #000000 (black) QColor
date undefined QDateTime
variant undefined QVariant

Les types d?objets QML peuvent également être utilisés en tant que types de propriétés. Ceci comprend les types QML personnalisés implémentés en C++. Ces propriétés sont définies ainsi :

 property Item itemProperty
 property QtObject objectProperty
 property MyCustomType customProperty

Ces propriétés de type objet ont comme valeur par défaut undefined.

Il est également possible de stocker une copie d?un objet JavaScript en utilisant le type de propriété variant. Cela crée des restrictions sur la façon dont la propriété doit être utilisée ; voir la documentation sur le type variant pour les détails.

Les propriétés de type liste sont créées avec la syntaxe list<Type> et leur valeur par défaut est une liste vide :

 property list<Item> listOfItems

Il est à noter que les propriétés de type liste ne peuvent pas être modifiées comme des tableaux JavaScript ordinaires. Voir la documentation sur le type liste pour les détails.

Les signaux de modifications de propriétés

Ajouter une propriété à un élément ajoute automatiquement un gestionnaire de signal valeur modifiée à l?élément. Pour se connecter à ce signal, on utilise un gestionnaire de signal nommé selon la syntaxe on<Propriété>Changed, en utilisant une majuscule pour la première lettre du nom de la propriété.

Par exemple, le gestionnaire de signal onMyNumberChanged suivant est automatiquement appelé dès que la propriété myNumber est modifiée :

 Item {
     property int myNumber
 
     onMyNumberChanged: { console.log("myNumber has changed:", myNumber); }
 
     Component.onCompleted: myNumber = 100
 }

Les propriétés par défaut

L?attribut optionnel default pour une propriété la désigne comme la « propriété par défaut » d'un type. Ceci permet à d?autres éléments de spécifier la valeur de la propriété par défaut directement sous forme d'éléments enfants, sans la nommer. Par exemple, la propriété par défaut de l?élément Item est sa propriété children. Ceci permet de spécifier les enfants d?un Item de cette façon :

 Item {
     Rectangle {}
     Rectangle {}
 }

Si la propriété children n?était pas la propriété par défaut de l?Item, sa valeur aurait dû être spécifiée ainsi :

 Item {
     children: [
         Rectangle {}
         Rectangle {}
     ]
 }

Voir l?exemple TabWidget pour une démonstration de l?utilisation des propriétés par défaut. Spécifier une propriété par défaut écrase toute propriété par défaut existante (par exemple, toute valeur par défaut héritée depuis un élément parent). Utiliser l?attribut default deux fois dans le même bloc de type est une erreur.

Les alias de propriétés

Les alias de propriétés sont une forme plus élaborée de déclaration de propriétés. Contrairement à la définition d?une propriété, qui alloue un nouvel et unique espace mémoire pour la propriété, l?alias d?une propriété crée la propriété nouvellement déclarée (appelée la propriété alias) comme étant une référence directe à la propriété existante (la propriété aliasée). Les opérations de lecture sur la propriété alias agissent comme les opérations de lecture sur la propriété aliasée, et les opérations d?écriture sur la propriété alias agissent comme les opérations d?écriture sur la propriété aliasée.

L?alias sur une propriété ressemble beaucoup à la définition d?une propriété ordinaire.

     [default] property alias <name>: <alias reference>

Étant donné que la propriété alias possède le même type que la propriété aliasée, le type explicite est omis, et le mot-clé « alias » est utilisé. À la place d?une valeur par défaut, une propriété alias doit contenir une référence d?alias. La référence d?alias est utilisée pour localiser la propriété aliasée. Bien que similaire à la liaison de propriété, la syntaxe d?une référence d?alias est très restrictive.

Une référence d?alias prend une des formes suivantes :

     <id>.<property>
     <id>

où <id> doit se référer à un identifiant d?objet dans le même composant que le type déclarant l?alias, et, de manière facultative, <property> doit se référer à la propriété de cet objet.

Dans l?exemple ci-dessous, le composant Button.qml possède la propriété aliasée buttonText qui est connectée à la propriété text de l?objet enfant Text :

 // Button.qml
 import QtQuick 1.0
 
 Item {
     property alias buttonText: textItem.text
 
     width: 200; height: 50
 
     Text { id: textItem }
 }

Le code suivant crée un Button avec une chaîne de caractères texte pour l?objet enfant Text :

 Button { buttonText: "This is a button" }

Ici, la modification de buttonText modifie directement la valeur textItem.text ; cette modification ne change aucune autre valeur que textItem.text.

Dans ce cas, l?utilisation d?une propriété aliasée est essentielle. Si buttonText n?était pas un alias, changer sa valeur n?aurait pas du tout modifié la valeur texte affichée, la liaison de propriété n?étant pas bidirectionnelle : la valeur buttonText aurait été modifiée lors de la modification de textItem.text, mais l?inverse n?aurait pas été vrai.

Les propriétés aliasées sont également importantes pour autoriser les objets externes à modifier et accéder directement aux objets enfants dans ce composant. Par exemple, voici une version du composant ImageViewer vu plus haut sur cette page. La propriété currentImage a été convertie en un alias vers l?objet enfant Image :

 // ImageViewer.qml
 import QtQuick 1.0
 
 Item {
     id: item
     width: 200; height: 200
 
     property alias currentImage: image
 
     Image { id: image }
 }
 // application.qml
 import QtQuick 1.0
 
 ImageViewer {
     id: viewer
 
     currentImage.source: "http://qt.nokia.com/logo.png"
     currentImage.width: width
     currentImage.height: height
     currentImage.fillMode: Image.Tile
 
     Text { text: currentImage.source }
 }

application.qml n?est plus limité à définir la source de l?Image, et peut maintenant accéder et modifier directement l?enfant objet Image et ses propriétés.

Évidemment, exposer ainsi les objets enfants doit être fait avec précaution, car cela permet aux objets externes de les modifier librement. Cependant, cette utilisation des propriétés aliasées peut être très utile dans des situations particulières, comme dans l?exemple TabWidget, où les nouveaux onglets sont réellement apparentés à l?objet enfant qui affiche l?onglet courant.

Quelques considérations sur les alias de propriétés

Les alias sont activés uniquement lorsque la création du composant qui les spécifie est terminée. La conséquence la plus évidente est que le composant lui-même ne peut généralement pas utiliser la propriété aliasée directement pendant la création. Par exemple, ceci ne fonctionnera pas :

     // Ne fonctionne PAS
     property alias buttonText: textItem.text
     buttonText: "Some text" // buttonText n'est pas encore défini

Une seconde conséquence, beaucoup moins significative, de cette activation retardée des alias est qu'une référence d?alias ne peut pas se référer à une autre propriété aliasée déclarée à l?intérieur du même composant. Ceci ne fonctionnera pas :

     // Ne fonctionne PAS
     id: root
     property alias buttonText: textItem.text
     property alias buttonText2: root.buttonText

Au moment où le composant est créé, la valeur buttonText n?a pas encore été affectée, de sorte que root.buttonText se réfère à une valeur indéfinie (cependant, de l?extérieur du composant, la propriété alias apparaît comme une propriété Qt ordinaire, et peut donc être utilisée dans les références d?alias).

Il est possible pour une variable aliasée d?avoir le même nom qu?une variable existante. Par exemple, le composant suivant possède une propriété alias color, avec le même nom que la propriété intégrée Rectangle::color :

 Rectangle {
     property alias color: childRect.color
     color: "red"
 
     Rectangle { id: childRect }
 }

Tout objet qui utilise ce composant et qui fait référence à sa propriété color se référera à l?alias plutôt qu?à la propriété Rectangle::color habituelle. En interne cependant, le rectangle peut affecter la valeur « red » à sa propriété de façon normale et se référer à la propriété effectivement définie plutôt qu?à son alias.

Ajouter des méthodes

Un composant QML peut définir des méthodes en code JavaScript. Ces méthodes peuvent être appelées soit en interne, soit par d?autres objets.

La syntaxe pour définir une méthode est :

 function <name>([<parameter name>[, ...]]) { <body> }

Cette déclaration peut apparaître n?importe où dans le corps du type, mais il est d?usage de l?inclure au début. La tentative de déclarer deux méthodes ou signaux avec le même nom dans un même bloc de type est une erreur. Cependant, une nouvelle méthode peut réutiliser le nom d?une méthode existante dans le type. (Ceci doit être fait avec précaution, la méthode existante étant alors invisible et inaccessible.)

Contrairement aux signaux, le type du paramètre de la méthode n?a pas besoin de déclaration, le type par défaut étant le type variant. Le corps de la méthode est écrit en JavaScript et peut accéder aux paramètres par leur nom.

Voici un exemple de composant avec une méthode say() qui accepte un argument text unique :

 Rectangle {
     id: rect
     width: 100; height: 100
 
     function say(text) {
         console.log("You said: " + text);
     }
 
     MouseArea {
         anchors.fill: parent
         onClicked: rect.say("Mouse clicked")
     }
 }

Une méthode peut être connectée à un signal, de sorte qu?elle soit automatiquement appelée lorsque le signal est émis. Voir connecter des signaux à des méthodes et à d?autres signaux plus bas.

Voir également comment intégrer JavaScript pour plus d'informations sur l'utilisation de JavaScript avec QML.

Ajouter des signaux

Les signaux fournissent un moyen d'informer les autres objets quand un événement se produit. Par exemple, le signal clicked de MouseArea informe les autres objets que la souris a été cliquée à l?intérieur de cette zone.

La syntaxe pour définir un nouveau signal est :

 signal <name>[([<type> <parameter name>[, ...]])]

Cette déclaration peut apparaître n?importe où dans le corps du type, mais il est d?usage de l?inclure au début. La tentative de déclarer deux méthodes ou signaux avec le même nom dans un même bloc de type est une erreur. Cependant, un nouveau signal peut réutiliser le nom d?un signal existant dans le type (ceci doit être fait avec précaution, le signal existant étant alors invisible et inaccessible).

Voici trois exemples de déclarations de signal :

 Item {
     signal clicked
     signal hovered()
     signal performAction(string action, variant actionArgument)
 }

Si le signal ne possède pas de paramètres, les parenthèses  »() » sont facultatives. Si des paramètres sont utilisés, le type des paramètres doit être déclaré, comme par exemple pour les arguments string et variant du signal performAction ci-dessus ; les types de paramètres autorisés sont les mêmes que ceux listés dans la section Ajouter des propriétés de cette page.

Ajouter un signal à un élément ajoute automatiquement un gestionnaire de signal. Le gestionnaire de signal est nommé on<NomDuSignal>, avec la première lettre du signal en majuscule. L?élément de l?exemple ci-dessus possède à présent les gestionnaires de signaux suivants :

  • onClicked ;
  • onHovered ;
  • onPerformAction.

Pour émettre le signal, il suffit de l?appeler de la même manière qu?une méthode. Ci-dessous à gauche, lorsque la MouseArea est cliquée, elle émet le signal buttonClicked du parent en appelant rect.buttonClicked(). Le signal est reçu par application.qml à travers le gestionnaire de signal onButtonClicked :

 // Button.qml
 import QtQuick 1.0
 
 Rectangle {
     id: rect
     width: 100; height: 100
 
     signal buttonClicked
 
     MouseArea {
         anchors.fill: parent
         onClicked: rect.buttonClicked()
     }
 }
 // application.qml
 import QtQuick 1.0
 
 Button {
     width: 100; height: 100
     onButtonClicked: console.log("Mouse was clicked")
 }

Si le signal possède des paramètres, ils sont accessibles par le nom du paramètre dans le gestionnaire de signal. Dans l?exemple ci-dessous, buttonClicked est cette fois émis avec les paramètres xPos et yPos :

 // Button.qml
 Rectangle {
     id: rect
     width: 100; height: 100
 
     signal buttonClicked(int xPos, int yPos)
 
     MouseArea {
         anchors.fill: parent
         onClicked: rect.buttonClicked(mouse.x, mouse.y)
     }
 }
 // application.qml
 Button {
     width: 100; height: 100
     onButtonClicked: {
         console.log("Mouse clicked at " + xPos + "," + yPos)
     }
 }

Connecter des signaux à des méthodes et à d?autres signaux

Les objets signaux possèdent une méthode connect(), qui peut être utilisée pour connecter un signal à une méthode ou un autre signal. Quand un signal est connecté à une méthode, la méthode est automatiquement appelée lorsque le signal est émis (dans la terminologie Qt, la méthode est un slot qui est connecté au signal ; toute méthode définie en QML est créée en tant que slot Qt). Ainsi, une méthode peut recevoir un signal, sans passer par un gestionnaire de signal.

Par exemple, application.qml vue précédemment peut être réécrite ainsi :

 Item {
     id: item
     width: 200; height: 200
 
     function myMethod() {
         console.log("Button was clicked!")
     }
 
     Button {
         id: button
         anchors.fill: parent
         Component.onCompleted: buttonClicked.connect(item.myMethod)
     }
 }

La méthode myMethod() sera appelée dès que le signal buttonClicked sera reçu.

Dans plusieurs cas, il est suffisant de recevoir des signaux à travers des gestionnaires de signaux plutôt qu?en utilisant la fonction connect() ; l?exemple ci-dessus ne fournit aucune amélioration par rapport à l?utilisation d?un simple gestionnaire onButtonClicked. Cependant, si l?on souhaite créer des objets dynamiquement ou intégrer du code JavaScript, alors la méthode connect() est utile. Par exemple, le composant ci-dessous créé trois objets Button dynamiquement et connecte le signal buttonClicked de chaque objet à la fonction myMethod() :

 Item {
     id: item
     width: 300; height: 100
 
     function myMethod() {
         console.log("Button was clicked!")
     }
 
     Row { id: row }
 
     Component.onCompleted: {
         var component = Qt.createComponent("Button.qml")
         for (var i=0; i<3; i++) {
             var button = component.createObject(row)
             button.border.width = 1
             button.buttonClicked.connect(myMethod)
         }
     }
 }

De la même manière, on peut connecter un signal aux méthodes définies dans un objet créé dynamiquement ou connecter un signal à une méthode JavaScript.

Il existe également une méthode disconnect() correspondante pour supprimer des signaux connectés. Le code suivant supprime les connexions créées dans application.qml précédemment :

 // application.qml
 Item {
     ...
 
     function removeSignal() {
         button.clicked.disconnect(item.myMethod)
     }
 }
Transférer des signaux

La méthode connect() peut également connecter un signal à d?autres signaux. Ceci a pour effet de « transférer » un signal : il est automatiquement émis lorsque le signal correspondant est émis. Par exemple, le gestionnaire onClicked de MouseArea dans Button.qml ci-dessus a été remplacé par un appel à connect():

 MouseArea {
     anchors.fill: parent
     Component.onCompleted: clicked.connect(item.buttonClicked)
 }

Dès que le signal clicked de MouseArea est émis, le signal rect.buttonClicked sera également automatiquement émis.

Remerciements

Merci à Lo?c Leguay pour la traduction ainsi qu'à Ilya Diallo, Jonathan Courtois, Thibaut Cuvelier et Claude Leloup 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 © 2020 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, 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 !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -

Partenaire : Hébergement Web