• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

course.header.alt.is_certifying

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 02/08/2019

Configurez la fenêtre principale

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Intéressons-nous maintenant à la fenêtre principale de nos applications.
Pour le moment, nous avons créé des fenêtres plutôt basiques en héritant deQWidget. C'est en effet largement suffisant pour de petites applications mais, au bout d'un moment, on a besoin de plus d'outils.

La classeQMainWindowa été spécialement conçue pour gérer la fenêtre principale de votre application quand celle-ci est complexe. Parmi les fonctionnalités qui nous sont offertes par la classeQMainWindow, on trouve notamment les menus, la barre d'outils et la barre d'état.

Voyons voir comment tout cela fonctionne !

Présentation de QMainWindow

La classeQMainWindowhérite directement deQWidget. C'est un widget généralement utilisé une seule fois par programme et qui sert uniquement à créer la fenêtre principale de l'application.

Certaines applications simples n'ont pas besoin de recourir à laQMainWindow. On va supposer ici que vous vous attaquez à un programme complexe et d'envergure.

Structure de laQMainWindow

Avant toute chose, il me semble indispensable de vous présenter l'organisation d'uneQMainWindow.
Commençons par analyser le schéma suivante.

Layout de la QMainWindow

Une fenêtre principale peut être constituée de tout ces éléments. Et j'ai bien dit peut, car rien ne vous oblige à utiliser à chaque fois chacun de ces éléments.

Détaillons-les :

  • Menu Bar : c'est la barre de menus. C'est là que vous allez pouvoir créer votre menu Fichier, Édition, Affichage, Aide, etc.

  • Toolbars : les barres d'outils. Dans un éditeur de texte, on a par exemple des icônes pour créer un nouveau fichier, pour enregistrer, etc.

  • Dock Widgets : plus complexes et plus rarement utilisés, ces docks sont des conteneurs que l'on place autour de la fenêtre principale. Ils peuvent contenir des outils, par exemple les différents types de pinceaux que l'on peut utiliser quand on fait un logiciel de dessin.

  • Central Widget : c'est le cœur de la fenêtre, là où il y aura le contenu proprement dit.

  • Status Bar : c'est la barre d'état. Elle affiche en général l'état du programme (« Prêt/Enregistrement en cours », etc.).

Exemple deQMainWindow

Pour imaginer ces éléments en pratique, je vous propose de prendre pour exemple le programme Qt Designer (figure suivante).

Qt Designer et sa QMainWindow

Vous repérez en haut la barre de menus : File, Edit, Form…

En dessous, on a la barre d'outils, avec les icônes pour créer un nouveau projet, ouvrir un projet, enregistrer, annuler…

Autour (sur la gauche et la droite), on a les fameux docks. Ils servent ici à sélectionner le widget que l'on veut utiliser ou à éditer les propriétés du widget par exemple.

Au centre figure une partie grise où il n'y a rien, c'est la zone centrale. Lorsqu'un document est ouvert, cette zone l'affiche. La zone centrale peut afficher un ou plusieurs documents à la fois, comme on le verra plus loin.

Enfin, en bas, il y a normalement la barre de statut mais Qt Designer n'en utilise pas vraiment, visiblement (en tout cas rien n'est affiché en bas).

Dans ce chapitre, nous étudierons les éléments les plus utilisés et les plus importants : les menus, la barre d'outils et la zone centrale. Les docks sont vraiment plus rares et la barre de statut, quant à elle, est suffisamment simple à utiliser pour que vous n'ayez pas de problème, à votre niveau, à la lecture de sa documentation. ;-)

Le code de base

Pour suivre ce chapitre, il va falloir créer un projet en même temps que moi. Nous allons créer notre propre classe de fenêtre principale qui héritera deQMainWindow, car c'est comme cela qu'on fait dans 99,99 % des cas.

Notre projet contiendra trois fichiers :

  • main.cpp: la fonctionmain();

  • FenPrincipale.h: définition de notre classeFenPrincipale, qui héritera deQMainWindow;

  • FenPrincipale.cpp: implémentation des méthodes de la fenêtre principale.

