La librairie Qt - 7
La liste simple d'items QListBox

Nous avons vu le mois dernier comment dessiner en utilisant la classe QPainter. Nous allons ce mois-ci mettre en application quelques-uns des principes vus précédemment, pour découvrir la liste simple de Qt - simple, mais pas si élémentaire...

1. Organisation

2. Le widget QListBox et ses items

3. Nos items personnalisés

4. Le widget principal

5. Modes de sélection

6. Conclusion

7. Références

codes sources

Petite information avant d'attaquer le programme du mois : TrollTech vient de sortir la première bêta de la version 3.1 de Qt, qui amène quelques nouveautés intéressantes. Peut-être plus de détails le mois prochain...

Revenons à notre liste simple. Comme exemple, nous allons réaliser un petit programme qui permet de remplir une liste, chaque item de la liste se voyant associé une couleur. Le résultat final se présentera ainsi :

Les bouton Ajouter et Retirer permettent respectivement d'ajouter ou de supprimer un élément de la liste, les boutons Couleur et Texte permettent de changer respectivement le texte ou la couleur de l'élément sélectionné. Tout cela nous donnera l'occasion de découvrir quelques-unes des boîtes de dialogue prédéfinies de Qt.

1. Organisation

Notre programme sera constitué de deux fichiers, plus un généré automatiquement pour nos slots. Les diverses déclarations vont être placées dans un fichier prog.h, les implémentations des méthodes de classes et de la fonction main() dans prog.cpp. Comme nous allons définir nos propres slots pour réagir aux boutons, le fichier prog.h devra être passé par l'outil moc de Qt, pour produire un fichier moc_prog.cpp :

$ moc -o moc_prog.cpp prog.h

...puis la compilation s'effectuera ainsi, pour obtenir l'exécutable prog.x :

g++ -o prog.x -I$QTDIR/include -L$QTDIR/lib -lqt moc_prog.cpp prog.cpp

Pour plus d'information sur tout cela, revoyez le deuxième article de cette série, consacré aux signaux et aux slots de Qt.

2. Le widget QListBox et ses items

La liste en elle-même est stockée et représentée par le widget QListBox, qui dérive du QScrollView que nous avons vu il y a deux numéros. De cette façon la gestion des éventuelles barres de défilement est entièrement automatisée, et nous n'avons pas à nous en préoccuper. Cette classe convient pour une liste à une seule colonne - d'où le qualificatif de « simple ». Pour des listes plus complexes, hiérarchisées ou comprenant plusieurs colonnes, il faut utiliser la classe QListView, que nous verrons plus tard.

La classe QListBox propose plusieurs méthodes nommées insertItem() pour ajouter des items, dont le contenu est soit du texte, soit un pixmap. Une même liste pouvant contenir les deux types. Les éléments de la liste sont alors des instances, soit de la classe QListBoxText, soit de la classe QListBoxPixmap, selon le cas. Ces deux classes dérivent de la classe virtuelle QListBoxItem. À tout moment, il est possible de parcourir les éléments : le premier est obtenu par la méthode firstItem() de QListBox, puis on passe de l'un à l'autre en utilisant les méthodes next() (suivant) ou prev() (précédent) de QListBoxItem. On peut également accéder aux éléments à partir de leur indice dans la liste.

Notre liste est toutefois plus riche que cela : elle affiche un dessin à coté du texte. On pourrait s'en sortir en créant pour chaque item un pixmap, dessiner dedans avec QPainter (voir l'article précédent), puis insérer le pixmap dans la liste. Une approche plus générale consiste à définir...

3. Nos items personnalisés

C'est-à-dire à dériver la classe QListBoxItem, pour que chaque item affiche exactement ce que nous voulons. Cette classe comporte une méthode virtuelle qu'il est impératif de surdéfinir : la méthode paint(), qui réalise justement le dessin de l'item dans la liste. Deux autres méthodes doivent être surdéfinies pour que la liste puisse agencer correctement nos items : width() et height(), qui renvoient la largeur et la hauteur de nos items. Pour nos besoins propres, nous avons également besoin de stocker une couleur et un texte.

