Qt Widgets
Objectifs
Ce module contient toutes les classes nécessaires à la création et au garnissage des fenêtres et des boîtes de dialogue des applications créées par Qt. Les composants ainsi définis ont un lien très fort avec le module Qt Gui pour le rendu des fenêtres, des polices de caractères et des images par exemple. Avant la version 5 de Qt, les deux modules ne faisaient qu’un.
C’est en particulier dans ce module que s’expriment toutes les qualités de Qt car les interfaces que vous créez sont véritablement multiplateformes et n’ont pas besoin d’être retouchées pour fonctionner selon votre conception initiale sur les systèmes PC compatibles avec Qt.
Il introduit cependant un certain nombre de notions et de concepts qu’il est nécessaire de connaître afin de bien maîtriser l’utilisation des différents outils de création et de compilation.
La première notion concerne le modèle objet :
Toutes les classes de composants graphiques, des fenêtres aux boutons et libellés, héritent de la classe QWidget. Celle-ci hérite bien entendu de QObject et possède donc toutes les fonctionnalités permettant d’envoyer et de recevoir des signaux. C’est d’ailleurs la seule manière recommandée pour interagir avec les éléments...
Intégration
Le module Qt Widgets est intégré à votre projet grâce à l’ajout du mot-clé widgets dans la déclaration QT de votre fichier .pro.
QT += widgets
Flux d’application et de GUI
Dans le chapitre Les fondations de Qt - Le modèle event-driven, nous avons abordé la notion de flux qui pourrait être représenté par un tuyau dans lequel s’écoulent les commandes de votre programme. Si l’on ne prend pas garde à la manière dont on agit sur le flux, il peut se produire des blocages dans l’exécution de certaines fonctions. La plupart du temps, ces blocages ne sont pas sensibles, cependant en présence d’une interface graphique ils sont immédiatement visibles. L’exemple le plus souvent rencontré est celui de la mise à jour d’une barre de progression par une boucle for :
for( int i = 0 ; i < 100 ; i++ )
{
uiControleur->changeValeurBarreProgression(i) ;
}
Le résultat sera très décevant car la barre de progression ne progressera pas en temps réel, mais passera directement à 100 à la sortie de la boucle :
Si vous réalisez ce test sur différents systèmes d’exploitation, le résultat sera différent. Sous Windows, la progression est fluide alors que sur macOS elle ne l’est pas. Cela ne signifie qu’une seule chose pour un concepteur d’applications : une application qui ne fonctionne pas de façon identique sur deux appareils...
Modèle objet et Vue-Modèle
Tous les composants d’interface graphique intégrables dans une fenêtre ou un dialogue héritent de la classe QWidget. Les autres héritent de la classe QObject directement.
La classe QWidget hérite à la fois de la classe QObject, qui lui confère la capacité d’envoyer et de recevoir des signaux ainsi que d’être traduisible, et de la classe QPaintDevice du module QtGui, qui lui donne la capacité à obtenir un rendu en 2D. La classe QWidget ne comporte quant à elle aucune forme, ni dimension, ni rendu. En revanche les classes qui en héritent, comme la classe QPushButton, possèdent un rendu et des dimensions.
En résumé, tous les composants d’interface graphique héritant de la classe QWidget, y compris ceux que vous créerez, auront les propriétés suivantes :
-
émet et reçoit des signaux,
-
est traduisible,
-
possède un rendu en 2D,
-
possède une origine (x,y) et des dimensions (hauteur, largeur),
-
possède de zéro à plusieurs actions liées à des événements de GUI.
Le module Qt Widgets ne possède pas que des composants visibles héritant de QWidget. Elle possède aussi des composants « virtuels » qui sont utilisés pour la mise en page des fenêtres : les layouts (QBoxLayout, QFormLayout, QGridLayout et QStackedLayout). Ces composants sont indispensables à la création des interfaces multiplateformes qui s’adaptent à tous les types d’écran.
1. Vue-Modèle
Vue-Modèle est un paradigme utilisé dans Qt pour séparer de son contenu la représentation graphique d’un composant. Par exemple, un tableau est un élément graphique du type QTableView, qui possède des colonnes, des lignes, avec ou sans bordure, des en-têtes, etc. Ce tableau, une fois inséré dans une fenêtre, apparaîtra vide. Si on le relie à un modèle héritant du type QAbstractTableModel il sera automatiquement rempli et affichera les bonnes valeurs et l’apparence souhaitée pour chaque ligne/colonne/cellule.
Ce modèle est utilisé pour tous les éléments graphiques complexes du type tableau...
Créer avec Qt Designer
Qt Designer est disponible en tant qu’application seule aussi bien qu’en tant que module dans Qt Creator. Pour entrer dans le mode "conception d’interface", vous devez cliquer sur le bouton Design de la barre des boutons située à la gauche de la fenêtre de Qt Creator. |
Avant de pouvoir accéder à ce module, vous devez créer un fichier d’interface graphique :
Cliquez dans le menu Fichier sur Nouveau fichier ou projet....
Dans la fenêtre, choisissez la catégorie de Fichiers et classes : Qt et Classe d’interface graphique Qt Designer qui vous permettra de créer à la fois la vue et le contrôleur.
Vous devez à présent choisir un modèle. Vous disposez de cinq modèles intégrés : trois pour les boîtes de dialogue, un modèle de fenêtre principale et un modèle de composant réutilisable personnalisé.
Choisissez Dialog with Buttons Bottom.
Enfin, il reste à donner un nom à la classe d’interface graphique ainsi qu’au contrôleur :
Dans ce dialogue, vous définissez le nom de votre classe de contrôleur. Ensuite, les fichiers d’en-tête et source de votre contrôleur et enfin le nom du fichier d’interface. Ce dernier sera le fichier XML qui codera l’interface pour le designer, il sera transformé en fichier source ultérieurement.
Qt Creator crée automatiquement trois fichiers :
-
Dialogue.ui : fichier XML à l’usage de Qt Creator codant la vue.
-
DialogueControleur.h : fichier d’en-tête du contrôleur DialogueControleur.
-
DialogueControleur.cpp : fichier de code du contrôleur.
C’est donc par l’intermédiaire de la classe DialogueControleur que vous agirez sur les éléments de votre dialogue.
1. Organisation du designer
La fenêtre de conception est organisée en cinq parties :
-
À gauche : les composants à glisser-déposer dans votre interface pour l’enrichir.
-
Au milieu-haut : l’aperçu en temps réel de votre interface.
-
En haut et à droite : l’arborescence des objets de votre interface.
-
En bas et à droite :...
Créer « à la main »
La création d’une interface graphique est rendue très simple et intuitive par le module Qt Designer, cependant il est parfois utile de modifier l’interface en codant les éléments qu’elle contient. Imaginons par exemple un programme qui lirait un fichier XML décrivant les éléments de l’interface et que celle-ci doive être construite au fur et à mesure du parcours de ce fichier. Il est impossible dans ce cas de la créer par avance avec Qt Designer et de la compiler, elle sera créée à la volée.
Voici donc comment procéder pour créer une interface graphique « à la main ».
Nous considérons que vous disposez d’une boîte de dialogue créée avec Qt Designer, celle-ci sera compilée dans votre application et servira de base à l’ajout des composants à la volée.
Une boîte de dialogue contient un layout par défaut qui n’est pas initialisé. Si vous essayez d’ajouter des éléments dans ce layout, vous aurez une exception de pointeur nul.
Tout se déroulera dans le constructeur. La première chose à faire est de définir le layout de la boîte de dialogue. Ensuite, vous instancierez et définirez les éléments successivement...
Modes SDI et MDI
Les modes SDI et MDI correspondent à deux façons d’organiser les fenêtres d’une application multifenêtrée.
-
SDI (Single Document Interface) : l’application affiche toutes les fenêtres de l’application au même niveau, sans hiérarchisation. Chaque fenêtre possède son propre menu et sa barre d’outils.
-
MDI (Multiple Documents interface) : l’application possède une fenêtre unique au sein de laquelle sont affichées toutes les autres fenêtres de l’application qui auront pour parent la fenêtre principale. Les sous-fenêtres n’ont pas de menu ni de barre d’outils.
En règle générale les applications du type utilitaire ou logiciel de gestion utilisent le mode SDI et n’ont qu’une seule fenêtre. Les applications qui éditent des documents ont plus généralement une organisation MDI permettant de garder tous les documents ouverts dans un même ensemble. Cependant ce mode tend à disparaître au profit de fenêtres de documents individualisées.
Dans la plupart des systèmes d’exploitation, l’utilisation du mode SDI aura pour effet de créer une icône dans la barre des tâches pour chaque fenêtre ouverte et non pour l’application uniquement, comme c’est le cas pour le mode MDI.
Le mode SDI correspond au fonctionnement naturel...
Créer un menu
Pour posséder un menu, une application doit utiliser comme contrôleur de base une instance de la classe QMainWindow. À partir de cette classe, l’intégration dans le système d’exploitation se fait de manière simple. Sous Windows, le menu est intégré à la fenêtre principale, sous OS X comme sous GNU/Linux (KDE et Gnome par exemple) le menu de l’application sera affiché en haut de l’écran.
L’intégration du menu de votre application sera prise en charge directement par Qt, sans que vous ayez à vous soucier de la manière dont les menus sont gérés par le système d’exploitation.
La création d’un menu se fait en mode design.
Dans la liste des fichiers de votre projet, double cliquez sur le fichier MainWindow.ui dans la section Formulaires.
Une fenêtre principale possède par défaut un menu principal menuBar, une barre d’outils située sous le menu mainToolBar et une barre de statut en bas de la fenêtre statusBar. Pour vous débarrasser de l’un de ces éléments, sélectionnez-le dans la liste des objets, cliquez avec le bouton droit et choisissez Supprimer.
Une barre de menu peut contenir plus éléments menus, comme par exemple les menus Fichier ou Editer. Chaque menu peut contenir aussi bien d’autres menus...
Réagir aux événements
Les actions sont les éléments émetteurs de signaux de l’interface. Nous devons donc les relier à des slots qui réagiront au clic de l’utilisateur.
Nous disposons de deux façons d’effectuer cette tâche :
1) |
Créer un slot et le relier au signal avec la fonction connect(). |
2) |
Créer un slot en respectant la nomenclature automatique de Qt. |
Étudions le premier cas :
MainWindow.h
namespace Ui {
class MainWindow ;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public :
explicit MainWindow(QWidget* parent = nullptr) ;
~MainWindow() ;
private slots :
void onAfficheInformation() ;
private:
UI::MainWindow* ui ;
} ;
MainWindow.cpp
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent), ui(new UI::MainWindow)
{
ui->setupUi(this) ;
//Connexion manuelle
connect(ui->actionTester, &QAction::triggered, this,
&Controleur::onAfficheInformation) ;
}
void MainWindow::onAfficheInformation() ...
Intégrer et afficher les dialogues créés
Après avoir créé une boîte de dialogue ou une fenêtre à l’aide du designer vous possédez donc trois nouveaux fichiers : deux pour le contrôleur et un pour la vue. Seuls les deux premiers seront intégrés dans votre programme en tant qu’objets compilés, le dernier est utilisé pour générer la classe de la vue.
Lors de la phase de compilation de votre projet, Qt Creator lancera l’utilitaire qmake qui analysera vos fichiers et créera des fichiers sources intermédiaires. Pour connaître les différentes étapes de la phase de compilation, consultez le chapitre Anatomie d’une application - La phase de compilation. En ce qui concerne l’interface graphique, qmake créera automatiquement les fichiers suivants :
-
qrc_[nom du fichier de ressource].cpp : ce fichier correspond aux données de vos ressources converties en valeurs hexadécimales.
-
ui_[nom du dialogue].h : ce fichier correspond à la définition de la classe permettant d’afficher la vue (la partie graphique) de votre dialogue.
D’autres fichiers liés à la classe QObject sont créés (les fichiers moc_*.cpp par exemple).
Votre classe de contrôleur inclut donc le fichier ui_[nom du dialogue].h pour y trouver la définition...
Feuilles de style
Qt offre un moyen très simple et unifié de modifier l’aspect initial des composants utilisés dans nos boîtes de dialogue : les feuilles de style. Elles utilisent une syntaxe du type CSS identique à celle utilisée dans les pages web.
Le référencement des objets se fait à l’aide des sélecteurs CSS, avec le préfixe # suivi du nom de l’objet : #label_4 {}. Si vous souhaitez appliquer un style à une classe entière d’objets, il suffit d’utiliser le nom de la classe : QWidget {} par exemple.
Les styles sont définis en cascade, cela signifie que les enfants hériteront des propriétés de style des parents si celles-ci n’ont pas été restreintes à une catégorie ou un objet précis.
Commençons par définir le style du conteneur de droite (widget_2). Notre objectif est de lui donner une bordure rouge arrondie.
Cliquez avec le bouton droit sur l’élément widget_2 dans l’arborescence des objets, puis sélectionnez Modifier la feuille de style....
La boîte qui s’affiche vous permet d’éditer le code CSS brut ou d’utiliser des assistants pour ajouter des ressources, des dégradés, des couleurs ou spécifier une police.
Saisissez le code CSS suivant :...
Gestion des événements
Après avoir personnalisé l’aspect de notre dialogue, nous allons gérer les événements de celui-ci. Un événement possède toujours un émetteur et correspond toujours à une action, par exemple lorsque l’utilisateur clique sur le bouton OK, l’émetteur est le bouton OK et l’action correspondante est indéterminée pour le moment. Notre travail est donc de créer des actions et de les relier aux émetteurs d’événements.
Dans notre dialogue, nous avons créé un bouton Vérifier. Celui-ci nous servira à afficher une boîte de dialogue qui confirmera ou non la bonne saisie du formulaire.
Cliquez avec le bouton droit sur le bouton Vérifier et sélectionnez Aller au slot....
Choisissez le signal clicked().
La boîte de dialogue qui s’est affichée présente la liste des signaux émis par l’élément que vous avez sélectionné. Ces signaux sont classés par type d’émetteur, en effet les signaux sont hérités au même titre que les fonctions membres.
Le signal clicked() correspond à l’action de presser puis de relâcher le bouton. Vous pouvez différencier ces deux actions successives en utilisant les signaux pressed()...
Ressources
Il est souvent utile de mettre des images et des icônes dans une application. Leur utilisation est très simple dans Qt grâce aux ressources.
Le principe des ressources est d’encapsuler les données de vos images, documents, icônes, etc. dans les données de votre programme et de les rendre disponibles à toutes les classes grâce à une syntaxe particulière.
1. Créer un fichier de ressources
Pour créer un fichier de ressource, suivez la procédure suivante :
Dans le menu Fichier, cliquez sur Nouveau fichier ou projet....
Dans la section Fichiers et classes et la catégorie Qt, choisissez Fichier de ressource Qt.
Cliquez sur le bouton Choisir....
Donnez un nom à votre fichier et validez.
Les fichiers de ressource portent l’extension .qrc.
Les ressources peuvent être organisées en groupes grâce à l’ajout d’un Préfixe, au sein d’un préfixe vous pouvez ajouter autant de fichiers que vous le souhaitez. Nous vous recommandons de donner un alias à votre ressource car cela facilitera son intégration dans le code :
/prefixe/Le nom du fichier.jpg sera avantageusement remplacé par /prefixe/monFichier grâce à l’alias monFichier.
Les types de fichiers acceptés sont les fichiers images dans différents formats (png, gif, jpg...
Widgets personnalisés
Vous pouvez tout à fait concevoir vos propres widgets comme des composants réutilisables. Ces composants sont créés avec le designer comme nous l’avons vu précédemment et peuvent être intégrés dans n’importe quel conteneur en tant que widget depuis le designer.
L’exemple suivant nous permettra de créer un widget de forme carrée comportant un logo et un libellé que nous utiliserons ensuite dans une boîte de dialogue.
Dans le menu Fichier, cliquez sur Nouveau fichier ou projet....
Dans la section Fichiers et classes, choisissez la catégorie Qt et sélectionnez Classe d’interface graphique Qt Designer.
Choisissez le modèle (templates/forms) Widget.
Donnez-lui le nom WidgetReutilisable.
Créez un fichier de ressources selon la méthode vue précédemment et ajoutez une image de petit format (64 x 64 pixels environ).
Ce nouveau composant n’a pas de bordure ni de boutons car il s’agit d’un composant ayant directement comme base la classe QWidget. En l’état, nous ne pouvons que l’intégrer dans un layout.
Nous allons déposer nos composants directement au fond de ce composant et appliquer un layout.
Déposez un nouvel élément de type Label et donnez-lui le nom lblImage.
Déposez encore...
Icône de notification (Tray icon)
La classe QSystemTrayIcon permet d’installer une icône et éventuellement un menu dans la barre de notification du système, si celui-ci en dispose.
Sous Windows, il s’agit de la zone située à côté de l’heure.
System-tray.png (emprunté à qt-project.org)
Sous OS X, il s’agit de la zone située à droite du menu.
Sous GNU/Linux il n’est pas garanti que le gestionnaire de fenêtres de l’utilisateur dispose d’une telle barre de notification. Avec KDE et Gnome la barre de notification est présente mais avec des gestionnaires de fenêtre plus légers il est probable qu’il n’y ait pas la possibilité d’afficher d’icône.
Si vous concevez une application multiplateforme, nous vous recommandons de prévoir l’éventualité où le système d’exploitation ne permet pas d’afficher d’icône dans la zone de notification, ou encore que celle-ci soit saturée. Il est donc important de prévoir plusieurs façons d’accéder à la fenêtre principale de votre application.
Une icône de notification se gère exclusivement avec le code, le designer ne dispose pas de composant permettant d’initialiser et manipuler ce composant.
Le code source est disponible dans le projet TrayIcon.
L’objet...
Composants graphiques 2D
Certains composants sont créés pour permettre l’affichage de formes dessinées en 2D comme des rectangles, des cercles ou du texte. C’est le cas du composant Graphics View. Ce dernier est plus complexe à programmer qu’un simple widget car son contenu est entièrement « codé à la main ».
Son contenu repose sur une scène 2D que la classe QGraphicsScene aide très largement à coder.
Créez un nouveau projet d’interface graphique.
Dans le designer de la classe MainWindow, déposez dans le fond de l’interface un nouvel élément de type Graphics View.
Sélectionnez l’élément racine dans l’arborescence des objets.
Définissez une mise en page verticale.
Votre scène 2D est maintenant prête à être affichée. Il ne reste qu’à en coder le contenu.
Retournez dans l’éditeur de code et ouvrez le contrôleur de votre fenêtre, le fichier MainWindow.cpp.
Une instance de QGraphicsScene n’est pas affichable en tant que telle. Il s’agit uniquement d’un conteneur d’objets graphiques à afficher, l’affichage est réalisé par l’objet QGraphicsView qui récupère les éléments de la scène et en effectue le rendu....
Drag and Drop
Le drag and drop est l’action qui consiste à cliquer sur un élément de l’interface, le déplacer (drag) à l’aide de la souris et de le déposer (drop) à un autre endroit de l’interface ou à l’extérieur de l’application.
Cette opération est déclenchée uniquement par l’utilisateur, l’application doit être préparée pour offrir cette fonctionnalité sur tout ou partie des éléments de l’interface mais aussi être codée pour savoir comment réagir à ce genre d’événements.
Les opérations de drag and drop ont plusieurs finalités :
-
Déplacer des éléments de l’interface pour réorganiser la mise en page.
-
Ordonner les éléments d’un tableau.
-
Déplacer des fichiers dans une arborescence.
-
Activer une fonction sur un objet.
-
Transférer des données de l’application vers le système ou une autre application, et vice-versa.
Les opérations étant de natures différentes, le développeur prendra soin de définir les comportements qu’il souhaite voir adopter par son application et les codera spécifiquement.
Le code source est disponible dans le projet Drag-Drop.
Une opération de drag and drop peut être vue comme une action ayant une source, une destination et une certaine quantité de données à transférer de l’une vers l’autre. Afin de rendre ces opérations compatibles d’une application à une autre, les données sont encodées dans le protocole MIME. Celui-ci permet à la fois de coder le type des données (application/pdf pour les documents PDF ou image/png pour les images compressées au format PNG par exemple), et les données elles-mêmes.
Une action de drag...
Copier-coller
La gestion du copier-coller est très utile dans les applications, cela participe grandement au sentiment de facilité d’utilisation et de confort que ressentiront les utilisateurs de vos logiciels. N’hésitez donc pas à intégrer ces fonctionnalités dans votre programme.
Le copier-coller de Qt est bien entendu multiplateforme et s’adapte le plus possible aux spécificités des différents systèmes. Lorsqu’un utilisateur effectue une action de copier, il doit généralement aller dans le menu Edition et sélectionner Copier, ou bien utiliser le raccourci-clavier correspondant à cette entrée dans le menu. Les données sélectionnées seront copiées dans le presse-papiers du système et restituées après un clic sur l’entrée Coller du menu Edition.
Les presse-papiers ne sont pas capables d’empiler des données, généralement le dernier jeu de données stocké dans le presse-papiers écrase le précédent.
Le code source est disponible dans le projet Presse_Papier.
La gestion du presse-papiers est assurée par la classe QClipboard qui, tout comme la classe QDrag utilisée pour le glisser-déposer, prend en charge des données encodées avec le protocole MIME. Il n’existe qu’une seule instance de presse-papiers...
Ouvrir et enregistrer des fichiers
Pour fournir à vos utilisateurs un moyen de choisir des fichiers à ouvrir ou à enregistrer, vous pouvez leur proposer les boîtes de dialogue de sélection de fichiers. Ces boîtes de dialogue sont regroupées dans la classe QFileDialog.
Si vous voulez proposer à l’utilisateur de choisir un fichier à ouvrir, vous devez utiliser la fonction statique getOpenFilename(). Vous avez alors la possibilité de personnaliser l’affichage du dialogue et les filtres d’extensions.
Cette fonction accepte quatre arguments :
-
QWidget* parent : l’instance parente, en général this. Ce paramètre vous permet de spécifier au centre de quel widget sera placé le dialogue.
-
QString titre : le titre du dialogue.
-
QString repertoire : le répertoire qui sera affiché à l’ouverture.
-
QString filtres : les filtres à afficher dans la boîte de dialogue de la forme « Fichiers texte (*.txt, *.rtf);;Fichiers binaires (*.exe, *.bin) ».
Sous OS X il est indispensable d’ouvrir la fenêtre en utilisant le répertoire de l’utilisateur courant, sinon votre application ne respectera pas les critères de la Sandbox. Le répertoire de l’utilisateur est obtenu grâce à un appel à la fonction statique...
Généraliser les styles
Qt permet de définir une feuille de style qui s’appliquera à tous les éléments de votre application. Celle-ci vous permettra soit de définir l’aspect par défaut de tous les composants par famille (QLabel, QPushButton, etc.), ou bien d’externaliser l’aspect de toute votre interface graphique. Ainsi vous serez capables de créer des skins pour votre application et laisser vos utilisateurs créer de nouveaux looks eux-mêmes, il n’y a rien de mieux pour créer une communauté d’utilisateurs !
Pour définir une feuille de style portant sur toute l’application, il vous faudra utiliser la fonction QApplication::setStylesheet(const QString&). Si votre feuille de style figure dans un fichier externe, vous prendrez soin de rendre ce fichier accessible par votre application et de le lire dans une instance de QString. Vous pouvez aussi intégrer ce fichier comme ressource dans votre application.
QApplication a(argc, argv) ;
QString stylesheet;
stylesheet += "QPushButton { color: white; background-color:black; }";
stylesheet += "QLabel { color: red; } ";
stylesheet += "QRadioButton { background-color: red; color: blue; }";
stylesheet += "QDialog { background-color: rgb(30, 200, 30); ...