Plusieurs interfaces, une même logique

| Johan Thelin | traducteur : Louis du Verdier | traducteur : Thibaut Cuvelier | Qt Quarterly |

Depuis la première interface graphique, on apprend que garder le code de l'interface et la logique séparés est la clé d'un bon design. Cependant, il est si fréquent d'échouer...

N'hésitez pas à commenter cet article !
Commentez

- Titre : Plusieurs interfaces, une même logique

- Auteurs : Johan Thelin, traducteur : Louis du Verdier, traducteur : Thibaut Cuvelier, Qt Quarterly

- Parution : 3 septembre 2011

- Mise à jour : 5 septembre 2011

- Licence : Copyright ® 2011 Johan Thelin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Qt Quick

- IV. HTML 5

- V. C++ classique

- VI. Panorama

- VII. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Sensor Bonanza de Geir Vattekar paru dans la Qt Quarterly Issue 35.

Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(-ies), inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Introduction

C'était effectivement le cas lorsque les interfaces utilisateur étaient réalisées en utilisant les mêmes outils et langages que la partie logique. Les développeurs de Qt ont eu bien des choix. Le plus populaire au fil des ans semble avoir été C++ et Python. Tous deux sont des langages.

Récemment, il y a eu du changement dans le département des outils d'interfaçage. Dans l'univers de Qt, Qt Quick est monté sur scène tandis que, dans le monde, HTML 5 étendait les possibilités d'une interface utilisateur basée sur le Web. Bien sûr, Qt supporte ces technologies - et la bonne vieille approche d'un langage.

Le grand changement dans tout cela est que l'on implémente la partie logique dans le langage. Cela force quiconque à séparer cette partie de l'interface utilisateur, rendant la partie logique indépendante de la technologie d'interfaçage utilisée.

Lorsque l'on utilise Qt comme base d'une application, il est merveilleux de constater que la façon dont on connecte la logique et l'interface utilisateur est la même, quelle que soit la technologie. La classe QObject sert de pont naturel. Il fournit une introspection sous forme de signaux, slots, méthodes invocables, propriétés et plus encore. Tout ceci aide à les lier au JavaScript du HTML 5 et au QML de Qt Quick. Tout comme pour ceux qui s'en tiennent aux interfaces utilisateur basées sur C++, un QObject est juste identique à un autre objet.

III - Qt Quick

Une interface utilisateur Qt Quick est construite depuis QML. Tout le code QML est exécuté dans un contexte. Chaque contexte est représenté par un objet QDeclarativeContext. Pour QDeclarativeEngine, il y a un contexte racine qui contient les objets et valeurs globalement disponibles.

Pour exposer un QObject en tant que pièce de code QML, l'objet est défini en tant que valeur d'une propriété contextuelle, comme montré dans le code suivant.

QObject *obj = new MyLogic();
QDeclarativeEngine *engine = ...;
engine->rootContext()->setContextProperty("logic", obj);

Cela expose l'objet au code QML dans lequel on peut y accéder en utilisant le nom logic.

MouseArea {
...
onClicked: { logic.actOnClick(); }
}

La bonne chose dans l'exposition de propriétés contextuelles à QML est qu'elles s'associent bien avec la nature de binding de QML. Cela signifie que, si l'on définit la propriété contextuelle sceneWidth à 400 et qu'on la modifie par la suite à 500 (ou toute autre valeur), toutes les expressions dans QML dépendant de sceneWidth seront automatiquement actualisées.

Pour de plus amples applications, le nombre de propriétés exposées au contexte déclaratif peut s'avérer élevé. L'appui sur setContextProperty dans ce type de scénario mène rapidement à une situation où il est nécessaire de passer du temps pour garantir que toutes les propriétés sont actualisées au bon moment et qu'aucune actualisation de propriété n'est oubliée. Il est possible d'obtenir une base de code où des appels à setContextProperty sont parsemés dans tous les sens. Dans cette situation, un objet contextuel est une meilleure approche.

Un objet contextuel est un QObject mettant à disposition un ensemble de propriétés. Chaque propriété de l'objet est exposée en tant que propriété contextuelle au code QML. Cela signifie qu'il est possible de recueillir toutes les propriétés passées au QML en un seul endroit.

Pour qu'une propriété soit utile au QML, cette dernière doit fournir au moins une méthode READ et un signal NOTIFY. Par exemple, l'exposition de la valeur sceneWidth depuis l'exemple précédent se ferait comme ceci :

class MyProps : public QObject
{
Q_OBJECT
Q_PROPERTY(int sceneWidth READ sceneWidth NOTIFY sceneWidthChanged)
...
};

...

MyProps *myProps = new MyProps();
QDeclarativeEngine *engine = ...;
engine->rootContext()->setPropertyObject(myProps);

Ayant exposé un QObject à travers le contexte déclaratif, ses méthodes invocables deviennent disponibles. Les méthodes invocables sont des slots mais aussi des méthodes marquées Q_INVOKABLE.

class MyLogic : public QObject
{
Q_OBJECT

public:
Q_INVOKABLE setValue(int);
...
};

Cette méthode peut alors être appelée depuis QML.

onSomething : { logic.setValue(42); }

Il est également possible d'invoquer des méthodes QML depuis le C++, d'émettre des signaux depuis QML vers le C++ et, si l'on base la classe C++ sur la classe QDeclarativeItem, d'émettre des signaux depuis le C++ vers le QML. Tout cela permet de réaliser une interface pratique et dirigée par les événements entre l'interface utilisateur QML et la partie logique C++.