Je vous propose le début suivant pour notre fichier prog.h :

01: #ifndef __PROG_H__
02: #define __PROG_H__ 1
03: #include <qwidget.h>
04: #include <qlistbox.h>
05: #include <qpushbutton.h>
06: #include <qcolor.h>
07: class clMonItem : public QListBoxItem
08: { public:
09:     clMonItem (const QString& texte, QColor coul, QListBox * listbox)
10:     : QListBoxItem(listbox), cCouleur(coul)
11:     { setText(text) ; }
12:     QColor color() const { return cCouleur ; }
13:     void setColor (QColor coul) { cCouleur = coul ; }
14:     virtual void setText (const QString& texte) { QListBoxItem::setText(texte) ; }
15:     virtual int width(const QListBox* lb) const { return lb->clipper()->width() ; }
16:     virtual int height(const QListBox*) const { return 16 ; }
17:   protected:
18:     virtual void paint(QPainter* p) ;
19:   private:
20:     QColor cCouleur ;
21: } ; // class clMonItem

J'ai intégré dans la déclaration le code des méthodes triviales.

L'intéressant est évidemment dans la méthode paint(). Son implémentation sera contenue dans le fichier prog.cpp, dont voici le début :

00001: #include <qlayout.h>
02: #include <qapplication.h>
03: #include <qpainter.h>
04: #include <qcolordialog.h>
05: #include <qinputdialog.h>
06: #include <qmessagebox.h>
07: #include "prog.h"
08: void clMonItem::paint(QPainter* p)
09: { int w = width(listBox()) ;
10:   int h = height(listBox()) ;
11:   if ( isSelected() )
12:   { p->fillRect(QRect(0, 0, w, h), listBox()->colorGroup().highlight()) ;
13:     p->setPen(listBox()->colorGroup().highlightedText()) ;
14:   }
15:   else
16:   { p->fillRect(QRect(0, 0, w, h), listBox()->colorGroup().base()) ;
17:     p->setPen(listBox()->colorGroup().text()) ;
18:   }
19:   p->setBrush(cCouleur) ;
20:   p->drawChord(QRect(2, 2, 20, h-4), 0, 5760) ;
21:   p->drawText(24, h-2, text()) ;
22: }


Un petit mot avant de continuer. Qt permet de pousser très loin la personnalisation de l'affichage, par la définition de styles et de groupes de couleurs. Pour preuve, voyez la capture d'écran au début de cet article : les boutons ont un aspect très particuliers ! En fait, leur graphisme est issu du style Liquid pour KDE 3, tel que développé par Mosfet. Sans rentrer dans les détails, chaque widget contient un groupe de couleurs, sous la forme d'une instance de la classe QColorGroup, accessible par la méthode colorGroup() de QWidget. La couleur de chaque élément constitutif de l'interface peut ainsi être obtenue, voire modifiée. Nous allons utiliser cette information pour adapter l'affichage de notre item.

Voilà notre item dessiné ! Voyons maintenant comment nous allons l'utiliser dans notre programme.

4. Le widget principal

Nous allons placer les éléments du programme dans un widget (dérivé de QWidget), agencés par un layout. Voici la déclaration de notre classe, ce qui constitue la fin du fichier prog.h :

01: class clMonWidget : public QWidget
02: { Q_OBJECT
03:   public:
04:     clMonWidget (QWidget * parent, const char* name = 0) ;
05:   public slots:
06:     void slotBtAjouter() ;
07:     void slotBtRetirer() ;
08:     void slotBtDefCouleur() ;
09:     void slotBtDefTexte() ;
10:     void slotBtQuitter() ;
11:   private:
12:     QPushButton* btAjouter ;
13:     QPushButton* btRetirer ;
14:     QPushButton* btDefCouleur ;
15:     QPushButton* btDefTexte ;
16:     QPushButton* btQuitter ;
17:     QListBox*    lbList ;
18: } ; // class clMonWidget
19: #endif // __DEMO_QLISTBOX_H__

Comme vous pouvez le constater, j'ai défini une méthode slot par bouton, ce qui impose le passage par moc évoqué au début de cet article. La donnée importante ici est lbList, qui va contenir l'instance de la liste.

