Cette article tente de décrire les différents moyens d'exposer un type enum écrit en langage C++ dans le contexte du langage descriptif QML (Qt Modeling Language) de Qt. Travailler avec des types enums en QML présente de nombreux avantages :
- une lecture explicite du code basée sur des types nommés plutôt que sur des chiffres sans signification précise.
- une comparaison optimisée basée sur des valeurs entières plutôt que sur des chaînes de caractères.
- le type énuméré est borné à un ensemble de valeurs constantes.
- l'auto-complétion disponible dans Qt Creator, pour faciliter le choix de l'enum en QML.
Illustration avec un simple exemple
Voici un simple code qui montre l'usage de type enum en QML. Ce code permet de créer des ventilateurs (Fan) qui possèdent trois caractéristiques : une vitesse (speed), une taille (size) et un état (state). Ces trois propriétés sont traitées chacune à l'aide d'un type enum importé grâce à l'instruction "import FanImport 1.0".
Main.qml
import QtQuick 2.0 import FanImport 1.0 Rectangle { width: 360 height: 100 Row { spacing: 2 Fan { speed : FanSpeed.Slow; size : FanSize.Big } Fan { speed : FanSpeed.Medium; state : FanState.On } Fan { speed : FanSpeed.Fast; size: FanSize.Small } } }
Le résultat c'est trois ventilateurs de différentes tailles dont celui du milieu est actif (non visible assurément dans l'image ci dessous, car le ventilateur du milieu est censé tourner à une vitesse moyenne).
Fan.qml : le code du composant Fan dont il est fait référence dans le code QML prédent.
import QtQuick 2.0 import FanImport 1.0 Fan { id: fan width: 100 height: 100 property int speed : FanSpeed.Medium property int size : FanSize.Medium property int state : FanState.Off Timer { interval: 100; running: fan.state === FanState.On ? true : false; repeat: true; onTriggered: { if (fan.speed === FanSpeed.Slow) fanText.rotation += 10 else if (fan.speed === FanSpeed.Medium) fanText.rotation += 25 else if (fan.speed === FanSpeed.Fast) fanText.rotation += 50 } } Image { id: fanText source: "qrc:///qml/images/fan.jpg" anchors.centerIn: parent width: 20 * (fan.size+1) height: 20 * (fan.size+1) } MouseArea { anchors.fill: parent onClicked: fan.state === FanState.Off ? fan.state = FanState.On : fan.state = FanState.Off } }
Rendre accessible en QML un type enum défini en C++
Le type énuméré en C/C++
En C/C++ les types énumérés sont équivalents à des entiers, dont le premier de la liste prend comme valeur entière par défaut 0, le second 1 et ainsi de suite.class FanState { enum StateType {On, Off}; }; class FanSpeed { enum Speed {Slow, Medium, Fast}; }; class Size { enum Size {Small, Medium, Big}; };
Dans l'exemple ci dessus, chaque type énuméré est créé dans une classe distincte, mais ce n'est pas obligatoire. En C++, il est courant de définir plusieurs types énumérés dans la même classe. L'inconvénient de cette pratique c'est qu'il n'est pas possible de déclarer deux types énumérés avec la même valeur (en l'occurence Medium). Le compilateur remonte une erreur de type : "redeclaration of enum" :
Il y a d'autres avantages a créer une classe à part, et pour des raisons de lisibilité, c'est la façon qui a été adopté dans cette présentation. Après c'est un peu à l'appréciation de chacun et en fonction du contexte.
Enregistrer l'enum dans les meta-données d'un QObject
Pour rendre visible le type enum dans un contexte QML, on doit :
- hériter de la classe
QObject
. - renseigner la macro
Q_ENUMS(...)
avec le nom du type énuméré. Cette macro va permettre d'enrichir les informations du meta-object. - ne pas oublier d'appeler la macro
Q_OBJECT
, nécessaire à la génération des méta-données.
class State : public QObject { Q_OBJECT Q_ENUMS(StateType) public: enum StateType {On, Off}; };
Enregistrer le Type Enum dans le contexte QML
Avant de créer la QQuickView qui va interpréter le QML, chaque type énuméré doit être enregistré à l'aide de la fonction template qmlRegisterType.int qmlRegisterType<T>(const char * uri, int versionMajor, int versionMinor, const char * qmlName)
T
: le template est renseigné avec le type énuméré que l'on souhaite exporter dans le QML.uri
: représente le nom de la librairie que l'on va importer dans l'entête du fichier QML.versionMajor, versionMinor
: assignent une version à la librairie importée, de façon à maintenir l'évolution de la librairieqmlName
: le dernier paramètre, c'est le nom du type enum dans le contexte QML. Généralement, et pour simplifier, c'est le même nom que le type enum définit en C++.
main.cpp : enregistrement des enums à destination du contexte QML
int main(int argc, char *argv[]) { QApplication *app = new QApplication(argc, argv); qmlRegisterType<FanSize>("FanImport", 1, 0, "FanSize"); qmlRegisterType<FanSpeed>("FanImport", 1, 0, "FanSpeed"); qmlRegisterType<FanState>("FanImport", 1, 0, "FanState"); QQuickView *view = new QQuickView(); view->setSource(QUrl("qrc:///qml/Fans/main.qml")); view->show(); return app->exec(); }
Utiliser les enum dans le QML
Pour accéder aux types énumérés, il faut d'abord importer la librairie grâce à la commande "import" au début du fichier. Il faudra également préciser la version que l'on souhaite utiliser.
import FanImport 1.0
A partir de maintenant, la complétion devient disponible dans l'éditeur QML de QtCreator. C'est une aide précieuse qui nous rappelle à tout moment quels sont les types enum disponibles. En QML, accéder à une des valeurs de l'enum ne se fait pas comme en C++, il faut utiliser la notation pointée.
property int speed : FanSpeed.Medium property int size : FanSize.Medium property int state : FanState.Off
Attention: contrairement au C++, en QML il n'y a pas de vérification de la cohérence des types énumérés. L'interpréteur QML considère que c'est une variable de type entière (int) et il est très facile d'écrire du code faux d'un point de vue sémantique, mais vrai d'un point de vue syntaxique :
fan.size = FanState.Off;
Echanger des enum entre le C++ et le QML
Accéder à un enum depuis une Q_PROPERTY
Quand un objet C++ est exposé dans le contexte QML, les propriétés marquées avec la macro
Q_PROPERTY
sont accessibles comme si c'était des données membres. Certaines de ces propriétés peuvent être de type enum.class Fan : public QObject { Q_OBJECT Q_PROPERTY(FanSpeed::Speed speed READ speed WRITE setSpeed NOTIFY speedChanged) public: explicit Fan(QObject *parent = 0) : QObject(parent), m_speed(FanSpeed::Medium) {} FanSpeed::Speed speed() const { return m_speed; } void setSpeed(FanSpeed::Speed value) { if (m_speed != value) { m_speed = value; emit speedChanged(value); } } signals: void speedChanged(FanSpeed::Speed arg); private: FanSpeed::Speed m_speed; };
Une classe Fan doit être instanciée en C++, puis injectée dans le contexte QML grâce à la commande
setContextProperty
.Fan MyFan; QQuickView *view = new QQuickView(); view->rootContext()->setContextProperty("MyFan",&MyFan);
Coté QML, on crée un composant Fan qui va prendre comme paramètre la propriété speed de la classe C++ injectée dans le contexte. Ensuite, au niveau du composant slider, on change la valeur de cet objet, ce qui aura pour conséquence d'accélérer ou de réduire la vitesse du ventilateur.
import QtQuick 2.1 import QtQuick.Controls 1.1 import FanImport 1.0 Rectangle { width: 200; height: 200 Fan { speed : MyFan.speed } Slider { id: slider anchors.bottom: parent.bottom anchors.left: parent.left minimumValue: FanSpeed.Slow maximumValue: FanSpeed.Fast value: FanSpeed.Medium onValueChanged: MyFan.speed = slider.value } }
Appeler une méthode C++ avec un paramètre enum depuis le QML
Pour appeler une méthode C++ depuis le QML, celle-ci doit être précédée du mot clé
Q_INVOKABLE
.Q_INVOKABLE void setEcoSpeed(FanSpeed::Speed value) ;
Fan { speed : MyFan.speed } Button { anchors.bottom: parent.bottom anchors.right: parent.right height: 20 text: "Set Eco Speed" onClicked: { MyFan.setEcoSpeed(MyFan.speed); } }
Dans l'état, l'appel à la méthode ne fonctionnera pas et l'erreur suivante sera retournée au moment du runtime :
qrc:///qml/Fans/main.qml:23: Error: Unknown method parameter type: FanSpeed::Speed
L'interpréteur QML ne reconnaissant pas ce nouveau type préfixé par le nom de sa classe d'origine, il faut forcer explicitement sa reconnaissance en appelant la fonction qRegisterMetaType.
Dans l'exemple, il faut donc rajouter en C++ l'appel à cette méthode :
qRegisterMetaType<FanSpeed::Speed>("FanSpeed::Speed");
Attention : du fait de la non vérification du typage en QML, il est possible de transmettre une valeur qui ne correspond à aucune valeur connue. Dans ce cas, il faut être prudent coté C++, et veiller à vérifier la valeur de l'enum transmise avant de le traiter.
onClicked: { MyFan.setEcoSpeed(17); //cette valeur n'existe pas! }
Instancier un type enum directement depuis le QML
La méthodeqmlRegisterType
autorise l'instanciation de l'objet enregistré directement en QML. C'est pratique quand la classe propose des méthodes préfixées comme étant Q_INVOKABLE
(c'est à dire qu'on peut les appeler directement depuis le QML).Dans l'exemple ci dessous, la méthode
toQString
convertit la valeur de l'enum en chaîne de caractères. Elle est définit en tant que Q_INVOKABLE
dans le code de l'enum FanSpeed
class FanSpeed : public QObject { Q_OBJECT Q_ENUMS(Speed) public: enum Speed {Slow, Medium, Fast}; Q_INVOKABLE QString toQString(FanSpeed::Speed speed) const { switch (speed) { case Slow: return "Slow"; case Medium: return "Medium"; case Fast: return "Fast"; } return ""; } };
Pour appeler cette méthode, un composant QML doit être instancié au préalable avec un identifiant unique définit dans la propriété
id
. Ensuite, il suffit d'appeler la méthode toQString
en faisant référence à l'id de ce composant.FanSpeed { id: fanSpeed } Text { text: fanSpeed.toQString(MyFan.speed) }
Fin de l'article.
Aucun commentaire:
Enregistrer un commentaire