I. Article original

Le blog KDAB est rédigé par les ingénieurs de KDAB s'occupant des formations, de la consultance ainsi que du développement (de Qt et de produits additionnels). Vous pouvez trouver les versions originales.

Cet article est une traduction de l'article original écrit par Thomas McGuire paru le 28 juin 2013.

Cet article est une traduction de l'un des articles en anglais écrits par KDAB. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à KDAB.

II. Récapitulatif

Le premier article de cette série s'intéressait au chargement des fichiers par le moteur QML. Une notion fondamentale est que chaque élément d'un fichier QML est soutenu par une classe C++ et que chaque assignation d'une propriété correspond à un Q_PROPERTY de cette classe C++. Jetons de nouveau un œil à cet exemple :

 
Sélectionnez
Rectangle {
    id: rectangle
    width: 300
    height: 300
    anchors.centerIn: parent
    color: "blue"
    radius: 25
    MouseArea {
        id: mouseArea
        anchors.fill: parent
    }
}

L'élément Rectangle correspond à la classe C++ QQuickRectangle, dans laquelle radius et color sont des Q_PROPERTY. Les propriétés width et height sont des Q_PROPERTY de la classe de base, QQuickItem. Il en va de même pour MouseArea qui est représenté par la classe C++ QquickMouseArea.

III. Des Q_PROPERTY partout ?

Complexifions légèrement l'exemple en coloriant le rectangle en rouge lors de l'appui du bouton de la souris :

 
Sélectionnez
states: State {
    when: mouseArea.pressed
    PropertyChanges {
        target: rectangle
        color: "red"
    }
}

Toujours la même histoire, pas vrai ? L'élément PropertyChanges est représenté par la classe QQuickPropertyChanges. En regardant le fichier d'en-tête, nous nous apercevons que la propriété target a bien une Q_PROPERTY correspondante. Concernant la propriété color  ? Aucune Q_PROPERTY à l'horizon pour cette propriété…

IV. Des analyseurs auxiliaires

En regardant de plus près les fichiers d'en-tête, nous pouvons déceler un indice concernant la propriété color : à la fin du fichier, il y a une autre classe nommée QQuickPropertyChangesParser qui hérite de QQmlCustomParser.

Les classes QML peuvent être associées à des analyseurs auxiliaires qui prennent en charge toutes les propriétés inconnues. Dans notre exemple, la propriété target de QQuickPropertyChanges est une Q_PROPERTY, elle est donc traitée normalement. Par contre, la propriété color n'étant pas une Q_PROPERTY, elle sera donc prise en charge par l'analyseur auxiliaire.

Pour comprendre le fonctionnement des analyseurs auxiliaires, revoyons ensemble comment se déroule le chargement d'un fichier QML, explicité dans le premier article. Le chargement d'un fichier QML se déroule en deux étapes.

  1. La compilation
    Le fichier QML est analysé puis compilé ce qui aboutit à un objet QQmlCompiledData qui, pour faire simple, contient une liste d'instructions à exécuter lors de l'instanciation dudit fichier. Il contient, en plus de cela, des données binaires en sus des instructions. Référez-vous au précédent article si vous voulez voir à quoi ressemblent ces instructions.
  2. La génération
    Chaque fois qu'un fichier QML est instancié, le moteur QML regarde les instructions stockées dans l'instance de QQmlCompiledData pour le fichier en question puis les exécute dans une machine virtuelle.