L'interface sera construite dans le constructeur, en utilisant une grille. Voici le code de ce constructeur, à placer dans prog.cpp :

01: clMonWidget::clMonWidget (QWidget * parent, const char* name)
02:            : QWidget(parent, name)
03: { QGridLayout * grid = new QGridLayout(this) ;
04:   lbList = new QListBox(this) ;
05:   grid->addMultiCellWidget(lbList, 0, 0, 0, 1) ;
06:   btAjouter = new QPushButton("Ajouter", this) ;
07:   grid->addWidget(btAjouter, 1, 0) ;
08:   btRetirer = new QPushButton("Retirer", this) ;
09:   grid->addWidget(btRetirer, 1, 1) ;
10:   btDefCouleur = new QPushButton("Couleur", this) ;
11:   grid->addWidget(btDefCouleur, 2, 0) ;
12:   btDefTexte = new QPushButton("Texte", this) ;
13:   grid->addWidget(btDefTexte, 2, 1) ;
14:   btQuitter = new QPushButton("Quitter", this) ;
15:   grid->addMultiCellWidget(btQuitter, 3, 3, 0, 1) ;
16: 
17:   connect (btAjouter, SIGNAL(clicked()),
18:            this,      SLOT(slotBtAjouter())) ;
19:   connect (btRetirer, SIGNAL(clicked()),
20:            this,      SLOT(slotBtRetirer())) ;
21:   connect (btDefCouleur, SIGNAL(clicked()),
22:            this,         SLOT(slotBtDefCouleur())) ;
23:   connect (btDefTexte, SIGNAL(clicked()),
24:            this,       SLOT(slotBtDefTexte())) ;
25:   connect (btQuitter, SIGNAL(clicked()),
26:            this,      SLOT(slotBtQuitter())) ;
27: }

Rien de bien sorcier dans tout cela. Voyons le code des différents slots, en commençant par le plus simple : l'ajout d'un élément dans la liste :

void clMonWidget::slotBtAjouter()
{ clMonItem * item = new clMonItem("texte", Qt::red, lbList) ; }

Rien de plus ! Il suffit en effet de créer un item en lui donnant la liste à laquelle il appartient, et celui-ci sera automatiquement pris en charge par celle-ci. Incidemment, la liste obtient la parenté des items qu'elle contient : cela signifie que les items seront détruits par la liste lorsque celle-ci sera détruite. Inutile donc de prévoir un appel à delete. Si vous voulez « récupérer » un item, par exemple pour le déplacer d'une liste vers une autre, vous pouvez utiliser la méthode QListBox::takeItem(QListBoxItem*) : celle-ci va retirer l'item de la liste sans le détruire. Vous devrez alors l'insérer dans une autre liste, ou bien le détruire manuellement.

Si vous voulez créer des items sans donner à la création la liste dans laquelle ils seront contenus, il est toujours possible de les insérer en utilisant la méthode insertItem(QListBoxItem*, int) de QListBox. Le deuxième paramètre est l'indice où vous voulez insérer l'item, en dernière position si vous ne le renseignez pas.

Voyons la suppression d'un item. Il faut pour cela qu'un item soit sélectionné, et afficher un message d'erreur dans le cas contraire. Voici l'implémentation de cette suppression :

1: void clMonWidget::slotBtRetirer()
2: { if ( lbList->currentItem() == -1 )
3:     QMessageBox::critical(this, "Erreur", "Sélectionnez un item !",
4:         QMessageBox::Ok, QMessageBox::NoButton) ;
5:   else
6:     lbList->removeItem(lbList->currentItem()) ; }

Le changement de la couleur de l'item est assuré par la méthode slotBtDefCouleur() :

1: void clMonWidget::slotBtDefCouleur()
2: { clMonItem * item = 
3:     dynamic_cast<clMonItem*>(lbList->item(lbList->currentItem())) ;
4:   QColor coul = QColorDialog::getColor(item->color(), this) ;
5:   if ( coul.isValid() ) item->setColor(coul) ; }