main.cpp
#include <QApplication>
#include <QtWidgets>
#include "FenPrincipale.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    FenPrincipale fenetre;
    fenetre.show();

    return app.exec();
}
FenPrincipale.h
#ifndef HEADER_FENPRINCIPALE
#define HEADER_FENPRINCIPALE

#include <QtWidgets>

class FenPrincipale : public QMainWindow
{
    public:
    FenPrincipale();

    private:

};

#endif
FenPrincipale.cpp
#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{

}
Résultat

Si tout va bien, ce code devrait avoir pour effet d'afficher une fenêtre vide, toute bête (figure suivante).

Une QMainWindow vide

Si c'est ce qui s'affiche chez vous, c'est bon, nous pouvons commencer.

La zone centrale (SDI et MDI)

La zone centrale de la fenêtre principale est prévue pour contenir un et un seul widget.
C'est le même principe que les onglets. On y insère unQWidget(ou une de ses classes filles) et on s'en sert comme conteneur pour mettre d'autres widgets à l'intérieur, si besoin est.

Nous allons refaire la manipulation ici pour nous assurer que tout le monde comprend comment cela fonctionne.

Sachez tout d'abord qu'on distingue deux types deQMainWindow:

  • Les SDI (Single Document Interface) : elles ne peuvent afficher qu'un document à la fois. C'est le cas du Bloc-Notes par exemple.

  • Les MDI (Multiple Document Interface) : elles peuvent afficher plusieurs documents à la fois. Elles affichent des sous-fenêtres dans la zone centrale. C'est le cas par exemple de Qt Designer (figure suivante).

Programme MDI : Qt Designer

Définition de la zone centrale (type SDI)

On utilise la méthodesetCentralWidget()de laQMainWindowpour indiquer quel widget contiendra la zone centrale. Faisons cela dans le constructeur deFenPrincipale:

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QWidget *zoneCentrale = new QWidget;
    setCentralWidget(zoneCentrale);
}

Visuellement, cela ne change rien pour le moment. Par contre, ce qui est intéressant, c'est qu'on a maintenant unQWidgetqui sert de conteneur pour les autres widgets de la zone centrale de la fenêtre.

On peut donc y insérer des widgets au milieu :

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QWidget *zoneCentrale = new QWidget;

    QLineEdit *nom = new QLineEdit;
    QLineEdit *prenom = new QLineEdit;
    QLineEdit *age = new QLineEdit;

    QFormLayout *layout = new QFormLayout;
    layout->addRow("Votre nom", nom);
    layout->addRow("Votre prénom", prenom);
    layout->addRow("Votre âge", age);

    zoneCentrale->setLayout(layout);

    setCentralWidget(zoneCentrale);
}

Vous noterez que j'ai repris le code du chapitre sur les layouts.

Bon, je reconnais qu'on ne fait rien de bien excitant pour le moment. Mais maintenant, vous savez au moins comment définir un widget central pour uneQMainWindowet cela, mine de rien, c'est important.

Définition de la zone centrale (type MDI)

Les choses se compliquent un peu si vous voulez créer un programme MDI… par exemple un éditeur de texte qui peut gérer plusieurs documents à la fois.
Nous allons utiliser pour cela uneQMdiArea, qui est une sorte de gros widget conteneur capable d'afficher plusieurs sous-fenêtres.

On peut se servir duQMdiAreacomme de widget conteneur pour la zone centrale :

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QMdiArea *zoneCentrale = new QMdiArea;
    setCentralWidget(zoneCentrale);
}

La fenêtre est maintenant prête à accepter des sous-fenêtres. On crée celles-ci en appelant la méthodeaddSubWindow()duQMdiArea. Cette méthode attend en paramètre le widget que la sous-fenêtre doit afficher à l'intérieur.
Là encore, vous pouvez créer unQWidgetgénérique qui contiendra d'autres widgets, eux-mêmes organisés selon un layout.