Les analyseurs auxiliaires sont appelés à la fois lors de la compilation et de la génération.

  1. Dans la phase de compilation, QQmlCustomParser::compile() est appelée.
    Elle reçoit comme argument la liste de toutes les propriétés inconnues du moteur QML, liste accompagnée du membre droit de chaque assignation à des propriétés, qui peuvent être de simples valeurs, des objets ou encore le script d'une liaison. Un peu de débogage confirme que la propriété color est donnée comme argument à l'analyseur auxiliaire :

     
    Sélectionnez
    for (const auto &prop : props) {
        qDebug() << prop.name();
        for (const QVariant &var : prop.assignedValues()) {
            const QQmlScript::Variant qmlVar = var.value();
            qDebug() << "	 " << qmlVar.asString();
        }
    }
    
  2. La sortie est :

     
    Sélectionnez
    "color"
    	&#160;&#160;"red"
    
  3. L'analyseur auxiliaire retournera un blob binaire sous la forme d'un QByteArray contenant toutes les informations nécessaires à la réussite de la phase de génération. Ce tableau de bits est stocké dans le QQmlCompiledData spécifique au fichier QML.

  4. Dans le cas de PropertyChanges, l'analyseur se contente de sérialiser tel quel le paramètre dans le tableau de bits.
    Durant la phase de génération, QQuickPropertyChangesParser::setCustomData() est appelée.
    Lorsque la VME crée un objet qui se trouve associé à un analyseur auxiliaire, il récupère le QByteArray généré plus tôt avec QQmlCustomParser::compile() depuis le QQmlCompiledData et le donne en paramètre à la méthode setCustomData() de l'analyseur. Puisque c'est l'analyseur qui a généré le QByteArray, il pourra sans mal interpréter son contenu.
    Dans le cas de l'analyseur auxiliaire pour l'élément PropertyChanges, ce premier s'attarde simplement sur les données de l'instance de QQuickPropertyChanges. Après cela, QQuickPropertyChangesPrivate::decode() traite effectivement les données : il les désérialise. Pour faire simple, l'analyseur crée une liste d'objets de type ExpressionChange. Ces changements d'expression sont traités lorsque la modification de la propriété est effectuée. L'opération la plus fréquente est la création d'une nouvelle liaison temporaire tant que la modification de la propriété est active.

Comment le compilateur QML et la VME connaissent-ils l'analyseur associé à chaque objet ? Ces informations sont enregistrées avec une surcharge de qmlRegisterCustomType() qui prend un analyseur auxiliaire en argument. L'élément modifiant la propriété est enregistré dans QQuickUtilModule::defineModule() si vous souhaitez y jeter un œil.

QQmlCustomParser ne fait pas partie de l'API publique de Qt, il vous sera donc difficilement possible d'écrire vos propres analyseurs.

V. Résumé

Afin de supporter des propriétés arbitraires dans les éléments QML, au lieu d'utiliser les habituelles Q_PROPERTY, un analyseur auxiliaire est utilisé. Ces analyseurs reçoivent une liste de toutes les propriétés inconnues et font ensuite ce qu'ils veulent de ces dernières. À l'instar du chargement des fichiers QML, il y a deux étapes : la compilation puis la génération. Lors de la compilation, l'analyseur crée un QByteArray, stocké sous la forme de données binaires pour le fichier en question. Ce QByteArray est ensuite donné à l'analyseur durant la phase de génération afin que ce dernier traite effectivement les données.

VI. Pensées autour de ces analyseurs auxiliaires

Cet analyseur auxiliaire pour l'élément PropertyChanges est relativement pratique, rien que la possibilité d'écrire color: “red” est fort pratique. Encore mieux, vous pouvez facilement lister de multiples propriétés :

 
Sélectionnez
PropertyChanges {
    target: rectangle
    color: "red"
    radius: 10
}

Regardons maintenant le code qu'il faut écrire lors de l'utilisation d'une PropertyAction  :

 
Sélectionnez
PropertyAction { target: rectangle; property: "color"; value: "red" }
PropertyAction { target: rectangle; property: "radius"; value: 10 }

Étant donné que PropertyAction n'a pas d'analyseur auxiliaire, la propriété doit donc être spécifiée en tant que chaîne de caractères dans la Q_PROPERTY nommée disons color et radius, vous aurez besoin de deux éléments PropertyAction.

Cette nécessité est une incohérence de l'API : dans un cas, il est possible d'utiliser la belle syntaxe et, dans l'autre, cela est impossible. C'est assez déroutant, surtout si l'on n'est pas au courant de l'existence des analyseurs auxiliaires.

Un autre exemple est l'élément ListElement, possédant lui aussi son analyseur auxiliaire. Étant donné que ses propriétés sont traitées par un analyseur auxiliaire, elles ne se comportent pas comme des propriétés normales. Cela peut mener à des comportements étranges, voire à des bogues comme QTBUG-16289.

Le dernier exemple d'analyseur auxiliaire est l'élément Connections, utilisé lors de la connexion à des signaux émis par des objet exporté depuis du code C++ :

 
Sélectionnez
Connections {
    target: _screenController
    onClearAddress: address.text = ""
}

D'un côté, les analyseurs auxiliaires embellissent la syntaxe de QML et facilitent son utilisation ; de l'autre, il subsiste des incohérences, des bogues et l'implémentation nécessite énormément de lignes de code.

VII. Remerciements

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !

Je tiens à remercier Thibaut Cuvelier pour ses conseils ainsi que Claude Leloup pour sa relecture.