Détail intéressant, normalement la boîte de sélection de couleur présente le même aspect quelque soit le système graphique sous-jacent, Windows, Unix/Linux ou Mac.

Le changement de texte se fait sur le même modèle :

1: void clMonWidget::slotBtDefTexte()
2: { clMonItem * item = 
3:     dynamic_cast<clMonItem*>(lbList->item(lbList->currentItem())) ;
4:   bool ok ;
5:   QString texte = 
6:     QInputDialog::getText("Texte de l'item", "Donnez un texte :",
7:       QLineEdit::Normal, item->text(), &ok, this) ;
8:   if ( ok ) item->setText(texte) ; }

Cette fois on utilise la méthode statique getText() de QInputDialog. Cette classe contient également des méthodes getInteger(), getDouble() et getItem() pour saisir, respectivement, un entier, un nombre réel, ou un élément d'une liste. getText() prend en premier paramètre le titre de la boîte qui sera affichée, en deuxième, un label éventuel, en troisième, le mode de saisie (parmis Normal, NoEcho pour ne rien afficher, ou Password pour afficher des étoiles), en quatrième, le texte initial. Le cinquième paramètre est l'adresse d'une variable booléenne permettant de savoir si l'utilisateur a validé ou annulé la saisie, donnez 0 si cela ne vous intéresse pas.

Enfin, dernier slot, pour demander confirmation avant de quitter :

1: void clMonWidget::slotBtQuitter()
2: { if ( QMessageBox::warning(this, "Quitter", "Vraiment quitter ?",
3:     QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes )
4:     qApp->quit() ; }

Ici nous utilisons la méthode statique warning() de QMessageBox, en donnant la liste des boutons que nous voulons voir affichés. La fonction retourne le bouton qui a été choisi par l'utilisateur : on peut donc le tester simplement pour savoir si l'utilisateur veut vraiment quitter.


Il nous reste à écrire une fonction main(), qui ne devrait plus présenter de secret pour vous :

1: int main (int argc, char* argv[])
2: { QApplication app(argc, argv) ;
3:   clMonWidget w(0) ;
4:   app.setMainWidget(&w) ;
5:   w.show() ;
6:   return app.exec() ;
7: }

5. Modes de sélection

Par défaut, la QListBox n'autorise la sélection que d'un seul item de la liste. Il est toutefois possible de changer ce comportement, avec la méthode setSelectionMode(), qui attend l'une des valeurs :

Si vous utilisez l'un des deux modes de sélection multiple, il faut naturellement en tenir compte dans votre code. Étrangement, Qt ne propose pas de mécanisme pour obtenir directement la liste des items sélectionnés : il est donc nécessaire de parcourir toute la liste. Voici par exemple quel pourrait être le code pour changer la couleur de tous les items sélectionnés :

clMonItem * item = dynamic_cast<clMonItem*>(lbList->firstItem()) ;
while ( item != 0 )
{ item->setColor(couleur) ;
  item = dynamic_cast<clMonItem*>(item->next()) ; }

Les transtypages sont nécessaires, car les méthodes QListBox::firstItem() et QListBoxItem::next() ne renvoient que des pointeurs sur QListBoxItem.

6. Conclusion

La classe QListBox comporte bien d'autres fonctionnalités, que je vous laisse découvrir en consultant la documentation de Qt. Cette classe permet de répondre à de nombreuses situations, notamment grâce à la possibilité de dériver la classe QListBoxItem qui représente ses éléments. Le mois prochain, je vous propose de découvrir l'autre classe liste de Qt : la QListView, qui offre des possibilités accrues dans le cas de listes à plusieurs colonnes et éventuellement hiérarchisées. Pour cela nous construirons un widget qui affiche une arborescence de répertoires, avec pour chacun la taille totale des fichiers qu'il contient et de ses sous-répertoires – l'occasion de découvrir deux classes non graphiques et orientées fichiers de Qt, les classes QDir et QFileInfo.

7. Références

[1] Les widgets « Liquid » de Mosfet pour KDE 3 : http://www.mosfet.org/liquid.html


Yves Bailly

http://www.kafka-fr.net


Article publié dans LinuxMagazine 43 d'octobre 2002