On vise plus simple dans notre exemple : on va faire en sorte que les sous-fenêtres contiennent juste unQTextEdit(pour notre éditeur de texte) :

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QMdiArea *zoneCentrale = new QMdiArea;

    QTextEdit *zoneTexte1 = new QTextEdit;
    QTextEdit *zoneTexte2 = new QTextEdit;

    QMdiSubWindow *sousFenetre1 = zoneCentrale->addSubWindow(zoneTexte1);
    QMdiSubWindow *sousFenetre2 = zoneCentrale->addSubWindow(zoneTexte2);

    setCentralWidget(zoneCentrale);
}

Résultat, on a une fenêtre principale qui contient plusieurs sous-fenêtres à l'intérieur (figure suivante).

Des sous-fenêtres

Ces fenêtres peuvent êtres réduites ou agrandies à l'intérieur même de la fenêtre principale.
On peut leur attribuer un titre et une icône avec les bonnes vieilles méthodessetWindowTitle,setWindowIcon, etc.

C'est quand même dingue tout ce qu'on peut faire en quelques lignes de code avec Qt !

Vous remarquerez queaddSubWindow()renvoie un pointeur sur uneQMdiSubWindow: ce pointeur représente la sous-fenêtre qui a été créée. Cela peut être une bonne idée de garder ce pointeur pour la suite. Vous pourrez ainsi supprimer la fenêtre en appelantremoveSubWindow().
Sinon, sachez que vous pouvez retrouver à tout moment la liste des sous-fenêtres créées en appelantsubWindowList(). Cette méthode renvoie la liste desQMdiSubWindowcontenues dans laQMdiArea.

Les menus

LaQMainWindowpeut afficher une barre de menus, comme par exemple : Fichier, Edition, Affichage, Aide…
Comment fait-on pour les créer ?

Créer un menu pour la fenêtre principale

La barre de menus est accessible depuis la méthodemenuBar(). Cette méthode renvoie un pointeur sur unQMenuBar, qui vous propose une méthodeaddMenu(). Cette méthode renvoie un pointeur sur leQMenucréé.

Puisqu'un petit code vaut tous les discours du monde, voici comment faire :

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QMenu *menuFichier = menuBar()->addMenu("&Fichier");
    QMenu *menuEdition = menuBar()->addMenu("&Edition");
    QMenu *menuAffichage = menuBar()->addMenu("&Affichage");
}

Avec cela, nous avons créé trois menus dont nous gardons les pointeurs (menuFichier,menuEdition,menuAffichage). Vous noterez qu'on utilise ici aussi le symbole&pour définir des raccourcis clavier (les lettres F, E et A seront donc des raccourcis vers leurs menus respectifs)).

Nous avons maintenant trois menus dans notre fenêtre (figure suivante).

Les menus

Mais… ces menus n'affichent rien ! En effet, ils ne contiennent pour le moment aucun élément.

Création d'actions pour les menus

Un élément de menu est représenté par une action. C'est la classeQActionqui gère cela.

Pourquoi avoir créé une classeQActionau lieu de… je sais pas moi…QSubMenupour dire « sous-menu » ?

En fait, lesQActionsont des éléments de menus génériques. Ils peuvent être utilisés à la fois pour les menus et pour la barre d'outils.
Par exemple, imaginons l'élément « Nouveau » qui permet de créer un nouveau document. On peut en général y accéder depuis plusieurs endroits différents :

  • le menu Fichier > Nouveau ;

  • le bouton de la barre d'outils « Nouveau », généralement représenté par une icône de document vide.

Une seuleQActionpeut servir à définir ces 2 éléments à la fois.
Les développeurs de Qt se sont en effet rendu compte que les actions des menus étaient souvent dupliquées dans la barre d'outils, d'où la création de la classeQActionque nous réutiliserons lorsque nous créerons la barre d'outils.

Pour créer une action vous avez deux possibilités :

  • soit vous la créez d'abord, puis vous créez l'élément de menu qui correspond ;

  • soit vous créez l'élément de menu directement et celui-ci vous renvoie un pointeur vers laQActioncréée automatiquement.

