Débuter dans la création d'interfaces graphiques avec Qt 4

Image non disponible


précédentsommairesuivant

X. Préparer le champ de bataille

t8.rar

Fichiers
  • tutorial/t8/cannonfield.cpp
  • tutorial/t8/cannonfield.h
  • tutorial/t8/lcdrange.cpp
  • tutorial/t8/lcdrange.h
  • tutorial/t8/main.cpp
Image non disponible

Dans cet exemple, on introduit le premier widget personnalisé qui se dessine de lui-même. On va aussi ajouter quelques interactions avec le clavier (en deux lignes de code).

X-A. Analyse du code ligne par ligne

X-A-1. t8/lcdrange.h

Ce fichier est très proche du fichier lcdrange.h du Chapitre IX. Nous n'avons ajouté qu'un seul slot setRange().

 
Sélectionnez
void setRange(int minValue, int maxValue);

On a maintenant la possibilité de régler l'intervalle LCDRange. Jusqu'à présent, on fixait ce dernier de 0 à 99.

X-A-2. t8/lcdrange.cpp

Il y a du changement dans le constructeur (on en discutera plus tard).

 
Sélectionnez
void LCDRange::setRange(int minValue, int maxValue)
 {
     if (minValue < 0 || maxValue > 99 || minValue > maxValue) {
         qWarning("LCDRange::setRange(%d, %d)\n"
                  "\tRange must be 0..99\n"
                  "\tand minValue must not be greater than maxValue",
                  minValue, maxValue);
         return;
     }
     slider->setRange(minValue, maxValue);
 }

Le slot setRange() fixe l'intervalle du curseur dans LCDRange. Puisqu'on a paramétré QLCDNumber pour qu'il puisse afficher deux chiffres, on voudrait limiter les valeurs de minValue et maxValue pour empêcher le débordement sur QLCDNumber (on aurait pu autoriser des valeurs allant jusqu'à -9, mais on a choisi de ne pas le faire). Si les arguments sont illégaux, on utilise la fonction qWarning() pour afficher un avertissement à l'utilisateur et retourner immédiatement. qWarning() est une fonction semblable à printf qui, par défaut, envoie sa sortie vers stderr. Si vous le souhaitez, vous pouvez aussi utiliser votre propre gestionnaire d'erreur en utilisant la fonction qInstallMsgHandler().

X-A-3. t8/cannonfield.h