IV - HTML 5

Dans quelques situations, Qt Quick se complète avec HTML 5. Utiliser HTML 5 signifie que l'on peut obtenir du code réutilisable si l'on possède une interface utilisateur que l'on souhaite déployer à la fois sur des moteurs Web standard et en tant qu'application personnalisée.

En utilisant le module QtWebKit, il est entièrement possible de réaliser une interface utilisateur autour d'une page Web, c'est-à-dire du code HTML 5. L'intégration de QtWebKit n'inclut pas seulement le rendu HTML et un moteur JavaScript, elle met également en place un pont permettant l'intégration de multiples QObject à l'intérieur de JavaScript.

Lors du rendu des contenus Web, chaque page Web est représentée par un objet QWebPage. Chaque page est alors représentée par une ou plusieurs frames, QWebFrame. Pour chaque frame, un contexte JavaScript existe. Ce contexte est nettoyé lorsque de nouvelles URL sont chargées. Cela résulte de l'émission du signal javaScriptWindowObjectCleared. Lorsqu'il est émis, les objets peuvent être ajoutés au contexte JavaScript par le biais de la méthode addToJavaScriptWindowObject comme montré plus bas. Il est bon de noter que l'on peut accéder à une frame (ou plusieurs) d'une page à travers la mainFrame.

QObject *obj = new MyLogic();
QWebFrame *frame = webPage->mainFrame();
frame->addToJavaScriptWindowObject("logic", obj);

Il n'existe aucun moyen d'exposer les objets contextuels de la même manière que pour l'intégration C++ et QML. Au lieu de cela, chaque objet doit être ajouté en tant qu'objet séparé au contexte.

Il est possible d'accéder et de modifier les propriétés de QObject depuis le JavaScript. On peut également appeler des méthodes depuis le C++ vers le JavaScript et vice-versa, tout comme émettre des signaux et réaliser des connexions depuis les deux camps.

Comparé à l'intégration Qt Quick, il est possible de faire face à quelques différences. Par exemple, le binding de propriétés n'est pas partie intégrante de HTML 5, il est donc nécessaire d'écouter activement les signaux et de réagir en conséquence. De plus, lors de l'émission de signaux depuis le JavaScript, ceux-ci doivent être définis dans une classe C++. Il n'est pas possible d'en définir de nouveaux depuis le JavaScript.

V - C++ classique

C++ a été écrit à de nombreuses reprises dans cet article mais tout cela s'applique également à tout langage tel que Python, Ruby ou tout autre langage avec les bindings Qt.

L'objectif n'est pas d'apprendre à réaliser une interface utilisateur en utilisant le C++ et Qt. Au lieu de cela, pourquoi ne pas regarder comment garantir que l'interface utilisateur et la partie logique soient gardées séparées ?

Lorsque l'on s'appuie sur Qt Quick ou HTML 5, on est forcé de définir une interface entre la partie logique et l'interface utilisateur. Lors de l'utilisation du C++ pour réaliser à la fois la partie logique et l'interface utilisateur, il est nécessaire de placer ces restrictions sur soi-même.

La clé ici est que tout code affectant uniquement l'interface utilisateur fait partie de l'interface utilisateur tandis que tout code affectant l'état de l'application (par exemple, le contenu d'un document) est membre de la partie logique.

La partie logique nécessite d'être maintenue dans un ensemble séparé de classes. Pour réellement se forcer à atteindre cet objectif, un objet de document pour chaque fenêtre de document est une bonne idée. Pour les autres types d'applications, l'objet contextuel en provenance d'une intégration Qt Quick peut être réutilisé.

Dès que tout ceci a été mis en place, un ensemble de classes d'interface utilisateur sont à disposition. Ces classes seront probablement plus ou moins graphiques ou liées aux interactions utilisateur (par exemple, QAction) et comporteront un ensemble d'objets logiques. Dès lors, il faut s'assurer que tout travail non trivial avec la partie logique est géré dans cette dernière. Cela signifie qu'il est bon de nettoyer et d'initialiser la logique en utilisant des appels de nettoyage et d'initialisation séparés du code de l'interface utilisateur. Il n'est, par contre, pas bon d'effectuer la même opération en appelant le nettoyage puis alors une série d'opérations pour initialiser la logique (par exemple, setPen, setBrush, setPageProperties, addBoilerplateText et ainsi de suite).

Comme avec toutes ces choses, il n'y a aucune règle claire lorsque trop de code est dans l'interface utilisateur. Le tout se résume à une question d'équilibre. La règle d'or est que, si l'on trouve de la duplication de code entre les implémentations des interfaces utilisateur, on devrait probablement mettre ce code dans la logique. Mais il n'y a pas de critère strict.

VI - Panorama

Différents utilisateurs attendent différentes interfaces utilisateur. Dans des systèmes multiutilisateurs, ces interfaces pourraient se différencier non seulement par l'apparence et les fonctionnalités, mais encore par la manière dont elles sont implémentées.

Qt supporte des interfaces classiques basées sur des widgets, tout comme des interfaces plus modernes via Qt Quick. En plus de ces deux technologies spécifiques à Qt, HTML5 est aussi supporté, grâce à l'intégration avec WebKit.

En séparant l'application en des parties séparées et réutilisables, on peut utiliser le même code logique pour créer des expériences utilisateur pour tous les utilisateurs - le tout avec Qt.

VII - Divers

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

Merci à Claude Leloup pour sa relecture !