Je vous propose d'essayer ici la première technique :

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    QMenu *menuFichier = menuBar()->addMenu("&Fichier");

    QAction *actionQuitter = new QAction("&Quitter", this);
    menuFichier->addAction(actionQuitter);

    QMenu *menuEdition = menuBar()->addMenu("&Edition");
    QMenu *menuAffichage = menuBar()->addMenu("&Affichage");


}

Dans l'exemple de code ci-dessus, nous créons d'abord uneQActioncorrespondant à l'action « Quitter ». Nous définissons en second paramètre de son constructeur un pointeur sur la fenêtre principale (this), qui servira de parent à l'action.
Puis, nous ajoutons l'action au menu « Fichier ».

Résultat, l'élément de menu est créé (figure suivante).

Un élément de menu

Les sous-menus

Les sous-menus sont gérés par la classeQMenu.

Imaginons que nous voulions créer un sous-menu « Fichiers récents » dans le menu « Fichier ». Ce sous-menu affichera une liste de fichiers récemment ouverts par le programme.

Au lieu d'appeleraddAction()de laQMenuBar, appelez cette foisaddMenu()qui renvoie un pointeur vers unQMenu:

QMenu *fichiersRecents = menuFichier->addMenu("Fichiers &récents");
fichiersRecents->addAction("Fichier bidon 1.txt");
fichiersRecents->addAction("Fichier bidon 2.txt");
fichiersRecents->addAction("Fichier bidon 3.txt");

Vous voyez que j'ajoute ensuite de nouvelles actions pour peupler le sous-menu « Fichiers récents » (figure suivante).

Les sous-menus

Je n'ai pas récupéré de pointeur vers lesQActioncréées à chaque fois. J'aurais dû le faire si je voulais ensuite connecter les signaux des actions à des slots, mais je ne l'ai pas fait ici pour simplifier le code.

Manipulations plus avancées desQAction

UneQActionest au minimum constituée d'un texte descriptif. Mais ce serait dommage de la limiter à cela.
Voyons un peu ce qu'on peut faire avec les QAction…

Connecter les signaux et les slots

Le premier rôle d'uneQActionest de générer des signaux, que l'on aura connectés à des slots.
LaQActionpropose plusieurs signaux intéressants. Le plus utilisé d'entre eux esttriggered()qui indique que l'action a été choisie par l'utilisateur.

On peut connecter notre action « Quitter » au slotquit()de l'application :

connect(actionQuitter, SIGNAL(triggered()), qApp, SLOT(quit()));

Désormais, un clic sur « Fichier > Quitter » fermera l'application.

Vous avez aussi un évènementhovered()qui s'active lorsqu'on passe la souris sur l'action. A tester !

Ajouter un raccourci

On peut définir un raccourci clavier pour l'action. On passe pour cela par la méthodeaddShortcut().

Cette méthode peut être utilisée de plusieurs manières différentes. La technique la plus simple est de lui envoyer uneQKeySequencereprésentant le raccourci clavier :

actionQuitter->setShortcut(QKeySequence("Ctrl+Q"));

Voilà, il suffit d'écrire dans le constructeur de laQKeySequencele raccourci approprié, Qt se chargera de comprendre le raccourci tout seul. Vous pouvez faire le raccourci clavier Ctrl + Q n'importe où dans la fenêtre, à partir de maintenant, cela activera l'action « Quitter » !

Ajouter une icône

Chaque action peut avoir une icône.
Lorsque l'action est associée à un menu, l'icône est affichée à gauche de l'élément de menu. Mais, souvenez-vous, une action peut aussi être associée à une barre d'outils comme on le verra plus tard. L'icône peut donc être réutilisée dans la barre d'outils.

Pour ajouter une icône, appelezsetIcon()et envoyez-lui unQIcon(figure suivante) :

actionQuitter->setIcon(QIcon("quitter.png"));
Une icône dans un menu
Pouvoir cocher une action

Lorsqu'une action peut avoir deux états (activée, désactivée), vous pouvez la rendre « cochable » grâce àsetCheckable().
Imaginons par exemple le menu Edition > Gras :

actionGras->setCheckable(true);

Le menu a maintenant deux états et peut être précédé d'une case à cocher (figure suivante).