CannonField est un nouveau widget personnalisé qui s'affiche de lui-même.

 
Sélectionnez
class CannonField : public QWidget
 {
     Q_OBJECT

 public:
     CannonField(QWidget *parent = 0);

CannonField hérite de QWidget. On utilise la même syntaxe que pour LCDRange.

 
Sélectionnez
int angle() const { return currentAngle; }

 public slots:
     void setAngle(int angle);

 signals:
     void angleChanged(int newAngle);

Pour le moment, CannonField ne contient que la valeur d'un angle pour laquelle on fournit une interface avec la même syntaxe que pour la valeur de LCDRange.

 
Sélectionnez
protected:
     void paintEvent(QPaintEvent *event);

On trouve ici un second exemple de gestionnaire d'événement : il fait partie de la multitude de gestionnaires qu'on peut rencontrer dans QWidget. Cette fonction virtuelle est appelée quand un widget a besoin de se redessiner (c'est-à-dire de repeindre sa propre surface).

X-A-4. t8/cannonfield.cpp

 
Sélectionnez
CannonField::CannonField(QWidget *parent)
     : QWidget(parent)
 {

Ici encore, on utilise la même syntaxe que dans le LCDRange du chapitre précédent.

 
Sélectionnez
 	currentAngle = 45;
     setPalette(QPalette(QColor(250, 250, 200)));
     setAutoFillBackground(true);
 }

Le constructeur initialise l'angle à 45 degrés et crée une palette personnalisée pour ce widget.

Cette palette utilise la couleur indiquée comme couleur d'arrière-plan et choisie selon le besoin (pour ce widget, seules les couleurs d'arrière-plan et de texte sont utilisées). Ensuite, on appelle setAutoFillBackground(true) pour demander à Qt de remplir automatiquement l'arrière-plan.

QColor est spécifié comme un triplet RVB (rouge-vert-bleu), où chaque composante (R, V et B) est comprise entre 0 (luminosité minimale) et 255 (luminosité maximale). Au lieu de spécifier une valeur RVB, on aurait pu aussi utiliser une couleur prédéfinie (par exemple, Qt::yellow).

 
Sélectionnez
void CannonField::setAngle(int angle)
 {
     if (angle < 5)
         angle = 5;
     if (angle > 70)
         angle = 70;
     if (currentAngle == angle)
         return;
     currentAngle = angle;
     update();
     emit angleChanged(currentAngle);
 }

Cette fonction définit la valeur de l'angle. On a choisi un intervalle compris entre 5 et 70 et on ajuste l'amplitude de l'angle spécifié en conséquence. On a choisi de ne pas afficher d'avertissement dans le cas où l'angle serait en dehors de cet intervalle.

Si l'angle actuel est égal au précédent, on retourne immédiatement. Il est important d'émettre le signal angleChanged() dans le cas où l'angle change vraiment.

Ensuite, on définit le nouvel angle et on redessine le widget. La fonction QWidget::update() efface le widget (généralement, en le remplissant avec la couleur de l'arrière-plan) et envoie un événement de dessin au widget. Le résultat est un appel à la fonction de l'événement de dessin de widget.

Pour terminer, on émet le signal angleChanged() pour signaler au monde extérieur que l'angle a changé. Le mot-clé emit est un élément syntaxique propre à Qt qui n'appartient donc pas à la syntaxe C++ normalisée. En fait, c'est une macro.

 
Sélectionnez
void CannonField::paintEvent(QPaintEvent * /* event */)
 {
     QPainter painter(this);
     painter.drawText(200, 200,
                      tr("Angle = ") + QString::number(currentAngle));
 }

Ceci est notre première tentative de création d'un gestionnaire d'événement de dessin. L'argument contient des informations concernant cet événement, comme, la région du widget qui doit être rafraîchie. Pour le moment, soyons paresseux et redessinons tout le widget.

Notre code affiche la valeur de l'angle à une certaine position dans le widget. Pour réaliser cela, on crée un QPainter, qui opère sur le widget CannonField et que l'on utilise pour dessiner une représentation littérale de la valeur de currentAngle. Nous reviendrons plus tard sur QPainter, et nous verrons qu'on peut faire des choses merveilleuses avec lui.

X-A-5. t8/main.cpp

 
Sélectionnez
#include "cannonfield.h"

On inclut la définition de notre nouvelle classe.

 
Sélectionnez
class MyWidget : public QWidget
 {
 public:
     MyWidget(QWidget *parent = 0);
 };

La classe MyWidget va inclure un seul LCDRange et un CannonField.

 
Sélectionnez
LCDRange *angle = new LCDRange;

Dans le constructeur, on crée et initialise un widget LCDRange.

 
Sélectionnez
angle->setRange(5, 70);

On initialise LCDRange pour qu'il accepte des angles compris entre 5 et 70.

 
Sélectionnez
CannonField *cannonField = new CannonField;

On crée notre widget CannonField.

 
Sélectionnez
connect(angle, SIGNAL(valueChanged(int)),
             cannonField, SLOT(setAngle(int)));
     connect(cannonField, SIGNAL(angleChanged(int)),
             angle, SLOT(setValue(int)));

Ici, on connecte le signal valueChanged() de LCDRange au slot setAngle() de cannonField. Ceci va permettre de mettre à jour l'angle de cannonField quand l'utilisateur manipule LCDRange. On rajoute aussi une connexion inversée, pour que le changement de l'angle de cannonField engendre réciproquement une mise à jour de LCDRange. Dans notre exemple, nous n'aurons jamais à modifier l'angle de cannonField directement. En rajoutant le dernier connect(), on s'assure que, dans le cas d'un changement futur, aucune modification ne perturbera la synchronisation entre les deux valeurs.

Ceci illustre la puissance de la programmation par composants quand elle met en oeuvre une encapsulation appropriée.

Notez qu'il est important de n'émettre le signal valueChanged() que dans le cas où l'angle change vraiment. Si les deux widgets LCDRange et cannonField avaient omis cette précaution, le programme entrerait dans une boucle infinie

 
Sélectionnez
QGridLayout *gridLayout = new QGridLayout;

Jusqu'à présent, on a utilisé QVBoxLayout pour gérer la géométrie, mais on voudrait à présent un meilleur contrôle de la disposition des widgets : pour cela, on va passer à une disposition plus puissante grâce à la classe QGridLayout. QGridLayout n'est pas un widget : c'est une classe différente qui va permettre de gérer les enfants de n'importe quel widget.

On n'a pas besoin de spécifier les dimensions dans le constructeur de QGridLayout, car il va déterminer lui-même le nombre de lignes et de colonnes en se basant sur les cellules de la grille qu'on définit.

Image non disponible Image non disponible

Le diagramme ci-dessus montre la disposition à laquelle on veut aboutir : le côté gauche montre un schéma de la disposition et le côté droit montre une capture d'écran actuelle du programme.

 
Sélectionnez
gridLayout->addWidget(quit, 0, 0);

On ajoute le bouton Quit à la cellule située dans le coin supérieur gauche de la grille, c'est-à-dire la cellule avec les coordonnées (0,0).

 
Sélectionnez
gridLayout->addWidget(angle, 1, 0);

On ajoute l'angle LCDRange dans la cellule (1,0).

 
Sélectionnez
gridLayout->addWidget(cannonField, 1, 1, 2, 1);

On laisse cannonField occuper les cellules (1,1) et (2,1).

 
Sélectionnez
gridLayout->setColumnStretch(1, 10);

On ordonne à QGridLayout de rendre la colonne de droite (2) extensible, avec un facteur de 10. Puisque la colonne de gauche ne l'est pas (son facteur d'extensibilité est 0, valeur par défaut), QGridLayout va essayer de garder inchangée la taille des widgets à gauche et va redimensionner le CannonField quand la taille de MyWidget changera.

Dans cet exemple particulier, tout facteur d'extensibilité plus grand que 0 pour la colonne 1 aurait le même effet. Dans des dispositions géométriques plus complexes, vous pouvez, en choisissant les facteurs d'extensibilité adéquats, utiliser ces facteurs pour spécifier qu'une colonne ou une ligne s'étend plus rapidement qu'une autre.

 
Sélectionnez
angle->setValue(60);

On définit la valeur initiale de l'angle. Noter que ceci va déclencher la connexion entre LCDRange et CannonField.

 
Sélectionnez
angle->setFocus();

Notre dernière intervention consiste à donner le focus au widget angle, de sorte que toute action sur le clavier ira vers le widget LCDRange par défaut.

LCDRange ne contient aucun keyPressEvent() : il semblerait que ceci n'ait pas été jugé vraiment intéressant ! Cependant, son constructeur possède une nouvelle ligne.

 
Sélectionnez
setFocusProxy(slider);

Le LCDRange va définir le curseur comme son « focus proxy », ce qui signifie que, quand quelqu'un donne le contrôle du clavier au LCDRange, le curseur doit le prendre en charge. QSlider possède une interface clavier décente, qui a permis avec une seule ligne de code d'en donner une à LCDRange.

X-B. Lancer l'application

Maintenant, le clavier peut être utilisé : les flèches Home, End, PageUp et PageDown sont vraiment opérationnelles.

Quand le curseur est manipulé, CannonField affiche un nouvel angle. Au redimensionnement, CannonField prend tout l'espace disponible.

X-C. Exercices

Essayez de redimensionner la fenêtre. Que se passe-t-il quand la fenêtre devient trop étroite ou bien trop large ?

Quand vous donnez à la colonne de gauche un facteur d'extensibilité supérieur à zéro, que se passe-t-il quand vous redimensionnez la fenêtre ?

Enlevez l'appel à QWidget::setFocus(). Quel est le comportement que vous préférez ?

Essayez de remplacer « Quit » par « &Quit », quel changement cela produit-il sur le look du bouton ? Que se passe-t-il si on appuie sur Alt+Q pendant l'exécution du programme ?

Centrez le texte sur CannonField.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2009 - 2019 Developpez.com LLC Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.