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...
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.
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.
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...
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.
lignes 3 à
6 : les #include
nécessaires
à la suite.
lignes 9 à 11 : le constructeur, qui appelle le constructeur ancêtre et stocke les données.
ligne 14 :
la classe QListBoxItem
contient un
mécanisme pour stocker un texte. Simplement, la méthode
setText()
est protégée
dans cette classe : il suffit de la rendre publique par ce
moyen.
ligne 15 :
cette méthode renvoit la largeur de l'item. Le paramètre
est un pointeur vers l'instance de QListBox
qui contient cet item. Ici, je considère que la largeur de
l'item est la largeur de la zone d'affichage de la liste (la méthode
clipper()
venant de QScrollView
,
dont dérive QListBox
).
Approximation qui nous suffit ici, dans la pratique il vaut mieux
donner une largeur minimale.
ligne 16 : la hauteur de notre item, ici une hauteur fixe de 16 pixels.
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: }
lignes 1 à
6 : les #include
qui seront
utilisés par la suite ; pour certains, la signification
n'apparaîtra que plus tard.
ligne 8 :
le prototype de la méthode. Remarquez qu'elle reçoit
une instance de QPainter
en paramètre :
celle-ci est fournie par la QListBox
qui contient l'item. L'origine des coordonnées correspond au
coin supérieur gauche de l'item, donc de la zone dans
laquelle nous allons dessiner.
lignes 9 et 10 : on commence par récupérer les dimensions de la zone dans laquelle nous allons dessiner, en utilisant les méthodes appropriées de notre classe.
ligne 11 : l'affichage d'un item de la liste n'est pas le même selon qu'il est sélectionné ou non. Normalement, le fond d'un item est blanc, ou bleu foncé s'il est sélectionné. Nous devons donc adapter le dessin aux deux situations.
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.
lignes 12 et 13
: initialisation correspondant au cas où l'item est
sélectionné. La première ligne efface toute la
zone avec QPainter::fillRect()
, avec la
couleur normalement utilisée pour le fond lorsque «
quelque chose » est sélectionné (par exemple,
dans un champ de saisie de texte) : cette couleur est obtenue par
QColorGroup::highlight()
. La deuxième
ligne définie la couleur de dessin du QPainter
,
à partir de la couleur dans laquelle est affiché ce
qui est sélectionné :
QColorGroup::hightlightedText()
.
lignes 16 et 17
: le cas normal. Le fond normal (en général, blanc)
est obtenu par QColorGroup::base()
, la
couleur normale (en général, noir) est obtenu par
QColorGroup::text()
.
ligne 19 :
la couleur de remplissage pour le QPainter
,
que nous voulons dans tous les cas de la couleur associée à
notre item.
lignes 20 et 21
: le dessin lui-même. La méthode QPainter::drawChord()
dessine un arc d'éllipse. Celui-ci est contenu dans le
rectangle passé en premier paramètre. On donne ensuite
l'angle de départ et l'angle d'arrivée, exprimés
en 16èmes de degré (donc, pour avoir 360 degrés,
on donne 360*16 = 5760). Remarquez que nous laissons une petite
marge de deux pixels autour de l'éllipse.
Voilà notre item dessiné ! Voyons maintenant comment nous allons l'utiliser dans notre programme.
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()) ; }
ligne 2 :
la méthode currentItem()
de
QListBox
renvoit l'indice de l'item
actuellement sélectionné, ou -1 si aucun n'est
sélectionné.
lignes 3 et 4
: la classe QMessageBox
contient
quelques méthodes statiques pour afficher un message simple à
l'utilisateur, avec un ou plusieurs boutons. Ces méthodes
sont information()
, warning()
et critical()
. Elles se distinguent
essentiellement par l'icône qui est affichée, selon la
nature du message, respectivement :
ou
ou
(les icônes pouvant s'adapter selon le système). Le
dernier paramètre indique que nous ne voulons pas d'autre
bouton que le bouton "Ok".
ligne 6 :
la suppression de l'item, donné par son indice. L'item est
non seulement retiré de la liste, mais l'instance
correspondante de QListBoxItem
(ou
dérivée !) est également détruite.
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) ; }
lignes 2 et 3
: on commence par récupérer l'item sélectionné.
La méthode QListBox::item()
permet d'obtenir l'instance de QListBoxItem
à partir de son indice. Comme nos items sont des clMonItem
qui dérivent de QListBoxItem, nous devons transtyper le
résultat. Il eût été bon de vérifier
comme précédemment que nous avions un item
sélectionné : ce test a été omis
pour raccourcir le code, je vous laisse le rajouter.
lignes 4 et 5
: la classe QColorDialog
contient une
méthode statique, getColor()
,
qui affiche une boîte de sélection de couleur. Le
premier paramètre (optionnel) est la couleur initiale. Une
autre méthode, getRgba()
, permet
en outre de demander la composante de transparence (alpha
channel). Ces méthode retournent une couleur sous la
forme d'une instance de QColor
. Si
l'utilisateur a annulé sa sélection, la couleur
renvoyée est marquée invalide : d'où le
test en ligne 5.
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: }
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 :
QListBox::Single
: un seul item peut être sélectionné ;
QListBox::Multi
: plusieurs items peuvent être sélectionnés,
simplement en cliquant dessus ;
QListBox::Extended
: mode hybride, ou un simple clic sélectionne un item et
déselectionne les autres ; la sélection multiple est
faite en utilisant les touches Control
(sélection d'items quelconque) ou Shift
(sélection par plage d'items) ;
QListBox::NoSelection
: les items ne peuvent pas être sélectionnés.
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
.
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
.
[1] Les widgets «
Liquid » de Mosfet pour KDE 3 :
http://www.mosfet.org/liquid.html
Yves Bailly
Article publié dans LinuxMagazine 43 d'octobre 2002