Un menu cochable

On vérifiera dans le code si l'action est cochée avecisChecked().

Lorsque l'action est utilisée sur une barre d'outils, le bouton reste enfoncé lorsque l'action est « cochée ». C'est ce que vous avez l'habitude de voir dans un traitement de texte par exemple.

Ah, puisqu'on parle de barre d'outils, il serait temps d'apprendre à en créer une !

La barre d'outils

La barre d'outils est généralement constituée d'icônes et située sous les menus.
Avec Qt, la barre d'outils utilise des actions pour construire chacun des éléments de la barre. Étant donné que vous avez appris à manipuler des actions juste avant, vous devriez donc être capables de créer une barre d'outils très rapidement.

Pour ajouter une barre d'outils, vous devez tout d'abord appeler la méthodeaddToolBar()de laQMainWindow. Il faudra donner un nom à la barre d'outils, même s'il ne s'affiche pas.
Vous récupérez un pointeur vers laQToolBar:

QToolBar *toolBarFichier = addToolBar("Fichier");

Maintenant que nous avons notreQToolBar, nous pouvons commencer !

Ajouter une action

Le plus simple est d'ajouter une action à laQToolBar. On utilise comme pour les menus une méthode appeléeaddAction()qui prend comme paramètre uneQAction.
Le gros intérêt que vous devriez saisir maintenant, c'est que vous pouvez réutiliser ici vosQActioncréées pour les menus !

#include "FenPrincipale.h"

FenPrincipale::FenPrincipale()
{
    // Création des menus
    QMenu *menuFichier = menuBar()->addMenu("&Fichier");
    QAction *actionQuitter = menuFichier->addAction("&Quitter");
    actionQuitter->setShortcut(QKeySequence("Ctrl+Q"));
    actionQuitter->setIcon(QIcon("quitter.png"));
    
    QMenu *menuEdition = menuBar()->addMenu("&Edition");
    QMenu *menuAffichage = menuBar()->addMenu("&Affichage");

    // Création de la barre d'outils
    QToolBar *toolBarFichier = addToolBar("Fichier");
    toolBarFichier->addAction(actionQuitter);

    connect(actionQuitter, SIGNAL(triggered()), qApp, SLOT(quit()));
}

Dans ce code, on voit qu'on crée d'abord uneQActionpour un menu, puis plus loin on réutilise cette action pour l'ajouter à la barre d'outils (figure suivante).

Une barre d'outils

Et voilà comment Qt fait d'une pierre deux coups grâce auxQAction!

Ajouter un widget

Les barres d'outils contiennent le plus souvent desQActionmais il arrivera que vous ayez besoin d'insérer des éléments plus complexes.
LaQToolBargère justement tous types de widgets.

Vous pouvez ajouter des widgets avec la méthodeaddWidget(), comme vous le faisiez avec les layouts :

QFontComboBox *choixPolice = new QFontComboBox;
toolBarFichier->addWidget(choixPolice);

Ici, on insère une liste déroulante. Le widget s'insère alors dans la barre d'outils (figure suivante).

Un widget dans une barre d'outils

Ajouter un séparateur

Si votre barre d'outils commence à comporter trop d'éléments, cela peut être une bonne idée de les séparer. C'est pour cela que Qt propose des separators (séparateurs).

Il suffit d'appeler la méthode addSeparator() à l'endroit où vous voulez insérer un séparateur :

toolBarFichier->addSeparator();

En résumé

  • Une QMainWindowest une fenêtre principale. Elle peut contenir des sous-fenêtres, des menus, une barre d'outils, une barre d'état, etc.

  • Une fenêtre SDI ne peut afficher qu'un seul document à la fois. Une fenêtre MDI peut en afficher plusieurs sous la forme de sous-fenêtres.

  • La barre d'outils peut contenir tous types de widgets.

  • Menus et barres d'outils partagent le même élément générique : la QAction. Une même QActionpeut être utilisée à la fois dans le menu et dans la barre d'outils.

Exemple de certificat de réussite
Exemple de certificat de réussite