Vous êtes sur la page 1sur 571

CampusPress Rfrence

Qt 4
et
++
C
Programmation dinterfaces GUI

Jasmin Blanchette et Mark Summerfield


Prface de Matthias Ettrich

Rseaux
et tlcom

Programmation

Gnie logiciel

Scurit

Systme
dexploitation

Qt 4 Livre Page I Jeudi, 7. dcembre 2006 12:14 12

Qt4 et C++
Programmation dinterfaces GUI

Jasmin Blanchette
et Mark Summerfield

Qt 4 Livre Page II Jeudi, 7. dcembre 2006 12:14 12

CampusPress a apport le plus grand soin la ralisation de ce livre afin de vous fournir une information complte et fiable. Cependant, CampusPress nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient
rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions
thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle.
CampusPress ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de
quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs
propritaires respectifs.
Publi par CampusPress
47 bis, rue des Vinaigriers
75010 PARIS
Tl. : 01 72 74 90 00

Titre original : C++ GUI programming with Qt 4,


Traduit de lamricain par Christine Eberhardt,
Chantal Kolb, Dorothe Sittler

Mise en pages : TyPAO

ISBN original : 0-13-187249-4


Copyright 2006 Trolltech S.A.

ISBN : 978-2-7440-4092-4
Copyright 2009 Pearson Education France
Tous droits rservs
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from Pearson Education, Inc.
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a)
du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France
ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code.

Qt 4 Livre Page III Jeudi, 7. dcembre 2006 12:14 12

Table des matires

A propos des auteurs .............................

VII

Implmenter le menu File ................

57

Avant-propos ..........................................

IX

Utiliser des botes de dialogue ...........

64

Prface ....................................................

XI

Stocker des paramtres .......................

70

Remerciements ......................................

XIII

Documents multiples .........................

72

Pages daccueil ...................................

75

Bref historique de Qt ............................

XV

Partie I - Qt : notions de base................

CHAPITRE 1. Pour dbuter ....................


Hello Qt .............................................
Etablir des connexions .......................
Disposer des widgets ..........................
Utiliser la documentation de rfrence

3
4
6
7
10

Le widget central ................................

78

Drivation de QTableWidget .............

79

Chargement et sauvegarde .................

85

Implmenter le menu Edit .................

88

Implmenter les autres menus ............

92

CHAPITRE 2. Crer des botes de dialogue 15


Drivation de QDialog ......................
16
Description dtaille des signaux et slots 22
Conception rapide
dune bote de dialogue ......................
25
Botes de dialogue multiformes .........
32
Botes de dialogue dynamiques .........
39
Classes de widgets et de botes de dialogue
intgres ............................................
40

Drivation de QTableWidgetItem .....

96

CHAPITRE 5. Crer des widgets


personnaliss .....................................

105

Personnaliser des widgets Qt .............

106

Driver QWidget ...............................

108

Intgrer des widgets personnaliss


avec le Qt Designer ...........................

118

Double mise en mmoire tampon ......

122

Partie II - Qt : niveau intermdiaire ....

141

CHAPITRE 6. Gestion des dispositions ..

143

CHAPITRE 3. Crer des fentres


principales .........................................
Drivation de QMainWindow ...........
Crer des menus et des barres doutils
Configurer la barre dtat ...................

45
46
51
56

CHAPITRE 4. Implmenter la fonctionnalit


dapplication .....................................
77

Disposer des widgets sur un formulaire

144

Dispositions empiles ........................

150

Sparateurs .........................................

152

Qt 4 Livre Page IV Jeudi, 7. dcembre 2006 12:14 12

IV

Qt4 et C++ : Programmation dinterfaces GUI

Zones droulantes .....................................

155

CHAPITRE 13. Les bases de donnes ............

309

Widgets et barres doutils ancrables ........

157

Connexion et excution de requtes .........

310

MDI (Multiple Document Interface) ........

159

CHAPITRE 7. Traitement des vnements ....

169

Prsenter les donnes sous une forme


tabulaire ....................................................

317

Implmenter des formulaires matre/dtail

321

Rimplmenter les gestionnaires


dvnements ............................................

170

CHAPITRE 14. Gestion de rseau ................

329

Installer des filtres dvnements ..............

175

Programmer les clients FTP ....................

330

Rester ractif pendant un traitement intensif

178

Programmer les clients HTTP .................

339

CHAPITRE 8. Graphiques 2D et 3D ..............

183

Dessiner avec QPainter .............................

184

Programmer les applications


client/serveur TCP ...................................

342

Transformations du painter .......................

188

Envoi et rception de datagrammes UDP .

353

Affichage de haute qualit avec QImage .

197

CHAPITRE 15. XML ......................................

359

Impression ................................................

199

Lire du code XML avec SAX ...................

360

Graphiques avec OpenGL .........................

207

Lire du code XML avec DOM ..................

365

CHAPITRE 9. Glisser-dposer .......................

213

Ecrire du code XML .................................

370

Activer le glisser-dposer ..........................

214

CHAPITRE 16. Aide en ligne .........................

373

Prendre en charge les types personnaliss


de glisser ...................................................

219

Infobulles, informations dtat et aide


"Quest-ce que cest ?" .............................

374

Grer le presse-papiers ..............................

224

Utilisation de QTextBrowser comme moteur


daide simple .............................................

376

Utilisation de lassistant pour une aide


en ligne puissante ......................................

379

Partie III - Qt : tude avance ....................

383

CHAPITRE 17. Internationalisation ..............

385

Travailler avec Unicode ............................

386

Crer des applications ouvertes


aux traductions .........................................

390

CHAPITRE 10. Classes daf chage dlments 227


Utiliser les classes ddies laffichage
dlments .................................................

229

Utiliser des modles prdfinis .................

236

Implmenter des modles personnaliss ...

241

Implmenter des dlgus personnaliss ..

256

CHAPITRE 11. Classes conteneur .................

263

Conteneurs squentiels ............................

264

Conteneurs associatifs ..............................

273

Algorithmes gnriques ...........................

276

Passer dynamiquement dune langue


une autre .................................................

396

Chanes, tableaux doctets et variants .......

278

Traduire les applications ..........................

402

CHAPITRE 12. Entres/Sorties ......................

287

Lire et crire des donnes binaires ............

288

Crer des threads ......................................

408

Lire et crire du texte ................................

294

Synchroniser des threads ..........................

411

Parcourir les rpertoires ............................

300

Communiquer avec le thread principal ....

418

Intgration des ressources ........................

302

Communication inter-processus ...............

303

Utiliser les classes Qt dans les threads


secondaires ...............................................

423

CHAPITRE 18. Environnement multithread

407

Qt 4 Livre Page V Jeudi, 7. dcembre 2006 12:14 12

Table des matires

CHAPITRE 19. Crer des plug-in ..................

425

Dvelopper Qt avec les plug-in ................

426

Crer des applications capables de grer


les plug-in ................................................

435

Ecrire des plug-in dapplication ...............

439

CHAPITRE 20. Fonctionnalits


spci ques
la plate-forme .......................................

443

Construire une interface avec les API natives

444

ActiveX sous Windows .............................

448

Prendre en charge la gestion de session X11

461

CHAPITRE 21. Programmation embarque .

469

Dmarrer avec Qtopia ...............................

470

ANNEXE A. Installer Qt ................................

477

A propos des licences .............................

478

Installer Qt/Windows ................................

478

Installer Qt/Mac ........................................

479

ANNEXE B. INTRODUCTION AU LANGAGE C++


POUR LES PROGRAMMEURS JAVA ET C# .......... 483.
Dmarrer avec C++ ................................... 484
Principales diffrences de langage ............ 489
Les types de donnes primitifs ................ 489
Dfinitions de classe .............................. 490
Les pointeurs ......................................... 497
Les rfrences ....................................... 500
Les tableaux .......................................... 502
Les chanes de caractres ...................... 505
Les numrations .................................. 507
TypeDef ................................................. 509
Conversions de type ............................... 509
Surcharge doprateur ........................... 512
Les types valeur ..................................... 514
Variables globales et fonctions ............... 516
Espaces de noms ................................... 518
Le prprocesseur ................................... 520
La bibliothque C++ standard .................. 523

Installer Qt/X11 ........................................

479

Index .............................................................

529

Qt 4 Livre Page VI Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page VII Jeudi, 7. dcembre 2006 12:14 12

A propos des auteurs

Jasmin Blanchette

Jasmin a obtenu un diplme dinformatique en 2001 lUniversit de Sherbrooke,


Qubec. Il a fait un stage chez Trolltech durant lt 2000 en tant quingnieur logiciel
et travaille dans cette socit depuis dbut 2001. En 2003, Jasmin a co-crit C++ GUI
Programming with Qt 3. Il assume dsormais la double responsabilit de directeur de la
documentation et dingnieur logiciel senior chez Trolltech. Il a dirig la conception de
loutil de traduction Qt Linguist et il reste un acteur cl dans la conception des classes
conteneur de Qt 4. Il est galement le co-diteur de Qt Quarterly, la lettre dinformation
technique de Trolltech.
Mark Summerfield

Mark a obtenu un diplme dinformatique en 1993 lUniversit de Wales Swansea.


Il sest ensuite consacr une anne de recherche avant de se lancer dans le monde du
travail. Il a travaill pendant plusieurs annes comme ingnieur logiciel pour diverses
entreprises avant de rejoindre Trolltech. Il a t directeur de la documentation de Trolltech pendant prs de trois ans, priode pendant laquelle il a cr Qt Quarterly et co-crit
C++ GUI Programming with Qt 3. Mark dtient un Qtraining.eu et est formateur et
consultant indpendant spcialis en langage C++, Qt et Python.

Qt 4 Livre Page VIII Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page IX Jeudi, 7. dcembre 2006 12:14 12

Avant-propos

Pourquoi Qt ? Pourquoi des programmeurs comme nous en viennent-ils choisir Qt ?


Bien entendu, les rponses sont videntes : la compatibilit de Qt, les nombreuses fonctionnalits, les performances du C++, la disponibilit du code source, la documentation,
le support technique de grande qualit et tous les autres lments mentionns dans les
documents marketing de Trolltech. Pour finir, voici laspect le plus important : Qt
rencontre un tel succs, parce que les programmeurs lapprcient.
Pourquoi des programmeurs vont-ils apprcier une technologie et pas une autre ? Je
pense personnellement que les ingnieurs logiciel aiment une technologie qui semble
bonne, mais napprcient pas celles qui ne le sont pas. "Bonne" dans ce cas peut avoir
diverses significations. Dans ldition Qt 3 du livre, jai voqu le systme tlphonique
de Trolltech comme tant un exemple particulirement bon dune technologie particulirement mauvaise. Le systme tlphonique ntait pas bon, parce quil nous obligeait
effectuer des tches apparemment alatoires qui dpendaient dun contexte galement
alatoire. Le hasard nest pas une bonne chose. La rptition et la redondance sont
dautres aspects qui ne sont pas bons non plus. Les bons programmeurs sont fainants.
Ce que nous aimons dans linformatique par rapport au jardinage par exemple, cest que
nous ne sommes pas contraints de faire continuellement les mmes choses.
Laissez-moi approfondir sur ce point laide dun exemple issu du monde rel : les
notes de frais. En gnral, ces notes de frais se prsentent sous forme de tableurs
complexes ; vous les remplissez et vous recevez votre argent en retour. Technologie
simple me direz-vous. De plus, vu lincitation pcuniaire, cela devrait constituer une
tche aise pour un ingnieur expriment.
Toutefois, la ralit est bien diffrente. Alors que personne dautre dans lentreprise ne
semble avoir de soucis pour remplir ces formulaires, les ingnieurs en ont. Et pour en
avoir discut avec des salaris dautres entreprises, cela parat tre un comportement
commun. Nous repoussons le remboursement jusquau dernier moment, et il arrive
mme parfois que nous loublions. Pourquoi ? Au vu de notre formulaire, cest une
procdure standard simple. La personne doit rassembler les reus, les numroter et insrer

Qt 4 Livre Page X Jeudi, 7. dcembre 2006 12:14 12

Qt4 et C++ : Programmation dinterfaces GUI

ces numros dans les champs appropris avec la date, lendroit, une description et le montant.
La numrotation et la copie sont conues pour faciliter la tche, mais ce sont des oprations
redondantes, sachant que la date, lendroit, la description et le montant identifient sans aucun doute
un reu. On pourrait penser quil sagit dun bien petit travail pour obtenir un remboursement.
Le tarif journalier qui dpend du lieu de sjour est quelque peu embarrassant. Il existe des
documents quelque part qui rpertorient les tarifs normaliss pour les divers lieux de sjour.
Vous ne pouvez pas simplement choisir "Chicago" ; vous devez rechercher le tarif correspondant Chicago vous-mme. Il en va de mme pour le champ correspondant au taux de change.
La personne doit trouver le taux de change en vigueur peut-tre laide de Google puis
saisir le taux dans chaque champ. En clair, vous devez attendre que votre banque vous fasse
parvenir une lettre dans laquelle ils vous informent du taux de change appliqu. Mme si
lopration ne savre pas trs complique, il nest pas commode de rechercher toutes ces
informations dans diffrentes sources, puis de les recopier plusieurs endroits dans le formulaire.
La programmation peut normment ressembler ces notes de frais, mais en pire. Et cest l
que Qt vient votre rescousse. Qt est diffrent. Dun ct, Qt est logique. Dun autre, Qt est
amusant. Qt vous permet de vous concentrer sur vos tches. Quand les architectes de Qt
rencontraient un problme, ils ne cherchaient pas uniquement une solution convenable ou la
solution la plus simple. Ils recherchaient la bonne solution, puis lexpliquaient. Cest vrai
quils faisaient des erreurs et que certaines de leurs dcisions de conception ne rsistaient pas
au temps, mais ils proposaient quand mme beaucoup de bonnes choses et les mauvais cts
pouvaient toujours tre amliors. Imaginez quun systme conu lorigine pour mettre en
relation Windows 95 et Unix/Motif unifie dsormais des systmes de bureau modernes aussi
divers que Windows XP, Mac OS X et GNU/Linux, et pose les fondements de la plate-forme
applicative Qtopia pour Linux Embarqu.
Bien avant que Qt ne devienne ce produit si populaire et si largement utilis, la dvotion avec
laquelle les dveloppeurs de ce framework recherchaient les bonnes solutions rendait Qt vraiment
particulier. Ce dvouement est toujours aussi fort aujourdhui et affecte quiconque dveloppe
et assure la maintenance de Qt. Pour nous, travailler avec Qt constitue une grande responsabilit et un privilge. Nous sommes fiers de contribuer rendre vos existences professionnelles
et open source plus simples et plus agrables.
Matthias Ettrich
Oslo, Norvge
Juin 2006

Qt 4 Livre Page XI Jeudi, 7. dcembre 2006 12:14 12

Prface

Qt est un framework C++ permettant de dvelopper des applications GUI multiplatesformes en se basant sur lapproche suivante : "Ecrire une fois, compiler nimporte o."
Qt permet aux programmeurs demployer une seule arborescence source pour des applications qui sexcuteront sous Windows 98 XP, Mac OS X, Linux, Solaris, HP-UX, et
de nombreuses autres versions dUnix avec X11. Les bibliothques et outils Qt font
galement partie de Qtopia Core, un produit qui propose son propre systme de fentrage au-dessus de Linux Embarqu.
Le but de ce livre est de vous apprendre crire des programmes GUI avec Qt 4. Le
livre commence par "Hello Qt" et progresse rapidement vers des sujets plus avancs,
tels que la cration de widgets personnaliss et le glisser-dposer. Le code source des
exemples de programmes est tlchargeable sur le site Pearson, www.pearson.fr, la
page ddie cet ouvrage. LAnnexe A vous explique comment installer le logiciel.
Ce manuel se divise en trois parties. La Partie I traite de toutes les notions et pratiques
ncessaires pour programmer des applications GUI avec Qt. Si vous ntudiez que cette
partie, vous serez dj en mesure dcrire des applications GUI utiles. La Partie II aborde
des thmes importants de Qt trs en dtail et la Partie III propose des sujets plus spcialiss et plus avancs. Vous pouvez lire les chapitres des Parties II et III dans nimporte
quel ordre, mais cela suppose que vous connaissez dj le contenu de la Partie I.
Les lecteurs de ldition Qt 3 de ce livre trouveront cette nouvelle dition familire tant
au niveau du contenu que du style. Cette dition a t mise jour pour profiter des
nouvelles fonctionnalits de Qt 4 (y compris certaines qui sont apparues avec Qt 4.1) et
pour prsenter du code qui illustre les techniques de programmation Qt 4. Dans la
plupart des cas, nous avons utilis des exemples similaires ceux de ldition Qt 3. Les
nouveaux lecteurs nen seront pas affects, mais ceux qui ont lu la version prcdente
pourront mieux se reprer dans le style plus clair, plus propre et plus expressif de Qt 4.

Qt 4 Livre Page XII Jeudi, 7. dcembre 2006 12:14 12

XII

Qt4 et C++ : Programmation dinterfaces GUI

Cette dition comporte de nouveaux chapitres analysant larchitecture modle/vue de Qt 4, le


nouveau framework de plug-in et la programmation intgre avec Qtopia, ainsi quune
nouvelle annexe. Et comme dans ldition Qt 3, nous nous sommes concentrs sur lexplication de la programmation Qt plutt que de simplement hacher ou rcapituler la documentation
en ligne de Qt.
Nous avons crit ce livre en supposant que vous connaissez les langages C++, Java ou C#. Les
exemples de code utilisent un sous-ensemble du langage C++, tout en vitant de multiples
fonctions C++ rarement ncessaires en programmation Qt. L o il ntait pas possible dviter
une construction C++ plus avance, nous vous avons expliqu son utilisation.
Si vous connaissez dj le langage Java ou C# mais que vous avez peu dexprience en langage
C++, nous vous recommandons de commencer par la lecture de lAnnexe B, qui vous offre une
introduction au langage C++ dans le but de pouvoir utiliser ce manuel. Pour une introduction
plus pousse la programmation oriente objet en C++, nous vous conseillons les livres C++
How to Program de Harvey Deitel et Paul Deitel, et C++Primer de Stanley B. Lippman, Jose
Lajoie, et Barbara E. Moo.
Qt a bti sa rputation de framework multiplate-forme, mais en raison de son API intuitive et
puissante, de nombreuses organisations utilisent Qt pour un dveloppement sur une seule
plate-forme. Adobe Photoshop Album est un exemple dapplication Windows de masse crite
en Qt. De multiples logiciels sophistiqus dentreprise, comme les outils danimation 3D, le
traitement de films numriques, la conception automatise lectronique (pour concevoir des
circuits), lexploration de ptrole et de gaz, les services financiers et limagerie mdicale sont
gnrs avec Qt. Si vous travaillez avec un bon produit Windows crit en Qt, vous pouvez facilement conqurir de nouveaux marchs dans les univers Mac OS X et Linux simplement grce
la recompilation.
Qt est disponible sous diverses licences. Si vous souhaitez concevoir des applications commerciales, vous devez acheter une licence Qt commerciale ; si vous voulez gnrer des programmes open source, vous avez la possibilit dutiliser ldition open source (GPL). Qt constitue la
base sur laquelle sont conus lenvironnement KDE (K Desktop Environment) et les nombreuses
applications open source qui y sont lies.
En plus des centaines de classes Qt, il existe des compagnons qui tendent la porte et la puissance de Qt. Certains de ces produits, tels que QSA (Qt Script for Applications) et les composants Qt Solutions, sont disponibles par le biais de Trolltech, alors que dautres sont fournis par
dautres socits et par la communaut open source. Consultez le site http://www.trolltech.com/products/3rdparty/ pour plus dinformations sur les compagnons Qt. Qt possde
galement une communaut bien tablie et prospre dutilisateurs qui emploie la liste de diffusion
qt-interest; voir http://lists.trolltech.com/ pour davantage de dtails.
Si vous reprez des erreurs dans ce livre, si vous avez des suggestions pour la prochaine dition
ou si vous voulez simplement faire un commentaire, nhsitez pas nous contacter. Vous
pouvez nous joindre par mail ladresse suivante : qt-book@trolltech.com. Un erratum sera
disponible sur le site http://doc.trolltech.com/qt-book-errata.html.

Qt 4 Livre Page XIII Jeudi, 7. dcembre 2006 12:14 12

Remerciements

Nous tenons tout dabord remercier Eirik Chambe-Eng, le prsident de Trolltech.


Eirik ne nous a pas uniquement encourag avec enthousiasme crire ldition Qt 3 du
livre, il nous a aussi permis de passer un temps considrable lcrire. Eirik et Haavard
Nord, le chef de la direction de Trolltech, ont lu le manuscrit et lont approuv. Matthias
Ettrich, le dveloppeur en chef de Trolltech, a galement fait preuve dune grande gnrosit. Il a allgrement accept que nous manquions nos tches lorsque nous tions
obnubils par lcriture de la premire dition de ce livre et nous a fortement conseill
de sorte adopter un bon style de programmation Qt.
Pour ldition Qt 3, nous avons demand deux utilisateurs Qt, Paul Curtis et Klaus
Schmidinger, de devenir nos critiques externes. Ce sont tous les deux des experts Qt trs
pointilleux sur les dtails techniques qui ont prouv leur sens du dtail en reprant des
erreurs trs subtiles dans notre manuscrit et en suggrant de nombreuses amliorations.
En plus de Matthias, Reginald Stadlbauer tait notre plus loyal critique chez Trolltech.
Ses connaissances techniques sont inestimables et il nous a appris faire certaines
choses dans Qt que nous ne pensions mme pas possible.
Pour cette dition Qt 4, nous avons galement profit de laide et du support dEirik,
Haavard et Matthias. Klaus Schmidinger nous a aussi fait bnficier de ses remarques et
chez Trolltech, nos principaux critiques taient Andreas Aardal Hanssen, Henrik Hartz,
Vivi Glckstad Karlsen, Trenton Schulz, Andy Shaw et Pl de Vibe.
En plus des critiques mentionns ci-dessus, nous avons reu laide experte de Harald
Fernengel (bases de donnes), Volker Hilsheimer (ActiveX), Bradley Hughes
(multithread), Trond Kjernsen (graphiques 3D et bases de donnes), Lars Knoll
(graphiques 2D et internationalisation), Sam Magnuson (qmake), Marius Bugge
Monsen (classes daffichage dlments), Dimitri Papadopoulos (Qt/X11), Paul Olav
Tvete (widgets personnaliss et programmation intgre), Rainer Schmid (mise en
rseau et XML), Amrit Pal Singh (introduction C++) et Gunnar Sletta (graphiques 2D
et traitement dvnements).

Qt 4 Livre Page XIV Jeudi, 7. dcembre 2006 12:14 12

XIV

Qt4 et C++ : Programmation dinterfaces GUI

Nous tenons aussi remercier tout particulirement les quipes de Trolltech en charge de la
documentation et du support pour avoir gr tous les problmes lis la documentation lorsque
le livre nous prenait la majorit de notre temps, de mme que les administrateurs systme de
Trolltech pour avoir laisser nos machines en excution et nos rseaux en communication tout
au long du projet.
Du ct de la production, Trenton Schulz a cr le CD compagnon de l'dition imprime de
cet ouvrage et Cathrine Bore de Trolltech a gr les contrats et les lgalits pour notre compte.
Un grand merci galement Nathan Clement pour les illustrations. Et enfin, nous remercions
Lara Wysong de Pearson pour avoir aussi bien gr les dtails pratiques de la production.

Qt 4 Livre Page XV Jeudi, 7. dcembre 2006 12:14 12

Bref historique de Qt

Qt a t mis disposition du public pour la premire fois en mai 1995. Il a t dvelopp


lorigine par Haavard Nord (le chef de la direction de Trolltech) et Eirik Chambe-Eng
(le prsident de Trolltech). Haavard et Eirik se sont rencontrs lInstitut Norvgien de
Technologie de Trondheim, do ils sont diplms dun master en informatique.
Lintrt dHaavard pour le dveloppement C++ dinterfaces graphiques utilisateurs
(GUI) a dbut en 1988 quand il a t charg par une entreprise sudoise de dvelopper
un framework GUI en langage C++. Quelques annes plus tard, pendant lt 1990,
Haavard et Eirik travaillaient ensemble sur une application C++ de base de donnes
pour les images ultrasons. Le systme devait pouvoir sexcuter avec une GUI sous
Unix, Macintosh et Windows. Un jour de cet t-l, Haavard et Eirik sont sortis profiter
du soleil, et lorsquils taient assis sur un banc dans un parc, Haavard a dit, "Nous avons
besoin dun systme daffichage orient objet". La discussion en rsultant a pos les
bases intellectuelles dune GUI multiplate-forme oriente objet quils ne tarderaient pas
concevoir.
En 1991, Haavard a commenc crire les classes qui deviendraient Qt, tout en collaborant avec Eirik sur la conception. Lanne suivante, Eirik a eu lide des "signaux et
slots", un paradigme simple mais puissant de programmation GUI qui est dsormais
adopt par de nombreux autres kits doutils. Haavard a repris cette ide pour en produire
une implmentation code. En 1993, Haavard et Eirik ont dvelopp le premier noyau
graphique de Qt et taient en mesure dimplmenter leurs propres widgets. A la fin de
lanne, Haavard a suggr de crer une entreprise dans le but de concevoir "le meilleur
framework GUI en langage C++".
Lanne 1994 a commenc sous de mauvais auspices avec deux jeunes programmeurs
souhaitant simplanter sur un march bien tabli, sans clients, avec un produit inachev
et sans argent. Heureusement, leurs pouses taient leurs employes et pouvaient donc
soutenir leurs maris pendant les deux annes ncessaires selon Eirik et Haavard pour
dvelopper le produit et enfin commencer percevoir un salaire.

Qt 4 Livre Page XVI Jeudi, 7. dcembre 2006 12:14 12

XVI

Qt4 et C++ : Programmation dinterfaces GUI

La lettre "Q" a t choisie comme prfixe de classe parce que ce caractre tait joli dans lcriture Emacs de Haavard. Le "t" a t ajout pour reprsenter "toolkit" inspir par Xt, the X
Toolkit. La socit a t cre le 4 mars 1994, tout dabord sous le nom Quasar Technologies,
puis Troll Tech et enfin Trolltech.
En avril 1995, grce lun des professeurs de Haavard, lentreprise norvgienne Metis a sign
un contrat avec eux pour dvelopper un logiciel bas sur Qt. A cette priode, Trolltech a
embauch Arnt Gulbrandsen, qui, pendant six ans, a imagin et implment un systme de
documentation ingnieux et a contribu llaboration du code de Qt.
Le 20 mai 1995, Qt 0.90 a t tlcharg sur sunsite.unc.edu. Six jours plus tard, la publication a t annonce sur comp.os.linux.announce. Ce fut la premire version publique de Qt.
Qt pouvait tre utilis pour un dveloppement sous Windows et Unix, proposant la mme API
sur les deux plates-formes. Qt tait disponible sous deux licences ds le dbut : une licence
commerciale tait ncessaire pour un dveloppement commercial et une dition gratuite tait
disponible pour un dveloppement open source. Le contrat avec Metis a permis Trolltech de
rester flot, alors que personne na achet de licence commerciale Qt pendant dix longs mois.
En mars 1996, lAgence spatiale europenne est devenue le deuxime client Qt, en achetant
dix licences commerciales. Eirik et Haavard y croyaient toujours aussi fortement et ont embauch un autre dveloppeur. Qt 0.97 a t publi fin mai et le 24 septembre 1996, Qt 1.0 a fait son
apparition. A la fin de la mme anne, Qt atteignait la version 1.1 : huit clients, chacun venant
dun pays diffrent, ont achet 18 licences au total. Cette anne a galement vu la naissance du
projet KDE, men par Matthias Ettrich.
Qt 1.2 a t publi en avril 1997. Matthias Ettrich a dcid dutiliser Qt pour concevoir un
environnement KDE, ce qui a favoris llvation de Qt au rang de standard de facto pour le
dveloppement GUI C++ sous Linux. Qt 1.3 a t publi en septembre 1997.
Matthias a rejoint Trolltech en 1998 et la dernire version principale de Qt 1, 1.40, a t conue
en septembre de la mme anne. Qt 2.0 a t publi en juin 1999. Qt 2 avait une nouvelle
licence open source, QPL (Q Public License), conforme la dfinition OSD (Open Source
De?nition). En aot 1999, Qt a reu le prix LinuxWorld en tant que meilleure bibliothque/
outil. A cette priode-l, Trolltech Pty Ltd (Australie) a t fonde.
Trolltech a publi Qtopia Core (appel ensuite Qt/Embedded) en 2000. Il tait conu pour
sexcuter sur les priphriques Linux Embarqu et proposait son propre systme de fentrage
en remplacement de X11. Qt/X11 et Qtopia Core taient dsormais proposs sous la licence
GNU GPL (General Public License) fortement utilise, de mme que sous des licences
commerciales. A la fin de lanne 2000, Trolltech a fond Trolltech Inc. (USA) et a publi la
premire version de Qtopia, une plate-forme applicative pour tlphones mobiles et PDA.
Qtopia Core a remport le prix LinuxWorld "Best Embedded Linux Solution" en 2001 et 2002,
et Qtopia Phone a obtenu la mme rcompense en 2004.
Qt 3.0 a t publi en 2001. Qt est dsormais disponible sous Windows, Mac OS X, Unix et
Linux (Desktop et Embarqu). Qt 3 proposait 42 nouvelles classes et son code slevait
500 000 lignes. Qt 3 a constitu une avance incroyable par rapport Qt 2 : un support local et

Qt 4 Livre Page XVII Jeudi, 7. dcembre 2006 12:14 12

Bref historique de Qt

XVII

Unicode largement amlior, un affichage de texte et une modification de widget totalement


innovant et une classe dexpressions rgulires de type Perl. Qt 3 a remport le prix "Jolt
Productivity Award" de Software Development Times en 2002.
En t 2005, Qt 4.0 a t publi. Avec prs de 500 classes et plus de 9000 fonctions, Qt 4 est
plus riche et plus important que nimporte quelle version antrieure. Il a t divis en plusieurs
bibliothques, de sorte que les dveloppeurs ne doivent se rattacher quaux parties de Qt dont
ils ont besoin. Qt 4 constitue une progression significative par rapport aux versions antrieures
avec diverses amliorations : un ensemble totalement nouveau de conteneurs template efficaces
et faciles demploi, une fonctionnalit avance modle/vue, un framework de dessin rapide et
flexible en 2D et des classes de modification et daffichage de texte Unicode, sans mentionner
les milliers de petites amliorations parmi toutes les classes Qt. Qt 4 est la premire dition Qt
disponible pour le dveloppement commercial et open source sur toutes les plates-formes quil
supporte.
Toujours en 2005, Trolltech a ouvert une agence Beijing pour proposer aux clients de Chine
et alentours un service de vente et de formation et un support technique pour Qtopia.
Depuis la cration de Trolltech, la popularit de Qt na cess de saccrotre et continue
gagner du terrain aujourdhui. Ce succs est une rflexion sur la qualit de Qt et sur la joie que
son utilisation procure. Pendant la dernire dcennie, Qt est pass du statut de produit utilis
par quelques "connaisseurs" un produit utilis quotidiennement par des milliers de clients et
des dizaines de milliers de dveloppeurs open source dans le monde entier.

Qt 4 Livre Page XVIII Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 1 Jeudi, 7. dcembre 2006 12:14 12

I
Qt : notions de base
1

Pour dbuter

Crer des botes de dialogue

Crer des fentres principales

Implmenter la fonctionnalit dapplication

Crer des widgets personnaliss

Qt 4 Livre Page 2 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 3 Jeudi, 7. dcembre 2006 12:14 12

1
Pour dbuter
Au sommaire de ce chapitre
Utiliser Hello Qt
Etablir des connexions
Disposer des widgets
Utiliser la documentation
de rfrence

Ce chapitre vous prsente comment combiner le langage C++ de base la fonctionnalit


fournie par Qt dans le but de crer quelques petites applications GUI ( interface utilisateur graphique). Ce chapitre introduit galement deux notions primordiales concernant
Qt : les "signaux et slots" et les dispositions. Dans le Chapitre 2, vous approfondirez ces
points, et dans le Chapitre 3, vous commencerez concevoir une application plus raliste.
Si vous connaissez dj les langages Java ou C#, mais que vous ne disposez que dune
maigre exprience concernant C++, nous vous recommandons de lire tout dabord
lintroduction C++ en Annexe B.

Qt 4 Livre Page 4 Jeudi, 7. dcembre 2006 12:14 12

Qt4 et C++ : Programmation dinterfaces GUI

Hello Qt
Commenons par un programme Qt trs simple. Nous ltudierons dabord ligne par ligne,
puis nous verrons comment le compiler et lexcuter.
1
2

#include <QApplication>
#include <QLabel>

3
4
5
6
7
8
9

int main(int argc, char *argv[])


{
QApplication app(argc, argv);
QLabel *label = new QLabel("Hello Qt!");
label->show();
return app.exec();
}

Les lignes 1 et 2 contiennent les dfinitions des classes QApplication et QLabel. Pour
chaque classe Qt, il y a un fichier den-tte qui porte le mme nom (en respectant la casse) que
la classe qui renferme la dfinition de classe.
La ligne 5 cre un objet QApplication pour grer les ressources au niveau de lapplication.
Le constructeur QApplication exige argc et argv parce que Qt prend personnellement en
charge quelques arguments de ligne de commande.
La ligne 6 cre un widget QLabel qui affiche "Hello Qt !". Dans la terminologie Qt et Unix, un
widget est un lment visuel dans une interface utilisateur. Ce terme provient de "gadget pour
fentres" et correspond "contrle" et "conteneur" dans la terminologie Windows. Les boutons,
les menus et les barres de dfilement sont des exemples de widgets. Les widgets peuvent
contenir dautres widgets ; par exemple, une fentre dapplication est habituellement un widget
qui comporte un QMenuBar, quelques QToolBar, un QStatusBar et dautres widgets. La
plupart des applications utilisent un QMainWindow ou un QDialog comme fentre dapplication,
mais Qt est si flexible que nimporte quel widget peut tre une fentre. Dans cet exemple, le
widget QLabel correspond la fentre dapplication.
La ligne 7 permet dafficher ltiquette. Lors de leur cration, les widgets sont toujours masqus,
de manire pouvoir les personnaliser avant de les afficher et donc dviter le phnomne du
scintillement.
La ligne 8 transmet le contrle de lapplication Qt. A ce stade, le programme entre dans la
boucle dvnement. Cest une sorte de mode dattente o le programme attend que lutilisateur
agisse, en cliquant sur la souris ou en appuyant sur une touche par exemple. Les actions de
lutilisateur dclenchent des vnements (aussi appels "messages") auquel le programme peut
rpondre, gnralement en excutant une ou plusieurs fonctions. Par exemple, quand lutilisateur clique sur un widget, les vnements "bouton souris enfonc" et "bouton souris relch" sont
dclenchs. A cet gard, les applications GUI diffrent normment des programmes par lots
traditionnels, qui traitent habituellement lentre, produisent des rsultats et sachvent sans
intervention de quiconque.

Qt 4 Livre Page 5 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 1

Pour dbuter

Pour des questions de simplicit, nous nappelons pas delete sur lobjet QLabel la fin de la
fonction main(). Cette petite fuite de mmoire est insignifiante dans un programme de cette
taille, puisque la mmoire est rcupre de toute faon par le systme dexploitation ds quil
se termine.
Figure 1.1
Hello sur Linux

Vous pouvez dsormais tester le programme sur votre ordinateur. Vous devrez dabord installer
Qt 4.1.1 (ou une version ultrieure de Qt 4), un processus expliqu en Annexe A. A partir de
maintenant, nous supposons que vous avez correctement install une copie de Qt 4 et que le
rpertoire bin de Qt se trouve dans votre variable denvironnement PATH. (Sous Windows,
cest effectu automatiquement par le programme dinstallation de Qt.) Vous aurez galement
besoin du code source du programme dans un fichier appel hello.cpp situ dans un rpertoire nomm hello. Vous pouvez saisir le code de ce programme vous-mme ou le copier
depuis le CD fourni avec ce livre, partir du fichier /examples/chap01/hello/hello.cpp.
Depuis une invite de commande, placez-vous dans le rpertoire hello, puis saisissez
qmake -project

pour crer un fichier de projet indpendant de la plate-forme (hello.pro), puis tapez


qmake hello.pro

pour crer un fichier Makefile du fichier de projet spcifique la plate-forme.


Tapez make pour gnrer le programme.1 Excutez-le en saisissant hello sous Windows,
./hello sous Unix et open hello.app sous Mac OS X. Pour terminer le programme,
cliquez sur le bouton de fermeture dans la barre de titre de la fentre.
Si vous utilisez Windows et que vous avez install Qt Open Source Edition et le compilateur
MinGW, vous aurez accs un raccourci vers la fentre dinvite DOS o toutes les variables
denvironnement sont correctement installes pour Qt. Si vous lancez cette fentre, vous
pouvez y compiler des applications Qt grce qmake et make dcrits prcdemment. Les
excutables produits sont placs dans les dossiers debug ou release de lapplication, par
exemple C:\qt-book\hello\release\hello.exe.

1. Si vous obtenez une erreur du compilateur sur <QApplication>, cela signifie certainement que vous
utilisez une version plus ancienne de Qt. Assurez-vous dutiliser Qt 4.1.1 ou une version ultrieure de Qt 4.

Qt 4 Livre Page 6 Jeudi, 7. dcembre 2006 12:14 12

Qt4 et C++ : Programmation dinterfaces GUI

Si vous vous servez de Microsoft Visual C++, vous devrez excuter nmake au lieu de make.
Vous pouvez aussi crer un fichier de projet Visual Studio depuis hello.pro en tapant
qmake -tp vc hello.pro

puis concevoir le programme dans Visual Studio. Si vous travaillez avec Xcode sous Mac OS
X, vous avez la possibilit de gnrer un projet Xcode laide de la commande
qmake -spec macx-xcode

Figure 1.2
Une tiquette avec une
mise en forme HTML
basique

Avant de continuer avec lexemple suivant, amusons-nous un peu : remplacez la ligne


QLabel *label = new QLabel("Hello Qt!");

par
QLabel *label = new QLabel("<h2><i>Hello</i> "
"<font color=red>Qt!</font></h2>");

et gnrez nouveau lapplication. Comme lillustre cet exemple, il est facile dgayer linterface utilisateur dune application Qt en utilisant une mise en forme simple de style HTML
(voir Figure 1.2).

Etablir des connexions


Le deuxime exemple vous montre comment rpondre aux actions de lutilisateur. Lapplication propose un bouton sur lequel lutilisateur peut cliquer pour quitter. Le code source ressemble beaucoup Hello, sauf que nous utilisons un QPushButton en lieu et place de QLabel
comme widget principal et que nous connectons laction dun utilisateur (cliquer sur un
bouton) du code.
Le code source de cette application se trouve sur le site web de Pearson, www.pearson.fr, la
page ddie cet ouvrage, sous /examples/chap01/quit/quit.cpp.
Voici le contenu du fichier :
1
2
3
4
5
6
7
8

#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton *button = new QPushButton("Quit");
QObject::connect(button, SIGNAL(clicked()),
&app, SLOT(quit()));

Qt 4 Livre Page 7 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 1

9
10
11

Pour dbuter

button->show();
return app.exec();
}

Les widgets de Qt mettent des signaux pour indiquer quune action utilisateur ou un changement dtat a eu lieu.1 Par exemple, QPushButton met un signal clicked() quand lutilisateur clique sur le bouton. Un signal peut tre connect une fonction (appel un slot dans ce
cas), de sorte quau moment o le signal est mis, le slot soit excut automatiquement. Dans
notre exemple, nous relions le signal clicked() du bouton au slot quit() de lobjet QApplication. Les macros SIGNAL() et SLOT() font partie de la syntaxe ; elles sont expliques plus
en dtail dans le prochain chapitre.
Figure 1.3
Lapplication Quit

Nous allons dsormais gnrer lapplication. Nous supposons que vous avez cr un rpertoire
appel quit contenant quit.cpp (voir Figure 1.3). Excutez qmake dans le rpertoire quit
pour gnrer le fichier de projet, puis excutez-le nouveau pour gnrer un fichier Makefile,
comme suit :
qmake -project
qmake quit.pro

A prsent, gnrez lapplication et excutez-la. Si vous cliquez sur Quit ou que vous appuyez
sur la barre despace (ce qui enfonce le bouton), lapplication se termine.

Disposer des widgets


Dans cette section, nous allons crer une petite application qui illustre comment utiliser les
dispositions, des outils pour grer la disposition des widgets dans une fentre et comment utiliser
des signaux et des slots pour synchroniser deux widgets. Lapplication demande lge de lutilisateur, quil peut rgler par le biais dun pointeur toupie ou dun curseur (voir Figure 1.4).
Figure 1.4
Lapplication Age

Lapplication contient trois widgets : QSpinBox, QSlider et QWidget. QWidget correspond


la fentre principale de lapplication. QSpinBox et QSlider sont affichs dans QWidget;
1. Les signaux Qt ne sont pas lis aux signaux Unix. Dans ce livre, nous ne nous intressons quaux
signaux Qt.

Qt 4 Livre Page 8 Jeudi, 7. dcembre 2006 12:14 12

Qt4 et C++ : Programmation dinterfaces GUI

ce sont des enfants de QWidget. Nous pouvons aussi dire que QWidget est le parent de
QSpinBox et QSlider. QWidget na pas de parent puisque cest une fentre de niveau le plus
haut. Les constructeurs de QWidget et toutes ses sous-classes reoivent un paramtre QWidget
* qui spcifie le widget parent.
Voici le code source :
1
2
3
4

#include
#include
#include
#include

<QApplication>
<QHBoxLayout>
<QSlider>
<QSpinBox>

5
6
7

int main(int argc, char *argv[])


{
QApplication app(argc, argv);
QWidget *window = new QWidget;
window->setWindowTitle("Enter your age");

8
9
10
11
12
13

QSpinBox *spinBox = new QSpinBox;


QSlider *slider = new QSlider(Qt::Horizontal);
spinBox->setRange(0, 130);
slider->setRange(0, 130);

14
15
16
17
18

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
spinBox->setValue(35);

19
20
21
22

QHBoxLayout *layout = new QHBoxLayout;


layout->addWidget(spinBox);
layout->addWidget(slider);
window->setLayout(layout);
window->show();

23
24
25

return app.exec();
}

Les lignes 8 et 9 configurent QWidget qui fera office de fentre principale de lapplication.
Nous appelons setWindowTitle() pour dfinir le texte affich dans la barre de titre de la
fentre.
Les lignes 10 et 11 crent QSpinBox et QSlider, et les lignes 12 et 13 dterminent leurs
plages valides. Nous pouvons affirmer que lutilisateur est g au maximum de 130 ans. Nous
pourrions transmettre window aux constructeurs de QSpinBox et QSlider, en spcifiant que le
parent de ces widgets doit tre window, mais ce nest pas ncessaire ici, parce que le systme
de positionnement le dduira lui-mme et dfinira automatiquement le parent du pointeur
toupie (spin box) et du curseur, comme nous allons le voir.

Qt 4 Livre Page 9 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 1

Pour dbuter

Les deux appels QObject::connect() prsents aux lignes 14 17 garantissent que le pointeur toupie et le curseur sont synchroniss, de sorte quils affichent toujours la mme valeur.
Ds que la valeur dun widget change, son signal valueChanged(int) est mis et le slot
setValue(int) de lautre widget est appel avec la nouvelle valeur.
La ligne 18 dfinit la valeur du pointeur toupie en 35. QSpinBox met donc le signal valueChanged(int) avec un argument int de 35. Cet argument est transmis au slot setValue(int) de QSlider, qui dfinit la valeur du curseur en 35. Le curseur met ensuite le signal
valueChanged(int) puisque sa propre valeur a chang, dclenchant le slot setValue(int)
du pointeur toupie. Cependant, ce stade, setValue(int) nmet aucun signal, parce que la
valeur du pointeur toupie est dj de 35. Vous vitez ainsi une rcursivit infinie. La Figure 1.5
rcapitule la situation.
Figure 1.5
Changer la valeur dun
widget entrane la modification des deux widgets

1.

setValue(35)

2.

35

valueChanged(35)
setValue(35)

3.

35

valueChanged(35)

setValue(35)

4.

35

Dans les lignes 19 22, nous positionnons le pointeur toupie et le curseur laide dun
gestionnaire de disposition (layout manager). Ce gestionnaire est un objet qui dfinit la taille
et la position des widgets qui se trouvent sous sa responsabilit. Qt propose trois principaux
gestionnaires de disposition :

QHBoxLayout dispose les widgets horizontalement de gauche droite (de droite gauche
dans certaines cultures).
QVBoxLayout dispose les widgets verticalement de haut en bas.
QGridLayout dispose les widgets dans une grille.
Lorsque vous appelez QWidget::setLayout() la ligne 22, le gestionnaire de disposition
est install dans la fentre. En arrire-plan, QSpinBox et QSlider sont "reparents" pour devenir des enfants du widget sur lequel la disposition sapplique, et cest pour cette raison que

Qt 4 Livre Page 10 Jeudi, 7. dcembre 2006 12:14 12

10

Qt4 et C++ : Programmation dinterfaces GUI

nous navons pas besoin de spcifier un parent explicite quand nous construisons un widget qui
sera insr dans une disposition.
Figure 1.6
Les widgets
de lapplication Age

Window Title
QWidget

QSpinBox

QSlider

QHBoxLayout

Mme si nous navons pas dfini explicitement la position ou la taille dun widget, QSpinBox
et QSlider apparaissent convenablement cte cte. Cest parce que QHBoxLayout assigne
automatiquement des positions et des tailles raisonnables aux widgets dont il est responsable
en fonction de leurs besoins. Grce aux gestionnaires de disposition, vous vitez la corve de
coder les positions lcran dans vos applications et vous tes sr que les fentres seront redimensionnes correctement.
Lapproche de Qt pour ce qui concerne la conception des interfaces utilisateurs est facile
comprendre et savre trs flexible. Habituellement, les programmeurs Qt instancient les
widgets ncessaires puis dfinissent leurs proprits de manire adquate. Les programmeurs
ajoutent les widgets aux dispositions, qui se chargent automatiquement de leur taille et de leur
position. Le comportement de linterface utilisateur est gr en connectant les widgets ensembles
grce aux signaux et aux slots de Qt.

Utiliser la documentation de rfrence


La documentation de rfrence de Qt est un outil indispensable pour tout dveloppeur Qt,
puisquelle contient toutes les classes et fonctions de cet environnement. Ce livre utilise de
nombreuses classes et fonctions Qt, mais il ne les aborde pas toutes et ne fournit pas de plus
amples dtails sur celles qui sont voques. Pour profiter pleinement de Qt, vous devez vous
familiariser avec la documentation de rfrence le plus rapidement possible.
Cette documentation est disponible en format HTML dans le rpertoire doc/html de Qt et peut
tre affiche dans nimporte quel navigateur Web. Vous pouvez galement utiliser lAssistant
Qt, le navigateur assistant de Qt, qui propose des fonctionnalits puissantes de recherche et
dindex qui sont plus faciles et plus rapides utiliser quun navigateur Web. Pour lancer
lAssistant Qt, cliquez sur Qt by Trolltech v4.x.y/Assistant dans le menu Dmarrer sous
Windows, saisissez assistant dans la ligne de commande sous Unix ou double-cliquez sur
Assistant dans le Finder de Mac OS X (voir Figure 1.7).
Les liens dans la section "API Reference" sur la page daccueil fournissent diffrents moyens
de localiser les classes de Qt. La page "All Classes" rpertorie chaque classe dans lAPI de Qt.
La page "Main Classes" regroupe uniquement les classes Qt les plus frquemment utilises.

Qt 4 Livre Page 11 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 1

Pour dbuter

11

En guise dentranement, vous rechercherez les classes et les fonctions dont nous avons parles
dans ce chapitre.

Figure 1.7
La documentation de Qt dans lAssistant sous Mac OS X

Notez que les fonctions hrites sont dtailles dans la classe de base ; par exemple, QPushButton ne possde pas de fonction show(), mais il en hrite une de son anctre QWidget.
La Figure 1.8 vous montre comment les classes que nous avons tudies jusque l sont lies les
unes aux autres.
Figure 1.8
Arbre dhritage des
classes Qt tudies QCoreApplication
jusqu prsent

QObject
QWidget

QLayout

QApplication

QBoxLayout

QAbstractButton

QAbstractButton

QAbstractSlider

QFrame

QPushButton

QSpinBox

QSlider

QLabel

QHBoxLayout

Qt 4 Livre Page 12 Jeudi, 7. dcembre 2006 12:14 12

12

Qt4 et C++ : Programmation dinterfaces GUI

La documentation de rfrence pour la version actuelle de Qt et pour certaines versions antrieures est disponible en ligne ladresse suivante, http://doc.trolltech.com/. Ce site propose
galement des articles slectionns dans Qt Quarterly, la lettre dinformation des programmeurs
Qt envoye tous les dtenteurs de licences.

Styles des widgets


Les captures prsentes jusque l ont t effectues sous Linux, mais les applications Qt se
fondent parfaitement dans chaque plate-forme prise en charge. Qt y parvient en mulant
laspect et lapparence de la plate-forme, au lieu dadopter un ensemble de widgets de bote
outils ou de plate-forme particulier.

Figure 1.9
Styles disponibles
partout
Windows

Plastique

CDE

Motif

Dans Qt/X11 et Qtopia Core, le style par dfaut est Plastique. Il utilise des dgrads et lanticrnelage pour proposer un aspect et une apparence modernes. Les utilisateurs de lapplication
Qt peuvent passer outre ce style par dfaut grce loption de ligne de commande -style.
Par exemple, pour lancer lapplication Age avec le style Motif sur X11, tapez simplement
./age -style motif

sur la ligne de commande.

Figure 1.10
Styles spcifiques la
plate-forme
Windows XP

Mac

Contrairement aux autres styles, les styles Windows XP et Mac ne sont disponibles que sur leurs
plate-formes natives, puisquils se basent sur les gnrateurs de thme de ces plate-formes.

Qt 4 Livre Page 13 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 1

Pour dbuter

13

Ce chapitre vous a prsent les concepts essentiels des connexions signal slot et des dispositions. Il a galement commenc dvoiler lapproche totalement oriente objet et cohrente de
Qt concernant la construction et lutilisation des widgets. Si vous parcourez la documentation
de Qt, vous dcouvrirez que lapproche savre homogne. Vous comprendrez donc beaucoup
plus facilement comment utiliser de nouveaux widgets et vous verrez aussi que les noms choisis
minutieusement pour les fonctions, les paramtres, les numrations, etc. rendent la programmation dans Qt tonnamment plaisante et aise.
Les chapitres suivants de la Partie I se basent sur les notions fondamentales tudies ici et vous
expliquent comment crer des applications GUI compltes avec des menus, des barres doutils,
des fentres de document, une barre dtat et des botes de dialogue, en plus des fonctionnalits
sous-jacentes permettant de lire, traiter et crire des fichiers.

Qt 4 Livre Page 14 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 15 Jeudi, 7. dcembre 2006 12:14 12

2
Crer des botes de dialogue
Au sommaire de ce chapitre
Drivation de QDialog
Description dtaille des signaux et slots
Conception rapide dune bote de dialogue
Botes de dialogue multiformes
Botes de dialogue dynamiques
Classes de widgets et de botes de dialogue
intgres

Dans ce chapitre, vous allez apprendre crer des botes de dialogue laide de Qt.
Celles-ci prsentent diverses options et possibilits aux utilisateurs et leur permettent de
dfinir les valeurs des options et de faire des choix. On les appelle des botes de dialogue,
puisquelles donnent la possibilit aux utilisateurs et aux applications de "discuter".
La majorit des applications GUI consiste en une fentre principale quipe dune barre
de menus et doutils, laquelle on ajoute des douzaines de botes de dialogue. Il est
galement possible de crer des applications "bote de dialogue" qui rpondent directement aux choix de lutilisateur en accomplissant les actions appropries (par exemple,
une application de calculatrice).

Qt 4 Livre Page 16 Jeudi, 7. dcembre 2006 12:14 12

16

Qt4 et C++ : Programmation dinterfaces GUI

Nous allons crer notre premire bote de dialogue en crivant compltement le code pour vous en
expliquer le fonctionnement. Puis nous verrons comment concevoir des botes de dialogue grce
au Qt Designer, un outil de conception de Qt. Avec le Qt Designer, vous codez beaucoup plus
rapidement et il est plus facile de tester les diffrentes conceptions et de les modifier par la suite.

Drivation de QDialog
Notre premier exemple est une bote de dialogue Find crite totalement en langage C++ (voir
Figure 2.1). Nous limplmenterons comme une classe part entire. Ainsi, elle deviendra un
composant indpendant et autonome, comportant ses propres signaux et slots.
Figure 2.1
La bote de dialogue Find

Le code source est rparti entre deux fichiers : finddialog.h et finddialog.cpp. Nous
commencerons par finddialog.h.
1
2

#ifndef FINDDIALOG_H
#define FINDDIALOG_H

#include <QDialog>

4
5
6
7

class
class
class
class

QCheckBox;
QLabel;
QLineEdit;
QPushButton;

Les lignes 1 et 2 (et 27) protgent le fichier den-tte contre les inclusions multiples.
La ligne 3 contient la dfinition de QDialog, la classe de base pour les botes de dialogue dans
Qt. QDialog hrite de QWidget.
Les lignes 4 7 sont des dclarations pralables des classes Qt que nous utiliserons pour
implmenter la bote de dialogue. Une dclaration pralable informe le compilateur C++
quune classe existe, sans donner tous les dtails dune dfinition de classe (gnralement
situe dans un fichier den-tte). Nous en parlerons davantage dans un instant.
Nous dfinissons ensuite FindDialog comme une sous-classe de QDialog:
8
9
10

class FindDialog: public QDialog


{
Q_OBJECT

Qt 4 Livre Page 17 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

11
12

Crer des botes de dialogue

17

public:
FindDialog(QWidget *parent = 0);

La macro Q_OBJECT au dbut de la dfinition de classe est ncessaire pour toutes les classes qui
dfinissent des signaux ou des slots.
Le constructeur de FindDialog est typique des classes Qt de widgets. Le paramtre parent
spcifie le widget parent. Par dfaut, cest un pointeur nul, ce qui signifie que la bote de dialogue
na pas de parent.
13
14
15

signals:
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);

La section des signaux dclare deux signaux que la bote de dialogue met quand lutilisateur
clique sur le bouton Find. Si loption Search backward est active, la bote de dialogue met
findPrevious(); sinon elle met findNext().
Le mot-cl signals est en fait une macro. Le prprocesseur C++ la convertit en langage C++
standard avant que le compilateur ne la voie. Qt::CaseSensitivity est un type numration
qui peut prendre les valeurs Qt::CaseSensitive et Qt::CaseInsensitive.
16
17
18

private slots:
void findClicked();
void enableFindButton(const QString &text);

26

private:
QLabel *label;
QLineEdit *lineEdit;
QCheckBox *caseCheckBox;
QCheckBox *backwardCheckBox;
QPushButton *findButton;
QPushButton *closeButton;
};

27

#endif

19
20
21
22
23
24
25

Dans la section prive de la classe, nous dclarons deux slots. Pour implmenter les slots, vous
devez avoir accs la plupart des widgets enfants de la bote de dialogue, nous conservons
donc galement des pointeurs vers eux. Le mot-cl slots, comme signals, est une macro qui
se dveloppe pour produire du code que le compilateur C++ peut digrer.
Sagissant des variables prives, nous avons utilis les dclarations pralables de leurs classes.
Ctait possible puisque ce sont toutes des pointeurs et que nous ny accdons pas dans le
fichier den-tte, le compilateur na donc pas besoin des dfinitions de classe compltes. Nous
aurions pu inclure les fichiers den-tte importants (<QCheckBox>, <QLabel>, etc.), mais la
compilation se rvle plus rapide si vous utilisez les dclarations pralables ds que possible.

Qt 4 Livre Page 18 Jeudi, 7. dcembre 2006 12:14 12

18

Qt4 et C++ : Programmation dinterfaces GUI

Nous allons dsormais nous pencher sur finddialog.cpp qui contient limplmentation de la
classe FindDialog.
1

#include <QtGui>

#include "finddialog.h"

Nous incluons dabord <QtGui>, un fichier den-tte qui contient la dfinition des classes GUI
de Qt. Qt est constitu de plusieurs modules, chacun deux se trouvant dans sa propre bibliothque. Les modules les plus importants sont QtCore, QtGui, QtNetwork, QtOpenGL, QtSql,
QtSvg et QtXml. Le fichier den-tte <QtGui> renferme la dfinition de toutes les classes qui
font partie des mo dules QtCore et QtGui. En incluant cet en-tte, vous vitez la tche fastidieuse dinclure chaque classe sparment.
Dans filedialog.h, au lieu dinclure <QDialog> et dutiliser des dclarations pralables
pour QCheckBox, QLabel, QLineEdit et QPushButton, nous aurions simplement pu spcifier
<QtGui>. Toutefois, il est gnralement malvenu dinclure un fichier den-tte si grand depuis
un autre fichier den-tte, notamment dans des applications plus importantes.
3
4
5
6
7
8

FindDialog::FindDialog(QWidget *parent)
: QDialog(parent)
{
label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit;
label->setBuddy(lineEdit);

9
10

caseCheckBox = new QCheckBox(tr("Match &case"));


backwardCheckBox = new QCheckBox(tr("Search &backward"));

11
12
13

findButton = new QPushButton(tr("&Find"));


findButton->setDefault(true);
findButton->setEnabled(false);

14

closeButton = new QPushButton(tr("Close"));

A la ligne 4, nous transmettons le paramtre parent au constructeur de la classe de base. Puis


nous crons les widgets enfants. Les appels de la fonction tr() autour des littraux chane les
marquent dans loptique dune traduction en dautres langues. La fonction est dclare dans
QObject et chaque sous-classe qui contient la macro Q_OBJECT. Il est recommand de prendre
lhabitude dencadrer les chanes visibles par lutilisateur avec tr(), mme si vous navez pas
lintention de faire traduire vos applications en dautres langues dans limmdiat. La traduction
des applications Qt est aborde au Chapitre 17.
Dans les littraux chane, nous utilisons le caractre & pour indiquer des raccourcis clavier. Par
exemple, la ligne 11 cre un bouton Find que lutilisateur peut activer en appuyant sur Alt+F
sur les plates-formes qui prennent en charge les raccourcis clavier. Le caractre & peut galement tre employ pour contrler le focus, cest--dire llment actif : la ligne 6, nous
crons une tiquette avec un raccourci clavier (Alt+W), et la ligne 8, nous dfinissons
lditeur de lignes comme widget compagnon de ltiquette. Ce compagnon (buddy) est un widget

Qt 4 Livre Page 19 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

19

qui reoit le focus quand vous appuyez sur le raccourci clavier de ltiquette. Donc, quand
lutilisateur appuie sur Alt+W (le raccourci de ltiquette), lditeur de lignes reoit le contrle.
A la ligne 12, nous faisons du bouton Find le bouton par dfaut de la bote de dialogue en
appelant setDefault(true). Le bouton par dfaut est le bouton qui est press quand lutilisateur appuie sur Entre. A la ligne 13, nous dsactivons le bouton Find. Quand un widget est
dsactiv, il apparat gnralement gris et ne rpondra pas en cas dinteraction de lutilisateur.
15
16
17
18
19
20

connect(lineEdit, SIGNAL(textChanged(const QString &)),


this, SLOT(enableFindButton(const QString &)));
connect(findButton, SIGNAL(clicked()),
this, SLOT(findClicked()));
connect(closeButton, SIGNAL(clicked()),
this, SLOT(close()));

Le slot priv enableFindButton(const QString &) est appel ds que le texte change
dans lditeur de lignes. Le slot priv findClicked() est invoqu lorsque lutilisateur clique
sur le bouton Find. La bote de dialogue se ferme si lutilisateur clique sur Close. Le slot
close() est hrit de QWidget et son comportement par dfaut consiste masquer le widget
(sans le supprimer). Nous allons tudier le code des slots enableFindButton() et findClicked() ultrieurement.
Etant donn que QObject est lun des anctres de FindDialog, nous pouvons omettre le
prfixe QObject:: avant les appels de connect().
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

QHBoxLayout *topLeftLayout = new QHBoxLayout;


topLeftLayout->addWidget(label);
topLeftLayout->addWidget(lineEdit);
QVBoxLayout *leftLayout = new QVBoxLayout;
leftLayout->addLayout(topLeftLayout);
leftLayout->addWidget(caseCheckBox);
leftLayout->addWidget(backwardCheckBox);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);

Nous disposons ensuite les widgets enfants laide des gestionnaires de disposition. Les dispositions peuvent contenir des widgets et dautres dispositions. En imbriquant QHBoxLayout,
QVBoxLayout et QGridLayout dans diverses combinaisons, il est possible de concevoir des
botes de dialogue trs sophistiques.

Qt 4 Livre Page 20 Jeudi, 7. dcembre 2006 12:14 12

20

Qt4 et C++ : Programmation dinterfaces GUI

Figure 2.2
Les dispositions de la
bote de dialogue Find

Titre de la fentre

leftLayout
topLeftLayout

QLabel

QLineEdit

QPushButton
QPushButton

QCheckBox
QCheckBox

rightLayout
rightLayout

Elment
d'espacement

Pour la bote de dialogue Find, nous employons deux QHBoxLayout et deux QVBoxLayout,
comme illustr en Figure 2.2. La disposition externe correspond la disposition principale ;
elle est installe sur FindDialog la ligne 35 et est responsable de toute la zone de la bote de
dialogue. Les trois autres dispositions sont des sous-dispositions. Le petit "ressort" en bas
droite de la Figure 2.2 est un lment despacement (ou "tirement"). Il comble lespace vide sous
les boutons Find et Close, ces boutons sont donc srs de se trouver en haut de leur disposition.
Les gestionnaires de disposition prsentent une subtilit : ce ne sont pas des widgets. Ils hritent de QLayout, qui hrite son tour de QObject. Dans la figure, les widgets sont reprsents
par des cadres aux traits pleins et les dispositions sont illustres par des cadres en pointills
pour mettre en avant la diffrence qui existe entre eux. Dans une application en excution, les
dispositions sont invisibles.
Quand les sous-dispositions sont ajoutes la disposition parent (lignes 25, 33 et 34), les sousdispositions sont automatiquement reparentes. Puis, lorsque la disposition principale est
installe dans la bote de dialogue (ligne 35), elle devient un enfant de cette dernire et tous les
widgets dans les dispositions sont reparents pour devenir des enfants de la bote de dialogue.
La hirarchie parent-enfant ainsi obtenue est prsente en Figure 2.3.
36
37
38

setWindowTitle(tr("Find"));
setFixedHeight(sizeHint().height());
}

Figure 2.3
Les relations parentenfant de la bote de
dialogue Find

FindDialog
QLabel (label)
QLineEdit (lineEdit)
QCheckBox (caseCheckBox)
QCheckBox (backwardCheckBox)
QPushButton (ndButton)
QPushButton (closeButton)
QHBoxLayout (mainLayout)
QVBoxLayout (leftLayout)
QHBoxLayout (topLeftLayout)
QVBoxLayout (rightLayout)

Enfin, nous dfinissons le titre afficher dans la barre de titre de la bote de dialogue et nous
configurons la fentre pour quelle prsente une hauteur fixe, tant donn quelle ne contient

Qt 4 Livre Page 21 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

21

aucun widget qui peut occuper de lespace supplmentaire verticalement. La fonction QWidget::sizeHint() retourne la taille "idale" dun widget.
Ceci termine lanalyse du constructeur de FindDialog. Vu que nous avons cr les widgets et
les dispositions de la bote de dialogue avec new, il semblerait logique dcrire un destructeur
qui appelle delete sur chaque widget et disposition que nous avons crs. Toutefois, ce nest
pas ncessaire, puisque Qt supprime automatiquement les objets enfants quand le parent est
dtruit, et les dispositions et widgets enfants sont tous des descendants de FindDialog.
Nous allons prsent analyser les slots de la bote de dialogue :
39
40
41
42
43
44
45
46
47
48
49
50

void FindDialog::findClicked()
{
QString text = lineEdit->text();
Qt::CaseSensitivity cs =
caseCheckBox->isChecked()? Qt::CaseSensitive
: Qt::CaseInsensitive;
if (backwardCheckBox->isChecked()) {
emit findPrevious(text, cs);
} else {
emit findNext(text, cs);
}
}

51
52
53
54

void FindDialog::enableFindButton(const QString &text)


{
findButton->setEnabled(!text.isEmpty());
}

Le slot findClicked() est appel lorsque lutilisateur clique sur le bouton Find. Il met le
signal findPrevious() ou findNext(), en fonction de loption Search backward. Le motcl emit est spcifique Qt ; comme les autres extensions Qt, il est converti en langage C++
standard par le prprocesseur C++.
Le slot enableFindButton() est invoqu ds que lutilisateur modifie le texte dans lditeur
de lignes. Il active le bouton sil y a du texte dans cet diteur, sinon il le dsactive.
Ces deux slots compltent la bote de dialogue. Nous avons dsormais la possibilit de crer un
fichier main.cpp pour tester notre widget FindDialog:
1

#include <QApplication>

#include "finddialog.h"

3
4
5
6
7
8
9

int main(int argc, char *argv[])


{
QApplication app(argc, argv);
FindDialog *dialog = new FindDialog;
dialog->show();
return app.exec();
}

Pour compiler le programme, excutez qmake comme dhabitude. Vu que la dfinition de la


classe FindDialog comporte la macro Q_OBJECT, le fichier Makefile gnr par qmake

Qt 4 Livre Page 22 Jeudi, 7. dcembre 2006 12:14 12

22

Qt4 et C++ : Programmation dinterfaces GUI

contiendra des rgles particulires pour excuter moc, le compilateur de mta-objets de Qt.
(Le systme de mta-objets de Qt est abord dans la prochaine section.)
Pour que moc fonctionne correctement, vous devez placer la dfinition de classe dans un fichier
den-tte, spar du fichier dimplmentation. Le code gnr par moc inclut ce fichier dentte et ajoute une certaine "magie" C++.

moc doit tre excut sur les classes qui se servent de la macro Q_OBJECT. Ce nest pas un
problme parce que qmake ajoute automatiquement les rgles ncessaires dans le fichier Makefile. Toutefois, si vous oubliez de gnrer nouveau votre fichier Makefile laide de qmake et
que moc nest pas excut, lditeur de liens indiquera que certaines fonctions sont dclares
mais pas implmentes. Les messages peuvent tre plutt obscurs. GCC produit des avertissements
comme celui-ci :
finddialog.o: In function FindDialog::tr(char const*, char const*):
/usr/lib/qt/src/corelib/global/qglobal.h:1430: undefined reference to
FindDialog::staticMetaObject

La sortie de Visual C++ commence ainsi :


finddialog.obj: error LNK2001: unresolved external symbol
"public:~virtual int __thiscall MyClass::qt_metacall(enum QMetaObject
::Call,int,void * *)"

Si vous vous trouvez dans ce cas, excutez nouveau qmake pour mettre jour le fichier
Makefile, puis gnrez nouveau lapplication.
Excutez maintenant le programme. Si des raccourcis clavier apparaissent sur votre plateforme, vrifiez que les raccourcis Alt+W, Alt+C, Alt+B et Alt+F dclenchent le bon comportement.
Appuyez sur la touche de tabulation pour parcourir les widgets en utilisant le clavier. Lordre
de tabulation par dfaut est lordre dans lequel les widgets ont t crs. Vous pouvez le modifier
grce QWidget::setTabOrder().
Proposer un ordre de tabulation et des raccourcis clavier cohrents permet aux utilisateurs qui
ne veulent pas (ou ne peuvent pas) utiliser une souris de profiter pleinement de lapplication.
Les dactylos apprcieront galement de pouvoir tout contrler depuis le clavier.
Dans le Chapitre 3, nous utiliserons la bote de dialogue Find dans une application relle, et
nous connecterons les signaux findPrevious() et findNext() certains slots.

Description dtaille des signaux et slots


Le mcanisme des signaux et des slots est une notion fondamentale en programmation Qt.
Il permet au programmeur de lapplication de relier des objets sans que ces objets ne sachent
quoi que ce soit les uns sur les autres. Nous avons dj connect certains signaux et slots
ensemble, dclar nos propres signaux et slots, implment nos slots et mis nos signaux.
Etudions dsormais ce mcanisme plus en dtail.

Qt 4 Livre Page 23 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

23

Les slots sont presque identiques aux fonctions membres ordinaires de C++. Ils peuvent tre
virtuels, surchargs, publics, protgs ou privs, tre invoqus directement comme toute autre
fonction membre C++, et leurs paramtres peuvent tre de nimporte quel type. La diffrence
est quun slot peut aussi tre connect un signal, auquel cas il est automatiquement appel
chaque fois que le signal est mis.
Voici la syntaxe de linstruction connect():
connect(sender, SIGNAL(signal), receiver, SLOT(slot));

o sender et receiver sont des pointeurs vers QObject et o signal et slot sont des signatures de fonction sans les noms de paramtre. Les macros SIGNAL() et SLOT() convertissent
leur argument en chane.
Dans les exemples tudis jusque l, nous avons toujours connect les signaux aux divers slots.
Il existe dautres possibilits envisager.

Un signal peut tre connect plusieurs slots :


connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(updateStatusBarIndicator(int)));

Quand le signal est mis, les slots sont appels les uns aprs les autres, dans un ordre non
spcifi.

Plusieurs signaux peuvent tre connects au mme slot :


connect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()),
this, SLOT(handleMathError()));

Quand lun des signaux est mis, le slot est appel.

Un signal peut tre connect un autre signal :


connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SIGNAL(updateRecord(const QString &)));

Quand le premier signal est mis, le second est galement mis. En dehors de cette caractristique, les connexions signal-signal sont en tout point identiques aux connexions
signal-slot.

Les connexions peuvent tre supprimes :


disconnect(lcd, SIGNAL(overflow()),
this, SLOT(handleMathError()));

Vous nen aurez que trs rarement besoin, parce que Qt supprime automatiquement toutes
les connexions concernant un objet quand celui-ci est supprim.

Qt 4 Livre Page 24 Jeudi, 7. dcembre 2006 12:14 12

24

Qt4 et C++ : Programmation dinterfaces GUI

Pour bien connecter un signal un slot (ou un autre signal), ils doivent avoir les mmes types
de paramtre dans le mme ordre :
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));

Exceptionnellement, si un signal comporte plus de paramtres que le slot auquel il est


connect, les paramtres supplmentaires sont simplement ignors :
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(checkErrorCode(int)));

Si les types de paramtre sont incompatibles, ou si le signal ou le slot nexiste pas, Qt mettra
un avertissement au moment de lexcution si lapplication est gnre en mode dbogage. De
mme, Qt enverra un avertissement si les noms de paramtre se trouvent dans les signatures du
signal ou du slot.
Jusqu prsent, nous navons utilis que des signaux et des slots avec des widgets. Cependant, le
mcanisme en soi est implment dans QObject et ne se limite pas la programmation dinterfaces
graphiques utilisateurs. Il peut tre employ par nimporte quelle sous-classe de QObject:
class Employee: public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
void Employee::setSalary(int newSalary)
{
if (newSalary!= mySalary) {
mySalary = newSalary;
emit salaryChanged(mySalary);
}
}

Vous remarquerez la manire dont le slot setSalary() est implment. Nous nmettons
le signal salaryChanged() que si newSalary!= mySalary. Vous tes donc sr que les
connexions cycliques ne dbouchent pas sur des boucles infinies.

Qt 4 Livre Page 25 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

25

Systme de mta-objets de Qt
Lune des amliorations majeures de Qt a t lintroduction dans le langage C++ dun mcanisme permettant de crer des composants logiciels indpendants qui peuvent tre relis les
uns aux autres sans quils ne sachent absolument rien sur les autres composants auxquels ils
sont connects.
Ce mcanisme est appel systme de mta-objets et propose deux services essentiels : les
signaux-slots et lintrospection. La fonction dintrospection est ncessaire pour implmenter
des signaux et des slots et permet aux programmeurs dapplications dobtenir des "mtainformations" sur les sous-classes de QObject lexcution, y compris la liste des signaux et des
slots pris en charge par lobjet et le nom de sa classe. Le mcanisme supporte galement
des proprits (pour le Qt Designer) et des traductions de texte (pour linternationalisation),
et il pose les fondements de QSA (Qt Script for Applications).
Le langage C++ standard ne propose pas de prise en charge des mta-informations dynamiques ncessaires pour le systme de mta-objets de Qt. Qt rsout ce problme en fournissant
un outil, moc, qui analyse les dfinitions de classe Q_OBJECT et rend les informations disponibles
par le biais de fonctions C++. Etant donn que moc implmente toute sa fonctionnalit en utilisant un langage C++ pur, le systme de mta-objets de Qt fonctionne avec nimporte quel
compilateur C++.
Ce mcanisme fonctionne de la manire suivante :

La macro Q_OBJECT dclare certaines fonctions dintrospection qui doivent tre implmentes
dans chaque sous-classe de QObject: metaObject(), tr(), qt_metacall() et quelques
autres.

Loutil moc de Qt gnre des implmentations pour les fonctions dclares par Q_OBJECT
et pour tous les signaux.

Les fonctions membres de QObject, telles que connect() et disconnect(), utilisent les
fonctions dintrospection pour effectuer leurs tches.

Tout est gr automatiquement par qmake, moc et QObject, vous ne vous en souciez donc que
trs rarement. Nanmoins, par curiosit, vous pouvez parcourir la documentation de la classe
QMetaObject et analyser les fichiers sources C++ gnrs par moc pour dcouvrir comment
fonctionne limplmentation.

Conception rapide dune bote de dialogue


Qt est conu pour tre agrable et intuitif crire, et il nest pas inhabituel que des programmeurs dveloppent des applications Qt compltes en saisissant la totalit du code source C++.
De nombreux programmeurs prfrent cependant utiliser une approche visuelle pour concevoir

Qt 4 Livre Page 26 Jeudi, 7. dcembre 2006 12:14 12

26

Qt4 et C++ : Programmation dinterfaces GUI

les formulaires. Ils la trouvent en effet plus naturelle et plus rapide, et ils veulent tre en
mesure de tester et de modifier leurs conceptions plus rapidement et facilement quavec des
formulaires cods manuellement.
Le Qt Designer dveloppe les options disposition des programmeurs en proposant une fonctionnalit visuelle de conception. Le Qt Designer peut tre employ pour dvelopper tous les
formulaires dune application ou juste quelques-uns. Les formulaires crs laide du Qt Designer tant uniquement constitus de code C++, le Qt Designer peut tre utilis avec une chane
doutils traditionnelle et nimpose aucune exigence particulire au compilateur.
Dans cette section, nous utiliserons le Qt Designer afin de crer la bote de dialogue Go-to-Cell
prsente en Figure 2.4. Quelle que soit la mthode de conception choisie, la cration dune
bote de dialogue implique toujours les mmes tapes cls :

crer et initialiser les widgets enfants ;

placer les widgets enfants dans des dispositions ;

dfinir lordre de tabulation ;

tablir les connexions signal-slot ;

implmenter les slots personnaliss de la bote de dialogue.

Figure 2.4
La bote de dialogue
Go-to-Cell

Pour lancer le Qt Designer, cliquez sur Qt by Trolltech v4.x.y > Designer dans le menu Dmarrer sous Windows, saisissez designer dans la ligne de commande sous Unix ou doublecliquez sur Designer dans le Finder de Mac OS X. Quand le Qt Designer dmarre, une liste de
modles saffiche. Cliquez sur le modle "Widget", puis sur OK. (Le modle "Dialog with
Buttons Bottom" peut tre tentant, mais pour cet exemple nous crerons les boutons OK et
Cancel manuellement pour vous expliquer le processus.) Vous devriez prsent vous trouver
dans une fentre appele "Untitled".
Par dfaut, linterface utilisateur du Qt Designer consiste en plusieurs fentres de haut niveau.
Si vous prfrez une interface de style MDI, avec une fentre de haut niveau et plusieurs sousfentres, cliquez sur Edit > User Interface Mode > Docked Window (voir Figure 2.5).
La premire tape consiste crer les widgets enfants et les placer sur le formulaire. Crez
une tiquette, un diteur de lignes, un lment despacement horizontal et deux boutons de
commande. Pour chaque lment, faites glisser son nom ou son icne depuis la bote des
widgets du Qt Designer vers son emplacement sur le formulaire. Llment despacement, qui
est invisible dans le formulaire final, est affich dans le Qt Designer sous forme dun ressort bleu.

Qt 4 Livre Page 27 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

27

Figure 2.5
Le Qt Designer en mode
daffichage fentres
ancres sous Windows

Faites glisser le bas du formulaire vers le haut pour le rtrcir. Vous devriez voir un formulaire
similaire celui de la Figure 2.6. Ne perdez pas trop de temps positionner les lments sur le
formulaire ; les gestionnaires de disposition de Qt les disposeront prcisment par la suite.
Figure 2.6
Le formulaire
avec quelques widgets

Configurez les proprits de chaque widget laide de lditeur de proprits du Qt Designer :


1. Cliquez sur ltiquette de texte. Assurez-vous que la proprit objectName est label et
dfinissez la proprit text en &Cell Location:.
2. Cliquez sur lditeur de lignes. Vrifiez que la proprit objectName est lineEdit.
3. Cliquez sur le premier bouton. Configurez la proprit objectName en okButton, la
proprit enabled en false, la proprit text en OK et la proprit default en true.
4. Cliquez sur le second bouton. Dfinissez la proprit objectName en cancelButton et la
proprit text en Cancel.
5. Cliquez sur larrire-plan du formulaire pour slectionner ce dernier. Dfinissez objectName en GoToCellDialog et windowTitle en Go to Cell.
Tous les widgets sont correctement prsents, sauf ltiquette de texte, qui affiche &Cell Location.
Cliquez sur Edit > Edit Buddies pour passer dans un mode spcial qui vous permet de configurer
les compagnons. Cliquez ensuite sur ltiquette et faites glisser la flche rouge vers lditeur de
lignes. Ltiquette devrait prsenter le texte "Cell Location" et lditeur de lignes devrait tre
son widget compagnon. Cliquez sur Edit > Edit widgets pour quitter le mode des compagnons.

Qt 4 Livre Page 28 Jeudi, 7. dcembre 2006 12:14 12

28

Qt4 et C++ : Programmation dinterfaces GUI

Figure 2.7
Le formulaire dont les
proprits sont dfinies

La prochaine tape consiste disposer les widgets sur le formulaire :


1. Cliquez sur ltiquette Cell Location et maintenez la touche Maj enfonce quand vous cliquez
sur lditeur de lignes, de manire ce quils soient slectionns tous les deux. Cliquez sur
Form > Lay Out Horizontally.
2. Cliquez sur llment despacement, maintenez la touche Maj enfonce et appuyez sur les
boutons OK et Cancel du formulaire. Cliquez sur Form > Lay Out Horizontally.
3. Cliquez sur larrire-plan du formulaire pour annuler toute slection dlment, puis cliquez
sur Form > Lay Out Vertically.
4. Cliquez sur Form > Ajust Size pour redimensionner le formulaire.
Les lignes rouges qui apparaissent sur le formulaire montrent les dispositions qui ont t
cres. Elles ne saffichent pas lorsque le formulaire est excut.
Figure 2.8
Le formulaire avec
les dispositions

Cliquez prsent sur Edit > Edit Tab Order. Un nombre apparatra dans un rectangle bleu
ct de chaque widget qui peut devenir actif (voir Figure 2.9). Cliquez sur chaque widget dans
lordre dans lequel vous voulez quils reoivent le focus, puis cliquez sur Edit > Edit widgets
pour quitter le mode ddition de lordre de tabulation.
Figure 2.9
Dfinir lordre
de tabulation
du formulaire

Pour avoir un aperu de la bote de dialogue, slectionnez loption Form > Preview du menu.
Vrifiez lordre de tabulation en appuyant plusieurs fois sur la touche Tab. Fermez la bote de
dialogue en appuyant sur le bouton de fermeture dans la barre de titre.

Qt 4 Livre Page 29 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

29

Enregistrez la bote de dialogue sous gotocelldialog.ui dans un rpertoire appel gotocell,


et crez un fichier main.cpp dans le mme rpertoire grce un diteur de texte ordinaire :
#include <QApplication>
#include <QDialog>
#include "ui_gotocelldialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Ui::GoToCellDialog ui;
QDialog *dialog = new QDialog;
ui.setupUi(dialog);
dialog->show();
return app.exec();
}

Excutez maintenant qmake pour crer un fichier .pro et un Makefile (qmake -project; qmake
goto-cell.pro). Loutil qmake est suffisamment intelligent pour dtecter le fichier de linterface utilisateur gotocelldialog.ui et pour gnrer les rgles appropries du fichier Makefile. Il va donc appeler uic, le compilateur Qt de linterface utilisateur. Loutil uic convertit
gotocelldialog.ui en langage C++ et intgre les rsultats dans ui_gotocelldialog.h.
Le fichier ui_gotocelldialog.h gnr contient la dfinition de la classe Ui::GoToCellDialog qui est un quivalent C++ du fichier gotocelldialog.ui. La classe dclare des
variables membres qui stockent les widgets enfants et les dispositions du formulaire, et une
fonction setupUi() qui initialise le formulaire. Voici la syntaxe de la classe gnre :
class Ui::GoToCellDialog
{
public:
QLabel *label;
QLineEdit *lineEdit;
QSpacerItem *spacerItem;
QPushButton *okButton;
QPushButton *cancelButton;
...

void setupUi(QWidget *widget) {


...
}
};

Cette classe nhrite pas de nimporte quelle classe Qt. Quand vous utilisez le formulaire dans
main.cpp, vous crez un QDialog et vous le transmettez setupUi().

Qt 4 Livre Page 30 Jeudi, 7. dcembre 2006 12:14 12

30

Qt4 et C++ : Programmation dinterfaces GUI

Si vous excutez le programme maintenant, la bote de dialogue fonctionnera, mais pas exactement
comme vous le souhaitiez :
Le bouton OK est toujours dsactiv.
Le bouton Cancel ne fait rien.
Lditeur de lignes accepte nimporte quel texte, au lieu daccepter uniquement des emplacements de cellule valides.
Pour faire fonctionner correctement la bote de dialogue, vous devrez crire du code. La
meilleure approche consiste crer une nouvelle classe qui hrite la fois de QDialog et de
Ui::GoToCellDialog et qui implmente la fonctionnalit manquante (ce qui prouve que tout
problme logiciel peut tre rsolu simplement en ajoutant une autre couche dindirection).
Notre convention de dnomination consiste attribuer cette nouvelle classe le mme nom
que la classe gnre par uic, mais sans le prfixe Ui::.

A laide dun diteur de texte, crez un fichier nomm gotocelldialog.h qui contient le
code suivant :
#ifndef GOTOCELLDIALOG_H
#define GOTOCELLDIALOG_H
#include <QDialog>
#include "ui_gotocelldialog.h"
class GoToCellDialog: public QDialog, public Ui::GoToCellDialog
{
Q_OBJECT
public:
GoToCellDialog(QWidget *parent = 0);
private slots:
void on_lineEdit_textChanged();
};
#endif

Limplmentation fait partie de gotocelldialog.cpp:


#include <QtGui>
#include "gotocelldialog.h"
GoToCellDialog::GoToCellDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");

Qt 4 Livre Page 31 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

31

lineEdit->setValidator(new QRegExpValidator(regExp, this));


connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
}
void GoToCellDialog::on_lineEdit_textChanged()
{
okButton->setEnabled(lineEdit->hasAcceptableInput());
}

Dans le constructeur, nous appelons setupUi() pour initialiser le formulaire. Grce lhritage multiple, nous pouvons accder directement aux membres de Ui::GoToCellDialog.
Aprs avoir cr linterface utilisateur, setupUi() connectera galement automatiquement
tout slot qui respecte la convention de dnomination on_Nomobjet_NomSignal() au signal
nomSignal() correspondant de objectName. Dans notre exemple, cela signifie que setupUi()
tablira la connexion signal-slot suivante :
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(on_lineEdit_textChanged()));

Toujours dans le constructeur, nous dfinissons un validateur qui restreint la plage des valeurs
en entre. Qt propose trois classes de validateurs :QIntValidator, QDoubleValidator et
QRegExpValidator. Nous utilisons ici QRegExpValidator avec lexpression rgulire
"[A-Za-z][1-9][0-9]{0,2}", qui signifie : autoriser une lettre majuscule ou minuscule, suivie
dun chiffre entre 1 et 9, puis de zro, un ou deux chiffres entre 0 et 9. (En guise dintroduction
aux expressions rgulires, consultez la documentation de la classe QRegExp.)
Si vous transmettez ceci au constructeur de QRegExpValidator, vous en faites un enfant de
lobjet GoToCellDialog. Ainsi, vous navez pas besoin de prvoir la suppression de QRegExpValidator; il sera supprim automatiquement en mme temps que son parent.
Le mcanisme parent-enfant de Qt est implment dans QObject. Quand vous crez un objet
(un widget, un validateur, ou autre) avec un parent, le parent ajoute lobjet sa liste denfants.
Quand le parent est supprim, il parcourt sa liste denfants et les supprime. Les enfants eux-mmes
effacent ensuite tous leurs enfants, et ainsi de suite jusqu ce quil nen reste plus aucun.
Ce mcanisme parent-enfant simplifie nettement la gestion de la mmoire, rduisant les risques
de fuites de mmoire. Les seuls objets que vous devrez supprimer explicitement sont les objets
que vous crez avec new et qui nont pas de parent. Et si vous supprimez un objet enfant avant
son parent, Qt supprimera automatiquement cet objet de la liste des enfants du parent.
Sagissant des widgets, le parent a une signification supplmentaire : les widgets enfants sont
affichs dans la zone du parent. Quand vous supprimez le widget parent, lenfant est effac de
la mmoire mais galement de lcran.
A la fin du constructeur, nous connectons le bouton OK au slot accept() de QDialog et le
bouton Cancel au slot reject(). Les deux slots ferment la bote de dialogue, mais accept()
dfinit la valeur de rsultat de la bote de dialogue en QDialog::Accepted (qui est gal 1),

Qt 4 Livre Page 32 Jeudi, 7. dcembre 2006 12:14 12

32

Qt4 et C++ : Programmation dinterfaces GUI

et reject() configure la valeur en QDialog::Rejected (gal 0). Quand nous utilisons


cette bote de dialogue, nous avons la possibilit dutiliser la valeur de rsultat pour voir si
lutilisateur a cliqu sur OK et agir de faon approprie.
Le slot on_lineEdit_textChanged() active ou dsactive le bouton OK, selon que lditeur
de lignes contient un emplacement de cellule valide ou non. QLineEdit::hasAcceptableInput() emploie le validateur que nous avons dfini dans le constructeur.
Ceci termine la bote de dialogue. Vous pouvez dsormais rcrire main.cpp pour lutiliser :
#include <QApplication>
#include "gotocelldialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
GoToCellDialog *dialog = new GoToCellDialog;
dialog->show();
return app.exec();
}

Rgnrez lapplication (qmake project; qmake gotocell.pro) et excutez-la


nouveau. Tapez "A12" dans lditeur de lignes et vous verrez que le bouton OK sactive.
Essayez de saisir du texte alatoire pour vrifier que le validateur effectue bien sa tche.
Cliquez sur Cancel pour fermer la bote de dialogue.
Lun des avantages du Qt Designer, cest que les programmeurs sont libres de modifier la
conception de leurs formulaires sans tre obligs de changer leur code source. Quand vous
dveloppez un formulaire simplement en rdigeant du code C++, les modifications apportes
la conception peuvent vous faire perdre normment de temps. Grce au Qt Designer, vous
gagnez en efficacit parce que uic rgnre simplement le code source pour tout formulaire
modifi. Linterface utilisateur de la bote de dialogue est enregistre dans un fichier .ui (un
format de fichier bas sur le langage XML), alors que la fonctionnalit personnalise est implmente en sous-classant la classe gnre par uic.

Botes de dialogue multiformes


Nous avons vu comment crer des botes de dialogue qui affichent toujours les mmes widgets
lors de leur utilisation. Dans certains cas, il est souhaitable de proposer des botes de dialogue
dont la forme peut varier. Les deux types les plus courants de botes de dialogue multiformes
sont les botes de dialogue extensibles et les botes de dialogue multipages. Ces deux types de
botes de dialogue peuvent tre implments dans Qt, simplement dans du code ou par le biais
du Qt Designer.

Qt 4 Livre Page 33 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

33

Les botes de dialogue extensibles ont habituellement une apparence simple, mais elles proposent un bouton de basculement qui permet lutilisateur dalterner entre les apparences simple
et dveloppe de la bote de dialogue. Ces botes de dialogue sont gnralement utilises dans
des applications qui tentent de rpondre la fois aux besoins des utilisateurs occasionnels et
ceux des utilisateurs expriments, masquant les options avances moins que lutilisateur ne
demande explicitement les voir. Dans cette section, nous utiliserons le Qt Designer afin de
crer la bote de dialogue extensible prsente en Figure 2.10.
Cest une bote de dialogue Sort dans un tableur, o lutilisateur a la possibilit de slectionner
une ou plusieurs colonnes trier. Lapparence simple de cette bote de dialogue permet
lutilisateur de saisir une seule cl de tri, et son apparence dveloppe propose deux cls de tri
supplmentaires. Grce au bouton More, lutilisateur bascule entre les apparences simple et
dveloppe.
Figure 2.10
La bote de dialogue Sort
dans ses deux versions,
simple et dveloppe

Nous crerons le widget avec son apparence dveloppe dans le Qt Designer, et nous masquerons les cls secondaires et tertiaires lexcution. Le widget semble complexe, mais il est
assez simple raliser dans le Qt Designer. Lastuce est de se charger dabord de la cl
primaire, puis de la dupliquer deux fois pour obtenir les cls secondaires et tertiaires :
1. Cliquez sur File > New Form et slectionnez le modle "Dialog with Buttons Right".
2. Crez le bouton More et faites-le glisser dans la disposition verticale, sous llment
despacement vertical. Dfinissez la proprit text du bouton More en &More et sa
proprit checkable en true. Configurez la proprit default du bouton OK en true.
3. Crez une zone de groupe, deux tiquettes, deux zones de liste droulante et un lment
despacement horizontal, puis placez-les sur le formulaire.
4. Faites glisser le coin infrieur droit de la zone de groupe pour lagrandir. Puis dplacez les
autres widgets dans la zone de groupe pour les positionner peu prs comme dans la
Figure 2.11 (a).

Qt 4 Livre Page 34 Jeudi, 7. dcembre 2006 12:14 12

34

Qt4 et C++ : Programmation dinterfaces GUI

5. Faites glisser le bord droit de la seconde zone de liste droulante, de sorte quelle soit environ
deux fois plus grande que la premire zone de liste.
6. Dfinissez la proprit title de la zone de groupe en &Primary Key, la proprit text de
la premire tiquette en Column: et celle de la deuxime tiquette en Order:.
7. Cliquez du bouton droit sur la premire zone de liste droulante et slectionnez Edit Items
dans le menu contextuel pour ouvrir lditeur de zone de liste droulante du Qt Designer.
Crez un lment avec le texte "None".
8. Cliquez du bouton droit sur la seconde zone de liste droulante et slectionnez Edit Items.
Crez les lments "Ascending" et "Descending".
9. Cliquez sur la zone de groupe, puis sur Form > Lay Out in a Grid. Cliquez nouveau sur la
zone de groupe et sur Form > Adjust Size. Vous aboutirez la disposition affiche dans
la Figure 2.11 (b).
Figure 2.11
Disposer les enfants
de la zone de groupe
dans une grille

(a) Sans disposition

(b) Avec disposition

Si une disposition ne savre pas trs bonne ou si vous avez fait une erreur, vous pouvez toujours
cliquer sur Edit > Undo ou Form > Break Layout, puis repositionner les widgets et ressayer.
Figure 2.12
Disposer les enfants
du formulaire dans
une grille

(a) Sans disposition

(b) Avec disposition

Qt 4 Livre Page 35 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

35

Nous allons maintenant ajouter les zones de groupe Secondary Key et Tertiary Key :
1. Prenez garde ce que la fentre soit assez grande pour accueillir les composants supplmentaires.
2. Maintenez la touche Ctrl enfonce (Alt sur Mac) et cliquez sur la zone de groupe Primary
Key pour en crer une copie (et de son contenu) au-dessus de loriginal. Faites glisser la
copie sous la zone de groupe originale en gardant toujours la touche Ctrl (ou Alt) enfonce.
Rptez ce processus pour crer une troisime zone de groupe, en la faisant glisser sous la
deuxime zone.
3. Transformez leurs proprits title en &Secondary Key et &Tertiary Key.
4. Crez un lment despacement vertical et placez-le entre la zone de la cl primaire et celle
de la cl secondaire.
5. Disposez les widgets comme illustr en Figure 2.12 (a).
6. Cliquez sur le formulaire pour annuler la slection de tout widget, puis sur Form > Lay Out
in a Grid. Le formulaire devrait dsormais correspondre celui de la Figure 2.12 (b).
7. Dfinissez la proprit sizeHint des deux lments despacement verticaux en [20, 0].
La disposition de type grille qui en rsulte comporte deux colonnes et quatre lignes, ce qui fait
un total de huit cellules. La zone de groupe Primary Key, llment despacement vertical le
plus gauche, les zones de groupe Secondary Key et Tertiary Key occupent chacun une seule
cellule. La disposition verticale qui contient les boutons OK, Cancel et More occupe deux
cellules. Il reste donc deux cellules vides en bas droite de la bote de dialogue. Si ce nest pas
le cas, annulez la disposition, repositionnez les widgets et essayez nouveau.
Renommez le formulaire "SortDialog" et changez le titre de la fentre en "Sort".Dfinissez les
noms des widgets enfants comme dans la Figure 2.13.
Figure 2.13
Nommer les widgets
du formulaire

primaryGroupBox

okButton

primaryColumnCombo

cancelButton

primaryOrderCombo

moreButton
secondaryGroupBox
secondaryColumnCombo
secondaryOrderCombo
tertiaryGroupBox
tertiaryColumnCombo
tertiaryOrderCombo

Qt 4 Livre Page 36 Jeudi, 7. dcembre 2006 12:14 12

36

Qt4 et C++ : Programmation dinterfaces GUI

Cliquez sur Edit > Edit Tab Order. Cliquez sur chaque zone de liste droulante de haut en bas,
puis cliquez sur les boutons OK, Cancel et More situs droite. Cliquez sur Edit > Edit
Widgets pour quitter le mode dition de lordre de tabulation.
Maintenant que le formulaire a t conu, nous sommes prts le rendre fonctionnel en configurant certaines connexions signal-slot. Le Qt Designer vous permet dtablir des connexions
entre les widgets qui font partie du mme formulaire. Nous devons tablir deux connexions.
Cliquez sur Edit > Edit Signals/Slots pour passer en mode de connexion dans le Qt Designer.
Les connexions sont reprsentes par des flches bleues entre les widgets du formulaire. Vu
que nous avons choisi le modle "Dialog with Buttons Right", les boutons OK et Cancel sont
dj connects aux slots accept() et reject() de QDialog. Les connexions sont galement
rpertories dans lditeur de signal/slot du Qt Designer.
Pour tablir une connexion entre deux widgets, cliquez sur le widget "expditeur" et faites glisser la flche rouge vers le widget "destinataire".Une bote de dialogue souvre o vous pouvez
choisir le signal et le slot connecter.
Figure 2.14
Connecter les widgets
du formulaire

La premire connexion est tablir entre moreButton et secondaryGroupBox. Faites glisser


la flche rouge entre ces deux widgets, puis choisissez toggled(bool) comme signal et
setVisible(bool) comme slot. Par dfaut, le Qt Designer ne rpertorie pas setVisible(bool) dans sa liste de slots, mais il apparatra si vous activez loption Show all signals
and slots.
Vous devez ensuite crer une connexion entre le signal toggled(bool) de moreButton et le
slot setVisible(bool) de tertiaryGroupBox. Lorsque les connexions sont effectues,
cliquez sur Edit > Edit Widgets pour quitter le mode de connexion.

Qt 4 Livre Page 37 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

37

Figure 2.15
Lditeur de connexions
du Qt Designer

Enregistrez la bote de dialogue sous sortdialog.ui dans un rpertoire appel sort. Pour
ajouter du code au formulaire, vous emploierez la mme technique dhritage multiple que
celle utilise pour la bote de dialogue Go-to-Cell de la section prcdente.
Crez tout dabord un fichier sortdialog.h qui comporte les lments suivants :
#ifndef SORTDIALOG_H
#define SORTDIALOG_H
#include <QDialog>
#include "ui_sortdialog.h"
class SortDialog: public QDialog, public Ui::SortDialog
{
Q_OBJECT
public:
SortDialog(QWidget *parent = 0);
void setColumnRange(QChar first, QChar last);
};
#endif

Puis crez sortdialog.cpp:


1

#include <QtGui>

#include "sortdialog.h"

3
4
5
6

SortDialog::SortDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);

7
8

secondaryGroupBox->hide();
tertiaryGroupBox->hide();

Qt 4 Livre Page 38 Jeudi, 7. dcembre 2006 12:14 12

38

Qt4 et C++ : Programmation dinterfaces GUI

layout()->setSizeConstraint(QLayout::SetFixedSize);

setColumnRange(A, Z);

10
11 }
12
13
14
15
16

void SortDialog::setColumnRange(QChar first, QChar last)


{
primaryColumnCombo->clear();
secondaryColumnCombo->clear();
tertiaryColumnCombo->clear();

17
18

secondaryColumnCombo->addItem(tr("None"));
tertiaryColumnCombo->addItem(tr("None"));

19
20

primaryColumnCombo->setMinimumSize(
secondaryColumnCombo->sizeHint());
QChar ch = first;
while (ch <= last) {
primaryColumnCombo->addItem(QString(ch));
secondaryColumnCombo->addItem(QString(ch));
tertiaryColumnCombo->addItem(QString(ch));
ch = ch.unicode() + 1;
}

21
22
23
24
25
26
27
28 }

Le constructeur masque les zones secondaire et tertiaire de la bote de dialogue. Il dfinit aussi
la proprit sizeConstraint de la disposition du formulaire en QLayout::SetFixedSize,
lutilisateur ne pourra donc pas la redimensionner. La disposition se charge ensuite de redimensionner automatiquement la bote de dialogue quand des widgets enfants sont affichs ou
masqus, vous tre donc sr que la bote de dialogue sera toujours prsente dans sa taille
optimale.
Le slot setColumnRange() initialise le contenu des zones de liste droulante en fonction des
colonnes slectionnes dans le tableur. Nous insrons un lment "None" dans ces zones de
liste pour les cls secondaire et tertiaire (facultatives).
Les lignes 19 et 20 prsentent un comportement subtil de la disposition. La fonction QWidget::sizeHint() retourne la taille "idale" dun widget, ce que le systme de disposition
essaie de respecter. Ceci explique pourquoi les diffrents types de widgets, ou des widgets
similaires avec un contenu diffrent, peuvent se voir attribuer des tailles diffrentes par le
systme de disposition. Concernant les zones de liste droulante, cela signifie que les zones
secondaire et tertiaire qui contiennent "None" seront plus grandes que la zone primaire qui ne
contient que des entres une lettre. Pour viter cette incohrence, nous dfinissons la taille
minimale de la zone de liste droulante primaire en taille idale de la zone secondaire.
Voici une fonction de test main() qui configure la plage de manire inclure les colonnes C
F, puis affiche la bote de dialogue :
#include <QApplication>
#include "sortdialog.h"

Qt 4 Livre Page 39 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

39

int main(int argc, char *argv[])


{
QApplication app(argc, argv);
SortDialog *dialog = new SortDialog;
dialog->setColumnRange(C, F);
dialog->show();
return app.exec();
}

Ceci termine la bote de dialogue extensible. Vous pouvez constater que ce type de bote de
dialogue nest pas plus compliqu concevoir quune bote de dialogue ordinaire : tout ce dont
vous avez besoin, cest un bouton de basculement, quelques connexions signal-slot supplmentaires et une disposition non redimensionnable. Dans des applications de production, il est
assez frquent que le bouton qui contrle lextension affiche le texte Advanced >>> quand
seule la bote de dialogue de base est visible et Advanced <<< quand elle est dveloppe. Cest
facile concevoir dans Qt en appelant setText() sur QPushButton ds quon clique dessus.
Lautre type courant de bote de dialogue multiforme, les botes de dialogue multipages, est
encore plus facile concevoir dans Qt, soit en crant le code, soit par le biais du Qt Designer.
De telles botes de dialogue peuvent tre gnres de diverses manires.

Un QTabWidget peut tre exploit indpendamment. Il propose une barre donglets en


haut qui contrle un QStackedWidget intgr.

Un QListWidget et un QStackedWidget peuvent tre employs ensemble, avec llment en


cours de QListWidget qui dtermine quelle page est affiche par QStackedWidget, en
connectant le signal QListWidget::currentRowChanged() au slot QStackedWidget::setCurrentIndex().

Un QTreeWidget peut tre utilis avec un QStackedWidget de la mme faon quavec un


QListWidget.

La classe QStackedWidget est aborde au Chapitre 6.

Botes de dialogue dynamiques


Les botes de dialogue dynamiques sont cres depuis les fichiers .ui du Qt Designer au
moment de lexcution. Au lieu de convertir le fichier .ui en code C++ grce uic, vous
pouvez charger le fichier lexcution laide de la classe QUiLoader:
QUiLoader uiLoader;
QFile file("sortdialog.ui");
QWidget *sortDialog = uiLoader.load(&file);
if (sortDialog) {
...
}

Qt 4 Livre Page 40 Jeudi, 7. dcembre 2006 12:14 12

40

Qt4 et C++ : Programmation dinterfaces GUI

Vous pouvez accder aux widgets enfants du formulaire en utilisant QObject::findChild<T>():


QComboBox *primaryColumnCombo =
sortDialog->findChild<QComboBox *>("primaryColumnCombo");
if (primaryColumnCombo) {
...
}

La fonction findChild<T>() est une fonction membre modle qui retourne lobjet enfant qui
correspond au nom et au type donn. Vu les limites du compilateur, elle nest pas disponible
pour MSVC 6. Si vous devez utiliser le compilateur MSVC 6, appelez plutt la fonction
globale qFindChild<T>() qui fonctionne exactement de la mme faon.
La classe QUiLoader se situe dans une bibliothque part. Pour employer QUiLoader depuis
une application Qt, vous devez ajouter cette ligne de code au fichier .pro de lapplication :
CONFIG += uitools

Les botes de dialogue dynamiques vous permettent de modifier la disposition dun formulaire
sans recompiler lapplication. Elles peuvent aussi servir crer des applications client lger, o
lexcutable intgre principalement un formulaire frontal et o tous les autres formulaires sont
crs comme ncessaire.

Classes de widgets et de botes de dialogue


intgres
Qt propose un ensemble complet de widgets intgrs et de botes de dialogue courantes adapts la plupart des situations. Dans cette section, nous allons prsenter une capture de la
plupart dentre eux. Quelques widgets spcialiss ne sont tudis quultrieurement : les
widgets de fentre principale comme QMenuBar, QToolBar et QStatusBar sont abords dans
le Chapitre 3, et les widgets lis la disposition, tels que QSplitter et QScrollArea, sont
analyss dans le Chapitre 6. La majorit des widgets intgrs et des botes de dialogue est
prsente dans les exemples de ce livre. Dans les captures suivantes, les widgets sont affichs
avec le style Plastique.
Figure 2.16
Les widgets bouton
de Qt
QPushButton

QToolButton

QCheckBox

QRadioButton

Qt propose quatre types de "boutons" : QPushButton, QToolButton, QCheckBox et QRadioButton. QPushButton et QToolButton sont le plus souvent ajouts pour initier une action
quand on clique dessus, mais ils peuvent aussi se comporter comme des boutons de basculement

Qt 4 Livre Page 41 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Crer des botes de dialogue

41

(clic pour enfoncer, clic pour restaurer). QCheckBox peut servir pour les options indpendantes
on/off, alors que les QRadioButton sexcluent mutuellement.
Figure 2.17
Les widgets conteneurs
une seule page de Qt

QGroupBox

QFrame

Les widgets conteneurs de Qt sont des widgets qui contiennent dautres widgets. QFrame peut
aussi tre employ seul pour tracer simplement des lignes et il est hrit par la plupart des
autres classes de widgets, dont QToolBox et QLabel.
Figure 2.18
Les widgets conteneurs
multipages de Qt

QTabWidget

QToolBox

QTabWidget et QToolBox sont des widgets multipages. Chaque page est un widget enfant, et
les pages sont numrotes en commenant 0.
Figure 2.19
Les widgets daffichage
dlments de Qt

QListView(liste)

QTreeView

QListView(icnes)

QTableView

Qt 4 Livre Page 42 Jeudi, 7. dcembre 2006 12:14 12

42

Qt4 et C++ : Programmation dinterfaces GUI

Les affichages dlments sont optimiss pour grer de grandes quantits de donnes et font
souvent appel des barres de dfilement. Le mcanisme de la barre de dfilement est implment dans QAbstractScrollArea, une classe de base pour les affichages dlments et
dautres types de widgets dfilement.
Qt fournit quelques widgets simplement destins la prsentation des informations. QLabel
est le plus important dentre eux et peut tre employ pour afficher un texte enrichi (grce
une syntaxe simple de style HTML) et des images.
QTextBrowser est une sous-classe de QTextEdit en lecture seule qui prend en charge la
syntaxe HTML de base, y compris les listes, les tables, les images et les liens hypertexte.
LAssistant de Qt se sert de QTextBrowser pour prsenter la documentation lutilisateur.
Figure 2.20
Les widgets daffichage
de Qt
QLabel(texte)

QLabel (image)

QLCDNumber

QProgressBar

QTextBrowser

Qt propose plusieurs widgets pour les entres de donnes. QLineEdit peut contrler son
entre par le biais dun masque de saisie ou dun validateur. QTextEdit est une sous-classe de
QAbstractScrollArea capable de modifier de grandes quantits de texte.
Qt met votre disposition un ensemble standard de botes de dialogue courantes pratiques pour
demander lutilisateur de choisir une couleur, une police ou un fichier ou dimprimer un
document.
Sous Windows et Mac OS X, Qt exploite les botes de dialogue natives plutt que ses propres
botes de dialogue si possible.
Une bote de message polyvalente et une bote de dialogue derreur qui conserve les messages
affichs apparaissent. La progression des oprations longues peut tre indique dans un QProgressDialog ou QProgressBar prsent prcdemment. QInputDialog se rvle trs pratique
quand une seule ligne de texte ou un seul chiffre est demand lutilisateur.
Les widgets intgrs et les botes de dialogue courantes mettent votre disposition de
nombreuses fonctionnalits prtes lemploi. Des exigences plus particulires peuvent
souvent tre satisfaites en configurant les proprits du widget ou en connectant des signaux
des slots et en implmentant un comportement personnalis dans les slots.

Qt 4 Livre Page 43 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 2

Figure 2.21
Les widgets dentre
de Qt

Crer des botes de dialogue

QSpinBox

QDoubleSpinBox

QDateEdit

QTimeEdit

QScrollBar

QComboBox

QDateTimeEdit

QSlider

QLineEdit

QTextEdit

QDial

Figure 2.22
Les botes de dialogue
relatives la couleur
et la police de Qt

QColorDialog

QFontDialog

Figure 2.23
Les botes de dialogue
relatives limpression
et aux fichiers de Qt
QPageSetupDialog

QFileDialog

QPrintDialog

43

Qt 4 Livre Page 44 Jeudi, 7. dcembre 2006 12:14 12

44

Qt4 et C++ : Programmation dinterfaces GUI

Figure 2.24
Les botes de dialogue
de feedback de Qt
QInputDialog

QProgressDialog

QMessageBox

QErrorMessage

Dans certains cas, il est judicieux de crer un widget personnalis en partant de zro. Qt facilite
normment ce processus, et les widgets personnaliss peuvent accder la mme fonction de
dessin indpendante de la plate-forme que les widgets intgrs de Qt. Les widgets personnaliss
peuvent mme tre intgrs par le biais du Qt Designer, de sorte quils puissent tre employs
de la mme faon que les widgets intgrs de Qt. Le Chapitre 5 vous explique comment crer
des widgets personnaliss.

Qt 4 Livre Page 45 Jeudi, 7. dcembre 2006 12:14 12

3
Crer des fentres
principales
Au sommaire de ce chapitre
Drivation de QMainWindow
Crer des menus et des barres doutils
Configurer la barre dtat
Implmenter le menu File
Utiliser des botes de dialogue
Stocker des paramtres
Documents multiples
Pages daccueil

Ce chapitre vous apprendra crer des fentres principales avec Qt. Vous serez ainsi
capable de concevoir toute linterface utilisateur dune application, constitue de menus,
de barres doutils, dune barre dtat et dautant de botes de dialogue que ncessaire.
La fentre principale dune application fournit le cadre dans lequel linterface utilisateur
est gnre. Celle de lapplication Spreadsheet illustre en Figure 3.1 servira de base
pour ltude dans ce chapitre. Cette application emploie les botes de dialogue Find,
Go-to-Cell et Sort cres au Chapitre 2.

Qt 4 Livre Page 46 Jeudi, 7. dcembre 2006 12:14 12

46

Qt4 et C++ : Programmation dinterfaces GUI

Figure 3.1
Lapplication
Spreadsheet

Derrire la plupart des applications GUI se cache du code qui fournit les fonctionnalits sousjacentes par exemple, le code qui lit et crit des fichiers ou qui traite les donnes prsentes
dans linterface utilisateur. Au Chapitre 4, vous verrez comment implmenter de telles fonctionnalits, toujours en utilisant lapplication Spreadsheet comme exemple.

Drivation de QMainWindow
La fentre principale dune application est cre en drivant QMainWindow. La plupart des
techniques tudies dans le Chapitre 2 pour crer des botes de dialogue sappliquent galement la conception de fentres principales, puisque QDialog et QMainWindow hritent de
QWidget.
Les fentres principales peuvent tre cres laide du Qt Designer, mais dans ce chapitre
nous effectuerons tout le processus dans du code pour vous montrer le fonctionnement. Si vous
prfrez une approche plus visuelle, consultez le chapitre "Creating Main Windows in Qt
Designer" dans le manuel en ligne de cet outil.
Le code source de la fentre principale de lapplication Spreadsheet est rparti entre
mainwindow.h et mainwindow.cpp. Commenons par examiner le fichier den-tte :
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class
class
class
class

QAction;
QLabel;
FindDialog;
Spreadsheet;

Qt 4 Livre Page 47 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

47

class MainWindow: public QMainWindow


{
Q_OBJECT
public:
MainWindow();
protected:
void closeEvent(QCloseEvent *event);

Nous dfinissons la classe MainWindow comme une sous-classe de QMainWindow. Elle contient
la macro Q_ OBJECT puisquelle fournit ses propres signaux et slots.
La fonction closeEvent() est une fonction virtuelle dans QWidget qui est appele automatiquement quand lutilisateur ferme la fentre. Elle est rimplmente dans MainWindow, de
sorte que vous puissiez poser lutilisateur la question standard "Voulez-vous enregistrer vos
modifications ?" et sauvegarder les prfrences de lutilisateur sur le disque.
private slots:
void newFile();
void open();
bool save();
bool saveAs();
void find();
void goToCell();
void sort();
void about();

Certaines options de menu, telles que File > New (Fichier > Nouveau) et Help > About (Aide >
A propos), sont implmentes comme des slots privs dans MainWindow. La majorit des
slots ont une valeur de retour void, mais save() et saveAs() retournent une valeur bool.
La valeur de retour est ignore quand un slot est excut en rponse un signal, mais lorsque
vous invoquez un slot comme une fonction, la valeur de retour est disponible, comme si vous
aviez appel nimporte quelle fonction C++ ordinaire.
void openRecentFile();
void updateStatusBar();
void spreadsheetModified();
private:
void
void
void
void
void
void
void
bool
bool
bool

createActions();
createMenus();
createContextMenu();
createToolBars();
createStatusBar();
readSettings();
writeSettings();
okToContinue();
loadFile(const QString &fileName);
saveFile(const QString &fileName);

Qt 4 Livre Page 48 Jeudi, 7. dcembre 2006 12:14 12

48

Qt4 et C++ : Programmation dinterfaces GUI

void setCurrentFile(const QString &fileName);


void updateRecentFileActions();
QString strippedName(const QString &fullFileName);

La fentre principale a besoin de slots privs et de plusieurs fonctions prives pour prendre en
charge linterface utilisateur.
Spreadsheet *spreadsheet;
FindDialog *findDialog;
QLabel *locationLabel;
QLabel *formulaLabel;
QStringList recentFiles;
QString curFile;
enum { MaxRecentFiles = 5 };
QAction *recentFileActions[MaxRecentFiles];
QAction *separatorAction;
QMenu *fileMenu;
QMenu *editMenu;
...
QToolBar *fileToolBar;
QToolBar *editToolBar;
QAction *newAction;
QAction *openAction;
...
QAction *aboutQtAction;
};
#endif

En plus de ses slots et fonctions privs, MainWindow possde aussi de nombreuses variables
prives. Elles seront analyses au fur et mesure que vous les rencontrerez.
Nous allons dsormais passer en revue limplmentation :
#include
#include
#include
#include
#include
#include

<QtGui>
"finddialog.h"
"gotocelldialog.h"
"mainwindow.h"
"sortdialog.h"
"spreadsheet.h"

Nous incluons le fichier den-tte <QtGui>, qui contient la dfinition de toutes les classes Qt
utilises dans notre sous-classe. Nous englobons aussi certains fichiers den-tte personnaliss,
notamment finddialog.h, gotocelldialog.h et sortdialog.h du Chapitre 2.
MainWindow::MainWindow()
{
spreadsheet = new Spreadsheet;
setCentralWidget(spreadsheet);
createActions();

Qt 4 Livre Page 49 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

49

createMenus();
createContextMenu();
createToolBars();
createStatusBar();
readSettings();
findDialog = 0;
setWindowIcon(QIcon(":/images/icon.png"));
setCurrentFile("");
}

Dans le constructeur, nous commenons par crer un widget Spreadsheet et nous le configurons de manire ce quil devienne le widget central de la fentre principale. Le widget central
se trouve au milieu de la fentre principale (voir Figure 3.2). La classe Spreadsheet est une
sous-classe de QTableWidget avec certaines fonctions de tableur, comme la prise en charge
des formules de tableur. Nous limplmenterons dans le Chapitre 4.
Nous appelons les fonctions prives createActions(), createMenus(), createContextMenu(), createToolBars() et createStatusBar() pour configurer le reste de la fentre
principale. Nous invoquons galement la fonction prive readSettings() afin de lire les
paramtres stocks de lapplication.
Nous initialisons le pointeur findDialog pour que ce soit un pointeur nul ; au premier appel
de MainWindow::find(), nous crerons lobjet FindDialog.
A la fin du constructeur, nous dfinissons licne de la fentre en icon.png, un fichier PNG.
Qt supporte de nombreux formats dimage, dont BMP, GIF1, JPEG, PNG, PNM, XBM et
XPM. Lappel de QWidget::setWindowIcon() dfinit licne affiche dans le coin suprieur
gauche de la fentre. Malheureusement, il nexiste aucun moyen indpendant de la plate-forme
pour configurer licne de lapplication qui apparat sur le bureau. Les procdures spcifiques
la plate-forme sont expliques ladresse suivante :
http://doc.trolltech.com/4.1/appicon.html.
Les applications GUI utilisent gnralement beaucoup dimages. Il existe plusieurs mthodes
pour introduire des images dans une application. Les plus communes sont les suivantes :

stocker des images dans des fichiers et les charger lexcution ;


inclure des fichiers XPM dans le code source (Cela fonctionne parce que les fichiers XPM
sont aussi des fichiers C++ valides.) ;
utiliser le mcanisme des ressources de Qt.

1. La prise en charge du format GIF est dsactive dans Qt par dfaut, parce que lalgorithme de dcompression utilis par les fichiers GIF tait brevet dans certains pays o les brevets logiciels taient
reconnus. Nous pensons que ce brevet est arriv expiration dans le monde entier. Pour activer le
support GIF dans Qt, transmettez loption de ligne de commande -qt-gif au script configure ou
dfinissez loption approprie dans le programme dinstallation de Qt.

Qt 4 Livre Page 50 Jeudi, 7. dcembre 2006 12:14 12

50

Qt4 et C++ : Programmation dinterfaces GUI

Figure 3.2
Les zones
de QMainWindow

Titre de la fentre

Barre de menus
Zones de la barre d'outils
Zones de la fentre ancre

Widget central

Barre d'tat

Dans notre cas, nous employons le mcanisme des ressources de Qt, puisquil savre plus
pratique que de charger des fichiers lexcution et il est compatible avec nimporte quel
format dimage pris en charge. Nous avons choisi de stocker les images dans larborescence
source dans un sous-rpertoire nomm images.
Pour utiliser le systme des ressources de Qt, nous devons crer un fichier de ressources et
ajouter une ligne au fichier .pro qui identifie le fichier de ressources. Dans cet exemple, nous
avons appel le fichier de ressources spreadsheet.qrc, nous insrons donc la ligne suivante
dans le fichier .pro:
RESOURCES = spreadsheet.qrc

Le fichier de ressources lui-mme utilise un format XML simple. Voici un extrait de celui que
nous avons employ :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/icon.png</file>
...
<file>images/gotocell.png</file>
</qresource>
</RCC>

Les fichiers de ressources sont compils dans lexcutable de lapplication, vous ne pouvez
donc pas les perdre. Quand vous vous rfrez aux ressources, vous codez le prfixe :/ (double
point, slash), cest pourquoi licne est spcifie comme suit, :/images/icon.png. Les
ressources peuvent tre de nimporte quel type (pas uniquement des images) et vous avez la
possibilit de les utiliser la plupart des emplacements o Qt attend un nom de fichier. Vous
les tudierez plus en dtail au Chapitre 12.

Qt 4 Livre Page 51 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

51

Crer des menus et des barres doutils


La majorit des applications GUI modernes proposent des menus, des menus contextuels et
des barres doutils. Les menus permettent aux utilisateurs dexplorer lapplication et dapprendre connatre de nouvelles commandes, alors que les menus contextuels et les barres doutils
fournissent un accs rapide aux fonctionnalits frquemment utilises.

Figure 3.3
Les menus de lapplication Spreadsheet

Qt simplifie la programmation des menus et des barres doutils grce son concept daction.
Une action est un lment qui peut tre ajout nimporte quel nombre de menus et barres
doutils. Crer des menus et des barres doutils dans Qt implique ces tapes :

crer et configurer les actions ;

crer des menus et y introduire des actions ;

crer des barres doutils et y introduire des actions.

Dans lapplication Spreadsheet, les actions sont cres dans createActions():


void MainWindow::createActions()
{
newAction = new QAction(tr("&New"), this);
newAction->setIcon(QIcon(":/images/new.png"));
newAction->setShortcut(tr("Ctrl+N"));
newAction->setStatusTip(tr("Create a new spreadsheet file"));
connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));

Laction New a un bouton daccs rapide (New), un parent (la fentre principale), une icne
(new.png), un raccourci clavier (Ctrl+N) et une infobulle lie ltat. Nous connectons le
signal triggered() de laction au slot priv newFile() de la fentre principale, que nous
implmenterons dans la prochaine section. Cette connexion garantit que lorsque lutilisateur
slectionne File > New, clique sur le bouton New de la barre doutils, ou appuie sur Ctrl+N, le
slot newFile() est appel.

Qt 4 Livre Page 52 Jeudi, 7. dcembre 2006 12:14 12

52

Qt4 et C++ : Programmation dinterfaces GUI

Les actions Open, Save et Save As ressemblent beaucoup laction New, nous passerons donc
directement la partie "fichiers ouverts rcemment" du menu File :
...
for (int i = 0; i < MaxRecentFiles; ++i) {
recentFileActions[i] = new QAction(this);
recentFileActions[i]->setVisible(false);
connect(recentFileActions[i], SIGNAL(triggered()),
this, SLOT(openRecentFile()));
}

Nous alimentons le tableau recentFileActions avec des actions. Chaque action est masque
et connecte au slot openRecentFile(). Plus tard, nous verrons comment afficher et utiliser
les actions relatives aux fichiers rcents.
Nous pouvons donc passer laction Select All:
...
selectAllAction = new QAction(tr("&All"), this);
selectAllAction->setShortcut(tr("Ctrl+A"));
selectAllAction->setStatusTip(tr("Select all the cells in the "
"spreadsheet"));
connect(selectAllAction, SIGNAL(triggered()),
spreadsheet, SLOT(selectAll()));

Le slot selectAll() est fourni par lun des anctres de QTableWidget, QAbstractItemView, nous navons donc pas limplmenter nous-mmes.
Continuons donc par laction Show Grid dans le menu Options :
...
showGridAction = new QAction(tr("&Show Grid"), this);
showGridAction->setCheckable(true);
showGridAction->setChecked(spreadsheet->showGrid());
showGridAction->setStatusTip(tr("Show or hide the spreadsheets "
"grid"));
connect(showGridAction, SIGNAL(toggled(bool)),
spreadsheet, SLOT(setShowGrid(bool)));

Show Grid est une action cocher. Elle est affiche avec une coche dans le menu et est implmente comme un bouton bascule dans la barre doutils. Quand laction est active, le composant Spreadsheet affiche une grille. Nous initialisons laction avec la valeur par dfaut du
composant Spreadsheet, de sorte quelles soient synchronises au dmarrage. Puis nous
connectons le signal toggled(bool) de laction Show Grid au slot setShowGrid(bool) du
composant Spreadsheet, quil hrite de QTableWidget. Lorsque cette action est ajoute un
menu ou une barre doutils, lutilisateur peut activer ou dsactiver laffichage de la grille.
Les actions Show Grid et Auto-Recalculate sont des actions cocher indpendantes. Qt
prend aussi en charge des actions qui sexcluent mutuellement par le biais de la classe
QActionGroup.

Qt 4 Livre Page 53 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

53

...
aboutQtAction = new QAction(tr("About &Qt"), this);
aboutQtAction->setStatusTip(tr("Show the Qt librarys About box"));
connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}

Concernant laction About Qt, nous utilisons le slot aboutQt() de lobjet QApplication,
accessible via la variable globale qApp.
Figure 3.4
About Qt

Maintenant que nous avons cr les actions, nous pouvons poursuivre en concevant un systme
de menus qui les englobe :
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addAction(saveAsAction);
separatorAction = fileMenu->addSeparator();
for (int i = 0; i < MaxRecentFiles; ++i)
fileMenu->addAction(recentFileActions[i]);
fileMenu->addSeparator();
fileMenu->addAction(exitAction);

Dans Qt, les menus sont des instances de QMenu. La fonction addMenu() cre un widget
QMenu avec le texte spcifi et lajoute la barre de menus. La fonction QMainWindow::menuBar() retourne un pointeur vers un QMenuBar. La barre de menus est cre la
premire fois que menuBar() est appel.
Nous commenons par crer le menu File, puis nous y ajoutons les actions New, Open, Save et
Save As. Nous insrons un sparateur pour regrouper visuellement des lments connexes.
Nous utilisons une boucle for pour ajouter les actions (masques lorigine) du tableau
recentFileActions, puis nous ajoutons laction exitAction la fin.

Qt 4 Livre Page 54 Jeudi, 7. dcembre 2006 12:14 12

54

Qt4 et C++ : Programmation dinterfaces GUI

Nous avons conserv un pointeur vers lun des sparateurs. Nous avons ainsi la possibilit de
le masquer (sil ny a pas de fichiers rcents) ou de lafficher, parce que nous ne voulons pas
afficher deux sparateurs sans rien entre eux.
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->addAction(cutAction);
editMenu->addAction(copyAction);
editMenu->addAction(pasteAction);
editMenu->addAction(deleteAction);
selectSubMenu = editMenu->addMenu(tr("&Select"));
selectSubMenu->addAction(selectRowAction);
selectSubMenu->addAction(selectColumnAction);
selectSubMenu->addAction(selectAllAction);
editMenu->addSeparator();
editMenu->addAction(findAction);
editMenu->addAction(goToCellAction);

Occupons-nous dsormais de crer le menu Edit, en ajoutant des actions avec QMenu::addAction() comme nous lavons fait pour le menu File et en ajoutant le sous-menu avec
QMenu::addMenu() lendroit o nous souhaitons quil apparaisse. Le sous-menu, comme le
menu auquel il appartient, est un QMenu.
toolsMenu = menuBar()->addMenu(tr("&Tools"));
toolsMenu->addAction(recalculateAction);
toolsMenu->addAction(sortAction);
optionsMenu = menuBar()->addMenu(tr("&Options"));
optionsMenu->addAction(showGridAction);
optionsMenu->addAction(autoRecalcAction);
menuBar()->addSeparator();
helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
}

Nous crons les menus Tools, Options et Help de manire similaire. Nous introduisons un
sparateur entre les menus Options et Help. En styles Motif et CDE, le sparateur aligne le
menu Help droite ; dans les autres styles, le sparateur est ignor.
Figure 3.5
Barre de menus en styles
Motif et Windows
void MainWindow::createContextMenu()
{
spreadsheet->addAction(cutAction);

Qt 4 Livre Page 55 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

55

spreadsheet->addAction(copyAction);
spreadsheet->addAction(pasteAction);
spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu);
}

Tout widget Qt peut avoir une liste de QAction associe. Pour proposer un menu contextuel
pour lapplication, nous ajoutons les actions souhaites au widget Spreadsheet et nous dfinissons la stratgie du menu contextuel de ce widget de sorte quil affiche un menu contextuel
avec ces actions. Les menus contextuels sont invoqus en cliquant du bouton droit sur un
widget ou en appuyant sur une touche spcifique la plate-forme.
Figure 3.6
Le menu contextuel de
lapplication Spreadsheet

Il existe un moyen plus labor de proposer des menus contextuels : implmenter nouveau la
fonction QWidget::contextMenuEvent(), crer un widget QMenu, lalimenter avec les
actions voulues et appeler exec().
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("&File"));
fileToolBar->addAction(newAction);
fileToolBar->addAction(openAction);
fileToolBar->addAction(saveAction);
editToolBar = addToolBar(tr("&Edit"));
editToolBar->addAction(cutAction);
editToolBar->addAction(copyAction);
editToolBar->addAction(pasteAction);
editToolBar->addSeparator();
editToolBar->addAction(findAction);
editToolBar->addAction(goToCellAction);
}

La cration de barres doutils ressemble beaucoup celle des menus. Nous concevons les
barres doutils File et Edit. Comme un menu, une barre doutils peut possder des sparateurs.
Figure 3.7
Les barres doutils de
lapplication Spreadsheet

Qt 4 Livre Page 56 Jeudi, 7. dcembre 2006 12:14 12

56

Qt4 et C++ : Programmation dinterfaces GUI

Configurer la barre dtat


Lorsque les menus et les barres doutils sont termins, vous tes prt vous charger de la barre
dtat de lapplication Spreadsheet.
Normalement, cette barre dtat contient deux indicateurs : lemplacement et la formule de la
cellule en cours.
Figure 3.8
La barre dtat de lapplication Spreadsheet

Normal

Infobulle sur l'tat

Message temporaire

Le constructeur de MainWindow appelle createStatusBar() pour configurer la barre dtat :


void MainWindow::createStatusBar()
{
locationLabel = new QLabel(" W999 ");
locationLabel->setAlignment(Qt::AlignHCenter);
locationLabel->setMinimumSize(locationLabel->sizeHint());
formulaLabel = new QLabel;
formulaLabel->setIndent(3);
statusBar()->addWidget(locationLabel);
statusBar()->addWidget(formulaLabel, 1);
connect(spreadsheet, SIGNAL(currentCellChanged(int, int, int, int)),
this, SLOT(updateStatusBar()));
connect(spreadsheet, SIGNAL(modified()),
this, SLOT(spreadsheetModified()));
updateStatusBar();
}

La fonction QMainWindow::statusBar() retourne un pointeur vers la barre dtat. (La barre


dtat est cre la premire fois que statusBar() est appele.) Les indicateurs dtat sont
simplement des QLabel dont nous modifions le texte ds que cela savre ncessaire.
Nous avons ajout une indentation formulaLabel, pour que le texte qui y est affich
soit lgrement dcal du bord gauche. Quand les QLabel sont ajouts la barre dtat, ils sont
automatiquement reparents pour devenir des enfants de cette dernire.
La Figure 3.8 montre que les deux tiquettes ont des exigences diffrentes sagissant de lespace.
Lindicateur relatif lemplacement de la cellule ne ncessite que trs peu de place, et lorsque
la fentre est redimensionne, tout espace supplmentaire devrait revenir lindicateur de la
formule de la cellule sur la droite. Vous y parvenez en spcifiant un facteur de redimensionnement

Qt 4 Livre Page 57 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

57

de 1 dans lappel QStatusBar::addWidget() de ltiquette de la formule. Lindicateur


demplacement prsente un facteur de redimensionnement par dfaut de 0, ce qui signifie quil
prfre ne pas tre tir.
Quand QStatusBar organise laffichage des widgets indicateur, il essaie de respecter la taille
idale de chaque widget spcifie par QWidget::sizeHint(), puis redimensionne tout
widget tirable pour combler lespace disponible. La taille idale dun widget dpend du
contenu de ce widget et varie en fonction des modifications du contenu. Pour viter de redimensionner constamment lindicateur demplacement, nous configurons sa taille minimale de
sorte quelle suffise pour contenir le texte le plus grand possible ("W999"), avec trs peu
despace supplmentaire. Nous dfinissons aussi son alignement en Qt::AlignHCenter pour
centrer le texte horizontalement.
Vers la fin de la fonction, nous connectons deux des signaux de Spreadsheet deux des slots
de MainWindow: updateStatusBar() et spreadsheetModified().
void MainWindow::updateStatusBar()
{
locationLabel->setText(spreadsheet->currentLocation());
formulaLabel->setText(spreadsheet->currentFormula());
}

Le slot updateStatusBar() met jour les indicateurs relatifs lemplacement et la


formule de la cellule. Il est invoqu ds que lutilisateur dplace le curseur vers une autre
cellule. Le slot sert galement de fonction ordinaire la fin de createStatusBar() pour
initialiser les indicateurs. Il se rvle ncessaire puisque Spreadsheet nmet pas le signal
currentCellChanged() au dmarrage.
void MainWindow::spreadsheetModified()
{
setWindowModified(true);
updateStatusBar();
}

Le slot spreadsheetModified() dfinit la proprit windowModified en true, ce qui met


jour la barre de titre. La fonction met galement jour les indicateurs demplacement et de
formule, pour quils refltent les circonstances actuelles.

Implmenter le menu File


Dans cette section, nous allons implmenter les slots et les fonctions prives ncessaires pour
faire fonctionner les options du menu File et pour grer la liste des fichiers ouverts rcemment.
void MainWindow::newFile()
{
if (okToContinue()) {
spreadsheet->clear();

Qt 4 Livre Page 58 Jeudi, 7. dcembre 2006 12:14 12

58

Qt4 et C++ : Programmation dinterfaces GUI

setCurrentFile("");
}
}

Le slot newFile() est appel lorsque lutilisateur clique sur loption File > New ou sur le
bouton New de la barre doutils. La fonction prive okToContinue() demande lutilisateur
sil dsire enregistrer ses modifications, si certaines modifications nont pas t sauvegardes
(voir Figure 3.9). Elle retourne true si lutilisateur choisit Yes ou No (vous enregistrez le
document en appuyant sur Yes), et false si lutilisateur clique sur Cancel. La fonction
Spreadsheet::clear() efface toutes les cellules et formules du tableur. La fonction prive
setCurrentFile() met jour le titre de la fentre pour indiquer quun document sans titre
est en train dtre modifi, en plus de configurer la variable prive curFile et de mettre jour
la liste des fichiers ouverts rcemment.
Figure 3.9
"Voulez-vous enregistrer
vos modifications ?"

bool MainWindow::okToContinue()
{
if (isWindowModified()) {
int r = QMessageBox::warning(this, tr("Spreadsheet"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No,
QMessageBox::Cancel | QMessageBox::Escape);
if (r == QMessageBox::Yes) {
return save();
} else if (r == QMessageBox::Cancel) {
return false;
}
}
return true;
}

Dans okToContinue(), nous contrlons ltat de la proprit windowModified. Sil est


correct, nous affichons la bote de message illustre en Figure 3.9. Celle-ci propose les boutons
Yes, No et Cancel. QMessageBox::Default dfinit le bouton Yes comme bouton par dfaut.
QMessageBox::Escape dfinit la touche Echap comme synonyme de Cancel.
Lappel de warning() peut sembler assez complexe de prime abord, mais la syntaxe gnrale
est simple :
QMessageBox::warning(parent, titre, message, bouton0, bouton1, ...);

Qt 4 Livre Page 59 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

59

QMessageBox propose aussi information(), question() et critical(), chacun possdant sa propre icne.
Figure 3.10
Icnes de bote
de message

Information

Question

Avertissement

Critique

void MainWindow::open()
{
if (okToContinue()) {
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (!fileName.isEmpty())
loadFile(fileName);
}
}

Le slot open() correspond File > Open. Comme newFile(), il appelle dabord okToContinue() pour grer toute modification non sauvegarde. Puis il utilise la fonction statique
QFileDialog::getOpenFileName() trs pratique pour demander le nom du nouveau fichier
lutilisateur. La fonction ouvre une bote de dialogue, permet lutilisateur de choisir un
fichier et retourne le nom de ce dernier ou une chane vide si lutilisateur clique sur Cancel.
Le premier argument de QFileDialog::getOpenFileName() est le widget parent. La relation parent-enfant ne signifie pas la mme chose pour les botes de dialogue et pour les autres
widgets. Une bote de dialogue est toujours une fentre en soi, mais si elle a un parent, elle est
centre en haut de ce dernier par dfaut. Une bote de dialogue enfant partage aussi lentre de
la barre des tches de son parent.
Le second argument est le titre que la bote de dialogue doit utiliser. Le troisime argument
indique le rpertoire depuis lequel il doit dmarrer, dans notre cas le rpertoire en cours.
Le quatrime argument spcifie les filtres de fichier. Un filtre de fichier est constitu dun texte
descriptif et dun modle gnrique. Si nous avions pris en charge les fichiers de valeurs spares par des virgules et les fichiers Lotus 1-2-3, en plus du format de fichier natif de
Spreadsheet, nous aurions employ le filtre suivant :
tr("Spreadsheet files (*.sp)\n"
"Comma-separated values files (*.csv)\n"
"Lotus 1-2-3 files (*.wk1 *.wks)")

La fonction prive loadFile() a t invoque dans open() pour charger le fichier. Nous en
avons fait une fonction indpendante, parce que nous aurons besoin de la mme fonctionnalit
pour charger les fichiers ouverts rcemment :
bool MainWindow::loadFile(const QString &fileName)
{
if (!spreadsheet->readFile(fileName)) {

Qt 4 Livre Page 60 Jeudi, 7. dcembre 2006 12:14 12

60

Qt4 et C++ : Programmation dinterfaces GUI

statusBar()->showMessage(tr("Loading canceled"), 2000);


return false;
}
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 2000);
return true;
}

Nous utilisons Spreadsheet::readFile() pour lire le fichier sur le disque. Si le chargement


est effectu avec succs, nous appelons setCurrentFile() pour mettre jour le titre de la
fentre ; sinon, Spreadsheet::readFile() aurait dj inform lutilisateur du problme par
une bote de message. En gnral, il est recommand de laisser les composants de bas niveau
mettre des messages derreur, parce quils peuvent apporter des dtails prcis sur ce qui sest
pass.
Dans les deux cas, nous affichons un message dans la barre dtat pendant 2 secondes (2000 millisecondes) pour informer lutilisateur des tches effectues par lapplication.
bool MainWindow::save()
{
if (curFile.isEmpty()) {
return saveAs();
} else {
return saveFile(curFile);
}
}
bool MainWindow::saveFile(const QString &fileName)
{
if (!spreadsheet->writeFile(fileName)) {
statusBar()->showMessage(tr("Saving canceled"), 2000);
return false;
}
setCurrentFile(fileName);
statusBar()->showMessage(tr("File saved"), 2000);
return true;
}

Le slot save() correspond File > Save. Si le fichier porte dj un nom puisque il a dj t
ouvert ou enregistr, save() appelle saveFile() avec ce nom ; sinon il invoque simplement
saveAs().
bool MainWindow::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (fileName.isEmpty())
return false;

Qt 4 Livre Page 61 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

61

return saveFile(fileName);
}

Le slot saveAs() correspond File > Save As. Nous appelons QFileDialog::getSaveFileName() pour que lutilisateur indique un nom de fichier. Si lutilisateur clique sur
Cancel, nous retournons false, qui est ensuite transmis son appelant (save() ou okToContinue()).
Si le fichier existe dj, la fonction getSaveFileName() demandera lutilisateur de
confirmer quil veut bien le remplacer. Ce comportement peut tre modifi en transmettant
QFileDialog::DontConfirmOverwrite comme argument supplmentaire getSaveFileName().
void MainWindow::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
writeSettings();
event->accept();
} else {
event->ignore();
}
}

Quand lutilisateur clique sur File > Exit ou sur le bouton de fermeture dans la barre de titre de
la fentre, le slot QWidget::close() est invoqu. Un vnement "close" est donc envoy au
widget. En implmentant nouveau QWidget::closeEvent(), nous avons la possibilit de
fermer la fentre principale et de dcider si nous voulons aussi fermer la fentre ou non.
Si certaines modifications nont pas t enregistres et si lutilisateur slectionne Cancel, nous
"ignorons" lvnement et la fentre nen sera pas affecte. En temps normal, nous acceptons
lvnement ; Qt masque donc la fentre. Nous invoquons galement la fonction prive
writeSettings() afin de sauvegarder les paramtres en cours de lapplication.
Quand la dernire fentre est ferme, lapplication se termine. Si ncessaire, nous pouvons
dsactiver ce comportement en configurant la proprit quitOnLastWindowClosed de
QApplication en false, auquel cas lapplication continue tre excute jusqu ce que
nous appelions QApplication::quit().
void MainWindow::setCurrentFile(const QString &fileName)
{
curFile = fileName;
setWindowModified(false);
QString shownName = "Untitled";
if (!curFile.isEmpty()) {
shownName = strippedName(curFile);
recentFiles.removeAll(curFile);
recentFiles.prepend(curFile);
updateRecentFileActions();
}

Qt 4 Livre Page 62 Jeudi, 7. dcembre 2006 12:14 12

62

Qt4 et C++ : Programmation dinterfaces GUI

setWindowTitle(tr("%1[*] - %2").arg(shownName)
.arg(tr("Spreadsheet")));
}
QString MainWindow::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}

Dans setCurrentFile(), nous dfinissons la variable prive curFile qui stocke le nom du
fichier en cours de modification. Avant dafficher le nom du fichier dans la barre de titre, nous
supprimons le chemin daccs du fichier avec strippedName() pour le rendre plus convivial.
Chaque QWidget possde une proprit windowModified qui doit tre dfinie en true si le
document prsente des modifications non sauvegardes et en false dans les autres cas. Sous
Mac OS X, les documents non sauvegards sont indiqus par un point dans le bouton de
fermeture de la barre de titre de la fentre ; sur les autres plates-formes, ils sont indiqus par un
astrisque aprs le nom de fichier. Qt se charge automatiquement de ce comportement, tant que
nous mettons jour la proprit windowModified et que nous plaons le marqueur "[*]" dans
le titre de la fentre lendroit o nous souhaitons voir apparatre lastrisque.
Le texte transmis la fonction setWindowTitle() tait le suivant :
tr("%1[*] - %2").arg(shownName)
.arg(tr("Spreadsheet"))

La fonction QString::arg() remplace le paramtre "%n" de numro le plus bas par son
argument et retourne la chane ainsi obtenue. Dans ce cas, arg() est utilis avec deux paramtres "%n". Le premier appel de arg() remplace "%1" ; le second appel remplace "%2".Si le
nom de fichier est "budget.sp" et quaucun fichier de traduction nest charg, la chane obtenue
serait "budget.sp[*] Spreadsheet".Il aurait t plus simple dcrire
setWindowTitle(shownName + tr("[*] - Spreadsheet"));

mais arg() offre une plus grande souplesse pour les traducteurs.
Sil existe un nom de fichier, nous mettons jour recentFiles, la liste des fichiers ouverts
rcemment. Nous invoquons removeAll() pour supprimer toutes les occurrences du nom de
fichier dans la liste afin dviter les copies ; puis nous appelons prepend() pour ajouter le
nom de fichier en tant que premier lment. Aprs la mise jour de la liste, nous appelons
la fonction prive updateRecentFileActions() de manire mettre jour les entres dans
le menu File.
void MainWindow::updateRecentFileActions()
{
QMutableStringListIterator i(recentFiles);
while (i.hasNext()) {
if (!QFile::exists(i.next()))
i.remove();

Qt 4 Livre Page 63 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

63

}
for (int j = 0; j < MaxRecentFiles; ++j) {
if (j < recentFiles.count()) {
QString text = tr("&%1%2")
.arg(j + 1)
.arg(strippedName(recentFiles[j]));
recentFileActions[j]->setText(text);
recentFileActions[j]->setData(recentFiles[j]);
recentFileActions[j]->setVisible(true);
} else {
recentFileActions[j]->setVisible(false);
}
}
separatorAction->setVisible(!recentFiles.isEmpty());
}

Nous commenons par supprimer tout fichier qui nexiste plus laide dun itrateur de style
Java. Certains fichiers peuvent avoir t utiliss dans une session antrieure, mais ont t
supprims depuis. La variable recentFiles est de type QStringList (liste de QString). Le
Chapitre 11 tudie en dtail les classes conteneur comme QStringList vous expliquant
comment elles sont lies la bibliothque C++ STL (Standard Template Library), et vous
explique comment employer les classes ditrateurs de style Java dans Qt.
Nous parcourons ensuite nouveau la liste des fichiers, mais cette fois-ci en utilisant une
indexation de style tableau. Pour chaque fichier, nous crons une chane compose dun caractre &, dun chiffre (j + 1), dun espace et du nom de fichier (sans son chemin daccs). Nous
dfinissons laction correspondante pour quelle utilise ce texte. Par exemple, si le premier
fichier tait C:\My Documents\tab04.sp, le texte de la premire action serait "&1 tab04.sp".
Figure 3.11
Le menu File
avec les fichiers
ouverts rcemment
separatorAction
recentFileActions[0]
recentFileActions[1]
recentFileActions[2]
recentFileActions[3]
recentFileActions[4]

Chaque action peut avoir un lment "donne" associ de type QVariant. Le type QVariant
peut contenir des valeurs de plusieurs types C++ et Qt ; cest expliqu au Chapitre 11. Ici, nous

Qt 4 Livre Page 64 Jeudi, 7. dcembre 2006 12:14 12

64

Qt4 et C++ : Programmation dinterfaces GUI

stockons le nom complet du fichier dans llment "donne" de laction, pour pouvoir le rcuprer
facilement par la suite. Nous configurons galement laction de sorte quelle soit visible.
Sil y a plus dactions de fichiers que de fichiers rcents, nous masquons simplement les actions
supplmentaires. Enfin, sil y a au moins un fichier rcent, nous dfinissons le sparateur pour
quil saffiche.
void MainWindow::openRecentFile()
{
if (okToContinue()) {
QAction *action = qobject_cast<QAction *>(sender());
if (action)
loadFile(action->data().toString());
}
}

Quand lutilisateur slectionne un fichier rcent, le slot openRecentFile() est appel. La fonction okToContinue() est excute si des changements nont pas t sauvegards, et si lutilisateur
nannule pas, nous identifions quelle action a appel le slot grce QObject::sender().
La fonction qobject_cast<T>() accomplit une conversion dynamique base sur les mtainformations gnres par moc, le compilateur des mta-objets de Qt. Elle retourne un pointeur
vers la sous-classe QObject demande, ou 0 si lobjet na pas pu tre converti dans ce type.
Contrairement dynamic_cast<T>() du langage C++ standard, qobject_cast<T>() de Qt
fonctionne correctement dans les bibliothques dynamiques. Dans notre exemple, nous utilisons qobject_cast<T>() pour convertir un pointeur QObject en une action QAction. Si la
conversion a t effectue avec succs (ce devrait tre le cas), nous appelons loadFile()
avec le nom complet du fichier que nous extrayons des donnes de laction.
Notez qutant donn que nous savons que lexpditeur est de type QAction, le programme
fonctionnerait toujours si nous avions utilis static_cast<T>() ou une conversion traditionnelle de style C. Consultez la section "Conversions de type" en Annexe B pour connatre les
diverses conversions C++.

Utiliser des botes de dialogue


Dans cette section, nous allons vous expliquer comment utiliser des botes de dialogue dans
Qt comment les crer et les initialiser, les excuter et rpondre aux slections effectues par
lutilisateur interagissant avec elles. Nous emploierons les botes de dialogue Find, Go-to-Cell
et Sort cres au Chapitre 2. Nous crerons aussi une bote simple About.
Nous commenons par la bote de dialogue Find. Nous voulons que lutilisateur puisse basculer
volont entre la fentre principale Spreadsheet et la bote de dialogue Find, cette dernire doit
donc tre non modale. Une fentre non modale est excute indpendamment de toute autre
fentre dans lapplication.

Qt 4 Livre Page 65 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

65

Figure 3.12
La bote de dialogue Find
de lapplication
Spreadsheet

Lorsque des botes de dialogue non modales sont cres, leurs signaux sont normalement
connects aux slots qui rpondent aux interactions de lutilisateur.
void MainWindow::find()
{
if (!findDialog) {
findDialog = new FindDialog(this);
connect(findDialog, SIGNAL(findNext(const QString &,
Qt::CaseSensitivity)),
spreadsheet, SLOT(findNext(const QString &,
Qt::CaseSensitivity)));
connect(findDialog, SIGNAL(findPrevious(const QString &,
Qt::CaseSensitivity)),
spreadsheet, SLOT(findPrevious(const QString &,
Qt::CaseSensitivity)));
}
findDialog->show();
findDialog->activateWindow();
}

La bote de dialogue Find est une fentre qui permet lutilisateur de rechercher du texte dans
le tableur. Le slot find() est appel lorsque lutilisateur clique sur Edit > Find pour ouvrir la
bote de dialogue Find. A ce stade, plusieurs scnarios sont possibles :
Cest la premire fois que lutilisateur appelle la bote de dialogue Find.
La bote de dialogue Find a dj t appele auparavant, mais lutilisateur la ferme.
La bote de dialogue Find a dj t appele auparavant et est toujours affiche.
Si la bote de dialogue Find nexiste pas encore, nous la crons et nous connectons ses signaux
findNext() et findPrevious() aux slots Spreadsheet correspondants. Nous aurions aussi
pu crer la bote de dialogue dans le constructeur de MainWindow, mais ajourner sa cration
rend le dmarrage plus rapide. De mme, si la bote de dialogue nest jamais utilise, elle nest
jamais cre, ce qui vous fait gagner du temps et de la mmoire.
Nous invoquons ensuite show() et activateWindow() pour nous assurer que la fentre est
visible et active. Un seul appel de show() est suffisant pour afficher et activer une fentre
masque, mais la bote de dialogue Find peut tre appele quand sa fentre est dj visible,
auquel cas show() ne fait rien et activateWindow() est ncessaire pour activer la fentre.
Vous auriez aussi pu crire
if (findDialog->isHidden()) {

Qt 4 Livre Page 66 Jeudi, 7. dcembre 2006 12:14 12

66

Qt4 et C++ : Programmation dinterfaces GUI

findDialog->show();
} else {
findDialog->activateWindow();
}

Ce code revient regarder des deux cts dune route sens unique avant de traverser.
Nous allons prsent analyser la bote de dialogue Go-to-Cell. Nous voulons que lutilisateur
louvre, lutilise, puis la ferme sans pouvoir basculer vers dautres fentres dans lapplication.
Cela signifie que la bote de dialogue Go-to-Cell doit tre modale. Une fentre modale est une
fentre qui saffiche quand elle est appele et bloque lapplication. Tout autre traitement ou
interaction est impossible tant que la fentre nest pas ferme. Les botes de dialogue douverture de fichier et les botes de message utilises prcdemment taient modales.
Figure 3.13
La bote de dialogue
Go-to-Cell de lapplication Spreadsheet

Une bote de dialogue nest pas modale si elle est appele laide de show() ( moins que
nous appelions setModal() au pralable pour la rendre modale) ; elle est modale si elle est
invoque avec exec().
void MainWindow::goToCell()
{
GoToCellDialog dialog(this);
if (dialog.exec()) {
QString str = dialog.lineEdit->text().toUpper();
spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,
str[0].unicode() - A);
}
}

La fonction QDialog::exec() retourne une valeur true (QDialog::Accepted) si la bote


de dialogue est accepte, et une valeur false (QDialog::Rejected) dans les autres cas.
Souvenez-vous que lorsque nous avons cr la bote de dialogue Go-to-Cell avec le Qt Designer
au Chapitre 2, nous avons connect OK accept() et Cancel reject(). Si lutilisateur clique
sur OK, nous dfinissons la cellule actuelle avec la valeur prsente dans lditeur de lignes.
La fonction QTableWidget::setCurrentCell() reoit deux arguments : un index des
lignes et un index des colonnes. Dans lapplication Spreadsheet, la cellule A1 correspond la
cellule (0, 0) et la cellule B27 la cellule (26, 1). Pour obtenir lindex des lignes du QString
retourn par QLineEdit::text(), nous devons extraire le nombre de lignes avec
QString::mid() (qui retourne une sous-chane allant du dbut la fin de la chane), la
convertir en type int avec QString::toInt() et soustraire 1. Pour le nombre de colonnes,
nous soustrayons la valeur numrique de A de la valeur numrique du premier caractre en

Qt 4 Livre Page 67 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

67

majuscule de la chane. Nous savons que la chane aura le bon format parce que QRegExpValidator cr pour la bote de dialogue nautorise lactivation du bouton OK que sil y a une
lettre suivie par 3 chiffres maximum.
La fonction goToCell() diffre de tout le code tudi jusqu prsent, puisquelle cre un
widget (GoToCellDialog) sous la forme dune variable sur la pile. En ajoutant une ligne,
nous aurions pu utiliser tout aussi facilement new et delete:
void MainWindow::goToCell()
{
GoToCellDialog *dialog = new GoToCellDialog(this);
if (dialog->exec()) {
QString str = dialog->lineEdit->text().toUpper();
spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,
str[0].unicode() - A);
}
delete dialog;
}

La cration de botes de dialogue modales (et de menus contextuels dans des rimplmentations QWidget::contextMenuEvent()) sur la pile est un modle de programmation courant,
parce quen rgle gnrale, nous navons plus besoin de la bote de dialogue (ou du menu)
aprs lavoir utilise, et elle sera automatiquement dtruite la fin de la porte dans laquelle
elle volue.
Examinons maintenant la bote de dialogue Sort. Celle-ci est une bote de dialogue modale qui
permet lutilisateur de trier la zone slectionne sur les colonnes quil spcifie. La Figure 3.14
montre un exemple de tri, avec la colonne B comme cl de tri primaire et la colonne A comme
cl de tri secondaire (toutes les deux par ordre croissant).
Figure 3.14
Trier la zone
slectionne
du tableur

(b) Avant le tri

(b) Aprs le tri

void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange(A + range.leftColumn(),
A + range.rightColumn());
if (dialog.exec()) {

Qt 4 Livre Page 68 Jeudi, 7. dcembre 2006 12:14 12

68

Qt4 et C++ : Programmation dinterfaces GUI

SpreadsheetCompare compare;
compare.keys[0] =
dialog.primaryColumnCombo->currentIndex();
compare.keys[1] =
dialog.secondaryColumnCombo->currentIndex() - 1;
compare.keys[2] =
dialog.tertiaryColumnCombo->currentIndex() - 1;
compare.ascending[0] =
(dialog.primaryOrderCombo->currentIndex() == 0);
compare.ascending[1] =
(dialog.secondaryOrderCombo->currentIndex() == 0);
compare.ascending[2] =
(dialog.tertiaryOrderCombo->currentIndex() == 0);
spreadsheet->sort(compare);
}
}

Le code dans sort() suit un modle similaire celui utilis pour goToCell():

Nous crons la bote de dialogue sur la pile et nous linitialisons.

Nous ouvrons la bote de dialogue avec exec().

Si lutilisateur clique sur OK, nous extrayons les valeurs saisies par ce dernier partir des
widgets de la bote de dialogue et nous les utilisons.

Lappel de setColumnRange() dfinit les colonnes disponibles pour le tri sur les colonnes
slectionnes. Par exemple, en utilisant la slection illustre en Figure 3.14, range.leftColumn() produirait 0, ce qui fait A + 0 = A, et range.rightColumn() produirait 2, ce
qui fait A + 2 = C.
Lobjet compare stocke les cls de tri primaire, secondaire et tertiaire, ainsi que leurs ordres de
tri. (Nous verrons la dfinition de la classe SpreadsheetCompare dans le prochain chapitre.)
Lobjet est employ par Spreadsheet::sort() pour comparer deux lignes. Le tableau keys
stocke les numros de colonne des cls. Par exemple, si la slection stend de C2 E5, la
colonne C correspond la position 0. Le tableau ascending conserve lordre associ chaque
cl comme une valeur bool. QComboBox::currentIndex() retourne lindex de llment
slectionn, en commenant 0. Concernant les cls secondaire et tertiaire, nous soustrayons
un de llment en cours pour prendre en compte llment "None (Aucun)".
La fonction sort() rpond la demande, mais elle manque de fiabilit. Elle suppose que la
bote de dialogue Sort est implmente de manire particulire, avec des zones de liste droulante et des lments "None". Cela signifie que si nous concevons nouveau la bote de dialogue Sort, nous devrions galement rcrire ce code. Alors que cette approche convient pour
une bote de dialogue qui est toujours appele depuis le mme emplacement, elle conduit un
vritable cauchemar pour la maintenance si elle est employe plusieurs endroits.
Une mthode plus fiable consiste rendre la classe SortDialog plus intelligente en la faisant
crer un objet SpreadsheetCompare auquel son appelant peut ensuite accder. Cela simplifie
significativement MainWindow::sort():

Qt 4 Livre Page 69 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

69

void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange(A + range.leftColumn(),
A + range.rightColumn());
if (dialog.exec())
spreadsheet->performSort(dialog.comparisonObject());
}

Cette approche conduit des composants relativement indpendants et constitue presque


toujours le bon choix pour des botes de dialogue qui seront invoques depuis plusieurs emplacements.
Une technique plus radicale serait de transmettre un pointeur lobjet Spreadsheet au
moment de linitialisation de lobjet SortDialog et de permettre la bote de dialogue
doprer directement sur Spreadsheet. SortDialog devient donc moins gnral, parce quil
ne fonctionnera que dans certains types de widgets, mais cela simplifie davantage le code en
liminant la fonction SortDialog::setColumnRange(). La fonction MainWindow::sort() devient donc
void MainWindow::sort()
{
SortDialog dialog(this);
dialog.setSpreadsheet(spreadsheet);
dialog.exec();
}

Cette approche reproduit la premire : lappelant na pas besoin de connatre la bote de dialogue
dans les moindres dtails, mais cest la bote de dialogue qui doit totalement connatre les structures
de donnes fournies par lappelant. Cette technique peut tre pratique quand la bote de dialogue
doit appliquer des changements en direct. Mais comme le code dappel peu fiable de la premire
approche, cette troisime mthode ne fonctionne plus si les structures de donnes changent.
Certains dveloppeurs choisissent une approche quant lutilisation des botes de dialogue et
nen changent plus. Cela prsente lavantage de favoriser la familiarit et la simplicit, parce
que toutes leurs botes de dialogue respectent le mme schma, mais ils passent ct des
bnfices apports par les autres approches. La meilleure approche consiste choisir la
mthode au cas par cas.
Nous allons clore cette section avec la bote de dialogue About. Nous pourrions crer une bote
de dialogue personnalise comme pour les botes de dialogue Find ou Go-to-Cell pour prsenter les informations relatives lapplication, mais vu que la plupart des botes About adoptent
le mme style, Qt propose une solution plus simple.
void MainWindow::about()
{
QMessageBox::about(this, tr("About Spreadsheet"),
tr("<h2>Spreadsheet 1.1</h2>"

Qt 4 Livre Page 70 Jeudi, 7. dcembre 2006 12:14 12

70

Qt4 et C++ : Programmation dinterfaces GUI

"<p>Copyright &copy; 2006 Software Inc."


"<p>Spreadsheet is a small application that "
"demonstrates QAction, QMainWindow, QMenuBar, "
"QStatusBar, QTableWidget, QToolBar, and many other "
"Qt classes."));
}

Vous obtenez la bote About en appelant tout simplement la fonction statique QMessageBox::about(). Cette fonction ressemble beaucoup QMessageBox::warning(), sauf quelle
emploie licne de la fentre parent au lieu de licne standard davertissement.
Figure 3.15
La bote About
de Spreadsheet

Jusqu prsent, nous avons utilis plusieurs fonctions statiques commodes dans QMessageBox et QFileDialog. Ces fonctions crent une bote de dialogue, linitialisent et appellent
exec(). Il est galement possible, mme si cest moins pratique, de crer un widget QMessageBox ou QFileDialog comme nimporte quel autre widget et dappeler explicitement
exec() ou mme show().

Stocker des paramtres


Dans le constructeur MainWindow, nous avons invoqu readSettings() afin de charger les
paramtres stocks de lapplication. De mme, dans closeEvent(), nous avons appel
writeSettings() pour sauvegarder les paramtres. Ces deux fonctions sont les dernires
fonctions membres MainWindow qui doivent tre implmentes.
void MainWindow::writeSettings()
{
QSettings settings("Software Inc.", "Spreadsheet");
settings.setValue("geometry", geometry());
settings.setValue("recentFiles", recentFiles);
settings.setValue("showGrid", showGridAction->isChecked());
settings.setValue("autoRecalc", autoRecalcAction->isChecked());
}

Qt 4 Livre Page 71 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

71

La fonction writeSettings() enregistre la disposition (position et taille) de la fentre principale, la liste des fichiers ouverts rcemment et les options Show Grid et Auto-Recalculate.
Par dfaut, QSettings stocke les paramtres de lapplication des emplacements spcifiques
la plate-forme. Sous Windows, il utilise le registre du systme ; sous Unix, il stocke les
donnes dans des fichiers texte ; sous Mac OS X, il emploie lAPI des prfrences de Core
Foundation.
Les arguments du constructeur spcifient les noms de lorganisation et de lapplication. Ces
informations sont exploites dune faon spcifique la plate-forme pour trouver un emplacement
aux paramtres.

QSettings stocke les paramtres sous forme de paires cl-valeur. La cl est similaire au
chemin daccs du systme de fichiers. Des sous-cls peuvent tre spcifies grce une
syntaxe de style chemin daccs (par exemple, findDialog/matchCase) ou beginGroup() et
endGroup():
settings.beginGroup("findDialog");
settings.setValue("matchCase", caseCheckBox->isChecked());
settings.setValue("searchBackward", backwardCheckBox->isChecked());
settings.endGroup();

La valeur peut tre de type int, bool, double, QString, QStringList, ou de nimporte quel
autre type pris en charge par QVariant, y compris des types personnaliss enregistrs.
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Spreadsheet");
QRect rect = settings.value("geometry",
QRect(200, 200, 400, 400)).toRect();
move(rect.topLeft());
resize(rect.size());
recentFiles = settings.value("recentFiles").toStringList();
updateRecentFileActions();
bool showGrid = settings.value("showGrid", true).toBool();
showGridAction->setChecked(showGrid);
bool autoRecalc = settings.value("autoRecalc", true).toBool();
autoRecalcAction->setChecked(autoRecalc);
}

La fonction readSettings() charge les paramtres qui taient sauvegards par writeSettings(). Le deuxime argument de la fonction value() indique une valeur par dfaut, dans
le cas o aucun paramtre nest disponible. Les valeurs par dfaut sont utilises la premire
fois que lapplication est excute. Etant donn quaucun second argument nest indiqu pour
la liste des fichiers rcents, il sera dfini en liste vide la premire excution.

Qt 4 Livre Page 72 Jeudi, 7. dcembre 2006 12:14 12

72

Qt4 et C++ : Programmation dinterfaces GUI

Qt propose une fonction QWidget::setGeometry() pour complter QWidget::geometry(), mais elle ne fonctionne pas toujours comme prvu sous X11 en raison des limites de
la plupart des gestionnaires de fentre. Cest pour cette raison que nous utilisons plutt move()
et resize(). (Voir http://doc.trolltech.com/4.1/geometry.html pour une explication plus
dtaille.)
Nous avons opt pour une organisation dans MainWindow parmi de nombreuses approches
possibles, avec tout le code associ QSettings dans readSettings() et writeSettings(). Un objet QSettings peut tre cr pour identifier ou modifier un paramtre pendant
lexcution de lapplication et nimporte o dans le code.
Nous avons dsormais implment MainWindow dans Spreadsheet. Dans les sections suivantes, nous verrons comment lapplication Spreadsheet peut tre modifie de manire grer
plusieurs documents, et comment implmenter une page daccueil. Nous complterons ses
fonctionnalits, notamment avec la gestion des formules et le tri, dans le prochain chapitre.

Documents multiples
Nous sommes dsormais prts coder la fonction main() de lapplication Spreadsheet :
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
return app.exec();
}

Cette fonction main() est lgrement diffrente de celles que nous avons crites jusque l :
nous avons cr linstance MainWindow comme une variable sur la pile au lieu dutiliser new.
Linstance MainWindow est ensuite automatiquement dtruite quand la fonction se termine.
Avec la fonction main() prsente ci-dessus, lapplication Spreadsheet propose une seule
fentre principale et ne peut grer quun document la fois. Si vous voulez modifier plusieurs
documents en mme temps, vous pourriez dmarrer plusieurs instances de lapplication
Spreadsheet. Mais ce nest pas aussi pratique pour les utilisateurs que davoir une seule
instance de lapplication proposant plusieurs fentres principales, tout comme une instance
dun navigateur Web peut fournir plusieurs fentres de navigateur simultanment.
Nous modifierons lapplication Spreadsheet de sorte quelle puisse grer plusieurs documents.
Nous avons tout dabord besoin dun menu File lgrement diffrent :
File > New cre une nouvelle fentre principale avec un document vide, au lieu de rutiliser
la fentre principale existante.

Qt 4 Livre Page 73 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

73

File > Close ferme la fentre principale active.


File > Exit ferme toutes les fentres.
Dans la version originale du menu File, il ny avait pas doption Close parce quelle aurait eu
la mme fonction quExit.

Figure 3.16
Le nouveau menu File

Voici la nouvelle fonction main():


int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow *mainWin = new MainWindow;
mainWin->show();
return app.exec();
}

Avec plusieurs fentres, il est maintenant intressant de crer MainWindow avec new, puisque
nous avons ensuite la possibilit dexcuter delete sur une fentre principale quand nous
avons fini afin de librer la mmoire.
Voici le nouveau slot MainWindow::newFile():
void MainWindow::newFile()
{
MainWindow *mainWin = new MainWindow;
mainWin->show();
}

Nous crons simplement une nouvelle instance de MainWindow. Cela peut sembler stupide de
ne pas conserver un pointeur vers la nouvelle fentre, mais ce nest pas un problme tant
donn que Qt assure le suivi de toutes les fentres pour nous.
Voici les actions pour Close et Exit :
void MainWindow::createActions()
{
...
closeAction = new QAction(tr("&Close"), this);
closeAction->setShortcut(tr("Ctrl+W"));
closeAction->setStatusTip(tr("Close this window"));

Qt 4 Livre Page 74 Jeudi, 7. dcembre 2006 12:14 12

74

Qt4 et C++ : Programmation dinterfaces GUI

connect(closeAction, SIGNAL(triggered()), this, SLOT(close()));


exitAction = new QAction(tr("E&xit"), this);
exitAction->setShortcut(tr("Ctrl+Q"));
exitAction->setStatusTip(tr("Exit the application"));
connect(exitAction, SIGNAL(triggered()),
qApp, SLOT(closeAllWindows()));
...
}

Le slot QApplication::closeAllWindows() ferme toutes les fentres de lapplication,


moins quune delles ne refuse lvnement close. Cest exactement le comportement dont
nous avons besoin ici. Nous navons pas nous soucier des modifications non sauvegardes
parce que MainWindow::closeEvent() sen charge ds quune fentre est ferme.
Il semble que notre application est maintenant capable de grer plusieurs fentres. Malheureusement, il reste un problme masqu : si lutilisateur continue crer et fermer des fentres
principales, la machine pourrait ventuellement manquer de mmoire. Cest parce que nous
continuons crer des widgets MainWindow dans newFile(), sans jamais les effacer. Quand
lutilisateur ferme une fentre principale, le comportement par dfaut consiste la masquer,
elle reste donc en mmoire. Vous risquez donc de rencontrer des problmes si le nombre de
fentres principales est important.
La solution est de dfinir lattribut Qt::WA_DeleteOnClose dans le constructeur :
MainWindow::MainWindow()
{
...
setAttribute(Qt::WA_DeleteOnClose);
...
}

Il ordonne Qt de supprimer la fentre lorsquelle est ferme. Lattribut Qt::WA_DeleteOnClose est lun des nombreux indicateurs qui peuvent tre dfinis sur un QWidget pour
influencer son comportement.
La fuite de mmoire nest pas le seul problme rencontr. La conception de notre application
dorigine supposait que nous aurions une seule fentre principale. Dans le cas de plusieurs
fentres, chaque fentre principale possde sa propre liste de fichiers ouverts rcemment et ses
propres options. Il est vident que la liste des fichiers ouverts rcemment doit tre globale
toute lapplication. En fait, il suffit de dclarer la variable recentFiles comme statique pour
quune seule instance soit gre par lapplication. Mais nous devons ensuite garantir que tous
les appels de updateRecentFileActions() destins mettre jour le menu File concernent
bien toutes les fentres principales. Voici le code pour obtenir ce rsultat :
foreach (QWidget *win, QApplication::topLevelWidgets()) {
if (MainWindow *mainWin = qobject_cast<MainWindow *>(win))
mainWin->updateRecentFileActions();
}

Qt 4 Livre Page 75 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 3

Crer des fentres principales

75

Ce code sappuie sur la construction foreach de Qt (explique au Chapitre 11) pour parcourir
toutes les fentres de lapplication et appelle updateRecentFileActions() sur tous les
widgets de type MainWindow. Un code similaire peut tre employ pour synchroniser les options
Show Grid et Auto-Recalculate ou pour sassurer que le mme fichier nest pas charg
deux fois.
Les applications qui proposent un document par fentre principale sont appeles des applications SDI (single document interface). Il existe une alternative courante sous Windows : MDI
(multiple document interface), o lapplication comporte une seule fentre principale qui gre
plusieurs fentres de document dans sa zone daffichage centrale. Qt peut tre utilis pour crer
des applications SDI et MDI sur toutes les plates-formes prises en charge. La Figure 3.17 montre
les deux versions de lapplication Spreadsheet. MDI est abord au Chapitre 6.
Figure 3.17
SDI versus MDI

Pages daccueil
De nombreuses applications affichent une page daccueil au dmarrage. Certains dveloppeurs
se servent de cette page pour dissimuler un dmarrage lent, alors que dautres lexploitent pour
leurs services marketing. La classe QSplashScreen facilite lajout dune page daccueil aux
applications Qt.
Cette classe affiche une image avant lapparition de la fentre principale. Elle peut aussi crire
des messages sur limage pour informer lutilisateur de la progression du processus dinitialisation de lapplication. En gnral, le code de la page daccueil se situe dans main(), avant
lappel de QApplication::exec().
Le code suivant est un exemple de fonction main() qui utilise QSplashScreen pour prsenter
une page daccueil dans une application qui charge des modules et tablit des connexions
rseau au dmarrage.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

Qt 4 Livre Page 76 Jeudi, 7. dcembre 2006 12:14 12

76

Qt4 et C++ : Programmation dinterfaces GUI

QSplashScreen *splash = new QSplashScreen;


splash->setPixmap(QPixmap(":/images/splash.png"));
splash->show();
Qt::Alignment topRight = Qt::AlignRight | Qt::AlignTop;
splash->showMessage(QObject::tr("Setting up the main window..."),
topRight, Qt::white);
MainWindow mainWin;
splash->showMessage(QObject::tr("Loading modules..."),
topRight, Qt::white);
loadModules();
splash->showMessage(QObject::tr("Establishing connections..."),
topRight, Qt::white);
establishConnections();
mainWin.show();
splash->finish(&mainWin);
delete splash;
return app.exec();
}

Figure 3.18
Une page daccueil

Nous avons dsormais termin ltude de linterface utilisateur de lapplication Spreadsheet.


Dans le prochain chapitre, vous complterez lapplication en implmentant la fonctionnalit
principale du tableur.

Qt 4 Livre Page 77 Jeudi, 7. dcembre 2006 12:14 12

4
Implmenter
la fonctionnalit dapplication
Au sommaire de ce chapitre
Le widget central
Drivation de QTable
Widget
Chargement et sauvegarde
Implmenter le menu Edit
Implmenter les autres menus
Drivation de QTableWidgetItem

Dans les deux prcdents chapitres, nous vous avons expliqu comment crer linterface
utilisateur de lapplication Spreadsheet. Dans ce chapitre, nous terminerons le
programme en codant sa fonctionnalit sous-jacente. Nous verrons entre autres
comment charger et sauvegarder des fichiers, stocker des donnes en mmoire, implmenter des oprations du presse-papiers et ajouter une prise en charge des formules de
la feuille de calcul QTableWidget.

Qt 4 Livre Page 78 Jeudi, 7. dcembre 2006 12:14 12

78

Qt4 et C++ : Programmation dinterfaces GUI

Le widget central
La zone centrale dun QMainWindow peut tre occupe par nimporte quel type de widget.
Voici quelques possibilits :
1. Utiliser un widget Qt standard.
Un widget standard comme QTableWidget ou QTextEdit peut tre employ comme
widget central. Dans ce cas, la fonctionnalit de lapplication, telle que le chargement et la
sauvegarde des fichiers, doit tre implmente quelque part (par exemple dans une sousclasse QMainWindow).
2. Utiliser un widget personnalis.
Des applications spcialises ont souvent besoin dafficher des donnes dans un widget
personnalis. Par exemple, un programme dditeur dicnes aurait un widget IconEditor
comme widget central. Le Chapitre 5 vous explique comment crire des widgets personnaliss dans Qt.
3. Utiliser un QWidget ordinaire avec un gestionnaire de disposition.
Il peut arriver que la zone centrale de lapplication soit occupe par plusieurs widgets.
Cest possible grce lutilisation dun QWidget comme parent de tous les autres widgets
et de gestionnaires de disposition pour dimensionner et positionner les widgets enfants.
4. Utiliser un sparateur.
Il existe un autre moyen dutiliser plusieurs widgets ensembles : un QSplitter. QSplitter dispose ses widgets enfants horizontalement ou verticalement et le sparateur offre la
possibilit lutilisateur dagir sur cette disposition. Les sparateurs peuvent contenir tout
type de widgets, y compris dautres sparateurs.
5. Utiliser un espace de travail MDI.
Si lapplication utilise MDI, la zone centrale est occupe par un widget QWorkspace et
chaque fentre MDI est un enfant de ce widget.
Les dispositions, les sparateurs et les espaces de travail MDI peuvent tre combins des
widgets Qt standards ou personnaliss. Le Chapitre 6 traite de ces classes trs en dtail.
Concernant lapplication Spreadsheet, une sous-classe QTableWidget sert de widget central.
La classe QTableWidget propose dj certaines fonctionnalits de feuille de calcul dont nous
avons besoin, mais elle ne prend pas en charge les oprations du presse-papiers et ne comprend
pas les formules comme "=A1+A2+A3".Nous implmenterons cette fonction manquante dans
la classe Spreadsheet.

Qt 4 Livre Page 79 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

79

Drivation de QTableWidget
La classe Spreadsheet hrite de QTableWidget. Un QTableWidget est une grille qui reprsente un tableau en deux dimensions. Il affiche nimporte quelle cellule que lutilisateur fait
dfiler, dans ses dimensions spcifies. Quand lutilisateur saisit du texte dans une cellule vide,
QTableWidget cre automatiquement un QTableWidgetItem pour stocker le texte.
Implmentons Spreadsheet en commenant par le fichier den-tte :
#ifndef SPREADSHEET_H
#define SPREADSHEET_H
#include <QTableWidget>
class Cell;
class SpreadsheetCompare;

Len-tte commence par les dclarations pralables des classes Cell et SpreadsheetCompare.
Figure 4.1
Arbres dhritage pour
Spreadsheet et Cell

QObject
QWidget
QTableWidget

QTableWidgetItem

Spreadsheet

Cell

Les attributs dune cellule QTableWidget, tels que son texte et son alignement, sont conservs
dans un QTableWidgetItem. Contrairement QTableWidget, QTableWidgetItem nest pas
une classe de widget ; cest une classe de donnes. La classe Cell hrite de QTableWidgetItem. Elle est dtaille pendant la prsentation de son implmentation dans la dernire section
de ce chapitre.
class Spreadsheet: public QTableWidget
{
Q_OBJECT
public:
Spreadsheet(QWidget *parent = 0);
bool autoRecalculate() const { return autoRecalc; }
QString currentLocation() const;
QString currentFormula() const;
QTableWidgetSelectionRange selectedRange() const;
void clear();
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
void sort(const SpreadsheetCompare &compare);

Qt 4 Livre Page 80 Jeudi, 7. dcembre 2006 12:14 12

80

Qt4 et C++ : Programmation dinterfaces GUI

La fonction autoRecalculate() est implmente en mode inline (en ligne) parce quelle
indique simplement si le recalcul automatique est activ ou non.
Dans le Chapitre 3, nous nous sommes bass sur des fonctions publiques dans Spreadsheet lorsque nous avons implment MainWindow. Par exemple, nous avons appel clear() depuis MainWindow::newFile() pour rinitialiser la feuille de calcul. Nous avons aussi utilis certaines
fonctions hrites de QTableWidget, notamment setCurrentCell() et setShowGrid().
public slots:
void cut();
void copy();
void paste();
void del();
void selectCurrentRow();
void selectCurrentColumn();
void recalculate();
void setAutoRecalculate(bool recalc);
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);
signals:
void modified();

Spreadsheet propose plusieurs slots implmentant des actions depuis les menus Edit, Tools
et Options, de mme quun signal, modified(), pour annoncer tout changement.
private slots:
void somethingChanged();

Nous dfinissons un slot priv exploit en interne par la classe Spreadsheet.


private:
enum { MagicNumber = 0x7F51C883, RowCount = 999, ColumnCount = 26 };
Cell *cell(int row, int column) const;
QString text(int row, int column) const;
QString formula(int row, int column) const;
void setFormula(int row, int column, const QString &formula);
bool autoRecalc;
};

Dans la section prive de la classe, nous dclarons trois constantes, quatre fonctions et une
variable.
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];

Qt 4 Livre Page 81 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

81

bool ascending[KeyCount];
};
#endif

Le fichier den-tte se termine par la dfinition de la classe SpreadsheetCompare. Nous


reviendrons sur ce point lorsque nous tudierons Spreadsheet::sort().
Nous allons dsormais passer en revue limplmentation :
#include <QtGui>
#include "cell.h"
#include "spreadsheet.h"
Spreadsheet::Spreadsheet(QWidget *parent)
: QTableWidget(parent)
{
autoRecalc = true;
setItemPrototype(new Cell);
setSelectionMode(ContiguousSelection);
connect(this, SIGNAL(itemChanged(QTableWidgetItem *)),
this, SLOT(somethingChanged()));
clear();
}

Normalement, quand lutilisateur saisit du texte dans une cellule vide, QTableWidget cre
automatiquement un QTableWidgetItem pour contenir le texte. Dans notre feuille de calcul,
nous voulons plutt crer des lments Cell. Pour y parvenir, nous appelons setItemPrototype() dans le constructeur. En interne, QTableWidget copie llment transmis comme un
prototype chaque fois quun nouvel lment est requis.
Toujours dans le constructeur, nous dfinissons le mode de slection en QAbstractItemView::ContiguousSelection pour autoriser une seule slection rectangulaire. Nous
connectons le signal itemChanged() du widget de la table au slot priv somethingChanged(); lorsque lutilisateur modifie une cellule, nous sommes donc srs que le slot somethingChanged() est appel. Enfin, nous invoquons clear() pour redimensionner la table et
configurer les en-ttes de colonne.
void Spreadsheet::clear()
{
setRowCount(0);
setColumnCount(0);
setRowCount(RowCount);
setColumnCount(ColumnCount);
for (int i = 0; i < ColumnCount; ++i) {
QTableWidgetItem *item = new QTableWidgetItem;
item->setText(QString(QChar(A + i)));

Qt 4 Livre Page 82 Jeudi, 7. dcembre 2006 12:14 12

82

Qt4 et C++ : Programmation dinterfaces GUI

setHorizontalHeaderItem(i, item);
}
setCurrentCell(0, 0);
}

La fonction clear() est appele depuis le constructeur Spreadsheet pour initialiser la


feuille de calcul. Elle est aussi invoque partir de MainWindow::newFile().
Nous aurions pu utiliser QTableWidget::clear() pour effacer tous les lments et toutes les
slections, mais les en-ttes auraient conserv leurs tailles actuelles. Au lieu de cela, nous redimensionnons la table en 0 0. Toute la feuille de calcul est donc efface, y compris les enttes. Nous redimensionnons ensuite la table en ColumnCount _ RowCount (26 _ 999) et nous
alimentons len-tte horizontal avec des QTableWidgetItem qui contiennent les noms de
colonne "A", "B", , "Z".Nous ne sommes pas obligs de dfinir les intituls des en-ttes
verticaux, parce quils prsentent par dfaut les valeurs suivantes : "1", "2",..., "999".
Pour terminer, nous plaons le curseur au niveau de la cellule A1.

viewport()

verticalScrollBar()

horizontalHeader()
verticalHeader()

Figure 4.2
Les widgets
qui constituent
QTableWidget

horizontalScrollBar()

Un QTableWidget se compose de plusieurs widgets enfants (voir Figure 4.2). Un QHeaderView horizontal est positionn en haut, un QHeaderView vertical gauche et deux QScrollBar terminent cette composition. La zone au centre est occupe par un widget spcial appel
viewport, sur lequel notre QTableWidget dessine les cellules. Les divers widgets enfants
sont accessibles par le biais de fonctions hrites de QTableView et QAbstractScrollArea
(voir Figure 4.2). QAbstractScrollArea fournit un viewport quip de deux barres de dfilement, que vous pouvez activer ou dsactiver. Sa sous-classe QScrollArea est aborde au
Chapitre 6.

Stocker des donnes en tant qulments


Dans lapplication Spreadsheet, chaque cellule non vide est stocke en mmoire sous forme
dobjet QTableWidgetItem individuel. Stocker des donnes en tant qulments est une approche galement utilise par QListWidget et QTreeWidget, qui agissent sur QListWidgetItem et
QTreeWidgetItem.

Qt 4 Livre Page 83 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

83

Les classes dlments de Qt peuvent tre directement employes comme des conteneurs de
donnes. Par exemple, un QTableWidgetItem stocke par dfinition quelques attributs, y compris
une chane, une police, une couleur et une icne, ainsi quun pointeur vers QTableWidget.
Les lments ont aussi la possibilit de contenir des donnes (QVariant), dont des types
personnaliss enregistrs, et en drivant la classe dlments, nous pouvons proposer des fonctionnalits supplmentaires.
Dautres kits doutils fournissent un pointeur void dans leurs classes dlments pour conserver
des donnes personnalises. Dans Qt, lapproche la plus naturelle consiste utiliser setData()
avec un QVariant, mais si un pointeur void est ncessaire, vous driverez simplement une
classe dlments et vous ajouterez une variable membre pointeur void.
Quand la gestion des donnes devient plus exigeante, comme dans le cas de jeux de donnes
de grande taille, dlments de donnes complexes, dune intgration de base de donnes et de
vues multiples de donnes, Qt propose un ensemble de classes modle/vue qui sparent les
donnes de leur reprsentation visuelle. Ces thmes sont traits au Chapitre 10.

Cell *Spreadsheet::cell(int row, int column) const


{
return static_cast<Cell *>(item(row, column));
}

La fonction prive cell() retourne lobjet Cell pour une ligne et une colonne donnes. Elle
est presque identique QTableWidget::item(), sauf quelle renvoie un pointeur de Cell au
lieu dun pointeur de QTableWidgetItem.
QString Spreadsheet::text(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->text();
} else {
return "";
}
}

La fonction prive text() retourne le texte dune cellule particulire. Si cell() retourne un
pointeur nul, la cellule est vide, une chane vide est donc renvoye.
QString Spreadsheet::formula(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->formula();
} else {
return "";
}
}

Qt 4 Livre Page 84 Jeudi, 7. dcembre 2006 12:14 12

84

Qt4 et C++ : Programmation dinterfaces GUI

La fonction formula() retourne la formule de la cellule. Dans la plupart des cas, la formule et le
texte sont identiques ; par exemple, la formule "Hello" dtermine la chane "Hello", donc si lutilisateur tape "Hello" dans une cellule et appuie sur Entre, cette cellule affichera le texte "Hello".
Mais il y a quelques exceptions :
Si la formule est un nombre, elle est interprte en tant que tel. Par exemple, la formule
"1,50" interprte la valeur en type double 1,5, qui est affich sous la forme "1,5" justifi
droite dans la feuille de calcul.
Si la formule commence par une apostrophe, le reste de la formule est considr comme du
texte. Par exemple, la formule " 12345" interprte cette valeur comme la chane "12345."
Si la formule commence par un signe gal (=), elle est considre comme une formule
arithmtique. Par exemple, si la cellule A1 contient "12" et si la cellule A2 comporte le
chiffre "6", la formule "=A1+A2" est gale 18.
La tche qui consiste convertir une formule en valeur est accomplie par la classe Cell. Pour
linstant, limportant est de se souvenir que le texte affich dans la cellule est le rsultat de
lvaluation dune formule et pas la formule en elle-mme.
void Spreadsheet::setFormula(int row, int column,
const QString &formula)
{
Cell *c = cell(row, column);
if (!c) {
c = new Cell;
setItem(row, column, c);
}
c->setFormula(formula);
}

La fonction prive setFormula() dfinit la formule dune cellule donne. Si la cellule


contient dj un objet Cell, nous le rutilisons. Sinon, nous crons un nouvel objet Cell et nous
appelons QTableWidget::setItem() pour linsrer dans la table. Pour terminer, nous invoquons la propre fonction setFormula() de la cellule, pour actualiser cette dernire si elle est
affiche lcran. Nous navons pas nous soucier de supprimer lobjet Cell par la suite ;
QTableWidget prend en charge la cellule et la supprimera automatiquement au moment voulu.
QString Spreadsheet::currentLocation() const
{
return QChar(A + currentColumn())
+ QString::number(currentRow() + 1);
}

La fonction currentLocation() retourne lemplacement de la cellule actuelle dans le format


habituel de la feuille de calcul, soit la lettre de la colonne suivie du numro de la ligne.
MainWindow::updateStatusBar() lutilise pour afficher lemplacement dans la barre dtat.
QString Spreadsheet::currentFormula() const
{
return formula(currentRow(), currentColumn());
}

Qt 4 Livre Page 85 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

85

La fonction currentFormula() retourne la formule de la cellule en cours. Elle est invoque


partir de MainWindow::updateStatusBar().
void Spreadsheet::somethingChanged()
{
if (autoRecalc)
recalculate();
emit modified();
}

Le slot priv somethingChanged() recalcule lensemble de la feuille de calcul si loption de


"recalcul automatique" est active. Il met galement le signal modified().

Chargement et sauvegarde
Nous allons dsormais implmenter le chargement et la sauvegarde des fichiers Spreadsheet
grce un format binaire personnalis. Pour ce faire, nous emploierons QFile et QDataStream
qui, ensemble, fournissent des entres/sorties binaires indpendantes de la plate-forme.
Nous commenons par crire un fichier Spreadsheet :
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot write file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(MagicNumber);
QApplication::setOverrideCursor(Qt::WaitCursor);
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
QApplication::restoreOverrideCursor();
return true;
}

La fonction writeFile() est appele depuis MainWindow::saveFile() pour crire le


fichier sur le disque. Elle retourne true en cas de succs et false en cas derreur.

Qt 4 Livre Page 86 Jeudi, 7. dcembre 2006 12:14 12

86

Qt4 et C++ : Programmation dinterfaces GUI

Nous crons un objet QFile avec un nom de fichier donn et nous invoquons open() pour
ouvrir le fichier en criture. Nous crons aussi un objet QDataStream qui agit sur le QFile et
sen sert pour crire les donnes.
Juste avant dcrire les donnes, nous changeons le pointeur de lapplication en pointeur
dattente standard (gnralement un sablier) et nous restaurons le pointeur normal lorsque
toutes les donnes ont t crites. A la fin de la fonction, le fichier est ferm automatiquement
par le destructeur de QFile.
QDataStream prend en charge les types C++ de base, de mme que plusieurs types de Qt.
La syntaxe est conue selon les classes <iostream> du langage C++ Standard. Par exemple,
out << x << y << z;

crit les variables x, y et z dans un flux, et


in >> x >> y >> z;

les lit depuis un flux. Etant donn que les types C++ de base char, short, int, long et long
long peuvent prsenter des tailles diffrentes selon les plates-formes, il est plus sr de convertir ces valeurs en qint8, quint8, qint16, quint16, qint32, quint32, qint64 ou
quint64; vous avez ainsi la garantie de travailler avec un type de la taille annonce (en bits).
Le format de fichier de lapplication Spreadsheet est assez simple (voir Figure 4.3). Un fichier
Spreadsheet commence par un numro sur 32 bits qui identifie le format de fichier (MagicNumber, dfini par 0x7F51C883 dans spreadsheet.h, un chiffre alatoire). Ce numro est
suivi dune srie de blocs, chacun deux contenant la ligne, la colonne et la formule dune
seule cellule. Pour conomiser de lespace, nous ncrivons pas de cellules vides.
Figure 4.3
Le format du fichier
Spreadsheet

0x7F51C883

123

5 Fr

123

6 Francium

La reprsentation binaire prcise des types de donnes est dtermine par QDataStream. Par
exemple, un type quint16 est stock sous forme de deux octets en ordre big-endian, et un
QString se compose de la longueur de la chane suivie des caractres Unicode.
La reprsentation binaire des types Qt a largement volu depuis Qt 1.0. Il est fort probable
quelle continue son dveloppement dans les futures versions de Qt pour suivre lvolution des
types existants et pour tenir compte des nouveaux types Qt. Par dfaut, QDataStream utilise la
version la plus rcente du format binaire (version 7 dans Qt 4.1), mais il peut tre configur de
manire lire des versions antrieures. Pour viter tout problme de compatibilit si lapplication est recompile par la suite avec une nouvelle version de Qt, nous demandons explicitement
QDataStream demployer la version 7 quelle que soit la version de Qt utilise pour la compilation. (QDataStream::Qt_4_1 est une constante pratique qui vaut 7.)
QDataStream est trs polyvalent. Il peut tre employ sur QFile, mais aussi sur QBuffer,
QProcess, QTcpSocket ou QUdpSocket. Qt propose galement une classe QTextStream qui

Qt 4 Livre Page 87 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

87

peut tre utilise la place de QDataStream pour lire et crire des fichiers texte. Le Chapitre 12 se penche en dtail sur ces classes et dcrit les diverses approches consistant grer les
diffrentes versions de QDataStream.
bool Spreadsheet::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot read file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_1);
quint32 magic;
in >> magic;
if (magic!= MagicNumber) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("The file is not a Spreadsheet file."));
return false;
}
clear();
quint16 row;
quint16 column;
QString str;
QApplication::setOverrideCursor(Qt::WaitCursor);
while (!in.atEnd()) {
in >> row >> column >> str;
setFormula(row, column, str);
}
QApplication::restoreOverrideCursor();
return true;
}

La fonction readFile() ressemble beaucoup writeFile(). Nous utilisons QFile pour lire
le fichier, mais cette fois-ci avec QIODevice::ReadOnly et pas QIODevice::WriteOnly.
Puis nous dfinissons la version de QDataStream en 7. Le format de lecture doit toujours tre
le mme que celui de lcriture.
Si le fichier dbute par le nombre magique appropri, nous appelons clear() pour vider
toutes les cellules de la feuille de calcul et nous lisons les donnes de la cellule. Vu que le
fichier ne contient que des donnes pour des cellules non vides, et quil est trs improbable que
chaque cellule de la feuille de calcul soit dfinie, nous devons nous assurer que toutes les cellules
sont effaces avant la lecture.

Qt 4 Livre Page 88 Jeudi, 7. dcembre 2006 12:14 12

88

Qt4 et C++ : Programmation dinterfaces GUI

Implmenter le menu Edit


Nous sommes dsormais prt implmenter les slots qui correspondent au menu Edit de
lapplication. Ce menu est prsent en Figure 4.4.
void Spreadsheet::cut()
{
copy();
del();
}

Le slot cut() correspond Edit > Cut. Limplmentation est simple parce que Cut est quivalent
Copy suivi de Delete.
Figure 4.4
Le menu Edit de lapplication Spreadsheet

void Spreadsheet::copy()
{
QTableWidgetSelectionRange range = selectedRange();
QString str;
for (int i = 0; i < range.rowCount(); ++i) {
if (i > 0)
str += "\n";
for (int j = 0; j < range.columnCount(); ++j) {
if (j > 0)
str += "\t";
str += formula(range.topRow() + i, range.leftColumn() + j);
}
}
QApplication::clipboard()->setText(str);
}

Le slot copy() correspond Edit > Copy. Il parcourt la slection actuelle (qui est simplement
la cellule en cours sil ny a pas de slection explicite). La formule de chaque cellule slectionne est ajoute QString, avec des lignes spares par des sauts de ligne et des colonnes spares
par des tabulations.
Le presse-papiers est disponible dans Qt par le biais de la fonction statique QApplication::clipboard(). En appelant QClipboard::setText(), le texte est disponible dans le

Qt 4 Livre Page 89 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

89

presse-papiers, la fois pour cette application et pour dautres qui prennent en charge le texte
brut (voir Figure 4.5). Notre format, bas sur les tabulations et les sauts de lignes en tant que
sparateurs, est compris par une multitude dapplications, dont Microsoft Excel.
Figure 4.5
Copier une slection
dans le presse-papiers

"Red\tGreen\tBlue\nCyan\tMagenta\tYellow"

La fonction QTableWidget::selectedRanges() retourne une liste de plages de slection.


Nous savons quil ne peut pas y en avoir plus dune, parce que nous avons dfini le mode de
slection en QAbstractItemView::ContiguousSelection dans le constructeur. Par souci
de commodit, nous configurons une fonction selectedRange() qui retourne la plage de
slection :
QTableWidgetSelectionRange Spreadsheet::selectedRange() const
{
QList<QTableWidgetSelectionRange> ranges = selectedRanges();
if (ranges.isEmpty())
return QTableWidgetSelectionRange();
return ranges.first();
}

Sil ny a quune slection, nous retournons simplement la premire (et unique). Vous ne
devriez jamais vous trouver dans le cas o il ny a aucune slection, tant donn que le mode
ContiguousSelection considre la cellule en cours comme slectionne. Toutefois pour
prvenir tout bogue dans notre programme, nous grons ce cas de figure.
void Spreadsheet::paste()
{
QTableWidgetSelectionRange range = selectedRange();
QString str = QApplication::clipboard()->text();
QStringList rows = str.split(\n);
int numRows = rows.count();
int numColumns = rows.first().count(\t) + 1;
if (range.rowCount() * range.columnCount()!= 1
&& (range.rowCount()!= numRows
|| range.columnCount()!= numColumns)) {
QMessageBox::information(this, tr("Spreadsheet"),
tr("The information cannot be pasted because the copy "
"and paste areas arent the same size."));
return;
}
for (int i = 0; i < numRows; ++i) {

Qt 4 Livre Page 90 Jeudi, 7. dcembre 2006 12:14 12

90

Qt4 et C++ : Programmation dinterfaces GUI

QStringList columns = rows[i].split(\t);


for (int j = 0; j < numColumns; ++j) {
int row = range.topRow() + i;
int column = range.leftColumn() + j;
if (row < RowCount && column < ColumnCount)
setFormula(row, column, columns[j]);
}
}
somethingChanged();
}

Le slot paste() correspond Edit > Paste. Nous rcuprons le texte dans le presse-papiers et
nous appelons la fonction statique QString::split() pour adapter la chane au QStringList.
Chaque ligne devient une chane dans la liste.
Nous dterminons ensuite la dimension de la zone de copie. Le nombre de lignes correspond
au nombre de chanes dans QStringList; le nombre de colonnes est le nombre de tabulations la premire ligne, plus 1. Si une seule cellule est slectionne, nous nous servons de
cette cellule comme coin suprieur gauche de la zone de collage ; sinon, nous utilisons la
slection actuelle comme zone de collage.
Pour effectuer le collage, nous parcourons les lignes et nous les divisons en cellules grce
QString::split(), mais cette fois-ci avec une tabulation comme sparateur. La Figure 4.6
illustre ces tapes.
"Red\tGreen\tBlue\nCyan\tMagenta\tYellow"

Figure 4.6
Coller le texte du pressepapiers dans la feuille
de calcul

["Red\tGreen\tBlue","Cyan\tMagenta\tYellow"]
["Red","Green","Blue"]
["Cyan","Magenta","Yellow"]

void Spreadsheet::del()
{
foreach (QTableWidgetItem *item, selectedItems())
delete item;
}

Le slot del() correspond Edit > Delete. Il suffit dutiliser delete sur chaque objet Cell de
la slection pour effacer les cellules. QTableWidget remarque quand ses QTableWidgetItem
sont supprims et se redessine automatiquement si lun des lments tait visible. Si nous invoquons cell() avec lemplacement dune cellule supprime, il renverra un pointeur nul.

Qt 4 Livre Page 91 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

91

void Spreadsheet::selectCurrentRow()
{
selectRow(currentRow());
}
void Spreadsheet::selectCurrentColumn()
{
selectColumn(currentColumn());
}

Les fonctions selectCurrentRow() et selectCurrentColumn() correspondent aux


options Edit > Select > Row et Edit > Select > Column. Les implmentations reposent sur les
fonctions selectRow() et selectColumn() de QTableWidget. Nous navons pas implmenter la fonctionnalit correspondant Edit > Select > All, tant donn quelle est propose
par la fonction hrite QAbstractItemView::selectAll() de QTableWidget.
void Spreadsheet::findNext(const QString &str, Qt::CaseSensitivity cs)
{
int row = currentRow();
int column = currentColumn() + 1;
while (row < RowCount) {
while (column < ColumnCount) {
if (text(row, column).contains(str, cs)) {
clearSelection();
setCurrentCell(row, column);
activateWindow();
return;
}
++column;
}
column = 0;
++row;
}
QApplication::beep();
}

Le slot findNext() parcourt les cellules en commenant par la cellule droite du pointeur et
en se dirigeant vers la droite jusqu la dernire colonne, puis il poursuit par la premire
colonne dans la ligne du dessous et ainsi de suite jusqu trouver le texte recherch ou jusqu
atteindre la toute dernire cellule. Par exemple, si la cellule en cours est la cellule C24, nous
recherchons D24, E24, , Z24, puis A25, B25, C25, , Z25, et ainsi de suite jusqu Z999. Si
nous trouvons une correspondance, nous supprimons la slection actuelle, nous dplaons le
pointeur vers cette cellule et nous activons la fentre qui contient Spreadsheet. Si aucune
correspondance nest dcouverte, lapplication met un signal sonore pour indiquer que la
recherche na pas abouti.
void Spreadsheet::findPrevious(const QString &str,
Qt::CaseSensitivity cs)
{

Qt 4 Livre Page 92 Jeudi, 7. dcembre 2006 12:14 12

92

Qt4 et C++ : Programmation dinterfaces GUI

int row = currentRow();


int column = currentColumn() - 1;
while (row >= 0) {
while (column >= 0) {
if (text(row, column).contains(str, cs)) {
clearSelection();
setCurrentCell(row, column);
activateWindow();
return;
}
--column;
}
column = ColumnCount - 1;
--row;
}
QApplication::beep();
}

Le slot findPrevious() est similaire findNext(), sauf quil effectue une recherche dans
lautre sens et sarrte la cellule A1.

Implmenter les autres menus


Nous allons maintenant implmenter les slots des menus Tools et Options. Ces menus sont
illustrs en Figure 4.7.
Figure 4.7
Les menus Tools et
Options de lapplication
Spreadsheet
void Spreadsheet::recalculate()
{
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
if (cell(row, column))
cell(row, column)->setDirty();
}
}
viewport()->update();
}

Le slot recalculate() correspond Tools > Recalculate. Il est aussi appel automatiquement
par Spreadsheet si ncessaire.

Qt 4 Livre Page 93 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

93

Nous parcourons toutes les cellules et invoquons setDirty() sur chacune delles pour signaler
celles qui doivent tre recalcules. La prochaine fois que QTableWidget appelle text() sur
Cell pour obtenir la valeur afficher dans la feuille de calcul, la valeur sera recalcule.
Nous appelons ensuite update() sur le viewport pour redessiner la feuille de calcul complte.
Le code de raffichage dans QTableWidget invoque ensuite text() sur chaque cellule visible
pour obtenir la valeur afficher. Vu que nous avons appel setDirty() sur chaque cellule, les
appels de text() utiliseront une valeur nouvellement calcule. Le calcul pourrait exiger que
les cellules non visibles soient recalcules, rpercutant la mme opration jusqu ce que chaque
cellule qui a besoin dtre recalcule pour afficher le bon texte dans le viewport ait une valeur
ractualise. Le calcul est effectu par la classe Cell.
void Spreadsheet::setAutoRecalculate(bool recalc)
{
autoRecalc = recalc;
if (autoRecalc)
recalculate();
}

Le slot setAutoRecalculate() correspond Options > Auto-Recalculate. Si la fonction est


active, nous recalculons immdiatement la feuille de calcul pour nous assurer quelle est
jour ; ensuite, recalculate() est appel automatiquement dans somethingChanged().
Nous navons pas besoin dimplmenter quoi que ce soit pour Options > Show Grid, parce que
QTableWidget a dj un slot setShowGrid() quil a hrit de sa classe de base QTableView.
Tout ce qui reste, cest Spreadsheet::sort() qui est invoque dans MainWindow::sort():
void Spreadsheet::sort(const SpreadsheetCompare &compare)
{
QList<QStringList> rows;
QTableWidgetSelectionRange range = selectedRange();
int i;
for (i = 0; i < range.rowCount(); ++i) {
QStringList row;
for (int j = 0; j < range.columnCount(); ++j)
row.append(formula(range.topRow() + i,
range.leftColumn() + j));
rows.append(row);
}
qStableSort(rows.begin(), rows.end(), compare);
for (i = 0; i < range.rowCount(); ++i) {
for (int j = 0; j < range.columnCount(); ++j)
setFormula(range.topRow() + i, range.leftColumn() + j,
rows[i][j]);
}
clearSelection();
somethingChanged();
}

Qt 4 Livre Page 94 Jeudi, 7. dcembre 2006 12:14 12

94

Qt4 et C++ : Programmation dinterfaces GUI

Le tri sopre sur la slection actuelle et rorganise les lignes selon les cls et les ordres de tri
stocks dans lobjet compare. Nous reprsentons chaque ligne de donnes avec QStringList
et nous conservons la slection sous forme de liste de lignes (voir Figure 4.8). Nous nous
servons de lalgorithme qStableSort() de Qt et pour une question de simplicit, le tri
seffectue sur la formule plutt que sur la valeur. Les algorithmes standards, de mme que les
structures de donnes de Qt sont abords au Chapitre 11.
Figure 4.8
Stocker la slection
sous forme de liste
de lignes

index

value

["Edsger","Dijkstra","1930-05-11"]

["Tony","Hoare","1934-01-11"]

["Niklaus","Wirth","1934-02-15"]

["Donald","Knuth","1938-01-10"]

La fonction qStableSort() reoit un itrateur de dbut et de fin, ainsi quune fonction de comparaison. La fonction de comparaison est une fonction qui reoit deux arguments (deux QStringList) et qui retourne true si le premier argument est "infrieur" au second argument et false
dans les autres cas. Lobjet compare que nous transmettons comme fonction de comparaison
nest pas vraiment une fonction, mais il peut tre utilis comme telle, comme nous allons le voir.
Figure 4.9
Rintgrer les
donnes dans
la table aprs le tri

index
0

value
["Donald","Knuth","1938-01-10"]

["Edsger","Dijkstra","1930-05-11"]

["Niklaus","Wirth","1934-02-15"]

["Tony","Hoare","1934-01-11"]

Aprs avoir excut qStableSort(), nous rintgrons les donnes dans la table (voir
Figure 4.9), nous effaons la slection et nous appelons somethingChanged().
Dans spreadsheet.h, la classe SpreadsheetCompare tait dfinie comme suit :
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];
bool ascending[KeyCount];
};

La classe SpreadsheetCompare est spciale parce quelle implmente un oprateur (). Nous
avons donc la possibilit dutiliser la classe comme si ctait une fonction. De telles classes
sont appeles des objets fonction, ou foncteurs. Pour comprendre comment fonctionnent les
foncteurs, nous dbutons par un exemple simple :

Qt 4 Livre Page 95 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

95

class Square
{
public:
int operator()(int x) const { return x * x; }
}

La classe Square fournit une fonction, operator()(int), qui retourne le carr de son paramtre. En nommant la fonction operator()(int), au lieu de compute(int) par exemple,
nous avons la possibilit dutiliser un objet de type Square comme si ctait une fonction :
Square square;
int y = square(5);

A prsent, analysons un exemple impliquant SpreadsheetCompare:


QStringList row1, row2;
QSpreadsheetCompare compare;
...
if (compare(row1, row2)) {
// row1 est infrieure
}

Lobjet compare peut tre employ comme sil tait une fonction compare() ordinaire. De
plus, son implmentation peut accder toutes les cls et ordres de tri stocks comme variables
membres.
Il existe une alternative : nous aurions pu conserver tous les ordres et cls de tri dans des variables
globales et utiliser une fonction compare() ordinaire. Cependant, la communication via les
variables globales nest pas trs lgante et peut engendrer des bogues subtils. Les foncteurs
sont plus puissants pour interfacer avec des fonctions modles comme qStableSort().
Voici limplmentation de la fonction employe pour comparer deux lignes de la feuille de calcul :
bool SpreadsheetCompare::operator()(const QStringList &row1,
const QStringList &row2) const
{
for (int i = 0; i < KeyCount; ++i) {
int column = keys[i];
if (column!= -1) {
if (row1[column]!= row2[column]) {
if (ascending[i]) {
return row1[column] < row2[column];
} else {
return row1[column] > row2[column];
}
}
}
}
return false;
}

Qt 4 Livre Page 96 Jeudi, 7. dcembre 2006 12:14 12

96

Qt4 et C++ : Programmation dinterfaces GUI

Loprateur retourne true si la premire ligne est infrieure la seconde et false dans les
autres cas. La fonction qStableSort() utilise ce rsultat pour effectuer le tri.
Les tables keys et ascending de lobjet SpreadsheetCompare sont alimentes dans la fonction
MainWindow::sort() (vue au Chapitre 2). Chaque cl possde un index de colonne ou 1
("None").
Nous comparons les entres de cellules correspondantes dans les deux lignes pour chaque cl
dans lordre. Ds que nous dcouvrons une diffrence, nous retournons une valeur true ou
false. Sil savre que toutes les comparaisons sont gales, nous retournons false. La fonction qStableSort() sappuie sur lordre avant le tri pour rsoudre les situations dgalit ; si
row1 prcdait row2 lorigine et nest jamais "infrieur " lautre, row1 prcdera toujours
row2 dans le rsultat. Cest ce qui distingue qStableSort() de son cousin qSort() dont le
rsultat est moins prvisible.
Nous avons dsormais termin la classe Spreadsheet. Dans la prochaine section, nous allons
analyser la classe Cell. Cette classe est employe pour contenir les formules des cellules et
propose une rimplmentation de la fonction QTableWidgetItem::data()
que
Spreadsheet appelle indirectement par le biais de la fonction QTableWidgetItem::text().
Lobjectif est dafficher le rsultat du calcul de la formule dune cellule.

Drivation de QTableWidgetItem
La classe Cell hrite de QTableWidgetItem. Cette classe est conue pour bien fonctionner
avec Spreadsheet, mais elle ne dpend pas spcifiquement de cette classe et pourrait, en
thorie, tre utilise dans nimporte quel QTableWidget. Voici le fichier den-tte :
#ifndef CELL_H
#define CELL_H
#include <QTableWidgetItem>
class Cell: public QTableWidgetItem
{
public:
Cell();
QTableWidgetItem *clone() const;
void setData(int role, const QVariant &value);
QVariant data(int role) const;
void setFormula(const QString &formula);
QString formula() const;
void setDirty();
private:
QVariant value() const;
QVariant evalExpression(const QString &str, int &pos) const;
QVariant evalTerm(const QString &str, int &pos) const;

Qt 4 Livre Page 97 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

97

QVariant evalFactor(const QString &str, int &pos) const;


mutable QVariant cachedValue;
mutable bool cacheIsDirty;
};
#endif

La classe Cell dveloppe QTableWidgetItem en ajoutant deux variables prives :

cachedValue met en cache la valeur de la cellule sous forme de QVariant.

cacheIsDirty est true si la valeur mise en cache nest pas jour.

Nous utilisons QVariant parce que certaines cellules ont une valeur double alors que dautres
ont une valeur QString.
Les variables cachedValue et cacheIsDirty sont dclares avec le mot-cl C++ mutable.
Nous avons ainsi la possibilit de modifier ces variables dans des fonctions const. Nous pourrions
aussi recalculer la valeur chaque fois que text() est appele, mais ce serait tout fait inefficace.
Notez quil ny a pas de macro Q_OBJECT dans la dfinition de classe. Cell est une classe C++
ordinaire, sans signaux ni slots. En fait, vu que QTableWidgetItem nhrite pas de QObject,
nous ne pouvons pas avoir de signaux et de slots dans Cell. Les classes dlments de Qt
nhritent pas de QObject pour optimiser les performances. Si des signaux et des slots se rvlent ncessaires, ils peuvent tre implments dans le widget qui contient les lments ou,
exceptionnellement, en utilisant lhritage multiple avec QObject.
Voici le dbut de cell.cpp:
#include <QtGui>
#include "cell.h"
Cell::Cell()
{
setDirty();
}

Dans le constructeur, nous devons simplement dfinir le cache comme tant actualiser
(dirty). Vous navez pas besoin de transmettre un parent ; quand la cellule est insre dans un
QTableWidget avec setItem(), le QTableWidget prend automatiquement possession de
celle-ci.
Chaque QTableWidgetItem peut contenir des donnes, jusqu un QVariant pour chaque
"rle" de donnes. Les rles les plus couramment utiliss sont Qt::EditRole et
Qt::DisplayRole. Le rle de modification est employ pour des donnes qui doivent tre
modifies et le rle daffichage pour des donnes qui doivent tre affiches. Il arrive souvent
que ces donnes soient les mmes, mais dans Cell le rle de modification correspond la

Qt 4 Livre Page 98 Jeudi, 7. dcembre 2006 12:14 12

98

Qt4 et C++ : Programmation dinterfaces GUI

formule de la cellule et le rle daffichage la valeur de la cellule (le rsultat de lvaluation de


la formule).
QTableWidgetItem *Cell::clone() const
{
return new Cell(*this);
}

La fonction clone() est invoque par QTableWidget quand il doit crer une nouvelle
cellule par exemple quand lutilisateur commence taper dans une cellule vide qui na
encore jamais t utilise. Linstance transmise QTableWidget::setItemPrototype() est
llment qui est clon. Vu que la copie au niveau du membre est suffisante pour Cell, nous
nous basons sur le constructeur de copie par dfaut cr automatiquement par C++ dans le but
de crer de nouvelles instances Cell dans la fonction clone().
void Cell::setFormula(const QString &formula)
{
setData(Qt::EditRole, formula);
}

La fonction setFormula() dfinit la formule de la cellule. Cest simplement une fonction


pratique permettant dappeler setData() avec le rle de modification. Elle est invoque dans
Spreadsheet::setFormula().
QString Cell::formula() const
{
return data(Qt::EditRole).toString();
}

La fonction formula() est appele dans Spreadsheet::formula(). Comme setFormula(),


cest une fonction commode, mais cette fois-ci qui rcupre les donnes EditRole de llment.
void Cell::setData(int role, const QVariant &value)
{
QTableWidgetItem::setData(role, value);
if (role == Qt::EditRole)
setDirty();
}

Si nous avons affaire une nouvelle formule, nous dfinissons cacheIsDirty en true pour
garantir que la cellule sera recalcule la prochaine fois que text() est appel.
Aucune fonction text() nest dfinie dans Cell, mme si nous appelons text() sur des
instances Cell dans Spreadsheet::text(). La fonction text() est une fonction de
convenance propose par QTableWidgetItem; cela revient au mme que dappeler
data(Qt::DisplayRole).toString().
void Cell::setDirty()
{
cacheIsDirty = true;
}

Qt 4 Livre Page 99 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

99

La fonction setDirty() est invoque pour forcer le recalcul de la valeur dune cellule. Elle
dfinit simplement cacheIsDirty en true, ce qui signifie que cachedValue nest plus
jour. Le recalcul nest effectu que lorsquil est ncessaire.
QVariant Cell::data(int role) const
{
if (role == Qt::DisplayRole) {
if (value().isValid()) {
return value().toString();
} else {
return "####";
}
} else if (role == Qt::TextAlignmentRole) {
if (value().type() == QVariant::String) {
return int(Qt::AlignLeft | Qt::AlignVCenter);
} else {
return int(Qt::AlignRight | Qt::AlignVCenter);
}
} else {
return QTableWidgetItem::data(role);
}
}

La fonction data() est rimplmente dans QTableWidgetItem. Elle retourne le texte qui
doit tre affich dans la feuille de calcul si elle est appele avec Qt::DisplayRole, et la
formule si elle est invoque avec Qt::EditRole. Elle renvoie lalignement appropri si elle
est appele avec Qt::TextAlignmentRole. Dans le cas de DisplayRole, elle se base sur
value() pour calculer la valeur de la cellule. Si la valeur nest pas valide (parce que la
formule est mauvaise), nous retournons "####".
La fonction Cell::value() utilise dans data() retourne un QVariant. Un QVariant peut
stocker des valeurs de diffrents types, comme double et QString, et propose des fonctions
pour convertir les variants dans dautres types. Par exemple, appeler toString() sur un
variant qui contient une valeur double produit une chane de double. Un QVariant construit
avec le constructeur par dfaut est un variant "invalide".
const QVariant Invalid;
QVariant Cell::value() const
{
if (cacheIsDirty) {
cacheIsDirty = false;
QString formulaStr = formula();
if (formulaStr.startsWith(\)) {
cachedValue = formulaStr.mid(1);
} else if (formulaStr.startsWith(=)) {
cachedValue = Invalid;
QString expr = formulaStr.mid(1);
expr.replace(" ", "");
expr.append(QChar::Null);

Qt 4 Livre Page 100 Jeudi, 7. dcembre 2006 12:14 12

100

Qt4 et C++ : Programmation dinterfaces GUI

int pos = 0;
cachedValue = evalExpression(expr, pos);
if (expr[pos]!= QChar::Null)
cachedValue = Invalid;
} else {
bool ok;
double d = formulaStr.toDouble(&ok);
if (ok) {
cachedValue = d;
} else {
cachedValue = formulaStr;
}
}
}
return cachedValue;
}

La fonction prive value() retourne la valeur de la cellule. Si cacheIsDirty est true, nous
devons la recalculer.
Si la formule commence par une apostrophe (par exemple " 12345"), lapostrophe se trouve
la position 0 et la valeur est la chane allant de la position 1 la fin.
Si la formule commence par un signe gal (=), nous prenons la chane partir de la position 1
et nous supprimons tout espace quelle contient. Nous appelons ensuite evalExpression()
pour calculer la valeur de lexpression. Largument pos est transmis par rfrence ; il indique
la position du caractre o lanalyse doit commencer. Aprs lappel de evalExpression(), le
caractre la position pos doit tre le caractre QChar::Null que nous avons ajout, sil a t
correctement analys. Si lanalyse a chou avant la fin, nous dfinissons cachedValue de
sorte quil soit Invalid.
Si la formule ne commence pas par une apostrophe ni par un signe gal, nous essayons de la
convertir en une valeur virgule flottante laide de toDouble(). Si la conversion fonctionne,
nous configurons cachedValue pour y stocker le nombre obtenu ; sinon, nous dfinissons
cachedValue avec la chane de la formule. Par exemple, avec une formule de "1,50," toDouble() dfinit ok en true et retourne 1,5, alors quavec une formule de "World Population"
toDouble() dfinit ok en false et renvoie 0,0.
En transmettant toDouble() un pointeur de type bool, nous sommes en mesure de faire une
distinction entre la conversion dune chane qui donne la valeur numrique 0,0 et une erreur de
conversion (o 0,0 est aussi retourn mais bool est dfini en false). Il est cependant parfois
ncessaire de retourner une valeur zro sur un chec de conversion, auquel cas nous navons
pas besoin de transmettre de pointeur de bool. Pour des questions de performances et de portabilit, Qt nutilise jamais dexceptions C++ pour rapporter des checs. Ceci ne vous empche pas
de les utiliser dans des programmes Qt, condition que votre compilateur les prenne en charge.
La fonction value() est dclare const. Nous devions dclarer cachedValue et cacheIsValid comme des variables mutable, de sorte que le compilateur nous permette de les modifier
dans des fonctions const. Ce pourrait tre tentant de rendre value() non-const et de supprimer

Qt 4 Livre Page 101 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

101

les mots cls mutable, mais le rsultat ne compilerait pas parce que nous appelons value()
depuis data(), une fonction const.
Nous avons dsormais fini lapplication Spreadsheet, except lanalyse des formules. Le reste
de cette section est ddie evalExpression() et les deux fonctions evalTerm() et evalFactor(). Le code est un peu compliqu, mais il est introduit ici pour complter lapplication.
Etant donn que le code nest pas li la programmation dinterfaces graphiques utilisateurs,
vous pouvez tranquillement lignorer et continuer lire le Chapitre 5.
La fonction evalExpression() retourne la valeur dune expression dans la feuille de calcul.
Une expression est dfinie sous la forme dun ou plusieurs termes spars par les oprateurs
"+" ou "".Les termes eux-mmes sont dfinis comme un ou plusieurs facteurs spars par les
oprateurs "*" ou "/".En divisant les expressions en termes et les termes en facteurs, nous
sommes srs que les oprateurs sont appliqus dans le bon ordre.
Par exemple, "2*C5+D6" est une expression avec "2*C5" comme premier terme et "D6"
comme second terme. Le terme "2*C5" a "2" comme premier facteur et "C5" comme
deuxime facteur, et le terme "D6" est constitu dun seul facteur "D6".Un facteur peut tre un
nombre ("2"), un emplacement de cellule ("C5") ou une expression entre parenthses, prcde
facultativement dun signe moins unaire.
Expression

Terme

Terme

Facteur

Facteur
Nombre

Emplacement de cellule
(

Expression

Figure 4.10
Diagramme de la syntaxe des expressions de la feuille de calcul

La syntaxe des expressions de la feuille de calcul est prsente dans la Figure 4.10. Pour
chaque symbole de la grammaire (Expression, Terme et Facteur), il existe une fonction membre
correspondante qui lanalyse et dont la structure respecte scrupuleusement cette grammaire.
Les analyseurs crits de la sorte sont appels des analyseurs vers le bas rcursifs.
Commenons par evalExpression(), la fonction qui analyse une Expression :
QVariant Cell::evalExpression(const QString &str, int &pos) const
{
QVariant result = evalTerm(str, pos);
while (str[pos]!= QChar::Null) {
QChar op = str[pos];
if (op!= + && op!= -)
return result;
++pos;

Qt 4 Livre Page 102 Jeudi, 7. dcembre 2006 12:14 12

102

Qt4 et C++ : Programmation dinterfaces GUI

QVariant term = evalTerm(str, pos);


if (result.type() == QVariant::Double
&& term.type() == QVariant::Double) {
if (op == +) {
result = result.toDouble() + term.toDouble();
} else {
result = result.toDouble() - term.toDouble();
}
} else {
result = Invalid;
}
}
return result;
}

Nous appelons tout dabord evalTerm() pour obtenir la valeur du premier terme. Si le caractre suivant est "+" ou "", nous appelons evalTerm() une deuxime fois ; sinon, lexpression est constitue dun seul terme et nous retournons sa valeur en tant que valeur de toute
lexpression. Une fois que nous avons les valeurs des deux premiers termes, nous calculons le
rsultat de lopration en fonction de loprateur. Si les deux termes ont t valus en double,
nous calculons le rsultat comme tant double; sinon nous dfinissons le rsultat comme
tant Invalid.
Nous continuons de cette manire jusqu ce quil ny ait plus de termes. Ceci fonctionne
correctement parce que les additions et les soustractions sont de type associatif gauche ; cest-dire que "123" signifie "(12)3" et non "1(23)."
QVariant Cell::evalTerm(const QString &str, int &pos) const
{
QVariant result = evalFactor(str, pos);
while (str[pos]!= QChar::Null) {
QChar op = str[pos];
if (op!= * && op!= /)
return result;
++pos;
QVariant factor = evalFactor(str, pos);
if (result.type() == QVariant::Double
&& factor.type() == QVariant::Double) {
if (op == *) {
result = result.toDouble() * factor.toDouble();
} else {
if (factor.toDouble() == 0.0) {
result = Invalid;
} else {
result = result.toDouble() / factor.toDouble();
}
}
} else {

Qt 4 Livre Page 103 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 4

Implmenter la fonctionnalit dapplication

103

result = Invalid;
}
}
return result;
}

La fonction evalTerm() ressemble beaucoup evalExpression(), sauf quelle traite des


multiplications et des divisions. La seule subtilit dans evalTerm(), cest que vous devez
viter de diviser par zro, parce que cela constitue une erreur dans certains processeurs. Bien
quil ne soit pas recommand de tester lgalit de valeurs de type virgule flottante en raison
des problmes darrondis, vous pouvez tester sans problmes lgalit par rapport 0,0 pour
viter toute division par zro.
QVariant Cell::evalFactor(const QString &str, int &pos) const
{
QVariant result;
bool negative = false;
if (str[pos] == -) {
negative = true;
++pos;
}
if (str[pos] == () {
++pos;
result = evalExpression(str, pos);
if (str[pos]!= ))
result = Invalid;
++pos;
} else {
QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");
QString token;
while (str[pos].isLetterOrNumber() || str[pos] == .) {
token += str[pos];
++pos;
}
if (regExp.exactMatch(token)) {
int column = token[0].toUpper().unicode() - A;
int row = token.mid(1).toInt() - 1;
Cell *c = static_cast<Cell *>(
tableWidget()->item(row, column));
if (c) {
result = c->value();
} else {
result = 0.0;
}
} else {
bool ok;
result = token.toDouble(&ok);

Qt 4 Livre Page 104 Jeudi, 7. dcembre 2006 12:14 12

104

Qt4 et C++ : Programmation dinterfaces GUI

if (!ok)
result = Invalid;
}
}
if (negative) {
if (result.type() == QVariant::Double) {
result = -result.toDouble();
} else {
result = Invalid;
}
}
return result;
}

La fonction evalFactor() est un peu plus complique que evalExpression() et evalTerm(). Nous regardons dabord si le facteur est prcd du signe ngatif. Nous examinons
ensuite sil commence par une parenthse ouverte. Si cest le cas, nous valuons le contenu des
parenthses comme une expression en appelant evalExpression(). Lorsque nous valuons
une expression entre parenthses, evalExpression() appelle evalTerm(), qui invoque
evalFactor(), qui appelle nouveau evalExpression(). Cest l quintervient la rcursivit dans lanalyseur.
Si le facteur nest pas une expression imbrique, nous extrayons le prochain jeton, qui devrait
tre un emplacement de cellule ou un nombre. Si le jeton correspond QRegExp, nous le considrons comme une rfrence de cellule et nous appelons value() sur la cellule lemplacement donn. La cellule pourrait se trouver nimporte o dans la feuille de calcul et pourrait tre
dpendante dautres cellules. Les dpendances ne sont pas un problme ; elles dclencheront
simplement plus dappels de value() et (pour les cellules " recalculer") plus danalyse
jusqu ce que les valeurs des cellules dpendantes soient calcules. Si le jeton nest pas un
emplacement de cellule, nous le considrons comme un nombre.
Que se passe-t-il si la cellule A1 contient la formule "=A1" ? Ou si la cellule A1 contient
"=A2" et la cellule A2 comporte "=A1" ? Mme si nous navons pas crit de code spcial pour
dtecter des dpendances circulaires, lanalyseur gre ces cas en retournant un QVariant invalide. Ceci fonctionne parce que nous dfinissons cacheIsDirty en false et cachedValue en
Invalid dans value() avant dappeler evalExpression(). Si evalExpression() appelle
de manire rcursive value() sur la mme cellule, il renvoie immdiatement Invalid et toute
lexpression est donc value en Invalid.
Nous avons dsormais termin lanalyseur de formules. Il nest pas compliqu de ltendre
pour quil gre des fonctions prdfinies de la feuille de calcul, comme sum() et avg(), en
dveloppant la dfinition grammaticale de Facteur. Une autre extension facile consiste implmenter loprateur "+" avec des oprandes de chane (comme une concatnation) ; aucun changement de grammaire nest exig.

Qt 4 Livre Page 105 Jeudi, 7. dcembre 2006 12:14 12

5
Crer des widgets
personnaliss
Au sommaire de ce chapitre
Personnaliser des widgets Qt
Driver QWidget
Intgrer des widgets personnaliss avec
le Qt Designer
Double mise en mmoire tampon

Ce chapitre vous explique comment concevoir des widgets personnaliss laide de Qt.
Les widgets personnaliss peuvent tre crs en drivant un widget Qt existant ou en
drivant directement QWidget. Nous vous prsenterons les deux approches et nous
verrons galement comment introduire un widget personnalis avec le Qt Designer de
sorte quil puisse tre utilis comme nimporte quel widget Qt intgr. Nous terminerons ce chapitre en vous parlant dun widget personnalis qui emploie la double mise en
mmoire tampon, une technique puissante pour actualiser trs rapidement laffichage.

Qt 4 Livre Page 106 Jeudi, 7. dcembre 2006 12:14 12

106

Qt4 et C++ : Programmation dinterfaces GUI

Personnaliser des widgets Qt


Il arrive quil ne soit pas possible dobtenir la personnalisation requise pour un widget Qt
simplement en configurant ses proprits dans le Qt Designer ou en appelant ses fonctions.
Une solution simple et directe consiste driver la classe de widget approprie et ladapter
pour satisfaire vos besoins.
Figure 5.1
Le widget HexSpinBox

Dans cette section, nous dvelopperons un pointeur toupie hexadcimal pour vous prsenter
son fonctionnement (voir Figure 5.1). QSpinBox ne prend en charge que les nombres dcimaux, mais grce la drivation, il est plutt facile de lui faire accepter et afficher des valeurs
hexadcimales.
#ifndef HEXSPINBOX_H
#define HEXSPINBOX_H
#include <QSpinBox>
class QRegExpValidator;
class HexSpinBox: public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = 0);
protected:
QValidator::State validate(QString &text, int &pos) const;
int valueFromText(const QString &text) const;
QString textFromValue(int value) const;
private:
QRegExpValidator *validator;
};
#endif

HexSpinBox hrite la majorit de ses fonctionnalits de QSpinBox. Il propose un constructeur


typique et rimplmente trois fonctions virtuelles de QSpinBox.
#include <QtGui>
#include "hexspinbox.h"
HexSpinBox::HexSpinBox(QWidget *parent)

Qt 4 Livre Page 107 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

107

: QSpinBox(parent)
{
setRange(0, 255);
validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this);
}

Nous dfinissons la plage par dfaut avec les valeurs 0 255 (0x00 0xFF), qui est plus approprie pour un pointeur toupie hexadcimal que les valeurs par dfaut de QSpinBox allant de 0
99.
Lutilisateur peut modifier la valeur en cours dun pointeur toupie, soit en cliquant sur ses
flches vers le haut et le bas, soit en saisissant une valeur dans son diteur de lignes. Dans le
second cas, nous souhaitons restreindre lentre de lutilisateur aux nombres hexadcimaux valides. Pour ce faire, nous employons QRegExpValidator qui accepte entre un et huit caractres,
chacun deux devant appartenir lun des ensembles suivants, "0" "9," "A" "F" et "a" "f".
QValidator::State HexSpinBox::validate(QString &text, int &pos) const
{
return validator->validate(text, pos);
}

Cette fonction est appele par QSpinBox pour vrifier que le texte saisi jusqu prsent est
valide. Il y a trois possibilits : Invalid (le texte ne correspond pas lexpression rgulire),
Intermediate (le texte est une partie plausible dune valeur valide) et Acceptable (le texte
est valide). QRegExpValidator possde une fonction validate() approprie, nous retournons donc simplement le rsultat de son appel. En thorie, nous devrions renvoyer Invalid ou
Intermediate pour les valeurs qui se situent en dehors de la plage du pointeur toupie, mais
QSpinBox est assez intelligent pour dtecter cette condition sans aucune aide.
QString HexSpinBox::textFromValue(int value) const
{
return QString::number(value, 16).toUpper();
}

La fonction textFromValue() convertit une valeur entire en chane. QSpinBox lappelle


pour mettre jour la partie "diteur" du pointeur toupie quand lutilisateur appuie sur les
flches haut et bas du pointeur. Nous utilisons la fonction statique QString::number() avec
un second argument de 16 pour convertir la valeur en hexadcimal minuscule et nous appelons
QString::toUpper() sur le rsultat pour le passer en majuscule.
int HexSpinBox::valueFromText(const QString &text) const
{
bool ok;
return text.toInt(&ok, 16);
}

La fonction valueFromText() effectue une conversion inverse, dune chane en une valeur
entire. Elle est appele par QSpinBox quand lutilisateur saisit une valeur dans la zone de
lditeur du pointeur toupie et appuie sur Entre. Nous excutons la fonction QString::toInt()

Qt 4 Livre Page 108 Jeudi, 7. dcembre 2006 12:14 12

108

Qt4 et C++ : Programmation dinterfaces GUI

pour essayer de convertir le texte en cours en une valeur entire, toujours en base 16. Si la
chane nest pas au format hexadcimal, ok est dfini en false et toInt() retourne 0. Ici,
nous ne sommes pas obligs denvisager cette possibilit, parce que le validateur naccepte que
la saisie de chanes hexadcimales valides. Au lieu de transmettre ladresse dune variable sans
intrt (ok), nous pourrions transmettre un pointeur nul comme premier argument de toInt().
Nous avons termin le pointeur toupie hexadcimal. La personnalisation dautres widgets Qt
suit le mme processus : choisir un widget Qt adapt, le driver et rimplmenter certaines
fonctions virtuelles pour modifier son comportement.

Driver QWidget
De nombreux widgets personnaliss sont simplement obtenus partir dune combinaison de
widgets existants, que ce soit des widgets Qt intgrs ou dautres widgets personnaliss
comme HexSpinBox. Les widgets personnaliss ainsi conus peuvent gnralement tre dvelopps dans le Qt Designer :

crez un nouveau formulaire laide du modle "Widget" ;

ajoutez les widgets ncessaires au formulaire, puis disposez-les ;

tablissez les connexions entre les signaux et les slots.

Si vous avez besoin dun comportement pour lequel de simples signaux et slots sont insuffisants, crivez le code ncessaire dans une classe qui hrite de QWidget et de celle gnre
par uic.

Il est vident que combiner des widgets existants peut se faire entirement dans du code.
Quelle que soit lapproche choisie, la classe en rsultant hrite directement de QWidget.
Si le widget ne possde aucun signal ni slot et quil ne rimplmente pas de fonction virtuelle, il
est mme possible de concevoir le widget simplement en combinant des widgets existants sans
sous-classe. Cest la technique que nous avons employe dans le Chapitre 1 pour crer lapplication Age, avec QWidget, QSpinBox et QSlider. Pourtant nous aurions pu tout aussi facilement
driver QWidget et crer QSpinBox et QSlider dans le constructeur de la sous-classe.
Lorsquaucun des widgets Qt ne convient une tche particulire et lorsquil nexiste aucun
moyen de combiner ou dadapter des widgets existants pour obtenir le rsultat souhait, nous
pouvons toujours crer le widget que nous dsirons. Pour ce faire, nous devons driver QWidget et rimplmenter quelques gestionnaires dvnements pour dessiner le widget et rpondre
aux clics de souris. Cette approche nous autorise une libert totale quant la dfinition et au
contrle de lapparence et du comportement de notre widget. Les widgets intgrs de Qt,
comme QLabel, QPushButton et QTableWidget, sont implments de cette manire. Sils
nexistaient pas dans Qt, il serait encore possible de les crer nous-mmes en utilisant les fonctions
publiques fournies par QWidget de faon totalement indpendante de la plate-forme.

Qt 4 Livre Page 109 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

109

Pour vous montrer comment crire un widget personnalis en se basant sur cette technique,
nous allons crer le widget IconEditor illustr en Figure 5.2. IconEditor est un widget qui
pourrait tre utilis dans un programme dditeur dicnes.
Figure 5.2
Le widget IconEditor

Commenons par analyser le fichier den-tte.


#ifndef ICONEDITOR_H
#define ICONEDITOR_H
#include <QColor>
#include <QImage>
#include <QWidget>
class IconEditor: public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor)
Q_PROPERTY(QImage iconImage READ iconImage WRITE setIconImage)
Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor)
public:
IconEditor(QWidget *parent = 0);
void setPenColor(const QColor &newColor);
QColor penColor() const { return curColor; }
void setZoomFactor(int newZoom);
int zoomFactor() const { return zoom; }
void setIconImage(const QImage &newImage);
QImage iconImage() const { return image; }
QSize sizeHint() const;

La classe IconEditor utilise la macro Q_PROPERTY() pour dclarer trois proprits personnalises : penColor, iconImage et zoomFactor. Chaque proprit a un type de donnes, une
fonction de "lecture" et une fonction facultative "dcriture".Par exemple, la proprit penColor est
de type QColor et peut tre lue et crite grce aux fonctions penColor() et setPenColor().

Qt 4 Livre Page 110 Jeudi, 7. dcembre 2006 12:14 12

110

Qt4 et C++ : Programmation dinterfaces GUI

Quand nous utilisons le widget dans le Qt Designer, les proprits personnalises apparaissent
dans lditeur de proprits du Qt Designer sous les proprits hrites de QWidget. Ces
proprits peuvent tre de nimporte quel type pris en charge par QVariant. La macro
Q_OBJECT est ncessaire pour les classes qui dfinissent des proprits.
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
void setImagePixel(const QPoint &pos, bool opaque);
QRect pixelRect(int i, int j) const;
QColor curColor;
QImage image;
int zoom;
};
#endif

IconEditor rimplmente trois fonctions protges de QWidget et possde quelques fonctions


et variables prives. Les trois variables prives contiennent les valeurs des trois proprits.
Le fichier dimplmentation commence par le constructeur de IconEditor:
#include <QtGui>
#include "iconeditor.h"
IconEditor::IconEditor(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
curColor = Qt::black;
zoom = 8;
image = QImage(16, 16, QImage::Format_ARGB32);
image.fill(qRgba(0, 0, 0, 0));
}

Le constructeur prsente certains aspects subtils, tels que lattribut Qt::WA_StaticContents


et lappel de setSizePolicy(). Nous y reviendrons dans un instant.
La couleur du crayon est dfinie en noir. Le facteur de zoom est de 8, ce qui signifie que
chaque pixel de licne sera affich sous forme dun carr de 8 8.
Les donnes de licne sont stockes dans la variable membre image et sont disponibles par le
biais des fonctions setIconImage() et iconImage(). Un programme dditeur dicnes
appellerait normalement setIconImage() quand lutilisateur ouvre un fichier dicne et

Qt 4 Livre Page 111 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

111

iconImage() pour rcuprer licne quand lutilisateur veut la sauvegarder. La variable


image est de type QImage. Nous linitialisons 16 16 pixels et au format ARGB 32 bits, un
format qui prend en charge la semi-transparence. Nous effaons les donnes de limage en la
remplissant avec une couleur transparente.
La classe QImage stocke une image indpendamment du matriel. Elle peut tre dfinie avec
une qualit de 1, 8 ou 32 bits. Une image avec une qualit de 32 bits utilise 8 bits pour chaque
composante rouge, vert et bleu dun pixel. Les 8 bits restants stockent le canal alpha du pixel
(opacit). Par exemple, les composantes rouge, vert, bleu et alpha dune couleur rouge pure
prsentent les valeurs 255, 0, 0 et 255. Dans Qt, cette couleur peut tre spcifie comme telle :
QRgb red = qRgba(255, 0, 0, 255);

ou, tant donn que la couleur est opaque, comme


QRgb red = qRgb(255, 0, 0);

QRgb est simplement le typedef dun type unsigned int, et qRgb() et qRgba() sont des
fonctions en ligne qui combinent leurs arguments en une valeur entire 32 bits. Il est aussi
possible dcrire
QRgb red = 0xFFFF0000;

o le premier FF correspond au canal alpha et le second FF la composante rouge. Dans le


constructeur de IconEditor, nous remplissons QImage avec une couleur transparente en utilisant
0 comme canal alpha.
Qt propose deux types permettant de stocker les couleurs : QRgb et QColor. Alors que QRgb est
un simple typedef employ dans QImage pour stocker les donnes 32 bits du pixel, QColor est une
classe dote de nombreuses fonctions pratiques qui est souvent utilise dans Qt pour stocker
des couleurs. Dans le widget IconEditor, nous employons uniquement QRgb lorsque nous
travaillons avec QImage; nous utilisons QColor pour tout le reste, notamment la proprit
penColor.
QSize IconEditor::sizeHint() const
{
QSize size = zoom * image.size();
if (zoom >= 3)
size += QSize(1, 1);
return size;
}

La fonction sizeHint() est rimplmente dans QWidget et retourne la taille idale dun
widget. Dans ce cas, nous recevons la taille de limage multiplie par le facteur de zoom, avec
un pixel supplmentaire dans chaque direction pour sadapter une grille si le facteur de zoom
est de 3 ou plus. (Nous naffichons pas de grille si le facteur de zoom est de 2 ou 1, parce
quelle ne laisserait presque pas de place pour les pixels de licne.)

Qt 4 Livre Page 112 Jeudi, 7. dcembre 2006 12:14 12

112

Qt4 et C++ : Programmation dinterfaces GUI

La taille requise dun widget est utile dans la plupart des cas lorsquelle est associe aux dispositions. Les gestionnaires de disposition de Qt essaient au maximum de respecter cette taille
quand ils disposent les widgets enfants dun formulaire. Pour que IconEditor se comporte
correctement, il doit signaler une taille requise crdible.
En plus de cette taille requise, la taille des widgets suit une stratgie qui indique au systme de
disposition sils peuvent tre tirs ou rtrcis. En appelant setSizePolicy() dans le constructeur
avec les stratgies horizontale et verticale QSizePolicy::Minimum, tout gestionnaire de
disposition responsable de ce widget sait que la taille requise de ce dernier correspond vraiment sa taille minimale. En dautres termes, le widget peut tre tir si ncessaire, mais ne
doit jamais tre rtrci une taille infrieure la taille requise. Vous pouvez annuler ce
comportement dans le Qt Designer en configurant la proprit sizePolicy du widget.
La signification des diverses stratgies lies la taille est explique au Chapitre 6.
void IconEditor::setPenColor(const QColor &newColor)
{
curColor = newColor;
}

La fonction setPenColor() dfinit la couleur du crayon. La couleur sera utilise pour les
pixels que vous dessinerez.
void IconEditor::setIconImage(const QImage &newImage)
{
if (newImage!= image) {
image = newImage.convertToFormat(QImage::Format_ARGB32);
update();
updateGeometry();
}
}

La fonction setIconImage() dtermine limage modifier. Nous invoquons convertToFormat() pour obtenir une image 32 bits avec une mmoire tampon alpha si elle nest pas
dans ce format. Ailleurs dans le code, nous supposerons que les donnes de limage sont stockes
sous forme de valeurs ARGB 32 bits.
Aprs avoir configur la variable image, nous appelons QWidget::update() pour forcer le
rafrachissement de laffichage du widget avec la nouvelle image. Nous invoquons ensuite
QWidget::updateGeometry() pour informer toute disposition qui contient le widget que
la taille requise du widget a chang. La disposition sadaptera automatiquement cette
nouvelle taille.
void IconEditor::setZoomFactor(int newZoom)
{
if (newZoom < 1)
newZoom = 1;
if (newZoom!= zoom) {
zoom = newZoom;

Qt 4 Livre Page 113 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

113

update();
updateGeometry();
}
}

La fonction setZoomFactor() dfinit le facteur de zoom de limage. Pour viter une division
par zro, nous corrigeons toute valeur infrieure 1. A nouveau, nous appelons update() et
updateGeometry() pour actualiser laffichage du widget et pour informer tout gestionnaire
de disposition de la modification de la taille requise.
Les fonctions penColor(), iconImage() et zoomFactor() sont implmentes en tant que
fonctions en ligne dans le fichier den-tte.
Nous allons maintenant passer en revue le code de la fonction paintEvent(). Cette fonction
est la fonction la plus importante de IconEditor. Elle est invoque ds que le widget a besoin
dtre redessin. Limplmentation par dfaut dans QWidget na aucune consquence, le
widget reste vide.
Tout comme closeEvent(), que nous avons rencontr dans le Chapitre 3, paintEvent() est
un gestionnaire dvnements. Qt propose de nombreux autres gestionnaires dvnements,
chacun deux correspondant un type diffrent dvnement. Le Chapitre 7 aborde en dtail le
traitement des vnements.
Il existe beaucoup de situations o un vnement paint est dclench et o paintEvent()
est appel :
Quand un widget est affich pour la premire fois, le systme gnre automatiquement un
vnement paint pour obliger le widget se dessiner lui-mme.
Quand un widget est redimensionn, le systme dclenche un vnement paint.
Si le widget est masqu par une autre fentre, puis affich nouveau, un vnement paint
est dclench pour la zone qui tait masque ( moins que le systme de fentrage ait
stock la zone).
Nous avons aussi la possibilit de forcer un vnement paint en appelant QWidget::update() ou QWidget::repaint(). La diffrence entre ces deux fonctions est que repaint()
impose un rafrachissement immdiat de laffichage, alors que update() planifie simplement
un vnement paint pour le prochain traitement dvnements de Qt. (Ces deux fonctions ne
font rien si le widget nest pas visible lcran.) Si update() est invoqu plusieurs fois, Qt
compresse les vnements paint conscutifs en un seul vnement paint pour viter le
phnomne du scintillement. Dans IconEditor, nous utilisons toujours update().
Voici le code :
void IconEditor::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
if (zoom >= 3) {
painter.setPen(palette().foreground().color());
for (int i = 0; i <= image.width(); ++i)

Qt 4 Livre Page 114 Jeudi, 7. dcembre 2006 12:14 12

114

Qt4 et C++ : Programmation dinterfaces GUI

painter.drawLine(zoom * i, 0,
zoom * i, zoom * image.height());
for (int j = 0; j <= image.height(); ++j)
painter.drawLine(0, zoom * j,
zoom * image.width(), zoom * j);
}
for (int i = 0; i < image.width(); ++i) {
for (int j = 0; j < image.height(); ++j) {
QRect rect = pixelRect(i, j);
if (!event->region().intersect(rect).isEmpty()) {
QColor color = QColor::fromRgba(image.pixel(i, j));
painter.fillRect(rect, color);
}
}
}
}

Nous commenons par construire un objet QPainter sur le widget. Si le facteur de zoom est
de 3 ou plus, nous dessinons des lignes horizontales et verticales qui forment une grille laide
de la fonction QPainter::drawLine().
Un appel de QPainter::drawLine() prsente la syntaxe suivante :
painter.drawLine(x1, y1, x2, y2);

o (x1, y1) est la position dune extrmit de la ligne et (x2, y2) la position de lautre
extrmit. Il existe galement une version surcharge de la fonction qui reoit deux QPoint au
lieu de quatre int.
Le pixel en haut gauche dun widget Qt se situe la position (0, 0), et le pixel en bas droite
se trouve (width() 1, height() 1). Cela ressemble au systme traditionnel de coordonnes cartsiennes, mais lenvers. Nous avons la possibilit de modifier le systme de
coordonnes de QPainter grce aux transformations, comme la translation, la mise
lchelle, la rotation et le glissement. Ces notions sont abordes au Chapitre 8 (Graphiques 2D
et 3D).
Figure 5.3
Tracer une ligne
avec QPainter

(0, 0)
(x1, y1)

(x2, y2)
(x

(width() -1,height() -1)

Qt 4 Livre Page 115 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

115

Avant dappeler drawLine() sur QPainter, nous dfinissons la couleur de la ligne au moyen
de setPen(). Nous pourrions coder une couleur, comme noir ou gris, mais il est plus judicieux
dutiliser la palette du widget.
Chaque widget est dot dune palette qui spcifie quelles couleurs doivent tre utilises selon
les situations. Par exemple, il existe une entre dans la palette pour la couleur darrire-plan
des widgets (gnralement gris clair) et une autre pour la couleur du texte sur ce fond (habituellement noir). Par dfaut, la palette dun widget adopte le modle de couleur du systme de
fentrage. En utilisant des couleurs de la palette, nous sommes srs que IconEditor respecte
les prfrences de lutilisateur.
La palette dun widget consiste en trois groupes de couleurs : active, inactive et disabled.
Vous choisirez le groupe de couleurs en fonction de ltat courant du widget :
Le groupe Active est employ pour des widgets situs dans la fentre actuellement active.
Le groupe Inactive est utilis pour les widgets des autres fentres.
Le groupe Disabled est utilis pour les widgets dsactivs dans nimporte quelle fentre.
La fonction QWidget::palette() retourne la palette du widget sous forme dobjet QPalette.
Les groupes de couleurs sont spcifis comme des numrations de type QPalette::ColorGroup.
Lorsque nous avons besoin dun pinceau ou dune couleur approprie pour dessiner, la bonne
approche consiste utiliser la palette courante, obtenue partir de QWidget::palette(), et
le rle requis, par exemple, QPalette::foreground(). Chaque fonction de rle retourne un
pinceau, qui correspond normalement ce que nous souhaitons, mais si nous navons besoin
que de la couleur, nous pouvons lextraire du pinceau, comme nous avons fait dans paintEvent(). Par dfaut, les pinceaux retourns sont adapts ltat du widget, nous ne sommes
donc pas forcs de spcifier un groupe de couleurs.
La fonction paintEvent() termine en dessinant limage elle-mme. Lappel de IconEditor::pixelRect() retourne un QRect qui dfinit la rgion redessiner. Pour une question
doptimisation simple, nous ne redessinons pas les pixels qui se trouvent en dehors de cette
rgion.
Figure 5.4
Dessiner un rectangle
avec QPainter

(0, 0)
(x, y)
h
w
(width() -1,height() -1)

Qt 4 Livre Page 116 Jeudi, 7. dcembre 2006 12:14 12

116

Qt4 et C++ : Programmation dinterfaces GUI

Nous invoquons QPainter::fillRect() pour dessiner un pixel sur lequel un zoom a t


effectu. QPainter::fillRect() reoit un QRect et un QBrush. En transmettant QColor
comme pinceau, nous obtenons un modle de remplissage correct.
QRect IconEditor::pixelRect(int i, int j) const
{
if (zoom >= 3) {
return QRect(zoom * i + 1, zoom * j + 1, zoom - 1, zoom - 1);
} else {
return QRect(zoom * i, zoom * j, zoom, zoom);
}
}

La fonction pixelRect() retourne un QRect adapt QPainter::fillRect(). Les paramtres i et j sont les coordonnes du pixel dans QImage pas dans le widget. Si le facteur de
zoom est de 1, les deux systmes de coordonnes concident parfaitement.
Le constructeur de QRect suit la syntaxe QRect(x, y, width, height), o (x, y) est la
position du coin suprieur gauche du rectangle et width_height correspond la taille du
rectangle. Si le facteur de zoom est de 3 ou plus, nous rduisons la taille du rectangle dun pixel
horizontalement et verticalement, de sorte que le remplissage ne dborde pas sur les lignes de la
grille.
void IconEditor::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
setImagePixel(event->pos(), true);
} else if (event->button() == Qt::RightButton) {
setImagePixel(event->pos(), false);
}
}

Quand lutilisateur appuie sur un bouton de la souris, le systme dclenche un vnement


"bouton souris enfonc". En rimplmentant QWidget::mousePressEvent(), nous avons la
possibilit de rpondre cet vnement et de dfinir ou effacer le pixel de limage sous le pointeur
de la souris.
Si lutilisateur a appuy sur le bouton gauche de la souris, nous appelons la fonction prive
setImagePixel() avec true comme second argument, lui demandant de dfinir le pixel dans
la couleur actuelle du crayon. Si lutilisateur a appuy sur le bouton droit de la souris, nous
invoquons aussi setImagePixel(), mais nous transmettons false pour effacer le pixel.
void IconEditor::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
setImagePixel(event->pos(), true);
} else if (event->buttons() & Qt::RightButton) {
setImagePixel(event->pos(), false);
}
}

Qt 4 Livre Page 117 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

117

mouseMoveEvent() gre les vnements "dplacement de souris".Par dfaut, ces vnements


ne sont dclenchs que lorsque lutilisateur enfonce un bouton. Il est possible de changer ce
comportement en appelant QWidget::setMouseTracking(), mais nous navons pas besoin
dagir de la sorte dans cet exemple.
Tout comme le fait dappuyer sur les boutons droit ou gauche de la souris configure ou efface
un pixel, garder ce bouton enfonc et se placer sur un pixel suffit aussi dfinir ou supprimer
un pixel. Vu quil est possible de maintenir enfonc plus dun bouton la fois, la valeur retourne par QMouseEvent::buttons() est un oprateur OR bit bit des boutons de la souris.
Nous testons si un certain bouton est enfonc laide de loprateur &, et si cest le cas, nous
invoquons setImagePixel().
void IconEditor::setImagePixel(const QPoint &pos, bool opaque)
{
int i = pos.x() / zoom;
int j = pos.y() / zoom;
if (image.rect().contains(i, j)) {
if (opaque) {
image.setPixel(i, j, penColor().rgba());
} else {
image.setPixel(i, j, qRgba(0, 0, 0, 0));
}
update(pixelRect(i, j));
}
}

La fonction setImagePixel() est appele depuis mousePressEvent() et mouseMoveEvent() pour dfinir ou effacer un pixel. Le paramtre pos correspond la position de la
souris dans le widget.
La premire tape consiste convertir la position de la souris dans les coordonnes du widget
vers les coordonnes de limage. Pour ce faire, les composants x() et y() de la position de la
souris sont diviss par le facteur de zoom. Puis nous vrifions si le point se trouve dans une
plage correcte. Ce contrle seffectue facilement en utilisant QImage::rect() et
QRect::contains(); vous vrifiez ainsi que i se situe entre 0 et image.width() 1 et que j
est entre 0 et image.height() 1.
Selon le paramtre opaque, nous dfinissons ou nous effaons le pixel dans limage. Effacer un
pixel consiste le rendre transparent. Nous devons convertir le crayon QColor en une valeur
ARGB 32 bits pour lappel de QImage::setPixel(). Nous terminons en appelant update()
avec un QRect de la zone qui doit tre redessine.
Maintenant que nous avons analys les fonctions membres, nous allons retourner lattribut
Qt::WA_StaticContents que nous avons utilis dans le constructeur. Cet attribut informe Qt
que le contenu du widget ne change pas quand le widget est redimensionn et que le contenu
reste ancr dans le coin suprieur gauche du widget. Qt se sert de ces informations pour viter

Qt 4 Livre Page 118 Jeudi, 7. dcembre 2006 12:14 12

118

Qt4 et C++ : Programmation dinterfaces GUI

tout retraage inutile des zones qui sont dj affiches au moment du redimensionnement du
widget.
Normalement, quand un widget est redimensionn, Qt dclenche un vnement paint pour
toute la zone visible du widget (voir Figure 5.5). Mais si le widget est cr avec lattribut
Qt::WA_StaticContents, la rgion de lvnement paint se limite aux pixels qui ntaient
pas encore affichs auparavant. Ceci implique que si le widget est redimensionn dans une
taille plus petite, aucun vnement paint ne sera dclench.
Figure 5.5
Redimensionner
un widget
Qt::WA_StaticContents

Le widget IconEditor est maintenant termin. Grce aux informations et aux exemples des
chapitres prcdents, nous pourrions crire un code qui utilise IconEditor comme une vritable fentre, comme un widget central dans QMainWindow, comme un widget enfant dans une
disposition ou comme un widget enfant dans QScrollArea. Dans la prochaine section, nous
verrons comment lintgrer avec le Qt Designer.

Intgrer des widgets personnaliss avec le Qt


Designer
Avant de pouvoir utiliser des widgets personnaliss dans le Qt Designer, celui-ci doit en avoir
connaissance. Il existe deux techniques : la "promotion" et le plug-in.
Lapproche de la promotion est la plus rapide et la plus simple. Elle consiste choisir un
widget Qt intgr qui possde une API similaire celle que nous voulons pour notre widget
personnalis, puis saisir quelques informations propos de ce widget personnalis dans une
bote de dialogue du Qt Designer. Le widget peut ensuite tre exploit dans des formulaires
dvelopps avec le Qt Designer, mme sil sera reprsent par le widget Qt intgr associ lors
de ldition ou de la prvisualisation du formulaire.
Voici comment insrer un widget HexSpinBox dans un formulaire avec cette approche :
1. Crez un QSpinBox en le faisant glisser depuis la bote des widgets du Qt Designer vers le
formulaire.
2. Cliquez du bouton droit sur le pointeur toupie et slectionnez Promote to Custom Widget
dans le menu contextuel.
3. Compltez la bote de dialogue qui souvre avec "HexSpinBox" comme nom de classe et
"hexspinbox.h" comme fichier den-tte (voir Figure 5.6).

Qt 4 Livre Page 119 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

119

Voil ! Le code gnr par uic contiendra hexspinbox.h au lieu de <QSpinBox> et instanciera un HexSpinBox. Dans le Qt Designer, le widget HexSpinBox sera reprsent par un
QSpinBox, ce qui nous permet de dfinir toutes les proprits dun QSpinBox (par exemple, la
plage et la valeur actuelle).
Figure 5.6
La bote de dialogue
du widget personnalis
dans le Qt Designer

Les inconvnients de lapproche de la promotion sont que les proprits spcifiques au widget
personnalis ne sont pas accessibles dans le Qt Designer et que le widget nest pas affich en
tant que tel. Ces deux problmes peuvent tre rsolus en utilisant lapproche du plug-in.
Lapproche du plug-in ncessite la cration dune bibliothque de plug-in que le Qt Designer
peut charger lexcution et utiliser pour crer des instances du widget. Le vritable widget est
ensuite employ par le Qt Designer pendant la modification du formulaire et la prvisualisation, et grce au systme de mta-objets de Qt, le Qt Designer peut obtenir dynamiquement la
liste de ses proprits. Pour voir comment cela fonctionne, nous intgrerons le widget
IconEditor de la section prcdente comme plug-in.
Nous devons dabord driver QDesignerCustomWidgetInterface et rimplmenter certaines fonctions virtuelles. Nous supposerons que le code source du plug-in se situe dans un
rpertoire appel iconeditorplugin et que le code source de IconEditor se trouve dans
un rpertoire parallle nomm iconeditor.
Voici la dfinition de classe :
#include <QDesignerCustomWidgetInterface>
class IconEditorPlugin: public QObject,
public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
IconEditorPlugin(QObject *parent = 0);
QString name() const;
QString includeFile() const;
QString group() const;
QIcon icon() const;
QString toolTip() const;

Qt 4 Livre Page 120 Jeudi, 7. dcembre 2006 12:14 12

120

Qt4 et C++ : Programmation dinterfaces GUI

QString whatsThis() const;


bool isContainer() const;
QWidget *createWidget(QWidget *parent);
};

La sous-classe IconEditorPlugin est une classe spcialise qui encapsule le widget


IconEditor. Elle hrite de QObject et de QDesignerCustomWidgetIterface et se sert de
la macro Q_INTERFACES() pour signaler moc que la seconde classe de base est une interface
de plug-in. Les fonctions sont utilises par le Qt Designer pour crer des instances de la classe
et obtenir des informations son sujet.
IconEditorPlugin::IconEditorPlugin(QObject *parent)
: QObject(parent)
{
}

Le constructeur est trs simple.


QString IconEditorPlugin::name() const
{
return "IconEditor";
}

La fonction name() retourne le nom du widget fourni par le plug-in.


QString IconEditorPlugin::includeFile() const
{
return "iconeditor.h";
}

La fonction includeFile() retourne le nom du fichier den-tte pour le widget spcifi


encapsul par le plug-in. Le fichier den-tte se trouve dans le code gnr par loutil uic.
QString IconEditorPlugin::group() const
{
return tr("Image Manipulation Widgets");
}

La fonction group() retourne le nom du groupe de widgets auquel doit appartenir ce widget
personnalis. Si le nom nest pas encore utilis, le Qt Designer crera un nouveau groupe pour
le widget.
QIcon IconEditorPlugin::icon() const
{
return QIcon(":/images/iconeditor.png");
}

La fonction icon() renvoie licne utiliser pour reprsenter le widget personnalis dans la
bote des widgets du Qt Designer. Dans notre cas, nous supposons que IconEditorPlugin
possde un fichier de ressources Qt associ avec une entre adapte pour limage de lditeur
dicnes.

Qt 4 Livre Page 121 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

121

QString IconEditorPlugin::toolTip() const


{
return tr("An icon editor widget");
}

La fonction toolTip() renvoie linfobulle afficher quand la souris se positionne sur le


widget personnalis dans la bote des widgets du Qt Designer.
QString IconEditorPlugin::whatsThis() const
{
return tr("This widget is presented in Chapter 5 of <i>C++ GUI "
"Programming with Qt 4</i> as an example of a custom Qt "
"widget.");
}

La fonction whatsThis() retourne le texte "Whats this ?" que le Qt Designer doit afficher.
bool IconEditorPlugin::isContainer() const
{
return false;
}

La fonction isContainer() retourne true si le widget peut contenir dautres widgets ; sinon
elle retourne false. Par exemple, QFrame est un widget qui peut comporter dautres widgets.
En gnral, tout widget Qt peut renfermer dautres widgets, mais le Qt Designer ne lautorise
pas quand isContainer() renvoie false.
QWidget *IconEditorPlugin::createWidget(QWidget *parent)
{
return new IconEditor(parent);
}

La fonction create() est invoque par le Qt Designer pour crer une instance dune classe de
widget avec le parent donn.
Q_EXPORT_PLUGIN2(iconeditorplugin, IconEditorPlugin)

A la fin du fichier source qui implmente la classe de plug-in, nous devons utiliser la macro
Q_EXPORT_PLUGIN2() pour que le Qt Designer puisse avoir accs au plug-in. Le premier
argument est le nom que nous souhaitons attribuer au plug-in ; le second argument est le nom
de la classe qui limplmente.
Voici le code dun fichier .pro permettant de gnrer le plug-in :
TEMPLATE
CONFIG
HEADERS
SOURCES
RESOURCES

= lib
+= designer plugin release
= ../iconeditor/iconeditor.h \
iconeditorplugin.h
= ../iconeditor/iconeditor.cpp \
iconeditorplugin.cpp
= iconeditorplugin.qrc

Qt 4 Livre Page 122 Jeudi, 7. dcembre 2006 12:14 12

122

Qt4 et C++ : Programmation dinterfaces GUI

DESTDIR

= $(QTDIR)/plugins/designer

Le fichier .pro suppose que la variable denvironnement QTDIR contient le rpertoire o Qt


est install. Quand vous tapez make ou nmake pour gnrer le plug-in, il sinstallera automatiquement dans le rpertoire plugins du Qt Designer. Une fois le plug-in gnr, le widget
IconEditor peut tre utilis dans le Qt Designer de la mme manire que nimporte quel
autre widget intgr de Qt.
Si vous voulez intgrer plusieurs widgets personnaliss avec le Qt Designer, vous avez la
possibilit soit de crer un plug-in pour chacun deux, soit de les combiner dans un seul plug-in en
drivant QDesignerCustomWidgetCollectionInterface.

Double mise en mmoire tampon


La double mise en mmoire tampon est une technique de programmation GUI qui consiste
afficher un widget dans un pixmap hors champ puis copier le pixmap lcran. Avec les
versions antrieures de Qt, cette technique tait frquemment utilise pour liminer le phnomne du scintillement et pour offrir une interface utilisateur plus confortable.
Dans Qt 4, QWidget gre ce phnomne automatiquement, nous sommes donc rarement obligs de nous soucier du scintillement des widgets. La double mise en mmoire tampon explicite
reste tout de mme avantageuse si le rendu du widget est complexe et doit tre ralis de faon
rptitive. Nous pouvons alors stocker un pixmap de faon permanente avec le widget,
toujours prt pour le prochain vnement paint, et copier le pixmap dans le widget ds que
nous dtectons cet vnement paint. Il se rvle particulirement utile si nous souhaitons
effectuer de lgres modifications, comme dessiner un rectangle de slection, sans avoir
recalculer chaque fois le rendu complet du widget.
Nous allons clore ce chapitre en tudiant le widget personnalis Plotter. Ce widget utilise la
double mise en mmoire tampon et illustre galement certains aspects de la programmation Qt,
notamment la gestion des vnements du clavier, la disposition manuelle et les systmes de
coordonnes.
Le widget Plotter affiche une ou plusieurs courbes spcifies sous forme de vecteurs de
coordonnes. Lutilisateur peut tracer un rectangle de slection sur limage et Plotter
zoomera sur la zone dlimite par ce trac (voir Figure 5.7). Lutilisateur dessine le rectangle
en cliquant un endroit dans le graphique, en faisant glisser la souris vers une autre position en
maintenant le bouton gauche enfonc puis en relchant le bouton de la souris.
Lutilisateur peut zoomer de manire rpte en traant des rectangles de slection plusieurs
fois, faire un zoom arrire grce au bouton Zoom Out, puis zoomer nouveau au moyen du
bouton Zoom In. Les boutons Zoom In et Zoom Out apparaissent la premire fois quils
deviennent accessibles, ils nencombrent donc pas lcran si lutilisateur ne fait pas de zoom
sur le graphique.

Qt 4 Livre Page 123 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

123

Figure 5.7
Zoomer sur le
widget Plotter

Le widget Plotter peut enregistrer les donnes de nombreuses courbes. Il assure aussi la
maintenance dune pile dobjets PlotSettings, chacun deux correspondant un niveau
particulier de zoom.
Analysons dsormais la classe, en commenant par plotter.h:
#ifndef PLOTTER_H
#define PLOTTER_H
#include
#include
#include
#include

<QMap>
<QPixmap>
<QVector>
<QWidget>

class QToolButton;
class PlotSettings;
class Plotter: public QWidget
{
Q_OBJECT
public:
Plotter(QWidget *parent = 0);
void setPlotSettings(const PlotSettings &settings);
void setCurveData(int id, const QVector<QPointF> &data);
void clearCurve(int id);
QSize minimumSizeHint() const;
QSize sizeHint() const;
public slots:
void zoomIn();
void zoomOut();

Nous commenons par inclure les fichiers den-tte des classes Qt utilises dans len-tte du
fichier du traceur (plotter) puis nous dclarons les classes dsignes par des pointeurs ou des
rfrences dans len-tte.

Qt 4 Livre Page 124 Jeudi, 7. dcembre 2006 12:14 12

124

Qt4 et C++ : Programmation dinterfaces GUI

Dans la classe Plotter, nous fournissons trois fonctions publiques pour configurer le trac et
deux slots publics pour faire des zooms avant et arrire. Nous rimplmentons aussi minimumSizeHint() et sizeHint() dans QWidget. Nous enregistrons les points dune courbe sous
forme de QVector<QPointF>, o QPointF est la version virgule flottante de QPoint.
protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void wheelEvent(QWheelEvent *event);

Dans la section protge de la classe, nous dclarons tous les gestionnaires dvnements de
QWidget que nous dsirons rimplmenter.
private:
void updateRubberBandRegion();
void refreshPixmap();
void drawGrid(QPainter *painter);
void drawCurves(QPainter *painter);
enum { Margin = 50 };
QToolButton *zoomInButton;
QToolButton *zoomOutButton;
QMap<int, QVector<QPointF> > curveMap;
QVector<PlotSettings> zoomStack;
int curZoom;
bool rubberBandIsShown;
QRect rubberBandRect;
QPixmap pixmap;
};

Dans la section prive de la classe, nous dclarons quelques fonctions pour dessiner le widget,
une constante et quelques variables membres. La constante Margin sert introduire un peu
despace autour du graphique.
Parmi les variables membres, on compte un pixmap de type QPixmap. Cette variable conserve
une copie du rendu de tout le widget, identique celui affich lcran. Le trac est toujours
dessin sur ce pixmap dabord hors champ ; puis le pixmap est copi dans le widget.
class PlotSettings
{
public:
PlotSettings();
void scroll(int dx, int dy);
void adjust();
double spanX() const { return maxX - minX; }

Qt 4 Livre Page 125 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

125

double spanY() const { return maxY - minY; }


double minX;
double maxX;
int numXTicks;
double minY;
double maxY;
int numYTicks;
private:
static void adjustAxis(double &min, double &max, int &numTicks);
};
#endif

La classe PlotSettings spcifie la plage des axes x et y et le nombre de graduations pour ces
axes. La Figure 5.8 montre la correspondance entre un objet PlotSettings et un widget
Plotter.
Par convention, numXTicks et numYTicks ont une unit de moins ; si numXTicks a la valeur 5,
Plotter dessinera 6 graduations sur laxe x. Cela simplifie les calculs par la suite.

maxY

numYTicks

Figure 5.8
Les variables membres
de PlotSettings

numXTicks
minY
minX

maxX

Analysons prsent le fichier dimplmentation :


#include <QtGui>
#include <cmath>
#include "plotter.h"

Nous incluons les fichiers den-ttes prvus et nous importons tous les symboles de lespace de
noms std dans lespace de noms global. Ceci nous permet daccder aux fonctions dclares
dans <cmath> sans les prfixer avec std:: (par exemple, floor() au lieu de std::floor()).
Plotter::Plotter(QWidget *parent)
: QWidget(parent)
{
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

Qt 4 Livre Page 126 Jeudi, 7. dcembre 2006 12:14 12

126

Qt4 et C++ : Programmation dinterfaces GUI

setFocusPolicy(Qt::StrongFocus);
rubberBandIsShown = false;
zoomInButton = new QToolButton(this);
zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
zoomInButton->adjustSize();
connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));
zoomOutButton = new QToolButton(this);
zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
zoomOutButton->adjustSize();
connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));
setPlotSettings(PlotSettings());
}

Lappel de setBackgroundRole() demande QWidget dutiliser le composant "dark" de la


palette comme couleur pour effacer le widget, la place du composant "window".Qt se voit
donc attribuer une couleur par dfaut quil peut employer pour remplir nimporte quel pixel
nouvellement affich quand le widget est redimensionn dans une taille plus grande, avant
mme que paintEvent() nait lopportunit de dessiner les nouveaux pixels. Nous devons
aussi invoquer setAutoFillBackground(true) dans le but dactiver ce mcanisme.
(Par dfaut, les widgets enfants hritent de larrire-plan de leur widget parent.)
Lappel de setSizePolicy() dfinit la stratgie de taille du widget en QSizePolicy::
Expanding dans les deux directions. Tout gestionnaire de disposition responsable du widget
sait donc que ce dernier peut tre agrandi, mais peut aussi tre rtrci. Ce paramtre est typique
des widgets qui peuvent prendre beaucoup de place lcran. La valeur par dfaut est QSizePolicy::Preferred dans les deux directions, ce qui signifie que le widget prfre tre sa
taille requise, mais quil peut tre rtrci sa taille minimale ou agrandi linfini si ncessaire.
Lappel de setFocusPolicy(Qt::StrongFocus) permet au widget de recevoir le focus lorsque
lutilisateur clique ou appuie sur Tab. Quand le Plotter est actif, il recevra les vnements
lis aux touches du clavier enfonces. Le widget Plotter ragit quelques touches : + pour
un zoom avant ; pour un zoom arrire ; et les flches directionnelles pour faire dfiler vers la
droite ou la gauche, le haut ou le bas (voir Figure 5.9).
Figure 5.9
Faire dfiler
le widget
Plotter

Qt 4 Livre Page 127 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

127

Toujours dans le constructeur, nous crons deux QToolButton, chacun avec une icne. Ces
boutons permettent lutilisateur de faire des zooms avant et arrire. Les icnes du bouton sont
stockes dans un fichier de ressources, donc toute application qui utilise le widget Plotter
aura besoin de cette entre dans son fichier .pro:
RESOURCES = plotter.qrc

Le fichier de ressources ressemble celui que nous avons utilis pour lapplication Spreadsheet :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/zoomin.png</file>
<file>images/zoomout.png</file>
</qresource>
</RCC>

Les appels de adjustSize() sur les boutons dfinissent leurs tailles de sorte quelles correspondent la taille requise. Les boutons ne font pas partie dune disposition ; nous les positionnerons manuellement dans lvnement resize de Plotter. Vu que nous ne nous servons pas
des dispositions, nous devons spcifier explicitement le parent des boutons en le transmettant
au constructeur de QPushButton.
Lappel de setPlotSettings() la fin termine linitialisation.
void Plotter::setPlotSettings(const PlotSettings &settings)
{
zoomStack.clear();
zoomStack.append(settings);
curZoom = 0;
zoomInButton->hide();
zoomOutButton->hide();
refreshPixmap();
}

La fonction setPlotSettings() est employe pour spcifier le PlotSettings utiliser


pour afficher le trac. Elle est appele par le constructeur Plotter et peut tre employe par
des utilisateurs de la classe. Le traceur commence son niveau de zoom par dfaut. A chaque
fois que lutilisateur fait un zoom avant, une nouvelle instance de PlotSettings est cre et
place sur la pile de zoom. La pile de zoom est reprsente par deux variables membres :

zoomStack contient les divers paramtres de zoom sous forme de QVector<PlotSettings>.


curZoom comporte lindex du PlotSettings actuel dans zoomStack.
Aprs lappel de setPlotSettings(), la pile de zoom ne contient quune seule entre et
les boutons Zoom In et Zoom Out sont masqus. Ces boutons ne safficheront que lorsque
nous appellerons show() sur eux dans les slots zoomIn() et zoomOut(). (Normalement il
suffit dinvoquer show() sur le widget de niveau suprieur pour afficher tous les enfants.

Qt 4 Livre Page 128 Jeudi, 7. dcembre 2006 12:14 12

128

Qt4 et C++ : Programmation dinterfaces GUI

Mais quand nous appelons explicitement hide() sur un widget enfant, il est masqu jusqu ce
nous appelions nouveau show() sur ce widget.)
Lappel de refreshPixmap() est ncessaire pour mettre jour laffichage. Normalement,
nous invoquerions update(), mais dans ce cas, nous agissons lgrement diffremment parce
que nous voulons conserver un QPixmap toujours mis jour. Aprs avoir rgnr le pixmap,
refreshPixmap() appelle update() pour copier le pixmap dans le widget.
void Plotter::zoomOut()
{
if (curZoom > 0) {
--curZoom;
zoomOutButton->setEnabled(curZoom > 0);
zoomInButton->setEnabled(true);
zoomInButton->show();
refreshPixmap();
}
}

Le slot zoomOut() fait un zoom arrire si vous avez dj zoom sur le graphique. Il dcrmente le niveau actuel de zoom et active le bouton Zoom Out selon quil est encore possible de
faire un zoom arrire ou pas. Le bouton Zoom In est activ et affich, et laffichage est mis
jour avec un appel de refreshPixmap().
void Plotter::zoomIn()
{
if (curZoom < zoomStack.count() - 1) {
++curZoom;
zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
zoomOutButton->setEnabled(true);
zoomOutButton->show();
refreshPixmap();
}
}

Si lutilisateur a fait un zoom avant puis un zoom arrire, le PlotSettings du prochain


niveau de zoom sera dans la pile de zoom et nous pourrons zoomer. (Sinon, il est toujours
possible de faire un zoom avant avec un rectangle de slection.)
Le slot incrmente curZoom pour descendre dun niveau dans la pile de zoom, active ou dsactive le bouton Zoom In selon quil est possible de faire encore un zoom avant ou non, et active
et affiche le bouton Zoom Out. A nouveau, nous appelons refreshPixmap() pour que le traceur
utilise les derniers paramtres du zoom.
void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
curveMap[id] = data;
refreshPixmap();
}

Qt 4 Livre Page 129 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

129

La fonction setCurveData() dfinit les donnes de courbe pour un ID de courbe donn. Sil
existe dj une courbe portant le mme ID dans curveMap, elle est remplace par les nouvelles
donnes de courbe ; sinon, la nouvelle courbe est simplement insre. La variable membre
curveMap est de type QMap<int,QVector<QPointF>>.
void Plotter::clearCurve(int id)
{
curveMap.remove(id);
refreshPixmap();
}

La fonction clearCurve() supprime la courbe spcifie dans curveMap.


QSize Plotter::minimumSizeHint() const
{
return QSize(6 * Margin, 4 * Margin);
}

La fonction minimumSizeHint() est similaire sizeHint(); tout comme sizeHint() spcifie la taille idale dun widget, minimumSizeHint() spcifie la taille minimale idale dun widget.
Une disposition ne redimensionne jamais un widget en dessous de sa taille requise minimale.
La valeur que nous retournons est 300 _ 200 (vu que Margin est gal 50) pour laisser une
marge des quatre cts et un peu despace pour le trac. En dessous de cette taille, le trac
serait trop petit pour tre utile.
QSize Plotter::sizeHint() const
{
return QSize(12 * Margin, 8 * Margin);
}

Dans sizeHint nous retournons une taille "idale" proportionnelle la constante Margin et
avec le mme format dimage de 3:2 que nous avons utilis pour minimumSizeHint().
Ceci termine lanalyse des slots et des fonctions publiques de Plotter. Etudions prsent les
gestionnaires dvnements protgs.
void Plotter::paintEvent(QPaintEvent * /* event */)
{
QStylePainter painter(this);
painter.drawPixmap(0, 0, pixmap);
if (rubberBandIsShown) {
painter.setPen(palette().light().color());
painter.drawRect(rubberBandRect.normalized()
.adjusted(0, 0, -1, -1));
}
if (hasFocus()) {
QStyleOptionFocusRect option;
option.initFrom(this);
option.backgroundColor = palette().dark().color();

Qt 4 Livre Page 130 Jeudi, 7. dcembre 2006 12:14 12

130

Qt4 et C++ : Programmation dinterfaces GUI

painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}

Normalement, cest dans paintEvent() que nous effectuons toutes les oprations de dessin.
Cependant, dans notre exemple, nous avons dessin tout le trac auparavant dans refreshPixmap(), nous avons donc la possibilit dafficher tout le trac simplement en copiant le
pixmap dans le widget la position (0, 0).
Si le rectangle de slection est visible, nous le dessinons au-dessus du trac. Nous utilisons le
composant "light" du groupe de couleurs actuel du widget comme couleur du crayon pour
garantir un bon contraste avec larrire-plan "dark".Notez que nous dessinons directement sur
le widget, nous ne touchons donc pas au pixmap hors champ. Utiliser QRect::normalized()
vous assure que le rectangle de slection prsente une largeur et une hauteur positives (en
changeant les coordonnes si ncessaire), et adjusted() rduit la taille du rectangle dun
pixel pour tenir compte de son contour dun pixel.
Si le Plotter est activ, un rectangle "de focus" est dessin au moyen de la fonction drawPrimitive() correspondant au style de widget, avec QStyle::PE_FrameFocusRect comme
premier argument et QStyleOptionFocusRect comme second argument. Les options graphiques du rectangle de focus sont hrites du widget Plotter (par lappel de initFrom()). La
couleur darrire-plan doit tre spcifie explicitement.
Si vous voulez dessiner en utilisant le style actuel, vous pouvez appeler directement une fonction
QStyle, par exemple,
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter,
this);

ou utiliser un QStylePainter au lieu dun QPainter normal, comme nous avons procd
dans Plotter. Vous dessinez ainsi plus confortablement.
La fonction QWidget::style() retourne le style qui doit tre utilis pour dessiner le widget.
Dans Qt, le style de widget est une sous-classe de QStyle. Les styles intgrs englobent QWindowsStyle, QWindowsXPStyle, QMotifStyle, QCDEStyle, QMacStyle et QPlastiqueStyle.
Chacun de ces styles rimplmente les fonctions virtuelles dans QStyle afin dadapter le
dessin la plate-forme pour laquelle le style est mul. La fonction drawPrimitive() de
QStylePainter appelle la fonction QStyle du mme nom, qui peut tre employe pour dessiner
des "lments primitifs" comme les panneaux, les boutons et les rectangles de focus. Le style de
widget est gnralement le mme pour tous les widgets dune application (QApplication::style()), mais vous pouvez ladapter au cas par cas laide de QWidget::setStyle().
En drivant QStyle, il est possible de dfinir un style personnalis. Vous pouvez ainsi attribuer
un aspect trs particulier une application ou une suite dapplications. Alors quil est habituellement recommand dadopter laspect et lapparence natifs de la plate-forme cible, Qt offre
une grande flexibilit si vous souhaitez intervenir dans ce domaine.
Les widgets intgrs de Qt se basent presque exclusivement sur QStyle pour se dessiner. Cest
pourquoi ils ressemblent aux widgets natifs sur toutes les plates-formes prises en charge par Qt.

Qt 4 Livre Page 131 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

131

Les widgets personnaliss peuvent adopter le style courant soit en utilisant QStyle pour se
tracer eux-mmes, soit en employant les widgets Qt intgrs comme widgets enfants. Sagissant de Plotter, nous utilisons une combinaison des deux approches : le rectangle de focus
est dessin avec QStyle (via un QStylePainter) et les boutons Zoom In et Zoom Out sont
des widgets Qt intgrs.
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
int x = width() - (zoomInButton->width()
+ zoomOutButton->width() + 10);
zoomInButton->move(x, 5);
zoomOutButton->move(x + zoomInButton->width() + 5, 5);
refreshPixmap();
}

Quand le widget Plotter est redimensionn, Qt dclenche un vnement "resize". Ici, nous
implmentons resizeEvent() pour placer les boutons Zoom In et Zoom Out en haut droite
du widget Plotter.
Nous dplaons les boutons Zoom In et Zoom Out pour quils soient cte cte, spars par un
espace de 5 pixels et dcals de 5 pixels par rapport aux bords suprieur et droit du widget parent.
Si nous avions voulu que les boutons restent ancrs dans le coin suprieur gauche, dont les
coordonnes sont (0, 0), nous les aurions simplement placs cet endroit dans le constructeur
de Plotter. Nanmoins, nous souhaitons assurer le suivi du coin suprieur droit, dont les
coordonnes dpendent de la taille du widget. Cest pour cette raison quil est ncessaire de
rimplmenter resizeEvent() et dy dfinir la position des boutons.
Nous navons pas configur les positions des boutons dans le constructeur de Plotter. Ce
nest pas un problme parce que Qt dclenche toujours un vnement resize avant dafficher
un widget pour la premire fois.
Plutt que de rimplmenter resizeEvent() et de disposer les widgets enfants manuellement, nous aurions pu faire appel un gestionnaire de disposition (par exemple, QGridLayout). Lutilisation dune disposition aurait t un peu plus complique et aurait
consomm davantage de ressources, mais les dispositions de droite gauche aurait t mieux
gres, notamment pour des langues comme larabe et lhbreu.
Nous terminons en invoquant refreshPixmap() pour redessiner le pixmap sa nouvelle taille.
void Plotter::mousePressEvent(QMouseEvent *event)
{
QRect rect(Margin, Margin,
width() - 2 * Margin, height() - 2 * Margin);
if (event->button() == Qt::LeftButton) {
if (rect.contains(event->pos())) {
rubberBandIsShown = true;
rubberBandRect.setTopLeft(event->pos());
rubberBandRect.setBottomRight(event->pos());
updateRubberBandRegion();

Qt 4 Livre Page 132 Jeudi, 7. dcembre 2006 12:14 12

132

Qt4 et C++ : Programmation dinterfaces GUI

setCursor(Qt::CrossCursor);
}
}
}

Quand lutilisateur appuie sur le bouton gauche de la souris, nous commenons afficher un
rectangle de slection. Ceci implique de dfinir rubberBandIsShown en true, dinitialiser la
variable membre rubberBandRect la position actuelle du pointeur de la souris, de planifier
un vnement paint pour tracer le rectangle et de changer le pointeur de la souris pour afficher
un pointeur rticule.
La variable rubberBandRect est de type QRect. Un QRect peut tre dfini soit sous forme
dun quadruple (x, y, width, height) o (x, y) est la position du coin suprieur gauche
et width _ height correspond la taille du rectangle soit comme une paire de coordonnes
suprieur-gauche et infrieur-droit. Dans ce cas, nous avons employ la reprsentation avec
des paires de coordonnes. Nous dfinissons le point o lutilisateur a cliqu la fois comme
tant le coin suprieur gauche et le coin infrieur droit. Puis nous appelons updateRubberBandRegion() pour forcer le rafrachissement de laffichage de la (toute petite) zone couverte
par le rectangle de slection.
Qt propose deux mcanismes pour contrler la forme du pointeur de la souris :

QWidget::setCursor() dfinit la forme du pointeur utiliser quand la souris se place sur


un widget particulier. Si aucun pointeur nest configur pour le widget, cest le pointeur du
widget parent qui est employ. Les widgets de haut niveau proposent par dfaut un pointeur
en forme de flche.

QApplication::setOverrideCursor() dfinit la forme du pointeur pour toute lapplication, ignorant les pointeurs configurs par chaque widget jusqu ce que restoreOverrideCursor() soit invoque.
Dans le Chapitre 4, nous avons appel QApplication::setOverrideCursor() avec
Qt::WaitCursor pour changer le pointeur de lapplication en sablier.

void Plotter::mouseMoveEvent(QMouseEvent *event)


{
if (rubberBandIsShown) {
updateRubberBandRegion();
rubberBandRect.setBottomRight(event->pos());
updateRubberBandRegion();
}
}

Quand lutilisateur dplace le pointeur de la souris alors quil maintient le bouton gauche
enfonc, nous appelons dabord updateRubberBandRegion() pour planifier un vnement
paint afin de redessiner la zone o se trouvait le rectangle de slection, puis nous recalculons
rubberBandRect pour tenir compte du dplacement de la souris, et enfin nous invoquons
updateRubberBandRegion() une deuxime fois pour retracer la zone vers laquelle sest

Qt 4 Livre Page 133 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

133

dplac le rectangle de slection. Ce rectangle est donc effectivement supprim et redessin


aux nouvelles coordonnes.
Si lutilisateur dplace la souris vers le haut ou la gauche, il est probable que le coin infrieur
droit de rubberBandRect se retrouve au-dessus ou gauche de son coin suprieur gauche.
Si cest le cas, QRect aura une largeur ou une hauteur ngative. Nous avons utilis
QRect::normalized() dans paintEvent() pour nous assurer que les coordonnes suprieur-gauche et infrieur-droit sont ajustes de manire ne pas avoir de largeur et de hauteur
ngatives.
void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
if ((event->button() == Qt::LeftButton) && rubberBandIsShown) {
rubberBandIsShown = false;
updateRubberBandRegion();
unsetCursor();
QRect rect = rubberBandRect.normalized();
if (rect.width() < 4 || rect.height() < 4)
return;
rect.translate(-Margin, -Margin);
PlotSettings prevSettings = zoomStack[curZoom];
PlotSettings settings;
double dx = prevSettings.spanX() / (width() - 2 * Margin);
double dy = prevSettings.spanY() / (height() - 2 * Margin);
settings.minX = prevSettings.minX
settings.maxX = prevSettings.minX
settings.minY = prevSettings.maxY
settings.maxY = prevSettings.maxY
settings.adjust();

+
+
-

dx
dx
dy
dy

*
*
*
*

rect.left();
rect.right();
rect.bottom();
rect.top();

zoomStack.resize(curZoom + 1);
zoomStack.append(settings);
zoomIn();
}
}

Quand lutilisateur relche le bouton gauche de la souris, nous supprimons le rectangle de


slection et nous restaurons le pointeur standard sous forme de flche. Si le rectangle est au
moins de 4 4, nous effectuons un zoom. Si le rectangle de slection est plus petit, il est
probable que lutilisateur a cliqu sur le widget par erreur ou uniquement pour lactiver, nous
ne faisons donc rien.
Le code permettant de zoomer est quelque peu complexe. Cest parce que nous traitons
des coordonnes du widget et de celles du traceur en mme temps. La plupart des tches
effectues ici servent convertir le rubberBandRect, pour transformer les coordonnes
du widget en coordonnes du traceur. Une fois la conversion effectue, nous invoquons

Qt 4 Livre Page 134 Jeudi, 7. dcembre 2006 12:14 12

134

Qt4 et C++ : Programmation dinterfaces GUI

PlotSettings::adjust() pour arrondir les chiffres et trouver un nombre raisonnable de


graduations pour chaque axe. Les Figures 5.10 et 5.11 illustrent la situation.
Figure 5.10
Convertir les coordonnes
dun rectangle de
slection du widget en
coordonnes du traceur

(0, 0)

(94, 73)

6
4

0
0

2.0

10

3.2

135

Figure 5.11
Ajuster les coordonnes
du traceur et zoomer sur
le rectangle de slection

6.5

68

6.8

2.4

10

10

10

7.0

10

7.0
6

4
3.0
2
0

5
4
3

10

Puis nous zoomons. Pour zoomer, nous devons appuyer sur le nouveau PlotSettings que
nous venons de calculer en haut de la pile de zoom et nous appelons zoomIn() qui se chargera
de la tche.
void Plotter::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Plus:
zoomIn();
break;
case Qt::Key_Minus:
zoomOut();
break;
case Qt::Key_Left:
zoomStack[curZoom].scroll(-1, 0);
refreshPixmap();
break;
case Qt::Key_Right:
zoomStack[curZoom].scroll(+1, 0);
refreshPixmap();
break;
case Qt::Key_Down:

Qt 4 Livre Page 135 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

135

zoomStack[curZoom].scroll(0, -1);
refreshPixmap();
break;
case Qt::Key_Up:
zoomStack[curZoom].scroll(0, +1);
refreshPixmap();
break;
default:
QWidget::keyPressEvent(event);
}
}

Quand lutilisateur appuie sur une touche et que le widget Plotter est actif, la fonction
keyPressEvent() est invoque. Nous la rimplmentons ici pour rpondre six touches : +, ,
Haut, Bas, Gauche et Droite. Si lutilisateur a appuy sur une touche que nous ne grons pas,
nous appelons limplmentation de la classe de base. Pour une question de simplicit, nous
ignorons les touches de modification Maj, Ctrl et Alt, disponibles via QKeyEvent::modifiers().
void Plotter::wheelEvent(QWheelEvent *event)
{
int numDegrees = event->delta() / 8;
int numTicks = numDegrees / 15;
if (event->orientation() == Qt::Horizontal) {
zoomStack[curZoom].scroll(numTicks, 0);
} else {
zoomStack[curZoom].scroll(0, numTicks);
}
refreshPixmap();
}

Les vnements wheel se dclenchent quand la molette de la souris est actionne. La majorit
des souris ne proposent quune molette verticale, mais certaines sont quipes dune molette
horizontale. Qt prend en charge les deux types de molette. Les vnements wheel sont transmis au widget actif. La fonction delta() retourne la distance parcourue par la molette en
huitimes de degr. Les souris proposent habituellement une plage de 15 degrs. Dans notre
exemple, nous faisons dfiler le nombre de graduations demandes en modifiant llment le
plus haut dans la pile de zoom et nous mettons jour laffichage au moyen de refreshPixmap().
Nous utilisons la molette de la souris le plus souvent pour faire drouler une barre de dfilement.
Quand nous employons QScrollArea (trait dans le Chapitre 6) pour proposer des barres de
dfilement, QScrollArea gre automatiquement les vnements lis la molette de la souris,
nous navons donc pas rimplmenter wheelEvent() nous-mmes.
Ceci achve limplmentation des gestionnaires dvnements. Passons maintenant en revue
les fonctions prives.
void Plotter::updateRubberBandRegion()
{

Qt 4 Livre Page 136 Jeudi, 7. dcembre 2006 12:14 12

136

Qt4 et C++ : Programmation dinterfaces GUI

QRect rect = rubberBandRect.normalized();


update(rect.left(), rect.top(), rect.width(), 1);
update(rect.left(), rect.top(), 1, rect.height());
update(rect.left(), rect.bottom(), rect.width(), 1);
update(rect.right(), rect.top(), 1, rect.height());
}

La fonction updateRubberBand() est appele depuis mousePressEvent(), mouseMoveEvent() et mouseReleaseEvent() pour effacer ou redessiner le rectangle de slection. Elle
est constitue de quatre appels de update() qui planifient un vnement paint pour les
quatre petites zones rectangulaires couvertes par le rectangle de slection (deux lignes verticales et deux lignes horizontales). Qt propose la classe QRubberBand pour dessiner des
rectangles de slection, mais dans ce cas, lcriture du code permet de mieux contrler lopration.
void Plotter::refreshPixmap()
{
pixmap = QPixmap(size());
pixmap.fill(this, 0, 0);
QPainter painter(&pixmap);
painter.initFrom(this);
drawGrid(&painter);
drawCurves(&painter);
update();
}

La fonction refreshPixmap() redessine le trac sur le pixmap hors champ et met laffichage
jour. Nous redimensionnons le pixmap de sorte quil ait la mme taille que le widget et nous
le remplissons avec la couleur deffacement du widget. Cette couleur correspond au composant
"dark" de la palette en raison de lappel de setBackgroundRole() dans le constructeur de
Plotter. Si larrire-plan nest pas uni, QPixmap::fill() doit connatre la position du
pixmap dans le widget pour aligner correctement le motif de couleur. Dans notre cas, le pixmap
correspond la totalit du widget, nous spcifions donc la position (0, 0).
Nous crons ensuite un QPainter pour dessiner sur le pixmap. Lappel de initFrom() dfinit
le crayon, larrire-plan et la police pour quils soient identiques ceux du widget Plotter.
Puis nous invoquons drawGrid() et drawCurves() pour raliser le dessin. Nous appelons
enfin update() pour planifier un vnement paint pour la totalit du widget. Le pixmap est
copi dans le widget dans la fonction paintEvent().
void Plotter::drawGrid(QPainter *painter)
{
QRect rect(Margin, Margin,
width() - 2 * Margin, height() - 2 * Margin);
if (!rect.isValid())
return;
PlotSettings settings = zoomStack[curZoom];
QPen quiteDark = palette().dark().color().light();
QPen light = palette().light().color();

Qt 4 Livre Page 137 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

137

for (int i = 0; i <= settings.numXTicks; ++i) {


int x = rect.left() + (i * (rect.width() - 1)
/ settings.numXTicks);
double label = settings.minX + (i * settings.spanX()
/ settings.numXTicks);
painter->setPen(quiteDark);
painter->drawLine(x, rect.top(), x, rect.bottom());
painter->setPen(light);
painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
painter->drawText(x - 50, rect.bottom() + 5, 100, 15,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(label));
}
for (int j = 0; j <= settings.numYTicks; ++j) {
int y = rect.bottom() - (j * (rect.height() - 1)
/ settings.numYTicks);
double label = settings.minY + (j * settings.spanY()
/ settings.numYTicks);
painter->setPen(quiteDark);
painter->drawLine(rect.left(), y, rect.right(), y);
painter->setPen(light);
painter->drawLine(rect.left() - 5, y, rect.left(), y);
painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
Qt::AlignRight | Qt::AlignVCenter,
QString::number(label));
}
painter->drawRect(rect.adjusted(0, 0, -1, -1));
}

La fonction drawGrid() dessine la grille derrire les courbes et les axes. La zone dans
laquelle nous dessinons la grille est spcifie par rect. Si le widget nest pas assez grand pour
sadapter au graphique, nous retournons immdiatement.
La premire boucle for trace les lignes verticales de la grille et les graduations sur laxe x. La
seconde boucle for trace les lignes horizontales de la grille et les graduations sur laxe y. A la fin,
nous dessinons un rectangle le long des marges. La fonction drawText() dessine les numros
correspondants aux graduations sur les deux axes.
Les appels de drawText() ont la syntaxe suivante :
painter->drawText(x, y, width, height, alignment, text);

o (x, y, width, height) dfinit un rectangle, alignment la position du texte dans ce


rectangle et text le texte dessiner.
void Plotter::drawCurves(QPainter *painter)
{
static const QColor colorForIds[6] = {
Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
};
PlotSettings settings = zoomStack[curZoom];
QRect rect(Margin, Margin,
width() - 2 * Margin, height() - 2 * Margin);

Qt 4 Livre Page 138 Jeudi, 7. dcembre 2006 12:14 12

138

Qt4 et C++ : Programmation dinterfaces GUI

if (!rect.isValid())
return;
painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
QMapIterator<int, QVector<QPointF> > i(curveMap);
while (i.hasNext()) {
i.next();
int id = i.key();
const QVector<QPointF> &data = i.value();
QPolygonF polyline(data.count());
for (int j = 0; j < data.count(); ++j) {
double dx = data[j].x() - settings.minX;
double dy = data[j].y() - settings.minY;
double x = rect.left() + (dx * (rect.width() - 1)
/ settings.spanX());
double y = rect.bottom() - (dy * (rect.height() - 1)
/ settings.spanY());
polyline[j] = QPointF(x, y);
}
painter->setPen(colorForIds[uint(id) % 6]);
painter->drawPolyline(polyline);
}
}

La fonction drawCurves() dessine les courbes au-dessus de la grille. Nous commenons par
appeler setClipRect() pour dfinir la zone daction de QPainter comme gale au rectangle
qui contient les courbes (except les marges et le cadre autour du graphique). QPainter ignorera ensuite les oprations de dessin sur les pixels situs en dehors de cette zone.
Puis, nous parcourons toutes les courbes laide dun itrateur de style Java, et pour chacune
delles, nous parcourons les QPointF dont elle est constitue. La fonction key() donne lID
de la courbe et la fonction value() donne les donnes de courbe correspondantes comme un
QVector<QPointF>. La boucle interne for convertit chaque QPointF pour transformer les
coordonnes du traceur en coordonnes du widget et les stocke dans la variable polyline.
Une fois que nous avons converti tous les points dune courbe en coordonnes du widget, nous
dterminons la couleur de crayon pour la courbe (en utilisant un des ensembles de couleurs
prdfinies) et nous appelons drawPolyline() pour tracer une ligne qui passe par tous les
points de cette dernire.
Voici la classe Plotter termine. Tout ce qui reste, ce sont quelques fonctions dans PlotSettings.
PlotSettings::PlotSettings()
{
minX = 0.0;
maxX = 10.0;
numXTicks = 5;

Qt 4 Livre Page 139 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 5

Crer des widgets personnaliss

139

minY = 0.0;
maxY = 10.0;
numYTicks = 5;
}

Le constructeur de PlotSettings initialise les deux axes avec une plage de 0 10 en


5 graduations.
void PlotSettings::scroll(int dx, int dy)
{
double stepX = spanX() / numXTicks;
minX += dx * stepX;
maxX += dx * stepX;
double stepY = spanY() / numYTicks;
minY += dy * stepY;
maxY += dy * stepY;
}

La fonction scroll() incrmente (ou dcrmente) minX, maxX, minY et maxY de la valeur de
lintervalle entre deux graduations multiplie par un nombre donn. Cette fonction est utilise
pour implmenter le dfilement dans Plotter::keyPressEvent().
void PlotSettings::adjust()
{
adjustAxis(minX, maxX, numXTicks);
adjustAxis(minY, maxY, numYTicks);
}

La fonction adjust() est invoque dans mouseReleaseEvent() pour arrondir les valeurs
minX, maxX, minY et maxY en valeurs "conviviales" et pour dterminer le bon nombre de
graduations pour chaque axe. La fonction prive adjustAxis() sexcute sur un axe la fois.
void PlotSettings::adjustAxis(double &min, double &max,
int &numTicks)
{
const int MinTicks = 4;
double grossStep = (max - min) / MinTicks;
double step = pow(10.0, floor(log10(grossStep)));
if (5 * step < grossStep) {
step *= 5;
} else if (2 * step < grossStep) {
step *= 2;
}
numTicks = int(ceil(max / step) - floor(min / step));
if (numTicks < MinTicks)
numTicks = MinTicks;
min = floor(min / step) * step;
max = ceil(max / step) * step;
}

Qt 4 Livre Page 140 Jeudi, 7. dcembre 2006 12:14 12

140

Qt4 et C++ : Programmation dinterfaces GUI

La fonction adjustAxis() convertit ses paramtres min et max en nombres "conviviaux" et


dfinit son paramtre numTicks en nombre de graduations quelle calcule comme tant appropries pour la plage [min, max] donne. Vu que adjustAxis() a besoin de modifier les
variables relles (minX, maxX, numXTicks, etc.) et pas uniquement des copies, ses paramtres
sont des rfrences non-const.
Le code de adjustAxis() est principalement consacr dterminer une valeur adquate pour
lintervalle entre deux graduations ("lchelon"). Pour obtenir des nombres convenables sur
laxe, nous devons slectionner lchelon avec soin. Par exemple, une valeur de 3,8 engendrerait des multiples de 3,8 sur un axe, ce qui nest pas trs significatif pour les utilisateurs. Pour
les axes avec une notation dcimale, des valeurs dchelons "conviviales" sont des chiffres de
la forme 10n, 2 10n ou 5 10n.
Nous commenons par calculer "lchelon brut," une sorte de valeur maximum pour lchelon.
Puis nous recherchons le nombre correspondant sous la forme 10n qui est infrieur ou gal
lchelon brut. Pour ce faire, nous prenons le logarithme dcimal de lchelon brut, en
arrondissant cette valeur vers le bas pour obtenir un nombre entier, puis en ajoutant 10 la
puissance de ce chiffre arrondi. Par exemple, si lchelon brut est de 236, nous calculons log
236 = 2,37291 ; puis nous larrondissons vers le bas pour aboutir 2 et nous obtenons
102 = 100 comme valeur dchelon sous la forme 10n.
Une fois que la premire valeur dchelon est dtermine, nous pouvons lutiliser pour calculer
les deux autres candidats : 2 10n et 5 10n. Dans lexemple ci-dessus, les deux autres candidats sont 200 et 500. Le candidat 500 est suprieur lchelon brut, nous navons donc pas la
possibilit de lemployer. Mais 200 est infrieur 236, nous utilisons ainsi 200 comme taille
dchelon dans cet exemple.
Il est assez facile de calculer numTicks, min et max partir de la valeur dchelon. La nouvelle
valeur min est obtenue en arrondissant la valeur min dorigine vers le bas vers le multiple le plus
proche de lchelon, et la nouvelle valeur max est obtenue en arrondissant vers le haut vers le multiple le plus proche de lchelon. La nouvelle valeur numTicks correspond au nombre dintervalles entre les valeurs min et max arrondies. Par exemple, si min est gal 240 et max 1184
au moment de la saisie de la fonction, la nouvelle plage devient [200, 1200], avec cinq graduations.
Cet algorithme donnera des rsultats optimaux dans certains cas. Un algorithme plus sophistiqu est dcrit dans larticle "Nice Numbers for Graph Labels" de Paul S. Heckbert publi dans
Graphics Gems (ISBN 0-12-286166-3).
Ce chapitre achve la Partie I. Il vous a expliqu comment personnaliser un widget Qt existant
et comment gnrer un widget en partant de zro laide de QWidget comme classe de base.
Nous avons aussi vu comment composer un widget partir de widgets existants dans le Chapitre 2 et nous allons explorer ce thme plus en dtail dans le Chapitre 6.
A ce stade, vous en savez suffisamment pour crire des applications GUI compltes avec Qt.
Dans les Parties II et III, nous tudierons Qt en profondeur pour pouvoir profiter de toute la
puissance de ce framework.

Qt 4 Livre Page 141 Jeudi, 7. dcembre 2006 12:14 12

II
Qt : niveau
intermdiaire
6

Gestion des dispositions

Traitement des vnements

Graphiques 2D et 3D

Glisser-dposer

10

Classes daffichage dlments

11

Classes conteneur

12

Entres/Sorties

13

Les bases de donnes

14

Gestion de rseau

15

XML

16

Aide en ligne

Qt 4 Livre Page 142 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 143 Jeudi, 7. dcembre 2006 12:14 12

6
Gestion des dispositions
Au sommaire de ce chapitre
Disposer des widgets sur un formulaire
Dispositions empiles
Sparateurs
Zones droulantes
Widgets et barres doutils ancrables
MDI (Multiple Document Interface)

Chaque widget plac dans un formulaire doit se voir attribuer une taille et une position
appropries. Qt propose plusieurs classes qui disposent les widgets dans un formulaire :
QHBoxLayout, QVBoxLayout, QGridLayout et QStackLayout. Ces classes sont si
pratiques et faciles utiliser que presque tous les dveloppeurs Qt sen servent, soit
directement dans du code source, soit par le biais du Qt Designer.
Il existe une autre raison demployer les classes de disposition (layout) de Qt : elles
garantissent que les formulaires sadaptent automatiquement aux diverses polices,
langues et plates-formes. Si lutilisateur modifie les paramtres de police du systme,
les formulaires de lapplication rpondront immdiatement en se redimensionnant euxmmes si ncessaire. Si vous traduisez linterface utilisateur de lapplication en dautres
langues, les classes de disposition prennent en compte le contenu traduit des widgets
pour viter toute coupure de texte.

Qt 4 Livre Page 144 Jeudi, 7. dcembre 2006 12:14 12

144

Qt4 et C++ : Programmation dinterfaces GUI

QSplitter, QScrollArea, QMainWindow et QWorkspace sont dautres classes qui se chargent de grer la disposition. Le point commun de ces classes cest quelles procurent une
disposition trs flexible sur laquelle lutilisateur peut agir. Par exemple, QSplitter propose un
sparateur que lutilisateur peut faire glisser pour redimensionner les widgets, et QWorkspace
prend en charge MDI (multiple document interface), un moyen dafficher plusieurs documents
simultanment dans la fentre principale dune application. Etant donn quelles sont souvent
utilises comme des alternatives aux classes de disposition, elles sont aussi prsentes dans ce
chapitre.

Disposer des widgets sur un formulaire


Il y a trois moyens de grer la disposition des widgets enfants dans un formulaire : le positionnement absolu, la disposition manuelle et les gestionnaires de disposition. Nous allons tudier
chacun deux tour de rle, en nous basant sur la bote de dialogue Find File illustre en
Figure 6.1.
Figure 6.1
La bote de dialogue
Find File

Le positionnement absolu est le moyen le plus rudimentaire de disposer des widgets. Il suffit
dassigner dans du code des tailles et des positions aux widgets enfants du formulaire et une
taille fixe au formulaire. Voici quoi ressemble le constructeur de FindFileDialog avec le
positionnement absolu :
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 200, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 200, 25);

Qt 4 Livre Page 145 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

145

subfoldersCheckBox->setGeometry(9, 71, 256, 23);


tableWidget->setGeometry(9, 100, 256, 100);
messageLabel->setGeometry(9, 206, 256, 25);
findButton->setGeometry(271, 9, 85, 32);
stopButton->setGeometry(271, 47, 85, 32);
closeButton->setGeometry(271, 84, 85, 32);
helpButton->setGeometry(271, 199, 85, 32);
setWindowTitle(tr("Find Files or Folders"));
setFixedSize(365, 240);
}

Le positionnement absolu prsente de nombreux inconvnients :


Lutilisateur ne peut pas redimensionner la fentre.
Une partie du texte peut tre coupe si lutilisateur choisit une police trop grande ou si
lapplication est traduite dans une autre langue.
Les widgets peuvent prsenter des tailles inadaptes pour certains styles.
Les positions et les tailles doivent tre calcules manuellement. Cette mthode est fastidieuse et sujette aux erreurs ; de plus, elle complique la maintenance.
Lalternative au positionnement absolu est la disposition manuelle. Avec cette technique, les
widgets ont toujours des positions absolues donnes, mais leurs tailles sont proportionnelles
la taille de la fentre au lieu dtre totalement codes. Il convient donc de rimplmenter la
fonction resizeEvent() du formulaire pour dfinir les gomtries de ses widgets enfants :
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
setMinimumSize(265, 190);
resize(365, 240);
}
void FindFileDialog::resizeEvent(QResizeEvent * /* event */)
{
int extraWidth = width() - minimumWidth();
int extraHeight = height() - minimumHeight();
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 100 + extraWidth, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 100 + extraWidth, 25);
subfoldersCheckBox->setGeometry(9, 71, 156 + extraWidth, 23);
tableWidget->setGeometry(9, 100, 156 + extraWidth,
50 + extraHeight);
messageLabel->setGeometry(9, 156 + extraHeight, 156 + extraWidth,
25);
findButton->setGeometry(171 + extraWidth, 9, 85, 32);
stopButton->setGeometry(171 + extraWidth, 47, 85, 32);

Qt 4 Livre Page 146 Jeudi, 7. dcembre 2006 12:14 12

146

Qt4 et C++ : Programmation dinterfaces GUI

closeButton->setGeometry(171 + extraWidth, 84, 85, 32);


helpButton->setGeometry(171 + extraWidth, 149 + extraHeight, 85,
32);
}

Dans le constructeur de FindFileDialog, nous configurons la taille minimale du formulaire


en 265 190 et la taille initiale en 365 240. Dans le gestionnaire resizeEvent(), nous
accordons de lespace supplmentaire aux widgets qui veulent sagrandir. Nous sommes ainsi
certains que le formulaire se met lchelle quand lutilisateur le redimensionne, comme illustr
en Figure 6.2.

Figure 6.2
Redimensionner une bote de dialogue redimensionnable

Tout comme le positionnement absolu, la disposition manuelle oblige le programmeur calculer


beaucoup de constantes codes. Ecrire du code de cette manire se rvle pnible, notamment
si la conception change. De plus, le texte court toujours le risque dtre coup. Nous pouvons
viter ce problme en tenant compte des tailles requises des widgets enfants, mais cela compliquerait encore plus le code.
La solution la plus pratique pour disposer des widgets sur un formulaire consiste utiliser les
gestionnaires de disposition de Qt. Ces gestionnaires proposent des valeurs par dfaut raisonnables pour chaque type de widget et tiennent compte de la taille requise de chacun deux, qui
dpend de la police, du style et du contenu du widget. Ces gestionnaires respectent galement
des dimensions minimales et maximales, et ajustent automatiquement la disposition en
rponse des changements de police ou de contenu et un redimensionnement de la fentre.
Les trois gestionnaires de disposition les plus importants sont QHBoxLayout, QVBoxLayout et
QGridLayout. Ces classes hritent de QLayout, qui fournit le cadre de base des dispositions.
Ces trois classes sont totalement prises en charge par le Qt Designer et peuvent aussi tre utilises
directement dans du code.
Voici le code de FindFileDialog avec des gestionnaires de disposition :
FindFileDialog::FindFileDialog(QWidget *parent)

Qt 4 Livre Page 147 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

147

: QDialog(parent)
{
...
QGridLayout *leftLayout = new QGridLayout;
leftLayout->addWidget(namedLabel, 0, 0);
leftLayout->addWidget(namedLineEdit, 0, 1);
leftLayout->addWidget(lookInLabel, 1, 0);
leftLayout->addWidget(lookInLineEdit, 1, 1);
leftLayout->addWidget(subfoldersCheckBox, 2, 0, 1, 2);
leftLayout->addWidget(tableWidget, 3, 0, 1, 2);
leftLayout->addWidget(messageLabel, 4, 0, 1, 2);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(stopButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();
rightLayout->addWidget(helpButton);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find Files or Folders"));
}

La disposition est gre par QHBoxLayout, QGridLayout et QVBoxLayout. QGridLayout


gauche et QVBoxLayout droite sont placs cte cte par le QHBoxLayout externe. Les
marges autour de la bote de dialogue et lespace entre les widgets enfants prsentent des
valeurs par dfaut en fonction du style de widget ; elles peuvent tre modifies grce
QLayout::setMargin() et QLayout::setSpacing().
La mme bote de dialogue aurait pu tre cre visuellement dans le Qt Designer en plaant les
widgets enfants leurs positions approximatives, en slectionnant ceux qui doivent tre disposs ensemble et en cliquant sur Form > Lay Out Horizontally, Form > Lay Out Vertically ou
Form > Lay Out in a Grid. Nous avons employ cette approche dans le Chapitre 2 pour crer
les botes de dialogue Go-to-Cell et Sort de lapplication Spreadsheet.
Utiliser QHBoxLayout et QVBoxLayout est plutt simple mais lutilisation de QGridLayout se
rvle un peu plus complexe. QGridLayout se base sur une grille de cellules deux dimensions. Le QLabel dans le coin suprieur gauche de la disposition se trouve la position (0, 0)
et le QLineEdit correspondant se situe la position (0, 1). Le QCheckBox stend sur deux
colonnes ; il occupe les cellules aux positions (2, 0) et (2, 1). Les QTreeWidget et QLabel en
dessous prennent aussi deux colonnes (voir Figure 6.3). Les appels de addWidget() ont la
syntaxe suivante :
layout->addWidget(widget, row, column, rowSpan, columnSpan);

Qt 4 Livre Page 148 Jeudi, 7. dcembre 2006 12:14 12

148

Qt4 et C++ : Programmation dinterfaces GUI

Figure 6.3
La disposition
de la bote
de dialogue
Find File

Titre de fentre

QLabel

QLineEdit

QPushButton

QLabel

QLineEdit

QPushButton

mainLayout

QCheckBox

QPushButton

QTreeWidget

QLabel

QPushButton

leftLayout

rightLayout

Dans ce cas, widget est le widget enfant insrer dans la disposition, (row, column) est la
cellule en haut gauche occupe par le widget, rowSpan correspond au nombre de lignes
occupes par le widget et columnSpan est le nombre de colonnes occupes par le widget.
Sils sont omis, les paramtres rowSpan et columnSpan ont la valeur 1 par dfaut.
Lappel de addStretch() ordonne au gestionnaire de disposition dutiliser lespace cet
endroit dans la disposition. En ajoutant un lment dtirement, nous avons demand au
gestionnaire de disposition de placer tout espace excdentaire entre les boutons Close et Help.
Dans le Qt Designer, nous pouvons aboutir au mme effet en insrant un lment despacement.
Les lments despacement apparaissent dans le Qt Designer sous forme de "ressorts" bleus.
Utiliser des gestionnaires de disposition prsente des avantages supplmentaires par rapport
ceux dcrits jusque l. Si nous ajoutons ou supprimons un widget dans une disposition, celle-ci
sadaptera automatiquement la nouvelle situation. Il en va de mme si nous invoquons hide()
ou show() sur un widget enfant. Si la taille requise dun widget enfant change, la disposition
sera automatiquement corrige, en tenant compte de cette nouvelle taille. En outre, les gestionnaires de disposition dfinissent automatiquement une taille minimale pour le formulaire, en
fonction des tailles minimales et des tailles requises des widgets enfants de ce dernier.
Dans les exemples donns jusqu prsent, nous avons simplement plac les widgets dans des
dispositions et utilis des lments despacement pour combler tout espace excdentaire. Dans
certains cas, ce nest pas suffisant pour que la disposition ressemble exactement ce que nous
voulons. Nous pouvons donc ajuster la disposition en changeant les stratgies de taille (rgles
auxquelles la taille est soumise, voir chapitre prcdent) et les tailles requises des widgets
disposer.
Grce la stratgie de taille dun widget, le systme de disposition sait comment ce widget
doit tre tir ou rtrci. Qt propose des stratgies par dfaut raisonnables pour tous ses
widgets intgrs. Cependant, puisquil nexiste pas de valeur par dfaut qui pourrait tenir
compte de toutes les dispositions possibles, il est courant que les dveloppeurs modifient les

Qt 4 Livre Page 149 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

149

stratgies pour un ou deux widgets dans un formulaire. Un QSizePolicy possde un composant


horizontal et un vertical. Voici les valeurs les plus utiles :
Fixed signifie que le widget ne peut pas tre rtrci ou tir. Le widget conserve toujours
sa taille requise.
Minimum signifie que la taille requise dun widget correspond sa taille minimale. Le
widget ne peut pas tre rtrci en dessous de la taille requise, mais il peut sagrandir pour
combler lespace disponible si ncessaire.
Maximum signifie que la taille requise dun widget correspond sa taille maximale. Le widget
peut tre rtrci jusqu sa taille requise minimum.
Preferred signifie que la taille requise dun widget correspond sa taille favorite, mais
que le widget peut toujours tre rtrci ou tir si ncessaire.
Expanding signifie que le widget peut tre rtrci ou tir, mais quil prfre tre agrandi.
La Figure 6.4 rcapitule la signification des diffrentes stratgies, en utilisant un QLabel affichant
le texte "Some Text" comme exemple.
Figure 6.4
La signification des diffrentes stratgies de taille

taille requise min

taille requise

Fixed

SomeText

Minimum

SomeText

SomeText

Maximum

Som

SomeText

Preferred

Som

SomeText

SomeText

Expanding

Som

SomeText

SomeText

Dans la figure, Preferred et Expanding donnent le mme rsultat. O se situe la diffrence ?


Quand un formulaire qui contient les widgets Preferred et Expanding est redimensionn,
lespace supplmentaire est attribu aux widgets Expanding, alors que les widgets Preferred
conservent leur taille requise.
Il existe deux autres stratgies : MinimumExpanding et Ignored. La premire tait ncessaire
dans quelques rares cas dans les versions antrieures de Qt, mais elle ne prsente plus dintrt ; la meilleure approche consiste utiliser Expanding et rimplmenter minimumSizeHint() de faon approprie. La seconde est similaire Expanding, sauf quelle ignore
la taille requise et la taille requise minimum du widget.
En plus des composants verticaux et horizontaux de la stratgie, la classe QSizePolicy stocke
un facteur dtirement horizontal et vertical. Ces facteurs dtirement peuvent tre utiliss pour
indiquer que les divers widgets enfants doivent stirer diffrents niveaux quand le formulaire
sagrandit. Par exemple, si nous avons un QTreeWidget au-dessus dun QTextEdit et que
nous voulons que le QTextEdit soit deux fois plus grand que le QTreeWidget, nous avons la

Qt 4 Livre Page 150 Jeudi, 7. dcembre 2006 12:14 12

150

Qt4 et C++ : Programmation dinterfaces GUI

possibilit de dfinir un facteur dtirement vertical de QTextEdit de 2 et un facteur dtirement


vertical de QTreeWidget de 1.
Cependant, un autre moyen dinfluencer une disposition consiste configurer une taille minimale ou maximale, ou une taille fixe pour les widgets enfants. Le gestionnaire de disposition
respectera ces contraintes lorsquil disposera les widgets. Et si ce nest pas suffisant, nous
pouvons toujours driver de la classe du widget enfant et rimplmenter sizeHint() pour
obtenir la taille requise dont nous avons besoin.

Dispositions empiles
La classe QStackedLayout dispose un ensemble de widgets enfants, ou "pages," et nen affiche quun seul la fois, en masquant les autres lutilisateur. QStackedLayout est invisible
en soi et lutilisateur na aucun moyen de changer une page. Les petites flches et le cadre gris
fonc dans la Figure 6.5 sont fournis par le Qt Designer pour faciliter la conception avec la
disposition. Pour des questions pratiques, Qt inclut galement un QStackedWidget qui
propose un QWidget avec un QStackedLayout intgr.
Figure 6.5
QStackedLayout

Les pages sont numrotes en commenant 0. Pour afficher un widget enfant spcifique, nous
pouvons appeler setCurrentIndex() avec un numro de page. Le numro de page dun
widget enfant est disponible grce indexOf().
Figure 6.6
Deux pages
de la bote
de dialogue
Preferences

Qt 4 Livre Page 151 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

151

La bote de dialogue Preferences illustre en Figure 6.6 est un exemple qui utilise QStackedLayout. Elle est constitue dun QListWidget gauche et dun QStackedLayout droite.
Chaque lment dans QListWidget correspond une page diffrente dans QStackedLayout.
Voici le code du constructeur de la bote de dialogue :
PreferenceDialog::PreferenceDialog(QWidget *parent)
: QDialog(parent)
{
...
listWidget = new QListWidget;
listWidget->addItem(tr("Appearance"));
listWidget->addItem(tr("Web Browser"));
listWidget->addItem(tr("Mail & News"));
listWidget->addItem(tr("Advanced"));
stackedLayout = new QStackedLayout;
stackedLayout->addWidget(appearancePage);
stackedLayout->addWidget(webBrowserPage);
stackedLayout->addWidget(mailAndNewsPage);
stackedLayout->addWidget(advancedPage);
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
...
listWidget->setCurrentRow(0);
}

Nous crons un QListWidget et nous lalimentons avec les noms des pages. Nous crons
ensuite un QStackedLayout et nous invoquons addWidget() pour chaque page. Nous
connectons le signal currentRowChanged(int) du widget liste setCurrentIndex(int)
de la disposition empile pour implmenter le changement de page, puis nous appelons
setCurrentRow() sur le widget liste la fin du constructeur pour commencer la page 0.
Ce genre de formulaire est aussi trs facile crer avec le Qt Designer :
1. crez un nouveau formulaire en vous basant sur les modles "Dialog" ou "Widget" ;
2. ajoutez un QListWidget et un QStackedWidget au formulaire ;
3. remplissez chaque page avec des widgets enfants et des dispositions ;
(Pour crer une nouvelle page, cliquez du bouton droit et slectionnez Insert Page ; pour
changer de page, cliquez sur la petite flche gauche ou droite situe en haut droite du
QStackedWidget)
4. disposez les widgets cte cte grce une disposition horizontale ;
5. connectez le signal currentRowChanged(int) du widget liste au slot setCurrentIndex(int) du widget empil ;
6. dfinissez la valeur de la proprit currentRow du widget liste en 0.
Etant donn que nous avons implment le changement de page en utilisant des signaux et des
slots prdfinis, la bote de dialogue prsentera le bon comportement quand elle sera prvisualise dans le Qt Designer.

Qt 4 Livre Page 152 Jeudi, 7. dcembre 2006 12:14 12

152

Qt4 et C++ : Programmation dinterfaces GUI

Sparateurs
QSplitter est un widget qui comporte dautres widgets. Les widgets dans un sparateur sont
spars par des poignes. Les utilisateurs peuvent changer les tailles des widgets enfants du
sparateur en faisant glisser ces poignes. Les sparateurs peuvent souvent tre utiliss comme
une alternative aux gestionnaires de disposition, pour accorder davantage de contrle lutilisateur.
Figure 6.7
Lapplication
Splitter

Les widgets enfants dun QSplitter sont automatiquement placs cte cte (ou un endessous de lautre) dans lordre dans lequel ils sont crs, avec des barres de sparation entre
les widgets adjacents. Voici le code permettant de crer la fentre illustre en Figure 6.7 :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextEdit *editor1 = new QTextEdit;
QTextEdit *editor2 = new QTextEdit;
QTextEdit *editor3 = new QTextEdit;
QSplitter splitter(Qt::Horizontal);
splitter.addWidget(editor1);
splitter.addWidget(editor2);
splitter.addWidget(editor3);
...
splitter.show();
return app.exec();
}

Lexemple est constitu de trois QTextEdit disposs horizontalement par un widget QSplitter.
Contrairement aux gestionnaires de disposition qui se contentent dorganiser les widgets
enfants dun formulaire et ne proposent aucune reprsentation visuelle, QSplitter hrite de
QWidget et peut tre utilis comme nimporte quel autre widget.
Vous obtenez des dispositions complexes en imbriquant des QSplitter horizontaux et verticaux. Par exemple, lapplication Mail Client prsente en Figure 6.9 consiste en un QSplitter
horizontal qui contient un QSplitter vertical sur sa droite.

Qt 4 Livre Page 153 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Figure 6.8
Les widgets
de lapplication
Splitter

Gestion des dispositions

153

Titre de fentre
QSplitter
QTextEdit

QTextEdit

QTextEdit

Figure 6.9
Lapplication
Mail Client
sous Mac OS X

Voici le code dans le constructeur de la sous-classe QMainWindow de lapplication Mail Client :


MailClient::MailClient()
{
...
rightSplitter = new QSplitter(Qt::Vertical);
rightSplitter->addWidget(messagesTreeWidget);
rightSplitter->addWidget(textEdit);
rightSplitter->setStretchFactor(1, 1);
mainSplitter = new QSplitter(Qt::Horizontal);
mainSplitter->addWidget(foldersTreeWidget);
mainSplitter->addWidget(rightSplitter);
mainSplitter->setStretchFactor(1, 1);
setCentralWidget(mainSplitter);
setWindowTitle(tr("Mail Client"));
readSettings();
}

Aprs avoir cr les trois widgets que nous voulons afficher, nous crons un sparateur vertical, rightSplitter, et nous ajoutons les deux widgets dont nous avons besoin sur la droite.

Qt 4 Livre Page 154 Jeudi, 7. dcembre 2006 12:14 12

154

Qt4 et C++ : Programmation dinterfaces GUI

Nous crons ensuite un sparateur horizontal, mainSplitter, et nous ajoutons le widget que
nous voulons quil affiche sur la gauche. Nous crons aussi rightSplitter dont les widgets
doivent safficher droite. mainSplitter devient le widget central de QMainWindow.
Quand lutilisateur redimensionne une fentre, QSplitter distribue normalement lespace de
sorte que les tailles relatives des widgets enfants restent les mmes. Dans lexemple Mail
Client, nous ne souhaitons pas ce comportement ; nous voulons plutt que QTreeWidget et
QTableWidget conservent leurs dimensions et nous voulons attribuer tout espace supplmentaire QTextEdit (voir Figure 6.10). Nous y parvenons grce aux deux appels de setStretchFactor(). Le premier argument est lindex de base zro du widget enfant du sparateur et le
second argument est le facteur dtirement que nous dsirons dfinir ; la valeur par dfaut est
gale 0.
mainSplitter

0
foldersTreeWidget

Figure 6.10
Index du sparateur de
lapplication Mail Client

1
messagesTableWidget

0
rightSplitter

textEdit

Le premier appel de setStretchFactor() est effectu sur rightSplitter et dfinit le


widget la position 1 (textEdit) pour avoir un facteur dtirement de 1. Le deuxime appel
de setStretchFactor() se fait sur mainSplitter et fixe le widget la position 1 (rightSplitter) pour obtenir un facteur dtirement de 1. Ceci garantit que tout espace supplmentaire disponible reviendra textEdit.
Quand lapplication est lance, QSplitter attribue aux widgets enfants des tailles appropries
en fonction de leurs dimensions initiales (ou en fonction de leur taille requise si la dimension
initiale nest pas spcifie). Nous pouvons grer le dplacement des poignes du sparateur
dans le code en appelant QSplitter::setSizes(). La classe QSplitter procure galement
un moyen de sauvegarder et restaurer son tat la prochaine fois que lapplication est excute.
Voici la fonction writeSettings() qui enregistre les paramtres de Mail Client :
void MailClient::writeSettings()
{
QSettings settings("Software Inc.", "Mail Client");
settings.beginGroup("mainWindow");
settings.setValue("size", size());
settings.setValue("mainSplitter", mainSplitter->saveState());
settings.setValue("rightSplitter", rightSplitter->saveState());
settings.endGroup();
}

Qt 4 Livre Page 155 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

155

Voil la fonction readSettings() correspondante :


void MailClient::readSettings()
{
QSettings settings("Software Inc.", "Mail Client");
settings.beginGroup("mainWindow");
resize(settings.value("size", QSize(480, 360)).toSize());
mainSplitter->restoreState(
settings.value("mainSplitter").toByteArray());
rightSplitter->restoreState(
settings.value("rightSplitter").toByteArray());
settings.endGroup();
}

QSplitter est totalement pris en charge par le Qt Designer. Pour placer des widgets dans un
sparateur, positionnez les widgets enfants plus ou moins leurs emplacements, slectionnez-les
et cliquez sur Form > Lay Out Horizontally in Splitter ou Form > Lay Out Vertically in Splitter.

Zones droulantes

Figure 6.11
Les widgets qui constituent
QScrollArea
viewport()

verticalScrollBar()

La classe QScrollArea propose une fentre daffichage droulante et deux barres de dfilement, comme le montre la Figure 6.11. Si vous voulez ajouter des barres de dfilement un
widget, le plus simple est dutiliser un QScrollArea au lieu dinstancier vos propres
QScrollBar et dimplmenter la fonctionnalit droulante vous-mme.

horizontalScrollBar()

Pour se servir de QScrollArea, il faut appeler setWidget() avec le widget auquel vous
souhaitez ajouter des barres de dfilement. QScrollArea reparente automatiquement le
widget pour quil devienne un enfant de la fentre daffichage (accessible via QScrollArea::viewport()) si ce nest pas encore le cas. Par exemple, si vous voulez des barres de
dfilement autour du widget IconEditor dvelopp au Chapitre 5, vous avez la possibilit
dcrire ceci :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

Qt 4 Livre Page 156 Jeudi, 7. dcembre 2006 12:14 12

156

Qt4 et C++ : Programmation dinterfaces GUI

IconEditor *iconEditor = new IconEditor;


iconEditor->setIconImage(QImage(":/images/mouse.png"));
QScrollArea scrollArea;
scrollArea.setWidget(iconEditor);
scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
scrollArea.viewport()->setAutoFillBackground(true);
scrollArea.setWindowTitle(QObject::tr("Icon Editor"));
scrollArea.show();
return app.exec();
}

QScrollArea prsente le widget dans sa taille actuelle ou utilise la taille requise si le widget
na pas encore t redimensionn. En appelant setWidgetResizable(true), vous pouvez
dire QScrollArea de redimensionner automatiquement le widget pour profiter de tout
espace supplmentaire au-del de sa taille requise.
Par dfaut, les barres de dfilement ne sont affiches que lorsque la fentre daffichage est
plus petite que le widget enfant. Nous pouvons obliger les barres de dfilement tre toujours
visibles en configurant les stratgies de barre de dfilement :
scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

Figure 6.12
Redimensionner
un QScrollArea

QScrollArea hrite la majorit de ses fonctionnalits de QAbstractScrollArea. Des classes


comme QTextEdit et QAbstractItemView (la classe de base des classes daffichage
dlments de Qt) drivent de QAbstractScrollArea, nous navons donc pas les encadrer
dans un QScrollArea pour obtenir des barres de dfilement.

Qt 4 Livre Page 157 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

157

Widgets et barres doutils ancrables


Les widgets ancrables sont des widgets qui peuvent tre ancrs dans un QMainWindow ou
rester flottants comme des fentres indpendantes. QMainWindow propose quatre zones
daccueil pour les widgets ancrables : une en dessous, une au-dessus, une gauche et une
droite du widget central. Des applications telles que Microsoft Visual Studio et Qt Linguist
utilisent normment les fentres ancrables pour offrir une interface utilisateur trs flexible.
Dans Qt, les widgets ancrables sont des instances de QDockWidget.
Chaque widget ancrable possde sa propre barre de titre, mme sil est ancr (voir Figure 6.13).
Les utilisateurs peuvent dplacer les fentres ancrables dune zone une autre en faisant glisser la barre de titre. Ils peuvent aussi dtacher une fentre ancre dune zone et en faire une
fentre flottante indpendante en la faisant glisser en dehors de tout point dancrage. Les fentres ancrables sont toujours affiches "au-dessus" de leur fentre principale lorsquelles sont
flottantes. Les utilisateurs peuvent fermer QDockWidget en cliquant sur le bouton de fermeture
dans la barre de titre du widget. Toute combinaison de ces fonctions peut tre dsactive en
appelant QDockWidget::setFeatures().
Figure 6.13
QMainWindow avec
un widget ancrable

Dans les versions antrieures de Qt, les barres doutils taient considres comme des widgets
ancrables et partageaient les mmes points dancrage. Avec Qt 4, les barres doutils occupent
leurs propres zones autour du widget central (comme illustr en Figure 6.14) et ne peuvent pas
tre dtaches. Si une barre doutils flottante savre ncessaire, nous pouvons simplement la
placer dans un QDockWindow.

Qt 4 Livre Page 158 Jeudi, 7. dcembre 2006 12:14 12

158

Qt4 et C++ : Programmation dinterfaces GUI

Figure 6.14
Les points dancrage et
les zones de barres doutils
de QMainWindow

Titre de la fentre
Barre de menus
Zone suprieure de barre d'outils

Zone droite de barre d'outils

Point d'ancrage droit

Point d'ancrage gauche

Zone gauche de barre d'outils

Point d'ancrage suprieur

Point d'ancrage infrieur


Zone infrieure de barre d'outils
Barre d'tat

Les coins matrialiss avec des lignes pointilles peuvent appartenir lun des deux points
dancrage adjacents. Par exemple, nous pourrions instaurer que le coin suprieur gauche
appartient la zone dancrage gauche en appelant QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea).
Lextrait de code suivant montre comment encadrer un widget existant (dans ce cas,
QTreeWidget) dans un QDockWidget et comment linsrer dans le point dancrage droit :
QDockWidget *shapesDockWidget = new QDockWidget(tr("Shapes"));
shapesDockWidget->setWidget(treeWidget);
shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea
| Qt::RightDockWidgetArea);
addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);

Lappel de setAllowedAreas() spcifie les contraintes selon lesquelles les points dancrage
peuvent accepter la fentre ancrable. Ici, nous autorisons uniquement lutilisateur faire glisser le widget ancrable vers les zones gauche et droite, o il y a suffisamment despace vertical
pour quil saffiche convenablement. Si aucune zone autorise nest explicitement spcifie,
lutilisateur a la possibilit de faire glisser ce widget vers lun des quatre points dancrage.
Voici comment crer une barre doutils contenant un QComboBox, un QSpinBox et quelques
QToolButton dans le constructeur dune sous-classe de QMainWindow:
QToolBar *fontToolBar = new QToolBar(tr("Font"));
fontToolBar->addWidget(familyComboBox);
fontToolBar->addWidget(sizeSpinBox);
fontToolBar->addAction(boldAction);
fontToolBar->addAction(italicAction);

Qt 4 Livre Page 159 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

159

fontToolBar->addAction(underlineAction);
fontToolBar->setAllowedAreas(Qt::TopToolBarArea
| Qt::BottomToolBarArea);
addToolBar(fontToolBar);

Si nous voulons sauvegarder la position de tous les widgets ancrables et barres doutils de
manire pouvoir les restaurer la prochaine fois que lapplication sera excute, nous pouvons
crire un code similaire celui utilis pour enregistrer ltat dun QSplitter laide des
fonctions saveState() et restoreState() de QMainWindow:
void MainWindow::writeSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
settings.setValue("size", size());
settings.setValue("state", saveState());
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
resize(settings.value("size").toSize());
restoreState(settings.value("state").toByteArray());
settings.endGroup();
}

Enfin, QMainWindow propose un menu contextuel qui rpertorie toutes les fentres ancrables et
toutes les barres doutils, comme illustr en Figure 6.15. Lutilisateur peut fermer et restaurer
ces fentres et masquer et restaurer des barres doutils par le biais de ce menu.
Figure 6.15
Le menu contextuel
de QMainWindow

MDI (Multiple Document Interface)


Les applications qui proposent plusieurs documents dans la zone centrale de la fentre principale
sont appeles des applications MDI (multiple document interface). Dans Qt, une application
MDI est cre en utilisant la classe QWorkspace comme widget central et en faisant de chaque
fentre de document un enfant de QWorkspace.

Qt 4 Livre Page 160 Jeudi, 7. dcembre 2006 12:14 12

160

Qt4 et C++ : Programmation dinterfaces GUI

Les applications MDI fournissent gnralement un menu Fentre (Window) partir duquel
vous grez les fentres et les listes de fentres. La fentre active est identifie par une
coche. Lutilisateur peut activer nimporte quelle fentre en cliquant sur son entre dans ce
menu.
Dans cette section, nous dvelopperons lapplication MDI Editor prsente en Figure 6.16
pour vous montrer comment crer une application MDI et comment implmenter son menu
Window.
Figure 6.16
Lapplication
MDI Editor

Lapplication comprend deux classes : MainWindow et Editor. Le code se trouve sur le site
www.pearson.fr, la page ddie cet ouvrage, et vu quune grande partie de celui-ci est
identique ou similaire lapplication Spreadsheet de la Partie I, nous ne prsenterons que les
parties nouvelles.
Figure 6.17
Les menus
de lapplication
MDI Editor

Qt 4 Livre Page 161 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

161

Commenons par la classe MainWindow.


MainWindow::MainWindow()
{
workspace = new QWorkspace;
setCentralWidget(workspace);
connect(workspace, SIGNAL(windowActivated(QWidget *)),
this, SLOT(updateMenus()));
createActions();
createMenus();
createToolBars();
createStatusBar();
setWindowTitle(tr("MDI Editor"));
setWindowIcon(QPixmap(":/images/icon.png"));
}

Dans le constructeur MainWindow, nous crons un widget QWorkspace qui devient le widget
central. Nous connectons le signal windowActivated() de QWorkspace au slot que nous
voulons utiliser pour conserver le menu Window jour.
void MainWindow::newFile()
{
Editor *editor = createEditor();
editor->newFile();
editor->show();
}

Le slot newFile() correspond loption File > New. Il dpend de la fonction prive createEditor() pour crer un widget enfant Editor.
Editor *MainWindow::createEditor()
{
Editor *editor = new Editor;
connect(editor, SIGNAL(copyAvailable(bool)),
cutAction, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(copyAvailable(bool)),
copyAction, SLOT(setEnabled(bool)));
workspace->addWindow(editor);
windowMenu->addAction(editor->windowMenuAction());
windowActionGroup->addAction(editor->windowMenuAction());
return editor;
}

La fonction createEditor() cre un widget Editor et tablit deux connexions signal-slot.


Ces connexions garantissent que Edit > Cut et Edit > Copy sont activs ou dsactivs selon que
du texte est slectionn ou non.

Qt 4 Livre Page 162 Jeudi, 7. dcembre 2006 12:14 12

162

Qt4 et C++ : Programmation dinterfaces GUI

Avec MDI, il est possible que plusieurs widgets Editor soient utiliss. Cest un souci parce
que nous ne voulons rpondre quau signal copyAvailable(bool) de la fentre Editor
active et pas aux autres. Cependant, ces signaux ne peuvent tre mis que par la fentre active,
ce nest donc pas un problme en pratique.
Lorsque nous avons configur Editor, nous avons ajout un QAction reprsentant la fentre
au menu Window. Laction est fournie par la classe Editor que nous tudierons plus loin.
Nous ajoutons galement laction un objet QActionGroup. Avec QActionGroup, nous
sommes srs quun seul lment du menu Window est coch la fois.
void MainWindow::open()
{
Editor *editor = createEditor();
if (editor->open()) {
editor->show();
} else {
editor->close();
}
}

La fonction open() correspond File > Open. Elle cre un Editor pour le nouveau document
et appelle open() sur ce dernier. Il est prfrable dimplmenter des oprations de fichier dans
la classe Editor que dans la classe MainWindow, parce que chaque Editor a besoin dassurer
le suivi de son propre tat indpendant.
Si open() choue, nous fermons simplement lditeur parce que lutilisateur aura dj t
inform de lerreur. Nous navons pas supprimer explicitement lobjet Editor nous-mmes ;
cest fait automatiquement par Editor par le biais de lattribut Qt::WA_DeleteOnClose, qui
est dfini dans le constructeur de Editor.
void MainWindow::save()
{
if (activeEditor())
activeEditor()->save();
}

Le slot save() invoque Editor::save() sur lditeur actif, sil y en a un. Une fois encore, le
code qui accomplit le vritable travail se situe dans la classe Editor.
Editor *MainWindow::activeEditor()
{
return qobject_cast<Editor *>(workspace->activeWindow());
}

La fonction prive activeEditor() retourne la fentre enfant active sous la forme dun pointeur
de Editor, ou dun pointeur nul sil ny en a pas.
void MainWindow::cut()
{
if (activeEditor())
activeEditor()->cut();
}

Qt 4 Livre Page 163 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

163

Le slot cut() invoque Editor::cut() sur lditeur actif. Nous ne montrons pas les slots
copy() et paste() puisquils suivent le mme modle.
void MainWindow::updateMenus()
{
bool hasEditor = (activeEditor()!= 0);
bool hasSelection = activeEditor()
&& activeEditor()->textCursor().hasSelection();
saveAction->setEnabled(hasEditor);
saveAsAction->setEnabled(hasEditor);
pasteAction->setEnabled(hasEditor);
cutAction->setEnabled(hasSelection);
copyAction->setEnabled(hasSelection);
closeAction->setEnabled(hasEditor);
closeAllAction->setEnabled(hasEditor);
tileAction->setEnabled(hasEditor);
cascadeAction->setEnabled(hasEditor);
nextAction->setEnabled(hasEditor);
previousAction->setEnabled(hasEditor);
separatorAction->setVisible(hasEditor);
if (activeEditor())
activeEditor()->windowMenuAction()->setChecked(true);
}

Le slot updateMenus() est invoqu ds quune fentre est active (et quand la dernire fentre
est ferme) pour mettre jour le systme de menus, en raison de la connexion signal-slot dans
le constructeur de MainWindow.
La plupart des options de menu ne sont intressantes que si une fentre est active, nous les
dsactivons donc quand ce nest pas le cas. Nous terminons en appelant setChecked() sur
QAction reprsentant la fentre active. Grce QActionGroup, nous navons pas besoin de
dcocher explicitement la fentre active prcdente.
void MainWindow::createMenus()
{
...
windowMenu = menuBar()->addMenu(tr("&Window"));
windowMenu->addAction(closeAction);
windowMenu->addAction(closeAllAction);
windowMenu->addSeparator();
windowMenu->addAction(tileAction);
windowMenu->addAction(cascadeAction);
windowMenu->addSeparator();
windowMenu->addAction(nextAction);
windowMenu->addAction(previousAction);
windowMenu->addAction(separatorAction);
...
}

Qt 4 Livre Page 164 Jeudi, 7. dcembre 2006 12:14 12

164

Qt4 et C++ : Programmation dinterfaces GUI

La fonction prive createMenus() introduit des actions dans le menu Window. Les actions
sont toutes typiques de tels menus et sont implmentes facilement laide des slots closeActiveWindow(), closeAllWindows(), tile() et cascade() de QWorkspace. A chaque
fois que lutilisateur ouvre une nouvelle fentre, elle est ajoute la liste dactions du menu
Window. (Ceci est effectu dans la fonction createEditor() tudie prcdemment.) Ds
que lutilisateur ferme une fentre dditeur, son action dans le menu Window est supprime
(tant donn que laction appartient la fentre dditeur), et elle disparat de ce menu.
void MainWindow::closeEvent(QCloseEvent *event)
{
workspace->closeAllWindows();
if (activeEditor()) {
event->ignore();
} else {
event->accept();
}
}

La fonction closeEvent() est rimplmente pour fermer toutes les fentres enfants, chaque
enfant reoit donc un vnement close. Si lun des widgets enfants "ignore" cet vnement
(parce que lutilisateur a annul une bote de message "modifications non enregistres"), nous
ignorons lvnement close pour MainWindow; sinon, nous lacceptons, ce qui a pour consquence de fermer la fentre complte. Si nous navions pas rimplment closeEvent() dans
MainWindow, lutilisateur naurait pas eu la possibilit de sauvegarder des modifications non
enregistres.
Nous avons termin notre analyse de MainWindow, nous pouvons donc passer limplmentation dEditor. La classe Editor reprsente une fentre enfant. Elle hrite de QTextEdit qui
propose une fonctionnalit de modification de texte. Tout comme nimporte quel widget Qt
peut tre employ comme une fentre autonome, tout widget Qt peut tre utilis comme une
fentre enfant dans un espace de travail MDI.
Voici la dfinition de classe :
class Editor: public QTextEdit
{
Q_OBJECT
public:
Editor(QWidget *parent = 0);
void newFile();
bool open();
bool openFile(const QString &fileName);
bool save();
bool saveAs();
QSize sizeHint() const;
QAction *windowMenuAction() const { return action; }

Qt 4 Livre Page 165 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

165

protected:
void closeEvent(QCloseEvent *event);
private slots:
void documentWasModified();
private:
bool okToContinue();
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QString curFile;
bool isUntitled;
QString fileFilters;
QAction *action;
};

Quatre des fonctions prives qui se trouvaient dans la classe MainWindow de lapplication
Spreadsheet sont galement prsentes dans la classe Editor: okToContinue(), saveFile(), setCurrentFile() et strippedName().
Editor::Editor(QWidget *parent)
: QTextEdit(parent)
{
action = new QAction(this);
action->setCheckable(true);
connect(action, SIGNAL(triggered()), this, SLOT(show()));
connect(action, SIGNAL(triggered()), this, SLOT(setFocus()));
isUntitled = true;
fileFilters = tr("Text files (*.txt)\n"
"All files (*)");
connect(document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
setWindowIcon(QPixmap(":/images/document.png"));
setAttribute(Qt::WA_DeleteOnClose);
}

Nous crons dabord un QAction reprsentant lditeur dans le menu Window de lapplication
et nous connectons cette action aux slots show() et setFocus().
Etant donn que nous autorisons les utilisateurs crer autant de fentres dditeurs quils le
souhaitent, nous devons prendre certaines dispositions concernant leur dnomination, dans le but
de faire une distinction avant le premier enregistrement. Un moyen courant de grer cette situation est dattribuer des noms qui incluent un chiffre (par exemple, document1.txt). Nous utilisons
la variable isUntitled pour faire une distinction entre les noms fournis par lutilisateur et les
noms crs par programme.

Qt 4 Livre Page 166 Jeudi, 7. dcembre 2006 12:14 12

166

Qt4 et C++ : Programmation dinterfaces GUI

Nous connectons le signal contentsChanged() du document au slot priv documentWasModified(). Ce slot appelle simplement setWindowModified(true).
Enfin, nous dfinissons lattribut Qt::WA_DeleteOnClose pour viter toute fuite de mmoire
quand lutilisateur ferme une fentre Editor.
Aprs le constructeur, il est logique dappeler newFile() ou open().
void Editor::newFile()
{
static int documentNumber = 1;
curFile = tr("document%1.txt").arg(documentNumber);
setWindowTitle(curFile + "[*]");
action->setText(curFile);
isUntitled = true;
++documentNumber;
}

La fonction newFile() gnre un nom au format document1.txt pour le nouveau document.


Ce code est plac dans newFile() plutt que dans le constructeur, parce quil ny a aucun
intrt numroter quand nous invoquons open() pour ouvrir un document existant dans un
Editor nouvellement cr. documentNumber tant dclar statique, il est partag par toutes
les instances dEditor.
Le symbole "[*]" dans le titre de la fentre rserve lemplacement de lastrisque qui doit
apparatre quand le fichier contient des modifications non sauvegardes sur des plates-formes
autres que Mac OS X. Nous avons parl de ce symbole dans le Chapitre 3.
bool Editor::open()
{
QString fileName =
QFileDialog::getOpenFileName(this, tr("Open"), ".",
fileFilters);
if (fileName.isEmpty())
return false;
return openFile(fileName);
}

La fonction open() essaie douvrir un fichier existant avec openFile().


bool Editor::save()
{
if (isUntitled) {
return saveAs();
} else {
return saveFile(curFile);
}
}

Qt 4 Livre Page 167 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 6

Gestion des dispositions

167

La fonction save() sappuie sur la variable isUntitled pour dterminer si elle doit appeler
saveFile() ou saveAs().
void Editor::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
event->accept();
} else {
event->ignore();
}
}

La fonction closeEvent() est rimplmente pour permettre lutilisateur de sauvegarder


des modifications non enregistres. La logique est code dans la fonction okToContinue()
qui ouvre une bote de message demandant, "Voulez-vous enregistrer vos modifications ?"
Si okToContinue() retourne true, nous acceptons lvnement close; sinon, nous "lignorons"
et la fentre nen sera pas affecte.
void Editor::setCurrentFile(const QString &fileName)
{
curFile = fileName;
isUntitled = false;
action->setText(strippedName(curFile));
document()->setModified(false);
setWindowTitle(strippedName(curFile) + "[*]");
setWindowModified(false);
}

La fonction setCurrentFile() est appele dans openFile() et saveFile() pour mettre


jour les variables curFile et isUntitled, pour dfinir le titre de la fentre et le texte de
laction et pour configurer lindicateur "modi?ed" du document en false. Ds que lutilisateur
modifie le texte dans lditeur, le QTextDocument sous-jacent met le signal contentsChanged()
et dfinit son indicateur "modified" interne en true.
QSize Editor::sizeHint() const
{
return QSize(72 * fontMetrics().width(x),
25 * fontMetrics().lineSpacing());
}

La fonction sizeHint() retourne une taille en fonction de la largeur de la lettre "x" et de la


hauteur de la ligne de texte. QWorkspace se sert de la taille requise pour attribuer une dimension
initiale la fentre.
Voici le fichier main.cpp de lapplication MDI Editor :
#include <QApplication>
#include "mainwindow.h"

Qt 4 Livre Page 168 Jeudi, 7. dcembre 2006 12:14 12

168

Qt4 et C++ : Programmation dinterfaces GUI

int main(int argc, char *argv[])


{
QApplication app(argc, argv);
QStringList args = app.arguments();
MainWindow mainWin;
if (args.count() > 1) {
for (int i = 1; i < args.count(); ++i)
mainWin.openFile(args[i]);
} else {
mainWin.newFile();
}
mainWin.show();
return app.exec();
}

Si lutilisateur spcifie des fichiers dans la ligne de commande, nous tentons de les charger.
Sinon, nous dmarrons avec un document vide. Les options de ligne de commande spcifiques
Qt, comme -style et -font, sont automatiquement supprimes de la liste darguments par
le constructeur QApplication. Donc si nous crivons :
mdieditor -style motif readme.txt

dans la ligne de commande, QApplication::arguments() retourne un QStringList contenant deux lments ("mdieditor" et "readme.txt") et lapplication MDI Editor dmarre
avec le document readme.txt.
MDI est un moyen de grer simultanment plusieurs documents. Sous Mac OS X, la meilleure
approche consiste utiliser plusieurs fentres de haut niveau. Cette technique est traite dans la
section "Documents multiples" du Chapitre 3.

Qt 4 Livre Page 169 Jeudi, 7. dcembre 2006 12:14 12

7
Traitement
des vnements
Au sommaire de ce chapitre
Rimplmenter les gestionnaires
dvnements
Installer des filtres dvnements
Rester ractif pendant un traitement
intensif

Les vnements sont dclenchs par le systme de fentrage ou par Qt en rponse


diverses circonstances. Quand lutilisateur enfonce ou relche une touche ou un bouton
de la souris, un vnement key ou mouse est dclench ; lorsquune fentre saffiche
pour la premire fois, un vnement paint est gnr pour informer la fentre
nouvellement affiche quelle doit se redessiner. La plupart des vnements sont
dclenchs en rponse des actions utilisateur, mais certains, comme les vnements
timer, sont dclenchs indpendamment par le systme.

Qt 4 Livre Page 170 Jeudi, 7. dcembre 2006 12:14 12

170

Qt4 et C++ : Programmation dinterfaces GUI

Quand nous programmons avec Qt, nous avons rarement besoin de penser aux vnements, parce que les widgets Qt mettent des signaux lorsque quelque chose de significatif
se produit. Les vnements deviennent utiles quand nous crivons nos propres widgets personnaliss ou quand nous voulons modifier le comportement des widgets Qt existants.
Il ne faut pas confondre vnements et signaux. En rgle gnrale, les signaux savrent utiles
lors de lemploi dun widget, alors que les vnements prsentent une utilit au moment de
limplmentation dun widget. Par exemple, quand nous utilisons QPushButton, nous nous
intressons plus son signal clicked() quaux vnements mouse ou key de bas niveau qui
provoquent lmission du signal. Cependant, si nous implmentons une classe telle que QPushButton, nous devons crire un code qui grera les vnements mouse et key et qui mettra le
signal clicked() si ncessaire.

Rimplmenter les gestionnaires dvnements


Dans Qt, un vnement est un objet qui hrite de QEvent. Qt gre plus dune centaine de types
dvnements, chacun deux tant identifi par une valeur dnumration. Par exemple,
QEvent::type() retourne QEvent::MouseButtonPress pour les vnements "bouton
souris enfonc".
De nombreux types dvnements exigent plus dinformations que ce qui peut tre stock dans
un objet QEvent ordinaire ; par exemple, les vnements "bouton souris enfonc" doivent stocker
quel bouton de la souris a dclench lvnement et lendroit o le pointeur de la souris se
trouvait quand lvnement sest dclench. Ces informations supplmentaires sont conserves
dans des sous-classes QEvent spciales, comme QMouseEvent.
Les vnements sont notifis aux objets par le biais de leur fonction event(), hrite de
QObject. Limplmentation de event() dans QWidget transmet les types les plus courants
dvnements des gestionnaires dvnements spcifiques, tels que mousePressEvent(),
keyPressEvent() et paintEvent().
Nous avons dj tudi plusieurs gestionnaires dvnements lorsque nous avons implment
MainWindow, IconEditor et Plotter dans les chapitres prcdents. Il existe beaucoup
dautres types dvnements rpertoris dans la documentation de rfrence de QEvent, et il
est aussi possible de crer des types dvnements personnaliss et denvoyer les vnements
soi-mme. Dans notre cas, nous analyserons deux types courants dvnements qui mritent
davantage dexplications : les vnements key et timer.
Les vnements key sont grs en rimplmentant keyPressEvent() et keyReleaseEvent(). Le widget Plotter rimplmente keyPressEvent(). Normalement, nous ne devons
rimplmenter que keyPressEvent() puisque les seules touches pour lesquelles il faut
contrler quelles ont t relches sont les touches de modification Ctrl, Maj et Alt, et vous pouvez
contrler leur tat dans un keyPressEvent() en utilisant QKeyEvent::modifiers().

Qt 4 Livre Page 171 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

171

Par exemple, si nous implmentions un widget CodeEditor, voici le code de sa fonction


keyPressEvent() qui devra interprter diffremment Home et Ctrl+Home:
void CodeEditor::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Home:
if (event->modifiers() & Qt::ControlModifier) {
goToBeginningOfDocument();
} else {
goToBeginningOfLine();
}
break;
case Qt::Key_End:
...
default:
QWidget::keyPressEvent(event);
}
}

Les touches de tabulation et de tabulation arrire (Maj+Tab) sont des cas particuliers. Elles
sont gres par QWidget::event() avant lappel de keyPressEvent(), avec la consigne de
transmettre le focus au widget suivant ou prcdent dans lordre de la chane de focus. Ce
comportement correspond habituellement ce que nous recherchons, mais dans un widget
CodeEditor, nous prfrerions que la touche Tab produise le dcalage dune ligne par rapport
la marge. Voici comment event() pourrait tre rimplment :
bool CodeEditor::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
insertAtCurrentPosition(\t);
return true;
}
}
return QWidget::event(event);
}

Si lvnement est li une touche sur laquelle lutilisateur a appuy, nous convertissons
lobjet QEvent en QKeyEvent et nous vrifions quelle touche a t presse. Sil sagit de la
touche Tab, nous effectuons un traitement et nous retournons true pour informer Qt que nous
avons gr lvnement. Si nous avions retourn false, Qt transmettrait lvnement au
widget parent.
Une approche plus intelligente pour implmenter les combinaisons de touches consiste se
servir de QAction. Par exemple, si goToBeginningOfLine() et goToBeginningOfDocument() sont des slots publics dans le widget CodeEditor, et si CodeEditor fait

Qt 4 Livre Page 172 Jeudi, 7. dcembre 2006 12:14 12

172

Qt4 et C++ : Programmation dinterfaces GUI

office de widget central dans une classe MainWindow, nous pourrions ajouter des combinaisons
de touches avec le code suivant :
MainWindow::MainWindow()
{
editor = new CodeEditor;
setCentralWidget(editor);
goToBeginningOfLineAction =
new QAction(tr("Go to Beginning of Line"), this);
goToBeginningOfLineAction->setShortcut(tr("Home"));
connect(goToBeginningOfLineAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfLine()));
goToBeginningOfDocumentAction =
new QAction(tr("Go to Beginning of Document"), this);
goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home"));
connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfDocument()));
...
}

Cela permet dajouter facilement des commandes un menu ou une barre doutils, comme
nous lavons vu dans le Chapitre 3. Si les commandes napparaissent pas dans linterface utilisateur, les objets QAction pourraient tre remplacs par un objet QShortcut, la classe
employe par QAction en interne pour prendre en charge les combinaisons de touches.
Par dfaut, les combinaisons de touches dfinies laide de QAction ou QShortcut sur un
widget sont actives ds que la fentre contenant le widget est active. Vous pouvez modifier ce
comportement grce QAction::setShortcutContext() ou QShortcut::setContext().
Lautre type courant dvnement est lvnement timer. Alors que la plupart des autres types
dvnements se dclenchent suite une action utilisateur, les vnements timer permettent
aux applications deffectuer un traitement intervalles rguliers. Les vnements timer
peuvent tre utiliss pour implmenter des curseurs clignotants et dautres animations, ou
simplement pour ractualiser laffichage.
Pour analyser les vnements timer, nous implmenterons un widget Ticker illustr en
Figure 7.1. Ce widget prsente une bannire qui dfile dun pixel vers la gauche toutes les
30 millisecondes. Si le widget est plus large que le texte, le texte est rpt autant de fois que
ncessaire pour remplir toute la largeur du widget.
Figure 7.1
Le widget Ticker

Voici le fichier den-tte :


#ifndef TICKER_H
#define TICKER_H

Qt 4 Livre Page 173 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

173

#include <QWidget>
class Ticker: public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public:
Ticker(QWidget *parent = 0);
void setText(const QString &newText);
QString text() const { return myText; }
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
private:
QString myText;
int offset;
int myTimerId;
};
#endif

Nous rimplmentons quatre gestionnaires dvnements dans Ticker, dont trois que nous
avons dj vus auparavant : timerEvent(), showEvent() et hideEvent().
Analysons prsent limplmentation :
#include <QtGui>
#include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}

Le constructeur initialise la variable offset 0. Les coordonnes x auxquelles le texte est


dessin sont calcules partir de la valeur offset. Les ID du timer sont toujours diffrents de
zro, nous utilisons donc 0 pour indiquer quaucun timer na t dmarr.
void Ticker::setText(const QString &newText)
{
myText = newText;
update();
updateGeometry();
}

Qt 4 Livre Page 174 Jeudi, 7. dcembre 2006 12:14 12

174

Qt4 et C++ : Programmation dinterfaces GUI

La fonction setText() dtermine le texte afficher. Elle invoque update() pour demander le
rafrachissement de laffichage et updateGeometry() pour informer tout gestionnaire de disposition responsable du widget Ticker dun changement de taille requise.
QSize Ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}

La fonction sizeHint() retourne lespace requis par le texte comme tant la taille idale du
widget. QWidget::fontMetrics() renvoie un objet QFontMetrics qui peut tre interrog
pour obtenir des informations lies la police du widget. Dans ce cas, nous demandons la taille
exige par le texte. (Puisque le premier argument de QFontMetrics::size() est un indicateur
qui nest pas ncessaire pour les chanes simples, nous transmettons simplement 0.)
void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text());
if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(),
Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}

La fonction paintEvent() dessine le texte avec QPainter::drawText(). Elle dtermine la


quantit despace horizontal exig par le texte laide de fontMetrics(), puis dessine
le texte autant de fois que ncessaire pour remplir toute la largeur du widget, en tenant compte
du dcalage offset.
void Ticker::showEvent(QShowEvent * /* event */)
{
myTimerId = startTimer(30);
}

La fonction showEvent() lance un timer. Lappel de QObject::startTimer() retourne un


ID, qui peut tre utilis ultrieurement pour identifier le timer. QObject prend en charge
plusieurs timers indpendants, chacun possdant son propre intervalle de temps. Aprs lappel
de startTimer(), Qt dclenche un vnement timer environ toutes les 30 millisecondes ; la
prcision dpend du systme dexploitation sous-jacent.
Nous aurions pu appeler startTimer() dans le constructeur de Ticker, mais nous conomisons
des ressources en ne dclenchant des vnements timer que lorsque le widget est visible.
void Ticker::timerEvent(QTimerEvent *event)

Qt 4 Livre Page 175 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

175

{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text()))
offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);
}
}

La fonction timerEvent() est invoque intervalles rguliers par le systme. Elle incrmente
offset de 1 pour simuler un mouvement, encadrant la largeur du texte. Puis elle fait dfiler le
contenu du widget dun pixel vers la gauche grce QWidget::scroll(). Nous aurions pu
simplement appeler update() au lieu de scroll(), mais scroll() est plus efficace, parce
quelle dplace simplement les pixels existants lcran et ne dclenche un vnement paint
que pour la zone nouvellement affiche du widget (une bande dun pixel de large dans ce cas).
Si lvnement timer ne correspond pas au timer qui nous intresse, nous le transmettons
notre classe de base.
void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
}

La fonction hideEvent() invoque QObject::killTimer() pour arrter le timer.


Les vnements timer sont de bas niveau, et si nous avons besoin de plusieurs timers, il peut
tre fastidieux dassurer le suivi de tous les ID de timer. Dans de telles situations, il est gnralement plus facile de crer un objet QTimer pour chaque timer. QTimer met le signal
timeout() chaque intervalle de temps. QTimer propose aussi une interface pratique pour les
timers usage unique (les timers qui ne chronomtrent quune seule fois).

Installer des filtres dvnements


Lune des fonctionnalits vraiment puissante du modle dvnement de Qt est quune instance
de QObject peut tre configure de manire contrler les vnements dune autre instance de
QObject avant mme que cette dernire ne les dtecte.
Supposons que nous avons un widget CustomerInfoDialog compos de plusieurs QLineEdit et que nous voulons utiliser la barre despace pour activer le prochain QLineEdit. Ce
comportement inhabituel peut se rvler appropri pour une application interne laquelle les
utilisateurs sont forms. Une solution simple consiste driver QLineEdit et rimplmenter
keyPressEvent() pour appeler focusNextChild(), comme dans le code suivant :
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{

Qt 4 Livre Page 176 Jeudi, 7. dcembre 2006 12:14 12

176

Qt4 et C++ : Programmation dinterfaces GUI

if (event->key() == Qt::Key_Space) {
focusNextChild();
} else {
QLineEdit::keyPressEvent(event);
}
}

Cette approche prsente un inconvnient de taille : si nous utilisons plusieurs types de widgets
dans le formulaire (par exemple, QComboBoxes et QSpinBoxes), nous devons galement les
driver pour quils affichent le mme comportement. Il existe une meilleure solution : CustomerInfoDialog contrle les vnements "bouton souris enfonc" de ses widgets enfants et
implmente le comportement ncessaire dans le code de contrle. Pour ce faire, vous utiliserez
des filtres dvnements. Dfinir un filtre dvnement implique deux tapes :
1. enregistrer lobjet contrleur avec lobjet cible en appelant installEventFilter() sur la
cible ;
2. grer les vnements de lobjet cible dans la fonction eventFilter() de lobjet contrleur.
Le code du constructeur de CustomerInfoDialog constitue un bon endroit pour enregistrer
lobjet contrleur :
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent)
: QDialog(parent)
{
...
firstNameEdit->installEventFilter(this);
lastNameEdit->installEventFilter(this);
cityEdit->installEventFilter(this);
phoneNumberEdit->installEventFilter(this);
}

Ds que le filtre dvnement est enregistr, les vnements qui sont envoys aux widgets
firstNameEdit, lastNameEdit, cityEdit et phoneNumberEdit sont dabord transmis la
fonction eventFilter() de CustomerInfoDialog avant dtre envoys vers la destination
prvue.
Voici la fonction eventFilter() qui reoit les vnements :
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
{
if (target == firstNameEdit || target == lastNameEdit
|| target == cityEdit || target == phoneNumberEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space) {
focusNextChild();
return true;
}
}
}
return QDialog::eventFilter(target, event);
}

Qt 4 Livre Page 177 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

177

Nous vrifions tout dabord que le widget cible est un des QLineEdit. Si lvnement est li
lenfoncement dune touche, nous le convertissons en QKeyEvent et nous vrifions quelle
touche a t presse. Si la touche enfonce correspondait la barre despace, nous invoquons
focusNextChild() pour activer le prochain widget dans la chane de focus, et nous retournons true pour dire Qt que nous avons gr lvnement. Si nous avions renvoy false, Qt
aurait envoy lvnement sa cible prvue, ce qui aurait introduit un espace parasite dans
QLineEdit.
Si le widget cible nest pas un QLineEdit, ou si lvnement ne rsulte pas de lenfoncement
de la barre despace, nous passons le contrle limplmentation de eventFilter() de la
classe de base. Le widget cible aurait aussi pu tre un widget que la classe de base, QDialog,
est en train de contrler. (Dans Qt 4.1, ce nest pas le cas pour QDialog. Cependant, dautres
classes de widgets Qt, comme QScrollArea, surveillent certains de leurs widgets enfants pour
diverses raisons.)
Qt propose cinq niveaux auxquels des vnements peuvent tre traits et filtrs :
1. Nous pouvons rimplmenter un gestionnaire dvnements spcifique.
Rimplmenter des gestionnaires dvnements comme mousePressEvent(), keyPressEvent() et paintEvent() est de loin le moyen le plus commun de traiter des vnements.
Nous en avons dj vu de nombreux exemples.
2. Nous pouvons rimplmenter QObject::event().
En rimplmentant la fonction event(), nous avons la possibilit de traiter des vnements avant quils natteignent les gestionnaires dvnements spcifiques. Cette approche
est surtout employe pour redfinir la signification par dfaut de la touche Tab, comme
expliqu prcdemment. Elle est aussi utilise pour grer des types rares dvnements
pour lesquels il nexiste aucun gestionnaire dvnements spcifique (par exemple,
QEvent::HoverEnter). Quand nous rimplmentons event(), nous devons appeler la
fonction event() de la classe de base pour grer les cas que nous ne grons pas explicitement.
3. Nous pouvons installer un filtre dvnement sur un seul QObject.
Lorsquun objet a t enregistr avec installEventFilter(), tous les vnements pour
lobjet cible sont dabord envoys la fonction eventFilter() de lobjet contrleur. Si
plusieurs filtres dvnements sont installs sur le mme objet, les filtres sont activs tour
de rle, du plus rcemment install au premier install.
4. Nous pouvons installer un filtre dvnement sur lobjet QApplication.
Lorsquun filtre dvnement a t enregistr pour qApp (lunique objet de QApplication), chaque vnement de chaque objet de lapplication est envoy la fonction eventFilter() avant dtre transmis un autre filtre dvnement. Cette technique est trs utile
pour le dbogage. Elle peut aussi tre employe pour grer des vnements mouse transmis
aux widgets dsactivs que QApplication ignore normalement.

Qt 4 Livre Page 178 Jeudi, 7. dcembre 2006 12:14 12

178

Qt4 et C++ : Programmation dinterfaces GUI

5. Nous pouvons driver QApplication et rimplmenter notify().


Qt appelle QApplication::notify() pour envoyer un vnement. Rimplmenter cette
fonction est le seul moyen de rcuprer tous les vnements avant quun filtre dvnement
quelconque nait lopportunit de les analyser. Les filtres dvnements sont gnralement
plus pratiques, parce que le nombre de filtres concomitants nest pas limit alors quil ne
peut y avoir quune seule fonction notify().
De nombreux types dvnements, dont les vnements mouse et key, peuvent se propager. Si
lvnement na pas t gr lors de son trajet vers son objet cible ou par lobjet cible luimme, tout le traitement de lvnement est rpt, mais cette fois-ci avec comme cible le
parent de lobjet cible initial. Ce processus se poursuit, en passant dun parent lautre,
jusqu ce que lvnement soit gr ou que lobjet de niveau suprieur soit atteint.
La Figure 7.2 vous montre comment un vnement "bouton souris enfonc" est transmis
dun enfant vers un parent dans une bote de dialogue. Quand lutilisateur appuie sur une
touche, lvnement est dabord envoy au widget actif, dans ce cas le QCheckBox en bas
droite. Si le QCheckBox ne gre pas lvnement, Qt lenvoie au QGroupBox et enfin
lobjet QDialog.
Figure 7.2
Propagation dun vnement dans une bote
de dialogue

Window Title
QDialog

QGroupBox

QCheckBox

QCheckBox

QCheckBox

QCheckBox

Rester ractif pendant un traitement intensif


Quand nous appelons QApplication::exec(), nous dmarrons une boucle dvnement de
Qt. Qt met quelques vnements au dmarrage pour afficher et dessiner les widgets. Puis, la
boucle dvnement est excute, contrlant en permanence si des vnements se sont dclenchs
et envoyant ces vnements aux QObject dans lapplication.
Pendant quun vnement est trait, des vnements supplmentaires peuvent tre dclenchs
et ajouts la file dattente dvnements de Qt. Si nous passons trop de temps traiter un
vnement particulier, linterface utilisateur ne rpondra plus. Par exemple, tout vnement
dclench par le systme de fentrage pendant que lapplication enregistre un fichier sur le
disque ne sera pas trait tant que le fichier na pas t sauvegard. Pendant lenregistrement,

Qt 4 Livre Page 179 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

179

lapplication ne rpondra pas aux requtes du systme de fentrage demandant le rafrachissement


de laffichage.
Une solution consiste utiliser plusieurs threads : un thread pour linterface utilisateur de lapplication et un autre pour accomplir la sauvegarde du fichier (ou toute autre opration de longue
dure). De cette faon, linterface utilisateur de lapplication continuera rpondre pendant
lenregistrement du fichier. Nous verrons comment y parvenir dans le Chapitre 18.
Une solution plus simple consiste appeler frquemment QApplication::processEvents() dans le code de sauvegarde du fichier. Cette fonction demande Qt de traiter tout
vnement en attente, puis retourne le contrle lappelant. En fait, QApplication::exec() ne se limite pas une simple boucle while autour dun appel de fonction
processEvents().
Voici par exemple comment vous pourriez obtenir de linterface utilisateur quelle reste ractive laide de processEvents(), face au code de sauvegarde de fichier de lapplication
Spreadsheet (voir Chapitre 4) :
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
...
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
qApp->processEvents();
}
return true;
}

Cette approche prsente un risque : lutilisateur peut fermer la fentre principale alors que
lapplication est toujours en train deffectuer la sauvegarde, ou mme cliquer sur File > Save
une seconde fois, ce qui provoque un comportement indtermin. La solution la plus simple
ce problme est de remplacer
qApp->processEvents();

par
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);

qui demande Qt dignorer les vnements mouse et key.


Nous avons souvent besoin dafficher un QProgressDialog alors quune longue opration se
produit. QProgressDialog propose une barre de progression qui informe lutilisateur de
lavancement de lopration. QProgressDialog propose aussi un bouton Cancel qui permet

Qt 4 Livre Page 180 Jeudi, 7. dcembre 2006 12:14 12

180

Qt4 et C++ : Programmation dinterfaces GUI

lutilisateur dannuler lopration. Voici le code permettant denregistrer une feuille de calcul
avec cette approche :
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
...
QProgressDialog progress(this);
progress.setLabelText(tr("Saving %1").arg(fileName));
progress.setRange(0, RowCount);
progress.setModal(true);
for (int row = 0; row < RowCount; ++row) {
progress.setValue(row);
qApp->processEvents();
if (progress.wasCanceled()) {
file.remove();
return false;
}
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
return true;
}

Nous crons un QProgressDialog avec NumRows comme nombre total dtapes. Puis, pour
chaque ligne, nous appelons setValue() pour mettre jour la barre de progression. QProgressDialog calcule automatiquement un pourcentage en divisant la valeur actuelle davancement par le nombre total dtapes. Nous invoquons QApplication::processEvents()
pour traiter tout vnement de rafrachissement daffichage, tout clic ou toute touche enfonce
par lutilisateur (par exemple pour permettre lutilisateur de cliquer sur Cancel). Si lutilisateur
clique sur Cancel, nous annulons la sauvegarde et nous supprimons le fichier.
Nous nappelons pas show() sur QProgressDialog parce que les botes de dialogue de
progression le font. Si lopration se rvle plus courte, peut-tre parce que le fichier enregistrer est petit ou parce que lordinateur est rapide, QProgressDialog le dtectera et ne
saffichera pas du tout.
En complment du multithread et de lutilisation de QProgressDialog, il existe une manire
totalement diffrente de traiter les longues oprations : au lieu daccomplir le traitement la
demande de lutilisateur, nous pouvons ajourner ce traitement jusqu ce que lapplication soit
inactive. Cette solution peut tre envisage si le traitement peut tre interrompu et repris en
toute scurit, parce que nous ne pouvons pas prdire combien de temps lapplication sera
inactive.

Qt 4 Livre Page 181 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 7

Traitement des vnements

181

Dans Qt, cette approche peut tre implmente en utilisant un timer de 0 milliseconde. Ces
timers chronomtrent ds quil ny a pas dvnements en attente. Voici un exemple dimplmentation de timerEvent() qui prsente cette approche :
void Spreadsheet::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
while (step < MaxStep &&!qApp->hasPendingEvents()) {
performStep(step);
++step;
}
} else {
QTableWidget::timerEvent(event);
}
}

Si hasPendingEvents() retourne true, nous interrompons le traitement et nous redonnons


le contrle Qt. Le traitement reprendra quand Qt aura gr tous ses vnements en attente.

Qt 4 Livre Page 182 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 183 Jeudi, 7. dcembre 2006 12:14 12

8
Graphiques 2D et 3D
Au sommaire de ce chapitre
Dessiner avec QPainter
Transformations du painter
Affichage de haute qualit avec QImage
Impression
Graphiques avec OpenGL

Les graphiques 2D de Qt se basent sur la classe QPainter. QPainter peut tracer des
formes gomtriques (points, lignes, rectangles, ellipses, arcs, cordes, segments, polygones et courbes de Bzier), de mme que des objets pixmaps, des images et du texte.
De plus, QPainter prend en charge des fonctionnalits avances, telles que lanticrnelage (pour les bords du texte et des formes), le mlange alpha, le remplissage dgrad
et les tracs de vecteur. QPainter supporte aussi les transformations qui permettent de
dessiner des graphiques 2D indpendants de la rsolution.
QPainter peut galement tre employe pour dessiner sur un "priphrique de dessin" tel
quun QWidget, QPixmap ou QImage. Cest utile quand nous crivons des widgets
personnaliss ou des classes dlments personnalises avec leurs propres aspect et apparence. Il est aussi possible dutiliser QPainter en association avec QPrinter pour imprimer et gnrer des fichiers PDF. Cela signifie que nous pouvons souvent nous servir du
mme code pour afficher des donnes lcran et pour produire des rapports imprims.
Il existe une alternative QPainter: OpenGL. OpenGL est une bibliothque standard
permettant de dessiner des graphiques 2D et 3D. Le module QtOpenGL facilite lintgration de code OpenGL dans des applications Qt.

Qt 4 Livre Page 184 Jeudi, 7. dcembre 2006 12:14 12

184

Qt4 et C++ : Programmation dinterfaces GUI

Dessiner avec QPainter


Pour commencer dessiner sur un priphrique de dessin (gnralement un widget), nous
crons simplement un QPainter et nous transmettons un pointeur au priphrique. Par exemple :
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
...
}

Nous avons la possibilit de dessiner diffrentes formes laide des fonctions draw...() de
QPainter. La Figure 8.1 rpertorie les plus importantes. Les paramtres de QPainter influencent
la faon de dessiner.
Figure 8.1
Les fonctions draw...()
de QPainter les plus
frquemment utilises

p3

p2

(x1, y1)
(x, y)
(x2, y2)
drawPoint()

drawLine()
p3

p2

p1

p1

drawPolyline()

p1

drawPoints()

p4
drawLines()

(x, y)

h
w
drawRoundRect()
(x, y)

p4

(x, y)

(x, y)

p1
drawPolygon()

(x, y)

w
drawRect()

w
drawArc()

w
drawEllipse()

w
drawChord()

w
drawPie()

drawPixmap()

drawPath()

(x, y)

(x, y)

Ag
drawText()

(x, y)

p3

p2

p3

p2

p4

p4

Qt 4 Livre Page 185 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

185

Certains dentre eux proviennent du priphrique, dautres sont initialiss leurs valeurs par
dfaut. Les trois principaux paramtres sont le crayon, le pinceau et la police :
Le crayon est utilis pour tracer des lignes et les contours des formes. Il est constitu dune
couleur, dune largeur, dun style de trait, de capuchon et de jointure (Figures 8.2 et 8.3).
Le pinceau permet de remplir des formes gomtriques. Il est compos normalement dune
couleur et dun style, mais peut galement appliquer une texture (un pixmap rpt
linfini) ou un dgrad (Voir Figure 8.4).
La police est utilise pour dessiner le texte. Une police possde de nombreux attributs, dont
une famille et une taille.
Figure 8.2
Styles de capuchon
et de jointure
FlatCap

SquareCap

MiterJoin

BevelJoin

Figure 8.3
Styles de crayon

RoundCap

RoundJoin

Largeur de trait
3
2

NoPen
SolidLine
DashLine
DotLine
DashDotLine
DashDotDotLine

Figure 8.4
Styles prdfinis
de pinceau
SolidPattern

Dense1Pattern

Dense2Pattern

Dense3Pattern

Dense4Pattern

Dense5Pattern

Dense6Pattern

Dense7Pattern

HorPattern

VerPattern

CrossPattern

BDiagPattern

FDiagPattern

DiagCrossPat.

NoBrush

Qt 4 Livre Page 186 Jeudi, 7. dcembre 2006 12:14 12

186

Qt4 et C++ : Programmation dinterfaces GUI

Ces paramtres peuvent tre modifis tout moment en appelant setPen(), setBrush() et
setFont() avec un objet QPen, QBrush ou QFont.
Figure 8.5
Exemples de formes
gomtriques

(a) Une ellipse

(b) Un segment

(c) Une courbe de Bzier

Analysons quelques exemples pratiques. Voici le code permettant de dessiner lellipse illustre
en Figure 8.5 (a) :
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
painter.drawEllipse(80, 80, 400, 240);

Lappel de setRenderHint() active lanticrnelage, demandant QPainter dutiliser diverses


intensits de couleur sur les bords pour rduire la distorsion visuelle qui se produit habituellement quand les contours dune forme sont convertis en pixels. Les bords sont donc plus homognes sur les plates-formes et les priphriques qui prennent en charge cette fonctionnalit.
Voici le code permettant de dessiner le segment illustr en Figure 8.5 (b) :
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 15, Qt::SolidLine, Qt::RoundCap,
Qt::MiterJoin));
painter.setBrush(QBrush(Qt::blue, Qt::DiagCrossPattern));
painter.drawPie(80, 80, 400, 240, 60 * 16, 270 * 16);

Les deux derniers arguments de drawPie() sont exprims en seizimes de degr.


Voici le code permettant de tracer la courbe de Bzier illustre en Figure 8.5 (c) :
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
QPainterPath path;
path.moveTo(80, 320);
path.cubicTo(200, 80, 320, 80, 480, 320);
painter.setPen(QPen(Qt::black, 8));
painter.drawPath(path);

La classe QPainterPath peut spcifier des formes vectorielles arbitraires en regroupant des
lments graphiques de base : droites, ellipses, polygones, arcs, courbes de Bzier cubiques et

Qt 4 Livre Page 187 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

187

quadratiques et autres tracs de dessin. Les tracs de dessin constituent la primitive graphique
ultime, dans le sens o on peut dsigner toute forme ou toute combinaison de formes par le
terme de trac.
Un trac spcifie un contour, et la zone dcrite par le contour peut tre remplie laide dun
pinceau. Dans lexemple de la Figure 8.5 (c), nous navons pas utilis de pinceau, seul le contour
est donc dessin.
Les trois exemples prcdents utilisent des modles de pinceau intgrs (Qt::SolidPattern,
Qt::DiagCrossPattern et Qt::NoBrush). Dans les applications modernes, les remplissages
dgrads reprsentent une alternative populaire aux remplissages monochromes. Les dgrads
reposent sur une interpolation de couleur permettant dobtenir des transitions homognes entre
deux ou plusieurs couleurs. Ils sont frquemment utiliss pour produire des effets 3D ; par
exemple, le style Plastique se sert des dgrads pour afficher des QPushButton.
Qt prend en charge trois types de dgrads : linaire, conique et circulaire. Lexemple Oven
Timer dans la section suivante combine les trois types de dgrads dans un seul widget pour le
rendre plus rel.
Les dgrads linaires sont dfinis par deux points de contrle et par une srie "darrts
couleur" sur la ligne qui relie ces deux points. Par exemple, le dgrad linaire de la
Figure 8.6 est cr avec le code suivant :
QLinearGradient gradient(50, 100, 300, 350);
gradient.setColorAt(0.0, Qt::white);
gradient.setColorAt(0.2, Qt::green);
gradient.setColorAt(1.0, Qt::black);

Figure 8.6
Les pinceaux dgrads
de QPainter

(x1, y1)
(xc, yc)

(x2, y2)

QLinearGradient

(xc, yc)

(xf, yf)

QRadialGradient

QRadialGradient

Nous spcifions trois couleurs trois positions diffrentes entre les deux points de contrle.

Qt 4 Livre Page 188 Jeudi, 7. dcembre 2006 12:14 12

188

Qt4 et C++ : Programmation dinterfaces GUI

Les positions sont indiques comme des valeurs virgule flottante entre 0 et 1, o 0 correspond au premier point de contrle et 1 au second. Les couleurs situes entre les interruptions
spcifies sont interpoles.
Les dgrads circulaires sont dfinis par un centre (xc, yc), un rayon r et une focale (xf, yf),
en complment des interruptions de dgrad. Le centre et le rayon spcifient un cercle. Les
couleurs se diffusent vers lextrieur partir de la focale, qui peut tre le centre ou tout
autre point dans le cercle.
Les dgrads coniques sont dfinis par un centre (xc, yc) et un angle . Les couleurs se
diffusent autour du point central comme la trajectoire de la petite aiguille dune montre.

Jusqu prsent, nous avons mentionn les paramtres de crayon, de pinceau et de police de
QPainter. En plus de ceux-ci, QPainter propose dautres paramtres qui influencent la faon
dont les formes et le texte sont dessins :
Le pinceau de fond est utilis pour remplir le fond des formes gomtriques (sous le
modle de pinceau), du texte ou des bitmaps quand le mode arrire-plan est configur en
Qt::OpaqueMode (la valeur par dfaut est Qt::TransparentMode).
Lorigine du pinceau correspond au point de dpart des modles de pinceau, normalement
le coin suprieur gauche du widget.
La zone daction est la zone du priphrique de dessin qui peut tre peinte. Dessiner en
dehors de cette zone na aucun effet.
Le viewport, la fentre et la matrice "world" dterminent la manire dont les coordonnes
logiques de QPainter correspondent aux coordonnes physiques du priphrique de dessin.
Par dfaut, celles-ci sont dfinies de sorte que les systmes de coordonnes logiques et
physiques concident. Les systmes de coordonnes sont abords dans la prochaine section.
Le mode de composition spcifie comment les pixels qui viennent dtre dessins doivent
interagir avec les pixels dj prsents sur le priphrique de dessin. La valeur par dfaut est
"source over," o les pixels sont dessins au-dessus des pixels existants. Ceci nest pris en
charge que sur certains priphriques et est trait ultrieurement dans ce chapitre.
Vous pouvez sauvegarder ltat courant dun module de rendu nomm painter tout moment
sur une pile interne en appelant save() et en le restaurant plus tard en invoquant restore().
Cela permet par exemple de changer temporairement certains paramtres, puis de les rinitialiser
leurs valeurs antrieures, comme nous le verrons dans la prochaine section.

Transformations du painter
Avec le systme de coordonnes par dfaut du QPainter, le point (0, 0) se situe dans le coin
suprieur gauche du priphrique de dessin ; les coordonnes x augmentent vers la droite et les
coordonnes y sont orientes vers le bas. Chaque pixel occupe une zone dune taille de 1 1
dans le systme de coordonnes par dfaut.

Qt 4 Livre Page 189 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

189

Il est important de comprendre que le centre dun pixel se trouve aux coordonnes dun "demi
pixel".Par exemple, le pixel en haut gauche couvre la zone entre les points (0, 0) et (1, 1) et
son centre se trouve (0,5, 0,5). Si nous demandons QPainter de dessiner un pixel (100,
100) par exemple, il se rapprochera du rsultat en dcalant les coordonnes de +0,5 dans les
deux sens, le pixel sera ainsi centr sur le point (100,5, 100,5).
Cette distinction peut sembler plutt acadmique de prime abord, mais elle prsente des consquences importantes en pratique. Premirement, le dcalage de +0,5 ne se produit que si lanticrnelage est dsactiv (par dfaut) ; si lanticrnelage est activ et si nous essayons de
dessiner un pixel (100, 100) en noir, QPainter coloriera les quatre pixels (99,5, 99,5), (99,5,
100,5), (100,5, 99,5) et (100,5, 100,5) en gris clair pour donner limpression quun pixel se
trouve exactement au point de rencontre de ces quatre pixels. Si cet effet ne vous plat pas,
vous pouvez lviter en spcifiant les coordonnes dun demi pixel, par exemple, (100,5,
100,5).
Lorsque vous tracez des formes comme des lignes, des rectangles et des ellipses, des rgles
similaires sappliquent. La Figure 8.7 vous montre comment le rsultat dun appel de
drawRect(2, 2, 6, 5) varie en fonction de la largeur du crayon quand lanticrnelage est
dsactiv. Il est notamment important de remarquer quun rectangle de 6 5 dessin avec une
largeur de crayon de 1 couvre en fait une zone de 7 6. Cest diffrent des anciens kits
doutils, y compris des versions antrieures de Qt, mais cest essentiel pour pouvoir dessiner
des images vectorielles rellement ajustables et indpendantes de la rsolution.
(0, 0)

(0,

Pas de crayon

Largeur de
crayon 1

Largeur de
crayon 2

Largeur de
crayon 3

Figure 8.7
Dessiner un rectangle de 6 5 sans anticrnelage

Maintenant que nous avons compris le systme de coordonnes par dfaut, nous pouvons nous
concentrer davantage sur la manire de le modifier en utilisant le viewport, la fentre et la
matrice world de QPainter. (Dans ce contexte, le terme de "fentre" ne se rfre pas une
fentre au sens de widget de niveau suprieur, et le "viewport" na rien voir avec le viewport
de QScrollArea.)
Le viewport et la fentre sont troitement lis. Le viewport est un rectangle arbitraire spcifi
en coordonnes physiques. La fentre spcifie le mme rectangle, mais en coordonnes logiques.

Qt 4 Livre Page 190 Jeudi, 7. dcembre 2006 12:14 12

190

Qt4 et C++ : Programmation dinterfaces GUI

Au moment du trac, nous indiquons des points en coordonnes logiques qui sont converties en
coordonnes physiques de manire algbrique linaire, en fonction des paramtres actuels de la
fentre et du viewport.
Par dfaut, le viewport et la fentre correspondent au rectangle du priphrique. Par exemple,
si ce dernier est un widget de 320 200, le viewport et la fentre reprsentent le mme rectangle de 320 200 avec son coin suprieur gauche la position (0, 0). Dans ce cas, les systmes
de coordonnes logiques et physiques sont identiques.
Le mcanisme fentre-viewport est utile pour que le code de dessin soit indpendant de la
taille ou de la rsolution du priphrique de dessin. Par exemple, si nous voulons que les coordonnes logiques stendent de (50, 50) (+50, +50) avec (0, 0) au milieu, nous pouvons
configurer la fentre comme suit :
painter.setWindow(-50, -50, 100, 100);

La paire (50, 50) spcifie lorigine et la paire (100, 100) indique la largeur et la hauteur.
Cela signifie que les coordonnes logiques (50, 50) correspondent dsormais aux coordonnes physiques (0, 0), et que les coordonnes logiques (+50, +50) correspondent aux coordonnes physiques (320, 200) (voir Figure 8.8). Dans cet exemple, nous navons pas modifi
le viewport.
(-50, -50)

(0,0)

(-30, -20)

(64, 60)

(+10, +20)
fentre

(192, 140)
(+50, +50)

viewport

(320 ,200)

Figure 8.8
Convertir des coordonnes logiques en coordonnes physiques

Venons-en prsent la matrice world. La matrice world est une matrice de transformation qui
sapplique en plus de la conversion fentre-viewport. Elle nous permet de translater, mettre
lchelle, pivoter et faire glisser les lments que nous dessinons. Par exemple, si nous voulions
dessiner un texte un angle de 45, nous utiliserions ce code :
QMatrix matrix;
matrix.rotate(45.0);
painter.setMatrix(matrix);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));

Les coordonnes logiques transmises drawText() sont transformes par la matrice world,
puis mappes aux coordonnes physiques grce aux paramtres fentre-viewport.

Qt 4 Livre Page 191 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

191

Si nous spcifions plusieurs transformations, elles sont appliques dans lordre dans lequel
nous les avons indiques. Par exemple, si nous voulons utiliser le point (10, 20) comme pivot
pour la rotation, nous pouvons translater la fentre, accomplir la rotation, puis translater
nouveau la fentre vers sa position dorigine :
QMatrix matrix;
matrix.translate(-10.0, -20.0);
matrix.rotate(45.0);
matrix.translate(+10.0, +20.0);
painter.setMatrix(matrix);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));

Il existe un moyen plus simple de spcifier des transformations : exploiter les fonctions pratiques
translate(), scale(), rotate() et shear() de QPainter:
painter.translate(-10.0, -20.0);
painter.rotate(45.0);
painter.translate(+10.0, +20.0);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));

Cependant, si nous voulons appliquer les mmes transformations de faon rptitive, il est plus
efficace de les stocker dans un objet QMatrix et de configurer la matrice world sur le painter
ds que les transformations sont ncessaires.
Figure 8.9
Le widget OvenTimer

Pour illustrer les transformations du painter, nous allons analyser le code du widget OvenTimer
prsent en Figure 8.9. Ce widget est conu daprs les minuteurs de cuisine que nous utilisions avant que les fours soient quips dhorloges intgres. Lutilisateur peut cliquer sur un cran
pour dfinir la dure. La molette tournera automatiquement dans le sens inverse des aiguilles
dune montre jusqu 0, cest ce moment-l que OvenTimer mettra le signal timeout().
class OvenTimer: public QWidget
{
Q_OBJECT
public:
OvenTimer(QWidget *parent = 0);
void setDuration(int secs);

Qt 4 Livre Page 192 Jeudi, 7. dcembre 2006 12:14 12

192

Qt4 et C++ : Programmation dinterfaces GUI

int duration() const;


void draw(QPainter *painter);
signals:
void timeout();
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
QDateTime finishTime;
QTimer *updateTimer;
QTimer *finishTimer;
};

La classe OvenTimer hrite de QWidget et rimplmente deux fonctions virtuelles : paintEvent() et mousePressEvent().
const
const
const
const
const

double DegreesPerMinute = 7.0;


double DegreesPerSecond = DegreesPerMinute / 60;
int MaxMinutes = 45;
int MaxSeconds = MaxMinutes * 60;
int UpdateInterval = 1;

Nous dfinissons dabord quelques constantes qui contrlent laspect et lapparence du minuteur
de four.
OvenTimer::OvenTimer(QWidget *parent)
: QWidget(parent)
{
finishTime = QDateTime::currentDateTime();
updateTimer = new QTimer(this);
connect(updateTimer, SIGNAL(timeout()), this, SLOT(update()));
finishTimer = new QTimer(this);
finishTimer->setSingleShot(true);
connect(finishTimer, SIGNAL(timeout()), this, SIGNAL(timeout()));
connect(finishTimer, SIGNAL(timeout()), updateTimer, SLOT(stop()));
}

Dans le constructeur, nous crons deux objets QTimer : updateTimer est employ pour actualiser lapparence du widget toutes les secondes, et finishTimer met le signal timeout() du
widget quand le minuteur du four atteint 0. finishTimer ne doit minuter quune seule fois, nous
appelons donc setSingleShot(true) ; par dfaut, les minuteurs se dclenchent de manire rpte jusqu ce quils soient stopps ou dtruits. Le dernier appel de connect() permet darrter
la mise jour du widget chaque seconde quand le minuteur est inactif.
void OvenTimer::setDuration(int secs)
{
if (secs > MaxSeconds) {

Qt 4 Livre Page 193 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

193

secs = MaxSeconds;
} else if (secs <= 0) {
secs = 0;
}
finishTime = QDateTime::currentDateTime().addSecs(secs);
if (secs > 0) {
updateTimer->start(UpdateInterval * 1000);
finishTimer->start(secs * 1000);
} else {
updateTimer->stop();
finishTimer->stop();
}
update();
}

La fonction setDuration() dfinit la dure du minuteur du four en nombre donn de secondes.


Nous calculons lheure de fin en ajoutant la dure lheure courante (obtenue partir de
QDateTime::currentDateTime()) et nous la stockons dans la variable prive finishTime.
Nous finissons en invoquant update() pour redessiner le widget avec la nouvelle dure.
La variable finishTime est de type QDateTime. Vu que la variable contient une date et une
heure, nous vitons ainsi tout bogue lorsque lheure courante se situe avant minuit et lheure de
fin aprs minuit.
int OvenTimer::duration() const
{
int secs = QDateTime::currentDateTime().secsTo(finishTime);
if (secs < 0)
secs = 0;
return secs;
}

La fonction duration() retourne le nombre de secondes restantes avant que le minuteur ne


sarrte. Si le minuteur est inactif, nous retournons 0.
void OvenTimer::mousePressEvent(QMouseEvent *event)
{
QPointF point = event->pos() - rect().center();
double theta = atan2(-point.x(), -point.y()) * 180 / 3.14159265359;
setDuration(duration() + int(theta / DegreesPerSecond));
update();
}

Si lutilisateur clique sur le widget, nous recherchons le cran le plus proche grce une
formule mathmatique subtile mais efficace, et nous utilisons le rsultat pour dfinir la
nouvelle dure. Puis nous planifions un raffichage. Le cran sur lequel lutilisateur a cliqu
sera dsormais en haut et se dplacera dans le sens inverse des aiguilles dune montre au fur et
mesure que le temps scoule jusqu atteindre 0.

Qt 4 Livre Page 194 Jeudi, 7. dcembre 2006 12:14 12

194

Qt4 et C++ : Programmation dinterfaces GUI

void OvenTimer::paintEvent(QPaintEvent * /* event */)


{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
int side = qMin(width(), height());
painter.setViewport((width() - side) / 2, (height() - side) / 2,
side, side);
painter.setWindow(-50, -50, 100, 100);
draw(&painter);
}

Dans paintEvent(), nous dfinissons le viewport de sorte quil devienne le carr le pus grand
qui peut entrer dans le widget et nous dfinissons la fentre en rectangle (50, 50, 100, 100),
cest--dire le rectangle de 100 _ 100 allant de (50, 50) (+50, +50). La fonction modle
qMin() retourne le plus bas de ses deux arguments. Nous appelons ensuite la fonction draw()
qui se chargera du dessin.
Figure 8.10
Le widget OvenTimer
en trois tailles diffrentes

Si nous navions pas dfini le viewport en carr, le minuteur du four se transformerait en


ellipse quand le widget serait redimensionn en rectangle (non carr). Pour viter de telles
dformations, nous devons configurer le viewport et la fentre en rectangles ayant le mme
format dimage.
Analysons prsent le code de dessin :
void OvenTimer::draw(QPainter *painter)
{
static const int triangle[3][2] = {
{ -2, -49 }, { +2, -49 }, { 0, -47 }
};
QPen thickPen(palette().foreground(), 1.5);
QPen thinPen(palette().foreground(), 0.5);
QColor niceBlue(150, 150, 200);

Qt 4 Livre Page 195 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

195

painter->setPen(thinPen);
painter->setBrush(palette().foreground());
painter->drawPolygon(QPolygon(3, &triangle[0][0]));

Nous commenons par dessiner le petit triangle qui symbolise la position 0 en haut du widget.
Le triangle est spcifi par trois coordonnes codes et nous utilisons drawPolygon() pour
lafficher.
Laspect pratique du mcanisme fentre-viewport, cest que nous avons la possibilit de coder
les coordonnes utilises dans les commandes de dessin et de toujours obtenir un bon comportement lors du redimensionnement.
QConicalGradient coneGradient(0, 0, -90.0);
coneGradient.setColorAt(0.0, Qt::darkGray);
coneGradient.setColorAt(0.2, niceBlue);
coneGradient.setColorAt(0.5, Qt::white);
coneGradient.setColorAt(1.0, Qt::darkGray);
painter->setBrush(coneGradient);
painter->drawEllipse(-46, -46, 92, 92);

Nous traons le cercle extrieur et nous le remplissons avec un dgrad conique. Le centre du
dgrad se trouve la position (0, 0) et son angle est de 90.
QRadialGradient haloGradient(0, 0, 20, 0, 0);
haloGradient.setColorAt(0.0, Qt::lightGray);
haloGradient.setColorAt(0.8, Qt::darkGray);
haloGradient.setColorAt(0.9, Qt::white);
haloGradient.setColorAt(1.0, Qt::black);
painter->setPen(Qt::NoPen);
painter->setBrush(haloGradient);
painter->drawEllipse(-20, -20, 40, 40);

Nous nous servons dun dgrad radial pour le cercle intrieur. Le centre et la focale du
dgrad se situent (0, 0). Le rayon du dgrad est gal 20.
QLinearGradient knobGradient(-7, -25, 7, -25);
knobGradient.setColorAt(0.0, Qt::black);
knobGradient.setColorAt(0.2, niceBlue);
knobGradient.setColorAt(0.3, Qt::lightGray);
knobGradient.setColorAt(0.8, Qt::white);
knobGradient.setColorAt(1.0, Qt::black);
painter->rotate(duration() * DegreesPerSecond);
painter->setBrush(knobGradient);
painter->setPen(thinPen);
painter->drawRoundRect(-7, -25, 14, 50, 150, 50);
for (int i = 0; i <= MaxMinutes; ++i) {
if (i % 5 == 0) {

Qt 4 Livre Page 196 Jeudi, 7. dcembre 2006 12:14 12

196

Qt4 et C++ : Programmation dinterfaces GUI

painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 25,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->rotate(-DegreesPerMinute);
}
}

Nous appelons rotate() pour faire pivoter le systme de coordonnes du painter. Dans
lancien systme de coordonnes, la marque correspondant 0 minute se trouvait en haut ;
maintenant, elle se dplace vers lendroit appropri pour le temps restant. Nous dessinons le
bouton rectangulaire aprs la rotation, parce que son orientation dpend de langle de cette
rotation.
Dans la boucle for, nous dessinons les graduations tout autour du cercle extrieur et les
nombres pour chaque multiple de 5 minutes. Le texte est dessin dans un rectangle invisible en
dessous de la graduation. A la fin de chaque itration, nous faisons pivoter le painter dans le
sens des aiguilles dune montre de 7, ce qui correspond une minute. La prochaine fois que
nous dessinons une graduation, elle sera une position diffrente autour du cercle, mme si les
coordonnes transmises aux appels de drawLine() et drawText() sont toujours les mmes.
Le code de la boucle for souffre dun dfaut mineur qui deviendrait rapidement apparent si
nous effectuions davantage ditrations. A chaque fois que nous appelons rotate(), nous
multiplions la matrice courante par une matrice de rotation, engendrant ainsi une nouvelle
matrice world. Les problmes darrondis associs larithmtique en virgule flottante entranent une matrice world trs imprcise. Voici un moyen de rcrire le code pour viter ce
problme, en excutant save() et restore() pour sauvegarder et recharger la matrice de
transformation originale pour chaque itration :
for (int i = 0; i <= MaxMinutes; ++i) {
painter->save();
painter->rotate(-i * DegreesPerMinute);
if (i % 5 == 0) {
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 25,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->restore();
}

Qt 4 Livre Page 197 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

197

Il existe un autre moyen dimplmenter un minuteur de four : calculer les positions (x, y) soimme, en utilisant sin() et cos() pour trouver les positions autour du cercle. Mais nous
aurions encore d employer une translation et une rotation pour dessiner le texte un certain
angle.

Affichage de haute qualit avec QImage


Au moment de dessiner, vous pourriez avoir trouver un compromis entre vitesse et prcision.
Par exemple, sous X11 et Mac OS X, le dessin sur un QWidget ou un QPixmap repose sur le
moteur de dessin natif de la plate-forme. Sous X11, ceci rduit au maximum les communications avec le serveur X ; seules les commandes de dessin sont envoyes plutt que les donnes
de limage. Le principal inconvnient de cette approche, cest que le champ daction de Qt est
limit celui de la prise en charge native de la plate-forme :
Sous X11, des fonctionnalits, telles que lanticrnelage et le support des coordonnes
fractionnaires, ne sont disponibles que si lextension X Render se trouve sur le serveur X.
Sous Mac OS X, le moteur graphique natif crnel sappuie sur des algorithmes diffrents
pour dessiner des polygones par rapport X11 et Windows, avec des rsultats lgrement
diffrents.
Quand la prcision est plus importante que lefficacit, nous pouvons dessiner un QImage et
copier le rsultat lcran. Celui-ci utilise toujours le moteur de dessin interne de Qt, aboutissant ainsi des rsultats identiques sur toutes les plates-formes. La seule restriction, cest que
le QImage, sur lequel nous dessinons, doit tre cr avec un argument de type
QImage::Format_RGB32 ou QImage::Format_ARGB32_Premultiplied.
Le format ARGB32 prmultipli est presque identique au format traditionnel ARGB32
(0xaarrggbb), la diffrence prs que les canaux rouge, vert et bleu sont "prmultiplis" par
le canal alpha. Cela signifie que les valeurs RVB, qui stendent normalement de 0x00 0xFF,
sont mises lchelle de 0x00 la valeur alpha. Par exemple, une couleur bleue transparente
50 % est reprsente par 0x7F0000FF en format ARGB32, mais par 0x7F00007F en format
ARGB32 prmultipli. De mme, une couleur vert fonc transparente 75 % reprsente par
0x3F008000 en format ARGB32 deviendrait 0x3F002000 en format ARGB32 prmultipli.
Supposons que nous souhaitons utiliser lanticrnelage pour dessiner un widget et que nous
voulons obtenir de bons rsultats mme sous des systmes X11 sans lextension X Render.
Voici la syntaxe du gestionnaire paintEvent() dorigine, qui se base sur X Render pour
lanticrnelage :
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
draw(&painter);
}

Qt 4 Livre Page 198 Jeudi, 7. dcembre 2006 12:14 12

198

Qt4 et C++ : Programmation dinterfaces GUI

Voil comment rcrire la fonction paintEvent() du widget pour exploiter le moteur graphique
de Qt indpendant de la plate-forme :
void MyWidget::paintEvent(QPaintEvent *event)
{
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
QPainter imagePainter(&image);
imagePainter.initFrom(this);
imagePainter.setRenderHint(QPainter::Antialiasing, true);
imagePainter.eraseRect(rect());
draw(&imagePainter);
imagePainter.end();
QPainter widgetPainter(this);
widgetPainter.drawImage(0, 0, image);
}

Nous crons un QImage de la mme taille que le widget en format ARGB32 prmultipli, et un
QPainter pour dessiner sur limage. Lappel de initFrom() initialise le crayon, le fond et la
police en fonction du widget. Nous effectuons notre dessin avec QPainter comme dhabitude,
et la fin, nous rutilisons lobjet QPainter pour copier limage sur le widget.
Cette approche produit dexcellents rsultats identiques sur toutes les plates-formes, lexception
de laffichage de la police qui dpend des polices installes.
Une fonctionnalit particulirement puissante du moteur graphique de Qt est sa prise en charge
des modes de composition. Ceux-ci spcifient comment un pixel source et un pixel de destination fusionnent pendant le dessin. Ceci sapplique toutes les oprations de dessin, y compris
le crayon, le pinceau, le dgrad et limage.
Le mode de composition par dfaut est QImage::CompositionMode_SourceOver, ce qui
signifie que le pixel source (celui que nous dessinons) remplace le pixel de destination (le pixel
existant) de telle manire que le composant alpha de la source dfinit sa translucidit. Dans la
Figure 8.11, vous voyez un papillon moiti transparent dessin sur un motif damiers avec
les diffrents modes.
Figure 8.11
Les modes de composition
de QPainter
Source

SourceOver

SourceIn

SourceOut

SourceAtop

Clear

Destination

DestinationOver

DestinationIn

DestinationOut

DestinationAtop

Xor

Les modes de composition sont dfinis laide de QPainter::setCompositionMode(). Par


exemple, voici comment crer un QImage qui combine en XOR le papillon et le motif damiers :

Qt 4 Livre Page 199 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

199

QImage resultImage = checkerPatternImage;


QPainter painter(&resultImage);
painter.setCompositionMode(QPainter::CompositionMode_Xor);
painter.drawImage(0, 0, butterflyImage);

Il faut tre conscient du problme li au fait que lopration QImage::CompositionMode_Xor sapplique au canal alpha. Cela signifie que si nous appliquons XOR (OU
exclusif) la couleur blanche (0xFFFFFFFF), nous obtenons une couleur transparente
(0x00000000), et pas noire (0xFF000000).

Impression
Limpression dans Qt est quivalente au dessin sur QWidget, QPixmap ou QImage. Plusieurs
tapes sont ncessaires :
1. crer un QPrinter qui fera office de priphrique de dessin ;
2. ouvrir un QPrintDialog, qui permet lutilisateur de choisir une imprimante et de configurer certaines options ;
3. crer un QPainter pour agir sur le QPrinter;
4. dessiner une page laide de QPainter;
5. appeler QPrinter::newPage() pour passer la page suivante ;
6. rpter les tapes 4 et 5 jusqu ce que toutes les pages soient imprimes.
Sous Windows et Mac OS X, QPrinter utilise les pilotes dimprimante du systme. Sous
Unix, il gnre un PostScript et lenvoie lp ou lpr (ou au programme dfini en excutant
QPrinter::setPrintProgram()). QPrinter peut aussi servir gnrer des fichiers PDF en
appelant setOutputFormat(QPrinter::PdfFormat).
Commenons par quelques exemples simples qui simpriment tous sur une seule page. Le premier
exemple imprime un QImage(voir Figure 8.12) :
void PrintWindow::printImage(const QImage &image)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec()) {
QPainter painter(&printer);
QRect rect = painter.viewport();
QSize size = image.size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewport(rect.x(), rect.y(),
size.width(), size.height());
painter.setWindow(image.rect());
painter.drawImage(0, 0, image);
}
}

Qt 4 Livre Page 200 Jeudi, 7. dcembre 2006 12:14 12

200

Qt4 et C++ : Programmation dinterfaces GUI

Figure 8.12
Imprimer un QImage

Nous supposons que la classe PrintWindow possde une variable membre appele printer
de type QPrinter. Nous aurions simplement pu crer QPrinter sur la pile dans printImage(), mais les paramtres de lutilisateur auraient t perdus entre deux impressions.
Nous crons un QPrintDialog et nous invoquons exec() pour lafficher. Il retourne true si
lutilisateur a cliqu sur le bouton OK ; sinon il retourne false. Aprs lappel de exec(),
lobjet QPrinter est prt tre utilis. (Il est aussi possible dimprimer sans utiliser QPrintDialog, en appelant directement des fonctions membres de QPrinter pour configurer les
divers aspects.)
Nous crons ensuite un QPainter pour dessiner sur le QPrinter. Nous dfinissons la fentre
en rectangle de limage et le viewport en un rectangle du mme format dimage, puis nous
dessinons limage la position (0, 0).
Par dfaut, la fentre de QPainter est initialise de sorte que limprimante semble avoir une
rsolution similaire lcran (en gnral entre 72 et 100 points par pouce), ce qui facilite la
rutilisation du code de dessin du widget pour limpression. Ici, ce ntait pas un problme,
parce que nous avons dfini notre propre fentre.
Imprimer des lments qui ne stendent pas sur plus dune page se rvle trs simple, mais
de nombreuses applications ont besoin dimprimer plusieurs pages. Pour celles-ci, nous
devons dessiner une page la fois et appeler newPage() pour passer la page suivante.

Qt 4 Livre Page 201 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

201

Ceci soulve un problme : dterminer la quantit dinformations que nous pouvons imprimer sur chaque page. Il existe deux approches principales pour grer les documents multipages
avec Qt :
Nous pouvons convertir nos donnes en format HTML et les afficher avec QTextDocument, le
moteur de texte de Qt.
Nous pouvons effectuer le dessin et la rpartition sur les pages manuellement.
Nous allons analyser les deux approches. En guise dexemple, nous allons imprimer un guide
des fleurs : une liste de noms de fleurs, chacun comprenant une description sous forme de
texte. Chaque entre du guide est stocke sous forme de chane au format "nom: description,"
par exemple :

Miltonopsis santanae: une des espces dorchides les plus dangereuses.

Vu que les donnes relatives chaque fleur sont reprsentes par une seule chane, nous
pouvons reprsenter toutes les fleurs dans le guide avec un QStringList. Voici la fonction qui
imprime le guide des fleurs au moyen du moteur de texte de Qt :
void PrintWindow::printFlowerGuide(const QStringList &entries)
{
QString html;
foreach (QString entry, entries) {
QStringList fields = entry.split(": ");
QString title = Qt::escape(fields[0]);
QString body = Qt::escape(fields[1]);
html += "<table width=\"100%\" border=1 cellspacing=0>\n"
"<tr><td bgcolor=\"lightgray\"><font size=\"+1\">"
"<b><i>" + title + "</i></b></font>\n<tr><td>" + body
+ "\n</table>\n<br>\n";
}
printHtml(html);
}

La premire tape consiste convertir QStringList en format HTML. Chaque fleur est reprsente par un tableau HTML avec deux cellules. Nous excutons Qt::escape() pour remplacer
les caractres spciaux "&", "<", ">" par les entits HTML correspondantes ("&amp;", "&lt;",
"&gt;"). Nous appelons ensuite printHtml() pour imprimer le texte.
void PrintWindow::printHtml(const QString &html)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec()) {
QPainter painter(&printer);
QTextDocument textDocument;
textDocument.setHtml(html);
textDocument.print(&printer);
}
}

Qt 4 Livre Page 202 Jeudi, 7. dcembre 2006 12:14 12

202

Qt4 et C++ : Programmation dinterfaces GUI

La fonction printHtml() ouvre un QPrintDialog et se charge dimprimer un document


HTML. Elle peut tre rutilise "telle quelle" dans nimporte quelle application Qt pour imprimer
des pages HTML arbitraires.
Figure 8.13
Imprimer un guide
des fleurs avec
QTextDocument

Aponogeton distachyos

Trapa natans

The Cape pondweed (water hawthorn) is a deciduous perennial that has floating, oblong,
dark green leaves which are sometimes splashed purple. The waxy-white flowers have a
characteristic 'forked' appearance, sweet scent and black stamens. They appear from early
spring until fall. They grow in deep or shallow water and spread to 1.2 m.

The Jesuit's nut (or water chestnut) has mid-green diamond-shaped leaves with deeply
toothed edges that grow in neat rosettes. The center of each leaf is often marked with deep
purple blotches. White flowers are produced in summer. Each floating plant can spread to 23
cm.

Cabomba caroliniana

Zantedeschia aethiopica

The Fish grass (or fanwort or Washington grass) is a useful oxygenator for ponds. It is a
deciduous or semi-evergreen submerged perennial that is used by fish as a source of food
and as a place in which to spawn. Plants form spreading hummocks of fan-shaped, coarsly
divided leaves which are bright green. Tiny white flowers appear in the summer.

The Arum lily is a South African native that grows well in shallow water. It flowers throughout
the summer, with the erect funnel-shaped spathes being held well above the arrow-shaped
glossy, deep green leaves. Each spathe surrounds a central yellow spadix. The leaves and
flowering stems arise from a tuber. Plants can reach up to 90 cm in height, spreading to 45
cm.

Caltha palustris
The Marsh marigold (or kingcup) is a deciduous perennial that grows in shallow water around
the edges of ponds. It is equally well suited to a bog garden, moist rock garden or
herbaceous border. The rounded dark green leaves set off its large, cup-shaped
golden-yellow flowers. Plants can grow to 60 cm in height, with a spread of 45 cm. The
double-flowered cultivar 'Flore Plena' only reaches 10 cm.

Ceratophyllum demersum
The Hornwort is a deciduous perennial that produces feathery submerged foliage. It
sometimes floats and spreads over a large area. It is a good oxygenator and grows best in
cool deep water. It has no roots.

Juncus effusus 'Spiralis'


The Corkscrew rush is a tufted evergreen perennial with mid-green leafless stems which are
twisted and curled like a corkscrew. The stems often lie on the ground. The greenish-brown
flowers appear in summer. Plants are best used at the edge of a pond, so that the stems can
be seen against the reflective water surface. Strong plants can send up 90 cm-tall twisted
shoots which are used in modern flower arranging.

Nuphar lutea
The Yellow water lily has small (6 cm diameter) yellow flowers that are bottle-shaped and
sickly smelling. They are held above a mat of broad, oval, mid-green leaves which are about
40 cm wide, giving the plant a spread of up to 1.5 m. The seed heads are rounded and warty.
This hardy deciduous perennial thrives in deep water, in sun or shade, and is useful for a
water-lily effect where Nymphaea will not grow.

Orontium aquaticum
The Golden club's flowers lack the spathe typical of other aroids, leaving the central yellow
and white spadix to provide color. A deciduous perennial, the golden club grows equally well
in shallow or deep water. In spring, the pencil-like flower spikes (spadices) emerge from
among the floating mass of waxy leaves which are a bluish or greyish green. Plants grow to
25 cm high spreading up to 60 cm. Large seeds develop later in the summer and are used to
propagate plants while they are still fresh.

Convertir un document au format HTML et utiliser QTextDocument pour limprimer est de


loin la mthode la plus pratique pour imprimer des rapports et dautres documents complexes.
Ds que vous avez besoin dun niveau de contrle suprieur, vous pouvez envisager de grer la
mise en page et le dessin manuellement. Voyons maintenant comment nous pouvons utiliser
cette approche pour imprimer le guide des fleurs (voir Figure 8.13). Voil la nouvelle fonction
printFlowerGuide():
void PrintWindow::printFlowerGuide(const QStringList &entries)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec()) {
QPainter painter(&printer);
QList<QStringList> pages;
paginate(&painter, &pages, entries);
printPages(&painter, pages);
}
}

Aprs avoir configur limprimante et construit le painter, nous appelons la fonction paginate() pour dterminer quelle entre doit apparatre sur quelle page. Vous obtenez donc une
liste de QStringList, chacun contenant les entres dune page. Nous transmettons ce rsultat
printPages().

Qt 4 Livre Page 203 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

203

Supposons, par exemple, que le guide des fleurs contient 6 entres, que nous appellerons A, B,
C, D, E et F. Imaginons maintenant quil y a suffisamment de place pour A et B sur la premire
page, pour C, D et E sur la deuxime page et pour F sur la troisime page. La liste pages
contiendrait donc la liste [A, B] la position dindex 0, la liste [C, D, E] la position
dindex 1 et la liste [F] la position dindex 2.
void PrintWindow::paginate(QPainter *painter, QList<QStringList> *pages,
const QStringList &entries)
{
QStringList currentPage;
int pageHeight = painter->window().height() - 2 * LargeGap;
int y = 0;
foreach (QString entry, entries) {
int height = entryHeight(painter, entry);
if (y + height > pageHeight &&!currentPage.empty()) {
pages->append(currentPage);
currentPage.clear();
y = 0;
}
currentPage.append(entry);
y += height + MediumGap;
}
if (!currentPage.empty())
pages->append(currentPage);
}

La fonction paginate() rpartit les entres du guide des fleurs sur les pages. Elle se base sur
la fonction entryHeight() qui calcule la hauteur dune entre. Elle tient galement compte
des espaces vides verticaux en haut et en bas de la page, de taille LargeGap.
Nous parcourons les entres et nous les ajoutons la page en cours jusqu ce quune entre
nait plus suffisamment de place sur cette page ; puis nous ajoutons la page en cours la liste
pages et nous commenons une nouvelle page.
int PrintWindow::entryHeight(QPainter *painter, const QString &entry)
{
QStringList fields = entry.split(": ");
QString title = fields[0];
QString body = fields[1];
int textWidth = painter->window().width() - 2 * SmallGap;
int maxHeight = painter->window().height();
painter->setFont(titleFont);
QRect titleRect = painter->boundingRect(0, 0, textWidth, maxHeight,
Qt::TextWordWrap, title);
painter->setFont(bodyFont);
QRect bodyRect = painter->boundingRect(0, 0, textWidth, maxHeight,
Qt::TextWordWrap, body);
return titleRect.height() + bodyRect.height() + 4 * SmallGap;
}

Qt 4 Livre Page 204 Jeudi, 7. dcembre 2006 12:14 12

204

Qt4 et C++ : Programmation dinterfaces GUI

La fonction entryHeight() se sert de QPainter::boundingRect() pour calculer lespace


vertical ncessaire pour une entre. La Figure 8.14 prsente la disposition dune entre et la
signification des constantes SmallGap et MediumGap.
Figure 8.14
La disposition
dune entre

SmallGap

SmallGap
SmallGap
Titre

SmallGap
SmallGap
Corps

MediumGap

void PrintWindow::printPages(QPainter *painter,


const QList<QStringList> &pages)
{
int firstPage = printer.fromPage() - 1;
if (firstPage >= pages.size())
return;
if (firstPage == -1)
firstPage = 0;
int lastPage = printer.toPage() - 1;
if (lastPage == -1 || lastPage >= pages.size())
lastPage = pages.size() - 1;
int numPages = lastPage - firstPage + 1;
for (int i = 0; i < printer.numCopies(); ++i) {
for (int j = 0; j < numPages; ++j) {
if (i!= 0 || j!= 0)
printer.newPage();
int index;
if (printer.pageOrder() == QPrinter::FirstPageFirst) {
index = firstPage + j;
} else {
index = lastPage - j;
}
printPage(painter, pages[index], index + 1);
}
}
}

SmallGap

Qt 4 Livre Page 205 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

205

Le rle de la fonction printPages() est dimprimer chaque page laide de printPage()


dans le bon ordre et les bonnes quantits. Grce QPrintDialog, lutilisateur peut demander
plusieurs copies, spcifier une plage dimpression ou demander les pages en ordre inverse.
Cest nous dhonorer ces options ou de les dsactiver au moyen de QPrintDialog::setEnabledOptions().
Nous dterminons tout dabord la plage imprimer. Les fonctions fromPage() et toPage()
de QPrinter retournent les numros de page slectionns par lutilisateur, ou 0 si aucune
plage na t choisie. Nous soustrayons 1, parce que notre liste pages est indexe partir de 0,
et nous dfinissons firstPage et lastPage de sorte couvrir la totalit de la plage si lutilisateur na rien prcis.
Puis nous imprimons chaque page. La boucle externe for effectue une itration autant de fois
que ncessaire pour produire le nombre de copies demand par lutilisateur. La plupart des
pilotes dimprimante prennent en charge les copies multiples, QPrinter::numCopies()
retourne toujours 1 pour celles-ci. Si le pilote ne peut pas grer plusieurs copies, numCopies() renvoie le nombre de copies demand par lutilisateur, et lapplication se charge
dimprimer ce nombre de copies. (Dans lexemple QImage prcdent, nous avons ignor
numCopies() pour une question de simplicit.)
Figure 8.15
Imprimer un guide des
fleurs avec QPainter

Aponogeton distachyos

Nuphar lutea

The Cape pondweed (water hawthorn) is a deciduous perennial that has floating, oblong, dark green
leaves which are sometimes splashed purple. The waxy-white flowers have a characteristic 'forked'
appearance, sweet scent and black stamens. They appear from early spring until fall. They grow in
deep or shallow water and spread to 1.2 m.

The Yellow water lily has small (6 cm diameter) yellow flowers that are bottle-shaped and sickly
smelling. They are held above a mat of broad, oval, mid-green leaves which are about 40 cm wide,
giving the plant a spread of up to 1.5 m. The seed heads are rounded and warty. This hardy deciduous
perennial thrives in deep water, in sun or shade, and is useful for a water-lily effect where Nymphaea
will not grow.

Cabomba caroliniana
Orontium aquaticum
The Fish grass (or fanwort or Washington grass) is a useful oxygenator for ponds. It is a deciduous or
semi-evergreen submerged perennial that is used by fish as a source of food and as a place in which to
spawn. Plants form spreading hummocks of fan-shaped, coarsly divided leaves which are bright green.
Tiny white flowers appear in the summer.

The Golden club's flowers lack the spathe typical of other aroids, leaving the central yellow and white
spadix to provide color. A deciduous perennial, the golden club grows equally well in shallow or deep
water. In spring, the pencil-like flower spikes (spadices) emerge from among the floating mass of waxy
leaves which are a bluish or greyish green. Plants grow to 25 cm high spreading up to 60 cm. Large
seeds develop later in the summer and are used to propagate plants while they are still fresh.

Caltha palustris
The Marsh marigold (or kingcup) is a deciduous perennial that grows in shallow water around the
edges of ponds. It is equally well suited to a bog garden, moist rock garden or herbaceous border.
The rounded dark green leaves set off its large, cup-shaped golden-yellow flowers. Plants can grow to
60 cm in height, with a spread of 45 cm. The double-flowered cultivar 'Flore Plena' only reaches 10 cm.

Trapa natans
The Jesuit's nut (or water chestnut) has mid-green diamond-shaped leaves with deeply toothed edges
that grow in neat rosettes. The center of each leaf is often marked with deep purple blotches. White
flowers are produced in summer. Each floating plant can spread to 23 cm.

Ceratophyllum demersum
The Hornwort is a deciduous perennial that produces feathery submerged foliage. It sometimes floats
and spreads over a large area. It is a good oxygenator and grows best in cool deep water. It has no
roots.

Zantedeschia aethiopica
The Arum lily is a South African native that grows well in shallow water. It flowers throughout the
summer, with the erect funnel-shaped spathes being held well above the arrow-shaped glossy, deep
green leaves. Each spathe surrounds a central yellow spadix. The leaves and flowering stems arise
from a tuber. Plants can reach up to 90 cm in height, spreading to 45 cm.

Juncus effusus 'Spiralis'


The Corkscrew rush is a tufted evergreen perennial with mid-green leafless stems which are twisted
and curled like a corkscrew. The stems often lie on the ground. The greenish-brown flowers appear in
summer. Plants are best used at the edge of a pond, so that the stems can be seen against the
reflective water surface. Strong plants can send up 90 cm-tall twisted shoots which are used in modern
flower arranging.

La boucle interne for parcourt les pages. Si la page nest pas la premire page, nous appelons
newPage() pour supprimer lancienne page de la mmoire et pour commencer dessiner sur
une nouvelle page. Nous invoquons printPage() pour dessiner chaque page.
void PrintWindow::printPage(QPainter *painter,
const QStringList &entries, int pageNumber)
{
painter->save();

Qt 4 Livre Page 206 Jeudi, 7. dcembre 2006 12:14 12

206

Qt4 et C++ : Programmation dinterfaces GUI

painter->translate(0, LargeGap);
foreach (QString entry, entries) {
QStringList fields = entry.split(": ");
QString title = fields[0];
QString body = fields[1];
printBox(painter, title, titleFont, Qt::lightGray);
printBox(painter, body, bodyFont, Qt::white);
painter->translate(0, MediumGap);
}
painter->restore();
painter->setFont(footerFont);
painter->drawText(painter->window(),
Qt::AlignHCenter | Qt::AlignBottom,
QString::number(pageNumber));
}

La fonction printPage() parcourt toutes les entres du guide des fleurs et les imprime grce
deux appels de printBox(): un pour le titre (le nom de la fleur) et un pour le corps (sa
description). Elle dessine galement le numro de page centr au bas de la page (voir
Figure 8.16).
Figure 8.16
La disposition dune page
du guide des fleurs

(0, 0)
LargeGap

page Height

Large Gap

(0, Large Gap)

Zone d'impression
des entres de fleur

[Numro de page]

void PrintWindow::printBox(QPainter *painter, const QString &str,


const QFont &font, const QBrush &brush)
{
painter->setFont(font);
int boxWidth = painter->window().width();
int textWidth = boxWidth - 2 * SmallGap;
int maxHeight = painter->window().height();

Fentre

Qt 4 Livre Page 207 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

207

QRect textRect = painter->boundingRect(SmallGap, SmallGap,


textWidth, maxHeight,
Qt::TextWordWrap, str);
int boxHeight = textRect.height() + 2 * SmallGap;
painter->setPen(QPen(Qt::black, 2, Qt::SolidLine));
painter->setBrush(brush);
painter->drawRect(0, 0, boxWidth, boxHeight);
painter->drawText(textRect, Qt::TextWordWrap, str);
painter->translate(0, boxHeight);
}

La fonction printBox() trace les contours dune bote, puis dessine le texte lintrieur.

Graphiques avec OpenGL


OpenGL est une API standard permettant dafficher des graphiques 2D et 3D. Les applications
Qt peuvent tracer des graphiques 3D en utilisant le module QtOpenGL, qui se base sur la bibliothque OpenGL du systme. Cette section suppose que vous connaissez dj OpenGL. Si vous
dcouvrez OpenGL, consultez dabord http://www.opengl.org/ pour en apprendre davantage.
Figure 8.17
Lapplication
Tetrahedron

Dessiner des graphiques avec OpenGL dans une application Qt se rvle trs facile : vous
devez driver QGLWidget, rimplmenter quelques fonctions virtuelles et relier lapplication aux bibliothques QtOpenGL et OpenGL. Etant donn que QGLWidget hrite de
QWidget, la majorit des notions que nous connaissons dj peuvent sappliquer ce cas.
La principale diffrence cest que nous utilisons des fonctions OpenGL standards pour dessiner
en lieu et place de QPainter.

Qt 4 Livre Page 208 Jeudi, 7. dcembre 2006 12:14 12

208

Qt4 et C++ : Programmation dinterfaces GUI

Pour vous montrer comment cela fonctionne, nous allons analyser le code de lapplication
Tetrahedron prsente en Figure 8.17. Lapplication prsente un ttradre en 3D, ou une
matrice quatre faces, chaque face ayant une couleur diffrente. Lutilisateur peut faire pivoter
le ttradre en appuyant sur un bouton de la souris et en le faisant glisser. Il peut aussi dfinir la
couleur dune face en double-cliquant dessus et en choisissant une couleur dans le QColorDialog qui souvre.
class Tetrahedron: public QGLWidget
{
Q_OBJECT
public:
Tetrahedron(QWidget *parent = 0);
protected:
void initializeGL();
void resizeGL(int width, int height);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
private:
void draw();
int faceAtPosition(const QPoint &pos);
GLfloat rotationX;
GLfloat rotationY;
GLfloat rotationZ;
QColor faceColors[4];
QPoint lastPos;
};

La classe Tetrahedron hrite de QGLWidget. Les fonctions initializeGL(), resizeGL()


et paintGL() sont rimplmentes dans QGLWidget. Les gestionnaires dvnements mouse
sont rimplments dans QWidget comme dhabitude.
Tetrahedron::Tetrahedron(QWidget *parent)
: QGLWidget(parent)
{
setFormat(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer));
rotationX = -21.0;
rotationY = -57.0;
rotationZ = 0.0;
faceColors[0] = Qt::red;
faceColors[1] = Qt::green;
faceColors[2] = Qt::blue;
faceColors[3] = Qt::yellow;
}

Qt 4 Livre Page 209 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

209

Dans le constructeur, nous appelons QGLWidget::setFormat() pour spcifier le contexte


daffichage OpenGL et nous initialisons les variables prives de la classe.
void Tetrahedron::initializeGL()
{
qglClearColor(Qt::black);
glShadeModel(GL_FLAT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}

La fonction initializeGL() est invoque une seule fois avant que paintGL() soit appele.
Cest donc dans le code de cette fonction que nous allons configurer le contexte daffichage
OpenGL, dfinir les listes daffichage et accomplir dautres initialisations.
Tout le code est en OpenGL standard, sauf lappel de la fonction qglClearColor() de
QGLWidget. Si nous voulions uniquement du code OpenGL standard, nous aurions pu appeler
glClearColor() en mode RGBA et glClearIndex() en mode table des couleurs.
void Tetrahedron::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLfloat x = GLfloat(width) / height;
glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0);
glMatrixMode(GL_MODELVIEW);
}

La fonction resizeGL() est invoque avant le premier appel de paintGL(), mais aprs
lappel de initializeGL(). Elle est aussi invoque ds que le widget est redimensionn.
Cest l que nous avons la possibilit de configurer le viewport OpenGL, la projection et tout
autre paramtre qui dpend de la taille du widget.
void Tetrahedron::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw();
}

La fonction paintGL() est invoque ds que le widget doit tre redessin. Elle ressemble
QWidget::paintEvent(), mais des fonctions OpenGL remplacent les fonctions de QPainter.
Le dessin est effectu par la fonction prive draw().
void Tetrahedron::draw()
{
static const GLfloat P1[3]
static const GLfloat P2[3]
static const GLfloat P3[3]
static const GLfloat P4[3]

=
=
=
=

{
{
{
{

0.0, -1.0, +2.0 };


+1.73205081, -1.0, -1.0 };
-1.73205081, -1.0, -1.0 };
0.0, +2.0, 0.0 };

Qt 4 Livre Page 210 Jeudi, 7. dcembre 2006 12:14 12

210

Qt4 et C++ : Programmation dinterfaces GUI

static const GLfloat * const coords[4][3] = {


{ P1, P2, P3 }, { P1, P3, P4 }, { P1, P4, P2 }, { P2, P4, P3 }
};
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -10.0);
glRotatef(rotationX, 1.0, 0.0, 0.0);
glRotatef(rotationY, 0.0, 1.0, 0.0);
glRotatef(rotationZ, 0.0, 0.0, 1.0);
for (int i = 0; i < 4; ++i) {
glLoadName(i);
glBegin(GL_TRIANGLES);
qglColor(faceColors[i]);
for (int j = 0; j < 3; ++j) {
glVertex3f(coords[i][j][0], coords[i][j][1],
coords[i][j][2]);
}
glEnd();
}
}

Dans draw(), nous dessinons le ttradre, en tenant compte des rotations x, y et z et des
couleurs conserves dans le tableau faceColors. Tout est en code OpenGL standard, sauf
lappel de qglColor(). Nous aurions pu choisir plutt une des fonctions OpenGL
glColor3d() ou glIndex() en fonction du mode.
void Tetrahedron::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void Tetrahedron::mouseMoveEvent(QMouseEvent *event)
{
GLfloat dx = GLfloat(event->x() - lastPos.x()) / width();
GLfloat dy = GLfloat(event->y() - lastPos.y()) / height();
if (event->buttons() & Qt::LeftButton) {
rotationX += 180 * dy;
rotationY += 180 * dx;
updateGL();
} else if (event->buttons() & Qt::RightButton) {
rotationX += 180 * dy;
rotationZ += 180 * dx;
updateGL();
}
lastPos = event->pos();
}

Les fonctions mousePressEvent() et mouseMoveEvent() sont rimplmentes dans QWidget pour permettre lutilisateur de faire pivoter la vue en cliquant dessus et en la faisant glisser.

Qt 4 Livre Page 211 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 8

Graphiques 2D et 3D

211

Le bouton gauche de la souris contrle la rotation autour des axes x et y, et le bouton droit
autour des axes x et z.
Aprs avoir modifi la variable rotationX et une des variables rotationY ou rotationZ,
nous appelons updateGL() pour redessiner la scne.
void Tetrahedron::mouseDoubleClickEvent(QMouseEvent *event)
{
int face = faceAtPosition(event->pos());
if (face!= -1) {
QColor color = QColorDialog::getColor(faceColors[face], this);
if (color.isValid()) {
faceColors[face] = color;
updateGL();
}
}
}

mouseDoubleClickEvent() est rimplmente dans QWidget pour permettre lutilisateur


de dfinir la couleur dune des faces du ttradre en double-cliquant dessus. Nous invoquons la
fonction prive faceAtPosition() pour dterminer quelle face se situe sous le curseur, sil y en
a une. Si lutilisateur a double-cliqu sur une face, nous appelons QColorDialog::getColor()
pour obtenir une nouvelle couleur pour cette face. Nous mettons ensuite jour le tableau
faceColors pour tenir compte de la nouvelle couleur et nous invoquons updateGL() pour
redessiner la scne.
int Tetrahedron::faceAtPosition(const QPoint &pos)
{
const int MaxSize = 512;
GLuint buffer[MaxSize];
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(MaxSize, buffer);
glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPickMatrix(GLdouble(pos.x()), GLdouble(viewport[3] - pos.y()),
5.0, 5.0, viewport);
GLfloat x = GLfloat(width()) / height();
glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0);
draw();
glMatrixMode(GL_PROJECTION);
glPopMatrix();

Qt 4 Livre Page 212 Jeudi, 7. dcembre 2006 12:14 12

212

Qt4 et C++ : Programmation dinterfaces GUI

if (!glRenderMode(GL_RENDER))
return -1;
return buffer[3];
}

La fonction faceAtPosition() retourne le nombre de faces une certaine position sur le


widget, ou 1 si aucune face ne se trouve cet endroit. Le code OpenGL permettant de dterminer ceci est quelque peu complexe. En rsum, nous affichons la scne en mode GL_SELECT
pour profiter des fonctionnalits de slection dOpenGL, puis nous rcuprons le numro de la
face (son "nom") dans lenregistrement du nombre daccs dOpenGL.
Voici main.cpp:
#include <QApplication>
#include <iostream>
#include "tetrahedron.h"
using namespace std;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!QGLFormat::hasOpenGL()) {
cerr << "This system has no OpenGL support" << endl;
return 1;
}
Tetrahedron tetrahedron;
tetrahedron.setWindowTitle(QObject::tr("Tetrahedron"));
tetrahedron.resize(300, 300);
tetrahedron.show();
return app.exec();
}

Si le systme de lutilisateur ne prend pas en charge OpenGL, nous imprimons un message


derreur sur la console et nous retournons immdiatement.
Pour associer lapplication au module QtOpenGL et la bibliothque OpenGL du systme, le
fichier .pro doit contenir cette entre :
QT += opengl

Lapplication Tetrahedron est termine. Pour plus dinformations sur le module QtOpenGL,
consultez la documentation de rfrence de QGLWidget, QGLFormat, QGLContext, QGLColormap et QGLPixelBuffer.

Qt 4 Livre Page 213 Jeudi, 7. dcembre 2006 12:14 12

9
Glisser-dposer
Au sommaire de ce chapitre
Activer le glisser-dposer
Prendre en charge les types personnaliss
de glisser
Grer le presse-papiers

Le glisser-dposer est un moyen moderne et intuitif de transfrer des informations dans


une application ou entre diffrentes applications. Cette technique est souvent propose
en complment du support du presse-papiers pour dplacer et copier des donnes.
Dans ce chapitre, nous verrons comment ajouter la prise en charge du glisser-dposer
une application et comment grer des formats personnaliss. Nous tudierons galement
la manire de rutiliser le code du glisser-dposer pour ajouter le support du pressepapiers. Cette rutilisation du code est possible parce que les deux mcanismes sont
bass sur QMimeData, une classe capable de fournir des donnes dans divers formats.

Qt 4 Livre Page 214 Jeudi, 7. dcembre 2006 12:14 12

214

Qt4 et C++ : Programmation dinterfaces GUI

Activer le glisser-dposer
Le glisser-dposer implique deux actions distinctes : glisser et dposer. On peut faire glisser et/ou
dposer des lments sur les widgets Qt.
Notre premier exemple vous prsente comment faire accepter une application Qt un glisser
initi par une autre application. Lapplication Qt est une fentre principale avec un widget
central QTextEdit. Quand lutilisateur fait glisser un fichier texte du bureau ou de lexplorateur
de fichiers vers lapplication, celle-ci charge le fichier dans le QTextEdit.
Voici la dfinition de la classe MainWindow de notre exemple :
class MainWindow: public QMainWindow
{
Q_OBJECT
public:
MainWindow();
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
private:
bool readFile(const QString &fileName);
QTextEdit *textEdit;
};

La classe MainWindow rimplmente dragEnterEvent() et dropEvent() dans QWidget. Vu


que lobjectif de notre exemple est de prsenter le glisser-dposer, la majorit des fonctionnalits
quune classe de fentre principale devrait contenir a t omise.
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->setAcceptDrops(false);
setAcceptDrops(true);
setWindowTitle(tr("Text Editor"));
}

Dans le constructeur, nous crons un QTextEdit et nous le dfinissons comme widget central.
Par dfaut, QTextEdit accepte des glisser sous forme de texte provenant dautres applications,
et si lutilisateur y dpose un fichier, le nom de fichier sera intgr dans le texte. Les vnements drop tant transmis de lenfant au parent, nous obtenons les vnements drop pour
toute la fentre dans MainWindow en dsactivant le dposer dans QTextEdit et en lactivant
dans la fentre principale.

Qt 4 Livre Page 215 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

215

void MainWindow::dragEnterEvent(QDragEnterEvent *event)


{
if (event->mimeData()->hasFormat("text/uri-list"))
event->acceptProposedAction();
}

dragEnterEvent() est appele ds que lutilisateur fait glisser un objet sur un widget. Si
nous invoquons acceptProposedAction() sur lvnement, nous indiquons que lutilisateur
est en mesure de dposer cet objet sur ce widget. Par dfaut, le widget naccepterait pas le glisser.
Qt modifie automatiquement le pointeur pour signaler lutilisateur si le widget est en mesure
daccepter le dpt.
Dans notre cas, nous voulons que lutilisateur puisse faire glisser des fichiers, mais rien
dautre. Pour ce faire, nous vrifions le type MIME du glisser. Le type MIME text/uri-list
est utilis pour stocker une liste dURI (universal resource identi?er), qui peuvent tre des
noms de fichiers, des URL (comme des chemins daccs HTTP ou FTP) ou dautres identifiants globaux de ressource. Les types MIME standards sont dfinis par lIANA (Internet Assigned Numbers Authority). Ils sont constitus dun type et dun sous-type spars par un slash.
Les types MIME sont employs par le presse-papiers et par le systme du glisser-dposer pour
identifier les diffrents types de donnes. La liste officielle des types MIME est disponible
ladresse suivante : http://www.iana.org/assignments/media-types/.
void MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty())
return;
QString fileName = urls.first().toLocalFile();
if (fileName.isEmpty())
return;
if (readFile(fileName))
setWindowTitle(tr("%1 - %2").arg(fileName)
.arg(tr("Drag File")));
}

dropEvent() est appele ds que lutilisateur dpose un objet sur un widget. Nous appelons
QMimeData::urls() pour obtenir une liste des QUrl. En gnral, les utilisateurs ne font glisser quun seul fichier la fois, mais il est possible den faire glisser plusieurs en mme temps
grce une slection. Sil y a plusieurs URL ou si lURL ne correspond pas un nom de
fichier local, nous retournons immdiatement.
QWidget propose aussi dragMoveEvent() et dragLeaveEvent(), mais ces fonctions nont
gnralement pas besoin dtre rimplmentes.
Le deuxime exemple illustre la faon dinitier un glisser et daccepter un dposer.
Nous allons crer une sous-classe QListWidget qui prend en charge le glisser-dposer et

Qt 4 Livre Page 216 Jeudi, 7. dcembre 2006 12:14 12

216

Qt4 et C++ : Programmation dinterfaces GUI

nous lutiliserons en tant que composant de lapplication Project Chooser prsente en


Figure 9.1.
Figure 9.1
Lapplication
Project Chooser

Lapplication Project Chooser prsente lutilisateur deux widgets liste remplis de noms.
Chaque widget reprsente un projet. Lutilisateur peut faire glisser et dposer les noms dans les
widgets liste pour dplacer une personne dun projet un autre.
Le code du glisser-dposer se situe en globalit dans la sous-classe QListWidget. Voici la
dfinition de classe :
class ProjectListWidget: public QListWidget
{
Q_OBJECT
public:
ProjectListWidget(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
private:
void startDrag();
QPoint startPos;
};

La classe ProjectListWidget rimplmente cinq gestionnaires dvnements dclars dans


QWidget.
ProjectListWidget::ProjectListWidget(QWidget *parent)
: QListWidget(parent)
{
setAcceptDrops(true);
}

Qt 4 Livre Page 217 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

217

Dans le constructeur, nous activons le dposer sur le widget liste.


void ProjectListWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
startPos = event->pos();
QListWidget::mousePressEvent(event);
}

Quand lutilisateur appuie sur le bouton gauche de la souris, nous stockons lemplacement de
cette dernire dans la variable prive startPos. Nous appelons limplmentation de mousePressEvent() du QListWidget pour nous assurer que ce dernier a la possibilit de traiter
des vnements "bouton souris enfonc" comme dhabitude.
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
startDrag();
}
QListWidget::mouseMoveEvent(event);
}

Quand lutilisateur dplace le pointeur tout en maintenant le bouton gauche de la souris


enfonc, nous commenons un glisser. Nous calculons la distance entre la position actuelle de
la souris et la position o le bouton gauche a t enfonc. Si la distance est suprieure la
distance recommande pour dmarrer un glisser de QApplication (normalement 4 pixels),
nous appelons la fonction prive startDrag() pour dbuter le glisser. Ceci vite dinitier un
glisser si la main de lutilisateur a trembl.
void ProjectListWidget::startDrag()
{
QListWidgetItem *item = currentItem();
if (item) {
QMimeData *mimeData = new QMimeData;
mimeData->setText(item->text());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(QPixmap(":/images/person.png"));
if (drag->start(Qt::MoveAction) == Qt::MoveAction)
delete item;
}
}

Dans startDrag(), nous crons un objet de type QDrag, this tant son parent. Lobjet
QDrag enregistre les donnes dans un objet QMimeData. Dans notre exemple, nous fournissons
les donnes sous forme de chane text/plain au moyen de QMimeData::setText().
QMimeData propose plusieurs fonctions permettant de grer les types les plus courants de glisser
(images, URL, couleurs, etc.) et peut grer des types MIME arbitraires reprsents comme

Qt 4 Livre Page 218 Jeudi, 7. dcembre 2006 12:14 12

218

Qt4 et C++ : Programmation dinterfaces GUI

tant des QByteArray. Lappel de QDrag::setPixmap() dfinit licne qui suit le pointeur
pendant le glisser.
Lappel de QDrag::start() dbute le glisser et se bloque jusqu ce que lutilisateur dpose
ou annule le glisser. Elle reoit en argument une combinaison des glisser pris en charge
(Qt::CopyAction, Qt::MoveAction et Qt::LinkAction) et retourne le glisser qui a t
excut (ou Qt::IgnoreAction si aucun glisser na t excut). Laction excute dpend de
ce que le widget source autorise, de ce que la cible supporte et des touches de modification
enfonces au moment de dposer. Aprs lappel de start(), Qt prend possession de lobjet
gliss et le supprimera quand il ne sera plus ncessaire.
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source!= this) {
event->setDropAction(Qt::MoveAction);
event->accept();
}
}

Le widget ProjectListWidget ne sert pas uniquement initialiser des glisser, il accepte


aussi des glisser provenant dun autre ProjectListWidget de la mme application. QDragEnterEvent::source() retourne un pointeur vers le widget lorigine du glisser si ce
widget fait partie de la mme application ; sinon elle renvoie un pointeur nul. Nous utilisons
qobject_cast<T>() pour nous assurer que le glisser provient dun ProjectListWidget. Si
tout est correct, nous informons Qt que nous sommes prts accepter laction en tant quaction
de dplacement.
void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source!= this) {
event->setDropAction(Qt::MoveAction);
event->accept();
}
}

Le code dans dragMoveEvent() est identique ce que nous effectu dans dragEnterEvent(). Il est ncessaire parce que nous devons remplacer limplmentation de la fonction
dans QListWidget (en fait dans QAbstractItemView).
void ProjectListWidget::dropEvent(QDropEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source!= this) {
addItem(event->mimeData()->text());
event->setDropAction(Qt::MoveAction);

Qt 4 Livre Page 219 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

219

event->accept();
}
}

Dans dropEvent(), nous rcuprons le texte gliss laide de QMimeData::text() et nous


crons un lment avec ce texte. Nous avons galement besoin daccepter lvnement comme
tant une "action de dplacement" afin de signaler au widget source quil peut maintenant
supprimer la version originale de llment gliss.
Le glisser-dposer est un mcanisme puissant permettant de transfrer des donnes entre des
applications. Cependant, dans certains cas, il est possible dimplmenter le glisser-dposer
sans utiliser les fonctionnalits de glisser-dposer de Qt. Si tout ce que nous souhaitons se
limite dplacer des donnes dans un widget dune application, il suffit de rimplmenter
mousePressEvent() et mouseReleaseEvent().

Prendre en charge les types personnaliss


de glisser
Jusqu prsent, nous nous sommes bass sur la prise en charge de QMimeData des types
MIME communs. Nous avons donc appel QMimeData::setText() pour crer un glisser
de texte et nous avons excut QMimeData:urls() pour rcuprer le contenu dun glisser
text/uri-list. Si nous voulons faire glisser du texte brut, du texte HTML, des images,
des URL ou des couleurs, nous pouvons employer QMimeData sans formalit. Mais si nous
souhaitons faire glisser des donnes personnalises, nous devons faire un choix entre
plusieurs possibilits :
1. Nous pouvons fournir des donnes arbitraires sous forme de QByteArray en utilisant
QMimeData::setData() et les extraire ultrieurement avec QMimeData::data().
2. Nous pouvons driver QMimeData et rimplmenter formats() et retrieveData() pour
grer nos types personnaliss de donnes.
3. Sagissant du glisser-dposer dans une seule application, nous avons la possibilit de driver
QMimeData et de stocker les donnes dans la structure de notre choix.
La premire approche nimplique pas de drivation, mais prsente certains inconvnients :
nous devons convertir notre structure de donnes en QByteArray mme si le glisser nest pas
accept la fin, et si nous voulons proposer plusieurs types MIME pour interagir correctement
avec une large gamme dapplications, nous devons enregistrer les donnes plusieurs fois (une
fois pour chaque type MIME). Si les donnes sont nombreuses, lapplication peut tre ralentie
inutilement. Les deuxime et troisime approches permettent dviter ou de minimiser ces
problmes. Ainsi, nous avons un contrle total et nous pouvons les utiliser ensemble.
Pour vous prsenter le fonctionnement de ces approches, nous vous montrerons comment ajouter des fonctions de glisser-dposer un QTableWidget. Le glisser prendra en charge les types

Qt 4 Livre Page 220 Jeudi, 7. dcembre 2006 12:14 12

220

Qt4 et C++ : Programmation dinterfaces GUI

MIME suivants : text/plain, text/html et text/csv. En utilisant la premire approche,


voici comment dbute un glisser :
void MyTableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
startDrag();
}
QTableWidget::mouseMoveEvent(event);
}
void MyTableWidget::startDrag()
{
QString plainText = selectionAsPlainText();
if (plainText.isEmpty())
return;
QMimeData *mimeData = new QMimeData;
mimeData->setText(plainText);
mimeData->setHtml(toHtml(plainText));
mimeData->setData("text/csv", toCsv(plainText).toUtf8());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
if (drag->start(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)
deleteSelection();
}

La fonction prive startDrag() a t invoque dans mouseMoveEvent() pour commencer


faire glisser une slection rectangulaire. Nous dfinissons les types MIME text/plain et text/
html avec setText() et setHtml() et nous configurons le type text/csv avec setData(),
qui reoit un type MIME arbitraire et un QByteArray. Le code de selectionAsString() est
plus ou moins le mme que la fonction Spreadsheet::copy() du Chapitre 4.
QString MyTableWidget::toCsv(const QString &plainText)
{
QString result = plainText;
result.replace("\\", "\\\\");
result.replace("\"", "\\\"");
result.replace("\t", "\", \"");
result.replace("\n", "\"\n\"");
result.prepend("\"");
result.append("\"");
return result;
}
QString MyTableWidget::toHtml(const QString &plainText)
{
QString result = Qt::escape(plainText);
result.replace("\t", "<td>");

Qt 4 Livre Page 221 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

221

result.replace("\n", "\n<tr><td>");
result.prepend("<table>\n<tr><td>");
result.append("\n</table>");
return result;
}

Les fonctions toCsv() et toHtml() convertissent une chane "tabulations et sauts de ligne"
en une chane CSV (comma-separated values, valeurs spares par des virgules) ou HTML.
Par exemple, les donnes
Red
Cyan

Green
Yellow

Blue
Magenta

sont converties en
"Red",
"Cyan",

"Green",
"Yellow",

"Blue"
"Magenta"

ou en
<table>
<tr><td>Red<td>Green<td>Blue
<tr><td>Cyan<td>Yellow<td>Magenta
</table>

La conversion est effectue de la manire la plus simple possible, en excutant


QString::replace(). Pour viter les caractres spciaux HTML, nous employons
Qt::escape().
void MyTableWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
...
event->acceptProposedAction();
}
}

Mme si nous fournissons les donnes dans trois formats diffrents, nous nacceptons que deux
dentre eux dans dropEvent(). Si lutilisateur fait glisser des cellules depuis un QTableWidget vers un diteur HTML, nous voulons que les cellules soient converties en un tableau
HTML. Mais si lutilisateur fait glisser un code HTML arbitraire vers un QTableWidget, nous
ne voulons pas laccepter.
Pour que cet exemple fonctionne, nous devons galement appeler setAcceptDrops(true) et
setSelectionMode(ContiguousSelection) dans le constructeur de MyTableWidget.

Qt 4 Livre Page 222 Jeudi, 7. dcembre 2006 12:14 12

222

Qt4 et C++ : Programmation dinterfaces GUI

Nous allons refaire notre exemple, mais cette fois-ci nous driverons QMimeData pour ajourner
ou viter les conversions (potentiellement onreuses en termes de performances) entre
QTableWidgetItem et QByteArray. Voici la dfinition de notre sous-classe :
class TableMimeData: public QMimeData
{
Q_OBJECT
public:
TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range);
const QTableWidget *tableWidget() const { return myTableWidget; }
QTableWidgetSelectionRange range() const { return myRange; }
QStringList formats() const;
protected:
QVariant retrieveData(const QString &format,
QVariant::Type preferredType) const;
private:
static QString toHtml(const QString &plainText);
static QString toCsv(const QString &plainText);
QString text(int row, int column) const;
QString rangeAsPlainText() const;
const QTableWidget *myTableWidget;
QTableWidgetSelectionRange myRange;
QStringList myFormats;
};

Au lieu de stocker les donnes relles, nous enregistrons un QTableWidgetSelectionRange


qui spcifie quelles cellules ont t glisses et nous conservons un pointeur vers QTableWidget. Les fonctions formats() et retrieveData() sont rimplmentes dans QMimeData.
TableMimeData::TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range)
{
myTableWidget = tableWidget;
myRange = range;
myFormats << "text/csv" << "text/html" << "text/plain";
}

Dans le constructeur, nous initialisons les variables prives.


QStringList TableMimeData::formats() const
{
return myFormats;
}

Qt 4 Livre Page 223 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

223

La fonction formats() retourne une liste de types MIME fournie par lobjet MIME. Lordre
prcis des formats nest gnralement pas important, mais il est recommand de placer les
"meilleurs" formats en premier. Il arrive en effet que les applications qui prennent en charge de
nombreux formats choisissent le premier qui convient.
QVariant TableMimeData::retrieveData(const QString &format,
QVariant::Type preferredType) const
{
if (format == "text/plain") {
return rangeAsPlainText();
} else if (format == "text/csv") {
return toCsv(rangeAsPlainText());
} else if (format == "text/html") {
return toHtml(rangeAsPlainText());
} else {
return QMimeData::retrieveData(format, preferredType);
}
}

La fonction retrieveData() retourne les donnes dun type MIME particulier sous forme de
QVariant. La valeur du paramtre de format correspond normalement une des chanes
retournes par formats(), mais nous ne pouvons pas en attester, tant donn que toutes les
applications ne comparent pas le type MIME formats(). Les fonctions daccs text(),
html(), urls(), imageData(), colorData() et data() proposes par QMimeData sont
implmentes en termes de retrieveData().
Le paramtre preferredType est un bon indicateur du type que nous devrions placer dans
QVariant. Ici, nous lignorons et nous faisons confiance QMimeData pour convertir la valeur
de retour dans le type souhait, si ncessaire.
void MyTableWidget::dropEvent(QDropEvent *event)
{
const TableMimeData *tableData =
qobject_cast<const TableMimeData *>(event->mimeData());
if (tableData) {
const QTableWidget *otherTable = tableData->tableWidget();
QTableWidgetSelectionRange otherRange = tableData->range();
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
...
event->acceptProposedAction();
}
QTableWidget::mouseMoveEvent(event);
}

Qt 4 Livre Page 224 Jeudi, 7. dcembre 2006 12:14 12

224

Qt4 et C++ : Programmation dinterfaces GUI

La fonction dropEvent() ressemble celle prsente prcdemment dans cette section, mais
cette fois-ci nous loptimisons en vrifiant dabord si nous pouvons convertir en toute scurit
lobjet QMimeData en TableMimeData. Si qobject_cast<T>() fonctionne, cela signifie
quun MyTableWidget de la mme application tait lorigine du glisser, et nous pouvons
directement accder aux donnes de la table au lieu de passer par lAPI de QMimeData. Si la
conversion choue, nous extrayons les donnes de faon habituelle.
Dans cet exemple, nous avons cod le texte CSV avec le format de codage UTF-8. Si nous
voulions tre srs dutiliser le bon codage, nous aurions pu utiliser le paramtre charset du
type MIME text/plain de sorte spcifier un codage explicite. Voici quelques exemples :
text/plain;charset=US-ASCII
text/plain;charset=ISO-8859-1
text/plain;charset=Shift_JIS
text/plain;charset=UTF-8

Grer le presse-papiers
La plupart des applications emploient la gestion intgre du presse-papiers de Qt dune
manire ou dune autre. Par exemple, la classe QTextEdit propose les slots cut(), copy() et
paste(), de mme que des raccourcis clavier. Vous navez donc pas besoin de code supplmentaire, ou alors trs peu.
Quand vous crivez vos propres classes, vous pouvez accder au presse-papiers par le biais de
QApplication::clipboard(), qui retourne un pointeur vers lobjet QClipboard de lapplication. Grer le presse-papiers savre assez facile : vous appelez setText(), setImage() ou
setPixmap() pour placer des donnes dans le presse-papiers, puis vous appelez text(),
image() ou pixmap() pour y rcuprer les donnes. Nous avons dj analys des exemples
dutilisation du presse-papiers dans lapplication Spreadsheet du Chapitre 4.
Pour certaines applications, la fonctionnalit intgre peut tre insuffisante. Par exemple, nous
voulons pouvoir fournir des donnes qui ne soient pas uniquement du texte ou une image, ou
proposer des donnes en diffrents formats pour un maximum dinteroprabilit avec dautres
applications. Ce problme ressemble beaucoup ce que nous avons rencontr prcdemment
avec le glisser-dposer et la rponse est similaire : nous pouvons driver QMimeData et rimplmenter quelques fonctions virtuelles.
Si notre application prend en charge le glisser-dposer via une sous-classe QMimeData personnalise, nous pouvons simplement rutiliser cette sous-classe et la placer dans le presse-papiers
en employant la fonction setMimeData(). Pour rcuprer les donnes, nous avons la possibilit dinvoquer mimeData() sur le presse-papiers.
Sous X11, il est habituellement possible de coller une slection en cliquant sur le bouton du
milieu dune souris dote de trois boutons. Vous faites alors appel un presse-papiers de
"slection" distinct. Si vous souhaitez que vos widgets supportent ce genre de presse-papiers
en complment du presse-papiers standard, vous devez transmettre QClipboard::Selection

Qt 4 Livre Page 225 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 9

Glisser-dposer

225

comme argument supplmentaire aux divers appels du presse-papiers. Voici par exemple comment
nous rimplmenterions mouseReleaseEvent() dans un diteur de texte pour prendre en
charge le collage avec le bouton du milieu de la souris :
void MyTextEditor::mouseReleaseEvent(QMouseEvent *event)
{
QClipboard *clipboard = QApplication::clipboard();
if (event->button() == Qt::MidButton
&& clipboard->supportsSelection()) {
QString text = clipboard->text(QClipboard::Selection);
pasteText(text);
}
}

Sous X11, la fonction supportsSelection() retourne true. Sur les autres plates-formes,
elle renvoie false.
Si vous voulez tre inform ds que le contenu du presse-papiers change, vous pouvez connecter
le signal QClipboard::dataChanged() un slot personnalis.

Qt 4 Livre Page 226 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 227 Jeudi, 7. dcembre 2006 12:14 12

10
Classes daffichage
dlments
Au sommaire de ce chapitre
Utiliser les classes ddies laffichage dlments
Utiliser des modles prdfinis
Implmenter des modles personnaliss
Implmenter des dlgus personnaliss

Beaucoup dapplications ont pour objectif la recherche, laffichage et la modification


dlments individuels appartenant un ensemble de donnes. Ces donnes peuvent se
trouver dans des fichiers, des bases de donnes ou des serveurs de rseau. Lapproche
standard relative au traitement des ensembles de donnes consiste utiliser les classes
daffichage dlments de Qt.
Dans les versions antrieures de Qt, les widgets daffichage dlments taient aliments avec la totalit de lensemble de donnes ; les utilisateurs pouvaient effectuer toutes
leurs recherches et modifications sur les donnes hberges dans le widget, et un

Qt 4 Livre Page 228 Jeudi, 7. dcembre 2006 12:14 12

228

Qt4 et C++ : Programmation dinterfaces GUI

moment donn, les modifications taient sauvegardes dans la source de donnes. Mme si elle
est simple comprendre et utiliser, cette approche nest pas adapte aux trs grands ensembles de donnes, ni pour laffichage du mme ensemble de donnes dans deux ou plusieurs
widgets diffrents.
Le langage Smalltalk a fait connatre une mthode flexible permettant de visualiser de grands
ensembles de donnes : MVC (Modle-Vue-Contrleur). Dans lapproche MVC, le modle
reprsente lensemble de donnes et il se charge de rcuprer les donnes ncessaires pour afficher et enregistrer toute modification. Chaque type densemble de donnes possde son propre
modle, mais lAPI que les modles proposent aux vues est identique quel que soit lensemble
de donnes sous-jacent. La vue prsente les donnes lutilisateur. Seule une quantit limite
de donnes dun grand ensemble sera visible en mme temps, cest--dire celles demandes
par la vue. Le contrleur sert dintermdiaire entre lutilisateur et la vue ; il convertit les
actions utilisateur en requtes pour rechercher ou modifier des donnes, que la vue transmet
ensuite au modle si ncessaire.
Figure 10.1
Larchitecture
modle/vue de Qt

Dlgu

Source de donnes

Modle

Vue

Qt propose une architecture modle/vue inspire de lapproche MVC (voir Figure 10.1).
Dans Qt, le modle se comporte de la mme manire que pour le MVC classique. Mais la
place du contrleur, Qt utilise une notion lgrement diffrente : le dlgu. Le dlgu offre
un contrle prcis de la manire dont les lments sont affichs et modifis. Qt fournit un dlgu par dfaut pour chaque type de vue. Cest suffisant pour la plupart des applications, cest
pourquoi nous navons gnralement pas besoin de nous en proccuper.
Grce larchitecture modle/vue de Qt, nous avons la possibilit dutiliser des modles qui ne
rcuprent que les donnes ncessaires laffichage de la vue. Nous grons donc de trs grands
ensembles de donnes beaucoup plus rapidement et nous consommons moins de mmoire que
si nous devions lire toutes les donnes. De plus, en enregistrant un modle avec deux vues ou
plus, nous donnons lopportunit lutilisateur dafficher et dinteragir avec les donnes de
diffrentes manires, avec peu de surcharge (voir Figure 10.2). Qt synchronise automatiquement plusieurs vues, refltant les changements apports dans lune delles dans toutes les
autres. Larchitecture modle/vue prsente un autre avantage : si nous dcidons de modifier
la faon dont lensemble de donnes sous-jacent est enregistr, nous navons qu changer le
modle ; les vues continueront se comporter correctement.
En gnral, nous ne devons prsenter quun nombre relativement faible dlments
lutilisateur. Dans ces cas frquents, nous pouvons utiliser les classes daffichage dlments
de Qt (QListWidget, QTableWidget et QTreeWidget) spcialement conues cet effet et
directement y enregistrer des lments. Ces classes se comportent de manire similaire aux

Qt 4 Livre Page 229 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

229

classes daffichage dlments proposes par les versions antrieures de Qt. Elles stockent
leurs donnes dans des "lments" (par exemple, un QTableWidget contient des QTableWidgetItem). En interne, ces classes sappuient sur des modles personnaliss qui affichent les
lments destins aux vues.
Vue de liste 1

Vue de liste 2

Vue de liste 3

Vue tableau 4

Vue tableau 5

Modle

Source de donnes

Figure 10.2
Un modle peut desservir plusieurs vues

Sagissant des grands ensembles de donnes, la duplication des donnes est souvent peu
recommande. Dans ces cas, nous pouvons utiliser les vues de Qt (QListView, QTableView,
et QTreeView, ), en association avec un modle de donnes, qui peut tre un modle personnalis ou un des modles prdfinis de Qt. Par exemple, si lensemble de donnes se
trouve dans une base de donnes, nous pouvons combiner un QTableView avec un QSqlTableModel.

Utiliser les classes ddies laffichage


dlments
Utiliser les sous-classes daffichage dlments de Qt est gnralement plus simple que de
dfinir un modle personnalis, et cette solution est plus approprie quand la sparation du
modle et de la vue ne prsente aucun intrt particulier. Nous avons employ cette technique
dans le Chapitre 4 quand nous avons driv QTableWidget et QTableWidgetItem pour
implmenter la fonctionnalit de feuille de calcul.
Dans cette section, nous verrons comment utiliser les sous-classes daffichage dlments pour
afficher des lments. Le premier exemple vous prsente un QListWidget en lecture seule, le
deuxime exemple vous montre un QTableWidget modifiable et le troisime vous expose un
QTreeWidget en lecture seule.
Nous commenons par une bote de dialogue simple qui propose lutilisateur de choisir un
symbole dorganigramme dans une liste, comme illustr en Figure 10.3. Chaque lment est
compos dune icne, de texte et dun ID unique.

Qt 4 Livre Page 230 Jeudi, 7. dcembre 2006 12:14 12

230

Qt4 et C++ : Programmation dinterfaces GUI

Figure 10.3
Lapplication
Flowchart Symbol Picker

Analysons dabord un extrait du fichier den-tte de la bote de dialogue :


class FlowChartSymbolPicker: public QDialog
{
Q_OBJECT
public:
FlowChartSymbolPicker(const QMap<int, QString> &symbolMap,
QWidget *parent = 0);
int selectedId() const { return id; }
void done(int result);
...
};

Quand nous construisons la bote de dialogue, nous devons lui transmettre un


QMap<int,QString>, et aprs son excution, nous pouvons rcuprer lID choisi (ou 1 si
lutilisateur na choisi aucun lment) en appelant selectedId().
FlowChartSymbolPicker::FlowChartSymbolPicker(
const QMap<int, QString> &symbolMap, QWidget *parent)
: QDialog(parent)
{
id = -1;
listWidget = new QListWidget;
listWidget->setIconSize(QSize(60, 60));
QMapIterator<int, QString> i(symbolMap);
while (i.hasNext()) {
i.next();
QListWidgetItem *item = new QListWidgetItem(i.value(),
listWidget);
item->setIcon(iconForSymbol(i.value()));
item->setData(Qt::UserRole, i.key());
}
...
}

Qt 4 Livre Page 231 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

231

Nous initialisons id (le dernier ID slectionn) 1. Puis nous construisons un QListWidget,


un widget ddi laffichage dlments. Nous parcourons chaque lment dans la liste des
symboles dorganigramme et nous crons un QListWidgetItem pour reprsenter chacun
deux. Le constructeur de QListWidgetItem reoit un QString qui reprsente le texte afficher,
suivi par le parent QListWidget.
Nous dfinissons ensuite licne de llment et nous invoquons setData() pour enregistrer
notre ID arbitraire dans le QListWidgetItem. La fonction prive iconForSymbol() retourne
un QIcon pour un nom de symbole donn.
Les QListWidgetItem endossent plusieurs rles, chacun ayant un QVariant associ. Les
rles les plus courants sont Qt::DisplayRole, Qt::EditRole et Qt::IconRole, pour
lesquels il existe des fonctions ddies daccs et de rglage (setText(), setIcon()). Toutefois, il existe plusieurs autres rles. Nous pouvons aussi dfinir des rles personnaliss en
spcifiant une valeur numrique de Qt::UserRole ou plus haut. Dans notre exemple, nous
utilisons Qt::UserRole pour stocker lID de chaque lment.
La partie non reprsente du constructeur se charge de crer les boutons, de disposer les
widgets et de dfinir le titre de la fentre.
void FlowChartSymbolPicker::done(int result)
{
id = -1;
if (result == QDialog::Accepted) {
QListWidgetItem *item = listWidget->currentItem();
if (item)
id = item->data(Qt::UserRole).toInt();
}
QDialog::done(result);
}

La fonction done() est rimplmente dans QDialog. Elle est appele quand lutilisateur
appuie sur OK ou Cancel. Si lutilisateur a cliqu sur OK, nous rcuprons llment pertinent
et nous extrayons lID grce la fonction data(). Si nous tions intresss par le texte de
llment, nous aurions pu le rcuprer en invoquant item->data(Qt::DisplayRole).toString() ou, ce qui est plus pratique, item->text().
Par dfaut, QListWidget est en lecture seule. Si nous voulions que lutilisateur puisse modifier les lments, nous aurions pu dfinir les dclencheurs de modification de la vue au moyen
de QAbstractItemView::setEditTriggers(); par exemple, configurer QAbstractItemView::AnyKeyPressed signifie que lutilisateur peut modifier un lment simplement
en commenant taper quelque chose. Nous aurions aussi pu proposer un bouton Edit (ou
peut-tre des boutons Add et Delete) et les connecter aux slots, de sorte dtre en mesure de
grer les oprations de modification par programme.
Maintenant que nous avons vu comment utiliser une classe ddie laffichage dlments
pour afficher et slectionner des donnes, nous allons tudier un exemple o nous pouvons
modifier des donnes. Nous utilisons nouveau une bote de dialogue, mais cette fois-ci, elle
prsente un ensemble de coordonnes (x, y) que lutilisateur peut modifier (voir Figure 10.4).

Qt 4 Livre Page 232 Jeudi, 7. dcembre 2006 12:14 12

232

Qt4 et C++ : Programmation dinterfaces GUI

Figure 10.4
Lapplication
Coordinate Setter

Comme pour lexemple prcdent, nous nous concentrerons sur le code daffichage de
llment, en commenant par le constructeur.
CoordinateSetter::CoordinateSetter(QList<QPointF> *coords,
QWidget *parent)
: QDialog(parent)
{
coordinates = coords;
tableWidget = new QTableWidget(0, 2);
tableWidget->setHorizontalHeaderLabels(
QStringList() << tr("X") << tr("Y"));
for (int row = 0; row < coordinates->count(); ++row) {
QPointF point = coordinates->at(row);
addRow();
tableWidget->item(row, 0)->setText(QString::number(point.x()));
tableWidget->item(row, 1)->setText(QString::number(point.y()));
}
...
}

Le constructeur de QTableWidget reoit le nombre initial de lignes et de colonnes du tableau


afficher. Chaque lment dans un QTableWidget est reprsent par un QTableWidgetItem,
y compris les en-ttes horizontaux et verticaux. La fonction setHorizontalHeaderLabels() inscrit dans chaque lment horizontal du widget tableau le texte qui lui est fourni
sous forme dune liste de chanes en argument. Par dfaut, QTableWidget propose un en-tte
vertical avec des lignes intitules partir de 1, ce qui correspond exactement ce que nous
recherchons, nous ne sommes donc pas contraints de configurer manuellement les intituls de
len-tte vertical.
Une fois que nous avons cr et centr les intituls des colonnes, nous parcourons les coordonnes transmises. Pour chaque paire (x, y), nous crons deux QTableWidgetItem correspondant
aux coordonnes x et y. Les lments sont ajouts au tableau grce QTableWidget::setItem(), qui reoit une ligne et une colonne en plus de llment.

Qt 4 Livre Page 233 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

233

Par dfaut, QTableWidget autorise les modifications. Lutilisateur peut modifier toute cellule
du tableau en la recherchant puis en appuyant sur F2 ou simplement en saisissant quelque
chose. Tous les changements effectus par lutilisateur dans la vue se reflteront automatiquement dans les QTableWidgetItem. Pour viter les modifications, nous avons la possibilit
dappeler setEditTriggers(QAbstractItemView::NoEditTriggers).
void CoordinateSetter::addRow()
{
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
QTableWidgetItem *item0 = new QTableWidgetItem;
item0->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 0, item0);
QTableWidgetItem *item1 = new QTableWidgetItem;
item1->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 1, item1);
tableWidget->setCurrentItem(item0);
}

Le slot addRow() est appel lorsque lutilisateur clique sur le bouton Add Row. Nous ajoutons
une nouvelle ligne laide de insertRow(). Si lutilisateur essaie de modifier une cellule
dans la nouvelle ligne, QTableWidget crera automatiquement un nouveau QTableWidgetItem.
void CoordinateSetter::done(int result)
{
if (result == QDialog::Accepted) {
coordinates->clear();
for (int row = 0; row < tableWidget->rowCount(); ++row) {
double x = tableWidget->item(row, 0)->text().toDouble();
double y = tableWidget->item(row, 1)->text().toDouble();
coordinates->append(QPointF(x, y));
}
}
QDialog::done(result);
}

Enfin, quand lutilisateur clique sur OK, nous effaons les coordonnes qui avaient t transmises la bote de dialogue et nous crons un nouvel ensemble bas sur les coordonnes des
lments du QTableWidget.
Pour notre troisime et dernier exemple de widget ddi laffichage dlments de Qt, nous
allons analyser quelques extraits de code dune application qui affiche les paramtres dune
application Qt grce un QTreeWidget (voir Figure 10.5). La lecture seule est loption par
dfaut de QTreeWidget.

Qt 4 Livre Page 234 Jeudi, 7. dcembre 2006 12:14 12

234

Qt4 et C++ : Programmation dinterfaces GUI

Figure 10.5
Lapplication
Settings Viewer

Voici un extrait du constructeur :


SettingsViewer::SettingsViewer(QWidget *parent)
: QDialog(parent)
{
organization = "Trolltech";
application = "Designer";
treeWidget = new QTreeWidget;
treeWidget->setColumnCount(2);
treeWidget->setHeaderLabels(
QStringList() << tr("Key") << tr("Value"));
treeWidget->header()->setResizeMode(0, QHeaderView::Stretch);
treeWidget->header()->setResizeMode(1, QHeaderView::Stretch);
...
setWindowTitle(tr("Settings Viewer"));
readSettings();
}

Pour accder aux paramtres dune application, un objet QSettings doit tre cr avec le nom
de lorganisation et le nom de lapplication comme paramtres. Nous dfinissons des noms par
dfaut ("Designer" par "Trolltech"), puis nous construisons un nouveau QTreeWidget. Pour
terminer, nous appelons la fonction readSettings().
void SettingsViewer::readSettings()
{
QSettings settings(organization, application);
treeWidget->clear();
addChildSettings(settings, 0, "");
treeWidget->sortByColumn(0);
treeWidget->setFocus();
setWindowTitle(tr("Settings Viewer - %1 by %2")
.arg(application).arg(organization));
}

Qt 4 Livre Page 235 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

235

Les paramtres dapplication sont stocks dans une hirarchie de cls et de valeurs. La fonction prive addChildSettings() reoit un objet settings, un parent QTreeWidgetItem et
le "groupe" en cours. Un groupe est lquivalent QSettings dun rpertoire de systme de
fichiers. La fonction addChildSettings() peut sappeler elle-mme de manire rcursive
pour faire dfiler une arborescence arbitraire. Le premier appel de la fonction readSettings() transmet 0 comme lment parent pour reprsenter la racine.
void SettingsViewer::addChildSettings(QSettings &settings,
QTreeWidgetItem *parent, const QString &group)
{
QTreeWidgetItem *item;
settings.beginGroup(group);
foreach (QString key, settings.childKeys()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, key);
item->setText(1, settings.value(key).toString());
}
foreach (QString group, settings.childGroups()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, group);
addChildSettings(settings, item, group);
}
settings.endGroup();
}

La fonction addChildSettings() est utilise pour crer tous les QTreeWidgetItem. Elle
parcourt toutes les cls au niveau en cours dans la hirarchie des paramtres et cre un
QTableWidgetItem par cl. Si 0 est transmis en tant qulment parent, nous crons
llment comme tant un enfant de QTreeWidget (il devient donc un lment de haut
niveau) ; sinon, nous crons llment comme tant un enfant de parent. La premire colonne
correspond au nom de la cl et la seconde la valeur correspondante.
La fonction parcourt ensuite chaque groupe du niveau en cours. Pour chacun deux, un
nouveau QTreeWidgetItem est cr avec sa premire colonne dfinie en nom du groupe. Puis,
la fonction sappelle elle-mme de manire rcursive avec llment de groupe comme parent
pour alimenter le QTreeWidget avec les lments enfants du groupe.
Les widgets daffichage dlments prsents dans cette section nous permettent dutiliser un
style de programmation trs similaire celui utilis dans les versions antrieures de Qt : lire
tout un ensemble de donnes dans un widget daffichage dlments, utiliser les objets des

Qt 4 Livre Page 236 Jeudi, 7. dcembre 2006 12:14 12

236

Qt4 et C++ : Programmation dinterfaces GUI

lments pour reprsenter les lments de donnes, et (si les lments sont modifiables) sauvegarder sur la source de donnes. Dans les sections suivantes, nous irons plus loin que cette
approche simple et nous profiterons pleinement de larchitecture modle/vue de Qt.

Utiliser des modles prdfinis


Qt propose plusieurs modles prdfinis utiliser avec les classes daffichage :
QStringListModel

Stocke une liste de chanes

QStandardItemModel

Stocke des donnes hirarchiques arbitraires

QDirModel

Encapsule le systme de fichiers local

QSqlQueryModel

Encapsule un jeu de rsultats SQL

QSqlTableModel

Encapsule une table SQL

QSqlRelationalTableModel

Encapsule une table SQL avec des cls trangres

QSortFilterProxyModel

Trie et/ou filtre un autre modle

Dans cette section, nous verrons comment employer QStringListModel, QDirModel et


QSortFilterProxyModel. Les modles SQL sont traits au Chapitre 13.
Commenons par une bote de dialogue simple dont les utilisateurs peuvent se servir pour
ajouter, supprimer et modifier un QStringList, o chaque chane reprsente un chef dquipe.
Celle-ci est prsente en Figure 10.6.
Figure 10.6
Lapplication
Team Leaders

Voici un extrait pertinent du constructeur :


TeamLeadersDialog::TeamLeadersDialog(const QStringList &leaders,
QWidget *parent)

Qt 4 Livre Page 237 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

237

: QDialog(parent)
{
model = new QStringListModel(this);
model->setStringList(leaders);
listView = new QListView;
listView->setModel(model);
listView->setEditTriggers(QAbstractItemView::AnyKeyPressed
| QAbstractItemView::DoubleClicked);
...
}

Nous crons et alimentons dabord un QStringListModel. Nous crons ensuite un QListView et nous lui affectons comme modle un de ceux que nous venons de crer. Nous configurons galement des dclencheurs de modification pour permettre lutilisateur de modifier une
chane simplement en commenant taper quelque chose ou en double-cliquant dessus. Par
dfaut, aucun dclencheur de modification nest dfini sur un QListView, la vue est donc
configure en lecture seule.
void TeamLeadersDialog::insert()
{
int row = listView->currentIndex().row();
model->insertRows(row, 1);
QModelIndex index = model->index(row);
listView->setCurrentIndex(index);
listView->edit(index);
}

Le slot insert() est invoqu lorsque lutilisateur clique sur le bouton Insert. Le slot
commence par rcuprer le numro de ligne de llment en cours dans la vue de liste. Chaque
lment de donnes dans un modle possde un "index de modle" correspondant qui est
reprsent par un objet QModelIndex. Nous allons tudier les index de modle plus en dtail
dans la prochaine section, mais pour linstant il suffit de savoir quun index comporte trois
composants principaux : une ligne, une colonne et un pointeur vers le modle auquel il appartient.
Pour un modle liste unidimensionnel, la colonne est toujours 0.
Lorsque nous connaissons le numro de ligne, nous insrons une nouvelle ligne cet endroit.
Linsertion est effectue sur le modle et le modle met automatiquement jour la vue de liste.
Nous dfinissons ensuite lindex en cours de la vue de liste sur la ligne vide que nous venons
dinsrer. Enfin, nous dfinissons la vue de liste en mode de modification sur la nouvelle ligne,
comme si lutilisateur avait appuy sur une touche ou double-cliqu pour initier la modification.
void TeamLeadersDialog::del()
{
model->removeRows(listView->currentIndex().row(), 1);
}

Dans le constructeur, le signal clicked() du bouton Delete est reli au slot del(). Vu que
nous avons supprim la ligne en cours, nous pouvons appeler removeRows() avec la position

Qt 4 Livre Page 238 Jeudi, 7. dcembre 2006 12:14 12

238

Qt4 et C++ : Programmation dinterfaces GUI

actuelle dindex et un nombre de lignes de 1. Comme avec linsertion, nous nous basons sur le
modle pour mettre jour la vue de faon approprie.
QStringList TeamLeadersDialog::leaders() const
{
return model->stringList();
}

Enfin, la fonction leaders() procure un moyen de lire les chanes modifies quand la bote de
dialogue est ferme.
TeamLeadersDialog pourrait devenir une bote de dialogue gnrique de modification de
liste de chanes simplement en paramtrant le titre de sa fentre. Une autre bote de dialogue
gnrique souvent demande est une bote qui prsente une liste de fichiers ou de rpertoires
lutilisateur. Le prochain exemple exploite la classe QDirModel, qui encapsule le systme de
fichiers de lordinateur et qui peut afficher (et masquer) les divers attributs de fichiers. Ce
modle peut appliquer un filtre pour limiter les types dentres du systme de fichiers qui sont
affiches et peut organiser les entres de plusieurs manires diffrentes.
Figure 10.7
Lapplication
Directory Viewer

Nous analyserons dabord la cration et nous configurerons le modle et la vue dans le constructeur
de la bote de dialogue Directory Viewer (voir Figure 10.7).
DirectoryViewer::DirectoryViewer(QWidget *parent)
: QDialog(parent)
{
model = new QDirModel;
model->setReadOnly(false);
model->setSorting(QDir::DirsFirst | QDir::IgnoreCase | QDir::Name);
treeView = new QTreeView;
treeView->setModel(model);
treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
treeView->header()->setClickable(true);

Qt 4 Livre Page 239 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

239

QModelIndex index = model->index(QDir::currentPath());


treeView->expand(index);
treeView->scrollTo(index);
treeView->resizeColumnToContents(0);
...
}

Lorsque le modle a t construit, nous faisons le ncessaire pour quil puisse tre modifi et
nous dfinissons les divers attributs dordre de tri. Nous crons ensuite le QTreeView qui affichera les donnes du modle. Len-tte du QTreeView peut tre utilis pour proposer un tri
contrl par lutilisateur. Si cet en-tte est cliquable, lutilisateur est en mesure de trier
nimporte quelle colonne en cliquant sur ce dernier ; en cliquant plusieurs fois dessus, il choisit
entre les tris croissants et dcroissants. Une fois que len-tte de larborescence a t configur,
nous obtenons lindex de modle du rpertoire en cours et nous sommes srs que ce rpertoire
est visible en dveloppant ses parents si ncessaire laide de expand() et en le localisant
grce scrollTo(). Nous nous assurons galement que la premire colonne est suffisamment
grande pour afficher toutes les entres sans utiliser de points de suspension (...).
Dans la partie du code du constructeur qui nest pas prsente ici, nous avons connect les
boutons Create Directory (Crer un rpertoire) et Remove (Supprimer) aux slots pour effectuer
ces actions. Nous navons pas besoin de bouton Rename parce que les utilisateurs peuvent
renommer directement en appuyant sur F2 et en tapant du texte.
void DirectoryViewer::createDirectory()
{
QModelIndex index = treeView->currentIndex();
if (!index.isValid())
return;
QString dirName = QInputDialog::getText(this,
tr("Create Directory"),
tr("Directory name"));
if (!dirName.isEmpty()) {
if (!model->mkdir(index, dirName).isValid())
QMessageBox::information(this, tr("Create Directory"),
tr("Failed to create the directory"));
}
}

Si lutilisateur entre un nom de rpertoire dans la bote de dialogue, nous essayons de crer un
rpertoire avec ce nom comme enfant du rpertoire en cours. La fonction QDirModel::mkdir()
reoit lindex du rpertoire parent et le nom du nouveau rpertoire, et retourne lindex de
modle du rpertoire quil a cr. Si lopration choue, elle retourne un index de modle
invalide.
void DirectoryViewer::remove()
{
QModelIndex index = treeView->currentIndex();

Qt 4 Livre Page 240 Jeudi, 7. dcembre 2006 12:14 12

240

Qt4 et C++ : Programmation dinterfaces GUI

if (!index.isValid())
return;
bool ok;
if (model->fileInfo(index).isDir()) {
ok = model->rmdir(index);
} else {
ok = model->remove(index);
}
if (!ok)
QMessageBox::information(this, tr("Remove"),
tr("Failed to remove %1").arg(model->fileName(index)));
}

Si lutilisateur clique sur Remove, nous tentons de supprimer le fichier ou le rpertoire associ
llment en cours. Pour ce faire, nous pourrions utiliser QDir, mais QDirModel propose des
fonctions pratiques qui fonctionnent avec QModelIndexes.
Le dernier exemple de cette section vous montre comment employer QSortFilterProxyModel. Contrairement aux autres modles prdfinis, ce modle encapsule un modle existant
et manipule les donnes qui sont transmises entre le modle sous-jacent et la vue. Dans notre
exemple, le modle sous-jacent est un QStringListModel initialis avec la liste des noms de
couleur reconnues par Qt (obtenue via QColor::colorNames()). Lutilisateur peut saisir une
chane de filtre dans un QLineEdit et spcifier la manire dont cette chane doit tre interprte (comme une expression rgulire, un modle gnrique ou une chane fixe) grce une
zone de liste droulante (voir Figure 10.8).
Figure 10.8
Lapplication
Color Names

Voici un extrait du constructeur de ColorNamesDialog:


ColorNamesDialog::ColorNamesDialog(QWidget *parent)
: QDialog(parent)
{
sourceModel = new QStringListModel(this);
sourceModel->setStringList(QColor::colorNames());
proxyModel = new QSortFilterProxyModel(this);

Qt 4 Livre Page 241 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

241

proxyModel->setSourceModel(sourceModel);
proxyModel->setFilterKeyColumn(0);
listView = new QListView;
listView->setModel(proxyModel);
...
syntaxComboBox = new QComboBox;
syntaxComboBox->addItem(tr("Regular expression"), QRegExp::RegExp);
syntaxComboBox->addItem(tr("Wildcard"), QRegExp::Wildcard);
syntaxComboBox->addItem(tr("Fixed string"), QRegExp::FixedString);
...
}

QStringListModel est cr et aliment de manire habituelle. Puis, nous construisons


QSortFilterProxyModel. Nous transmettons le modle sous-jacent laide de setSourceModel() et nous demandons au proxy de filtrer en se basant sur la colonne 0 du modle original.
La fonction QComboBox::addItem() reoit un argument facultatif "donne" de type QVariant;
nous lutilisons pour enregistrer la valeur QRegExp::PatternSyntax qui correspond au texte
de chaque lment.
void ColorNamesDialog::reapplyFilter()
{
QRegExp::PatternSyntax syntax =
QRegExp::PatternSyntax(syntaxComboBox->itemData(
syntaxComboBox->currentIndex()).toInt());
QRegExp regExp(filterLineEdit->text(), Qt::CaseInsensitive, syntax);
proxyModel->setFilterRegExp(regExp);
}

Le slot reapplyFilter() est invoqu ds que lutilisateur modifie la chane de filtre ou la


zone de liste droulante correspondant au modle. Nous crons un QRegExp en utilisant le
texte prsent dans lditeur de lignes. Nous faisons ensuite correspondre la syntaxe de son
modle celle stocke dans les donnes de llment en cours dans la zone de liste droulante
relative la syntaxe. Puis nous appelons setFilterRegExp(), le nouveau filtre sactive et la
vue est mise jour automatiquement.

Implmenter des modles personnaliss


Les modles prdfinis de Qt sont pratiques pour grer et afficher des donnes. Cependant,
certaines sources de donnes ne peuvent pas tre utilises efficacement avec les modles
prdfinis, cest pourquoi il est parfois ncessaire de crer des modles personnaliss optimiss
pour la source de donnes sous-jacente.
Avant de commencer crer des modles personnaliss, analysons dabord les concepts essentiels utiliss dans larchitecture modle/vue de Qt. Chaque lment de donnes dans un modle
possde un index de modle et un ensemble dattributs, appels rles, qui peuvent prendre des
valeurs arbitraires. Nous avons vu prcdemment que les rles les plus couramment employs

Qt 4 Livre Page 242 Jeudi, 7. dcembre 2006 12:14 12

242

Qt4 et C++ : Programmation dinterfaces GUI

sont Qt::EditRole et Qt::DisplayRole. Dautres rles sont utiliss pour des donnes
supplmentaires (par exemple Qt::ToolTipRole, Qt::StatusTipRole et Qt::WhatsThisRole) et dautres encore pour contrler les attributs daffichage de base (tels que
Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole et Qt::BackgroundColorRole).
Figure 10.9
Vue schmatique des
modles de Qt

Modle liste

racine
ligne
0
1

Modle de tableau
racine
ligne

Modle arborescence

racine
ligne

1
colonne 0

1
2
colonne

0 1 2

Pour un modle liste, le seul composant dindex pertinent est le nombre de lignes, accessible
depuis QModelIndex::row(). Pour un modle de tableau, les composants dindex pertinents
sont les nombres de lignes et de colonnes, accessibles depuis QModelIndex::row() et
QModelIndex::column(). Pour les modles liste et tableau, le parent de chaque lment est
la racine, qui est reprsente par un QModelIndex invalide. Les deux premiers exemples de
cette section vous montrent comment implmenter des modles de tableau personnaliss.
Un modle arborescence ressemble un modle de tableau, quelques diffrences prs.
Comme un modle de tableau, la racine est le parent des lments de haut niveau (un QModelIndex invalide), mais le parent de tout autre lment est un autre lment dans la hirarchie.
Les parents sont accessibles depuis QModelIndex::parent(). Chaque lment possde ses
donnes de rle et aucun ou plusieurs enfants, chacun tant un lment en soi. Vu que les
lments peuvent avoir dautres lments comme enfants, il est possible de reprsenter des
structures de donnes rcursives ( la faon dune arborescence), comme vous le montrera le
dernier exemple de cette section.
Le premier exemple de cette section est un modle de tableau en lecture seule qui affiche des
valeurs montaires en relation les unes avec les autres (voir Figure 10.10).
Lapplication pourrait tre implmente partir dun simple tableau, mais nous voulons nous
servir dun modle personnalis pour profiter de certaines proprits des donnes qui minimisent le stockage. Si nous voulions conserver les 162 devises actuellement cotes dans un
tableau, nous devrions stocker 162 162 = 26 244 valeurs ; avec le modle personnalis
prsent ci-aprs, nous nenregistrons que 162 valeurs (la valeur de chaque devise par rapport
au dollar amricain).

Qt 4 Livre Page 243 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

243

Figure 10.10
Lapplication Currencies

La classe CurrencyModel sera utilise avec un QTableView standard. Elle est alimente avec
un QMap<QString,double>; chaque cl correspond au code de la devise et chaque valeur
correspond la valeur de la devise en dollars amricains. Voici un extrait de code qui montre
comment le tableau de correspondance est aliment et comment le modle est utilis :
QMap<QString, double> currencyMap;
currencyMap.insert("AUD", 1.3259);
currencyMap.insert("CHF", 1.2970);
...
currencyMap.insert("SGD", 1.6901);
currencyMap.insert("USD", 1.0000);
CurrencyModel currencyModel;
currencyModel.setCurrencyMap(currencyMap);
QTableView tableView;
tableView.setModel(&currencyModel);
tableView.setAlternatingRowColors(true);

Etudions dsormais limplmentation du modle, en commenant par son en-tte :


class CurrencyModel: public QAbstractTableModel
{
public:
CurrencyModel(QObject *parent = 0);
void setCurrencyMap(const QMap<QString, double> &map);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role) const;
private:
QString currencyAt(int offset) const;
QMap<QString, double> currencyMap;
};

Qt 4 Livre Page 244 Jeudi, 7. dcembre 2006 12:14 12

244

Qt4 et C++ : Programmation dinterfaces GUI

Nous avons choisi de driver QAbstractTableModel pour notre modle, parce que cela
correspond le plus notre source de donnes. Qt propose plusieurs classes de base de modle,
y compris QAbstractListModel, QAbstractTableModel et QAbstractItemModel (voir
Figure 10.11). La classe QAbstractItemModel est employe pour supporter une grande
varit de modles, dont ceux qui se basent sur des structures de donnes rcursives, alors que
les classes QAbstractListModel et QAbstractTableModel sont proposes pour une question
de commodit lors de lutilisation densembles de donnes une ou deux dimensions.
Figure 10.11
Arbre dhritage
des classes de modle
abstraites

QObject
QAbstractItemModel
QAbstractListModel

QAbstractTableModel

Pour un modle de tableau en lecture seule, nous devons rimplmenter trois fonctions :
rowCount(), columnCount() et data(). Dans ce cas, nous avons aussi rimplment
headerData() et nous fournissons une fonction pour initialiser les donnes (setCurrencyMap()).
CurrencyModel::CurrencyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}

Nous navons pas besoin de faire quoi que ce soit dans le constructeur, sauf transmettre le paramtre parent la classe de base.
int CurrencyModel::rowCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}
int CurrencyModel::columnCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}

Pour ce modle de tableau, les nombres de lignes et de colonnes correspondent aux nombres
de devises dans le tableau de correspondance des devises. Le paramtre parent na aucune
signification pour un modle de tableau ; il est prsent parce que rowCount() et columnCount() sont hrits de la classe de base QAbstractItemModel plus gnrique, qui prend en
charge les hirarchies.
QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

Qt 4 Livre Page 245 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

245

if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
QString rowCurrency = currencyAt(index.row());
QString columnCurrency = currencyAt(index.column());
if (currencyMap.value(rowCurrency) == 0.0)
return "####";
double amount = currencyMap.value(columnCurrency)
/ currencyMap.value(rowCurrency);
return QString("%1").arg(amount, 0, f, 4);
}
return QVariant();
}

La fonction data() retourne la valeur de nimporte quel rle dun lment. Llment est
spcifi sous forme de QModelIndex. Pour un modle de tableau, les composants intressants
dun QModelIndex sont ses nombres de lignes et de colonnes, disponibles grce row() et
column().
Si le rle est Qt::TextAlignmentRole, nous retournons un alignement adapt aux nombres.
Si le rle daffichage est Qt::DisplayRole, nous recherchons la valeur de chaque devise et
nous calculons le taux de change.
Nous pourrions retourner la valeur calcule sous forme de type double, mais nous naurions
aucun contrle sur le nombre de chiffres aprs la virgule ( moins dutiliser un dlgu personnalis). Nous retournons donc plutt la valeur sous forme de chane, mise en forme comme
nous le souhaitons.
QVariant CurrencyModel::headerData(int section,
Qt::Orientation /* orientation */,
int role) const
{
if (role!= Qt::DisplayRole)
return QVariant();
return currencyAt(section);
}

La fonction headerData() est appele par la vue pour alimenter ses en-ttes verticaux et horizontaux. Le paramtre section correspond au nombre de lignes ou de colonnes (selon lorientation). Vu que les lignes et les colonnes ont les mmes codes de devise, nous ne nous soucions
pas de lorientation et nous retournons simplement le code de la devise pour le numro de
section donn.
void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
{
currencyMap = map;
reset();
}

Qt 4 Livre Page 246 Jeudi, 7. dcembre 2006 12:14 12

246

Qt4 et C++ : Programmation dinterfaces GUI

Lappelant peut modifier le tableau de correspondance des devises en excutant setCurrencyMap(). Lappel de QAbstractItemModel::reset() informe nimporte quelle vue qui
utilise le modle que toutes leurs donnes sont invalides ; ceci les oblige demander des
donnes actualises pour les lments visibles.
QString CurrencyModel::currencyAt(int offset) const
{
return (currencyMap.begin() + offset).key();
}

La fonction currencyAt() retourne la cl (le code de la devise) la position donne dans le


tableau de correspondance des devises. Nous utilisons un itrateur de style STL pour trouver
llment et appeler key().
Comme nous venons de le voir, il nest pas difficile de crer des modles en lecture seule, et en
fonction de la nature des donnes sous-jacentes, il est possible dconomiser de la mmoire et
dacclrer les temps de rponse avec un modle bien conu. Le prochain exemple, lapplication Cities, se base aussi sur un tableau, mais cette fois-ci les donnes sont saisies par lutilisateur
(voir Figure 10.12).
Cette application est utilise pour enregistrer des valeurs indiquant la distance entre deux
villes. Comme lexemple prcdent, nous pourrions simplement utiliser un QTableWidget et
stocker un lment pour chaque paire de villes. Cependant, un modle personnalis pourrait
tre plus efficace, parce que la distance entre une ville A et une ville B est la mme que vous
alliez de A B ou de B A, les lments se refltent donc le long de la diagonale principale.
Pour voir comment un modle personnalis se compare un simple tableau, supposons que
nous avons trois villes, A, B et C. Si nous conservions une valeur pour chaque combinaison,
nous devrions stocker neuf valeurs. Un modle bien conu ne ncessiterait que trois lments
(A, B), (A, C) et (B, C).
Figure 10.12
Lapplication Cities

Voici comment nous avons configur et exploit le modle :


QStringList cities;
cities << "Arvika" << "Boden" << "Eskilstuna" << "Falun"
<< "Filipstad" << "Halmstad" << "Helsingborg" << "Karlstad"

Qt 4 Livre Page 247 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

247

<< "Kiruna" << "Kramfors" << "Motala" << "Sandviken"


<< "Skara" << "Stockholm" << "Sundsvall" << "Trelleborg";
CityModel cityModel;
cityModel.setCities(cities);
QTableView tableView;
tableView.setModel(&cityModel);
tableView.setAlternatingRowColors(true);

Nous devons rimplmenter les mmes fonctions que pour lexemple prcdent. De plus, nous
devons aussi rimplmenter setData() et flags() pour que le modle puisse tre modifi.
Voici la dfinition de classe :
class CityModel: public QAbstractTableModel
{
Q_OBJECT
public:
CityModel(QObject *parent = 0);
void setCities(const QStringList &cityNames);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role);
QVariant headerData(int section, Qt::Orientation orientation,
int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
private:
int offsetOf(int row, int column) const;
QStringList cities;
QVector<int> distances;
};

Pour ce modle, nous utilisons deux structures de donnes : cities de type QStringList
pour contenir les noms de ville, et distances de type QVector<int> pour enregistrer la
distance entre chaque paire unique de villes.
CityModel::CityModel(QObject *parent)
: QAbstractTableModel(parent)
{
}

Le constructeur ne fait rien part transmettre le paramtre parent la classe de base.


int CityModel::rowCount(const QModelIndex & /* parent */) const
{
return cities.count();

Qt 4 Livre Page 248 Jeudi, 7. dcembre 2006 12:14 12

248

Qt4 et C++ : Programmation dinterfaces GUI

}
int CityModel::columnCount(const QModelIndex & /* parent */) const
{
return cities.count();
}

Vu que nous avons une grille carre de villes, le nombre de lignes et de colonnes correspond au
nombre de villes de notre liste.
QVariant CityModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
if (index.row() == index.column())
return 0;
int offset = offsetOf(index.row(), index.column());
return distances[offset];
}
return QVariant();
}

La fonction data() est similaire ce que nous effectu dans CurrencyModel. Elle retourne 0
si la ligne et la colonne sont identiques, parce que cela correspond au cas o les deux villes
sont les mmes ; sinon elle recherche lentre de la ligne et de la colonne dans le vecteur
distances et renvoie la distance pour cette paire de villes particulire.
QVariant CityModel::headerData(int section,
Qt::Orientation /* orientation */,
int role) const
{
if (role == Qt::DisplayRole)
return cities[section];
return QVariant();
}

La fonction headerData() est simple puisque nous avons un tableau carr o chaque ligne
possde un en-tte de colonne identique. Nous retournons simplement le nom de la ville la
position donne dans la liste de chane cities.
bool CityModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && index.row()!= index.column()
&& role == Qt::EditRole) {
int offset = offsetOf(index.row(), index.column());
distances[offset] = value.toInt();

Qt 4 Livre Page 249 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

249

QModelIndex transposedIndex = createIndex(index.column(),


index.row());
emit dataChanged(index, index);
emit dataChanged(transposedIndex, transposedIndex);
return true;
}
return false;
}

La fonction setData() est invoque quand lutilisateur modifie un lment. En supposant que
lindex de modle est valide, que les deux villes sont diffrentes et que llment de donnes
modifier est Qt::EditRole, la fonction stocke la valeur que lutilisateur a saisie dans le
vecteur distances.
La fonction createIndex() sert gnrer un index de modle. Nous en avons besoin pour
obtenir lindex de modle de llment symtrique de llment configur par rapport la
diagonale principale, vu que les deux lments doivent afficher les mmes donnes. La fonction createIndex() reoit la ligne avant la colonne ; ici, nous inversons les paramtres pour
obtenir lindex de modle de llment symtrique celui spcifi par index.
Nous mettons le signal dataChanged() avec lindex de modle de llment qui a t modifi.
Ce signal reoit deux index de modle, parce quun changement peut affecter une rgion
rectangulaire constitue de plusieurs lignes et colonnes. Les index transmis reprsentent
llment situ en haut gauche et llment en bas droite de la zone modifie. Nous mettons aussi le signal dataChanged() lattention de lindex transpos afin que la vue actualise
laffichage de llment. Enfin, nous retournons true ou false pour indiquer si la modification a
t effectue avec succs ou non.
Qt::ItemFlags CityModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if (index.row()!= index.column())
flags |= Qt::ItemIsEditable;
return flags;
}

Le modle se sert de la fonction flags() pour annoncer les possibilits daction sur llment
(par exemple, sil peut tre modifi ou non). Limplmentation par dfaut de QAbstractTableModel retourne Qt::ItemIsSelectable | Qt::ItemIsEnabled. Nous ajoutons lindicateur
Qt::ItemIsEditable pour tous les lments sauf ceux qui se trouvent sur les diagonales (qui
sont toujours nuls).
void CityModel::setCities(const QStringList &cityNames)
{
cities = cityNames;
distances.resize(cities.count() * (cities.count() - 1) / 2);
distances.fill(0);
reset();
}

Qt 4 Livre Page 250 Jeudi, 7. dcembre 2006 12:14 12

250

Qt4 et C++ : Programmation dinterfaces GUI

Si nous recevons une nouvelle liste de villes, nous dfinissons le QStringList priv en
nouvelle liste, nous redimensionnons et nous effaons le vecteur distances puis nous appelons
QAbstractItemModel::reset() pour informer toutes les vues que leurs lments visibles
doivent tre nouveau rcuprs.
int CityModel::offsetOf(int row, int column) const
{
if (row < column)
qSwap(row, column);
return (row * (row - 1) / 2) + column;
}

La fonction prive offsetOf() calcule lindex dune paire de villes donne dans le vecteur
distances. Par exemple, si nous avions les villes A, B, C et D et si lutilisateur avait mis
jour la ligne 3, colonne 1, B D, le dcalage serait de 3 _ (3 1)/2 + 1 = 4. Si lutilisateur avait
mis jour la ligne 1, colonne 3, D B, grce qSwap(), exactement le mme calcul aurait t
accompli et un dcalage identique aurait t retourn.
Figure 10.13
Les structures de donnes
cities et distances
et le modle de tableau

Modle de tableau

Villes
A

D
A

Distances

A B A C A D B C B D C D

A
0

B
C
D
A B A C A D

A B

C
D

0
A C B C
C D
0
A D B D C D

B C B D

Le dernier exemple de cette section est un modle qui prsente larbre danalyse dune expression rgulire donne. Une expression rgulire est constitue dun ou plusieurs termes, spars par des caractres "|". Lexpression rgulire "alpha|bravo|charlie" contient donc trois
termes. Chaque terme est une squence dun ou plusieurs facteurs ; par exemple, le terme
"bravo" est compos de cinq facteurs (chaque lettre est un facteur). Les facteurs peuvent
encore tre dcomposs en atome et en quantificateur facultatif, comme "*", "+" et "?".Vu que
les expressions rgulires peuvent contenir des sous-expressions entre parenthses, les arbres
danalyse correspondants seront rcursifs.
Lexpression rgulire prsente en Figure 10.14, "ab|(cd)?e", correspond un a suivi dun
b, ou dun c suivi dun d puis dun e, ou simplement dun e. Elle correspondra ainsi
"ab" et "cde", mais pas "bc" ou "cd".
Lapplication Regexp Parser se compose de quatre classes :

RegExpWindow est une fentre qui permet lutilisateur de saisir une expression rgulire
et qui affiche larbre danalyse correspondant.

RegExpParser gnre un arbre danalyse partir dune expression rgulire.


RegExpModel est un modle darborescence qui encapsule un arbre danalyse.
Node reprsente un lment dans un arbre danalyse.

Qt 4 Livre Page 251 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

251

Figure 10.14
Lapplication
Regexp Parser

Commenons par la classe Node:


class Node
{
public:
enum Type { RegExp, Expression, Term, Factor, Atom, Terminal };
Node(Type type, const QString &str = "");
~Node();
Type type;
QString str;
Node *parent;
QList<Node *> children;
};

Chaque nud possde un type, une chane (qui peut tre vide), un parent (qui peut tre 0) et
une liste de nuds enfants (qui peut tre vide).
Node::Node(Type type, const QString &str)
{
this->type = type;
this->str = str;
parent = 0;
}

Le constructeur initialise simplement le type et la chane du nud. Etant donn que toutes les
donnes sont publiques, le code qui utilise Node peut oprer directement sur le type, la chane,
le parent et les enfants.
Node::~Node()
{
qDeleteAll(children);
}

Qt 4 Livre Page 252 Jeudi, 7. dcembre 2006 12:14 12

252

Qt4 et C++ : Programmation dinterfaces GUI

La fonction qDeleteAll() parcourt un conteneur de pointeurs et appelle delete sur chacun


deux. Elle ne dfinit pas les pointeurs en 0, donc si elle est utilise en dehors dun destructeur,
il est frquent de la voir suivie dun appel de clear() sur le conteneur qui renferme les
pointeurs.
Maintenant que nous avons dfini nos lments de donnes (chacun reprsent par un Node),
nous sommes prts crer un modle :
class RegExpModel: public QAbstractItemModel
{
public:
RegExpModel(QObject *parent = 0);
~RegExpModel();
void setRootNode(Node *node);
QModelIndex index(int row, int column,
const QModelIndex &parent) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role) const;
private:
Node *nodeFromIndex(const QModelIndex &index) const;
Node *rootNode;
};

Cette fois-ci nous avons hrit de QAbstractItemModel plutt que de sa sous-classe ddie
QAbstractTableModel, parce que nous voulons crer un modle hirarchique. Les fonctions
essentielles que nous devons rimplmenter sont toujours les mmes, sauf que nous devons
aussi implmenter index() et parent(). Pour dfinir les donnes du modle, une fonction
setRootNode() doit tre invoque avec le nud racine de larbre danalyse.
RegExpModel::RegExpModel(QObject *parent)
: QAbstractItemModel(parent)
{
rootNode = 0;
}

Dans le constructeur du modle, nous navons qu configurer le nud racine en valeur nulle et
transmettre le parent la classe de base.
RegExpModel::~RegExpModel()
{
delete rootNode;
}

Qt 4 Livre Page 253 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

253

Dans le destructeur, nous supprimons le nud racine. Si le nud racine a des enfants, chacun
deux est supprim par le destructeur Node, et ainsi de suite de manire rcursive.
void RegExpModel::setRootNode(Node *node)
{
delete rootNode;
rootNode = node;
reset();
}

Quand un nouveau nud racine est dfini, nous supprimons dabord tout nud racine prcdent (et tous ses enfants). Nous configurons ensuite le nouveau nud racine et nous appelons
reset() pour informer les vues quelles doivent nouveau rcuprer les donnes des
lments visibles.
QModelIndex RegExpModel::index(int row, int column,
const QModelIndex &parent) const
{
if (!rootNode)
return QModelIndex();
Node *parentNode = nodeFromIndex(parent)
return createIndex(row, column, parentNode->children[row]);
}

La fonction index() est rimplmente dans QAbstractItemModel. Elle est appele ds que
le modle ou la vue doit crer un QModelIndex pour un lment enfant particulier (ou un
lment de haut niveau si parent est un QModelIndex invalide). Pour les modles de tableau
et de liste, nous navons pas besoin de rimplmenter cette fonction, parce que les implmentations par dfaut de QAbstractListModel et QAbstractTableModel sont normalement
suffisantes.
Dans notre implmentation dindex(), si aucun arbre danalyse nest configur, nous retournons un QModelIndex invalide. Sinon, nous crons un QModelIndex avec la ligne et la
colonne donnes et avec un Node * pour lenfant demand. Sagissant des modles hirarchiques, il nest pas suffisant de connatre la ligne et la colonne dun lment par rapport son
parent pour lidentifier ; nous devons aussi savoir qui est son parent. Pour rsoudre ce
problme, nous pouvons stocker un pointeur vers le nud interne dans le QModelIndex.
QModelIndex nous permet de conserver un void * ou un int en plus des nombres de lignes
et de colonnes.
Le Node * de lenfant est obtenu par le biais de la liste children du nud parent. Le nud
parent est extrait de lindex de modle parent grce la fonction prive nodeFromIndex():
Node *RegExpModel::nodeFromIndex(const QModelIndex &index) const
{
if (index.isValid()) {
return static_cast<Node *>(index.internalPointer());
} else {
return rootNode;
}
}

Qt 4 Livre Page 254 Jeudi, 7. dcembre 2006 12:14 12

254

Qt4 et C++ : Programmation dinterfaces GUI

La fonction nodeFromIndex() convertit le void * de lindex donn en Node * ou retourne le


nud racine si lindex est invalide, puisquun index de modle invalide reprsente la racine
dans un modle.
int RegExpModel::rowCount(const QModelIndex &parent) const
{
Node *parentNode = nodeFromIndex(parent);
if (!parentNode)
return 0;
return parentNode->children.count();
}

Le nombre de lignes dun lment particulier correspond simplement au nombre denfants


quil possde.
int RegExpModel::columnCount(const QModelIndex & /* parent */) const
{
return 2;
}

Le nombre de colonnes est fix 2. La premire colonne contient les types de nuds ; la
seconde comporte les valeurs des nuds.
QModelIndex RegExpModel::parent(const QModelIndex &child) const
{
Node *node = nodeFromIndex(child);
if (!node)
return QModelIndex();
Node *parentNode = node->parent;
if (!parentNode)
return QModelIndex();
Node *grandparentNode = parentNode->parent;
if (!grandparentNode)
return QModelIndex();
int row = grandparentNode->children.indexOf(parentNode);
return createIndex(row, child.column(), parentNode);
}

Rcuprer le parent QModelIndex dun enfant est un peu plus complexe que de rechercher
lenfant dun parent. Nous pouvons facilement rcuprer le nud parent laide de nodeFromIndex() et poursuivre en utilisant le pointeur du parent de Node, mais pour obtenir le numro
de ligne (la position du parent parmi ses pairs), nous devons remonter jusquau grand-parent et
rechercher la position dindex du parent dans la liste des enfants de son parent (cest--dire
celle du grand-parent de lenfant).
QVariant RegExpModel::data(const QModelIndex &index, int role) const
{
if (role!= Qt::DisplayRole)
return QVariant();

Qt 4 Livre Page 255 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

255

Node *node = nodeFromIndex(index);


if (!node)
return QVariant();
if (index.column() == 0) {
switch (node->type) {
case Node::RegExp:
return tr("RegExp");
case Node::Expression:
return tr("Expression");
case Node::Term:
return tr("Term");
case Node::Factor:
return tr("Factor");
case Node::Atom:
return tr("Atom");
case Node::Terminal:
return tr("Terminal");
default:
return tr("Unknown");
}
} else if (index.column() == 1) {
return node->str;
}
return QVariant();
}

Dans data(), nous rcuprons le Node * de llment demand et nous nous en servons pour
accder aux donnes sous-jacentes. Si lappelant veut une valeur pour nimporte quel rle
except Qt::DisplayRole ou sil ne peut pas rcuprer un Node pour lindex de modle
donn, nous retournons un QVariant invalide. Si la colonne est 0, nous renvoyons le nom du
type du nud ; si la colonne est 1, nous retournons la valeur du nud (sa chane).
QVariant RegExpModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
if (section == 0) {
return tr("Node");
} else if (section == 1) {
return tr("Value");
}
}
return QVariant();
}

Qt 4 Livre Page 256 Jeudi, 7. dcembre 2006 12:14 12

256

Qt4 et C++ : Programmation dinterfaces GUI

Dans notre rimplmentation de headerData(), nous retournons les intituls appropris des
en-ttes horizontaux. La classe QTreeView, qui est employe pour visualiser des modles
hirarchiques, ne possde pas den-tte vertical, nous ignorons donc cette ventualit.
Maintenant que nous avons tudi les classes Node et RegExpModel, voyons comment le nud
racine est cr quand lutilisateur modifie le texte dans lditeur de lignes :
void RegExpWindow::regExpChanged(const QString &regExp)
{
RegExpParser parser;
Node *rootNode = parser.parse(regExp);
regExpModel->setRootNode(rootNode);
}

Quand lutilisateur change le texte dans lditeur de lignes de lapplication, le slot regExpChanged() de la fentre principale est appel. Dans ce slot, le texte de lutilisateur est analys
et lanalyseur retourne un pointeur vers le nud racine de larbre danalyse.
Nous navons pas tudi la classe RegExpParser parce quelle nest pas pertinente pour les
interfaces graphiques ou la programmation modle/vue. Le code source complet de cet exemple
se trouve sur la page ddie cet ouvrage sur le site web de Pearson, www.pearson.fr.
Dans cette section, nous avons vu comment crer trois modles personnaliss diffrents. De
nombreux modles sont beaucoup plus simples que ceux prsents ici, avec des correspondances uniques entre les lments et les index de modle. Dautres exemples modle/vue sont
fournis avec Qt, accompagns dune documentation dtaille.

Implmenter des dlgus personnaliss


Les lments individuels dans les vues sont affichs et modifis laide de dlgus. Dans la
majorit des cas, le dlgu par dfaut propos par une vue savre suffisant. Si nous voulons
contrler davantage laffichage des lments, nous pouvons atteindre notre objectif simplement en utilisant un modle personnalis : dans notre rimplmentation de data(), nous
avons la possibilit de grer Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole et Qt::BackgroundColorRole et ceux-ci sont employs par le dlgu par dfaut. Par
exemple, dans les exemples Cities et Currencies prsents auparavant, nous avons gr
Qt::TextAlignmentRole pour obtenir des nombres justifis droite.
Si nous voulons encore plus de contrle, nous pouvons crer notre propre classe de dlgu et
la dfinir sur les vues qui lutiliseront. La bote de dialogue Track Editor illustre en
Figure 10.15 est base sur un dlgu personnalis. Elle affiche les titres des pistes de musique
ainsi que leur dure. Les donnes stockes dans le modle seront simplement des QString
(pour les titres) et des int (pour les secondes), mais les dures seront divises en minutes et en
secondes et pourront tre modifies laide de QTimeEdit.

Qt 4 Livre Page 257 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

257

Figure 10.15
La bote de dialogue
Track Editor

La bote de dialogue Track Editor se sert dun QTableWidget, une sous-classe ddie laffichage dlments qui agit sur des QTableWidgetItem. Les donnes sont proposes sous
forme dune liste de Track:
class Track
{
public:
Track(const QString &title = "", int duration = 0);
QString title;
int duration;
};

Voici un extrait du constructeur qui prsente la cration et lalimentation en donnes du widget


tableau :
TrackEditor::TrackEditor(QList<Track> *tracks, QWidget *parent)
: QDialog(parent)
{
this->tracks = tracks;
tableWidget = new QTableWidget(tracks->count(), 2);
tableWidget->setItemDelegate(new TrackDelegate(1));
tableWidget->setHorizontalHeaderLabels(
QStringList() << tr("Track") << tr("Duration"));
for (int row = 0; row < tracks->count(); ++row) {
Track track = tracks->at(row);
QTableWidgetItem *item0 = new QTableWidgetItem(track.title);
tableWidget->setItem(row, 0, item0);
QTableWidgetItem *item1
= new QTableWidgetItem(QString::number(track.duration));
item1->setTextAlignment(Qt::AlignRight);
tableWidget->setItem(row, 1, item1);
}
...
}

Qt 4 Livre Page 258 Jeudi, 7. dcembre 2006 12:14 12

258

Qt4 et C++ : Programmation dinterfaces GUI

Le constructeur cre un widget tableau et au lieu dutiliser simplement le dlgu par dfaut,
nous dfinissons notre TrackDelegate personnalis, lui transmettant la colonne qui contient
les donnes de temps. Nous configurons dabord les en-ttes des colonnes, puis nous parcourons
les donnes, en alimentant les lignes avec le nom et la dure de chaque piste.
Le reste du constructeur et de la bote de dialogue TrackEditor ne prsentent aucune particularit, nous allons donc analyser maintenant le TrackDelegate qui gre le rendu et la modification
des donnes de la piste.
class TrackDelegate: public QItemDelegate
{
Q_OBJECT
public:
TrackDelegate(int durationColumn, QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private slots:
void commitAndCloseEditor();
private:
int durationColumn;
};

Nous utilisons QItemDelegate comme classe de base afin de bnficier de limplmentation


du dlgu par dfaut. Nous aurions aussi pu utiliser QAbstractItemDelegate si nous avions
voulu tout commencer zro. Pour proposer un dlgu qui peut modifier des donnes, nous
devons implmenter createEditor(), setEditorData() et setModelData(). Nous implmentons aussi paint() pour modifier laffichage de la colonne de dure.
TrackDelegate::TrackDelegate(int durationColumn, QObject *parent)
: QItemDelegate(parent)
{
this->durationColumn = durationColumn;
}

Le paramtre durationColumn du constructeur indique au dlgu quelle colonne contient la


dure de la piste.
void TrackDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
int secs = index.model()->data(index, Qt::DisplayRole).toInt();

Qt 4 Livre Page 259 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

259

QString text = QString("%1:%2")


.arg(secs / 60, 2, 10, QChar(0))
.arg(secs % 60, 2, 10, QChar(0));
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
drawDisplay(painter, myOption, myOption.rect, text);
drawFocus(painter, myOption, myOption.rect);
} else{
QItemDelegate::paint(painter, option, index);
}
}

Vu que nous voulons afficher la dure sous la forme "minutes : secondes" nous avons rimplment la fonction paint(). Les appels de arg() reoivent un nombre entier afficher
sous forme de chane, la quantit de caractres que la chane doit contenir, la base de lentier
(10 pour un nombre dcimal) et le caractre de remplissage.
Pour justifier le texte droite, nous copions les options de style en cours et nous remplaons
lalignement par dfaut. Nous appelons ensuite QItemDelegate::drawDisplay() pour
dessiner le texte, suivi de QItemDelegate::drawFocus() qui tracera un rectangle de focus si
llment est actif et ne fera rien dans les autres cas. La fonction drawDisplay() se rvle trs
pratique, notamment avec nos propres options de style. Nous pourrions aussi dessiner directement
en utilisant le painter.
QWidget *TrackDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
QTimeEdit *timeEdit = new QTimeEdit(parent);
timeEdit->setDisplayFormat("mm:ss");
connect(timeEdit, SIGNAL(editingFinished()),
this, SLOT(commitAndCloseEditor()));
return timeEdit;
} else {
return QItemDelegate::createEditor(parent, option, index);
}
}

Nous ne voulons modifier que la dure des pistes, le changement des noms de piste reste la
charge du dlgu par dfaut. Pour ce faire, nous vrifions pour quelle colonne un diteur a t
demand au dlgu. Sil sagit de la colonne de dure, nous crons un QTimeEdit, nous dfinissons le format daffichage de manire approprie et nous relions son signal editingFinished() notre slot commitAndCloseEditor(). Pour toute autre colonne, nous cdons
la gestion des modifications au dlgu par dfaut.
void TrackDelegate::commitAndCloseEditor()
{

Qt 4 Livre Page 260 Jeudi, 7. dcembre 2006 12:14 12

260

Qt4 et C++ : Programmation dinterfaces GUI

QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender());


emit commitData(editor);
emit closeEditor(editor);
}

Si lutilisateur appuie sur Entre ou dplace le focus hors du QTimeEdit (mais pas sil appuie
sur Echap), le signal editingFinished() est mis et le slot commitAndCloseEditor() est
invoqu. Ce slot met le signal commitData() pour informer la vue quil y a des donnes
modifies qui remplacent les donnes existantes. Il met aussi le signal closeEditor() pour
informer la vue que cet diteur nest plus requis, le modle le supprimera donc. Lditeur est
rcupr laide de QObject::sender() qui retourne lobjet qui a mis le signal qui a
dclench le slot. Si lutilisateur annule (en appuyant sur Echap), la vue supprimera simplement
lditeur.
void TrackDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
int secs = index.model()->data(index, Qt::DisplayRole).toInt();
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
timeEdit->setTime(QTime(0, secs / 60, secs % 60));
} else {
QItemDelegate::setEditorData(editor, index);
}
}

Quand lutilisateur initie une modification, la vue appelle createEditor() pour crer un
diteur, puis setEditorData() pour initialiser lditeur avec les donnes en cours de llment.
Si lditeur concerne la colonne de dure, nous extrayons la dure de la piste en secondes et
nous dfinissons le temps de QTimeEdit avec le nombre correspondant de minutes et de secondes ;
sinon nous laissons le dlgu par dfaut soccuper de linitialisation.
void TrackDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
QTime time = timeEdit->time();
int secs = (time.minute() * 60) + time.second();
model->setData(index, secs);
} else {
QItemDelegate::setModelData(editor, model, index);
}
}

Si lutilisateur termine la modification (par exemple en cliquant en dehors du widget ou en


appuyant sur Entre ou Tab) au lieu de lannuler, le modle doit tre mis jour avec les
donnes de lditeur. Si la dure a chang, nous extrayons les minutes et les secondes du
QTimeEdit et nous configurons les donnes avec le nombre quivalent en secondes.

Qt 4 Livre Page 261 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 10

Classes daffichage dlments

261

Mme si ce nest pas ncessaire dans ce cas, il est tout fait possible de crer un dlgu
personnalis qui contrle troitement la modification et laffichage de nimporte quel lment
dun modle. Nous avons choisi de nous occuper dune colonne particulire, mais vu que
QModelIndex est transmis toutes les fonctions de QItemDelegate que nous rimplmentons, nous pouvons prendre le contrle par colonne, ligne, zone rectangulaire, parent ou toute
combinaison de ceux-ci jusquaux lments individuels si ncessaire.
Dans ce chapitre, nous vous avons prsent un large aperu de larchitecture modle/vue de Qt.
Nous avons vu comment utiliser les sous-classes ddies laffichage et les modles prdfinis
de Qt et comment crer des modles et des dlgus personnaliss. Toutefois, larchitecture
modle/vue est si riche que nous naurions pas suffisamment de place pour traiter tous ses
aspects. Par exemple, nous pourrions crer une vue personnalise qui naffiche pas ses
lments sous forme de liste, de tableau ou darborescence. Cest ce que propose lexemple
Chart situ dans le rpertoire examples/itemviews/chart de Qt, qui prsente une vue personnalise affichant des donnes du modle sous forme de graphique secteurs.
Il est galement possible demployer plusieurs vues pour afficher le mme modle sans mise
en forme. Toute modification effectue via une vue se refltera automatiquement et immdiatement dans les autres vues. Ce type de fonctionnalit est particulirement utile pour afficher de
grands ensembles de donnes o lutilisateur veut voir des sections de donnes qui sont logiquement loignes les unes des autres. Larchitecture prend en charge les slections : quand
deux vues ou plus utilisent le mme modle, chaque vue peut tre dfinie de manire avoir
ses propres slections indpendantes, ou alors les slections peuvent se rpartir entre les vues.
La documentation en ligne de Qt aborde la programmation daffichage dlments et les classes
qui limplmentent. Consultez le site http://doc.trolltech.com/4.1/model-view.html pour
obtenir une liste des classes pertinentes et http://doc.trolltech.com/4.1/model-viewprogramming.html pour des informations supplmentaires et des liens vers les exemples
fournis avec Qt.

Qt 4 Livre Page 262 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 263 Jeudi, 7. dcembre 2006 12:14 12

11
Classes conteneur
Au sommaire de ce chapitre
Conteneurs squentiels
Conteneurs associatifs
Algorithmes gnriques
Chanes, tableaux doctets et variants

Les classes conteneur sont des classes template polyvalentes qui stockent des lments
dun type donn en mmoire. C++ offre dj de nombreux conteneurs dans la STL
(Standard Template Library), qui est incluse dans la bibliothque C++ standard.
Qt fournissant ses propres classes conteneur, nous pouvons utiliser la fois les conteneurs STL et Qt pour les programmes Qt. Les conteneurs Qt prsentent lavantage de se
comporter de la mme faon sur toutes les plates-formes et dtre partags implicitement. Le partage implicite, ou la technique de "copie lcriture", est une optimisation
qui permet la transmission de conteneurs entiers comme valeurs sans cot significatif
pour les performances. Les conteneurs Qt comportent galement des classes ditrateurs
simple demploi inspires par Java. Elles peuvent tre diffuses au moyen dun
QDataStream et elles ncessitent moins de code dans lexcutable que les conteneurs
STL correspondants. Enfin, sur certaines plates-formes matrielles supportes par
Qtopia Core (la version Qt pour priphriques mobiles), les conteneurs Qt sont les seuls
disponibles.

Qt 4 Livre Page 264 Jeudi, 7. dcembre 2006 12:14 12

264

Qt4 et C++ : Programmation dinterfaces GUI

Qt offre la fois des conteneurs squentiels tels que QVector<T>, QLinkedList<T> et


QList<T> et des conteneurs associatifs comme QMap<K,T> et QHash<K,T>. Logiquement, les
conteneurs squentiels stockent les lments les uns aprs les autres, alors que les conteneurs
associatifs stockent des paires cl/valeur.
Qt fournit galement des algorithmes gnriques qui ralisent des oprations sur les conteneurs. Par exemple, lalgorithme qSort() trie un conteneur squentiel et qBinaryFind()
effectue une recherche binaire sur un conteneur squentiel tri. Ces algorithmes sont similaires
ceux offerts par la STL.
Si vous tes dj familier avec les conteneurs de la STL et si vous disposez de cette bibliothque sur vos plates-formes cibles, vous pouvez les utiliser la place ou en plus des conteneurs
Qt. Pour plus dinformations au sujet des fonctions et des classes de la STL, rendez-vous sur le
site Web de SGI ladresse http://www.sgi.com/tech/stl/.
Dans ce chapitre, nous tudierons galement les classes QString, QByteArray et QVariant,
qui ont toutes de nombreux points en commun avec les conteneurs. QString est une chane
Unicode 16 bits utilise dans lAPI de Qt. QByteArray est un tableau de caractres de 8 bits
utilis pour stocker des donnes binaires brutes. QVariant est un type susceptible de stocker la
plupart des types de valeurs Qt et C++.

Conteneurs squentiels
Un QVector<T> est une structure de donnes de type tableau qui stocke ses lments des
emplacements adjacents en mmoire. Un vecteur se distingue dun tableau C++ brut par le fait
quil connat sa propre taille et peut tre redimensionn. Lajout dlments supplmentaires
la fin dun vecteur est assez efficace, alors que linsertion dlments devant ou au milieu de
celui-ci peut savrer coteux. (voir Figure 11.1)
Figure 11.1
Un vecteur dlments
de type double

937.81

25.984

308.74

310.92

40.9

Si nous savons lavance combien dlments nous seront ncessaires, nous pouvons attribuer au vecteur une taille initiale lors de sa dfinition et utiliser loprateur [] pour affecter
une valeur aux lments. Dans le cas contraire, nous devons redimensionner le vecteur ultrieurement ou ajouter les lments. Voici un exemple dans lequel nous spcifions la taille
initiale :
QVector<double> vect(3);
vect[0] = 1.0;
vect[1] = 0.540302;
vect[2] = -0.416147;

Qt 4 Livre Page 265 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

265

Voici le mme exemple, commenant cette fois avec un vecteur vide et utilisant la fonction
append() pour ajouter des lments la fin :
QVector<double> vect;
vect.append(1.0);
vect.append(0.540302);
vect.append(-0.416147);

Nous pouvons galement remplacer append() par loprateur <<:


vect << 1.0 << 0.540302 << -0.416147;

Vous parcourez les lments du vecteur laide de [] et count():


double sum = 0.0;
for (int i = 0; i < vect.count(); ++i)
sum += vect[i];

Les entres de vecteur cres sans quune valeur explicite ne leur soit attribue sont initialises
au moyen du constructeur par dfaut de la classe de llment. Les types de base et les types
pointeur sont initialiss en zro.
Linsertion dlments au dbut ou au milieu dun QVector<T>, ou la suppression dlments
ces emplacements, risque de ne pas tre efficace pour de gros vecteurs. Cest pourquoi Qt
offre galement QLinkedList<T>, une structure de donnes qui stocke ses lments des
emplacements non adjacents en mmoire. Contrairement aux vecteurs, les listes chanes ne
prennent pas en charge laccs alatoire, mais elles garantissent les performances des insertions et
des suppressions. (Voir Figure 11.2)

937.81

25.984

308.74

310.92

40.9

Figure 11.2
Une liste chane dlments de type double

Les listes chanes ne fournissent pas loprateur []. Il est donc ncessaire de recourir aux
itrateurs pour parcourir leurs lments. Les itrateurs sont galement utiliss pour spcifier la
position des lments. Par exemple, le code suivant insre la chane "Tote Hosen" entre
"Clash" et "Ramones" :
QLinkedList<QString> list;
list.append("Clash");
list.append("Ramones");
QLinkedList<QString>::iterator i = list.find("Ramones");
list.insert(i, "Tote Hosen");

Nous tudierons les itrateurs en dtail ultrieurement dans cette section.

Qt 4 Livre Page 266 Jeudi, 7. dcembre 2006 12:14 12

266

Qt4 et C++ : Programmation dinterfaces GUI

Le conteneur squentiel QList<T> est une "liste-tableau" qui combine les principaux avantages de QVector<T> et de QLinkedList<T> dans une seule classe. Il prend en charge laccs
alatoire et son interface est base sur les index, de la mme faon que celle de QVector.
Lajout ou la suppression dun lment une extrmit dun QList<T> est trs rapide. En
outre, une insertion au sein dune liste contenant jusqu un millier dlments est galement
trs simple. A moins que nous souhaitions raliser des insertions au milieu de listes de taille
trs importante ou que nous ayons besoin que les lments de la liste occupent des adresses
conscutives en mmoire, QList<T> constitue gnralement la classe conteneur polyvalente la
plus approprie. La classe QStringList est une sous-classe de QList<QString> qui est
largement utilise dans lAPI de Qt. En plus des fonctions quelle hrite de sa classe de base,
elle fournit des fonctions supplmentaires qui la rendent plus souple demploi pour la gestion
de chane. QStringList est tudie dans la dernire section de ce chapitre.

QStack<T> et QQueue<T> sont deux exemples supplmentaires de sous-classes utilitaires.


QStack<T> est un vecteur qui fournit push(), pop() et top(). QQueue<T> est une liste qui
fournit enqueue(), dequeue() et head().
Pour toutes les classes conteneur rencontres jusqu prsent, le type de valeur T peut tre un
type de base tel que int ou double, un type pointeur ou une classe qui possde un constructeur par dfaut (un constructeur qui ne reoit aucun argument), un constructeur de copie et un
oprateur daffectation. Les classes qui remplissent les conditions requises incluent QByteArray, QDateTime, QRegExp, QString et QVariant. Les classes Qt qui hritent de QObject
savrent inadquates, car il leur manque un constructeur de copie et un oprateur daffectation. Ceci ne pose pas de problme dans la pratique, car nous pouvons simplement stocker des
pointeurs vers des types QObject plutt que les objets eux-mmes.
Le type de valeur T peut galement tre un conteneur, auquel cas nous devons sparer deux
crochets conscutifs par des espaces. Sinon, le compilateur butera sur ce quil pense tre un
oprateur >>. Par exemple :
QList<QVector<double> > list;

En plus des types que nous venons de mentionner, le type de valeur dun conteneur peut tre
toute classe personnalise correspondant aux critres dcrits prcdemment. Voici un exemple
de classe de ce type :
class Movie
{
public:
Movie(const QString &title = "", int duration = 0);
void setTitle(const QString &title) { myTitle = title; }
QString title() const { return myTitle; }
void setDuration(int duration) { myDuration = duration; }
QString duration() const { return myDuration; }

Qt 4 Livre Page 267 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

267

private:
QString myTitle;
int myDuration;
};

La classe possde un constructeur qui nexige aucun argument (bien quil puisse en recevoir
jusqu deux). Elle possde galement un constructeur de copie et un oprateur daffectation,
tous deux tant implicitement fournis par C++. Pour cette classe, la copie au membre par
membre est suffisante. Il nest donc pas ncessaire dimplmenter votre propre constructeur de
copie et votre oprateur daffectation.
Qt fournit deux catgories ditrateurs afin de parcourir les lments stocks dans un conteneur. Les itrateurs de style Java et ceux de style STL. Les itrateurs de style Java sont plus
faciles utiliser, alors que ceux de style STL sont plus puissants et peuvent tre combins avec
les algorithmes gnriques de Qt et de STL.
Pour chaque classe conteneur, il existe deux types ditrateurs de style Java : un itrateur en
lecture seulement et un itrateur en lecture-criture. Les classes ditrateur en lecture seulement sont QVectorIterator<T>, QLinkedListIterator<T> et QListIterator<T>. Les
itrateurs en lecture/criture correspondants comportent le terme Mutable dans leur nom (par
exemple, QMutableVectorIterator<T>). Dans cette discussion, nous allons surtout tudier
les itrateurs de QList; les itrateurs pour les listes chanes et les vecteurs possdent la
mme API. (Voir Figure 11.3)
Figure 11.3
Emplacements valides
pour les itrateurs
de style Java

Le premier point garder lesprit lors de lutilisation ditrateurs de style Java est quils ne
pointent pas directement vers des lments. Ils peuvent tre situs avant le premier lment,
aprs le dernier ou entre deux. Voici la syntaxe dune boucle ditration typique :
QList<double> list;
...
QListIterator<double> i(list);
while (i.hasNext()) {
do_something(i.next());
}

Litrateur est initialis avec le conteneur parcourir. A ce stade, litrateur est situ juste avant
le premier lment. Lappel hasNext() retourne true si un lment se situe sur la droite de
litrateur. La fonction next() retourne llment situ sur la droite de litrateur et avance ce
dernier jusqu la prochaine position valide.

Qt 4 Livre Page 268 Jeudi, 7. dcembre 2006 12:14 12

268

Qt4 et C++ : Programmation dinterfaces GUI

Litration vers larrire est similaire, si ce nest que nous devons tout dabord appeler
toBack() pour placer litrateur aprs le dernier lment :
QListIterator<double> i(list);
i.toBack();
while (i.hasPrevious()) {
do_something(i.previous());
}

La fonction hasPrevious() retourne true si un lment se trouve sur la gauche de litrateur ; previous() retourne cet lment et le dplace vers larrire. Les itrateurs next() et
previous() retournent llment que litrateur vient de passer. (Voir Figure 11.4).
Figure 11.4
Effet de previous()
et de next() sur
un itrateur de style Java

A
previous()

next()

Les itrateurs mutables fournissent des fonctions destines insrer, modifier et supprimer des
lments lors de litration. La boucle suivante supprime tous les nombres ngatifs dune liste :
QMutableListIterator<double> i(list);
while (i.hasNext()) {
if (i.next() < 0.0)
i.remove();
}

La fonction remove() opre toujours sur le dernier lment pass. Elle fonctionne galement
lors de litration vers larrire.
QMutableListIterator<double> i(list);
i.toBack();
while (i.hasPrevious()) {
if (i.previous() < 0.0)
i.remove();
}

De la mme faon, les itrateurs mutables de style Java fournissent une fonction setValue()
qui modifie le dernier lment pass. Voici comment nous remplacerions des nombres ngatifs
par leur valeur absolue :
QMutableListIterator<double> i(list);
while (i.hasNext()) {
int val = i.next();
if (val < 0.0)
i.setValue(-val);
}

Qt 4 Livre Page 269 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

269

Il est galement possible dinsrer un lment lemplacement courant de litrateur en appelant insert(). Litrateur est alors avanc lemplacement se situant entre le nouvel lment
et llment suivant.
En plus des itrateurs de style Java, chaque classe conteneur squentiel C<T> possde deux
types ditrateurs de style STL : C<T>::iterator et C<T>::const_iterator. La diffrence
entre les deux est que const_iterator ne nous permet pas de modifier les donnes.
La fonction begin() dun conteneur retourne un itrateur de style STL faisant rfrence au
premier lment du conteneur (par exemple list[0]), alors que end() retourne un itrateur
pointant vers llment suivant le dernier (par exemple, list[5] pour une liste de taille 5). Si
un conteneur est vide, begin() est gal end(). Cette caractristique peut tre utilise pour
dterminer si le conteneur comporte des lments, bien quil soit gnralement plus appropri
dappeler isEmpty() cette fin. (Voir Figure 11.5)
Figure 11.5
Emplacements valides
pour les itrateurs
de style STL

begin()

end()

La syntaxe dun itrateur de style STL est modele sur celle des pointeurs C++ dans un
tableau. Nous pouvons utiliser les oprateurs ++ et -- pour passer llment prcdent ou
suivant et loprateur * unaire pour rcuprer llment en cours. Pour QVector<T>, litrateur
et les types const_iterator sont simplement des typedefs de T* et constT*. (Ceci est possible parce que QVector<T> stocke ses lments dans des emplacements conscutifs en
mmoire.)
Lexemple suivant remplace chaque valeur dun QList<double> par sa valeur absolue :
QList<double>::iterator i = list.begin();
while (i!= list.end()) {
*i = qAbs(*i);
++i;
}

Quelques fonctions Qt retournent un conteneur. Si nous voulons parcourir la valeur de retour


dune fonction au moyen dun itrateur de style STL, nous devons prendre une copie du conteneur et parcourir cette copie. Le code suivant, par exemple, illustre comment parcourir correctement
le QList<int> retourn par QSplitter::sizes():
QList<int> list = splitter->sizes();
QList<int>::const_iterator i = list.begin();
while (i!= list.end()) {
do_something(*i);
++i;
}

Qt 4 Livre Page 270 Jeudi, 7. dcembre 2006 12:14 12

270

Qt4 et C++ : Programmation dinterfaces GUI

Le code suivant est incorrect :


// INEXACT
QList<int>::const_iterator i = splitter->sizes().begin();
while (i!= splitter->sizes().end()) {
do_something(*i);
++i;
}

En effet, QSplitter::sizes() retourne un nouveau QList<int> par valeur chacun de ses


appels. Si nous ne stockons pas la valeur de retour, C++ la dtruit automatiquement avant
mme que nous ayons dbut litration, nous laissant avec un itrateur sans liaison. Pire
encore, chaque excution de la boucle, QSplitter::sizes() doit gnrer une nouvelle
copie de la liste cause de lappel splitter_>sizes().end().
En rsum : lorsque vous utilisez des itrateurs de style STL, parcourez toujours vos lments
sur une copie dun conteneur.
Avec les itrateurs de style Java en lecture seulement, il est inutile de recourir une copie.
Litrateur se charge de crer cette copie en arrire-plan. Par exemple :
QListIterator<int> i(splitter->sizes());
while (i.hasNext()) {
do_something(i.next());
}

La copie dun conteneur tel que celui-ci semble coteuse, mais il nen est rien, grce loptimisation obtenue par le partage implicite. La copie dun conteneur Qt est pratiquement aussi
rapide que celle dun pointeur unique. Les donnes ne sont vritablement copies que si lune
des copies est change et tout ceci est gr automatiquement larrire-plan. Cest pourquoi
le partage implicite est quelquefois nomm "copie lcriture".
Lintrt du partage implicite est quil sagit dune optimisation dont nous bnficions sans
intervention de la part du programmeur. En outre, le partage implicite favorise un style de
programmation clair, o les objets sont retourns par valeur. Considrez la fonction suivante :
QVector<double> sineTable()
{
QVector<double> vect(360);
for (int i = 0; i < 360; ++i)
vect[i] = sin(i / (2 * M_PI));
return vect;
}

Voici lappel la fonction :


QVector<double> table = sineTable();

STL, nous incite plutt transmettre le vecteur comme rfrence non const pour viter
lexcution de la copie lorsque la valeur de retour de la fonction est stocke dans une variable :

Qt 4 Livre Page 271 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

271

using namespace std;


void sineTable(vector<double> &vect)
{
vect.resize(360);
for (int i = 0; i < 360; ++i)
vect[i] = sin(i / (2 * M_PI));
}

Lappel devient alors plus difficile crire et lire :


vector<double> table;
sineTable(table);

Qt utilise le partage implicite pour tous ses conteneurs ainsi que pour de nombreuses autres
classes, dont QByteArray, QBrush, QFont, QImage, QPixmap et QString .
Le partage implicite est une garantie de la part de Qt que les donnes ne seront pas copies si
nous ne les modifions pas. Pour obtenir le meilleur du partage implicite, nous pouvons adopter
deux nouvelles habitudes de programmation. Lune consiste coder la fonction at() au lieu
de loprateur [] pour un accs en lecture seulement sur une liste ou un vecteur (non const).
Les conteneurs Qt ne pouvant pas dterminer si [] apparat sur le ct gauche dune affectation ou non, le pire est envisag et une copie intgrale est dclenche alors que at() nest
pas autoris sur le ct gauche dune affectation.
Un problme similaire se pose lorsque nous parcourons un conteneur avec des itrateurs de
style STL. Ds que nous appelons begin() ou end() sur un conteneur non const, Qt force une
copie complte si les donnes sont partages. Pour viter ceci, la solution consiste utiliser
const_iterator, constBegin() et constEnd() ds que possible.
Qt fournit une dernire mthode pour parcourir les lments situs dans un conteneur squentiel :
la boucle foreach. Voici sa syntaxe :
QLinkedList<Movie> list;
...
foreach (Movie movie, list) {
if (movie.title() == "Citizen Kane") {
cout << "Found Citizen Kane" << endl;
break;
}
}

Le pseudo mot-cl foreach est implment sous la forme dune boucle for standard. A
chaque itration de la boucle, la variable ditration (movie) est dfinie en un nouvel lment,
commenant au premier lment du conteneur et progressant vers lavant. La boucle foreach
reoit automatiquement une copie du conteneur. Elle ne sera donc pas affecte si le conteneur
est modifi durant litration.

Qt 4 Livre Page 272 Jeudi, 7. dcembre 2006 12:14 12

272

Qt4 et C++ : Programmation dinterfaces GUI

Fonctionnement du partage implicite


Le partage implicite seffectue automatiquement en arrire-plan. Aucune action nest donc
ncessaire dans notre code pour que cette optimisation se produise. Mais comme il est intressant de comprendre comment les choses fonctionnent, nous allons tudier un exemple et voir
ce qui se passe en interne. Lexemple utilise QString, une des nombreuses classes implicitement
partages de Qt.
QString str1 = "Humpty";
QString str2 = str1;

Nous dfinissons str1 en "Humpty" et str2 de sorte quil soit gal str1. A ce stade,
les deux objets QString pointent vers la mme structure de donnes interne en mmoire. Avec les
donnes de type caractre, il existe pour une structure de donnes un compteur de rfrence
indiquant le nombre de QString pointant vers celle-ci. str1 et str2 pointant vers la mme
donne, le compteur de rfrence indique 2.
str2[0] = D;

Lorsque nous modifions str2, il ralise tout dabord une copie intgrale des donnes pour
sassurer que str1 et str2 pointent vers des structures de donnes diffrentes, puis il applique
la modification sa propre copie des donnes. Le compteur de rfrence des donnes de str1
("Humpty") indique alors 1 et celui des donnes de str2 ("Dumpty") est dfini en 1. Quand un
compteur de rfrence indique 1, les donnes ne sont pas partages.
str2.truncate(4);

Si nous modifions de nouveau str2, aucune copie ne se produit car le compteur de rfrence
des donnes de str2 indique 1. La fonction truncate() agit directement sur les donnes de
str2, rsultant en la chane "Dump". Le compteur de rfrence reste 1.
str1 = str2;

Lorsque nous affectons str2 str1, le compteur de rfrence des donnes de str1 descend
0, ce qui signifie quaucun QString nutilise plus la donne "Humpty". La donne est alors libre de la mmoire. Les deux QString pointent vers "Dump", dont le compteur de rfrence
indique maintenant 2.
Le partage de donnes est une option souvent ignore dans les programmes multithread,
cause des conditions de comptition dans le dcompte des rfrences. Avec Qt, ceci nest plus
un problme. En interne, les classes conteneur utilisent des instructions du langage dassembly
pour effectuer un dcompte de rfrences atomique. Cette technologie est la porte des
utilisateurs de Qt par le biais des classes QSharedData et QSharedDataPointer.

Qt 4 Livre Page 273 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

273

Les instructions de boucle break et continue sont prises en charge. Si le corps est constitu
dune seule instruction, les accolades ne sont pas ncessaires. Comme pour une instruction
for, la variable ditration peut tre dfinie lextrieur de la boucle, comme suit :
QLinkedList<Movie> list;
Movie movie;
...
foreach (movie, list) {
if (movie.title() == "Citizen Kane") {
cout << "Found Citizen Kane" << endl;
break;
}
}

La dfinition de la variable ditration lextrieur de la boucle est la seule solution pour les conteneurs comportant des types de donnes avec une virgule (par exemple, QPair<QString,int>).

Conteneurs associatifs
Un conteneur associatif comporte un nombre arbitraire dlments du mme type, indexs par
une cl. Qt fournit deux classes de conteneurs associatifs principales : QMap<K,T> et
QHash<K,T>.
Un QMap<K,T> est une structure de donnes qui stocke des paires cl/valeur dans un ordre
croissant des cls. Cette organisation permet dobtenir de bonnes performances en matire de
recherche et dinsertion ainsi quune itration ordonne. En interne, QMap<K,T> est implment sous forme de liste branchement. (Voir Figure 11.6)
Figure 11.6
Un map de QString
vers int

Mexico City

22 350 000

Seoul

22 050 000

Tokyo

34 000 000

Un moyen simple dinsrer des lments dans un map consiste appeler insert():
QMap<QString, int> map;
map.insert("eins", 1);
map.insert("sieben", 7);
map.insert("dreiundzwanzig", 23);

Nous avons aussi la possibilit daffecter simplement une valeur une cl donne comme suit :
map["eins"] = 1;
map["sieben"] = 7;
map["dreiundzwanzig"] = 23;

Qt 4 Livre Page 274 Jeudi, 7. dcembre 2006 12:14 12

274

Qt4 et C++ : Programmation dinterfaces GUI

Loprateur [] peut tre utilis la fois pour linsertion et la rcupration. Si [] est utilis pour
rcuprer une valeur dune cl non existante dans un map non const, un nouvel lment sera
cr avec la cl donne et une valeur vide. Lutilisation de la fonction value() la place de []
pour rcuprer les lments permet dviter la cration accidentelle de valeurs vides :
int val = map.value("dreiundzwanzig");

Si la cl nexiste pas, une valeur par dfaut est retourne et aucun nouvel lment nest cr.
Pour les types de base et les types pointeur, la valeur retourne est nulle. Nous pouvons spcifier
une autre valeur par dfaut comme second argument pour value():
int int seconds = map.value("delay", 30);

Cette ligne de code est quivalente :


int seconds = 30;
if (map.contains("delay"))
seconds = map.value("delay");

Les types de donnes K et T dun QMap<K,T> peuvent tre des types de base tels que int et
double, des types pointeur ou de classes possdant un constructeur par dfaut, un constructeur
de copie et un oprateur daffectation. En outre, le type K doit fournir un operator<() car
QMap<K,T> utilise cet oprateur pour stocker les lments dans un ordre de cl croissant.
QMap<K,T> possde deux fonctions utilitaires keys() et values(), qui savrent particulirement intressantes pour travailler avec de petits ensembles de donnes. Elles retournent des
QList des cls et valeurs dun map.
Les maps sont gnralement valeur unique : si une nouvelle valeur est affecte une cl existante, lancienne valeur est remplace par la nouvelle. De cette faon, deux lments ne partagent pas la mme cl. Il est possible davoir des valeurs multiples pour la mme cl en utilisant
la fonction insertMulti() ou la sous-classe utilitaire QMultiMap<K,T>. QMap<K,T>
possde une surcharge values(constK &) qui retourne un QList de toutes les valeurs pour
une cl donne. Par exemple :
QMultiMap<int, QString> multiMap;
multiMap.insert(1, "one");
multiMap.insert(1, "eins");
multiMap.insert(1, "uno");
QList<QString> vals = multiMap.values(1);

Un QHash<K,T> est une structure de donnes qui stocke des paires cl/valeur dans une table de
hachage. Son interface est pratiquement identique celle de QMap<K,T>, mais ses exigences
concernant le type template K sont diffrentes et il offre des oprations de recherche beaucoup
plus rapides que QMap<K,T>. Une autre diffrence est que QHash<K,T> nest pas ordonn.
En complment des conditions standard concernant tout type de valeur stock dans un
conteneur, le type K dun QHash<K,T> doit fournir un operator==() et tre support par une

Qt 4 Livre Page 275 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

275

fonction QHash() globale qui retourne une valeur de hachage pour une cl. Qt fournit dj
des fonctions QHash() pour les types entiers, les types pointeur, QChar, QString et QByteArray.

QHash<K,T> alloue automatiquement un nombre principal de blocs pour sa table de hachage


interne et redimensionne celle-ci lors de linsertion ou de la suppression dlments. Il est
galement possible de rgler avec prcision les performances en appelant reserve() pour
spcifier le nombre dlments stocker dans la table et squeeze() pour rduire cette table en
fonction du nombre dlments en cours. Une pratique courante consiste appeler reserve()
avec le nombre maximum dlments susceptibles dtre stocks, puis insrer les donnes et
finalement appeler squeeze() pour rduire lutilisation de la mmoire si les lments sont
moins nombreux que prvu.
Les hachages sont gnralement valeur unique, mais plusieurs valeurs peuvent tre affectes
la mme cl laide de la fonction insertMulti() ou de la sous-classe utilitaire QMultihash<K,T>.
En plus de QHash<K,T>, Qt fournit une classe QCache<K,T> qui peut tre utilise pour placer
en cache des objets associs une cl, et un conteneur QSet<K> qui ne stocke que des cls. En
interne, tous deux reposent sur QHash<K,T> et prsentent les mmes exigences concernant le
type K.
Le moyen le plus simple de parcourir toutes les paires cl/valeur stockes dans un conteneur
associatif consiste utiliser un itrateur de style Java. Les itrateurs de ce style utiliss pour les
conteneurs associatifs ne fonctionnent pas tout fait de la mme faon que leurs homologues
squentiels. La principale diffrence est la suivante : les fonctions next() et previous()
retournent un objet qui reprsente une paire cl/valeur, et non simplement une valeur. Les composants cl et valeur sont accessibles depuis cet objet en tant que key() et value(). Par exemple :
QMap<QString, int> map;
...
int sum = 0;
QMapIterator<QString, int> i(map);
while (i.hasNext())
sum += i.next().value();

Si nous devons accder la fois la cl et la valeur, nous pouvons simplement ignorer la


valeur de retour de next() ou de previous() et excuter les fonctions key() et value() de
litrateur, qui oprent sur le dernier lment franchi :
QMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
if (i.value() > largestValue) {
largestKey = i.key();
largestValue = i.value();
}
}

Qt 4 Livre Page 276 Jeudi, 7. dcembre 2006 12:14 12

276

Qt4 et C++ : Programmation dinterfaces GUI

Les itrateurs mutables possdent une fonction setValue() qui modifie la valeur associe
un lment courant :
QMutableMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
if (i.value() < 0.0)
i.setValue(-i.value());
}

Les itrateurs de style STL fournissent galement des fonctions key() et value(). Avec des
types ditrateur non const, value() retourne une rfrence non const, ce qui nous permet
de changer la valeur au cours de litration. Remarquez que, bien que ces itrateurs soient "de
style STL", ils prsentent des diffrences significatives avec les itrateurs map<K,T> de STL,
qui sont bass sur pair<K,T>.
La boucle foreach fonctionne galement avec les conteneurs associatifs, mais uniquement
avec le composant valeur des paires cl/valeur. Si nous avons besoin des deux composants cl
et valeur des lments, nous pouvons appeler les fonctions keys() et values(constK &)
dans des boucles foreach imbriques comme suit :
QMultiMap<QString, int> map;
...
foreach (QString key, map.keys()) {
foreach (int value, map.values(key)) {
do_something(key, value);
}
}

Algorithmes gnriques
Len-tte <QtAlgorithms> dclare un ensemble de fonctions template globales qui implmentent des algorithmes de base sur les conteneurs. La plupart de ces fonctions agissent sur
des itrateurs de style STL.
Len-tte STL <algorithm> fournit un ensemble dalgorithmes gnriques plus complet. Ces
algorithmes peuvent tre employs avec des conteneurs Qt ainsi que des conteneurs STL. Si
les implmentations STL sont disponibles sur toutes vos plates-formes, il ny a probablement
aucune raison de ne pas utiliser les algorithmes STL lorsque Qt ne propose pas lalgorithme
quivalent. Nous prsenterons ici les algorithmes Qt les plus importants.
Lalgorithme qFind() recher286che une valeur particulire dans un conteneur. Il reoit un
itrateur "begin" et un itrateur "end" et retourne un itrateur pointant sur le premier lment
correspondant, ou "end" sil nexiste pas de correspondance. Dans lexemple suivant, i est
dfini en list.begin() + 1 alors que j est dfini en list.end().
QStringList list;

Qt 4 Livre Page 277 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

277

list << "Emma" << "Karl" << "James" << "Mariette";


QStringList::iterator i = qFind(list.begin(), list.end(), "Karl");
QStringList::iterator j = qFind(list.begin(), list.end(), "Petra");

Lalgorithme qBinaryFind() effectue une recherche similaire qFind(), mais il suppose


que les lments sont tris dans un ordre croissant et utilise la recherche binaire rapide au lieu
de la recherche linaire de qFind().
Lalgorithme qFill() remplit un conteneur avec une valeur particulire :
QLinkedList<int> list(10);
qFill(list.begin(), list.end(), 1009);

Comme les autres algorithmes bass sur les itrateurs, nous pouvons galement utiliser
qFill() sur une partie du conteneur en variant les arguments. Lextrait de code suivant initialise les cinq premiers lments dun vecteur en 1009 et les cinq derniers lments en 2013 :
QVector<int> vect(10);
qFill(vect.begin(), vect.begin() + 5, 1009);
qFill(vect.end() - 5, vect.end(), 2013);

Lalgorithme qCopy() copie des valeurs dun conteneur un autre :


QVector<int> vect(list.count());
qCopy(list.begin(), list.end(), vect.begin());

qCopy() peut galement tre utilis pour copier des valeurs dans le mme conteneur, ceci tant
que la plage source et la plage cible ne se chevauchent pas. Dans lextrait de code suivant, nous
lutilisons pour remplacer les deux derniers lments dune liste par les deux premiers :
qCopy(list.begin(), list.begin() + 2, list.end() - 2);

Lalgorithme qSort() trie les lments du conteneur en ordre croissant :


qSort(list.begin(), list.end());

Par dfaut, qSort() se sert de loprateur < pour comparer les lments. Pour trier les
lments en ordre dcroissant, transmettez qGreater<T>() comme troisime argument (o T
est le type des valeurs du conteneur), comme suit :
qSort(list.begin(), list.end(), qGreater<int>());

Nous pouvons utiliser le troisime paramtre pour dfinir un critre de tri personnalis. Voici,
par exemple, une fonction de comparaison "infrieur " qui compare des QString sans prendre la
casse (majuscule-minuscule) en considration :
bool insensitiveLessThan(const QString &str1, const QString &str2)
{
return str1.toLower() < str2.toLower();

Qt 4 Livre Page 278 Jeudi, 7. dcembre 2006 12:14 12

278

Qt4 et C++ : Programmation dinterfaces GUI

Lappel qSort() devient alors :


QStringList list;
...
qSort(list.begin(), list.end(), insensitiveLessThan);

Lalgorithme qStableSort() est similaire qSort(), si ce nest quil garantit que les
lments gaux apparaissent dans le mme ordre avant et aprs le tri. Cette caractristique est
utile si le critre de tri ne prend en compte que des parties de la valeur et si les rsultats sont
visibles pour lutilisateur. Nous avons utilis qStableSort() dans le Chapitre 4 pour implmenter le tri dans lapplication Spreadsheet.
Lalgorithme qDeleteAll() appelle delete sur chaque pointeur stock dans un conteneur.
Ceci nest utile que pour les conteneurs dont le type de valeur est un type pointeur. Aprs lappel,
les lments sont toujours prsents, vous excutez clear() sur le conteneur. Par exemple :
qDeleteAll(list);
list.clear();

Lalgorithme qSwap() change la valeur de deux variables. Par exemple :


int x1 = line.x1();
int x2 = line.x2();
if (x1 > x2)
qSwap(x1, x2);

Enfin, len-tte <QtGlobal> fournit plusieurs dfinitions utiles, dont la fonction qAbs(), qui
retourne la valeur absolue de son argument ainsi que les fonctions qMin() et qMax() qui retournent
le minimum ou le maximum entre deux valeurs.

Chanes, tableaux doctets et variants


QString, QByteArray et QVariant sont trois classes ayant de nombreux points en commun
avec les conteneurs. Elles sont susceptibles dtre utilises la place de ceux-ci dans certaines
situations. En outre, comme les conteneurs, ces classes utilisent le partage implicite pour
optimiser la mmoire et la vitesse.
Nous allons commencer par QString. Les chanes sont utilises par tout programme GUI, non
seulement pour linterface utilisateur, mais galement en tant que structures de donnes. C++
fournit en natif deux types de chanes : les traditionnels tableaux de caractres termins par "0"
et la classe std::string. Contrairement celles-ci, QString contient des valeurs Unicode
16 bits. Unicode comprend les systmes ASCII et Latin-1, avec leurs valeurs numriques habituelles. Mais QString tant une classe 16 bits, elle peut reprsenter des milliers de caractres
diffrents utiliss dans la plupart des langues mondiales. Reportez-vous au Chapitre 17 pour de
plus amples informations concernant Unicode.

Qt 4 Livre Page 279 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

279

Lorsque vous utilisez QString, vous navez pas besoin de vous proccuper de dtails comme
lallocation dune mmoire suffisante ou de vrifier que la donne est termine par "0".
Conceptuellement, les objets QString peuvent tre considrs comme des vecteurs de QChar.
Un QString peut intgrer des caractres "0". La fonction length() retourne la taille de la
chane entire, dont les caractres "0" intgrs.

QString fournit un oprateur + binaire destin concatner deux chanes ainsi quun oprateur += dont la fonction est daccoler une chane une autre. Voici un exemple combinant
+ et +=.
QString str = "User: ";
str += userName + "\n";

Il existe galement une fonction QString::append() dont la tche est identique celle de
loprateur +=:
str = "User: ";
str.append(userName);
str.append("\n");

Un moyen totalement diffrent de combiner des chanes consiste utiliser la fonction


sprintf() de QString:
str.sprintf("%s %.1f%%", "perfect competition", 100.0);

Cette fonction prend en charge les mmes spcificateurs de format que la fonction sprintf()
de la bibliothque C++. Dans lexemple ci-dessus, "perfect competition 100.0 %" est affect
str.
Un moyen supplmentaire de crer une chane partir dautres chanes ou de nombres consiste
utiliser arg():
str = QString("%1%2 (%3s-%4s)")
.arg("permissive").arg("society").arg(1950).arg(1970);

Dans cet exemple, "%1", "%2", "%3" et "%4" sont remplacs par "permissive", "society",
"1950" et "1970", respectivement. On obtient "permissive society (1950s-1970s)". Il existe des
surcharges de arg() destines grer divers types de donnes. Certaines surcharges comportent des paramtres supplmentaires afin de contrler la largeur de champ, la base numrique
ou la prcision de la virgule flottante. En gnral, arg() reprsente une solution bien meilleure
que sprintf(), car elle est de type scuris, prend totalement en charge Unicode et autorise
les convertisseurs rordonner les paramtres "%n".

QString peut convertir des nombres en chanes au moyen de la fonction statique QString:
:number():
str = QString::number(59.6);

Qt 4 Livre Page 280 Jeudi, 7. dcembre 2006 12:14 12

280

Qt4 et C++ : Programmation dinterfaces GUI

Ou en utilisant la fonction setNum():


str.setNum(59.6);

La conversion inverse, dune chane en un nombre, est ralise laide de toInt(),


toLongLong(), toDouble() et ainsi de suite. Par exemple :
bool ok;
double d = str.toDouble(&ok);

Ces fonctions peuvent recevoir en option un pointeur facultatif vers une variable bool et elles
dfinissent la variable en true ou false selon le succs de la conversion. Si la conversion
choue, les fonctions retournent zro.
Il est souvent ncessaire dextraire des parties dune chane. La fonction mid() retourne la
sous-chane dbutant un emplacement donn (premier argument) et de longueur donne
(second argument). Par exemple, le code suivant affiche "pays" sur la console1 :
QString str = "polluter pays principle";
qDebug() << str.mid(9, 4);

Si nous omettons le second argument, mid() retourne la sous-chane dbutant lemplacement donn et se terminant la fin de la chane. Par exemple, le code suivant affiche "pays
principle" sur la console :
QString str = "polluter pays principle";
qDebug() << str.mid(9);

Il existe aussi des fonctions left() et right() dont la tche est similaire. Toutes deux reoivent un nombre de caractres, n, et retournent les n premiers ou derniers caractres de la
chane. Par exemple, le code suivant affiche "polluter principle" sur la console :
QString str = "polluter pays principle";
qDebug() << str.left(8) << " " << str.right(9);

Pour rechercher si un chane contient un caractre particulier, une sous-chane ou une expression
rgulire, nous pouvons utiliser lune des fonctions indexOf() de QString :
QString str = "the middle bit";
int i = str.indexOf("middle");

Dans ce cas, i sera dfini en 4. La fonction indexOf() retourne 1 en cas dchec et reoit en
option un emplacement de dpart ainsi quun indicateur de sensibilit la casse.

1. La syntaxe qDebug()<<arg utilise ici ncessite linclusion du fichier den-tte <QtDebug>, alors
que la syntaxe qDebug("",arg) est disponible dans tout fichier incluant au moins un en-tte Qt.

Qt 4 Livre Page 281 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

281

Si nous souhaitons simplement vrifier si une chane commence ou se termine par quelque
chose, nous pouvons utiliser les fonctions startsWith() et endsWith():
if (url.startsWith("http:") && url.endsWith(".png"))
...

Ce qui est la fois plus rapide et plus simple que :


if (url.left(5) == "http:" && url.right(4) == ".png")
...

La comparaison de chanes avec loprateur == diffrencie les majuscules des minuscules. Si


nous comparons des chanes visibles pour lutilisateur, localeAwareCompare() reprsente
gnralement un bon choix, et si nous souhaitons effectuer des comparaisons sensibles la
casse, nous pouvons utiliser toUpper() ou toLower(). Par exemple :
if (fileName.toLower() == "readme.txt")
...

Pour remplacer une certaine partie dune chane par une autre chane, nous codons
replace():
QString str = "a cloudy day";
str.replace(2, 6, "sunny");

On obtient "a sunny day". Le code peut tre rcrit de faon excuter remove() et
insert().
str.remove(2, 6);
str.insert(2, "sunny");

Dans un premier temps, nous supprimons six caractres en commenant lemplacement 2, ce


qui aboutit la chane "a day" (avec deux espaces), puis nous insrons "sunny" ce mme
emplacement.
Des versions surcharges de replace() permettent de remplacer toutes les occurrences de
leur premier argument par leur second argument. Par exemple, voici comment remplacer toutes
les occurrences de "&" par "&amp;" dans une chane :
str.replace("&", "&amp;");

Il est trs souvent ncessaire de supprimer les blancs (tels que les espaces, les tabulations et les
retours la lignes) dans une chane. QString possde une fonction qui limine les espaces
situs aux deux extrmits dune chane :
QString str = " BOB \t THE \nDOG \n";
qDebug() << str.trimmed();

Qt 4 Livre Page 282 Jeudi, 7. dcembre 2006 12:14 12

282

Qt4 et C++ : Programmation dinterfaces GUI

La chane str peut tre reprsente comme suit :


B

\t

\n

\n

La chane retourne par trimmed() est


B

\t

\n

Lorsque nous grons les entres utilisateur, nous avons souvent besoin de remplacer les
squences internes dun ou de plusieurs caractres despace par un espace unique, ainsi que
dliminer les espaces aux deux extrmits. Voici laction de la fonction simplified():
QString str = " BOB \t THE \nDOG \n";
qDebug() << str.simplified();

La chane retourne par simplified() est :


B

Une chane peut tre divise en un QStringList de sous-chanes au moyen de


QString::split():
QString str = "polluter pays principle";
QStringList words = str.split(" ");

Dans lexemple ci-dessus, nous divisons la chane "polluter pays principle" en trois souschanes : "polluter", "pays" et "principle". La fonction split() possde un troisime argument facultatif qui spcifie si les sous-chanes vides doivent tre conserves (option par dfaut)
ou limines.
Les lments dun QStringList peuvent tre unis pour former une chane unique au moyen
de join(). Largument de join() est insr entre chaque paire de chanes jointes. Par exemple, voici comment crer une chane unique qui est compose de toutes les chanes contenues
dans un QStringList tries par ordre alphabtique et spares par des retours la lignes :
words.sort();
str = words.join("\n");

En travaillant avec les chanes, il est souvent ncessaire de dterminer si elles sont vides ou
non. Pour ce faire, appelez isEmpty() ou vrifiez si length() est gal 0.
La conversion de chanes const char* en QString est automatique dans la plupart des cas.
Par exemple :
str += " (1870)";

Qt 4 Livre Page 283 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

283

Ici nous ajoutons un constchar* un QString sans formalit. Pour convertir explicitement
un const char* en un QString, il suffit dutiliser une conversion QString ou encore
dappeler fromAscii() ou fromLatin1(). (Reportez-vous au Chapitre 17 pour obtenir une
explication concernant la gestion des chanes littrales et autres codages.)
Pour convertir un QString en un const char *, excutez toAscii() ou toLatin1(). Ces
fonctions retournent un QByteArray, qui peut tre converti en un const char* en codant
QByteArray::data() ou QByteArray::constData(). Par exemple :
printf("User: %s\n", str.toAscii().data());

Pour des raisons pratiques, Qt fournit la macro qPrintable() dont laction est identique
celle de la squence toAscii().constData():
printf("User: %s\n", qPrintable(str));

Lorsque nous appelons data() ou constData() sur un QByteArray, la chane retourne


appartient lobjet QByteArray, ce qui signifie que nous navons pas nous proccuper de
problmes de pertes de mmoire. Qt se chargera de librer la mmoire. Dautre part, nous
devons veiller ne pas utiliser le pointeur trop longtemps. Si le QByteArray nest pas stock
dans une variable, il sera automatiquement supprim la fin de linstruction.
La classe QByteArray possde une API trs similaire celle de QString. Des fonctions telles
que left(), right(), mid(), toLower(), toUpper(), trimmed() et simplified() ont la
mme smantique dans QByteArray que leurs homologues QString. QByteArray est utile
pour stocker des donnes binaires brutes et des chanes de texte codes en 8 bits. En gnral,
nous conseillons dutiliser QString plutt que QByteArray pour stocker du texte, car cette
classe supporte Unicode.
Pour des raisons de commodit, QByteArray sassure automatiquement que loctet suivant le
dernier lment est toujours "0", ce qui facilite la transmission dun QByteArray une fonction
recevant un const char *. QByteArray prend aussi en charge les caractres "0" intgrs, ce
qui nous permet de lutiliser pour stocker des donnes binaires arbitraires.
Dans certaines situations, il est ncessaire de stocker des donnes de types diffrents dans la
mme variable. Une approche consiste coder les donnes en tant que QByteArray ou
QString. Ces approches offrent une flexibilit totale, mais annule certains avantages du C++,
et notamment la scurit et lefficacit des types. Qt offre une bien meilleure solution pour
grer des variables contenant diffrents types : QVariant.
La classe QVariant peut contenir des valeurs de nombreux types Qt, dont QBrush, QColor,
QCursor, QDateTime, QFont, QKeySequence, QPalette, QPen, QPixmap, QPoint, QRect,
QRegion, QSize et QString, ainsi que des types numriques C++ de base tels que double et
int. Cette classe est galement susceptible de contenir des conteneurs : QMap<QString,
QVariant>, QStringList et QList<QVariant>.

Qt 4 Livre Page 284 Jeudi, 7. dcembre 2006 12:14 12

284

Qt4 et C++ : Programmation dinterfaces GUI

Les variants sont abondamment utiliss par les classes daffichage dlments, le module de
base de donnes et QSettings, ce qui nous permet de lire et dcrire des donnes dlment,
des donnes de base de donnes et des prfrences utilisateur pour tout type compatible
QVariant. Nous en avons dj rencontr un exemple dans le Chapitre 3, o nous avons transmis un QRect, un QStringList et deux bool en tant que variants QSettings::setValue().
Nous les avons rcuprs ultrieurement comme variants.
Il est possible de crer arbitrairement des structures de donnes complexes utilisant QVariant
en imbriquant des valeurs de types conteneur :
QMap<QString, QVariant> pearMap;
pearMap["Standard"] = 1.95;
pearMap["Organic"] = 2.25;
QMap<QString, QVariant> fruitMap;
fruitMap["Orange"] = 2.10;
fruitMap["Pineapple"] = 3.85;
fruitMap["Pear"] = pearMap;

Ici, nous avons cr un map avec des cls sous forme de chanes (noms de produit) et des
valeurs qui sont soit des nombres virgule flottante (prix), soit des maps. Le map de niveau
suprieur contient trois cls : "Orange", "Pear" et "Pineapple". La valeur associe la cl
"Pear" est un map qui contient deux cls ("Standard" et "Organic"). Lorsque nous parcourons
un map contenant des variants, nous devons utiliser type() pour en contrler le type de faon
pouvoir rpondre de faon approprie.
La cration de telles structures de donnes peut sembler trs sduisante, car nous pouvons ainsi
organiser les donnes exactement comme nous le souhaitons. Mais le caractre pratique de
QVariant est obtenu au dtriment de lefficacit et de la lisibilit. En rgle gnrale, il
convient de dfinir une classe C++ correcte pour stocker les donnes ds que possible.

QVariant est utilis par le systme mtaobjet de Qt et fait donc partie du module QtCore.
Nanmoins, lorsque nous le rattachons au module QtGui, QVariant peut stocker des types
en liaison avec linterface utilisateur graphique tels que QColor, QFont, QIcon, QImage et
QPixmap:
QIcon icon("open.png");
QVariant variant = icon;

Pour rcuprer la valeur dun tel type partir dun QVariant, nous pouvons utiliser la fonction
membre template QVariant::Value<T>() comme suit :
QIcon icon = variant.value<QIcon>();

La fonction value<T>() permet galement deffectuer des conversions entre des types de
donnes non graphiques et QVariant, mais en pratique, nous utilisons habituellement les
fonctions de conversion to() (par exemple, toString()) pour les types non graphiques.

Qt 4 Livre Page 285 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 11

Classes conteneur

285

QVariant peut aussi tre utilis pour stocker des types de donnes personnaliss, en
supposant quils fournissent un constructeur par dfaut et un constructeur de copie. Pour
que ceci fonctionne, nous devons tout dabord enregistrer le type au moyen de la macro
Q_DECLARE_METATYPE(), gnralement dans un fichier den-tte en dessous de la dfinition
de classe :
Q_DECLARE_METATYPE(BusinessCard)

Cette technique nous permet dcrire du code tel que celui-ci :


BusinessCard businessCard;
QVariant variant = QVariant::fromValue(businessCard);
...
if (variant.canConvert<BusinessCard>()) {
BusinessCard card = variant.value<BusinessCard>();
...
}

Ces fonctions membre template ne sont pas disponibles pour MSVC 6, cause dune limite du
compilateur. Si vous devez employer ce compilateur, utilisez plutt les fonctions globales
qVariantFromValue(), qVariantValue<T>()et qVariantCanConvert<T>().
Si le type de donnes personnalis possde des oprateurs << et >> pour effectuer des oprations
de lecture et dcriture dans un QDataStream, nous pouvons les enregistrer au moyen de
qRegisterMetaTypeStreamOperators<T>(). Il est ainsi possible de stocker les prfrences
des types de donnes personnaliss laide de QSettings, entre autres. Par exemple :
qRegisterMetaTypeStreamOperators<BusinessCard>("BusinessCard");

Dans ce chapitre, nous avons principalement tudi les conteneurs Qt, ainsi que QString,
QByteArray et QVariant. En complment de ces classes, Qt fournit quelques autres conteneurs. QPair<T1,T2> en fait partie, qui stocke simplement deux valeurs et prsente des similitudes avec std::pair<T1,T2>. QBitArray est un autre conteneur que nous utiliserons dans
la premire partie du Chapitre 19. Il existe enfin QVarLengthArray<T,Prealloc>, une alternative de bas niveau QVector<T>. Comme il pralloue de la mmoire sur la pile et nest pas
implicitement partag, sa surcharge est infrieure celle de QVector<T>, ce qui le rend plus
appropri pour les boucles troites.
Les algorithmes de Qt, dont quelques-uns nayant pas t tudis ici tels que qCopyBackward()
et qEqual(), sont dcrits dans la documentation de Qt ladresse http://doc.trolltech.com/
4.1/algorithms.html. Vous trouverez des dtails complmentaires concernant les conteneurs
de Qt ladresse http://doc.trolltech.com/4.1/containers.html.

Qt 4 Livre Page 286 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 287 Jeudi, 7. dcembre 2006 12:14 12

12
Entres/Sorties
Au sommaire de ce chapitre
Lire et crire des donnes binaires
Lire et crire du texte
Parcourir des rpertoires
Intgrer des ressources
Communication inter-processus

Le besoin deffectuer des oprations de lecture et dcriture dans des fichiers ou sur un
autre support est commun presque toute application. Qt fournit un excellent support
pour ces oprations par le biais de QIODevice, une abstraction puissante qui encapsule
des "priphriques" capables de lire et dcrire des blocs doctets. Qt inclut les sousclasses QIODevice suivantes :
QFile

Accde aux fichiers dun systme de fichiers local et de ressources intgres.

QTemporaryFile Cre et accde des fichiers temporaires du systme de fichiers local.


QBuffer

Effectue des oprations de lecture et dcriture de donnes dans un QByteArray.

QProcess

Excute des programmes externes et gre la communication inter-processus.

QTcpSocket

Transfre un flux de donnes sur le rseau au moyen de TCP.

QUdpSocket

Envoie ou reoit des datagrammes UDP sur le rseau.

Qt 4 Livre Page 288 Jeudi, 7. dcembre 2006 12:14 12

288

Qt4 et C++ : Programmation dinterfaces GUI

QProcess, QTcpSocket et QUdpSocket sont des classes squentielles, ce qui implique un


accs unique aux donnes, en commenant par le premier octet et en progressant dans lordre
jusquau dernier octet. QFile, QTemporaryFile et QBuffer sont des classes accs alatoire.
Les octets peuvent donc tre lus plusieurs fois partir de tout emplacement. Elles fournissent
la fonction QIODevice::seek() qui permet de repositionner le pointeur de fichier.
En plus de ces classes de priphrique, Qt fournit deux classes de flux de niveau plus lev qui
excutent des oprations de lecture et criture sur tout priphrique dE/S : QDataStream pour
les donnes binaires et QTextStream pour le texte. Ces classes grent des problmes tels que
le classement des octets et les codages de texte, de sorte que des applications Qt sexcutant
sur dautres plates-formes ou pays puissent effectuer des oprations de lecture et dcriture sur
leurs fichiers respectifs. Ceci rend les classes dE/S de Qt beaucoup plus pratiques que les classes
C++ standard correspondantes, qui laissent la gestion de ces problmes au programmeur de
lapplication.
QFile facilite laccs aux fichiers individuels, quils soient dans le systme de fichier ou intgrs dans lexcutable de lapplication en tant que ressources. Pour les applications ayant
besoin didentifier des jeux complets de fichiers sur lesquels travailler, Qt fournit les classes
QDir et QFileInfo, qui grent des rpertoires et fournissent des informations concernant
leurs fichiers.
La classe QProcess nous permet de lancer des programmes externes et de communiquer avec
ceux-ci par le biais de leurs canaux dentre, de sortie et derreur standard (cin, cout et
cerr). Nous pouvons dfinir les variables denvironnement et le rpertoire de travail qui seront
utiliss par lapplication externe. Par dfaut, la communication avec le processus est asynchrone (non bloquante), mais il est possible de parvenir un blocage pour certaines oprations.
La gestion de rseau ainsi que la lecture et lcriture XML sont des thmes importants qui
seront traits sparment dans leurs propres chapitres (Chapitre 14 et Chapitre 15).

Lire et crire des donnes binaires


La faon la plus simple de charger et denregistrer des donnes binaires avec Qt consiste
instancier un QFile, ouvrir le fichier et y accder par le biais dun objet QDataStream. Ce
dernier fournit un format de stockage indpendant de la plate-forme qui supporte les types C++
de base tels que int et double, de nombreux types de donnes Qt, dont QByteArray, QFont,
QImage, QPixmap, QString et QVariant ainsi que des classes conteneur telles que QList<T>
et QMap<K,T>.
Voici comment stocker un entier, un QImage et un QMap<QString,QColor> dans un fichier
nomm facts.dat:
QImage image("philip.png");
QMap<QString, QColor> map;
map.insert("red", Qt::red);

Qt 4 Livre Page 289 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

289

map.insert("green", Qt::green);
map.insert("blue", Qt::blue);
QFile file("facts.dat");
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Cannot open file for writing: "
<< qPrintable(file.errorString()) << endl;
return;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(0x12345678) << image << map;

Si nous ne pouvons pas ouvrir le fichier, nous en informons lutilisateur et rendons le contrle.
La macro qPrintable() retourne un constchar* pour un QString. (Une autre approche
consiste excuter QString::toStdString(), qui retourne un std::string, pour lequel
<iostream> possde une surcharge <<.)
Si le fichier souvre avec succs, nous crons un qDataStream et dfinissons son numro de
version. Le numro de version est un entier qui influence la faon dont les types de donnes Qt
sont reprsents (les types de donnes C++ de base sont toujours reprsents de la mme
faon). Dans Qt 4.1, le format le plus complet est la version 7. Nous pouvons soit coder en dur
la constante 7, soit utiliser le nom symbolique QDataStream::Qt_4_1.
Pour garantir que le nombre 0x12345678 sera bien enregistr en tant quentier non sign de
32 bits sur toutes les plates-formes, nous le convertissons en quint32, un type de donnes
dont les 32 bits sont garantis. Pour assurer linteroprabilit, QDataStream est bas par dfaut
sur Big-Endian, ce qui peut tre modifi en appelant setByteOrder().
Il est inutile de fermer explicitement le fichier, cette opration tant effectue automatiquement
lorsque la variable QFile sort de la porte. Si nous souhaitons vrifier que les donnes ont bien
t crites, nous appelons flush() et vrifions sa valeur de retour (true en cas de succs).
Le code destin lire les donnes reflte celui que nous avons utilis pour les crire :
quint32 n;
QImage image;
QMap<QString, QColor> map;
QFile file("facts.dat");
if (!file.open(QIODevice::ReadOnly)) {
cerr << "Cannot open file for reading: "
<< qPrintable(file.errorString()) << endl;
return;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_1);
in >> n >> image >> map;

Qt 4 Livre Page 290 Jeudi, 7. dcembre 2006 12:14 12

290

Qt4 et C++ : Programmation dinterfaces GUI

La version de QDataStream que nous employons pour la lecture est la mme que celle utilise
pour lcriture, ce qui doit toujours tre le cas. En codant en dur le numro de version, nous
garantissons que lapplication est toujours en mesure de lire et dcrire les donnes.
QDataStream stocke les donnes de telle faon que nous puissions les lire parfaitement. Par
exemple, un QByteArray est reprsent sous la forme dun dcompte doctets, suivi des octets
eux-mmes. QDataStream peut aussi tre utilis pour lire et crire des octets bruts, sans entte de dcompte doctets, au moyen de readRawBytes() et de writeRawBytes().
Lors de la lecture partir dun QDataStream, la gestion des erreurs est assez facile. Le flux
possde une valeur status() qui peut tre QDataStream::Ok, QDataStream::ReadPastEnd ou QDataStream::ReadCorruptData. Quand une erreur se produit, loprateur >>
lit toujours zro ou des valeurs vides. Nous pouvons ainsi lire un fichier entier sans nous proccuper derreurs potentielles et vrifier la valeur de status() la fin pour dterminer si ce que
nous avons lu tait valide.
QDataStream gre plusieurs types de donnes C++ et Qt. La liste complte est disponible
ladresse http://doc.trolltech.com/4.1/datastreamformat.html. Nous pouvons galement
ajouter la prise en charge de nos propres types personnaliss en surchargeant les oprateurs
<< et >>. Voici la dfinition dun type de donnes personnalis susceptible dtre utilis avec
QDataStream:
class Painting
{
public:
Painting() { myYear = 0; }
Painting(const QString &title, const QString &artist, int year) {
myTitle = title;
myArtist = artist;
myYear = year;
}
void setTitle(const QString &title) { myTitle = title; }
QString title() const { return myTitle; }
...
private:
QString myTitle;
QString myArtist;
int myYear;
};
QDataStream &operator<<(QDataStream &out, const Painting &painting);
QDataStream &operator>>(QDataStream &in, Painting &painting);
Voici comment nous implmenterions loprateur <<:
QDataStream &operator<<(QDataStream &out, const Painting &painting)
{
out << painting.title() << painting.artist()
<< quint32(painting.year());
return out;
}

Qt 4 Livre Page 291 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

291

Pour mettre en sortie un Painting, nous mettons simplement deux QString et un


quint32. A la fin de la fonction, nous retournons le flux. Cest une expression C++ courante
qui nous permet dutiliser une chane doprateurs << avec un flux de sortie. Par exemple :
out << painting1 << painting2 << painting3;

Limplmentation de operator>>() est similaire celle de operator<<():


QDataStream &operator>>(QDataStream &in, Painting &painting)
{
QString title;
QString artist;
quint32 year;
in >> title >> artist >> year;
painting = Painting(title, artist, year);
return in;
}

Il existe plusieurs avantages fournir des oprateurs de flux pour les types de donnes personnaliss. Lun deux est que nous pouvons ainsi transmettre des conteneurs qui utilisent le type
personnalis. Par exemple :
QList<Painting> paintings = ...;
out << paintings;

Nous pouvons lire les conteneurs tout aussi facilement :


QList<Painting> paintings;
in >> paintings;

Ceci provoquerait une erreur de compilateur si Painting ne supportait pas << ou >>. Un autre
avantage des oprateurs de flux pour les types personnaliss est que nous pouvons stocker
les valeurs de ces types en tant que QVariant, ce qui les rend plus largement utilisables,
par exemple par les QSetting. Ceci ne fonctionne que si nous enregistrons pralablement
le type en excutant qRegisterMetaTypeStreamOperators<T>(), comme expliqu dans le
Chapitre 11.
Lorsque nous utilisons QDataStream, Qt se charge de lire et dcrire chaque type, dont les
conteneurs avec un nombre arbitraire dlments. Cette caractristique nous vite de structurer
ce que nous crivons et dappliquer une conversion ce que nous lisons. Notre seule obligation
consiste nous assurer que nous lisons tous les types dans leur ordre dcriture, en laissant
Qt le soin de grer tous les dtails.
QDataStream est utile la fois pour nos formats de fichiers dapplication personnaliss et
pour les formats binaires standard. Nous pouvons lire et crire des formats binaires standard en
utilisant les oprateurs de flux sur les types de base (tels que quint16 ou float) ou au moyen
de readRawBytes() et de writeRawBytes(). Si le QDataStream est purement utilis pour
lire et crire des types de donnes C++ de base, il est inutile dappeler setVersion().

Qt 4 Livre Page 292 Jeudi, 7. dcembre 2006 12:14 12

292

Qt4 et C++ : Programmation dinterfaces GUI

Jusqu prsent, nous avons charg et enregistr les donnes avec la version code en dur du
flux sous la forme QDataStream::Qt_4_1. Cette approche est simple et sre, mais elle
prsente un lger inconvnient : nous ne pouvons pas tirer parti des formats nouveaux ou mis
jour. Par exemple, si une version ultrieure de Qt ajoutait un nouvel attribut QFont (en plus
de sa famille, de sa taille, etc.) et que nous ayons cod en dur le numro de version en Qt_4_1,
cet attribut ne serait pas enregistr ou charg. Deux solutions soffrent vous. La premire
approche consiste intgrer le numro de version QDataStream dans le fichier :
QDataStream out(&file);
out << quint32(MagicNumber) << quint16(out.version());

(MagicNumber est une constante qui identifie de faon unique le type de fichier.) Avec cette
approche, nous crivons toujours les donnes en utilisant la version la plus rcente de QDataStream. Lorsque nous en venons lire le fichier, nous lisons la version du flux :
quint32 magic;
quint16 streamVersion;
QDataStream in(&file);
in >> magic >> streamVersion;
if (magic!= MagicNumber) {
cerr << "File is not recognized by this application" << endl;
} else if (streamVersion > in.version()) {
cerr << "File is from a more recent version of the application"
<< endl;
return false;
}
in.setVersion(streamVersion);

Nous pouvons lire les donnes tant que la version du flux est infrieure ou gale la version
utilise par lapplication. Dans le cas contraire, nous signalons une erreur.
Si le format de fichier contient un numro de version personnel, nous pouvons lutiliser pour
dduire le numro de version du flux au lieu de le stocker explicitement. Supposons, par exemple, que le format de fichier est destin la version 1.3 de notre application. Nous pouvons
alors crire les donnes comme suit :
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(MagicNumber) << quint16(0x0103);

Lorsque nous les relisons, nous dterminons quelle version de QDataStream utiliser selon le
numro de version de lapplication :
QDataStream in(&file);
in >> magic >> appVersion;

Qt 4 Livre Page 293 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

293

if (magic!= MagicNumber) {
cerr << "File is not recognized by this application" << endl;
return false;
} else if (appVersion > 0x0103) {
cerr << "File is from a more recent version of the application"
<< endl;
return false;
}
if (appVersion < 0x0103) {
in.setVersion(QDataStream::Qt_3_0);
} else {
in.setVersion(QDataStream::Qt_4_1);
}

Dans cet exemple, nous spcifions que tout fichier enregistr avec une version antrieure la
version 1.3 de lapplication utilise la version de flux de donnes 4 (Qt_3_0) et que les fichiers
enregistrs avec la version 1.3 de lapplication utilisent la version de flux de donnes 7
(Qt_4_1).
En rsum, il existe trois stratgies pour grer les versions de QDataStream: coder en dur le
numro de version, crire et lire explicitement le numro de version et utiliser diffrents numros de version cods en dur en fonction de la version de lapplication. Toutes peuvent tre
employes afin dassurer que les donnes crites par une ancienne version dune application
peuvent tre lues par une nouvelle version. Une fois cette stratgie de gestion des versions de
QDataStream choisie, la lecture et lcriture de donnes binaires au moyen de Qt est la fois
simple et fiable.
Si nous souhaitons lire ou crire un fichier en une seule fois, nous pouvons viter lutilisation
de QDataStream et recourir la place aux fonctions write() et readAll() de QIODevice.
Par exemple :
bool copyFile(const QString &source, const QString &dest)
{
QFile sourceFile(source);
if (!sourceFile.open(QIODevice::ReadOnly))
return false;
QFile destFile(dest);
if (!destFile.open(QIODevice::WriteOnly))
return false;
destFile.write(sourceFile.readAll());
return sourceFile.error() == QFile::NoError
&& destFile.error() == QFile::NoError;
}

Sur la ligne de lappel de readAll(), le contenu entier du fichier dentre est lu et plac dans
un QByteArray. Il est alors transmis la fonction write() pour tre crit dans le fichier de
sortie. Le fait davoir toutes les donnes dans un QByteArray ncessite plus de mmoire que

Qt 4 Livre Page 294 Jeudi, 7. dcembre 2006 12:14 12

294

Qt4 et C++ : Programmation dinterfaces GUI

de les lire lment par lment, mais offre quelques avantages. Nous pouvons excuter, par
exemple, qCompress() et qUncompress() pour les compresser et les dcompresser.
Il existe dautres scnarios o il est plus appropri daccder directement QIODevice que
dutiliser QDataStream. QIODevice fournit une fonction peek() qui retourne les octets de
donne suivants sans changer lemplacement du priphrique ainsi quune fonction ungetChar() qui permet de revenir un octet en arrire. Ceci fonctionne la fois pour les priphriques daccs alatoire (tels que les fichiers) et pour les priphriques squentiels (tels que les
sockets rseau). Il existe galement une fonction seek() qui dfinit la position des priphriques supportant laccs alatoire.
Les formats de fichier binaires offrent les moyens les plus souples et les plus compacts de stockage de donnes. QDataStream facilite laccs aux donnes binaires. En plus des exemples de
cette section, nous avons dj tudi lutilisation de QDataStream au Chapitre 4 pour lire et
crire des fichiers de tableur, et nous lutiliserons de nouveau dans le Chapitre 19 pour lire et crire
des fichiers de curseur Windows.

Lire et crire du texte


Les formats de fichiers binaires sont gnralement plus compacts que ceux bass sur le
texte, mais ils ne sont pas lisibles ou modifiables par lhomme. Si cela reprsente un
problme, il est possible dutiliser la place les formats texte. Qt fournit la classe QTextStream pour lire et crire des fichiers de texte brut et pour des fichiers utilisant dautres
formats texte, tels que HTML, XML et du code source. La gestion des fichiers XML est
traite dans le Chapitre 15.

QTextStream se charge de la conversion entre Unicode et le codage local du systme ou tout


autre codage, et gre de faon transparente les conventions de fin de ligne utilises par les
diffrents systmes dexploitation ("\r\n" sur Windows, "n" sur Unix et Mac OS X). QTextStream utilise le type QChar 16 bits comme unit de donne fondamentale. En plus des caractres et des chanes, QTextStream prend en charge les types numriques de base de C++, quil
convertit en chanes. Par exemple, le code suivant crit "Thomas M. Disch : 334" dans le
fichier sf-book.txt:
QFile file("sf-book.txt");
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Cannot open file for writing: "
<< qPrintable(file.errorString()) << endl;
return;
}
QTextStream out(&file);
out << "Thomas M. Disch: " << 334 << endl;

Qt 4 Livre Page 295 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

295

Lcriture du texte est trs facile, mais sa lecture peut reprsenter un vritable dfit, car les
donnes textuelles (contrairement aux donnes binaires crites au moyen de QDataStream)
sont fondamentalement ambigus. Considrons lexemple suivant :
out << "Norway" << "Sweden";

Si out est un QTextStream, les donnes vritablement crites sont la chane "NorwaySweden".
Nous ne pouvons pas vraiment nous attendre ce que le code suivant lise les donnes correctement :
in >> str1 >> str2;

En fait, str1 obtient le mot "NorwaySweden" entier et str2 nobtient rien. Ce problme ne
se pose pas avec QDataStream, car la longueur de chaque chane est stocke devant les
donnes.
Pour les formats de fichier complexes, un analyseur risque dtre requis. Un tel analyseur peut
fonctionner en lisant les donnes caractre par caractre en utilisant un >> sur un QChar, ou
ligne par ligne au moyen de QTextStream::readLine(). A la fin de cette section, nous
prsentons deux petits exemples. Le premier lit un fichier dentre ligne par ligne et lautre le
lit caractre par caractre. Pour ce qui est des analyseurs qui traitent le texte entier, nous
pouvons lire le fichier complet en une seule fois laide de QTextStream::readAll() si
nous ne nous proccupons pas de lutilisation de la mmoire, ou si nous savons que le fichier
est petit.
Par dfaut, QTextStream utilise le codage local du systme (par exemple, ISO 8859-1 ou ISO
8859-15 aux Etats-Unis et dans une grande partie de lEurope) pour les oprations de lecture et
dcriture. Vous pouvez changer ceci en excutant setCodec() comme suit :
stream.setCodec("UTF-8");

UTF-8 est un codage populaire compatible ASCII capable de reprsenter la totalit du jeu de
caractres Unicode. Pour plus dinformations concernant Unicode et la prise en charge des
codages de QTextStream, reportez-vous au Chapitre 17 (Internationalisation).
QTextStream possde plusieurs options modeles sur celles offertes par <iostream>. Elles
peuvent tre dfinies en transmettant des objets spciaux, nomms manipulateurs de flux, au
flux pour modifier son tat. Lexemple suivant dfinit les options showbase, uppercasedigits
et hex avant la sortie de lentier 12345678, produisant le texte "0xBC614E" :
out << showbase << uppercasedigits << hex << 12345678;

Les options peuvent galement tre dfinies en utilisant les fonctions membres :
out.setNumberFlags(QTextStream::ShowBase
| QTextStream::UppercaseDigits);
out.setIntegerBase(16);
out << 12345678;

Qt 4 Livre Page 296 Jeudi, 7. dcembre 2006 12:14 12

296

Qt4 et C++ : Programmation dinterfaces GUI

setIntegerBase(int)
0

Dtection automatique base sur le prfixe (lors de la lecture)

Binaire

Octal

10

Dcimal

16

Hexadcimal

setNumberFlags(NumberFlags)
ShowBase

Affiche un prfixe si la base est 2 ("0b"), 8 ("0") ou 16 ("0x")

ForceSign

Affiche toujours le signe des nombres rels

ForcePoint

Place toujours le sparateur de dcimale dans les nombres

UppercaseBase

Utilise les versions majuscules des prfixes de base ("0X", "0B")

UppercaseDigits

Utilise des lettres majuscules dans les nombres hexadcimaux

setRealNumberNotation(RealNumberNotation)
FixedNotation

Notation point fixe (par exemple, "0.000123")

ScientificNotation

Notation scientifique (par exemple, "1.234568e-04")

SmartNotation

Notation la plus compacte entre point fixe ou scientifique

setRealNumberPrecision(int)
Dfinit le nombre maximum de chiffres devant tre gnrs (6 par dfaut)
setFieldWidth(int)
Dfinit la taille minimum dun champ (0 par dfaut)
setFieldAlignment(FieldAlignment)
AlignLeft

Force un alignement sur le ct gauche du champ

AlignRight

Force un alignement sur le ct droit du champ

AlignCenter

Force un alignement sur les deux cts du champ

AlignAccountingStyle

Force un alignement entre le signe et le nombre

setPadChar(QChar)
Dfini le caractre utiliser pour lalignement (espace par dfaut)
Figure 12.1
Fonctions destines dfinir les options de QTextStream

Qt 4 Livre Page 297 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

297

Comme QDataStream, QTextStream agit sur une sous-classe de QIODevice, qui peut tre un
QFile, un QTemporaryFile, un QBuffer, un QProcess, un QTcpSocket ou un
QUdpSocket. En outre, il peut tre utilis directement sur un QString. Par exemple :
QString str;
QTextStream(&str) << oct << 31 << " " << dec << 25 << endl;

Le contenu de str est donc "37 25/n", car le nombre dcimal 31 est exprim sous la forme 37
en base huit. Dans ce cas, il nest pas ncessaire de dfinir un codage sur le flux, car QString
est toujours Unicode.
Examinons un exemple simple de format de fichier bas sur du texte. Dans lapplication
Spreadsheet dcrite dans la Partie 1, nous avons utilis un format binaire pour stocker les
donnes. Ces donnes consistaient en une squence de triplets (ligne, colonne, formule), une
pour chaque cellule non vide. Il nest pas difficile dcrire les donnes sous forme de texte.
Voici un extrait dune version rvise de Spreadsheet::writeFile().
QTextStream out(&file);
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << row << " " << column << " " << str << endl;
}
}

Nous avons utilis un format simple, chaque ligne reprsentant une cellule avec des espaces
entre la ligne et la colonne ainsi quentre la colonne et la formule. La formule contient des
espaces, mais nous pouvons supposer quelle ne comporte aucun /n (qui insre un retour la
ligne). Examinons maintenant le code de lecture correspondant :
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
QStringList fields = line.split( );
if (fields.size() >= 3) {
int row = fields.takeFirst().toInt();
int column = fields.takeFirst().toInt();
setFormula(row, column, fields.join( ));
}
}

Nous lisons les donnes de Spreadsheet ligne par ligne. La fonction readLine() supprime le
/n de fin. QString::split() retourne une liste de chanes de caractres qui est scinde
lemplacement dapparition du sparateur qui lui est fournit. Par exemple, la ligne "5 19 Total
Value" rsulte en une liste de quatre lments ["5", "19", "Total", "Value"].
Si nous disposons au moins de trois champs, nous sommes prts extraire les donnes. La
fonction QStringList::takeFirst() supprime le premier lment dune liste et retourne
llment supprim. Nous lutilisons pour extraire les nombres de ligne et de colonne.

Qt 4 Livre Page 298 Jeudi, 7. dcembre 2006 12:14 12

298

Qt4 et C++ : Programmation dinterfaces GUI

Nous ne ralisons aucune vrification derreur. Si nous lisons une valeur de ligne ou de
colonne non entire, QString::toInt() retourne 0. Lorsque nous appelons setFormula(),
nous devons concatner les champs restants en une seule chane.
Dans notre deuxime exemple de QTextStream, nous utilisons une approche caractre par
caractre pour implmenter un programme qui lit un fichier texte et met en sortie le mme
texte avec les espaces de fin supprims et toutes les tabulations remplaces par des espaces.
Le programme accomplit cette tche dans la fonction tidyFile():
void tidyFile(QIODevice *inDevice, QIODevice *outDevice)
{
QTextStream in(inDevice);
QTextStream out(outDevice);
const int TabSize = 8;
int endlCount = 0;
int spaceCount = 0;
int column = 0;
QChar ch;
while (!in.atEnd()) {
in >> ch;
if (ch == \n) {
++endlCount;
spaceCount = 0;
column = 0;
} else if (ch == \t) {
int size = TabSize - (column % TabSize);
spaceCount += size;
column += size;
} else if (ch == ) {
++spaceCount;
++column;
} else {
while (endlCount > 0) {
out << endl;
--endlCount;
column = 0;
}
while (spaceCount > 0) {
out << ;
--spaceCount;
++column;
}
out << ch;
++column;
}
}
out << endl;
}

Qt 4 Livre Page 299 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

299

Nous crons un QTextStream dentre et de sortie bas sur les QIODevice transmis la fonction. Nous conservons trois lments dtat : un dcompte des nouvelles lignes, un dcompte
des espaces et lemplacement de colonne courant dans la ligne en cours (pour convertir les
tabulations en un nombre despaces correct).
Lanalyse est faite dans une boucle while qui parcourt chaque caractre du fichier dentre. Le
code prsente quelques subtilits certains endroits. Par exemple, bien que nous dfinissions
tabSize en 8, nous remplaons les tabulations par le nombre despaces qui permet datteindre
le taquet de tabulation suivant, au lieu de remplacer grossirement chaque tabulation par huit
espaces. Dans le cas dune nouvelle ligne, dune nouvelle tabulation ou dun nouvel espace,
nous mettons simplement jour les donnes dtat. Pour les autres types de caractres, nous
produisons une sortie. Avant dcrire le caractre nous introduisons tous les espaces et les
nouvelles lignes ncessaires (pour respecter les lignes vierges et prserver le retrait) et mettons
jour ltat.
int main()
{
QFile inFile;
QFile outFile;
inFile.open(stdin, QFile::ReadOnly);
outFile.open(stdout, QFile::WriteOnly);
tidyFile(&inFile, &outFile);
return 0;
}

Pour cet exemple, nous navons pas besoin dobjet QApplication, car nous nutilisons que
les classes doutils de Qt. Vous trouverez la liste de toutes les classes doutils ladresse
http://doc.trolltech.com/4.1/tools.html. Nous avons suppos que le programme est utilis en
tant que filtre, par exemple :
tidy < cool.cpp > cooler.cpp

Il serait facile de le dvelopper afin de grer les noms de fichier qui seraient transmis sur la
ligne de commande ainsi que pour filtrer cin en cout.
Comme il sagit dune application de console, le fichier .pro diffre lgrement de ceux que
nous avons rencontrs pour les applications GUI :
TEMPLATE
QT
CONFIG
CONFIG
SOURCES

=
=
+=
-=
=

app
core
console
app_bundle
tidy.cpp

Nous ntablissons de liaison quavec QtCore, car nous nutilisons aucune fonctionnalit GUI.
Puis nous spcifions que nous souhaitons activer la sortie de la console sur Windows et que
nous ne voulons pas que lapplication soit hberge dans un package sur Mac OS X.

Qt 4 Livre Page 300 Jeudi, 7. dcembre 2006 12:14 12

300

Qt4 et C++ : Programmation dinterfaces GUI

Pour lire et crire des fichiers ASCII ou ISO 8859-1 (Latin-1) bruts, il est possible dutiliser
directement lAPI de QIODevice au lieu de QTextStream. Mais cette mthode est rarement
conseille dans la mesure o la plupart des applications doivent prendre en charge dautres
codages un stade ou un autre, et o seul QTextStream offre une prise en charge parfaite de
ces codages. Si vous souhaitez nanmoins crire le texte directement dans un QIODevice,
vous devez spcifier explicitement la balise QIODevice::Text dans la fonction open().
Par exemple :
file.open(QIODevice::WriteOnly | QIODevice::Text);

Lors de lcriture, cette balise indique QIODevice de convertir les caractres \n en des
squences "\r\n" sur Windows. Lors de la lecture, elle indique au priphrique dignorer les
caractres r sur toutes les plates-formes. Nous pouvons alors supposer que la fin de chaque
ligne est marque par un caractre de nouvelle ligne n quelle que soit la convention de fin de
ligne utilise par le systme dexploitation.

Parcourir les rpertoires


La classe QDir fournit un moyen indpendant de la plate-forme de parcourir les rpertoires et
de rcuprer des informations concernant les fichiers. Pour dterminer comment QDir est utilise, nous allons crire une petite application de console qui calcule lespace occup par toutes
les images dun rpertoire particulier et de tous ses sous-rpertoires, quelle que soit leur
profondeur.
Le cur de lapplication est la fonction imageSpace(), qui calcule rcursivement la taille
cumule des images dun rpertoire donn :
qlonglong imageSpace(const QString &path)
{
QDir dir(path);
qlonglong size = 0;
QStringList filters;
foreach (QByteArray format, QImageReader::supportedImageFormats())
filters += "*." + format;
foreach (QString file, dir.entryList(filters, QDir::Files))
size += QFileInfo(dir, file).size();
foreach (QString subDir, dir.entryList(QDir::Dirs
| QDir::NoDotAndDotDot))
size += imageSpace(path + QDir::separator() + subDir);
return size;
}

Qt 4 Livre Page 301 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

301

Nous commenons par crer un objet QDir en utilisant un chemin donn, qui peut tre relatif
au rpertoire courant ou absolu. Nous transmettons deux arguments la fonction
entryList(). Le premier est une liste de filtres de noms de fichiers. Ils peuvent contenir les
caractres gnriques * et ?. Dans cet exemple, nous ralisons un filtrage de faon
ninclure que les formats de fichiers susceptibles dtre lus par QImage. Le second argument
spcifie le type dentre souhait (fichiers normaux, rpertoires, etc.).
Nous parcourons la liste de fichiers, en additionnant leurs tailles. La classe QFileInfo nous
permet daccder aux attributs dun fichier, tels que la taille, les autorisations, le propritaire et
lhorodateur de ce fichier.
Le deuxime appel de entryList() rcupre tous les sous-rpertoires de ce rpertoire. Nous
les parcourons et nous appelons imageSpace() rcursivement pour tablir la taille cumule de
leurs images.
Pour crer le chemin de chaque sous-rpertoire, nous combinons de chemin du rpertoire en
cours avec le nom du sous-rpertoire, en les sparant par un slash (/).

QDir traite / comme un sparateur de rpertoires sur toutes les plates-formes. Pour prsenter
les chemins lutilisateur, nous pouvons appeler la fonction statique QDir::convertSeparators() qui convertit les slash en sparateurs spcifiques la plate-forme.
Ajoutons une fonction main() notre petit programme :
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
QString path = QDir::currentPath();
if (args.count() > 1)
path = args[1];
cout << "Space used by images in " << qPrintable(path)
<< " and its subdirectories is " << (imageSpace(path) / 1024)
<< " KB" << endl;
return 0;
}

Nous utilisons QDir::currentPath() pour initialiser le chemin vers le rpertoire courant.


Nous aurions aussi pu faire appel QDir::homePath() pour linitialiser avec le rpertoire de
base de lutilisateur. Si ce dernier a spcifi un chemin sur la ligne de commande, nous lutilisons la place. Nous appelons enfin notre fonction imageSpace() pour calculer lespace
occup par les images.
La classe QDir fournit dautres fonctions lies aux rpertoires et aux fichiers, dont entryInfoList() (qui retourne une liste dobjets QFileInfo), rename(), exists(), mkdir() et
rmdir(). La classe QFile fournit des fonctions utilitaires statiques, dont remove() et exists().

Qt 4 Livre Page 302 Jeudi, 7. dcembre 2006 12:14 12

302

Qt4 et C++ : Programmation dinterfaces GUI

Intgration des ressources


Jusqu prsent, nous avons tudi laccs aux donnes dans des priphriques externes, mais
avec Qt, il est galement possible dintgrer du texte ou des donnes binaires dans lexcutable
de lapplication. Pour ce faire, il convient dutiliser le systme de ressources de Qt. Dans
les autres chapitres, nous avons utilis les fichiers de ressources pour intgrer des images
dans lexcutable, mais il est possible dintgrer tout type de fichier. Les fichiers intgrs
peuvent tre lus au moyen de QFile, exactement comme les fichiers normaux dun systme de
fichiers.
Les ressources sont converties en code C++ par rcc, le compilateur de ressources de Qt. Nous
pouvons demander qmake dinclure des rgles spciales dexcution de rcc en ajoutant cette
ligne au fichier .pro:
RESOURCES = myresourcefile.qrc

myresourcefile.qrc est un fichier XML qui rpertorie les fichiers intgrer dans lexcutable.
Imaginons que nous crivions une application destine rpertorier des coordonnes. Pour des
raisons pratiques, nous souhaitons intgrer les indicatifs tlphoniques internationaux dans
lexcutable. Si le fichier se situe dans le rpertoire datafiles de lapplication, le fichier de
ressources serait le suivant :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>datafiles/phone-codes.dat</file>
</qresource>
</RCC>

Depuis lapplication, les ressources sont identifies par le prfixe de chemin :/. Dans cet exemple,
le chemin du fichier contenant les indicatifs tlphoniques est :/datafiles/phonecodes.dat et peut tre lu comme tout autre fichier en utilisant QFile.
Lintgration de donnes dans lexcutable prsente plusieurs avantages : les donnes ne
peuvent tre perdues et cette opration permet la cration dexcutables vritablement autonomes (si une liaison statique est galement utilise). Les inconvnients sont les suivants : si
les donnes intgres doivent tre changes, il est impratif de remplacer lexcutable entier, et
la taille de ce dernier sera plus importante car il doit sadapter aux donnes intgres.
Le systme de ressources de Qt fournit des fonctionnalits supplmentaires, telles que la prise
en charge des alias de noms de fichiers et la localisation. Ces fonctionnalits sont documentes
ladresse http://doc.trolltech.com/4.1/resources.html.

Qt 4 Livre Page 303 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

303

Communication inter-processus
La classe QProcess nous permet dexcuter des programmes externes et dinteragir avec
ceux-ci. La classe fonctionne de faon asynchrone, effectuant sa tche larrire-plan de sorte
que linterface utilisateur reste ractive. QProcess met des signaux pour nous indiquer quand
le processus externe dtient des donnes ou a termin.
Nous allons rviser le code dune petite application qui fournit une interface utilisateur pour un
programme externe de conversion des images. Pour cet exemple, nous nous reposons sur le
programme ImageMagick convert, qui est disponible gratuitement sur toutes les platesformes principales. (Voir Figure 12.2)
Figure 12.2
Lapplication
Image Converter

Linterface utilisateur a t cre dans Qt Designer. Le fichier .ui se trouve sur le site web de
Pearson, www.pearson.fr, la page ddie ce livre. Ici, nous allons nous concentrer sur
la sous-classe qui hrite de la classe Ui::ConvertDialog
.
gnre par le compilateur
d'interface utilisateur. Commenons par len-tte :
#ifndef CONVERTDIALOG_H
#define CONVERTDIALOG_H
#include <QDialog>
#include <QProcess>
#include "ui_convertdialog.h"
class ConvertDialog: public QDialog, public Ui::ConvertDialog
{
Q_OBJECT
public:
ConvertDialog(QWidget *parent = 0);

Qt 4 Livre Page 304 Jeudi, 7. dcembre 2006 12:14 12

304

Qt4 et C++ : Programmation dinterfaces GUI

private
void
void
void
void
void

slots:
on_browseButton_clicked();
on_convertButton_clicked();
updateOutputTextEdit();
processFinished(int exitCode, QProcess::ExitStatus exitStatus);
processError(QProcess::ProcessError error);

private:
QProcess process;
QString targetFile;
};
#endif

Len-tte est conforme celui dune sous-classe de formulaire Qt Designer. Grce au mcanisme de connexion automatique de Qt Designer, les slots on_browseButton_clicked() et
on_convertButton_clicked() sont automatiquement connects aux signaux clicked()
des boutons Browse et Convert.
ConvertDialog::ConvertDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
connect(&process, SIGNAL(readyReadStandardError()),
this, SLOT(updateOutputTextEdit()));
connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(processFinished(int, QProcess::ExitStatus)));
connect(&process, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(processError(QProcess::ProcessError)));
}

Lappel de setupUi() cre et positionne tous les widgets du formulaire, tablit les connexions
signal/slot pour les slots on_objectName_signalName() et connecte le bouton Quit
QDialog::accept(). Nous connectons ensuite manuellement trois signaux de lobjet QProcess trois slots privs. A chaque fois que le processus externe va dtecter des donnes sur
son cerr, il les grera avec updateOutputTextEdit().
void ConvertDialog::on_browseButton_clicked()
{
QString initialName = sourceFileEdit->text();
if (initialName.isEmpty())
initialName = QDir::homePath();
QString fileName =
QFileDialog::getOpenFileName(this, tr("Choose File"),
initialName);
fileName = QDir::convertSeparators(fileName);
if (!fileName.isEmpty()) {
sourceFileEdit->setText(fileName);
convertButton->setEnabled(true);
}
}

Qt 4 Livre Page 305 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

305

Le signal clicked() du bouton Browse est automatiquement connect au slot


on_browseButton_clicked() par setupUi(). Si lutilisateur a pralablement slectionn
un fichier, nous initialisons la bote de dialogue avec le nom de ce fichier. Sinon, nous prenons
le rpertoire de base de lutilisateur.
void ConvertDialog::on_convertButton_clicked()
{
QString sourceFile = sourceFileEdit->text();
targetFile = QFileInfo(sourceFile).path() + QDir::separator()
+ QFileInfo(sourceFile).baseName() + "."
+ targetFormatComboBox->currentText().toLower();
convertButton->setEnabled(false);
outputTextEdit->clear();
QStringList args;
if (enhanceCheckBox->isChecked())
args << "-enhance";
if (monochromeCheckBox->isChecked())
args << "-monochrome";
args << sourceFile << targetFile;
process.start("convert", args);
}

Lorsque lutilisateur clique sur le bouton Convert, nous copions le nom du fichier source et
changeons lextension afin quelle corresponde au format du fichier cible.
Nous utilisons le sparateur de rpertoires spcifique la plate-forme (/ ou \, disponible
sous la forme QDir::separator()) au lieu de coder les slash en dur, car le nom du fichier
sera visible pour lutilisateur.
Nous dsactivons alors le bouton Convert pour viter que lutilisateur ne lance accidentellement plusieurs conversions, et nous effaons lditeur de texte qui nous permet dafficher les
informations dtat.
Pour lancer le processus externe, nous appelons QProcess::start() avec le nom du
programme excuter (convert) et tous les arguments requis. Dans ce cas, nous transmettons
les balises enhance et monochrome si lutilisateur a coch les options appropries, suivies
des noms des fichiers source et cible. Le programme convert dduit la conversion requise
partir des extensions de fichier.
void ConvertDialog::updateOutputTextEdit()
{
QByteArray newData = process.readAllStandardError();
QString text = outputTextEdit->toPlainText()
+ QString::fromLocal8Bit(newData);
outputTextEdit->setPlainText(text);
}

Qt 4 Livre Page 306 Jeudi, 7. dcembre 2006 12:14 12

306

Qt4 et C++ : Programmation dinterfaces GUI

Lorsque le processus externe effectue une opration dcriture dans le cerr, le slot updateOutputTextEdit() est appel. Nous lisons le texte derreur et lajoutons au texte existant
de QTextEdit.
void ConvertDialog::processFinished(int exitCode,
QProcess::ExitStatus exitStatus)
{
if (exitStatus == QProcess::CrashExit) {
outputTextEdit->append(tr("Conversion program crashed"));
} else if (exitCode!= 0) {
outputTextEdit->append(tr("Conversion failed"));
} else {
outputTextEdit->append(tr("File %1 created").arg(targetFile));
}
convertButton->setEnabled(true);
}

Une fois le processus termin, nous faisons connatre le rsultat lutilisateur et activons le
bouton Convert.
void ConvertDialog::processError(QProcess::ProcessError error)
{
if (error == QProcess::FailedToStart) {
outputTextEdit->append(tr("Conversion program not found"));
convertButton->setEnabled(true);
}
}

Si le processus ne peut tre lanc, QProcess met error() au lieu de finished(). Nous
rapportons toute erreur et activons le bouton Click.
Dans cet exemple, nous avons effectu les conversions de fichier de faon asynchrone nous
avons demand QProcess dexcuter le programme convert et de rendre immdiatement le
contrle lapplication. Cette mthode laisse linterface utilisateur ractive puisque le processus sexcute larrire-plan. Mais dans certains cas, il est ncessaire que le processus externe
soit termin avant de pouvoir poursuivre avec notre application. Dans de telles situations,
QProcess doit agir de faon synchrone.
Les applications qui prennent en charge ldition de texte brut dans lditeur de texte prfr de
lutilisateur sont typiques de celles dont le comportement doit tre synchrone. Limplmentation sobtient facilement en utilisant QProcess. Supposons, par exemple, que le texte brut se
trouve dans un QTextEdit, et que vous fournissiez un bouton Edit sur lequel lutilisateur peut
cliquer, connect un slot edit().
void ExternalEditor::edit()
{
QTemporaryFile outFile;
if (!outFile.open())
return;
QString fileName = outFile.fileName();

Qt 4 Livre Page 307 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 12

Entres/Sorties

307

QTextStream out(&outFile);
out << textEdit->toPlainText();
outFile.close();
QProcess::execute(editor, QStringList() << options << fileName);
QFile inFile(fileName);
if (!inFile.open(QIODevice::ReadOnly))
return;
QTextStream in(&inFile);
textEdit->setPlainText(in.readAll());
}

Nous utilisons QTemporaryFile pour crer un fichier vide avec un nom unique. Nous ne
spcifions aucun argument pour QTemporaryFile::open() puisquelle est judicieusement
dfinie pour ouvrir en mode criture/lecture par dfaut. Nous crivons le contenu de lditeur
de texte dans le fichier temporaire, puis nous fermons ce dernier car certains diteurs de texte
ne peuvent travailler avec des fichiers dj ouverts.
La fonction statique QProcess::execute() excute un processus externe et provoque un
blocage jusqu ce que le processus soit termin. Largument editor est un QString contenant le nom dun excutable diteur ("gvim", par exemple). Largument options est un
QStringList (contenant un lment, "-f", si nous utilisons gvim).
Une fois que lutilisateur a ferm lditeur de texte, le processus se termine ainsi que lappel de
execute(). Nous ouvrons alors le fichier temporaire et lisons son contenu dans le QTextEdit. QTemporaryFile supprime automatiquement le fichier temporaire lorsque lobjet sort
de la porte.
Les connexions signal/slot ne sont pas ncessaires lorsque QProcess est utilis de faon
synchrone. Si un contrle plus fin que celui fourni par la fonction statique execute() est
requis, nous pouvons utiliser une autre approche qui implique de crer un objet QProcess et
dappeler start() sur celui-ci, puis de forcer un blocage en appelant QProcess::waitForStarted(), et en cas de succs, en appelant QProcess::waitForFinished(). Reportez-vous
la documentation de rfrence de QProcess pour trouver un exemple utilisant cette approche.
Dans cette section, nous avons exploit QProcess pour obtenir laccs une fonctionnalit
prexistante. Lutilisation dapplications dj existantes reprsente un gain de temps et vous
vite davoir rsoudre des problmes trs loigns de lobjectif principal de votre application.
Une autre faon daccder une fonctionnalit prexistante consiste tablir une liaison vers
une bibliothque qui la fournit. Si une telle bibliothque nexiste pas, vous pouvez aussi envisager dencapsuler votre application de console dans un QProcess.
QProcess peut galement servir lancer dautres applications GUI, telles quun navigateur
Web ou un client email. Si, cependant, votre objectif est la communication entre applications et
non la simple excution de lune partir de lautre, il serait prfrable de les faire communiquer directement en utilisant les classes de gestion de rseau de Qt ou lextension ActiveQt sur
Windows.

Qt 4 Livre Page 308 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 309 Jeudi, 7. dcembre 2006 12:14 12

13
Les bases de donnes
Au sommaire de ce chapitre
Connexion et excution de requtes
Prsenter les donnes sous une forme tabulaire
Implmenter des formulaires matre/dtail

Le module QtSql fournit une interface indpendante de la plate-forme pour accder aux
bases de donnes. Cette interface est prise en charge par un ensemble de classes qui
utilisent larchitecture modle/vue de Qt pour intgrer la base de donnes linterface
utilisateur. Ce chapitre suppose une certaine familiarit avec les classes modle/vue de
Qt, traites dans le Chapitre 10.

Qt 4 Livre Page 310 Jeudi, 7. dcembre 2006 12:14 12

310

Qt4 et C++ : Programmation dinterfaces GUI

Une connexion de base de donnes est reprsente par un objet QSqlDatabase. Qt utilise des
pilotes pour communiquer avec les diverses API de base de donnes. Qt Desktop Edition inclut
les pilotes suivants :
Pilote

Base de donnes

QDB2

IBM DB2 version 7.1 et ultrieure

QIBASE

Borland InterBase

QMYSQL

MySQL

QOCI

Oracle (Oracle Call Interface)

QODBC

ODBC (inclut Microsoft SQL Server)

QPSQL

PostgreSQL versions 6.x et 7.x

QSQLITE

SQLite version 3 et ultrieure

QSQLITE2

SQLite version 2

QTDS

Sybase Adaptive Server

Tous les pilotes ne sont pas fournis avec Qt Open Source Edition, en raison de restrictions de
licence. Lors de la configuration de Qt, nous pouvons choisir entre inclure directement les pilotes SQL Qt et les crer en tant que plugin. Qt est fourni avec la base de donnes SQLite, une
base de donnes in-process (qui sintgre aux applications) de domaine public.
Pour les utilisateurs familiariss avec la syntaxe SQL, la classe QSqlQuery reprsente un bon
moyen dexcuter directement des instructions SQL arbitraires et de grer leurs rsultats. Pour
les utilisateurs qui prfrent une interface de base de donnes plus volue masquant la syntaxe
SQL, QSqlTableModel et QSqlRelationalTableModel fournissent des abstractions acceptables. Ces classes reprsentent une table SQL de la mme faon que les autres classes modle
de Qt (traites dans le Chapitre 10).Elles peuvent tre utilises de faon autonome pour
parcourir et modifier des donnes dans le code, ou encore tre associes des vues par le biais
desquelles les utilisateurs finaux peuvent afficher et modifier les donnes.

Connexion et excution de requtes


Pour excuter des requtes SQL, nous devons tout dabord tablir une connexion avec une base
de donnes. En rgle gnrale, les connexions aux bases de donnes sont dfinies dans une
fonction spare que nous appelons au lancement de lapplication. Par exemple :
bool createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");

Qt 4 Livre Page 311 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

311

db.setHostName("mozart.konkordia.edu");
db.setDatabaseName("musicdb");
db.setUserName("gbatstone");
db.setPassword("T17aV44");
if (!db.open()) {
QMessageBox::critical(0, QObject::tr("Database Error"),
db.lastError().text());
return false;
}
return true;
}

Dans un premier temps, nous appelons QSqlDatabase::addDatabase() pour crer un objet


QSqlDatabase. Le premier argument de addDatabase() spcifie quel pilote doit tre utilis
par Qt pour accder la base de donnes. Dans ce cas, nous utilisons MySQL.
Nous dfinissons ensuite le nom de lhte de la base de donnes, le nom de cette base de
donnes, le nom dutilisateur et le mot de passe, puis nous ouvrons la connexion. Si open()
choue, nous affichons un message derreur.
Nous appelons gnralement createConnection() dans main():
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!createConnection())
return 1;

return app.exec();
}

Une fois quune connexion est tablie, nous pouvons utiliser QSqlQuery pour excuter toute
instruction SQL supporte par la base de donnes concerne. Par exemple, voici comment
excuter une instruction SELECT:
QSqlQuery query;
query.exec("SELECT title, year FROM cd WHERE year >= 1998");

Aprs lappel dexec(), nous pouvons parcourir lensemble de rsultats de la requte :


while (query.next()) {
QString title = query.value(0).toString();
int year = query.value(1).toInt();
cerr << qPrintable(title) << ": " << year << endl;
}

Au premier appel de next(), QSqlQuery est positionn sur le premier enregistrement de


lensemble de rsultats. Les appels ultrieurs next() permettent davancer le pointeur vers
les enregistrements successifs, jusqu ce que la fin soit atteinte. A ce stade, next() retourne
false. Si lensemble de rsultats est vide (ou si la requte a chou), le premier appel
next() retourne false.

Qt 4 Livre Page 312 Jeudi, 7. dcembre 2006 12:14 12

312

Qt4 et C++ : Programmation dinterfaces GUI

La fonction value() retourne la valeur dun champ en tant que QVariant. Les champs sont
numrots en partant de 0 dans lordre fourni dans linstruction SELECT. La classe QVariant
peut contenir de nombreux types Qt et C++, dont int et QString. Les diffrents types de
donnes susceptibles dtre stocks dans une base de donnes sont transforms en types Qt et
C++ correspondants et stocks en tant que QVariant. Par exemple, un VARCHAR est reprsent
en tant que QString et un DATETIME en tant que QDateTime.
QSqlQuery fournit quelques autres fonctions destines parcourir lensemble de rsultats :
first(), last(), previous() et seek(). Ces fonctions sont pratiques, mais pour certaines
bases de donnes elles peuvent savrer plus lentes et plus gourmandes en mmoire que
next(). Pour optimiser facilement dans le cas de jeux de donnes de taille importante, nous
pouvons appeler QSqlQuery::setForwardOnly(true) avant dappeler exec(), puis
seulement utiliser next() pour parcourir lensemble de rsultats.
Nous avons prcdemment prsent la requte SQL comme un argument de QSqlQuery::exec(), mais il est galement possible de la transmettre directement au constructeur,
qui lexcute immdiatement :
QSqlQuery query("SELECT title, year FROM cd WHERE year >= 1998");

Nous pouvons contrler lexistence dune erreur en appelant isActive() sur la requte :
if (!query.isActive())
QMessageBox::warning(this, tr("Database Error"),
query.lastError().text());

Si aucune erreur napparat, la requte devient "active" et nous pouvons utiliser next() pour
parcourir lensemble de rsultats.
Il est presque aussi facile de raliser un INSERT que deffectuer un SELECT:
QSqlQuery query("INSERT INTO cd (id, artistid, title, year) "
"VALUES (203, 102, Living in America, 2002)");

Aprs cette opration, numRowsAffected() retourne le nombre de lignes affectes par


linstruction SQL (ou -1 en cas derreur).
Si nous devons insrer de nombreux enregistrements, ou si nous souhaitons viter la conversion de valeurs en chanes, nous pouvons recourir prepare() pour excuter une requte
contenant des emplacements rservs puis lier les valeurs insrer. Qt prend en charge la fois
la syntaxe de style ODBC et celle de style Oracle pour les espaces rservs, en utilisant le
support natif lorsquil est disponible et en le simulant dans les autres cas. Voici un exemple
utilisant la syntaxe de style Oracle avec des espaces rservs nomms :
QSqlQuery query;
query.prepare("INSERT INTO cd (id, artistid, title, year) "
"VALUES (:id,:artistid,:title,:year)");
query.bindValue(":id", 203);
query.bindValue(":artistid", 102);
query.bindValue(":title", "Living in America");

Qt 4 Livre Page 313 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

313

query.bindValue(":year", 2002);
query.exec();

Voici le mme exemple utilisant des espaces rservs positionnels de style ODBC :
QSqlQuery query;
query.prepare("INSERT INTO cd (id, artistid, title, year) "
"VALUES (?,?,?,?)");
query.addBindValue(203);
query.addBindValue(102);
query.addBindValue("Living in America");
query.addBindValue(2002);
query.exec();

Aprs lappel exec(), nous pouvons appeler bindValue() ou addBindValue() pour lier
de nouvelles valeurs, puis nous appelons de nouveau exec() pour excuter la requte avec ces
nouvelles valeurs.
Les espaces rservs sont souvent utiliss pour des donnes binaires contenant des caractres
non ASCII ou nappartenant pas au jeu de caractres Latin-1. A larrire-plan, Qt utilise
Unicode avec les bases de donnes qui prennent en charge cette norme. Pour les autres, Qt
convertit de faon transparente les chanes en codage appropri.
Qt supporte les transactions SQL sur les bases de donnes pour lesquelles elles sont disponibles. Pour lancer une transaction, nous appelons transaction() sur lobjet QSqlDatabase
qui reprsente la connexion de base de donnes. Pour mettre fin la transaction, nous appelons
soit commit(), soit rollback(). Voici, par exemple, comment rechercher une cl trangre et
excuter une instruction INSERT dans une transaction :
QSqlDatabase::database().transaction();
QSqlQuery query;
query.exec("SELECT id FROM artist WHERE name = Gluecifer");
if (query.next()) {
int artistId = query.value(0).toInt();
query.exec("INSERT INTO cd (id, artistid, title, year) "
"VALUES (201, " + QString::number(artistId)
+ ", Riding the Tiger, 1997)");
}
QSqlDatabase::database().commit();

La fonction QSqlDatabase::database() retourne un objet QSqlDatabase reprsentant la


connexion cre dans createConnection(). Si une transaction ne peut tre dmarre, QSqlDatabase::transaction() retourne false. Certaines bases de donnes ne supportent pas
les transactions. Dans cette situation, les fonctions transaction(), commit() et rollback() nont aucune action. Nous pouvons dterminer si une base de donnes prend en charge
les transactions en excutant hasFeature() sur le QSqlDriver associ cette base :
QSqlDriver *driver = QSqlDatabase::database().driver();
if (driver->hasFeature(QSqlDriver::Transactions))

Qt 4 Livre Page 314 Jeudi, 7. dcembre 2006 12:14 12

314

Qt4 et C++ : Programmation dinterfaces GUI

Plusieurs autres fonctionnalits de bases de donnes peuvent tre testes, notamment la prise
en charge des BLOB (objets binaires volumineux), dUnicode et des requtes prpares.
Dans les exemples fournis jusqu prsent, nous avons suppos que lapplication utilise une
seule connexion de base de donnes. Pour crer plusieurs connexions, il est possible de transmettre un nom en tant que second argument addDatabase(). Par exemple :
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL", "OTHER");
db.setHostName("saturn.mcmanamy.edu");
db.setDatabaseName("starsdb");
db.setUserName("hilbert");
db.setPassword("ixtapa7");

Nous pouvons alors obtenir un pointeur vers lobjet QSqlDatabase en transmettant le nom
QSqlDatabase::database():
QSqlDatabase db = QSqlDatabase::database("OTHER");

Pour excuter des requtes en utilisant lautre connexion, nous transmettons lobjet QSqlDatabase au constructeur de QSqlQuery:
QSqlQuery query(db);
query.exec("SELECT id FROM artist WHERE name = Mando Diao");

Des connexions multiples peuvent savrer utiles si vous souhaitez effectuer plusieurs transactions la fois, chaque connexion ne pouvant grer quune seule transaction. Lorsque nous utilisons les connexions de base de donnes multiples, nous pouvons toujours avoir une connexion
non nomme qui sera exploite par QSqlQuery si aucune connexion nest spcifie.
En plus de QSqlQuery, Qt fournit la classe QSqlTableModel comme interface de haut niveau,
ce qui permet dviter lemploi du code SQL brut pour raliser les oprations SQL les plus
courantes (SELECT, INSERT, UPDATE et DELETE). La classe peut tre utilise de faon autonome pour manipuler une base de donnes sans aucune implication GUI. Elle peut galement
tre utilise comme source de donnes pour QListView ou QTableView.
Voici un exemple qui utilise QSqlTableModel pour raliser un SELECT:
QSqlTableModel model;
model.setTable("cd");
model.setFilter("year >= 1998");
model.select();

Ce qui est quivalent la requte


SELECT * FROM cd WHERE year >= 1998

Pour parcourir un ensemble de rsultats, nous rcuprons un enregistrement donn au moyen de


QSqlTableModel::record() et nous accdons aux champs individuels laide de value():
for (int i = 0; i < model.rowCount(); ++i) {

Qt 4 Livre Page 315 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

315

QSqlRecord record = model.record(i);


QString title = record.value("title").toString();
int year = record.value("year").toInt();
cerr << qPrintable(title) << ": " << year << endl;
}

La fonction QSqlRecord::value() reoit soit un nom, soit un index de champ. En travaillant


sur des jeux de donnes de taille importante, il est prfrable de dsigner les champs par leurs
index. Par exemple :
int titleIndex = model.record().indexOf("title");
int yearIndex = model.record().indexOf("year");
for (int i = 0; i < model.rowCount(); ++i) {
QSqlRecord record = model.record(i);
QString title = record.value(titleIndex).toString();
int year = record.value(yearIndex).toInt();
cerr << qPrintable(title) << ": " << year << endl;
}

Pour insrer un enregistrement dans une table de base de donnes, nous utilisons la mme
approche que pour une insertion dans tout modle bidimensionnel : en premier lieu, nous appelons insertRow() pour crer une nouvelle ligne (enregistrement) vide, puis nous faisons appel
setData() pour dfinir les valeurs de chaque colonne (champ).
QSqlTableModel model;
model.setTable("cd");
int row = 0;
model.insertRows(row, 1);
model.setData(model.index(row,
model.setData(model.index(row,
model.setData(model.index(row,
model.setData(model.index(row,
model.submitAll();

0),
1),
2),
3),

113);
"Shanghai My Heart");
224);
2003);

Aprs lappel submitAll(), lenregistrement peut tre dplac vers un emplacement diffrent dans la ligne, selon lorganisation de la table. Lappel de cette fonction retournera false
si linsertion a chou.
Une diffrence importante entre un modle SQL et un modle standard est que dans le premier
cas, nous devons appeler submitAll() pour valider une modification dans la base de donnes.
Pour mettre jour un enregistrement, nous devons tout dabord placer le QSqlTableModel sur
lenregistrement modifier (en excutant select()), par exemple. Nous extrayons ensuite
lenregistrement, mettons jour les champs voulus, puis rcrivons nos modifications dans la
base de donnes :
QSqlTableModel model;
model.setTable("cd");
model.setFilter("id = 125");
model.select();
if (model.rowCount() == 1) {

Qt 4 Livre Page 316 Jeudi, 7. dcembre 2006 12:14 12

316

Qt4 et C++ : Programmation dinterfaces GUI

QSqlRecord record = model.record(0);


record.setValue("title", "Melody A.M.");
record.setValue("year", record.value("year").toInt() + 1);
model.setRecord(0, record);
model.submitAll();
}

Si un enregistrement correspond au filtre spcifi, nous le rcuprons au moyen de QSqlTableModel::record(). Nous appliquons nos modifications et remplaons lenregistrement initial
par ce dernier.
Comme dans le cas dun modle non SQL, il est galement possible de raliser une mise jour
au moyen de setData(). Les index de modle que nous rcuprons correspondent une ligne
ou une colonne donne :
model.select();
if (model.rowCount() == 1) {
model.setData(model.index(0, 1), "Melody A.M.");
model.setData(model.index(0, 3),
model.data(model.index(0, 3)).toInt() + 1);
model.submitAll();
}

La suppression dun enregistrement est similaire sa mise jour :


model.setTable("cd");
model.setFilter("id = 125");
model.select();
if (model.rowCount() == 1) {
model.removeRows(0, 1);
model.submitAll();
}

Lappel de removeRows() reoit le numro de ligne du premier enregistrement supprimer


ainsi que le nombre denregistrements liminer. Lexemple suivant supprime tous les enregistrements correspondant au filtre :
model.setTable("cd");
model.setFilter("year < 1990");
model.select();
if (model.rowCount() > 0) {
model.removeRows(0, model.rowCount());
model.submitAll();
}

Les classes QSqlQuery et QSqlTableModel fournissent une interface entre Qt et une base de
donnes SQL. En utilisant ces classes, nous pouvons crer des formulaires qui prsentent les
donnes aux utilisateurs et leur permettent dinsrer, de mettre jour et de supprimer des enregistrements.

Qt 4 Livre Page 317 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

317

Prsenter les donnes sous une forme tabulaire


Dans de nombreuses situations, il est plus simple de proposer aux utilisateurs une vue tabulaire
dun jeu de donnes. Dans cette section ainsi que dans la suivante, nous prsentons une application CD Collection simple qui fait appel QSqlTableModel et sa sous-classe QSqlRelationalTableModel pour permettre aux utilisateurs dafficher et dinteragir avec les donnes
stockes dans une base de donnes.
Le formulaire principal prsente une vue matre/dtail dun CD et les pistes du CD en cours de
slection, comme illustr en Figure 13.1.
Figure 13.1
Lapplication
CD Collection

Lapplication utilise trois tables, dfinies comme suit :


CREATE TABLE artist (
id INTEGER PRIMARY KEY,
name VARCHAR(40) NOT NULL,
country VARCHAR(40));
CREATE TABLE cd (
id INTEGER PRIMARY KEY,
title VARCHAR(40) NOT NULL,
artistid INTEGER NOT NULL,
year INTEGER NOT NULL,
FOREIGN KEY (artistid) REFERENCES artist);
CREATE TABLE track (
id INTEGER PRIMARY KEY,

Qt 4 Livre Page 318 Jeudi, 7. dcembre 2006 12:14 12

318

Qt4 et C++ : Programmation dinterfaces GUI

title VARCHAR(40) NOT NULL,


duration INTEGER NOT NULL,
cdid INTEGER NOT NULL,
FOREIGN KEY (cdid) REFERENCES cd);

Certaines bases de donnes ne supportent pas les cls trangres. Dans ce cas, nous devons
supprimer les clauses FOREIGN KEY. Lexemple fonctionnera toujours, mais la base de donnes
nappliquera pas lintgrit rfrentielle. (Voir Figure 13.2)
Figure 13.2
Les tables
de lapplication
CD Collection

artist
id
name
country

1:N

cd
id
title
artistid
year

1:N

track
id
title
duration
cdid

Dans cette section, nous allons crire une bote de dialogue dans laquelle les utilisateurs pourront modifier une liste dartistes en utilisant une forme tabulaire simple. Lutilisateur peut insrer ou supprimer des artistes au moyen des boutons du formulaire. Les mises jour peuvent
tre appliques directement, simplement en modifiant le texte des cellules. Les changements
sont appliqus la base de donnes lorsque lutilisateur appuie sur Entre ou passe un autre
enregistrement. (Voir Figure 13.3)
Figure 13.3
La bote de dialogue
ArtistForm

Voici la dfinition de classe pour cette bote de dialogue :


class ArtistForm: public QDialog
{
Q_OBJECT
public:
ArtistForm(const QString &name, QWidget *parent = 0);
private slots:
void addArtist();
void deleteArtist();

Qt 4 Livre Page 319 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

319

void beforeInsertArtist(QSqlRecord &record);


private:
enum {
Artist_Id = 0,
Artist_Name = 1,
Artist_Country = 2
};
QSqlTableModel *model;
QTableView *tableView;
QPushButton *addButton;
QPushButton *deleteButton;
QPushButton *closeButton;
};

Le constructeur est trs similaire celui qui serait utilis pour crer un formulaire bas sur un
modle non SQL :
ArtistForm::ArtistForm(const QString &name, QWidget *parent)
: QDialog(parent)
{
model = new QSqlTableModel(this);
model->setTable("artist");
model->setSort(Artist_Name, Qt::AscendingOrder);
model->setHeaderData(Artist_Name, Qt::Horizontal, tr("Name"));
model->setHeaderData(Artist_Country, Qt::Horizontal, tr("Country"));
model->select();
connect(model, SIGNAL(beforeInsert(QSqlRecord &)),
this, SLOT(beforeInsertArtist(QSqlRecord &)));
tableView = new QTableView;
tableView->setModel(model);
tableView->setColumnHidden(Artist_Id, true);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->resizeColumnsToContents();
for (int row = 0; row < model->rowCount(); ++row) {
QSqlRecord record = model->record(row);
if (record.value(Artist_Name).toString() == name) {
tableView->selectRow(row);
break;
}
}

Nous commenons le constructeur en crant un QSqlTableModel. Nous lui transmettons


this comme parent pour octroyer la proprit au formulaire. Nous avons choisi de baser le tri
sur la colonne 1 (spcifie par la constante Artist_Name), ce qui correspond au champ name.
Si nous ne spcifions pas den-tte de colonnes, ce sont les noms des champs qui sont utiliss.

Qt 4 Livre Page 320 Jeudi, 7. dcembre 2006 12:14 12

320

Qt4 et C++ : Programmation dinterfaces GUI

Nous prfrons les nommer nous-mmes pour garantir une casse et une internationalisation
correctes.
Nous crons ensuite un QTableView pour visualiser le modle. Nous masquons le champ id et
dfinissons les largeurs des colonnes en fonction de leur texte afin de ne pas avoir afficher de
points de suspension.
Le constructeur de ArtistForm reoit le nom de lartiste qui doit tre slectionn louverture de la bote de dialogue. Nous parcourons les enregistrements de la table artist et slectionnons lartiste voulu. Le reste du code du constructeur permet de crer et de connecter les
boutons ainsi que de positionner les widgets enfants.
void ArtistForm::addArtist()
{
int row = model->rowCount();
model->insertRow(row);
QModelIndex index = model->index(row, Artist_Name);
tableView->setCurrentIndex(index);
tableView->edit(index);
}

Pour ajouter un nouvel artiste, nous insrons une ligne vierge dans le bas de QTableView.
Lutilisateur peut maintenant entrer le nom et le pays du nouvel artiste. Sil confirme linsertion en appuyant sur Entre, le signal beforeInsert() est mis puis le nouvel enregistrement
est insr dans la base de donnes.
void ArtistForm::beforeInsertArtist(QSqlRecord &record)
{
record.setValue("id", generateId("artist"));
}

Dans le constructeur, nous avons connect le signal beforeInsert() du modle ce slot.


Une rfrence non-const lenregistrement nous est transmise juste avant son insertion dans la
base de donnes. A ce stade, nous remplissons son champ id.
Comme nous aurons besoin de generateId() plusieurs reprises, nous la dfinissons "en
ligne" dans un fichier den-tte et lincluons chaque fois que ncessaire. Voici un moyen
rapide (et inefficace) de limplmenter :
inline int generateId(const QString &table)
{
QSqlQuery query;
query.exec("SELECT MAX(id) FROM " + table);
int id = 0;
if (query.next())
id = query.value(0).toInt() + 1;
return id;
}

La fonction generateId() nest assure de fonctionner correctement que si elle est excute dans le contexte de la mme transaction que linstruction INSERT correspondante.

Qt 4 Livre Page 321 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

321

Certaines bases de donnes supportent les champs gnrs automatiquement, et il est gnralement nettement prfrable dutiliser la prise en charge spcifique chaque base de donnes
pour cette opration.
La dernire possibilit offerte par la bote de dialogue ArtistForm est la suppression. Au lieu
deffectuer des suppressions en cascade (que nous avons abordes brivement), nous avons
choisi de nautoriser que les suppressions dartistes ne possdant pas de CD dans la collection.
void ArtistForm::deleteArtist()
{
tableView->setFocus();
QModelIndex index = tableView->currentIndex();
if (!index.isValid())
return;
QSqlRecord record = model->record(index.row());
QSqlTableModel cdModel;
cdModel.setTable("cd");
cdModel.setFilter("artistid = " + record.value("id").toString());
cdModel.select();
if (cdModel.rowCount() == 0) {
model->removeRow(tableView->currentIndex().row());
} else {
QMessageBox::information(this,
tr("Delete Artist"),
tr("Cannot delete %1 because there are CDs associated "
"with this artist in the collection.")
.arg(record.value("name").toString()));
}
}

Si un enregistrement est slectionn, nous dterminons si lartiste possde un CD. Si tel nest
pas le cas, nous le supprimons immdiatement. Sinon, nous affichons une bote de message
expliquant pourquoi la suppression na pas eu lieu. Strictement parlant, nous aurions d utiliser
une transaction, car tel que le code se prsente, il est possible que lartiste que nous supprimons soit associ un CD entre les appels de cdModel.select() et model->removeRow().
Nous prsenterons une transaction dans la prochaine section.

Implmenter des formulaires matre/dtail


A prsent, nous allons rviser le formulaire principal avec une approche matre/dtail. La vue
matre est une liste de CD. La vue dtail est une liste de pistes pour le CD en cours. Ce formulaire
reprsente la fentre principale de lapplication CD Collection comme illustr en Figure 13.1.
class MainForm: public QWidget
{
Q_OBJECT

Qt 4 Livre Page 322 Jeudi, 7. dcembre 2006 12:14 12

322

Qt4 et C++ : Programmation dinterfaces GUI

public:
MainForm();
private
void
void
void
void
void
void
void
void
void

slots:
addCd();
deleteCd();
addTrack();
deleteTrack();
editArtists();
currentCdChanged(const QModelIndex &index);
beforeInsertCd(QSqlRecord &record);
beforeInsertTrack(QSqlRecord &record);
refreshTrackViewHeader();

private:
enum {
Cd_Id = 0,
Cd_Title = 1,
Cd_ArtistId = 2,
Cd_Year = 3
};
enum {
Track_Id = 0,
Track_Title = 1,
Track_Duration = 2,
Track_CdId = 3
};
QSqlRelationalTableModel *cdModel;
QSqlTableModel *trackModel;
QTableView *cdTableView;
QTableView *trackTableView;
QPushButton *addCdButton;
QPushButton *deleteCdButton;
QPushButton *addTrackButton;
QPushButton *deleteTrackButton;
QPushButton *editArtistsButton;
QPushButton *quitButton;
};

Au lieu dun QSqlTableModel, nous utilisons un QSqlRelationalTableModel pour la table


cd, car nous devons grer les cls trangres. Nous allons maintenant revoir chaque fonction
tour tour, en commenant par le constructeur que nous tudierons par segments car il est
assez long.
MainForm::MainForm()
{
cdModel = new QSqlRelationalTableModel(this);
cdModel->setTable("cd");
cdModel->setRelation(Cd_ArtistId,
QSqlRelation("artist", "id", "name"));
cdModel->setSort(Cd_Title, Qt::AscendingOrder);

Qt 4 Livre Page 323 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

323

cdModel->setHeaderData(Cd_Title, Qt::Horizontal, tr("Title"));


cdModel->setHeaderData(Cd_ArtistId, Qt::Horizontal, tr("Artist"));
cdModel->setHeaderData(Cd_Year, Qt::Horizontal, tr("Year"));
cdModel->select();

Le constructeur dfinit tout dabord le QSqlRelationalTableModel qui gre la table cd.


Lappel de setRelation() indique au modle que son champ artistid (dont lindex est
inclus dans Cd_ArtistId) possde la cl trangre id de la table artist, et que le contenu du
champ name correspondant doit tre affich la place des ID. Si lutilisateur choisit dditer ce
champ (par exemple en appuyant sur F2), le modle prsentera automatiquement une zone de
liste droulante avec les noms de tous les artistes, et si lutilisateur choisit un artiste diffrent, il
mettra la table cd jour.
cdTableView = new QTableView;
cdTableView->setModel(cdModel);
cdTableView->setItemDelegate(new QSqlRelationalDelegate(this));
cdTableView->setSelectionMode(QAbstractItemView::SingleSelection);
cdTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
cdTableView->setColumnHidden(Cd_Id, true);
cdTableView->resizeColumnsToContents();

La dfinition de la vue pour la table cd est similaire ce que nous avons dj vu. La seule
diffrence significative est la suivante : au lieu dutiliser le dlgu par dfaut de la vue, nous
utilisons QSqlRelationalDelegate. Cest ce dlgu qui gre les cls trangres.
trackModel = new QSqlTableModel(this);
trackModel->setTable("track");
trackModel->setHeaderData(Track_Title, Qt::Horizontal, tr("Title"));
trackModel->setHeaderData(Track_Duration, Qt::Horizontal,
tr("Duration"));
trackTableView = new QTableView;
trackTableView->setModel(trackModel);
trackTableView->setItemDelegate(
new TrackDelegate(Track_Duration, this));
trackTableView->setSelectionMode(
QAbstractItemView::SingleSelection);
trackTableView->setSelectionBehavior(QAbstractItemView::SelectRows);

Pour ce qui est des pistes, nous nallons montrer que leurs noms et leurs dures. Cest pourquoi
QSqlTableModel est suffisant. Le seul aspect remarquable de cette partie du code est que nous
utilisons le TrackDelegate dvelopp dans le Chapitre 10 pour afficher les dures des pistes
sous la forme "minutes:secondes" et permettre leur dition en utilisant un QTimeEdit adapt.
La cration, la connexion et la disposition des vues ainsi que des boutons ne prsente pas de
surprise. Cest pourquoi la seule autre partie du constructeur que nous allons prsenter contient
quelques connexions non videntes.

connect(cdTableView->selectionModel(),

Qt 4 Livre Page 324 Jeudi, 7. dcembre 2006 12:14 12

324

Qt4 et C++ : Programmation dinterfaces GUI

SIGNAL(currentRowChanged(const QModelIndex &,


const QModelIndex &)),
this, SLOT(currentCdChanged(const QModelIndex &)));
connect(cdModel, SIGNAL(beforeInsert(QSqlRecord &)),
this, SLOT(beforeInsertCd(QSqlRecord &)));
connect(trackModel, SIGNAL(beforeInsert(QSqlRecord &)),
this, SLOT(beforeInsertTrack(QSqlRecord &)));
connect(trackModel, SIGNAL(rowsInserted(const QModelIndex &, int,
int)),
this, SLOT(refreshTrackViewHeader()));

La premire connexion est inhabituelle, car au lieu de connecter un widget, nous tablissons
une connexion avec un modle de slection. La classe QItemSelectionModel est utilise
pour assurer le suivi des slections dans les vues. En tant connect au modle de slection de
la vue table, notre slot currentCdChanged() sera appel ds que lutilisateur passe dun
enregistrement lautre.
void MainForm::currentCdChanged(const QModelIndex &index)
{
if (index.isValid()) {
QSqlRecord record = cdModel->record(index.row());
int id = record.value("id").toInt();
trackModel->setFilter(QString("cdid = %1").arg(id));
} else {
trackModel->setFilter("cdid = -1");
}
trackModel->select();
refreshTrackViewHeader();
}

Ce slot est appel ds que le CD en cours change, ce qui se produit lorsque lutilisateur passe
un autre CD (en cliquant ou en utilisant les touches flches). Si le CD est invalide (sil
nexiste pas de CD, si un nouveau CD est en cours dinsertion ou encore si celui en cours vient
dtre supprim), nous dfinissons le cdid de la table track en 1 (un ID invalide qui ne
correspondra aucun enregistrement).
Puis, en ayant dfini le filtre, nous slectionnons les enregistrements de piste correspondants.
La fonction refreshTrackViewHeader() sera tudie dans un moment.
void MainForm::addCd()
{
int row = 0;
if (cdTableView->currentIndex().isValid())
row = cdTableView->currentIndex().row();
cdModel->insertRow(row);
cdModel->setData(cdModel->index(row, Cd_Year),
QDate::currentDate().year());

Qt 4 Livre Page 325 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

325

QModelIndex index = cdModel->index(row, Cd_Title);


cdTableView->setCurrentIndex(index);
cdTableView->edit(index);
}

Lorsque lutilisateur clique sur le bouton Add CD, une nouvelle ligne vierge est insre dans le
cdTableView et nous entrons en mode dition. Nous dfinissons galement une valeur par
dfaut pour le champ year. A ce stade, lutilisateur peut modifier lenregistrement en remplissant les champs vierges et en slectionnant un artiste dans la zone de liste droulante qui est
automatiquement fournie par le QSqlRelationalTableModel grce lappel de setRelation(). Il peut aussi modifier lanne si celle propose par dfaut savre inapproprie.
Si lutilisateur confirme linsertion en appuyant sur Entre, lenregistrement est insr. Lutilisateur peut annuler en appuyant sur Echap.
void MainForm::beforeInsertCd(QSqlRecord &record)
{
record.setValue("id", generateId("cd"));
}

Ce slot est appel lorsque le cdModel met son signal beforeInsert(). Nous lutilisons pour
remplir le champ id de la mme faon que nous lavons fait pour insrer de nouveaux artistes.
Les mmes rgles sappliquent : cette opration doit seffectuer dans la porte dune transaction et avec les mthodes de cration dID spcifiques la base de donnes (par exemple, les
ID gnrs automatiquement).
void MainForm::deleteCd()
{
QModelIndex index = cdTableView->currentIndex();
if (!index.isValid())
return;
QSqlDatabase db = QSqlDatabase::database();
db.transaction();
QSqlRecord record = cdModel->record(index.row());
int id = record.value(Cd_Id).toInt();
int tracks = 0;
QSqlQuery query;
query.exec(QString("SELECT COUNT(*) FROM track WHERE cdid = %1")
.arg(id));
if (query.next())
tracks = query.value(0).toInt();
if (tracks > 0) {
int r = QMessageBox::question(this, tr("Delete CD"),
tr("Delete \"%1\" and all its tracks?")
.arg(record.value(Cd_ArtistId).toString()),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape);
if (r == QMessageBox::No) {
db.rollback();
return;
}

Qt 4 Livre Page 326 Jeudi, 7. dcembre 2006 12:14 12

326

Qt4 et C++ : Programmation dinterfaces GUI

query.exec(QString("DELETE FROM track WHERE cdid = %1")


.arg(id));
}
cdModel->removeRow(index.row());
cdModel->submitAll();
db.commit();
currentCdChanged(QModelIndex());
}

Lorsque lutilisateur clique sur le bouton Delete CD, ce slot est appel. Quand un CD est en
cours, nous dterminons son nombre de pistes. Si nous ne trouvons pas de piste, nous supprimons directement lenregistrement du CD. Sil existe au moins une piste, nous demandons
lutilisateur de confirmer la suppression. Sil clique sur Yes, nous supprimons tous les enregistrements de piste, puis lenregistrement du CD. Toutes ces oprations sont effectues dans la
porte dune transaction. Ainsi, soit la suppression en cascade choue en bloc, soit elle russit
dans son ensemble en supposant que la base de donnes en question supporte les transactions.
La gestion des donnes de piste est trs similaire celle des donnes de CD. Les mises jour
peuvent tre effectues simplement via les cellules ddition fournies lutilisateur. Dans le
cas des dures de piste, notre TrackDelegate sassure quelles sont prsentes dans le bon
format et quelles sont facilement modifiables au moyen de QTimeEdit.
void MainForm::addTrack()
{
if (!cdTableView->currentIndex().isValid())
return;
int row = 0;
if (trackTableView->currentIndex().isValid())
row = trackTableView->currentIndex().row();
trackModel->insertRow(row);
QModelIndex index = trackModel->index(row, Track_Title);
trackTableView->setCurrentIndex(index);
trackTableView->edit(index);
}

Le fonctionnement ici est le mme que celui de addCd(), avec une nouvelle ligne vierge insre
dans la vue.
void MainForm::beforeInsertTrack(QSqlRecord &record)
{
QSqlRecord cdRecord = cdModel->record(cdTableView->currentIndex()
.row());
record.setValue("id", generateId("track"));
record.setValue("cdid", cdRecord.value(Cd_Id).toInt());
}

Qt 4 Livre Page 327 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 13

Les bases de donnes

327

Si lutilisateur confirme linsertion initie par addTrack(), cette fonction est appele pour
remplir les champs id et cdid. Les rgles mentionnes prcdemment sappliquent bien sr
toujours ici.
void MainForm::deleteTrack()
{
trackModel->removeRow(trackTableView->currentIndex().row());
if (trackModel->rowCount() == 0)
trackTableView->horizontalHeader()->setVisible(false);
}

Si lutilisateur clique sur le bouton Delete Track, nous supprimons la piste sans formalit.
Il serait facile dafficher une bote de message de type Yes/No si nous envisagions de faire
confirmer les suppressions.
void MainForm::refreshTrackViewHeader()
{
trackTableView->horizontalHeader()->setVisible(
trackModel->rowCount() > 0);
trackTableView->setColumnHidden(Track_Id, true);
trackTableView->setColumnHidden(Track_CdId, true);
trackTableView->resizeColumnsToContents();
}

Le slot refreshTrackViewHeader() est invoqu depuis plusieurs emplacements pour


sassurer que len-tte horizontal de la vue de piste nest prsent que sil existe des pistes
afficher. Il masque aussi les champs id et cdid et redimensionne les colonnes de table visibles
en fonction du contenu courant de la table.
void MainForm::editArtists()
{
QSqlRecord record = cdModel->record(cdTableView->currentIndex()
.row());
ArtistForm artistForm(record.value(Cd_ArtistId).toString(), this);
artistForm.exec();
cdModel->select();
}

Ce slot est appel si lutilisateur clique sur le bouton Edit Artists. Il affiche les donnes
concernant lartiste du CD en cours, invoquant le ArtistForm trait dans la section prcdente
et slectionnant lartiste appropri. Sil nexiste pas denregistrement en cours, un enregistrement vide est retourn par record(). Il ne correspond naturellement aucun artiste dans le
formulaire. Voici ce qui se produit vritablement : comme nous utilisons un QSqlRelationalTableModel qui tablit une correspondance entre les ID des artistes et leurs noms, la valeur
qui est retourne lorsque nous appelons record.value(Cd_ArtistId) est le nom de lartiste
(qui sera une chane vide si lenregistrement est vide). Nous forons enfin le cdModel slectionner de nouveau ses donnes, ce qui conduit le cdTableView rafrachir ses cellules visibles.

Qt 4 Livre Page 328 Jeudi, 7. dcembre 2006 12:14 12

328

Qt4 et C++ : Programmation dinterfaces GUI

Cette opration permet de sassurer que les noms des artistes sont affichs correctement,
certains dentre eux ayant pu tre modifis par lutilisateur dans la bote de dialogue ArtistForm.
Pour les projets qui utilisent les classes SQL, nous devons ajouter la ligne
QT

+= sql

aux fichiers .pro, ce qui garantit la liaison de lapplication la bibliothque QtSql.


Ce chapitre vous a dmontr que les classes vue/modle de Qt facilitent autant que possible
laffichage et la modification de donnes dans les bases SQL. Dans les cas o les cls trangres
se rfrent des tables comportant de nombreux enregistrements (des milliers, voir plus), il est
probablement prfrable de crer votre propre dlgu et de lutiliser pour prsenter un formulaire de "listes de valeurs" offrant des possibilits de recherche au lieu de vous reposer sur les
zones de liste droulante par dfaut de QSqlRelationalTableModel. Et si nous souhaitons
prsenter des enregistrements en utilisant un mode formulaire, nous devons le grer par nousmmes : en faisant appel un QSqlQuery ou un QSqlTableModel pour grer linteraction
avec la base de donnes, et en tablissant une correspondance entre le contenu des widgets de
linterface utilisateur que nous souhaitons utiliser pour prsenter et modifier les donnes et la
base de donnes concerne dans notre propre code.

Qt 4 Livre Page 329 Jeudi, 7. dcembre 2006 12:14 12

14
Gestion de rseau
Au sommaire de ce chapitre
Programmer les clients FTP
Programmer les clients HTTP
Programmer les applications
client/serveur TCP
Envoyer et recevoir des datagrammes UDP

Qt fournit les classes QFtp et QHttp pour la programmation de FTP et HTTP. Ces
protocoles sont faciles utiliser pour tlcharger des fichiers et, dans le cas de HTTP,
pour envoyer des requtes aux serveurs Web et rcuprer les rsultats.
Qt fournit galement les classes de bas niveau QTcpSocket et QUdpSocket, qui implmentent les protocoles de transport TCP et UDP. TCP est un protocole orient
connexion fiable qui agit en termes de flux de donnes transmis entre les nuds rseau,
alors que UDP est un protocole fonctionnant en mode non connect non fiable qui
permet denvoyer des paquets discrets entre des nuds rseau. Tous deux peuvent tre
utiliss pour crer des applications rseau clientes et serveur. En ce qui concerne les
serveurs, nous avons aussi besoin de la classe QTcpServer pour grer les connexions
TCP entrantes.

Qt 4 Livre Page 330 Jeudi, 7. dcembre 2006 12:14 12

330

Qt4 et C++ : Programmation dinterfaces GUI

Programmer les clients FTP


La classe QFtp implmente le ct client du protocole FTP dans Qt. Elle offre diverses fonctions destines raliser les oprations FTP les plus courantes et nous permet dexcuter des
commandes FTP arbitraires.
La classe QFtp fonctionne de faon asynchrone. Lorsque nous appelons une fonction telle que
get() ou put(), elle se termine immdiatement et le transfert de donnes se produit quand le
contrle revient la boucle dvnement de Qt. Ainsi, linterface utilisateur reste ractive
pendant lexcution des commandes FTP.
Nous allons commencer par un exemple qui illustre comment rcuprer un fichier unique au
moyen de get(). Lexemple est une application de console nomme ftpget qui tlcharge le
fichier distant spcifi sur la ligne de commande. Commenons par la fonction main():
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
if (args.count()!= 2) {
cerr << "Usage: ftpget url" << endl
<< "Example:" << endl
<< " ftpget ftp://ftp.trolltech.com/mirrors" << endl;
return 1;
}
FtpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}

Nous crons un QCoreApplication plutt que sa sous-classe QApplication pour viter une
liaison dans la bibliothque QtGui. La fonction QCoreApplication::arguments() retourne
les arguments de ligne de commande sous forme de QStringList, le premier lment tant le
nom sous lequel le programme a t invoqu, et tous les arguments propres Qt (tels que -style)
tant supprims. Le cur de la fonction main() est la construction de lobjet FtpGet et
lappel de getFile(). Si lappel russit, nous laissons la boucle dvnement sexcuter
jusqu la fin du tlchargement.
Tout le travail est effectu par la sous-classe FtpGet, qui est dfinie comme suit :
class FtpGet: public QObject
{
Q_OBJECT

Qt 4 Livre Page 331 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

331

public:
FtpGet(QObject *parent = 0);
bool getFile(const QUrl &url);
signals:
void done();
private slots:
void ftpDone(bool error);
private:
QFtp ftp;
QFile file;
};

La classe possde une fonction publique, getFile(), qui rcupre le fichier spcifi par une
URL. La classe QUrl fournit une interface de haut niveau destine extraire les diffrents
segments dune URL, tels que le nom du fichier, le chemin daccs, le protocole et le port.
FtpGet possde un slot priv, ftpDone(), qui est appel lorsque le transfert de fichier est
termin, et un signal done() qui est mis une fois le fichier tlcharg. La classe contient
galement deux variables prives : la variable ftp, de type QFtp, qui encapsule la connexion
avec un serveur FTP et la variable file qui est utilise pour crire le fichier tlcharg sur le
disque.
FtpGet::FtpGet(QObject *parent)
: QObject(parent)
{
connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}

Dans le constructeur, nous connectons le signal QFtp::done(bool) notre slot priv


ftpDone(bool). QFtp met done(bool) une fois le traitement de toutes les requtes termin.
Le paramtre bool indique si une erreur sest produite ou non.
bool FtpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
cerr << "Error: Invalid URL" << endl;
return false;
}
if (url.scheme()!= "ftp") {
cerr << "Error: URL must start with ftp:" << endl;
return false;
}
if (url.path().isEmpty()) {
cerr << "Error: URL has no path" << endl;
return false;
}

Qt 4 Livre Page 332 Jeudi, 7. dcembre 2006 12:14 12

332

Qt4 et C++ : Programmation dinterfaces GUI

QString localFileName = QFileInfo(url.path()).fileName();


if (localFileName.isEmpty())
localFileName = "ftpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Error: Cannot open " << qPrintable(file.fileName())
<< " for writing: " << qPrintable(file.errorString())
<< endl;
return false;
}
ftp.connectToHost(url.host(), url.port(21));
ftp.login();
ftp.get(url.path(), &file);
ftp.close();
return true;
}

La fonction getFile() commence en vrifiant lURL transmise. Si un problme est rencontr,


elle met un message derreur vers cerr et retourne false pour indiquer que le tlchargement a
chou.
Au lieu dobliger lutilisateur crer un nom de fichier local, nous essayons de gnrer un nom
judicieux constitu de lURL elle-mme, avec ftpget.out comme solution de secours. Si
nous ne parvenons pas ouvrir le fichier, nous affichons un message derreur et retournons
false.
Nous excutons ensuite une squence de quatre commandes FTP en utilisant notre objet QFtp.
Lappel de url.port(21) retourne le numro de port mentionn dans lURL, ou le port 21 si
lURL nen spcifie aucun. Aucun nom dutilisateur ou mot de passe ntant transmis la
fonction login(), on tente une ouverture de session anonyme. Le second argument de get()
spcifie le priphrique de sortie.
Les commandes FTP sont places en file dattente et excutes dans la boucle dvnement de
Qt. Lachvement de toutes les commandes est indiqu par le signal done(bool) de QFtp, que
nous avons connect ftpDone(bool) dans le constructeur.
void FtpGet::ftpDone(bool error)
{
if (error) {
cerr << "Error: " << qPrintable(ftp.errorString()) << endl;
} else {
cerr << "File downloaded as " << qPrintable(file.fileName())
<< endl;
}
file.close();
emit done();
}

Qt 4 Livre Page 333 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

333

Les commandes FTP ayant toutes t excutes, nous fermons le fichier et mettons notre
propre signal done(). Il peut sembler trange de fermer le fichier ici, et non aprs lappel de
ftp.close() la fin de la fonction getFile(), mais souvenez-vous que les commandes FTP
sont excutes de faon asynchrone et peuvent trs bien tre en cours la fin de lexcution de
getFile(). Seule lmission du signal done() de lobjet QFtp nous permet de savoir que le
tlchargement est termin et que le fichier peut tre ferm en toute scurit.
QFtp fournit plusieurs commandes FTP, dont connectToHost(), login(), close(),
list(), cd(), get(), put(), remove(), mkdir(), rmdir() et rename(). Toutes ces fonctions programment une commande FTP et retournent un numro dID qui identifie cette
commande. Il est galement possible de contrler le mode et le type de transfert (loption par
dfaut est un mode passif et un type binaire).
Les commandes FTP arbitraires peuvent tre excutes au moyen de la commande rawCommand(). Voici, par exemple, comment excuter une commande SITE CHMOD:
ftp.rawCommand("SITE CHMOD 755 fortune");

QFtp met le signal commandStarted(int) quand il commence excuter une commande


et le signal commandFinished(int, bool) une fois la commande termine. Le paramtre
int est le numro dID qui identifie la commande. Si nous nous intressons au sort des
commandes individuelles, nous pouvons stocker les numros dID lors de la programmation
des commandes. Le fait de suivre ces numros nous permet de fournir un rapport dtaill
lutilisateur. Par exemple :
bool FtpGet::getFile(const QUrl &url)
{
...
connectId = ftp.connectToHost(url.host(), url.port(21));
loginId = ftp.login();
getId = ftp.get(url.path(), &file);
closeId = ftp.close();
return true;
}
void FtpGet::ftpCommandStarted(int id)
{
if (id == connectId) {
cerr << "Connecting..." << endl;
} else if (id == loginId) {
cerr << "Logging in..." << endl;
...
}

Un autre moyen de fournir un rapport consiste tablir une connexion au signal stateChanged()
de QFtp, qui est mis lorsque la connexion entre dans un nouvel tat (QFtp::Connecting,
QFtp::Connected, QFtp::LoggedIn, etc.)

Qt 4 Livre Page 334 Jeudi, 7. dcembre 2006 12:14 12

334

Qt4 et C++ : Programmation dinterfaces GUI

Dans la plupart des applications, nous nous intressons plus au sort de la squence de
commandes dans son ensemble quaux commandes particulires. Dans ce cas, nous pouvons
simplement nous connecter au signal done(bool), qui est mis ds que la file dattente de
commandes est vide.
Quand une erreur se produit, QFtp vide automatiquement la file dattente de commandes.
Ainsi, si la connexion ou louverture de session choue, les commandes qui suivent dans la file
dattente ne sont jamais excutes. Si nous programmons de nouvelles commandes au moyen
de lobjet QFtp aprs que lerreur se soit produite, elles sont places en file dattente et excutes.
Dans le fichier .pro de lapplication, la ligne suivante est ncessaire pour tablir une liaison
avec la bibliothque QtNetwork :
QT

+= network

Nous allons maintenant tudier un exemple plus sophistiqu. Le programme de ligne de


commande spider tlcharge tous les fichiers situs dans un rpertoire FTP. La logique rseau
est gre dans la classe Spider:
class Spider: public QObject
{
Q_OBJECT
public:
Spider(QObject *parent = 0);
bool getDirectory(const QUrl &url);
signals:
void done();
private slots:
void ftpDone(bool error);
void ftpListInfo(const QUrlInfo &urlInfo);
private:
void processNextDirectory();
QFtp ftp;
QList<QFile *> openedFiles;
QString currentDir;
QString currentLocalDir;
QStringList pendingDirs;
};

Le rpertoire de dpart est spcifi en tant que QUrl et est dfini au moyen de la fonction
getDirectory().
Spider::Spider(QObject *parent)
: QObject(parent)
{

Qt 4 Livre Page 335 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

335

connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));


connect(&ftp, SIGNAL(listInfo(const QUrlInfo &)),
this, SLOT(ftpListInfo(const QUrlInfo &)));
}

Dans le constructeur, nous tablissons deux connexions signal/slot. Le signal listInfo


(const QUrlInfo &) est mis par QFtp lorsque nous demandons un listing de rpertoires
(dans getDirectory()) pour chaque fichier rcupr. Ce signal est connect un slot nomm
ftpListInfo(), qui tlcharge le fichier associ lURL qui lui est fournie.
bool Spider::getDirectory(const QUrl &url)
{
if (!url.isValid()) {
cerr << "Error: Invalid URL" << endl;
return false;
}
if (url.scheme()!= "ftp") {
cerr << "Error: URL must start with ftp:" << endl;
return false;
}
ftp.connectToHost(url.host(), url.port(21));
ftp.login();
QString path = url.path();
if (path.isEmpty())
path = "/";
pendingDirs.append(path);
processNextDirectory();
return true;
}

Lorsque la fonction getDirectory() est appele, elle commence par effectuer quelques vrifications de base, et, si tout va bien, tente dtablir une connexion FTP. Elle appelle processNextDirectory() pour lancer le tlchargement du rpertoire racine.
void Spider::processNextDirectory()
{
if (!pendingDirs.isEmpty()) {
currentDir = pendingDirs.takeFirst();
currentLocalDir = "downloads/" + currentDir;
QDir(".").mkpath(currentLocalDir);
ftp.cd(currentDir);
ftp.list();
} else {
emit done();
}
}

Qt 4 Livre Page 336 Jeudi, 7. dcembre 2006 12:14 12

336

Qt4 et C++ : Programmation dinterfaces GUI

La fonction processNextDirectory() reoit le premier rpertoire distant provenant de la


liste pendingDirs et cre un rpertoire correspondant dans le systme de fichiers local.
Elle indique ensuite lobjet QFtp de remplacer le rpertoire existant par celui reu et de
rpertorier ses fichiers. Pour tout fichier trait par list(), un signal listInfo() provoquant
lappel du slot ftpListInfo() est mis.
Sil ne reste plus de rpertoire traiter, la fonction met le signal done() pour indiquer que le
tlchargement est achev.
void Spider::ftpListInfo(const QUrlInfo &urlInfo)
{
if (urlInfo.isFile()) {
if (urlInfo.isReadable()) {
QFile *file = new QFile(currentLocalDir + "/"
+ urlInfo.name());
if (!file->open(QIODevice::WriteOnly)) {
cerr << "Warning: Cannot open file "
<< qPrintable(
QDir::convertSeparators(file->fileName()))
<< endl;
return;
}
ftp.get(urlInfo.name(), file);
openedFiles.append(file);
}
} else if (urlInfo.isDir() &&!urlInfo.isSymLink()) {
pendingDirs.append(currentDir + "/" + urlInfo.name());
}
}

Le paramtre urlInfo du slot ftpListInfo() fournit des informations dtailles concernant


un fichier distant. Sil sagit dun fichier normal (et non dun rpertoire) lisible, nous appelons
get() pour le tlcharger. Lobjet QFile utilis pour le tlchargement est allou au moyen de
new et un pointeur dirig vers celui-ci est stock dans la liste openedFiles.
Si le QUrlInfo contient les dtails dun rpertoire distant qui nest pas un lien symbolique,
nous ajoutons ce rpertoire la liste pendingDirs. Nous ignorons les liens symboliques car
ils peuvent aisment mener une rcurrence infinie.
void Spider::ftpDone(bool error)
{
if (error) {
cerr << "Error: " << qPrintable(ftp.errorString()) << endl;
} else {
cout << "Downloaded " << qPrintable(currentDir) << " to "
<< qPrintable(QDir::convertSeparators(
QDir(currentLocalDir).canonicalPath()));
}

Qt 4 Livre Page 337 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

337

qDeleteAll(openedFiles);
openedFiles.clear();
processNextDirectory();
}

Le slot ftpDone() est appel lorsque toutes les commandes FTP sont termines ou si une
erreur se produit. Nous supprimons les objets QFile pour viter les fuites de mmoire et galement pour fermer chaque fichier. Nous appelons enfin processNextDirectory(). Sil reste
des rpertoires, tout le processus recommence pour le rpertoire suivant dans la liste. Dans le
cas contraire, le tlchargement est interrompu une fois done() mis.
Si aucune erreur nintervient, la squence de signaux et de commandes FTP est la suivante :
connectToHost(host, port)
login()
cd(directory_1)
list()
emit listInfo(file_1_1)
get(file_1_1)
emit listInfo(file_1_2)
get(file_1_2)
...
emit done()
...
cd(directory_N)
list()
emit listInfo(file_N_1)
get(file_N_1)
emit listInfo(file_N_2)
get(file_N_2)
...
emit done()

Si un fichier est un rpertoire, il est ajout la liste pendingDirs. Une fois le dernier fichier
de la commande list() tlcharg, une nouvelle commande cd() est mise, suivie dune
commande list() avec le rpertoire suivant en attente. Tout le processus recommence alors
avec ce rpertoire. Ces oprations sont rptes jusqu ce que chaque fichier ait t tlcharg.
A ce moment l, la liste pendingDirs est vide.
Si une erreur rseau se produit lors du tlchargement du cinquime ou, disons, du vingtime
fichier dun rpertoire, les fichiers restants ne sont pas tlchargs. Pour tlcharger autant de
fichiers que possible, une solution consisterait planifier les oprations GET une par une et
attendre le signal done(bool) avant la planification de lopration suivante. Dans
listInfo(), nous accolerions simplement le nom de fichier un QStringList au lieu

Qt 4 Livre Page 338 Jeudi, 7. dcembre 2006 12:14 12

338

Qt4 et C++ : Programmation dinterfaces GUI

dappeler get() directement, et dans done(bool) nous appellerions get() sur le fichier
suivant tlcharger dans le QStringList. La squence dexcution serait alors celle-ci :
connectToHost(host, port)
login()
cd(directory_1)
list()
...
cd(directory_N)
list()
emit listInfo(file_1_1)
emit listInfo(file_1_2)
...
emit listInfo(file_N_1)
emit listInfo(file_N_2)
...
emit done()
get(file_1_1)
emit done()
get(file_1_2)
emit done()
...
get(file_N_1)
emit done()
get(file_N_2)
emit done()
...

Une autre solution consisterait utiliser un objet QFtp pour chaque fichier, ce qui nous permettrait
de tlcharger les fichiers en parallle, par le biais de connexions FTP spares.
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
if (args.count()!= 2) {
cerr << "Usage: spider url" << endl
<< "Example:" << endl
<< " spider ftp://ftp.trolltech.com/freebies/leafnode"
<< endl;
return 1;
}

Qt 4 Livre Page 339 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

339

Spider spider;
if (!spider.getDirectory(QUrl(args[1])))
return 1;
QObject::connect(&spider, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}

La fonction main() achve le programme. Si lutilisateur ne spcifie pas dURL sur la ligne de
commande, nous gnrons un message derreur et terminons le programme.
Dans les deux exemples FTP, les donnes rcupres au moyen de get() ont t crites dans
un QFile. Ceci nest pas obligatoire. Si nous souhaitions enregistrer les donnes en mmoire,
nous pourrions utiliser un QBuffer, la sous-classe QIODevice qui intgre un QByteArray.
Par exemple :
QBuffer *buffer = new QBuffer;
buffer->open(QIODevice::WriteOnly);
ftp.get(urlInfo.name(), buffer);

Nous pourrions galement omettre largument de priphrique dE/S de get() ou transmettre


un pointeur nul. La classe QFtp met alors un signal readyRead() ds quune nouvelle
donne est disponible. Cette dernire est ensuite lue au moyen de read() ou de readAll().

Programmer les clients HTTP


La classe QHttp implmente le ct client du protocole HTTP dans Qt. Elle fournit diverses
fonctions destines effectuer les oprations HTTP les plus courantes dont get() et post(),
et permet denvoyer des requtes HTTP arbitraires. Si vous avez lu la section prcdente
concernant QFtp, vous constaterez quil existe des similitudes entre QFtp et QHttp.
La classe QHttp fonctionne de faon asynchrone. Lorsque nous appelons une fonction telle
que get() ou post(), elle se termine immdiatement et le transfert de donnes se produit
ultrieurement quand le contrle revient la boucle dvnement de Qt. Ainsi, linterface utilisateur de lapplication reste ractive pendant le traitement des requtes HTTP.
Nous allons tudier un exemple dapplication de console nomme httpget qui illustre
comment tlcharger un fichier en utilisant le protocole HTTP. Il est trs similaire lexemple
ftpget de la section prcdente, la fois en matire de fonctionnalit et dimplmentation.
Nous ne prsenterons donc pas le fichier den-tte.
HttpGet::HttpGet(QObject *parent)
: QObject(parent)
{
connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
}

Qt 4 Livre Page 340 Jeudi, 7. dcembre 2006 12:14 12

340

Qt4 et C++ : Programmation dinterfaces GUI

Dans le constructeur, nous connectons le signal done(bool) de lobjet QHttp au slot priv
httpDone(bool).
bool HttpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
cerr << "Error: Invalid URL" << endl;
return false;
}
if (url.scheme()!= "http") {
cerr << "Error: URL must start with http:" << endl;
return false;
}
if (url.path().isEmpty()) {
cerr << "Error: URL has no path" << endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "httpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Error: Cannot open " << qPrintable(file.fileName())
<< " for writing: " << qPrintable(file.errorString())
<< endl;
return false;
}
http.setHost(url.host(), url.port(80));
http.get(url.path(), &file);
http.close();
return true;
}

La fonction getFile() effectue le mme type de contrle derreur que la fonction FtpGet::
getFile() prsente prcdemment et utilise la mme approche pour attribuer au fichier un
nom local. Lors dune rcupration depuis un site Web, aucun nom de connexion nest ncessaire. Nous dfinissons simplement lhte et le port (en utilisant le port HTTP 80 par dfaut sil
nest pas spcifi dans lURL) et tlchargeons les donnes dans le fichier, puisque le
deuxime argument de QHttp::get() spcifie le priphrique dE/S.
Les requtes HTTP sont places en file dattente et excutes de faon asynchrone dans la
boucle dvnement de Qt. Lachvement des requtes est indiqu par le signal done(bool)
de QHttp, que nous avons connect httpDone(bool) dans le constructeur.
void HttpGet::httpDone(bool error)
{

Qt 4 Livre Page 341 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

341

if (error) {
cerr << "Error: " << qPrintable(http.errorString()) << endl;
} else {
cerr << "File downloaded as " << qPrintable(file.fileName())
<< endl;
}
file.close();
emit done();
}

Une fois les requtes HTTP termines, nous fermons le fichier, en avertissant lutilisateur si
une erreur sest produite.
La fonction main() est trs similaire celle utilise par ftpget:
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
if (args.count()!= 2) {
cerr << "Usage: httpget url" << endl
<< "Example:" << endl
<< " httpget http://doc.trolltech.com/qq/index.html"
<< endl;
return 1;
}
HttpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}

La classe QHttp fournit de nombreuses oprations, dont setHost(), get(), post() et


head(). Si un site requiert une authentification, setUser() sera utilis pour fournir un nom
dutilisateur et un mot de passe. QHttp peut utiliser un socket transmis par le programmeur au
lieu de son QTcpSocket interne, ce qui autorise lemploi dun QtSslSocket scuris, fourni
en tant que Qt Solution par Trolltech.
Pour envoyer une liste de paires "nom=valeur" un script CGI, nous faisons appel post():
http.setHost("www.example.com");
http.post("/cgi/somescript.py", "x=200&y=320", &file);

Nous pouvons transmettre les donnes soit sous la forme dune chane de 8 octets, soit en
transmettant un QIODevice ouvert, tel quun QFile. Pour plus de contrle, il est possible de
recourir la fonction request(), qui accepte des donnes et un en-tte HTTP arbitraire.

Qt 4 Livre Page 342 Jeudi, 7. dcembre 2006 12:14 12

342

Qt4 et C++ : Programmation dinterfaces GUI

Par exemple :
QHttpRequestHeader header("POST", "/search.html");
header.setValue("Host", "www.trolltech.com");
header.setContentType("application/x-www-form-urlencoded");
http.setHost("www.trolltech.com");
http.request(header, "qt-interest=on&search=opengl");

QHttp met le signal requestStarted(int) quand il commence excuter une requte, puis
le signal requestFinished(int, bool) une fois la commande termine. Le paramtre int
est le numro dID qui identifie une requte. Si nous nous intressons au sort des requtes individuelles, nous pouvons stocker les numros dID lors de la programmation de ces dernires.
Le suivi de ces identifiants nous permet de fournir un rapport dtaill lutilisateur.
Dans la plupart des applications, nous souhaitons simplement savoir si la squence de requtes
dans son ensemble sest termine avec succs ou non. Dans ce cas, nous tablissons une
connexion au signal done() , qui est mis lorsque la file dattente de la requte est vide.
Quand une erreur se produit, la file dattente de la requte est vide automatiquement. Si nous
programmons de nouvelles requtes au moyen de lobjet QHttp aprs que lerreur sest
produite, elles sont places en file dattente et envoyes.
Comme QFtp, QHttp fournit un signal readyRead() ainsi que les fonctions read() et
readAll(), dont lemploi vite la spcification dun priphrique dE/S.

Programmer les applications client/serveur TCP


Les classes QTcpSocket et QTcpServer peuvent tre utilises pour implmenter des serveurs
et des clients TCP. TCP est un protocole de transport sur lequel sont bass la plupart des protocoles Internet de niveau application, y compris FTP et HTTP. En outre, il est susceptible dtre
utilis pour les protocoles personnaliss.
TCP est un protocole orient flux. Pour les applications, les donnes apparaissent sous la forme
dun long flux, plutt que sous la forme dun gros fichier plat. Les protocoles de haut niveau
bass sur TCP sont gnralement orients ligne ou bloc :

Les protocoles orients ligne transfrent les donnes sous la forme de lignes de texte,
chacune tant termine par un retour la ligne.

Les protocoles orients bloc transfrent les donne sous la forme de blocs de donnes
binaires. Chaque bloc comprend un champ de taille suivi de la quantit de donnes spcifie.

QTcpSocket hrite de QIODevice par le biais de QAbstractSocket. Il peut donc tre lu et


crit au moyen dun QDataStream ou dun QTextStream. Une diffrence notable entre la
lecture de donnes partir dun rseau et celle effectue depuis un fichier est que nous devons
veiller avoir reu suffisamment de donnes avant dutiliser loprateur >>. Dans le cas
contraire, nous obtenons un comportement alatoire.

Qt 4 Livre Page 343 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

343

Dans cette section, nous allons examiner le code dun client et dun serveur qui utilisent un
protocole personnalis orient bloc. Le client se nomme Trip Planner et permet aux utilisateurs
de planifier leur prochain voyage ferroviaire. Le serveur se nomme Trip Server et fournit les
informations concernant le voyage au client. Nous allons commencer par crire le client Trip
Planner.
Trip Planner fournit les champs From, To, Date et Approximate Time ainsi que deux boutons
doption indiquant si lheure approximative est celle de dpart ou darrive. Lorsque lutilisateur clique sur Search, lapplication expdie une requte au serveur, qui renvoie une liste des
trajets correspondant aux critres de lutilisateur. La liste est prsente sous la forme dun
QTableWidget dans la fentre Trip Planner. Le bas de la fentre est occup par un QProgressBar ainsi que par un QLabel qui affiche le statut de la dernire opration. (Voir Figure 14.1)
Figure 14.1
Lapplication
Trip Planner

Linterface utilisateur de Trip Planner a t cre au moyen de Qt Designer dans un fichier


nomm tripplanner.ui. Ici, nous allons nous concentrer sur le code source de la sousclasse QDialog qui implmente la fonctionnalit de lapplication :
#include "ui_tripplanner.h"
class TripPlanner: public QDialog, public Ui::TripPlanner
{
Q_OBJECT
public:
TripPlanner(QWidget *parent = 0);
private
void
void
void
void
void
void

slots:
connectToServer();
sendRequest();
updateTableWidget();
stopSearch();
connectionClosedByServer();
error();

Qt 4 Livre Page 344 Jeudi, 7. dcembre 2006 12:14 12

344

Qt4 et C++ : Programmation dinterfaces GUI

private:
void closeConnection();
QTcpSocket tcpSocket;
quint16 nextBlockSize;
};

La classe TripPlanner hrite de Ui::TripPlanner (qui est gnr par le uic de tripplanner.ui) en plus de QDialog. La variable membre tcpSocket encapsule la connexion TCP.
La variable nextBlockSize est utilise lors de lanalyse des blocs reus du serveur.
TripPlanner::TripPlanner(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
QDateTime dateTime = QDateTime::currentDateTime();
dateEdit->setDate(dateTime.date());
timeEdit->setTime(QTime(dateTime.time().hour(), 0));
progressBar->hide();
progressBar->setSizePolicy(QSizePolicy::Preferred,
QSizePolicy::Ignored);
tableWidget->verticalHeader()->hide();
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
connect(searchButton, SIGNAL(clicked()),
this, SLOT(connectToServer()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(stopSearch()));
connect(&tcpSocket, SIGNAL(connected()), this, SLOT(sendRequest()));
connect(&tcpSocket, SIGNAL(disconnected()),
this, SLOT(connectionClosedByServer()));
connect(&tcpSocket, SIGNAL(readyRead()),
this, SLOT(updateTableWidget()));
connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(error()));
}

Dans le constructeur, nous initialisons les diteurs de date et dheure en fonction de la date et
de lheure courantes. Nous masquons galement la barre de progression, car nous souhaitons
ne lafficher que lorsquune connexion est active. Dans Qt Designer, les proprits minimum et
maximum de la barre de progression sont toutes deux dfinies en 0, ce qui indique au QProgressBar de se comporter comme un indicateur dactivit au lieu dune barre de progression
standard base sur les pourcentages.
En outre, dans le constructeur, nous connectons les signaux connected(), disconnected(),
readyRead() et error(QAbstractSocket::SocketError) de QTcpSocket des slots
privs.

Qt 4 Livre Page 345 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

345

void TripPlanner::connectToServer()
{
tcpSocket.connectToHost("tripserver.zugbahn.de", 6178);
tableWidget->setRowCount(0);
searchButton->setEnabled(false);
stopButton->setEnabled(true);
statusLabel->setText(tr("Connecting to server..."));
progressBar->show();
nextBlockSize = 0;
}

Le slot connectToServer() est excut lorsque lutilisateur clique sur Search pour lancer
une recherche. Nous appelons connectToHost() sur lobjet QTcpSocket pour tablir une
connexion avec le serveur, que nous supposons tre accessible par le biais du port 6178 sur
lhte fictif tripserver.zugbahn.de. (Si vous souhaitez tester lexemple sur votre propre
machine, remplacez le nom de lhte par QHostAddress::LocalHost.) Lappel de connectToHost() est asynchrone. Il rend toujours le contrle immdiatement. La connexion est gnralement tablie ultrieurement. Lobjet QTcpSocket met le signal connected() lorsque la
connexion fonctionne ou error(QAbstractSocket::SocketError) en cas dchec.
Nous mettons ensuite jour linterface utilisateur, en particulier en affichant la barre de
progression.
Nous dfinissons enfin la variable nextBlockSize en 0. Cette variable stocke la longueur du
prochain bloc en provenance du serveur. Nous avons choisi dutiliser la valeur 0 pour indiquer
que nous ne connaissons pas encore la taille du bloc venir.
void TripPlanner::sendRequest()
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
out << quint16(0) << quint8(S) << fromComboBox->currentText()
<< toComboBox->currentText() << dateEdit->date()
<< timeEdit->time();
if (departureRadioButton->isChecked()) {
out << quint8(D);
} else {
out << quint8(A);
}
out.device()->seek(0);
out << quint16(block.size() - sizeof(quint16));
tcpSocket.write(block);
statusLabel->setText(tr("Sending request..."));
}

Qt 4 Livre Page 346 Jeudi, 7. dcembre 2006 12:14 12

346

Qt4 et C++ : Programmation dinterfaces GUI

Le slot sendRequest() est excut lorsque lobjet QTcpSocket met le signal connected(),
indiquant quune connexion a t tablie. La tche du slot consiste gnrer une requte
destination du serveur, avec toutes les informations entres par lutilisateur.
La requte est un bloc binaire au format suivant :
quint16

Taille du bloc en octets (excluant ce champ)

quint8

Type de requte (toujours S)

QString

Ville de dpart

QString

Ville darrive

QDate

Date du voyage

QTime

Horaire approximatif du voyage

quint8

Heure de dpart (D) ou darrive (A)

Dans un premier temps, nous crivons les donnes dans un QByteArray nomm block. Nous
ne pouvons pas crire les donnes directement dans le QTcpSocket car nous ne connaissons
pas la taille du bloc avant dy avoir plac toutes les donnes.
Nous avons initialement indiqu 0 pour la taille du bloc, suivi du reste des donnes. Nous
appelons ensuite seek(0) sur le priphrique dE/S (un QBuffer cr par QDataStream
larrire-plan) pour revenir au dbut du tableau doctets, et nous remplaons le 0 initial par la
taille des donnes du bloc. Elle est calcule en prenant la taille du bloc et en soustrayant
sizeof(quint16) (cest--dire 2) pour exclure le champ de taille du compte doctets. Nous
appelons alors write() sur le QTcpSocket pour envoyer le bloc au serveur.
void TripPlanner::updateTableWidget()
{
QDataStream in(&tcpSocket);
in.setVersion(QDataStream::Qt_4_1);
forever {
int row = tableWidget->rowCount();
if (nextBlockSize == 0) {
if (tcpSocket.bytesAvailable() < sizeof(quint16))
break;
in >> nextBlockSize;
}
if (nextBlockSize == 0xFFFF) {
closeConnection();
statusLabel->setText(tr("Found %1 trip(s)").arg(row));
break;
}

Qt 4 Livre Page 347 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

347

if (tcpSocket.bytesAvailable() < nextBlockSize)


break;
QDate date;
QTime departureTime;
QTime arrivalTime;
quint16 duration;
quint8 changes;
QString trainType;
in >> date >> departureTime >> duration >> changes >> trainType;
arrivalTime = departureTime.addSecs(duration * 60);
tableWidget->setRowCount(row + 1);
QStringList fields;
fields << date.toString(Qt::LocalDate)
<< departureTime.toString(tr("hh:mm"))
<< arrivalTime.toString(tr("hh:mm"))
<< tr("%1 hr %2 min").arg(duration / 60)
.arg(duration % 60)
<< QString::number(changes)
<< trainType;
for (int i = 0; i < fields.count(); ++i)
tableWidget->setItem(row, i,
new QTableWidgetItem(fields[i]));
nextBlockSize = 0;
}
}

Le slot updateTableWidget() est connect au signal readyRead() de QTcpSocket, qui est


mis ds que le QTcpSocket reoit de nouvelles donnes en provenance du serveur. Le serveur
nous envoie une liste des trajets possibles correspondant aux critres de lutilisateur. Chaque
trajet est expdi sous la forme dun bloc unique. La boucle forever est ncessaire dans la
mesure o nous ne recevons pas obligatoirement un seul bloc de donnes la fois de la part du
serveur. Nous pouvons recevoir un bloc entier, une partie de celui-ci, un bloc et demi ou encore
tous les blocs la fois. (Voir Figure 14.2)
Figure 14.2
Les blocs de Trip Server

51 octets

51

data

48 octets

48

data

53 octets

53

data

0xFFFF

Comment fonctionne la boucle forever? Si la variable nextBlockSize a pour valeur 0,


nous navons pas lu la taille du bloc suivant. Nous essayons de la lire (en prenant en compte le
fait que deux octets au moins sont disponibles pour la lecture). Le serveur utilise une valeur de
taille de 0xFFFF pour indiquer quil ne reste plus de donnes recevoir. Ainsi, si nous lisons
cette valeur, nous savons que nous avons atteint la fin.

Qt 4 Livre Page 348 Jeudi, 7. dcembre 2006 12:14 12

348

Qt4 et C++ : Programmation dinterfaces GUI

Si la taille du bloc nest pas de 0xFFFF, nous essayons de lire le bloc suivant. Dans un premier
temps, nous essayons de dterminer si des octets de taille de bloc sont disponibles la lecture.
Si tel nest pas le cas, nous interrompons cette action un instant. Le signal readyRead() sera
de nouveau mis lorsque des donnes supplmentaires seront disponibles. Nous procderons
alors de nouvelles tentatives.
Lorsque nous sommes certains que le bloc entier est arriv, nous pouvons utiliser loprateur
>> en toute scurit sur le QDataStream pour extraire les informations relatives au voyage, et
nous crons un QTableWidgetItems avec ces informations. Le format dun bloc reu du
serveur est le suivant :
quint16

Taille du bloc en octets (en excluant son champ)

QDate

Date de dpart

QTime

Heure de dpart

quint16

Dure (en minutes)

quint8

Nombre de changements

QString

Type de train

A la fin, nous rinitialisons la variable nextBlockSize en 0 pour indiquer que la taille du bloc
suivant est inconnue et doit tre lue.
void TripPlanner::closeConnection()
{
tcpSocket.close();
searchButton->setEnabled(true);
stopButton->setEnabled(false);
progressBar->hide();
}

La fonction prive closeConnection() ferme la connexion avec le serveur TCP et met jour
linterface utilisateur. Elle est appele depuis updateTableWidget() lorsque 0xFFFF est lu
ainsi que depuis plusieurs autres slots sur lesquels nous reviendrons dans un instant.
void TripPlanner::stopSearch()
{
statusLabel->setText(tr("Search stopped"));
closeConnection();
}

Le slot stopSearch() est connect au signal clicked() du bouton Stop. Il appelle simplement
closeConnection().
void TripPlanner::connectionClosedByServer()
{

Qt 4 Livre Page 349 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

349

if (nextBlockSize!= 0xFFFF)
statusLabel->setText(tr("Error: Connection closed by server"));
closeConnection();
}

Le slot connectionClosedByServer() est connect au signal disconnected() du QTcpSocket. Si le serveur ferme la connexion sans que nous ayons encore reu le marqueur
0xFFFF de fin de donnes, nous indiquons lutilisateur quune erreur sest produite. Nous
appelons normalement closeConnection() pour mettre jour linterface utilisateur.
void TripPlanner::error()
{
statusLabel->setText(tcpSocket.errorString());
closeConnection();
}

Le slot error() est connect au signal error(QAbstractSocket::SocketError) du


QTcpSocket. Nous ignorons le code derreur et nous utilisons QTcpSocket::errorString(), qui retourne un message en texte clair concernant la dernire erreur dtecte.
Tout ceci concerne la classe TripPlanner. Comme nous pouvons nous y attendre, la fonction
main() de lapplication TripPlanner est la suivante :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TripPlanner tripPlanner;
tripPlanner.show();
return app.exec();
}

Implmentons maintenant le serveur. Ce dernier est compos de deux classes : TripServer et


ClientSocket. La classe TripServer hrite de QTcpServer, une classe qui nous permet
daccepter des connexions TCP entrantes. ClientSocket rimplmente QTcpSocket et gre
une connexion unique. Il existe chaque instant autant dobjets ClientSocket en mmoire
que de clients servis.
class TripServer: public QTcpServer
{
Q_OBJECT
public:
TripServer(QObject *parent = 0);
private:
void incomingConnection(int socketId);
};

La classe TripServer rimplmente la fonction incomingConnection() depuis QTcpServer.


Cette fonction est appele ds quun client tente dtablir une connexion au port cout par le
serveur.

Qt 4 Livre Page 350 Jeudi, 7. dcembre 2006 12:14 12

350

Qt4 et C++ : Programmation dinterfaces GUI

TripServer::TripServer(QObject *parent)
: QTcpServer(parent)
{
}

Le constructeur TripServer est simple.


void TripServer::incomingConnection(int socketId)
{
ClientSocket *socket = new ClientSocket(this);
socket->setSocketDescriptor(socketId);
}

Dans incomingConnection(), nous crons un objet ClientSocket qui est un enfant de


lobjet TripServer, et nous attribuons son descripteur de socket le nombre qui nous a t
fourni. Lobjet ClientSocket se supprimera automatiquement de lui-mme une fois la
connexion termine.
class ClientSocket: public QTcpSocket
{
Q_OBJECT
public:
ClientSocket(QObject *parent = 0);
private slots:
void readClient();
private:
void generateRandomTrip(const QString &from, const QString &to,
const QDate &date, const QTime &time);
quint16 nextBlockSize;
};

La classe ClientSocket hrite de QTcpSocket et encapsule ltat dun client unique.


ClientSocket::ClientSocket(QObject *parent)
: QTcpSocket(parent)
{
connect(this, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(this, SIGNAL(disconnected()), this, SLOT(deleteLater()));
nextBlockSize = 0;
}

Dans le constructeur, nous tablissons les connexions signal/slot ncessaires, et nous dfinissons la variable nextBlockSize en 0, indiquant ainsi que nous ne connaissons pas encore la
taille du bloc envoy par le client.
Le signal disconnected() est connect deleteLater(), une fonction hrite de QObject
qui supprime lobjet lorsque le contrle retourne la boucle dvnement de Qt. De cette
faon, lobjet ClientSocket est supprim lorsque la connexion du socket est ferme.

Qt 4 Livre Page 351 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

351

void ClientSocket::readClient()
{
QDataStream in(this);
in.setVersion(QDataStream::Qt_4_1);
if (nextBlockSize == 0) {
if (bytesAvailable() < sizeof(quint16))
return;
in >> nextBlockSize;
}
if (bytesAvailable() < nextBlockSize)
return;
quint8 requestType;
QString from;
QString to;
QDate date;
QTime time;
quint8 flag;
in >> requestType;
if (requestType == S) {
in >> from >> to >> date >> time >> flag;
srand(from.length() * 3600 + to.length() * 60 + time.hour());
int numTrips = rand() % 8;
for (int i = 0; i < numTrips; ++i)
generateRandomTrip(from, to, date, time);
QDataStream out(this);
out << quint16(0xFFFF);
}
close();
}

Le slot readClient() est connect au signal readyRead() du QTcpSocket. Si nextBlockSize est dfini en 0, nous commenons par lire la taille du bloc. Dans le cas contraire, nous
lavons dj lue. Nous poursuivons donc en vrifiant si un bloc entier est arriv, et nous le
lisons dune seule traite. Nous utilisons QDataStream directement sur le QTcpSocket (lobjet
this) et lisons les champs au moyen de loprateur >>.
Une fois la requte du client lue, nous sommes prts gnrer une rponse. Dans le cas dune
application relle, nous rechercherions les informations dans une base de donnes dhoraires et
tenterions de trouver les trajets correspondants. Ici, nous nous contenterons dune fonction
nomme generateRandomTrip() qui gnrera un trajet alatoire. Nous appelons la fonction
un nombre quelconque de fois, puis nous envoyons 0xFFFF pour signaler la fin des donnes.
Enfin, nous fermons la connexion.
void ClientSocket::generateRandomTrip(const QString & /* from */,
const QString & /* to */, const QDate &date, const QTime &time)

Qt 4 Livre Page 352 Jeudi, 7. dcembre 2006 12:14 12

352

Qt4 et C++ : Programmation dinterfaces GUI

{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
quint16 duration = rand() % 200;
out << quint16(0) << date << time << duration << quint8(1)
<< QString("InterCity");
out.device()->seek(0);
out << quint16(block.size() - sizeof(quint16));
write(block);
}

La fonction generateRandomTrip() illustre comment envoyer un bloc de donnes par le


biais dune connexion TCP. Ce processus est trs similaire celui suivi sur le client, dans la
fonction sendRequest(). Une fois encore, nous crivons le bloc dans un QByteArray en
excutant write(), de faon pouvoir dterminer sa taille avant de lenvoyer.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TripServer server;
if (!server.listen(QHostAddress::Any, 6178)) {
cerr << "Failed to bind to port" << endl;
return 1;
}
QPushButton quitButton(QObject::tr("&Quit"));
quitButton.setWindowTitle(QObject::tr("Trip Server"));
QObject::connect(&quitButton, SIGNAL(clicked()),
&app, SLOT(quit()));
quitButton.show();
return app.exec();
}

Dans main(), nous crons un objet TripServer et un QPushButton qui permet lutilisateur
darrter le serveur. Nous lanons le serveur en appelant QTcpSocket::listen(), qui reoit
ladresse IP et le numro de port sur lequel nous souhaitons accepter les connexions. Ladresse
spciale 0.0.0.0 (QHostAddress::Any) signifie toute interface IP prsente sur lhte local.
Ceci termine notre exemple client/serveur. Ici, nous avons utilis un protocole orient bloc qui
nous permet de faire appel QDataStream pour la lecture et lcriture. Si nous souhaitions
utiliser un protocole orient ligne, lapproche la plus simple serait de recourir aux fonctions
canReadLine() et readLine() de QTcpSocket dans un slot connect au signal readyRead():
QStringList lines;
while (tcpSocket.canReadLine())
lines.append(tcpSocket.readLine());

Qt 4 Livre Page 353 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

353

Nous traiterions alors chaque ligne lue. Comme pour lenvoi des donnes, ceci pourrait tre
effectu en utilisant un QTextStream sur le QTcpSocket.
Limplmentation serveur que nous avons utilise nest pas adapte une situation o les
connexions sont nombreuses. En effet, lorsque nous traitons une requte, nous ne grons pas
les autres connexions. Une approche plus souple consisterait dmarrer un nouveau thread pour
chaque connexion. Lexemple Threaded Fortune Server situ dans le rpertoire examples/
network/threadedfortuneserver illustre ce procd.

Envoi et rception de datagrammes UDP


La classe QUdpSocket peut tre utilise pour envoyer et recevoir des datagrammes UDP. UDP
est un protocole orient datagramme non fiable. Certains protocoles de niveau application utilisent UDP car il est plus lger que TCP. Avec UDP, les donnes sont envoyes sous forme de
paquets (datagrammes) dun hte un autre. Il nexiste pas de concept de connexion, et si un
paquet UDP nest pas remis avec succs, aucune erreur nest signale lexpditeur. (Voir
Figure 14.3)
Figure 14.3
Lapplication
Weather Station

Les exemples Weather Balloon et Weather Station vous montreront comment utiliser UDP
partir dune application Qt. Lapplication Weather Balloon reproduit un ballon mto qui
envoie un datagramme UDP (au moyen dune connexion sans fil) contenant les conditions
atmosphriques courantes toutes les deux secondes. Lapplication Weather Station reoit
ces datagrammes et les affiche lcran. Nous allons commencer par le code du Weather
Ballon.
class WeatherBalloon: public QPushButton
{
Q_OBJECT
public:
WeatherBalloon(QWidget *parent = 0);
double temperature() const;
double humidity() const;

Qt 4 Livre Page 354 Jeudi, 7. dcembre 2006 12:14 12

354

Qt4 et C++ : Programmation dinterfaces GUI

double altitude() const;


private slots:
void sendDatagram();
private:
QUdpSocket udpSocket;
QTimer timer;
};

La classe WeatherBalloon hrite de QPushButton. Elle utilise sa variable prive QUdpSocket


pour communiquer avec la station mto (Weather Station).
WeatherBalloon::WeatherBalloon(QWidget *parent)
: QPushButton(tr("Quit"), parent)
{
connect(this, SIGNAL(clicked()), this, SLOT(close()));
connect(&timer, SIGNAL(timeout()), this, SLOT(sendDatagram()));
timer.start(2 * 1000);
setWindowTitle(tr("Weather Balloon"));
}

Dans le constructeur, nous lanons un QTimer pour invoquer sendDatagram() toutes les
deux secondes.
void WeatherBalloon::sendDatagram()
{
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
out << QDateTime::currentDateTime() << temperature() << humidity()
<< altitude();
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, 5824);
}

Dans sendDatagram(), nous gnrons et envoyons un datagramme contenant la date, lheure,


la temprature, lhumidit et laltitude :
QDateTime

Date et heure de mesure

double

Temprature (en C)

double

Humidit (en %)

double

Altitude (in mtres)

Qt 4 Livre Page 355 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

355

Le datagramme est expdi au moyen de QUdpSocket::writeDatagram(). Les deuxime


et troisime arguments de writeDatagram() sont ladresse IP et le numro de port de
lhomologue (la Weather Station). Nous supposons ici que la Weather Station sexcute sur
la mme machine que le Weather Balloon. Nous utilisons donc ladresse IP 127.0.0.1 (QHostAddress::LocalHost), une adresse spciale qui dsigne lhte local.
Contrairement aux sous-classes de QAbstractSocket, QUdpSocket accepte uniquement les
adresses dhte, mais pas les noms. Si nous devions convertir un nom dhte en son adresse IP,
deux solutions soffriraient nous : si nous nous sommes prpars un blocage pendant la
recherche, nous pouvons faire appel la fonction statique QHostInfo::fromName(). Dans le cas
contraire, nous employons la fonction statique QHostInfo::lookupHost(), qui rend le
contrle immdiatement et, une fois la recherche termine, appelle le slot qui lui est transmis
avec un objet QHostInfo contenant les adresses correspondantes.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherBalloon balloon;
balloon.show();
return app.exec();
}

La fonction main() cre simplement un objet WeatherBalloon, qui sert la fois dhomologue UDP et de QPushButton lcran. En cliquant sur le QPushButton, lutilisateur quitte
lapplication.
Revenons maintenant au code source du client Weather Station.
class WeatherStation: public QDialog
{
Q_OBJECT
public:
WeatherStation(QWidget *parent = 0);
private slots:
void processPendingDatagrams();
private:
QUdpSocket udpSocket;
QLabel *dateLabel;
QLabel *timeLabel;

QLineEdit *altitudeLineEdit;
};

La classe WeatherStation hrite de QDialog. Elle coute un port UDP particulier, analyse
tous les datagrammes entrants (en provenance du Weather Balloon) et affiche leur contenu
dans cinq QLineEdits en lecture seulement. La seule variable prive prsentant un intrt ici

Qt 4 Livre Page 356 Jeudi, 7. dcembre 2006 12:14 12

356

Qt4 et C++ : Programmation dinterfaces GUI

est la variable udpSocket du type QUdpSocket, laquelle nous allons faire appel pour recevoir les
datagrammes.
WeatherStation::WeatherStation(QWidget *parent)
: QDialog(parent)
{
udpSocket.bind(5824);
connect(&udpSocket, SIGNAL(readyRead()),
this, SLOT(processPendingDatagrams()));

Dans le constructeur, nous commenons par tablir une liaison entre le QUdpSocket et le port
auquel le Weather Balloon transmet ses donnes. Comme nous navons pas spcifi dadresse
hte, le socket accepte les datagrammes envoys nimporte quelle adresse IP appartenant la
machine sur laquelle sexcute la Weather Station. Puis nous connectons le signal readyRead() du socket au processPendingDatagrams() priv qui extrait les donnes et les affiche.
void WeatherStation::processPendingDatagrams()
{
QByteArray datagram;
do {
datagram.resize(udpSocket.pendingDatagramSize());
udpSocket.readDatagram(datagram.data(), datagram.size());
} while (udpSocket.hasPendingDatagrams());
QDateTime dateTime;
double temperature;
double humidity;
double altitude;
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_1);
in >> dateTime >> temperature >> humidity >> altitude;
dateLineEdit->setText(dateTime.date().toString());
timeLineEdit->setText(dateTime.time().toString());
temperatureLineEdit->setText(tr("%1 C").arg(temperature));
humidityLineEdit->setText(tr("%1%").arg(humidity));
altitudeLineEdit->setText(tr("%1 m").arg(altitude));
}

Le slot processPendingDatagrams() est appel quand un datagramme est arriv. QUdpSocket


place les datagrammes entrants en file dattente et nous permet dy accder un par un. Normalement, il ne devrait y avoir quun seul datagramme, mais nous ne pouvons pas exclure la
possibilit que lexpditeur en envoie plusieurs la fois avant que le signal readyRead() ne
soit mis. Dans ce cas, nous les ignorons tous, lexception du dernier. Les prcdents vhiculent
en effet des informations obsoltes.

Qt 4 Livre Page 357 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 14

Gestion de rseau

357

La fonction pendingDatagramSize() retourne la taille du premier datagramme en attente.


Du point de vue de lapplication, les datagrammes sont toujours envoys et reus sous la forme
dune unit de donnes unique. Ainsi, si des octets quelconques sont disponibles, le datagramme entier peut tre lu. Lappel de readDatagram() copie le contenu du premier datagramme
en attente dans la mmoire tampon char* spcifie (en troquant les donnes si la capacit de
cette mmoire nest pas suffisante) et passe au datagramme suivant en attente. Une fois tous les
datagrammes lus, nous dcomposons le dernier (celui avec les mesures atmosphriques les plus
rcentes) et alimentons le QLineEdits avec les nouvelles donnes.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherStation station;
station.show();
return app.exec();
}

Enfin, nous crons et affichons la WeatherStation dans main().


Nous en avons maintenant termin avec notre metteur et destinataire UDP. Les applications
sont aussi simples que possible, puisque le Weather Balloon envoie des datagrammes la
Weather Station qui les reoit. Dans la plupart des cas du monde rel, les deux applications
auraient besoin deffectuer des oprations de lecture et dcriture sur leur socket.
Un numro de port et une adresse hte peuvent tre transmis aux fonctions QUdpSocket::writeDatagram(), de sorte que le QUdpSocket puisse raliser une lecture depuis lhte et le port
auquel il est li avec bind(), et effectuer une opration de lecture vers un autre hte ou port.

Qt 4 Livre Page 358 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 359 Jeudi, 7. dcembre 2006 12:14 12

15
XML
Au sommaire de ce chapitre
Lire du code XML avec SAX
Lire du code XML avec DOM
Ecrire du code XML

XML (Extensible Markup Language) est un format de fichier texte polyvalent, populaire pour lchange et le stockage des donnes. Qt fournit deux API distinctes faisant
partie du module QtXml pour la lecture de documents XML :
SAX (Simple API for XML) rapporte des "vnements danalyse" directement
lapplication par le biais de fonctions virtuelles.
DOM (Document Object Model) convertit une documentation XML en une structure
arborescente, que lapplication peut parcourir.
Trois facteurs principaux sont prendre en compte lors du choix entre DOM et SAX
pour une application particulire. SAX est de niveau infrieur et gnralement plus
rapide, ce qui le rend particulirement appropri pour des tches simples (telles que la
recherche de toutes les occurrences dune balise donne dans un document XML) ou

Qt 4 Livre Page 360 Jeudi, 7. dcembre 2006 12:14 12

360

Qt4 et C++ : Programmation dinterfaces GUI

pour la lecture de fichiers de trs grande taille pour lesquels la mmoire sera insuffisante. Mais
pour de nombreuses applications, la commodit de DOM prime sur la vitesse potentielle et les
avantages offerts par SAX concernant la mmoire.
Pour crire des fichiers XML, deux options sont disponibles : nous pouvons gnrer le code
XML manuellement, ou reprsenter les donnes sous la forme dun arbre DOM en mmoire et
demander ce dernier de scrire par lui-mme dans un fichier.

Lire du code XML avec SAX


SAX est une API standard de domaine public destine la lecture de documents XML. Les
classes SAX de Qt sont modeles sur limplmentation SAX2 de Java, avec quelques diffrences
dans lattribution des noms afin de sadapter aux conventions de Qt. Pour plus dinformations
concernant SAX, reportez-vous ladresse http://www.saxproject.org/.
Qt fournit un analyseur XML bas sur SAX nomm QXmlSimpleReader. Cet analyseur
reconnat du code XML bien form et prend en charge les espaces de noms XML. Quand il
parcourt le document, il appelle les fonctions virtuelles des classes gestionnaires enregistres
pour signaler des vnements danalyse. (Ces "vnements danalyse" nont aucun rapport
avec les vnements Qt, tels que les vnements touche et souris.) Supposons, par exemple que
lanalyseur examine le document XML suivant :
<doc>
<quote>Ars longa vita brevis</quote>
</doc>

Il appelle les gestionnaires dvnements danalyse ci-aprs :


startDocument()
startElement("doc")
startElement("quote")
characters("Ars longa vita brevis")
endElement("quote")
endElement("doc")
endDocument()

Ces fonctions sont toutes dclares dans QXmlContentHandler. Pour des questions de simplicit, nous avons omis certains arguments de startElement() et endElement().

QXmlContentHandler est juste lune des nombreuses classes gestionnaires susceptible dtre
utilise avec QXmlSimpleReader. Les autres sont QXmlEntityResolver, QXmlDTDHandler,
QXmlErrorHandler, QXmlDeclHandler et QXmlLexicalHandler. Ces classes ne dclarent
que des fonctions purement virtuelles et fournissent des informations concernant les diffrents
types dvnements danalyse. Pour la plupart des applications, QXmlContentHandler et
QXmlErrorHandler sont les deux seules ncessaires.

Qt 4 Livre Page 361 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

361

Pour des raisons de commodit, Qt fournit aussi QXmlDefaultHandler, une classe qui hrite
de toutes les classes gestionnaires et qui fournit des implmentations simples de toutes les
fonctions. Une telle conception, avec de nombreuses classes gestionnaires abstraites et une
sous-classe simple, est inhabituelle pour Qt. Elle a t adopte pour se conformer limplmentation de modle Java.
Nous allons tudier un exemple qui illustre comment utiliser QXmlSimpleReader et QXmlDefaultHandler pour analyser un fichier XML ad hoc et afficher son contenu dans un
QTreeWidget. La sous-classe QXmlDefaultHandler se nomme SaxHandler, et le format
gr par celle-ci est celui dun index de livre, avec les entres et les sous-entres.
Figure 15.1
Arbre dhritage
pour SaxHandler

QXmlContentHandler

QXmlDTDHandler

QXmlErrorHandler

QXmlLexicalHandler

QXmlEntityResolver

QXmlDeclHandler

QXmlDefaultHandler
SaxHandler

Voici le fichier dindex qui est affich dans le QTreeWidget en Figure 15.2 :
<?xml version="1.0"?>
<bookindex>
<entry term="sidebearings">
<page>10</page>
<page>34-35</page>
<page>307-308</page>
</entry>
<entry term="subtraction">
<entry term="of pictures">
<page>115</page>
<page>244</page>
</entry>
<entry term="of vectors">
<page>9</page>
</entry>
</entry>
</bookindex>

Figure 15.2
Un fichier dindex affich
dans un QTreeWidget

Qt 4 Livre Page 362 Jeudi, 7. dcembre 2006 12:14 12

362

Qt4 et C++ : Programmation dinterfaces GUI

La premire tape dans limplmentation de lanalyseur consiste dfinir la sous-classe QXmlDefaultHandler:


class SaxHandler: public QXmlDefaultHandler
{
public:
SaxHandler(QTreeWidget *tree);
bool startElement(const QString &namespaceURI,
const QString &localName,
const QString &qName,
const QXmlAttributes &attributes);
bool endElement(const QString &namespaceURI,
const QString &localName,
const QString &qName);
bool characters(const QString &str);
bool fatalError(const QXmlParseException &exception);
private:
QTreeWidget *treeWidget;
QTreeWidgetItem *currentItem;
QString currentText;
};

La classe SaxHandler hrite de QXmlDefaultHandler et rimplmente quatre fonctions : startElement(), endElement(), characters() et fatalError(). Les trois
premires sont dclares dans QXmlContentHandler. La dernire est dclare dans QXmlErrorHandler.
SaxHandler::SaxHandler(QTreeWidget *tree)
{
treeWidget = tree;
currentItem = 0;
}

Le constructeur SaxHandler accepte le QTreeWidget que nous souhaitons remplir avec les
informations stockes dans le fichier XML.
bool SaxHandler::startElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName,
const QXmlAttributes &attributes)
{
if (qName == "entry") {
if (currentItem) {
currentItem = new QTreeWidgetItem(currentItem);
} else {
currentItem = new QTreeWidgetItem(treeWidget);
}

Qt 4 Livre Page 363 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

363

currentItem->setText(0, attributes.value("term"));
} else if (qName == "page") {
currentText.clear();
}
return true;
}

La fonction startElement() est appele ds que le lecteur rencontre une nouvelle balise
douverture. Le troisime paramtre est le nom de la balise (ou plus prcisment, son "nom
qualifi"). Le quatrime paramtre est la liste des attributs. Dans cet exemple, nous ignorons
les premier et deuxime paramtres. Ils sont utiles pour les fichiers XML qui utilisent le mcanisme despace de noms de XML, un sujet qui est trait en dtail dans la documentation de
rfrence.
Si la balise est <entry>, nous crons un nouvel lment QTreeWidget. Si elle est imbrique
dans une autre balise <entry>, la nouvelle balise dfinit une sous-entre dans lindex, et le
nouveau QTreeWidgetItem est cr en tant quenfant du QTreeWidgetItem qui reprsente
lentre principale. Dans le cas contraire, nous crons le QTreeWidgetItem avec llment
treeWidget en tant que parent, en faisant de celui-ci un lment de haut niveau. Nous appelons setText() pour dfinir le texte prsent en colonne 0 avec la valeur de lattribut term de
la balise <entry>.
Si la balise est <page>, nous dfinissons le currentText en une chane vide. Le currentText sert daccumulateur pour le texte situ entre les balises <page> et </page>.
Nous retournons enfin true pour demander SAX de poursuivre lanalyse du fichier. Si nous
souhaitions signaler les balises inconnues comme des erreurs, nous retournerions false dans
ces situations. Nous rimplmenterions galement errorString() partir de QXmlDefaultHandler pour retourner un message derreur appropri.
bool SaxHandler::characters(const QString &str)
{
currentText += str;
return true;
}

La fonction characters() est appele si des donnes caractres sont rencontres dans le
document XML. Nous accolons simplement les caractres la variable currentText.
bool SaxHandler::endElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName)
{
if (qName == "entry") {
currentItem = currentItem->parent();
} else if (qName == "page") {
if (currentItem) {
QString allPages = currentItem->text(1);

Qt 4 Livre Page 364 Jeudi, 7. dcembre 2006 12:14 12

364

Qt4 et C++ : Programmation dinterfaces GUI

if (!allPages.isEmpty())
allPages += ", ";
allPages += currentText;
currentItem->setText(1, allPages);
}
}
return true;
}

La fonction endElement() est appele quand le lecteur rencontre une balise de fermeture.
Comme pour startElement(), le troisime paramtre est le nom de la balise.
Si la balise est </entry>, nous mettons jour la variable prive currentItem de faon la
diriger vers le parent de QTreeWidgetItem en cours. De cette faon, la variable currentItem
reprend la valeur qui tait la sienne avant la lecture de la balise <entry> correspondante.
Si la balise est </page>, nous ajoutons le numro de page ou la plage de pages sous la forme
dune liste spare par des virgules au texte de llment courant de la colonne 1.
bool SaxHandler::fatalError(const QXmlParseException &exception)
{
QMessageBox::warning(0, QObject::tr("SAX Handler"),
QObject::tr("Parse error at line %1, column "
"%2:\n%3.")
.arg(exception.lineNumber())
.arg(exception.columnNumber())
.arg(exception.message()));
return false;
}

La fonction fatalError() est appele lorsque le lecteur ne parvient pas analyser le fichier
XML. Dans cette situation, nous affichons simplement une bote de message, en donnant le
numro de ligne, le numro de colonne et le texte derreur de lanalyseur.
Ceci termine limplmentation de la classe SaxHandler. Voyons maintenant comment
lutiliser :
bool parseFile(const QString &fileName)
{
QStringList labels;
labels << QObject::tr("Terms") << QObject::tr("Pages");
QTreeWidget *treeWidget = new QTreeWidget;
treeWidget->setHeaderLabels(labels);
treeWidget->setWindowTitle(QObject::tr("SAX Handler"));
treeWidget->show();

Qt 4 Livre Page 365 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

365

QFile file(fileName);
QXmlInputSource inputSource(&file);
QXmlSimpleReader reader;
SaxHandler handler(treeWidget);
reader.setContentHandler(&handler);
reader.setErrorHandler(&handler);
return reader.parse(inputSource);
}

Nous dfinissons un QTreeWidget avec deux colonnes. Puis nous crons un objet QFile pour
le fichier devant tre lu et un QXmlSimpleReader pour analyser le fichier. Il nest pas ncessaire douvrir le QFile par nous-mmes. QXmlInputSource sen charge automatiquement.
Nous crons enfin un objet SaxHandler, nous linstallons sur le lecteur la fois en tant que
gestionnaire de contenu et en tant que gestionnaire derreur, et nous appelons parse() sur le
lecteur pour effectuer lanalyse.
Au lieu de transmettre un simple objet de fichier la fonction parse(), nous transmettons un
QXmlInputSource. Cette classe ouvre le fichier qui lui est fourni, le lit (en prenant en considration tout codage de caractres spcifi dans la dclaration <?xml?>), et fournit une interface par le biais de laquelle lanalyseur lit le fichier.
Dans SaxHandler, nous rimplmentons uniquement les fonctions des classes QXmlContentHandler et QXmlErrorHandler. Si nous avions implment des fonctions dautres classes
gestionnaires, nous aurions galement d appeler leurs fonctions de rglage (set) sur le lecteur.
Pour lier lapplication la bibliothque QtXml, nous devons ajouter cette ligne dans le fichier
.pro:
QT

+= xml

Lire du code XML avec DOM


DOM est une API standard pour lanalyse de code XML dveloppe par le W3C (World Wide
Web Consortium). Qt fournit une implmentation DOM Niveau 2 destine la lecture, la
manipulation et lcriture de documents XML.
DOM prsente un fichier XML sous la forme dun arbre en mmoire. Nous pouvons parcourir
larbre DOM autant que ncessaire. Il nous est galement possible de le modifier et de le renregistrer sur le disque en tant que fichier XML.
Considrons le document XML suivant :
<doc>
<quote>Ars longa vita brevis</quote>
<translation>Art is long, life is short</translation>
</doc>

Qt 4 Livre Page 366 Jeudi, 7. dcembre 2006 12:14 12

366

Qt4 et C++ : Programmation dinterfaces GUI

Il correspond larbre DOM ci-aprs :


Document
Element (doc)
Element (quote)
Text (Ars longa vita brevis)
Element (translation)
Text (Art is long, life is short)

Larbre DOM contient des nuds de types diffrents. Par exemple, un nud Element correspond une balise douverture et sa balise de fermeture. Le matriau situ entre les balises
apparat sous la forme de nuds enfants de Element.
Dans Qt, les types de nud (comme toutes les autres classes en liaison avec DOM) possdent
un prfixe QDom. Ainsi, QDomElement reprsente un nud Element, et QDomText reprsente
un nud Text.
Chaque nud peut possder diffrents types de nuds enfants. Par exemple, un nud
Element peut contenir dautres nuds Element, ainsi que des nuds EntityReference,
Text, CDATASection, ProcessingInstruction et Comment. La Figure 15.3 prsente les
types de nuds enfants correspondant aux nuds parents. Ceux apparaissant en gris ne
peuvent pas avoir de nud enfant.
Document

Element

Element

Document
Type

Attr

Processing
Instruction

Comment

Entity
Reference

Document
Fragment

Element

Entity
Reference

Entity

Entity
Reference

Text

CDATA
Section

Processing
Instruction

Text

Comment

Figure 15.3
Relations parent/enfant entre les nuds DOM

Nous allons voir comment utiliser DOM pour lire des fichiers XML en crant un analyseur
pour le format de fichier dindex dcrit dans la section prcdente.

Qt 4 Livre Page 367 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

367

class DomParser
{
public:
DomParser(QIODevice *device, QTreeWidget *tree);
private:
void parseEntry(const QDomElement &element,
QTreeWidgetItem *parent);
QTreeWidget *treeWidget;
};

Nous dfinissons une classe nomme DomParser qui analysera un index de livre se prsentant
sous la forme dun document XML et affichera le rsultat dans un QTreeWidget. Cette classe
nhrite daucune autre classe.
DomParser::DomParser(QIODevice *device, QTreeWidget *tree)
{
treeWidget = tree;
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(device, true, &errorStr, &errorLine,
&errorColumn)) {
QMessageBox::warning(0, QObject::tr("DOM Parser"),
QObject::tr("Parse error at line %1, "
"column %2:\n%3")
.arg(errorLine)
.arg(errorColumn)
.arg(errorStr));
return;
}
QDomElement root = doc.documentElement();
if (root.tagName()!= "bookindex")
return;
QDomNode node = root.firstChild();
while (!node.isNull()) {
if (node.toElement().tagName() == "entry")
parseEntry(node.toElement(), 0);
node = node.nextSibling();
}
}

Dans le constructeur, nous crons un objet QDomDocument et appelons setContent() sur


celui-ci pour lamener lire le document XML fourni par le QIODevice. La fonction
setContent() ouvre automatiquement le priphrique si ce nest dj fait. Nous appelons
ensuite documentElement() sur le QDomDocument pour obtenir son enfant QDomElement

Qt 4 Livre Page 368 Jeudi, 7. dcembre 2006 12:14 12

368

Qt4 et C++ : Programmation dinterfaces GUI

unique, et nous vrifions sil sagit bien dun lment <bookindex>. Nous parcourons tous
les nuds enfants, et si le nud est un lment <entry>, nous appelons parseEntry() pour
lanalyser.
La classe QDomNode peut stocker tout type de nud. Si nous souhaitons traiter un nud de
faon plus prcise, nous devons tout dabord le convertir en un type de donne correct. Dans
cet exemple, nous ne nous proccupons que des nuds Element. Nous appelons donc toElement() sur le QDomNode pour le convertir en un QDomElement, puis nous appelons
tagName() afin de rcuprer le nom de balise de llment. Si le nud nest pas du type
Element, la fonction toElement() retourne un objet QDomElement nul, avec un nom de
balise vide.
void DomParser::parseEntry(const QDomElement &element,
QTreeWidgetItem *parent)
{
QTreeWidgetItem *item;
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, element.attribute("term"));
QDomNode node = element.firstChild();
while (!node.isNull()) {
if (node.toElement().tagName() == "entry") {
parseEntry(node.toElement(), item);
} else if (node.toElement().tagName() == "page") {
QDomNode childNode = node.firstChild();
while (!childNode.isNull()) {
if (childNode.nodeType() == QDomNode::TextNode) {
QString page = childNode.toText().data();
QString allPages = item->text(1);
if (!allPages.isEmpty())
allPages += ", ";
allPages += page;
item->setText(1, allPages);
break;
}
childNode = childNode.nextSibling();
}
}
node = node.nextSibling();
}
}

Dans parseEntry(), nous crons un lment QTreeWidget. Si elle est imbrique dans une
autre balise <entry>, la nouvelle balise dfinit une sous-entre dans lindex, et le nouveau
QTreeWidgetItem est cr en tant quenfant du QTreeWidgetItem qui reprsente lentre

Qt 4 Livre Page 369 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

369

principale. Dans le cas contraire, nous crons le QTreeWidgetItem avec treeWidget en tant
que parent, en faisant de celui-ci un lment de haut niveau. Nous appelons setText() pour
dfinir le texte prsent en colonne 0 en la valeur de lattribut term de la balise <entry>.
Une fois le QTreeWidgetItem initialis, nous parcourons les nuds enfants du QDomElement
correspondant la balise <entry> courante.
Si llment est <entry>, nous appelons parseEntry() avec llment courant en tant que
deuxime argument. Le QTreeWidgetItem de la nouvelle entre sera alors cr avec le
QTreeWidgetItem de lentre principale en tant que parent.
Si llment est <page>, nous parcourons la liste enfant de llment la recherche dun nud
Text. Une fois celui-ci trouv, nous appelons Text() pour le convertir en un objet QDomText,
et data() pour extraire le texte en tant que QString. Puis nous ajoutons le texte la liste de
numros de page dlimite par des virgules dans la colonne 1 du QTreeWidgetItem.
Voyons comment utiliser la classe DomParser pour analyser un fichier :
void parseFile(const QString &fileName)
{
QStringList labels;
labels << QObject::tr("Terms") << QObject::tr("Pages");
QTreeWidget *treeWidget = new QTreeWidget;
treeWidget->setHeaderLabels(labels);
treeWidget->setWindowTitle(QObject::tr("DOM Parser"));
treeWidget->show();
QFile file(fileName);
DomParser(&file, treeWidget);
}

Nous commenons par dfinir un QTreeWidget. Puis nous crons un QFile et un DomParser.
Une fois le DomParser construit, il analyse le fichier et alimente larborescence.
Comme dans lexemple prcdent, nous avons besoin de la ligne suivante dans le fichier .pro
de lapplication pour tablir un lien avec la bibliothque QtXml :
QT

+= xml

Comme le montre lexemple, lopration consistant parcourir un arbre DOM peut savrer
assez lourde. La simple extraction du texte entre les balises <page> et </page> a ncessit le
parcours dune liste de QDomNode au moyen de firstChild() et de nextSibling(). Les
programmeurs qui utilisent beaucoup DOM crivent souvent leurs propres fonctions conteneur
de haut niveau afin de simplifier les oprations courantes, telles que lextraction de texte entre
les balises douverture et de fermeture.

Qt 4 Livre Page 370 Jeudi, 7. dcembre 2006 12:14 12

370

Qt4 et C++ : Programmation dinterfaces GUI

Ecrire du code XML


Deux approches soffrent vous pour gnrer des fichiers XML partir dapplications Qt :

gnrer un arbre DOM et appeler save() sur celui-ci ;

gnrer le code XML manuellement.

Le choix entre ces approches est souvent indpendant du fait que nous utilisions SAX ou DOM
pour la lecture des documents XML.
Voici un extrait de code qui illustre comment crer un arbre DOM et lcrire au moyen dun
QTextStream:
const int Indent = 4;
QDomDocument doc;
QDomElement root = doc.createElement("doc");
QDomElement quote = doc.createElement("quote");
QDomElement translation = doc.createElement("translation");
QDomText latin = doc.createTextNode("Ars longa vita brevis");
QDomText english = doc.createTextNode("Art is long, life is short");
doc.appendChild(root);
root.appendChild(quote);
root.appendChild(translation);
quote.appendChild(latin);
translation.appendChild(english);
QTextStream out(&file);
doc.save(out, Indent);

Le deuxime argument de save() est la taille du retrait utiliser. Une valeur diffrente de zro
facilite la lecture du contenu du fichier. Voici la sortie du fichier XML :
<doc>
<quote>Ars longa vita brevis</quote>
<translation>Art is long, life is short</translation>
</doc>

Un autre scnario se produit dans les applications qui utilisent larbre DOM comme structure
de donnes primaire. Ces applications effectuent gnralement des oprations de lecture dans
des documents XML en utilisant DOM, puis modifient larbre DOM en mmoire et appellent
finalement save() pour convertir de nouveau larbre vers XML.
Par dfaut, QDomDocument::save() utilise lencodage UTF-8 pour le fichier gnr. Nous
pouvons utiliser un autre encodage en ajoutant au dbut de larbre DOM une dclaration XML
telle que :
<?xml version="1.0" encoding="ISO-8859-1"?>

Qt 4 Livre Page 371 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 15

XML

371

Lextrait de code suivant vous montre comment y parvenir :


QTextStream out(&file);
QDomNode xmlNode = doc.createProcessingInstruction("xml",
"version=\"1.0\" encoding=\"ISO-8859-1\"");
doc.insertBefore(xmlNode, doc.firstChild());
doc.save(out, Indent);

La gnration manuelle de fichiers XML nest pas beaucoup plus difficile quavec DOM. Nous
pouvons employer QTextStream et crire les chanes comme nous le ferions avec tout autre
fichier texte. La partie la plus dlicate est de neutraliser linterprtation des caractres spciaux
qui apparaissent dans le texte et les valeurs dattribut. La fonction Qt::escape() neutralise
les caractres <, > et &. Voici un extrait de code qui fait appel cette fonction :
QTextStream out(&file);
out.setCodec("UTF-8");
out << "<doc>\n"
<< " <quote>" << Qt::escape(quoteText) << "</quote>\n"
<< " <translation>" << Qt::escape(translationText)
<< "</translation>\n"
<< "</doc>\n";

Larticle "Generating XML" disponible ladresse http://doc.trolltech.com/qq/qq05-generating-xml.html prsente une classe trs simple qui facilite la gnration de fichiers XML. Cette
classe prend en charge les dtails tels que les caractres spciaux, le retrait et les problmes
dencodage, nous permettant de nous concentrer librement sur le code XML gnrer.
La classe a t conue pour fonctionner avec Qt 3, mais il nest pas difficile de ladapter Qt 4.

Qt 4 Livre Page 372 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 373 Jeudi, 7. dcembre 2006 12:14 12

16
Aide en ligne
Au sommaire de ce chapitre
Infobulles, informations dtat et aide
"Quest-ce que cest ?"
Utiliser QTextBrowser comme moteur
daide simple
Utiliser lAssistant Qt pour une aide
en ligne puissante

La plupart des applications fournissent une aide en ligne leurs utilisateurs. Certaines
indications apparaissent sous une forme brve, telles que les infobulles, les informations
dtat et "Quest-ce que cest ?". Qt prend naturellement en charge toutes ces informations.
Il existe galement un autre type daide, beaucoup plus approfondi, qui implique de
nombreuses pages de texte. Dans ce cas, nous pouvons utiliser QTextBrowser en tant
que navigateur daide simple. Il est galement possible dinvoquer lAssistant Qt ou un
navigateur HTML depuis notre application.

Qt 4 Livre Page 374 Jeudi, 7. dcembre 2006 12:14 12

374

Qt4 et C++ : Programmation dinterfaces GUI

Infobulles, informations dtat et aide


"Quest-ce que cest ?"
Une infobulle est un petit texte qui apparat lorsque la souris survole un widget. Les infobulles
sont prsentes sous la forme de texte noir sur un arrire-plan jaune. Leur objectif principal est
de fournir des descriptions textuelles de boutons des barres doutils.
Nous pouvons ajouter des infobulles des widgets arbitraires dans le code au moyen de QWidget::setToolTip(). Par exemple :
findButton->setToolTip(tr("Find next"));

Pour dfinir linfobulle dun QAction que vous ajoutez un menu ou une barre doutils,
nous appelons simplement setToolTip() sur laction. Par exemple :
newAction = new QAction(tr("&New"), this);
newAction->setToolTip(tr("New document"));

Si nous ne dfinissons pas explicitement une infobulle, QAction utilise automatiquement le


texte de laction.
Une information dtat est galement un texte descriptif bref, mais gnralement un peu plus
long que celui dune infobulle. Lorsque la souris survole un bouton de barre doutils ou une
option de menu, une information dtat apparat dans la barre dtat. (Voir Figure 16.1)
Appelez setStatusTip() pour ajouter une information dtat une action ou un widget :
newAction->setStatusTip(tr("Create a new document"));

Figure 16.1
Une application affichant
une infobulle et une information dtat

Qt 4 Livre Page 375 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 16

Aide en ligne

375

Dans certaines situations, il est souhaitable de fournir une information plus complte que celle
offerte par les infobulles et les indicateurs dtat. Nous pouvons, par exemple, afficher une
bote de dialogue complexe contenant un texte explicatif concernant chaque champ, ceci sans
forcer lutilisateur invoquer une fentre daide distincte. Le mode "Quest-ce que cest ?"
reprsente une solution idale dans cette situation. Quand une fentre se trouve en mode
"Quest-ce que cest ?", le curseur se transforme en
et lutilisateur peut cliquer sur tout
composant de linterface utilisateur pour obtenir le texte daide le concernant. Pour entrer en
mode "Quest-ce que cest ?", lutilisateur peut soit cliquer sur le bouton ? dans la barre de titre
de la fentre (sous Windows et KDE), soit appuyer sur Maj+F1.
Voici un exemple de texte "Quest-ce que cest ?" :
dialog->setWhatsThis(tr("<img src=\":/images/icon.png\">"
"&nbsp;The meaning of the Source field depends "
"on the Type field:"
"<ul>"
"<li><b>Books</b> have a Publisher"
"<li><b>Articles</b> have a Journal name with "
"volume and issue number"
"<li><b>Theses</b> have an Institution name "
"and a Department name"
"</ul>"));

Nous pouvons utiliser les balises HTML pour mettre en forme le texte dinformation dun
"Quest-ce que cest ?". Dans lexemple fourni, nous incluons une image (qui est rpertorie dans le fichier de ressources de lapplication), une liste puces et du texte en gras.
(Voir Figure 16.2.) Vous trouverez les balises et attributs pris en charge par Qt ladresse
http://doc.trolltech.com/4.1/richtext-html-subset.html.
Figure 16.2
Une bote de dialogue
affichant du texte daide
"Quest-ce que cest ?"

Qt 4 Livre Page 376 Jeudi, 7. dcembre 2006 12:14 12

376

Qt4 et C++ : Programmation dinterfaces GUI

Un texte "Quest-ce que cest ?" dfini sur une action apparat quand lutilisateur clique sur
llment de menu ou sur le bouton de la barre doutils, ou encore sil appuie sur la touche
de raccourci alors quil se trouve en mode "Quest-ce que cest ?".Lorsque les composants de
linterface utilisateur de la fentre principale dune application sont associs du texte
"Quest-ce que cest ?", il est habituel de proposer une option de mme nom dans le menu
Aide et un bouton correspondant dans la barre doutils. Pour ce faire, il suffit de crer une
action Quest-ce que cest? avec la fonction statique QWhatThis::createAction() et
dajouter laction retourne un menu Aide et une barre doutils. La classe QWhatsThis
fournit des fonctions statiques destines programmer lentre dans le mode "Quest-ce que
cest ?" et la sortie de ce mode.

Utilisation de QTextBrowser comme moteur


daide simple
Les grosses applications ncessitent souvent une aide en ligne plus riche que celle susceptible
dtre offerte pas les infobulles, les informations dtat et le mode "Quest-ce que cest ?". Une
solution simple consiste fournir un navigateur daide. Les applications incluant un navigateur
de ce type possdent gnralement une entre Aide dans le menu Aide de la fentre principale
ainsi quun bouton Aide dans chaque bote de dialogue.
Dans cette section, nous prsentons le navigateur daide illustr en Figure 16.3 et expliquons
comment lutiliser dans une application. La fentre fait appel QTextBrowser pour afficher
les pages daide dont la syntaxe est base sur HTML. QTextBrowser tant en mesure de grer
de nombreuses balises HTML, il savre idal dans cette situation.
Nous commenons par le fichier den-tte :
#include <QWidget>
class QPushButton;
class QTextBrowser;
class HelpBrowser: public QWidget
{
Q_OBJECT
public:
HelpBrowser(const QString &path, const QString &page,
QWidget *parent = 0);
static void showPage(const QString &page);
private slots:
void updateWindowTitle();

Qt 4 Livre Page 377 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 16

Aide en ligne

377

private:
QTextBrowser *textBrowser;
QPushButton *homeButton;
QPushButton *backButton;
QPushButton *closeButton;
};

HelpBrowser fournit une fonction statique pouvant tre appele nimporte o dans lapplication.
Cette fonction cre une fentre HelpBrowser et affiche la page donne.
Figure 16.3
Le widget HelpBrowser

Voici le dbut de limplmentation :


#include <QtGui>
#include "helpbrowser.h"
HelpBrowser::HelpBrowser(const QString &path, const QString &page,
QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_GroupLeader);
textBrowser = new QTextBrowser;
homeButton = new QPushButton(tr("&Home"));
backButton = new QPushButton(tr("&Back"));
closeButton = new QPushButton(tr("Close"));
closeButton->setShortcut(tr("Esc"));

Qt 4 Livre Page 378 Jeudi, 7. dcembre 2006 12:14 12

378

Qt4 et C++ : Programmation dinterfaces GUI

QHBoxLayout *buttonLayout = new QHBoxLayout;


buttonLayout->addWidget(homeButton);
buttonLayout->addWidget(backButton);
buttonLayout->addStretch();
buttonLayout->addWidget(closeButton);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(buttonLayout);
mainLayout->addWidget(textBrowser);
setLayout(mainLayout);
connect(homeButton, SIGNAL(clicked()), textBrowser, SLOT(home()));
connect(backButton, SIGNAL(clicked()),
textBrowser, SLOT(backward()));
connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
connect(textBrowser, SIGNAL(sourceChanged(const QUrl &)),
this, SLOT(updateWindowTitle()));
textBrowser->setSearchPaths(QStringList() << path << ":/images");
textBrowser->setSource(page);
}

Nous dfinissons lattribut Qt::WA_GroupLeader car nous souhaitons faire apparatre les
fentres HelpBrowser depuis des botes de dialogue modales en complment de la fentre
principale. Ces botes empchent normalement lutilisateur dinteragir avec toute autre fentre
de lapplication. Cependant, aprs avoir demand de laide, cet utilisateur doit de toute
vidence tre autoris interagir la fois avec la bote de dialogue modale et le navigateur
daide. La dfinition de lattribut Qt::WA_GroupLeader autorise cette interaction.
Nous fournissons deux accs pour la recherche, le premier tant le systme de fichiers contenant la documentation de lapplication et le second tant lemplacement des ressources image.
Le code HTML peut inclure des rfrences aux images dans le systme de fichiers de faon
classique, mais il peut galement faire rfrence aux ressources image en utilisant un
chemin daccs commenant par :/ (deux points, slash). Le paramtre page est le nom du
fichier de documentation, avec une ancre HTML facultative (une ancre HTML est la cible dun
lien).
void HelpBrowser::updateWindowTitle()
{
setWindowTitle(tr("Help: %1").arg(textBrowser->documentTitle()));
}

Ds que la page source change, le slot updateWindowTitle() est appel. La fonction documentTitle() retourne le texte spcifi dans la balise <title> de la page.
void HelpBrowser::showPage(const QString &page)
{
QString path = QApplication::applicationDirPath() + "/doc";
HelpBrowser *browser = new HelpBrowser(path, page);

Qt 4 Livre Page 379 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 16

Aide en ligne

379

browser->resize(500, 400);
browser->show();
}

Dans la fonction statique showPage(), nous crons la fentre HelpBrowser, puis nous laffichons. Comme nous avons dfini lattribut Qt::WA_DeleteOnClose dans le constructeur de
HelpBrowser, la fentre sera dtruite automatiquement lors de sa fermeture par lutilisateur.
Pour cet exemple, nous supposons que la documentation est stocke dans le sous-rpertoire
doc du rpertoire contenant lexcutable de lapplication. Toutes les pages transmises la
fonction showPage() seront extraites de ce sous-rpertoire.
Vous tes maintenant prt invoquer le navigateur daide depuis lapplication. Dans la fentre
principale de lapplication, crez une action Aide et connectez-la un slot help() sur le
modle suivant :
void MainWindow::help()
{
HelpBrowser::showPage("index.html");
}

Nous supposons ici que le fichier daide principal se nomme index.html. Dans le cas de
botes de dialogue, vous connecteriez le bouton Aide un slot help() similaire celui-ci :
void EntryDialog::help()
{
HelpBrowser::showPage("forms.html#editing");
}

Ici, nous effectuons la recherche dans un fichier daide diffrent, forms.html et faisons dfiler
le QTextBrowser jusqu lancre editing.

Utilisation de lassistant pour une aide en ligne


puissante
LAssistant Qt est une application daide en ligne redistribuable fournie par Trolltech. Elle
prsente lavantage de prendre en charge lindexation et la recherche de texte intgral et dtre
capable de grer plusieurs jeux de documentation distincts correspondant diffrentes applications.
Pour utiliser lAssistant Qt, nous devons incorporer le code ncessaire dans notre application et
faire connatre lexistence de notre documentation cet assistant.
La communication entre une application Qt et lAssistant Qt est gre par la classe QAssistantClient, qui est situe dans une bibliothque distincte. Pour tablir une liaison entre cette bibliothque et une application, vous devez ajouter cette ligne de code au fichier .pro de lapplication :
CONFIG

+= assistant

Qt 4 Livre Page 380 Jeudi, 7. dcembre 2006 12:14 12

380

Qt4 et C++ : Programmation dinterfaces GUI

Nous allons maintenant examiner le code dune nouvelle classe HelpBrowser qui utilise
lAssistant Qt.
#ifndef HELPBROWSER_H
#define HELPBROWSER_H
class QAssistantClient;
class QString;
class HelpBrowser
{
public:
static void showPage(const QString &page);
private:
static QAssistantClient *assistant;
};
#endif
Voici le nouveau fichier helpbrowser.cpp:
#include <QApplication>
#include <QAssistantClient>
#include "helpbrowser.h"
QAssistantClient *HelpBrowser::assistant = 0;
void HelpBrowser::showPage(const QString &page)
{
QString path = QApplication::applicationDirPath() + "/doc/" + page;
if (!assistant)
assistant = new QAssistantClient("");
assistant->showPage(path);
}

Le constructeur de QAssistantClient accepte comme premier argument un chemin daccs


quil utilise pour situer lexcutable de Assistant Qt. En transmettant un chemin daccs vide,
nous indiquons QAssistantClient de rechercher lexcutable dans la variable denvironnement PATH. QAssistantClient possde une fonction showPage() qui accepte un nom de
page avec une ancre HTML en option.
La prochaine tape consiste prparer une table des matires et un index pour la documentation. Pour ce faire, nous crons un profile Assistant Qt et crivons un fichier .dcf qui fournit
des informations concernant la documentation. Tout ceci est expliqu dans la documentation
en ligne de lAssistant Qt. Nous ne rpterons donc pas ces indications ici.
Une solution alternative lemploi de QTextBrowser ou celui de lAssistant Qt consiste
sorienter vers des approches spcifiques la plate-forme. Pour les applications Windows, il
peut tre souhaitable de crer des fichiers daide HTML Windows et doffrir un accs ceux-ci
par le biais de Microsoft Internet Explorer. Pour ce faire, vous pouvez recourir la classe

Qt 4 Livre Page 381 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 16

Aide en ligne

381

QProcess de Qt ou linfrastructure ActiveQt. Lapproche la plus judicieuse pour les


applications X11 serait de fournir des fichiers HTML et de lancer un navigateur Web au
moyen de QProcess. Sous Mac OS X, lAide Apple fournit une fonctionnalit similaire
lAssistant Qt.
Nous sommes prsent la fin de la Partie II. Les chapitres de la Partie III traitent des fonctionnalits avances et spcialises de Qt. Le code C++ et Qt prsent nest pas plus difficile
que celui de la Partie II, mais certains concepts et ides vous paratront peut-tre plus ardus,
car ces domaines sont nouveaux pour vous.

Qt 4 Livre Page 382 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 383 Jeudi, 7. dcembre 2006 12:14 12

III
Qt : tude avance
17

Internationalisation

18

Environnement multithread

19

Crer des plug-in

20

Fonctionnalits spcifiques la plate-forme


Programmation embarque

21

Qt 4 Livre Page 384 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 385 Jeudi, 7. dcembre 2006 12:14 12

17
Internationalisation
Au sommaire de ce chapitre
Travailler avec Unicode
Crer des applications ouvertes aux
traductions
Passer dynamiquement dune langue
une autre
Traduire les applications

En complment de lalphabet latin utilis pour le franais et de nombreuses langues europennes, Qt offre une large prise en charge des systmes dcriture du reste du monde.
Qt utilise Unicode en interne et par le biais de lAPI. Ainsi, toutes les langues utilises
par linterface utilisateur sont prises en charge de faon identique.
Le moteur de texte de Qt est en mesure de grer tous les systmes dcriture nonlatins majeurs, dont larabe, le chinois, le cyrillique, lhbreu, le japonais, le coren,
le tha et les langues Hindi.
Le moteur de disposition de Qt prend en charge lcriture de la droite vers la gauche
pour les langues telles que larabe et lhbreu.
Certaines langues ncessitent des mthodes spciales dentre de texte. Les widgets
ddition tels que QLineEdit et QTextEdit fonctionnent correctement avec toute
mthode dentre installe sur le systme de lutilisateur.

Qt 4 Livre Page 386 Jeudi, 7. dcembre 2006 12:14 12

386

Qt4 et C++ : Programmation dinterfaces GUI

Il faut souvent fournir plus quune simple adaptation du texte saisi par les utilisateurs dans leur
langue native. Linterface utilisateur entire doit galement tre traduite. Qt facilite cette
tche : il suffit de traiter toutes les chanes visibles par lutilisateur avec la fonction tr()
(comme nous lavons fait dans les chapitres prcdents) et dutiliser les outils de prise en
charge de Qt pour prparer la traduction des fichiers dans les langues requises. Qt fournit un
outil GUI nomm Qt Linguist destin tre utilis par les traducteurs. Qt Linguist est complt
par deux programmes de ligne de commande, lupdate et lrelease, qui sont gnralement
excuts par les dveloppeurs de lapplication.
Pour la plupart des applications, un fichier de traduction est charg au dmarrage qui tient
compte des paramtres locaux de lutilisateur. Mais dans certains cas, il est galement ncessaire de
pouvoir basculer dune langue lautre au moment de lexcution. Ceci est parfaitement possible avec Qt, bien que cette opration implique un travail supplmentaire. Et grce au systme
de disposition de Qt, les divers composants de linterface utilisateur sont automatiquement
ajusts pour faire de la place aux textes traduits quand ils sont plus longs que les originaux.

Travailler avec Unicode


Unicode est un systme de codage de caractres qui prend en charge la plupart des systmes
dcriture mondiaux. Lide lorigine du dveloppement dUnicode est quen utilisant 16 bits
au lieu de 8 pour stocker les caractres, il devient possible de coder environ 65 000 caractres
au lieu de 2561. Unicode comprend les systmes ASCII et ISO 8859-1 (Latin-1) et ces deux
sous-ensembles se trouvent sur les mmes positions de code. La valeur du caractre "A", par
exemple, est de 0x41 dans les systmes ASCII, Latin-1 et Unicode et celle de "" est de 0xD1
dans les systmes Latin-1 et Unicode.
La classe QString de Qt stocke les chanes utilisant le systme Unicode. Chaque caractre
dun QString est un QChar de 16 bits et non un char de 8 bits. Voici deux mthodes pour
dfinir le premier caractre dune chane en "A" :
str[0] = A;
str[0] = QChar(0x41);

Si le fichier source est cod en Latin-1, il est ais de spcifier des caractres en Latin-1 :
str[0] = ;

Et si le codage du fichier source est diffrent, la valeur numrique fonctionne bien :


str[0] = QChar(0xD1);

1. Les versions rcentes dUnicode affectent des valeurs de caractres au-dessus de 65 535. Ces caractres
peuvent tre reprsents avec des squences de deux valeurs de 16 bits nommes "paires de substitution".

Qt 4 Livre Page 387 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

387

Nous pouvons dsigner tout caractre Unicode par sa valeur numrique. Voici, par exemple,
comment spcifier la lettre grecque majuscule sigma ("") et le caractre montaire euro ("").
str[0] = QChar(0x3A3);
str[0] = QChar(0x20AC);

Les valeurs numriques de tous les caractres pris en charge par Unicode sont rpertories
ladresse http://www.unicode.org/standard/. Si votre besoin en caractres Unicode nonLatin-1 est rare et ponctuel, la recherche en ligne est la solution approprie. Qt fournit cependant
des moyens plus pratiques dentrer des chanes Unicode dans un programme, comme nous le
verrons ultrieurement dans cette section.
Le moteur de texte de Qt 4 prend en charge les systmes dcriture suivants sur toutes les
plates-formes : arabe, chinois, cyrillique, grec, hbreu, japonais, coren, lao, latin, tha et vietnamien. Il prend aussi en charge tous les scripts Unicode 4.1 ne ncessitant pas de traitement
spcial. En outre, les systmes dcriture suivants sont pris en charge sur X11 avec Fonconfig
et sur les versions rcentes de Windows : bengali, devanagari, gujarati, gurmukhi, kannada,
khmer, malayalam, syriac, tamil, telugu, thaana (dhivehi) et tibtain. Loriya, enfin, est pris en
charge sur X11 et le mongolien ainsi que le sinhala sont pris en charge par Windows XP.
En supposant que les polices correctes sont installes sur le systme, Qt peut afficher le texte
au moyen de ces systmes dcriture. Et en supposant que les mthodes dentre correctes sont
installes, les utilisateurs pourront entrer du texte correspondant ces systmes dcriture dans
leurs applications Qt.
La programmation avec QChar diffre lgrement de celle avec char. Pour obtenir la valeur
numrique dun QChar, vous devez appeler unicode() sur celui-ci. Pour obtenir la valeur
ASCII ou Latin-1 dun QChar, il vous faut appeler toLatin1(). Pour les caractres nonLatin-1, toLatin1() retourne "\0".
Si nous savons que toutes les chanes dun programme appartiennent au systme ASCII, nous
pouvons utiliser des fonctions <cctype> standard telles que isalpha(), isdigit() et
isspace() sur la valeur de retour de toLatin1(). Il est cependant gnralement prfrable
de faire appel aux fonctions membre de QChar pour raliser ces oprations, car elles fonctionneront pour tout caractre Unicode. Les fonctions fournies par QChar incluent isPrint(),
isPunct(), isSpace(), isMark(), isLetter(), isNumber(), isLetterOrNumber(),
isDigit(), isSymbol(), isLower() et isUpper(). Voici, par exemple, un moyen de tester
si un caractre est un chiffre ou une lettre majuscule :
if (ch.isDigit() || ch.isUpper())
...

Lextrait de code fonctionne pour tout alphabet qui distingue les majuscules des minuscules,
dont lalphabet latin, grec et cyrillique.
Lorsque vous avez une chane Unicode, vous pouvez lutiliser tout emplacement de lAPI de
Qt o est attendu un QString. Qt prend alors la responsabilit de lafficher correctement et
de la convertir en codages adquats pour le systme dexploitation.

Qt 4 Livre Page 388 Jeudi, 7. dcembre 2006 12:14 12

388

Qt4 et C++ : Programmation dinterfaces GUI

Nous devons tre particulirement attentifs lorsque nous lisons ou crivons des fichiers texte.
Ces derniers peuvent utiliser plusieurs codages, et il est souvent impossible de deviner le
codage dun fichier de ce type partir de son contenu. Par dfaut, QTextStream utilise le codage
8 bits local du systme pour la lecture et lcriture. Pour les Etats-Unis et lEurope de louest,
il sagit habituellement de Latin-1.
Si nous concevons notre propre format de fichier et souhaitons tre en mesure de lire et dcrire
des caractres Unicode arbitraires, nous pouvons enregistrer les donnes sous la forme Unicode
en appelant
stream.setCodec("UTF-16");
stream.setGenerateByteOrderMark(true);

avant de commencer lcriture dans le QTextStream. Les donnes seront alors enregistres au
format UTF-16, format qui ncessite deux octets par caractre, et seront prfixes par une
valeur spciale de 16 bits (la marque dordre doctet Unicode, 0xFFFE) indiquant si ce fichier
est en Unicode et si les octets se trouvent dans lordre little-endian ou big-endian. Le format
UTF-16 tant identique la reprsentation mmoire dun QString, la lecture et lcriture de
chanes Unicode dans ce format peut tre trs rapide. Il se produit cependant une surcharge lors
de lenregistrement de donnes ASCII pures au format UTF-16, car deux octets sont stocks
pour chaque caractre au lieu dun seul.
Il est possible de mentionner dautres codage en appelant setCodec() avec un QTextCodec
appropri. Un QTextCodec est un objet qui effectue une conversion entre Unicode et un
codage donn. Les QTextCodec sont employs dans diffrents contextes par Qt. En interne, ils
sont utiliss pour la prise en charge des polices, des mthodes dentre, du presse-papiers, des
oprations de glisser-dposer et des noms de fichiers. Mais ils sont galement utiles pour
lcriture dapplications Qt.
Lors de la lecture dun fichier texte, QTextStream dtecte Unicode automatiquement si le
fichier dbute par une marque dordre doctet. Ce comportement peut tre dsactiv en appelant setAutoDetectUnicode(false). Si les donnes ne sont pas supposes commencer par la
marque dordre doctet, il est prfrable dappeler setCodec() avec "UTF-16" avant la lecture.
UTF-8 est un autre codage qui prend en charge la totalit du systme Unicode. Son principal
avantage par rapport UTF-16 est quil sagit dun super ensemble ASCII. Tout caractre se
situant dans la plage 0x00 0x7F est reprsent par un seul octet. Les autres caractres, dont
les caractres Latin-1 au-dessus de 0x7F, sont reprsents par des squences multi-octets. Pour
ce qui est du texte en majorit ASCII, UTF-8 occupe environ la moiti de lespace consomm
par UTF-16. Pour utiliser UTF-8 avec QTextStream, appelez setCodec() avec "UTF-8"
comme nom de codec avant les oprations de lecture et dcriture.
Si nous souhaitons toujours lire et crire en Latin-1 sans tenir compte du systme de codage
local de lutilisateur, nous pouvons dfinir le codec "ISO 8859-1" sur QTextStream.
Par exemple :
QTextStream in(&file);
in.setCodec("ISO 8859-1");

Qt 4 Livre Page 389 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

389

Certains formats de fichiers spcifient leur codage dans leur en-tte. Len-tte est gnralement
en ASCII brut pour assurer une lecture correcte quel que soit le codage utilis. Le format de
fichier XML en est un exemple intressant. Les fichiers XML sont normalement encods sous
la forme UTF-8 ou UTF-16. Pour les lire correctement, il faut appeler setCodec() avec
"UTF-8".Si le format est UTF-16, QTextStream le dtectera automatiquement et sadaptera.
Len-tte <?xml?> dun fichier XML contient quelquefois un argument encoding. Par exemple :
<?xml version="1.0" encoding="EUC-KR"?>

Comme QTextStream ne vous permet pas de changer le codage une fois la lecture commence, la meilleure faon dappliquer un codage explicite consiste recommencer lire le fichier,
en utilisant le codec correct (obtenu laide de QTextCodec::codecForName()). Dans le cas
de XML, nous pouvons viter davoir grer le codage nous-mmes en utilisant les classes
XML de Qt dcrites dans le Chapitre 15.
Les QTextCodec peuvent galement tre employs pour spcifier le codage de chanes dans le
code source. Considrons, par exemple, une quipe de programmeurs japonais crivant une
application destine principalement au march des particuliers. Il est probable que ces
programmeurs crivent leur code source dans un diteur de texte qui utilise un codage tel que
EUC-JP ou Shift-JIS. Un diteur de ce type leur permet de saisir des caractres japonais sans
problme. Ils peuvent donc crire le type de code suivant :
QPushButton *button = new QPushButton(tr("

"));

Par dfaut, Qt interprte les arguments de tr() comme Latin-1. Pour les autres cas, nous appelons
la fonction statique QTextCodec::setCodecForTr(). Par exemple :
QTextCodec::setCodecForTr(QTextCodec::codecForName("EUC-JP"));

Cette opration doit tre effectue avant le premier appel tr(). En gnral, elle est ralise
dans main(), immdiatement aprs la cration de lobjet QApplication.
Les autres chanes spcifies dans le programme seront interprtes comme des chanes Latin1. Si les programmeurs souhaitent y entrer galement des caractres japonais, ils peuvent les
convertir explicitement en Unicode au moyen dun QTextCodec:
QString text = japaneseCodec->toUnicode("

");

Ils peuvent alternativement demander Qt de faire appel un codec spcifique lors de la


conversion entre const char* et QString en appelant QTextCodec::setCodecForCStrings():
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("EUC-JP"));

Les techniques dcrites ci-dessus peuvent tre appliques toute langue non Latin-1, dont le
chinois, le grec, le coren et le russe.

Qt 4 Livre Page 390 Jeudi, 7. dcembre 2006 12:14 12

390

Qt4 et C++ : Programmation dinterfaces GUI

Voici une liste des codages pris en charge par Qt 4 :


cApple Roman

cISO 8859-5

cIscii-Mlm

cUTF-8

cBig5

cISO 8859-6

cIscii-Ori

cUTF-16

cBig5-HKSCS

cISO 8859-7

cIscii-Pnj

cUTF-16BE

cEUC-JP

cISO 8859-8

cIscii-Tlg

cUTF-16LE

cEUC-KR

cISO 8859-9

cIscii-Tml

cWindows-1250

cGB18030-0

cISO 8859-10

cJIS X 0201

cWindows-1251

cIBM 850

cISO 8859-13

cJIS X 0208

cWindows-1252

cIBM 866

cISO 8859-14

cKOI8-R

cWindows-1253

cIBM 874

cISO 8859-15

cKOI8-U

cWindows-1254

cISO 2022-JP

cISO 8859-16

cMuleLao-1

cWindows-1255

cISO 8859-1

cIscii-Bng

cROMAN8

cWindows-1256

cISO 8859-2

cIscii-Dev

cShift-JIS

cWindows-1257

cISO 8859-3

cIscii-Gjr

cTIS-620

cWindows-1258

cISO 8859-4

cIscii-Knd

cTSCII

cWINSAMI2

Pour tous ces codages, QTextCodex::codecForName() retournera toujours un pointeur


valide. Les autres codages peuvent tre pris en charge en drivant QTextCodec.

Crer des applications ouvertes aux traductions


Pour que nos applications soient disponibles dans plusieurs langues, il convient de veiller
deux points :

Sassurer que chaque chane visible par lutilisateur passe par tr().

Charger un fichier de traduction (.qm) au dmarrage.

Aucune de ces oprations nest ncessaire pour les applications qui ne seront jamais traduites.
Mais lemploi de tr() ne ncessite pratiquement aucun effort et laisse la porte ouverte toute
traduction ultrieure.

tr() est une fonction statique dfinie dans QObject et remplace dans chaque sous-classe
dfinie avec la macro Q_OBJECT. Lorsque nous crivons du code dans une sous-classe QObject,
nous pouvons appeler tr() sans formalit. Un appel tr() retourne une traduction si elle est
disponible. Dans le cas contraire, le texte original est retourn.

Qt 4 Livre Page 391 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

391

Pour prparer les fichiers de traduction, nous devons excuter loutil lupdate de Qt. Cet outil
extrait tous les littraux de chane qui apparaissent dans les appels de tr() et produit des
fichiers de traduction qui contiennent toutes les chanes prtes tre traduites. Les fichiers
peuvent alors tre expdis un traducteur afin quil y ajoute les traductions. Ce processus est
expliqu dans la section "Traduction dapplication" un peu plus loin dans ce chapitre.
La syntaxe dun appel de tr() est la suivante :
Context::tr(sourceText, comment)

La partie Context est le nom dune sous-classe QObject dfinie avec la macro Q_OBJECT.
Il nest pas ncessaire de lui prciser si nous appelons tr() depuis une fonction membre de la
classe en question. La partie sourceText est le littral chane traduire. La partie comment
est facultative. Elle permet de fournir des informations supplmentaires au traducteur.
Voici quelques exemples :
RockyWidget::RockyWidget(QWidget *parent)
: QWidget(parent)
{
QString str1 = tr("Letter");
QString str2 = RockyWidget::tr("Letter");
QString str3 = SnazzyDialog::tr("Letter");
QString str4 = SnazzyDialog::tr("Letter", "US paper size");
}

Le contexte des deux premiers appels tr() est "RockyWidget" et celui des deux derniers
appels est "SnazzyDialog"."Letter" est le texte source des quatre appels. Le dernier dentre eux
comporte galement un commentaire destin aider le traducteur comprendre le sens du
texte source.
Dans des contextes diffrents, les chanes sont traduites indpendamment les unes des autres.
Les traducteurs travaillent gnralement sur un seul contexte la fois, souvent avec lapplication
en cours dexcution et en affichant la bote de dialogue ou le widget soumis la traduction.
Lorsque nous appelons tr() depuis une fonction globale, nous devons spcifier le contexte
explicitement. Toute sous-classe QObject de lapplication peut tre employe en tant que
contexte. Si aucun contexte nest appropri, il est toujours possible de recourir QObject luimme. Par exemple :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QPushButton button(QObject::tr("Hello Qt!"));


button.show();
return app.exec();
}

Dans tous les exemples tudis jusqu prsent, le contexte tait celui dun nom de classe.
Cest pratique, car nous pouvons presque toujours lomettre, mais ce nest pas une obligation.

Qt 4 Livre Page 392 Jeudi, 7. dcembre 2006 12:14 12

392

Qt4 et C++ : Programmation dinterfaces GUI

La faon la plus courante de traduire une chane en Qt consiste utiliser la fonction QApplication::translate(), qui accepte jusqu trois arguments : le contexte, le texte source et le
commentaire facultatif. Voici, par exemple, une autre faon de traduire "Hello Qt !":
QApplication::translate("Global Stuff", "Hello Qt!")

Ici, nous plaons le texte dans le contexte "Global Stuff".


Lusage des fonctions tr() et translate() est double : elles remplissent la fois le rle des
marqueurs utiliss par lupdate pour trouver les chanes visibles par lutilisateur, et elles agissent en tant que fonctions C++ qui traduisent du texte. Cette caractristique a un impact sur la
faon dont nous crivons le code. Les lignes suivantes, par exemple, ne fonctionneront pas :
// INCORRECT
const char *appName = "OpenDrawer 2D";
QString translated = tr(appName);

Le problme ici est que lupdate ne sera pas en mesure dextraire le littral chane "OpenDrawer 2D", car il napparat pas lintrieur dun appel tr(). Le traducteur naura donc pas
la possibilit de traduire la chane. Ce problme se pose souvent avec les chanes dynamiques :
// INCORRECT
statusBar()->showMessage(tr("Host " + hostName + " found"));

Ici, la chane que nous transmettons tr() varie en fonction de la valeur de hostName, de sorte
que nous ne pouvons pas raisonnablement nous attendre ce que tr() la traduise correctement.
La solution consiste excuter QString::arg():
statusBar()->showMessage(tr("Host %1 found").arg(hostName));

Ce code repose sur le principe suivant : le littral chane "Host %1 found" est transmis tr().
En supposant quun fichier de traduction en franais est charg, tr() retournera quelque chose
comme "Hte %1 trouv".Puis le paramtre "%1" est remplac par le contenu de la variable
hostName.
Bien quil soit gnralement dconseill dappeler tr() sur une variable, il est possible de
faire fonctionner cette technique correctement. Nous devons utiliser la macro QT_TR_NOOP()
afin de marquer les littraux chane traduire avant de les affecter une variable. Cette
mthode se rvle particulirement intressante pour les tableaux statiques de chanes. Par
exemple :
void OrderForm::init()
{
static const char * const flowers[] = {
QT_TR_NOOP("Medium Stem Pink Roses"),
QT_TR_NOOP("One Dozen Boxed Roses"),
QT_TR_NOOP("Calypso Orchid"),
QT_TR_NOOP("Dried Red Rose Bouquet"),
QT_TR_NOOP("Mixed Peonies Bouquet"),
0

Qt 4 Livre Page 393 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

393

};
for (int i = 0; flowers[i]; ++i)
comboBox->addItem(tr(flowers[i]));
}

La macro QT_TR_NOOP() retourne simplement son argument. Mais lupdate extraira toutes
les chanes encadres par cette dernire afin quelles puissent tre traduites. Par la suite, au
moment dutiliser la variable, nous appellerons normalement tr(). Mme si elle reoit une
variable, cette fonction remplit correctement son rle de traduction.
La macro QT_TRANSLATE_NOOP() fonctionne comme QT_TR_NOOP() la diffrence quelle
reoit aussi un contexte. Cette macro est pratique pour initialiser des variables lextrieur
dune classe :
static const char * const flowers[] = {
QT_TRANSLATE_NOOP("OrderForm", "Medium Stem Pink Roses"),
QT_TRANSLATE_NOOP("OrderForm", "One Dozen Boxed Roses"),
QT_TRANSLATE_NOOP("OrderForm", "Calypso Orchid"),
QT_TRANSLATE_NOOP("OrderForm", "Dried Red Rose Bouquet"),
QT_TRANSLATE_NOOP("OrderForm", "Mixed Peonies Bouquet"),
0
};

Largument de contexte doit tre identique au contexte fourni ultrieurement tr() ou


translate().
Lorsque nous commenons utiliser tr() dans une application, le risque est grand doublier
dinsrer des chanes visibles par lutilisateur dans un appel de cette fonction. Si ces appels
manquants ne sont pas dtects par le traducteur, les utilisateurs de lapplication vont voir
apparatre certaines chanes dans la langue originale. Pour viter ce problme, nous pouvons
demander Qt dinterdire les conversions implicites de const char* en QString. Pour ce
faire, nous dfinissons le symbole de prprocesseur QT_NO_CAST_FROM_ASCII avant dinclure
tout en-tte Qt. Le moyen le plus facile de sassurer que ce symbole est dfini consiste ajouter
la ligne suivante au fichier .pro de lapplication :
DEFINES

+= QT_NO_CAST_FROM_ASCII

Chaque littral chane devra ainsi tre obligatoirement trait par tr() ou QLatin1String(),
selon quil devra tre traduit ou non. Les chanes qui ne seront pas encadres par ces fonctions
vont ainsi gnrer une erreur la compilation, et il ne vous restera plus qu ajouter les appels
de tr() ou QLatin1String() manquants.
Une fois chaque chane visible par lutilisateur insre dans un appel de tr(), il ne reste plus
qu charger un fichier de traduction. Lopration se droule gnralement dans la fonction
main() de lapplication. Voici, par exemple, comment nous chargerions un fichier de traduction
bas sur les paramtres locaux de lutilisateur :
int main(int argc, char *argv[])
{

Qt 4 Livre Page 394 Jeudi, 7. dcembre 2006 12:14 12

394

Qt4 et C++ : Programmation dinterfaces GUI

QApplication app(argc, argv);


QTranslator appTranslator;
appTranslator.load("myapp_" + QLocale::system().name(),
qApp->applicationDirPath());
app.installTranslator(&appTranslator);

return app.exec();
}

La fonction QLocale::System() retourne un objet QLocale qui fournit des informations


concernant les paramtres locaux de lutilisateur. Par convention, nous intgrons le nom des
paramtres locaux au nom du fichier .qm. Ces noms peuvent tre plus ou moins prcis : fr, par
exemple, indique des paramtres locaux en langue franaise, fr_CA reprsente des paramtres
locaux en franais canadien et fr_CA.ISO8859-15 en franais canadien avec du codage ISO
8859-15 (un codage qui prend en charge "_", "" et "").
En supposant que les paramtres locaux soient en fr_CA.ISO8859-15, la fonction QTranslator::load() essaie tout dabord de charger le fichier myapp_fr_CA.ISO8859-15.qm.
Si le fichier nexiste pas, load() essaie ensuite myapp_fr_CA.qm, puis myapp_fr.qm et
enfin myapp.qm avant dabandonner. Nous ne fournissons normalement quun fichier
myapp_fr.qm, contenant une traduction en franais standard, mais si nous souhaitons un
fichier diffrent pour le franais canadien, nous pouvons aussi fournir un fichier myapp_fr_CA.qm
qui sera utilis pour les paramtres locaux fr_CA.
Le deuxime argument de QTranslator::load() est le rpertoire o nous souhaitons que
load() recherche le fichier de traduction. Dans ce cas, nous supposons que les fichiers de
traduction sont situs dans le mme rpertoire que lexcutable.
Les bibliothques Qt contiennent quelques chanes ncessitant une traduction. Trolltech fournit
des traductions en franais, en allemand et en chinois simplifi dans le rpertoire translations de Qt. Quelques autres langues sont galement fournies, mais par les utilisateurs Qt.
Ils ne sont pas officiellement pris en charge. Le fichier de traduction des bibliothques Qt doit
galement tre charg.
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(),
qApp->applicationDirPath());
app.installTranslator(&qtTranslator);

Un objet QTranslator ne peut contenir quun seul fichier de traduction la fois. Cest pourquoi nous utilisons un QTranslator distinct pour la traduction de Qt. Cela ne prsente aucun
problme puisque nous pouvons installer autant de traducteurs que ncessaire. QApplication
les utilisera tous lors de la recherche dune traduction.
Certaines langues, telles que larabe et lhbreu, sont crites de droite gauche au lieu de
gauche droite. Dans cette situation, la mise en forme complte de lapplication doit tre
inverse en appelant QApplication::setLayoutDirection( Qt::RightToLeft). Les
fichiers de traduction de Qt contiennent un marqueur spcial nomm "LTR" qui indique si la

Qt 4 Livre Page 395 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

395

langue scrit de gauche droite ou de droite gauche. Nous navons donc gnralement pas
besoin dappeler setLayoutDirection().
Il serait plus pratique pour les utilisateurs de fournir les applications avec les fichiers de traduction
intgrs lexcutable, en utilisant le systme de ressource de Qt. Cette technique permettrait
non seulement de rduire le nombre de fichiers distribus pour constituer le produit, mais aussi
dviter le risque de perte ou de suppression accidentelle de fichiers de traduction.
En supposant que les fichiers .qm sont situs dans un sous-rpertoire translations se trouvant
dans larbre source, nous aurions alors un fichier myapp.qrc avec le contenu suivant :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>translations/myapp_de.qm</file>
<file>translations/myapp_fr.qm</file>
<file>translations/myapp_zh.qm</file>
<file>translations/qt_de.qm</file>
<file>translations/qt_fr.qm</file>
<file>translations/qt_zh.qm</file>
</qresource>
</RCC>

Le fichier .pro contiendrait lentre suivante :


RESOURCES

= myapp.qrc

Nous devons enfin spcifier :/translations comme chemin daccs aux fichiers de traduction
dans main(). Les deux points qui apparaissent en premire position indiquent que le chemin
daccs fait rfrence une ressource et non un fichier situ dans le systme de fichiers.
Nous avons maintenant tudi tous les points ncessaires pour permettre une application de
fonctionner en utilisant des traductions dans dautres langues. Mais la langue et la direction du
systme dcriture ne sont pas les seuls points variables entre diffrents pays et cultures. Un
programme internationalis doit galement prendre en compte les formats de date, dheure,
montaire, numrique et lordre de classement des chanes. Qt inclut une classe QLocale qui
fournit des formats de date/dheure et numrique localiss. Pour obtenir dautres informations
locales, nous pouvons faire appel aux fonctions C++ setlocale() et localeconv().
Certaines classes et fonctions de Qt adaptent leur comportement en fonction des paramtres
locaux :
QString::localeAwareCompare() compare deux chanes en prenant en compte les
paramtres locaux. Elle permet de trier les lments visibles par lutilisateur.
La fonction toString() fournie par QDate, QTime et QDateTime retourne une chane
dans un format local quand elle est appele avec Qt::LocalDate comme argument.
Par dfaut, les widgets QDateEdit et QDateTimeEdit prsentent les dates dans le format
local.
Enfin, il est possible quune application traduite ait besoin dutiliser des icnes diffrentes de
celles fournies initialement. Par exemple, les flches gauche et droite apparaissant sur les

Qt 4 Livre Page 396 Jeudi, 7. dcembre 2006 12:14 12

396

Qt4 et C++ : Programmation dinterfaces GUI

boutons Prcdente et Suivante dun navigateur Web doivent tre inverses dans le cas
dune langue scrivant de droite gauche. Voici comment procder :
if (QApplication::isRightToLeft()) {
backAction->setIcon(forwardIcon);
forwardAction->setIcon(backIcon);
} else {
backAction->setIcon(backIcon);
forwardAction->setIcon(forwardIcon);
}

Les icnes contenant des caractres alphabtiques doivent trs souvent tre traduites. Par
exemple, la lettre "I" qui apparat sur un bouton de barre doutils associ une option Italique dun traitement de texte doit tre remplace par un "C" en espagnol (Cursivo) et par un
"K" en danois, nerlandais, allemand, norvgien et sudois (Kursiv). Voici un moyen simple
dy parvenir :
if (tr("Italic")[0] == C) {
italicAction->setIcon(iconC);
} else if (tr("Italic")[0] == K) {
italicAction->setIcon(iconK);
} else {
italicAction->setIcon(iconI);
}

Une alternative consiste utiliser la prise en charge de multiples paramtres locaux de la part
du systme de ressource. Dans le fichier .qrc, il est possible de spcifier un paramtre rgional
pour une ressource au moyen de lattribut lang. Par exemple :
<qresource>
<file>italic.png</file>
</qresource>
<qresource lang="es">
<file alias="italic.png">cursivo.png</file>
</qresource>
<qresource lang="sv">
<file alias="italic.png">kursiv.png</file>
</qresource>

Si le paramtre local de lutilisateur est es (Espaol), :/italic.png fait alors rfrence


limage cursivo.png. Si le paramtre local est sv (Svenska), cest limage kursiv.png qui
est employe. Pour dautres paramtres locaux, italic.png est utilis.

Passer dynamiquement dune langue une autre


Pour la plupart des applications, la dtection de la langue prfre de lutilisateur dans main()
et le chargement des fichiers .qm appropris donne un rsultat satisfaisant. Mais il existe des
situations dans lesquelles les utilisateurs doivent pouvoir basculer dynamiquement dune

Qt 4 Livre Page 397 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

397

langue lautre. Un changement de langue sans redmarrage peut tre ncessaire pour une
application employe en permanence par diffrentes personnes. Par exemple, les applications
employes par les oprateurs de centres dappels, par des traducteurs simultans et par des
oprateurs de caisses enregistreuses informatises requirent souvent cette possibilit.
Le passage dynamique dune langue une autre est un peu plus complexe programmer que le
chargement dune traduction unique au lancement de lapplication, mais ce nest pas trs difficile. Voici comment procder :
Vous devez offrir lutilisateur le moyen de passer une autre langue.
Pour chaque widget ou bote de dialogue, vous devez dfinir toutes les chanes susceptibles
dtre traduites dans une fonction distincte (souvent nomme retranslateUi()) et appeler
cette fonction lors des changements de langue.
Examinons les segments les plus importants du code source dune application "centre
dappel".Lapplication fournit un menu Language permettant lutilisateur de dfinir la
langue au moment de lexcution. La langue par dfaut est langlais. (Voir Figure 17.1)
Figure 17.1
Un menu Language
dynamique

Comme nous ne savons pas quelle langue lutilisateur souhaite employer au lancement de
lapplication, nous ne chargeons pas de traduction dans la fonction main(). Nous les chargeons plutt dynamiquement quand elles savrent ncessaires. Ainsi, tout le code dont nous
avons besoin pour grer les traductions doit tre plac dans les classes des botes de dialogue et
de la fentre principale.
Examinons la sous-classe QMainWindow de lapplication.
MainWindow::MainWindow()
{
journalView = new JournalView;
setCentralWidget(journalView);
qApp->installTranslator(&appTranslator);
qApp->installTranslator(&qtTranslator);
qmPath = qApp->applicationDirPath() + "/translations";
createActions();
createMenus();
retranslateUi();
}

Qt 4 Livre Page 398 Jeudi, 7. dcembre 2006 12:14 12

398

Qt4 et C++ : Programmation dinterfaces GUI

Dans le constructeur, nous dfinissons le widget central en tant que JournalView, une sousclasse de QTableWidget. Puis nous dfinissons quelques variables membre prives en liaison
avec la traduction :
La variable appTranslator est un objet QTranslator utilis pour stocker la traduction
de lapplication en cours.
La variable qtTranslator est un objet QTranslator utilis pour stocker la traduction de Qt.
La variable qmPath est un QString qui spcifie le chemin daccs au rpertoire contenant
les fichiers de traduction de lapplication.
Nous appelons enfin les fonctions prives createActions() et createMenus() pour crer le
systme de menus, et nous appelons retranslateUi(), galement une fonction prive, pour
dfinir les chanes initialement visibles par lutilisateur :
void MainWindow::createActions()
{
newAction = new QAction(this);
connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));

aboutQtAction = new QAction(this);


connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}

La fonction createActions() cre normalement ses objets QAction, mais sans dfinir aucun
texte ou touche de raccourci. Ces oprations seront effectues dans retranslateUi().
void MainWindow::createMenus()
{
fileMenu = new QMenu(this);
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addAction(exitAction);

createLanguageMenu();
helpMenu = new QMenu(this);
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(editMenu);
menuBar()->addMenu(reportsMenu);
menuBar()->addMenu(languageMenu);
menuBar()->addMenu(helpMenu);
}

La fonction createMenus() cre des menus, mais ne leur affecte aucun titre. Une fois encore,
cette opration sera effectue dans retranslateUi().

Qt 4 Livre Page 399 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

399

Au milieu de la fonction, nous appelons createLanguageMenu() pour remplir le menu


Language avec la liste des langues prises en charge. Nous reviendrons sur son code source
dans un moment. Examinons tout dabord retranslateUi():
void MainWindow::retranslateUi()
{
newAction->setText(tr("&New"));
newAction->setShortcut(tr("Ctrl+N"));
newAction->setStatusTip(tr("Create a new journal"));

aboutQtAction->setText(tr("About &Qt"));
aboutQtAction->setStatusTip(tr("Show the Qt librarys About box"));
fileMenu->setTitle(tr("&File"));
editMenu->setTitle(tr("&Edit"));
reportsMenu->setTitle(tr("&Reports"));
languageMenu->setTitle(tr("&Language"));
helpMenu->setTitle(tr("&Help"));
setWindowTitle(tr("Call Center"));
}

Cest dans la fonction retranslateUi() que se produisent tous les appels de tr() pour la
classe MainWindow. Ces appels ont lieu la fin du constructeur de MainWindow et chaque
fois quun utilisateur change la langue de lapplication par le biais du menu Language.
Nous dfinissons chaque texte, touche de raccourci et information dtat de QAction. Nous
dfinissons galement chaque titre de QMenu ainsi que le titre de fentre.
La fonction createMenus() prsente prcdemment a appel createLanguageMenu()
pour remplir le menu Language avec une liste de langues :
void MainWindow::createLanguageMenu()
{
languageMenu = new QMenu(this);
languageActionGroup = new QActionGroup(this);
connect(languageActionGroup, SIGNAL(triggered(QAction *)),
this, SLOT(switchLanguage(QAction *)));
QDir dir(qmPath);
QStringList fileNames =
dir.entryList(QStringList("callcenter_*.qm"));
for (int i = 0; i < fileNames.size(); ++i) {
QString locale = fileNames[i];
locale.remove(0, locale.indexOf(_) + 1);
locale.truncate(locale.lastIndexOf(.));
QTranslator translator;
translator.load(fileNames[i], qmPath);
QString language = translator.translate("MainWindow",
"English");

Qt 4 Livre Page 400 Jeudi, 7. dcembre 2006 12:14 12

400

Qt4 et C++ : Programmation dinterfaces GUI

QAction *action = new QAction(tr("&%1%2")


.arg(i + 1).arg(language), this);
action->setCheckable(true);
action->setData(locale);
languageMenu->addAction(action);
languageActionGroup->addAction(action);
if (language == "English")
action->setChecked(true);
}
}

Au lieu de coder en dur les langues prises en charge par lapplication, nous crons une entre
de menu pour chaque fichier .qm situ dans le rpertoire translations de lapplication. Pour des
raisons de simplicit, nous supposons que la langue anglaise (English) possde aussi un fichier
.qm. Une alternative consisterait appeler clear() sur les objets QTranslator lorsque
lutilisateur choisit English.
La difficult consiste trouver un nom adquat pour la langue fournie par chaque fichier .qm.
Le fait dafficher simplement "en" pour "English" ou "de" pour "Deutsch" semble primaire et
risque de semer la confusion dans lesprit de certains utilisateurs. La solution retenue dans
createLanguageMenu() est de contrler la traduction de la chane "English" dans le contexte
"MainWindow".Cette chane doit tre transforme en "Deutsch" pour une traduction allemande et en "

" pour une traduction japonaise.

Nous crons un QAction cocher pour chaque langue et stockons le nom local dans llment
"data" de laction. Nous les ajoutons un objet QActionGroup afin de nous assurer quun seul
lment du menu Language est coch la fois. Quand une action du groupe est choisie par
lutilisateur, le QActionGroup met le signal triggered(QAction *), qui est connect
switchLanguage().
void MainWindow::switchLanguage(QAction *action)
{
QString locale = action->data().toString();
appTranslator.load("callcenter_" + locale, qmPath);
qtTranslator.load("qt_" + locale, qmPath);
retranslateUi();
}

Le slot switchLanguage() est appel lorsque lutilisateur choisit une langue dans le menu
Language. Nous chargeons les fichiers de traduction de lapplication et de Qt, et nous appelons
retranslateUi() pour traduire toutes les chanes de la fentre principale.
Sur Windows, une solution alternative au menu Language consisterait rpondre des vnements LocaleChange, un type dvnement mis par Qt quand un changement dans les paramtres locaux de lenvironnement est dtect. Ce type existe sur toutes les plates-formes prises
en charge par Qt, mais il nest en fait gnr que sur Windows, lorsque lutilisateur change les
paramtres locaux du systme (dans les Options rgionales et linguistiques du Panneau de

Qt 4 Livre Page 401 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

401

configuration). Pour grer les vnements LocaleChange, nous pouvons rimplmenter


QWidget::changeEvent() comme suit :
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LocaleChange) {
appTranslator.load("callcenter_"
+ QLocale::system().name(), qmPath);
qtTranslator.load("qt_" + QLocale::system().name(), qmPath);
retranslateUi();
}
QMainWindow::changeEvent(event);
}

Si lutilisateur change les paramtres locaux pendant que lapplication est en cours dexcution,
nous tentons de charger les fichiers de traduction corrects pour les nouveaux paramtres et
appelons retranslateUi() pour mettre jour linterface utilisateur. Dans tous les cas, nous
transmettons lvnement la fonction changeEvent() de la classe de base, cette dernire
pouvant aussi tre intresse par LocaleChange ou dautres vnements de changement.
Nous en avons maintenant termin avec lexamen du code de MainWindow. Nous allons
prsent observer le code de lune des classes de widget de lapplication, la classe JournalView, afin de dterminer quelles modifications sont ncessaires pour lui permettre de prendre
en charge la traduction dynamique.
JournalView::JournalView(QWidget *parent)
: QTableWidget(parent)
{

retranslateUi();
}

La classe JournalView est une sous-classe de QTableWidget. A la fin du constructeur, nous


appelons la fonction prive retranslateUi() pour dfinir les chanes des widgets. Cette
opration est similaire celle que nous avons effectue pour MainWindows.
void JournalView::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange)
retranslateUi();
QTableWidget::changeEvent(event);
}

Nous rimplmentons galement la fonction changeEvent() pour appeler retranslateUi() sur les vnements LanguageChange. Qt gnre un vnement LanguageChange
lorsque le contenu dun QTranslator install sur QApplication change. Dans notre application, ceci se produit lorsque nous appelons load() sur appTranslator ou qtTranslator
depuis MainWindow::switchLanguage() ou MainWindow::changeEvent().

Qt 4 Livre Page 402 Jeudi, 7. dcembre 2006 12:14 12

402

Qt4 et C++ : Programmation dinterfaces GUI

Les vnements LanguageChange ne doivent pas tre confondus avec les vnements LocaleChange. Ces derniers sont gnrs par le systme et demandent lapplication de charger une
nouvelle traduction. Les vnements LanguageChange sont gnrs par Qt et demandent aux
widgets de lapplication de traduire toutes leurs chanes.
Lorsque nous avons implment MainWindow, il ne nous a pas t ncessaire de rpondre
LanguageChange. Nous avons simplement appel retranslateUi() chaque appel de
load() sur un QTranslator.
void JournalView::retranslateUi()
{
QStringList labels;
labels << tr("Time") << tr("Priority") << tr("Phone Number")
<< tr("Subject");
setHorizontalHeaderLabels(labels);
}

La fonction retranslateUi() met jour les en-ttes de colonnes avec les textes nouvellement traduits, compltant ainsi la partie traduction du code dun widget crit la main. Pour
ce qui est des widgets et des botes de dialogue dvelopps avec Qt Designer, loutil uic
gnre automatiquement une fonction similaire retranslateUi(). Celle-ci est automatiquement appele en rponse aux vnements LanguageChange.

Traduire les applications


La traduction dune application Qt qui contient des appels tr() est un processus en trois tapes :
1. Excution de lupdate pour extraire toutes les chanes visibles par lutilisateur du code
source de lapplication.
2. Traduction de lapplication au moyen de Qt Linguist.
3. Excution de lrelease pour gnrer des fichiers binaires .qm que lapplication peut charger
au moyen de QTranslator.
La responsabilit des tapes 1 et 3 revient aux dveloppeurs. Ltape 2 est gre par les traducteurs.
Ce cycle peut tre rpt aussi souvent que ncessaire pendant le dveloppement et la dure de
vie de lapplication.
Nous allons, par exemple, vous montrer comment traduire lapplication Spreadsheet du Chapitre 3.
Cette application contient dj des appels de tr() encadrant chaque chane visible par lutilisateur.
Dans un premier temps, nous devons modifier le fichier .pro de lapplication pour prciser
quelles langues nous souhaitons prendre en charge. Si, par exemple, nous voulons prendre en
charge lallemand et le franais en plus de langlais, nous ajoutons lentre TRANSLATIONS
suivante spreadsheet.pro:
TRANSLATIONS = spreadsheet_de.ts \
spreadsheet_fr.ts

Qt 4 Livre Page 403 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

403

Ici, nous mentionnons deux fichiers de traduction : un pour lallemand et un pour le franais.
Ces fichiers seront crs lors de la premire excution de lupdate et seront chargs chacune
de ses excutions ultrieures.
Ces fichiers possdent normalement une extension .ts. Ils se trouvent dans un format XML
simple et ne sont pas aussi compacts que les fichiers binaires .qm compris par QTranslator.
La tche de conversion des fichiers .ts lisibles par lhomme en fichiers .qm efficaces pour la
machine revient lrelease. Pour les esprits curieux, .ts et .qm signifient fichier "translation
source" et fichier "Qt message", respectivement.
En supposant que nous nous trouvions dans le rpertoire contenant le code source de lapplication Spreadsheet, nous pouvons excuter lupdate sur spreadsheet partir de la ligne de
commande comme suit :
lupdate -verbose spreadsheet.pro

Loption verbose invite lupdate fournir plus de commentaires que dhabitude. Voici la
sortie attendue :
Updating
Found
Updating
Found

spreadsheet_de.ts...
98 source texts (98 new and 0 already existing)
spreadsheet_fr.ts...
98 source texts (98 new and 0 already existing)

Chaque chane qui apparat dans un appel de tr() dans le code source de lapplication est
stocke dans les fichiers .ts, avec une traduction vide. Les chanes qui apparaissent dans les
fichiers .ui de lapplication sont galement incluses.
Loutil lupdate suppose par dfaut que les arguments de tr() sont des chanes Latin-1. Si tel
nest pas le cas, nous devons ajouter une entre CODECFORTR au fichier .pro. Par exemple :
CODECFORTR

= EUC-JP

Cette opration doit tre ralise en plus de lappel QTextCodec::setCodecForTr()


depuis la fonction main() de lapplication.
Les traductions sont alors ajoutes aux fichiers spreadsheet_de.ts et spreadsheet_fr.ts
au moyen de Qt Linguist.
Pour excuter Qt Linguist, cliquez sur Qt by Trolltech v4.x.y/Linguist dans le menu Dmarrer
sous Windows, saisissez linguist sur la ligne de commande sous Unix ou double-cliquez sur
Linguist dans le Finder de Mac OS X. Pour commencer ajouter des traductions un fichier
.ts, cliquez sur File/Open et slectionnez le fichier traduire.
La partie gauche de la fentre principale de Qt Linguist affiche la liste des contextes pour
lapplication en cours de traduction. En ce qui concerne lapplication Spreadsheet, les contextes sont "FindDialog", "GoToCellDialog", "MainWindows", "SortDialog" et "Spreadsheet".La
zone suprieure droite est la liste des textes sources pour le contexte en cours. Chaque texte
source est prsent avec une traduction et un indicateur Done. La zone du milieu nous permet

Qt 4 Livre Page 404 Jeudi, 7. dcembre 2006 12:14 12

404

Qt4 et C++ : Programmation dinterfaces GUI

dentrer une traduction pour llment source activ. Dans la zone infrieure droite apparat
une liste de suggestions fournies automatiquement par Qt Linguist.
Lorsque nous disposons dun fichier .ts traduit, nous devons le convertir en un fichier
binaire .qm afin quil puisse tre exploit par QTranslator. Pour effectuer cette opration
depuis Qt Linguist, cliquez sur File/Release. Nous commenons gnralement par
traduire quelques chanes et nous excutons lapplication avec le fichier .qm pour nous assurer
que tout fonctionne correctement. (Voir Figure 17.2)
Figure 17.2
Qt Linguist en action

Si vous souhaitez gnrer les fichiers .qm pour tous les fichiers .ts, vous pouvez utiliser
loutil lrelease comme suit :
lrelease -verbose spreadsheet.pro

En supposant que vous avez traduit dix-neuf chanes en franais et cliqu sur lindicateur Done
pour dix-sept dentre elles, lrelease produit la sortie suivante :
Updating spreadsheet_de.qm...
Generated 0 translations (0 finished and 0 unfinished)
Ignored 98 untranslated source texts
Updating spreadsheet_fr.qm...
Generated 19 translations (17 finished and 2 unfinished)
Ignored 79 untranslated source texts

Les chanes non traduites sont prsentes dans les langues initiales lors de lexcution de
lapplication. Lindicateur Done est ignor par lrelease. Il peut tre utilis par les traducteurs
pour identifier les traductions termines et celles qui ont encore besoin dtre rvises.

Qt 4 Livre Page 405 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 17

Internationalisation

405

Lorsque nous modifions le code source de lapplication, les fichiers de traduction deviennent
obsoltes. La solution consiste excuter de nouveau lupdate, fournir les traductions pour
les nouvelles chanes et regnrer les fichiers .qm. Certaines quipes de dveloppement
considrent quil faut rgulirement excuter lupdate, alors que dautres prfrent attendre
que lapplication soit prte tre commercialise.
Les outils lupdate et Qt Linguist sont relativement intelligents. Les traductions qui ne sont
plus utilises sont conserves dans les fichiers .ts au cas o elles seraient ncessaires dans des
versions ultrieures. Lors de la mise jour des fichiers .ts, lupdate utilise un algorithme de
fusion intelligent qui permet aux traducteurs dconomiser un temps considrable dans le cas
de texte identique ou similaire utilis dans des contextes diffrents.
Pour plus dinformations concernant Qt Linguist, lupdate et lrelease, rfrez-vous au
manuel Qt Linguist ladresse http://doc.trolltech.com/4.1/linguist-manual.html. Ce
manuel contient une tude dtaille de linterface utilisateur de Qt Linguist et un didactitiel
pour les programmeurs.

Qt 4 Livre Page 406 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 407 Jeudi, 7. dcembre 2006 12:14 12

18
Environnement multithread
Au sommaire de ce chapitre
Crer des threads
Synchroniser des threads
Communiquer avec le thread principal
Utiliser les classes Qt dans les threads
secondaires

Les applications GUI conventionnelles possdent un thread dexcution et ralisent, en rgle


gnrale, une seule opration la fois. Si lutilisateur invoque une opration longue depuis
linterface utilisateur, cette dernire se fige pendant la progression de lopration. Le Chapitre 7 prsente des solutions ce problme. Lenvironnement multithread en est une autre.
Dans une application multithread, linterface utilisateur graphique sexcute dans son
propre thread et le traitement a lieu dans un ou plusieurs threads distincts. De cette
faon, linterface utilisateur graphique des applications reste ractive, mme pendant un
traitement intensif. Un autre avantage de lenvironnement multitread est le suivant : les
systmes multiprocesseurs peuvent excuter plusieurs threads simultanment sur diffrents
processeurs, offrant ainsi de meilleures performances.
Dans ce chapitre, nous allons commencer par vous montrer comment driver QThread et
comment utiliser QMutex, QSemaphore ainsi que QWaitCondition pour synchroniser

Qt 4 Livre Page 408 Jeudi, 7. dcembre 2006 12:14 12

408

Qt4 et C++ : Programmation dinterfaces GUI

les threads. Puis nous vous expliquerons comment communiquer avec le thread principal
depuis les threads secondaires pendant que la boucle dvnement est en cours dexcution.
Nous conclurons enfin par une tude des classes Qt susceptibles ou non dtre utilises dans
des threads secondaires.
Lenvironnement multithread est un sujet vaste, auquel de nombreux livres sont exclusivement
consacrs. Ici, nous supposons que vous matrisez les bases de la programmation multithread.
Nous insisterons davantage sur la faon de dvelopper des applications Qt multithread plutt
que sur le thme du dcoupage en threads lui-mme.

Crer des threads


Il nest pas difficile de fournir plusieurs threads dans une application Qt : il suffit de driver
QThread et de rimplmenter sa fonction run(). Pour illustrer cette opration, nous allons
commencer par examiner le code dune sous-classe QThread trs simple qui affiche de faon
rpte une chane donne sur une console.
class Thread: public QThread
{
Q_OBJECT
public:
Thread();
void setMessage(const QString &message);
void stop();
protected:
void run();
private:
QString messageStr;
volatile bool stopped;
};

La classe Thread hrite de QThread et rimplmente la fonction run(). Elle fournit deux
fonctions supplmentaires : setMessage() et stop().
La variable stopped est dclare volatile car il est possible dy accder depuis plusieurs
threads et nous souhaitons garantir une lecture de sa version actualise chaque fois quelle est
ncessaire. Si nous omettons le mot-cl volatile, le compilateur peut optimiser laccs la
variable, ce qui risque daboutir des rsultats incorrects.
Thread::Thread()
{
stopped = false;
}

Nous avons dfini stopped en false dans le constructeur.

Qt 4 Livre Page 409 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

409

void Thread::run()
{
while (!stopped)
cerr << qPrintable(messageStr);
stopped = false;
cerr << endl;
}

La fonction run() est appele pour lancer lexcution du thread. Tant que la variable stopped
est dfinie en false, la fonction continue afficher le message donn sur la console. Le thread
se termine lorsque la fonction run() rend le contrle.
void Thread::stop()
{
stopped = true;
}

La fonction stop() dfinit la variable stopped en true, indiquant ainsi run() dinterrompre laffichage du texte sur la console. Cette fonction peut tre appele tout moment depuis
un thread quelconque. Dans le cadre de cet exemple, nous supposons que laffectation un
bool est une opration atomique. Cest une supposition raisonnable, si lon considre quun bool
ne peut avoir que deux tats. Nous verrons ultrieurement comment utiliser QMutex pour
garantir latomicit de laffectation une variable.
QThread fournit une fonction terminate() qui met fin lexcution dun thread pendant
quil est toujours en cours dexcution. Lemploi de terminate() nest pas conseill, car elle
peut mettre fin au thread tout moment et ne donne ce dernier aucune chance de lancer les
oprations de rcupration de la mmoire. Il est toujours prfrable de faire appel une variable
stopped et une fonction stop() comme cest le cas ici.
Figure 18.1
Lapplication Threads

A prsent, nous allons voir comment utiliser la classe Thread dans une petite application Qt
qui utilise deux threads, A et B, en plus du thread principal.
class ThreadDialog: public QDialog
{
Q_OBJECT
public:
ThreadDialog(QWidget *parent = 0);
protected:
void closeEvent(QCloseEvent *event);
private slots:
void startOrStopThreadA();
void startOrStopThreadB();

Qt 4 Livre Page 410 Jeudi, 7. dcembre 2006 12:14 12

410

Qt4 et C++ : Programmation dinterfaces GUI

private:
Thread threadA;
Thread threadB;
QPushButton *threadAButton;
QPushButton *threadBButton;
QPushButton *quitButton;
};

La classe ThreadDialog dclare deux variables de type Thread et quelques boutons pour
fournir une interface utilisateur de base.
ThreadDialog::ThreadDialog(QWidget *parent)
: QDialog(parent)
{
threadA.setMessage("A");
threadB.setMessage("B");
threadAButton = new QPushButton(tr("Start A"));
threadBButton = new QPushButton(tr("Start B"));
quitButton = new QPushButton(tr("Quit"));
quitButton->setDefault(true);
connect(threadAButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadA()));
connect(threadBButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadB()));

Dans le constructeur, nous appelons setMessage() pour amener le premier et le second thread
afficher de faon rpte des A et des B, respectivement.
void ThreadDialog::startOrStopThreadA()
{
if (threadA.isRunning()) {
threadA.stop();
threadAButton->setText(tr("Start A"));
} else {
threadA.start();
threadAButton->setText(tr("Stop A"));
}
}

Lorsque lutilisateur clique sur le bouton correspondant au thread A, startOrStopThreadA()


stoppe ce dernier sil tait en cours dexcution ou le lance dans le cas contraire. Elle met
galement jour le texte du bouton.
void ThreadDialog::startOrStopThreadB()
{
if (threadB.isRunning()) {

Qt 4 Livre Page 411 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

411

threadB.stop();
threadBButton->setText(tr("Start B"));
} else {
threadB.start();
threadBButton->setText(tr("Stop B"));
}
}

Le code de startOrStopThreadB() est trs similaire.


void ThreadDialog::closeEvent(QCloseEvent *event)
{
threadA.stop();
threadB.stop();
threadA.wait();
threadB.wait();
event->accept();
}

Si lutilisateur clique sur Quit ou ferme la fentre, nous arrtons tous les threads en cours
dexcution et attendons leur fin (en excutant QThread::wait()) avant dappeler QCloseEvent::accept(). Ce processus assure une fermeture correcte de lapplication, bien quil ne
prsente pas vraiment dimportance dans le cadre de cet exemple.
Si vous excutez lapplication et cliquez sur Start A, la console se remplit de A. Si vous
cliquez sur Start B, elle se remplit maintenant de squences alternatives de A et de B. Cliquez
sur Stop A, et elle naffiche alors que les B.

Synchroniser des threads


La synchronisation de plusieurs threads reprsente une ncessit pour la plupart des applications
multithread. Qt fournit les classes de synchronisation suivantes : QMutex, QReadWriteLock,
QSemaphore et QWaitCondition.
La classe QMutex offre un moyen de protger une variable ou une partie de code de sorte quun
seul thread puisse y accder la fois. Elle fournit une fonction lock() qui verrouille le mutex.
Si ce dernier est dverrouill, le thread en cours sen empare immdiatement et le verrouille.
Dans le cas contraire, le thread en cours est bloqu jusqu ce que celui qui le contient le dverrouille. Dans tous les cas, lorsque lappel lock() se termine, le thread en cours conserve le
mutex jusqu ce que unlock() soit appel. La classe QMutex fournit galement une fonction
tryLock() qui se termine immdiatement si le mutex est dj verrouill.
Supposons, par exemple que nous souhaitions protger la variable stopped de la classe
Thread de la section prcdente avec un QMutex. Nous ajouterions alors la donne membre
suivante Thread:
private:

Qt 4 Livre Page 412 Jeudi, 7. dcembre 2006 12:14 12

412

Qt4 et C++ : Programmation dinterfaces GUI

QMutex mutex;
};

La fonction run() prendrait la forme suivante :


void Thread::run()
{
forever {
mutex.lock();
if (stopped) {
stopped = false;
mutex.unlock();
break;
}
mutex.unlock();
cerr << qPrintable(messageStr);
}
cerr << endl;
}

La fonction stop() prendrait la forme suivante :


void Thread::stop()
{
mutex.lock();
stopped = true;
mutex.unlock();
}

Les oprations de verrouillage et de dverrouillage dun mutex dans les fonctions complexes,
ou celles utilisant des exceptions C++, peuvent tre sujettes erreur. Qt offre la classe utilitaire
QMutexLocker pour simplifier la gestion des mutex. Le constructeur de QMutexLocker
accepte QMutex comme argument et le verrouille. Le destructeur de QMutexLocker dverrouille le mutex. Nous pourrions, par exemple, rcrire les fonctions run() et stop() comme
suit :
void Thread::run()
{
forever {
{
QMutexLocker locker(&mutex);
if (stopped) {
stopped = false;
break;
}
}
cerr << qPrintable(messageStr);
}
cerr << endl;
}

Qt 4 Livre Page 413 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

413

void Thread::stop()
{
QMutexLocker locker(&mutex);
stopped = true;
}

Le problme de lemploi des mutex est que seul un thread peut accder la mme variable la
fois. Dans les programmes avec de nombreux threads essayant de lire la mme variable simultanment (sans la modifier), le mutex risque de compromettre srieusement les performances.
Dans ces situations, nous pouvons faire appel QReadWriteLock, une classe de synchronisation
qui autorise des accs simultans en lecture seulement sans altrer les performances.
Dans la classe Thread, il serait illogique de remplacer QMutex par QReadWriteLock pour protger la variable stopped, car un thread au maximum tentera de lire la variable un moment donn.
Un exemple plus appropri impliquerait un ou plusieurs threads de lecture accdant des
donnes partages et un ou plusieurs threads dcriture modifiant les donnes. Par exemple :
MyData data;
QReadWriteLock lock;
void ReaderThread::run()
{
...
lock.lockForRead();
access_data_without_modifying_it(&data);
lock.unlock();
...
}
void WriterThread::run()
{
...
lock.lockForWrite();
modify_data(&data);
lock.unlock();
...
}

Pour des raisons de commodit, il est possible dutiliser les classes QReadLocker et QWriteLocker pour verrouiller et dverrouiller un QReadWriteLock.
QSemaphore est une autre gnralisation des mutex, mais contrairement au verrouillage en
lecture/criture, les smaphores peuvent tre employs pour protger un certain nombre de
ressources identiques. Les deux extraits de code suivants montrent la correspondance entre
QSemaphore et QMutex:
QSemaphore semaphore(1); QMutex mutex;
semaphore.acquire(); mutex.lock();
semaphore.release(); mutex.unlock();

Qt 4 Livre Page 414 Jeudi, 7. dcembre 2006 12:14 12

414

Qt4 et C++ : Programmation dinterfaces GUI

En transmettant 1 au constructeur, nous indiquons au smaphore quil ne contrle quune seule


ressource. Lavantage de lemploi dun smaphore est que nous pouvons transmettre un
nombre suprieur 1 au constructeur, puis appeler acquire() plusieurs reprise pour acqurir
de nombreuses ressources.
Une application typique des smaphores est le transfert dune certaine quantit de donnes
(DataSize) entre deux threads laide dune mmoire tampon circulaire partag dune
certaine taille (BufferSize) :
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];

Le thread producteur crit les donnes dans la mmoire tampon jusqu ce quil atteigne la fin,
puis recommence au dbut, crasant les donnes existantes. Le thread consommateur lit les
donnes lors de sa gnration. La Figure 18.2 illustre ce processus, avec une mmoire tampon
minuscule de 16 octets.
Figure 18.2
Le modle producteur/
consommateur

usedSpace(5)
0

A A G C C T
consommateur

freeSpace(11)
8

10 11 12 13 14 15

A C

producteur

Le besoin de synchronisation dans lexemple producteur/consommateur est double : si le


producteur gnre les donnes trop rapidement, il crasera celles que le consommateur naura
pas encore lues et si le consommateur lit trop rapidement, il dpassera le producteur et obtiendra
des donnes incohrentes.
Un moyen radical de rsoudre ce problme consiste amener le producteur remplir la
mmoire tampon, puis attendre que le consommateur ait lu cette dernire en entier, et ainsi de
suite. Mais sur les machines multiprocesseurs, ce processus nest pas aussi rapide que celui
consistant laisser les threads producteur et consommateur agir sur diffrentes parties de la
mmoire tampon en mme temps.
Pour rsoudre efficacement ce problme, il est possible de recourir deux smaphores :
QSemaphore freeSpace(BufferSize);
QSemaphore usedSpace(0);

Le smaphore freeSpace gouverne la partie de la mmoire tampon que le producteur remplit


de donnes. Le smaphore usedSpace gouverne la zone susceptible dtre lue par le consommateur. Ces deux rgions sont complmentaires. Le smaphore freeSpace est initialis avec
BufferSize (4096), ce qui signifie quil possde autant de ressources quil est possible den
acqurir. Lorsque lapplication dmarre, le thread lecteur commence par acqurir des octets

Qt 4 Livre Page 415 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

415

"libres" et les convertit en octets "utiliss".Le smaphore usedSpace est initialis avec la
valeur 0 pour sassurer que le consommateur ne lit pas des donnes incohrentes au dmarrage.
Dans cet exemple, chaque octet est considr comme une ressource. Dans une application du
monde rel, nous agirions probablement sur des units de taille plus importante (64 ou 256 octets
la fois, par exemple) pour rduire la surcharge associe lemploi des smaphores.
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "ACGT"[uint(rand()) % 4];
usedSpace.release();
}

Dans le producteur, chaque itration dbute par lacquisition dun octet "libre".Si la mmoire
tampon est remplie de donnes nayant pas encore t lues par le consommateur, lappel
acquire() bloquera le processus jusqu ce que le consommateur ait commenc consommer les donnes. Une fois loctet acquis, nous le remplissons de donnes alatoires (A, C,
G ou T) et le librons avec le statut "utilis", de sorte quil puisse tre lu par le thread
consommateur.
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
cerr << buffer[i % BufferSize];
freeSpace.release();
}
cerr << endl;
}

Dans le consommateur, nous commenons par acqurir un octet "utilis".Si la mmoire


tampon ne contient aucune donne lire, lappel acquire() bloquera le processus jusqu
ce que le producteur en ait produites. Une fois loctet acquis, nous laffichons et le librons
avec le statut "libre", en permettant ainsi au producteur de le remplir de nouveau avec des
donnes.
int main()
{
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}

Qt 4 Livre Page 416 Jeudi, 7. dcembre 2006 12:14 12

416

Qt4 et C++ : Programmation dinterfaces GUI

Enfin, nous lanons les threads producteur et consommateur dans main(). Le producteur
convertit alors de lespace "libre" en espace "utilis", puis le consommateur le reconvertit en
espace "libre".
Lorsque nous excutons le programme, il crit une squence alatoire de 100 000 A, C, G
et T sur la console et se termine. Pour vraiment comprendre ce qui se passe, nous pouvons
dsactiver lcriture de la sortie et crire la place un P chaque fois que le producteur
gnre un octet et c chaque fois que le consommateur en lit un. Pour simplifier les choses,
nous pouvons utiliser des valeurs plus petites pour DataSize et BufferSize.
Voici, par exemple, une excution possible avec un DataSize de 10 et un BufferSize de 4:
"PcPcPcPcPcPcPcPcPcPc". Dans ce cas, le consommateur lit les octets ds quils sont gnrs
par le producteur. Les deux threads sont excuts la mme vitesse. Il est galement possible
que le producteur remplisse toute la mmoire tampon avant que le consommateur ne
commence sa lecture : "PPPPccccPPPPccccPPcc". Il existe de nombreuses autres possibilits.
Les smaphores offrent une grande libert au planificateur de thread spcifique au systme, qui
peut tudier le comportement des threads et choisir une stratgie de planification approprie.
Une approche diffrente au problme de synchronisation dun producteur et dun consommateur consiste employer QWaitCondition et QMutex. Un QWaitCondition permet un
thread den rveiller dautres lorsque les conditions requises sont remplies. Cette approche
autorise un contrle plus prcis quavec les mutex seuls. Pour illustrer le fonctionnement de ce
processus, nous allons reprendre lexemple producteur/consommateur en dclarant des conditions
dattente.
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;

En plus du tampon, nous dclarons deux QWaitConditions, un QMutex et une variable qui
stocke le nombre doctets "utiliss" dans la mmoire tampon.
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
buffer[i % BufferSize] = "ACGT"[uint(rand()) % 4];
++usedSpace;
bufferIsNotEmpty.wakeAll();
mutex.unlock();
}
}

Qt 4 Livre Page 417 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

417

Dans le producteur, nous commenons par vrifier si la mmoire tampon est remplie. Si tel est
le cas, nous attendons la condition "buffer is not full".Une fois cette condition remplie, nous
crivons un octet dans la mmoire tampon, nous incrmentons usedSpace, puis nous rveillons
tout thread en attente de la condition "buffer is not full".
Nous faisons appel un mutex pour protger tous les accs la variable usedSpace. La fonction
QWaitCondition::wait() peut recevoir un mutex verrouill comme premier argument, quelle
dverrouille avant de bloquer le thread en cours puis verrouille avant de rendre le contrle.
Dans cet exemple, nous aurions pu remplacer la boucle while
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);

par cette instruction if:


if (usedSpace == BufferSize) {
mutex.unlock();
bufferIsNotFull.wait();
mutex.lock();
}

Ce processus risque dtre perturb si nous autorisons plusieurs threads producteurs, car un
autre producteur pourrait se saisir du mutex immdiatement aprs lappel de wait() et annuler
la condition "buffer is not full".
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == 0)
bufferIsNotEmpty.wait(&mutex);
cerr << buffer[i % BufferSize];
--usedSpace;
bufferIsNotFull.wakeAll();
mutex.unlock();
}
cerr << endl;
}

Laction du consommateur est trs exactement oppose celle du producteur : il attend la


condition "buffer is not full" et rveille tout thread en attente de la vrification de cette condition.
Dans tous les exemples tudis jusqu prsent, nos threads ont accd aux mmes variables
globales. Mais dans certaines applications, une variable globale doit contenir diffrentes
valeurs dans diffrents threads. Ce procd se nomme souvent stockage local de thread ou
donne spcifique aux threads. Nous pouvons le reproduire en utilisant un map index sur les
ID de thread (retourns par QThread::currentThread()), mais une approche plus lgante
consiste faire appel la classe QThreadStorage<T>.

Qt 4 Livre Page 418 Jeudi, 7. dcembre 2006 12:14 12

418

Qt4 et C++ : Programmation dinterfaces GUI

Cette classe est gnralement utilise pour les caches. En ayant un cache distinct dans les diffrents threads, nous vitons la surcharge du verrouillage, dverrouillage et dattente dun mutex.
Par exemple :
QThreadStorage<QHash<int, double> *> cache;
void insertIntoCache(int id, double value)
{
if (!cache.hasLocalData())
cache.setLocalData(new QHash<int, double>);
cache.localData()->insert(id, value);
}
void removeFromCache(int id)
{
if (cache.hasLocalData())
cache.localData()->remove(id);
}

La variable cache contient un pointeur vers un QMap<int,double> par thread. Lorsque nous
utilisons le cache pour la premire fois dans un thread particulier, hasLocalData() retourne
false et nous crons lobjet QHash<int,double>.

QThreadStorage<T> peut galement tre utilis pour les variables dtat derreur globales
(exactement comme errno) afin de sassurer que les modifications apportes un thread
naffectent pas les autres.

Communiquer avec le thread principal


Quand une application Qt dmarre, un seul thread sexcute le thread principal. Cest le seul
thread qui est autoris crer lobjet QApplication ou QCoreApplication et appeler
exec() sur celui-ci. Aprs lappel exec(), ce thread est soit en attente dun vnement, soit
en cours de traitement dun vnement.
Le thread principal peut dmarrer de nouveaux threads en crant les objets dune sous-classe
QThread, comme ce fut le cas dans la section prcdente. Si ces nouveaux threads doivent
communiquer entre eux, ils peuvent utiliser des variables partages avec des mutex, des
verrouillages en lecture/criture, des smaphores ou des conditions dattente. Mais aucune
de ces techniques ne peut tre exploite pour communiquer avec le thread principal, car elles
verrouilleraient la boucle de lvnement et figeraient linterface utilisateur.
La solution pour communiquer avec le thread principal depuis un thread secondaire consiste
faire appel des connexions signal/slot entre threads. En rgle gnrale, le mcanisme des
signaux et des slots opre de faon synchrone. Les slots sont donc connects un signal et
invoqus immdiatement une fois le signal mis, par le biais dun appel de fonction direct.

Qt 4 Livre Page 419 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

419

Cependant, lorsque nous connectons des objets "actifs" dans des threads diffrents, le mcanisme
devient asynchrone. (Ce comportement peut tre modifi par le biais dun cinquime paramtre facultatif de QObject::connect().) Ces connexions sont implmentes larrire-plan en
publiant un vnement. Le slot est alors appel par la boucle dvnement du thread dans
laquelle se trouve lobjet destinataire. Par dfaut, un QObject se situe dans le thread o il a t
cr. Ceci peut tre chang tout moment en appelant QObject::moveToThread().
Figure 18.3
Lapplication Image Pro

Pour illustrer le fonctionnement des connexions signal/slot entre threads, nous allons tudier le
code de lapplication Image Pro, une application de retouche dimages de base qui permet
lutilisateur de faire pivoter, de redimensionner et de changer la prcision des couleurs dune
image. Lapplication utilise un thread secondaire pour pouvoir raliser des oprations sur les images
sans verrouiller la boucle dvnement. La diffrence est significative lors du traitement de trs
grosses images. Le thread secondaire possde une liste de tches, ou "transactions", accomplir
et envoie les vnements la fentre principale pour indiquer la progression.
ImageWindow::ImageWindow()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Dark);
imageLabel->setAutoFillBackground(true);
imageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
setCentralWidget(imageLabel);
createActions();
createMenus();
statusBar()->showMessage(tr("Ready"), 2000);
connect(&thread, SIGNAL(transactionStarted(const QString &)),
statusBar(), SLOT(showMessage(const QString &)));
connect(&thread, SIGNAL(finished()),

Qt 4 Livre Page 420 Jeudi, 7. dcembre 2006 12:14 12

420

Qt4 et C++ : Programmation dinterfaces GUI

this, SLOT(allTransactionsDone()));
setCurrentFile("");
}

La partie la plus intressante du constructeur de ImageWindow contient les deux connexions


signal/slot. Toutes deux impliquent des signaux mis par lobjet TransactionThread, sur
lequel nous reviendrons dans un instant.
void ImageWindow::flipHorizontally()
{
addTransaction(new FlipTransaction(Qt::Horizontal));
}

Le slot flipHorizontally() cre une transaction "de rotation" et lenregistre au moyen de la


fonction prive addTransaction(). Les fonctions flipVertically(), resizeImage(),
convertTo32Bit(), convertTo8Bit() et convertTo1Bit() sont similaires.
void ImageWindow::addTransaction(Transaction *transact)
{
thread.addTransaction(transact);
openAction->setEnabled(false);
saveAction->setEnabled(false);
saveAsAction->setEnabled(false);
}

La fonction addTransaction() ajoute une transaction la file dattente de transactions du


thread secondaire et dsactive les actions Open, Save et Save As pendant que les transactions
sont en cours de traitement.
void ImageWindow::allTransactionsDone()
{
openAction->setEnabled(true);
saveAction->setEnabled(true);
saveAsAction->setEnabled(true);
imageLabel->setPixmap(QPixmap::fromImage(thread.image()));
setWindowModified(true);
statusBar()->showMessage(tr("Ready"), 2000);
}

Le slot allTransactionsDone() est appel quand la file dattente de transactions de TransactionThread se vide.
Passons maintenant la classe TransactionThread:
class TransactionThread: public QThread
{
Q_OBJECT
public:
void addTransaction(Transaction *transact);
void setImage(const QImage &image);
QImage image();

Qt 4 Livre Page 421 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

421

signals:
void transactionStarted(const QString &message);
protected:
void run();
private:
QMutex mutex;
QImage currentImage;
QQueue<Transaction *> transactions;
};

La classe TransactionThread conserve une liste des transactions grer et les excute les
unes aprs les autres larrire-plan.
void TransactionThread::addTransaction(Transaction *transact)
{
QMutexLocker locker(&mutex);
transactions.enqueue(transact);
if (!isRunning())
start();
}

La fonction addTransaction() ajoute une transaction la file dattente des transactions et


lance le thread de cette transaction sil nest pas dj en cours dexcution. Tous les accs la
variable membre transactions sont protgs par un mutex, le thread principal risquant de
les modifier par lintermdiaire de addTransaction() pendant que le thread secondaire
parcourt transactions.
void TransactionThread::setImage(const QImage &image)
{
QMutexLocker locker(&mutex);
currentImage = image;
}
QImage TransactionThread::image()
{
QMutexLocker locker(&mutex);
return currentImage;
}

Les fonctions setImage() et image() permettent au thread principal de dfinir limage sur
laquelle les transactions doivent tre effectues et de rcuprer limage rsultante une fois
toutes les transactions termines. Nous protgeons de nouveau les accs une variable membre
en utilisant un mutex.
void TransactionThread::run()
{
Transaction *transact;

Qt 4 Livre Page 422 Jeudi, 7. dcembre 2006 12:14 12

422

Qt4 et C++ : Programmation dinterfaces GUI

forever {
mutex.lock();
if (transactions.isEmpty()) {
mutex.unlock();
break;
}
QImage oldImage = currentImage;
transact = transactions.dequeue();
mutex.unlock();
emit transactionStarted(transact->message());
QImage newImage = transact->apply(oldImage);
delete transact;
mutex.lock();
currentImage = newImage;
mutex.unlock();
}
}

La fonction run() parcourt la file dattente des transactions et les excute chacune tour tour
en appelant apply() sur celles-ci.
Quand une transaction est lance, nous mettons le signal transactionStarted() avec un
message afficher dans la barre dtat de lapplication. Une fois le traitement de toutes les
transactions termin, la fonction run() se termine et QThread met le signal finished().
class Transaction
{
public:
virtual ~Transaction() { }
virtual QImage apply(const QImage &image) = 0;
virtual QString message() = 0;
};

La classe Transaction est une classe de base abstraite pour les oprations susceptibles dtre
effectues par lutilisateur sur une image. Le destructeur virtuel est ncessaire, car nous devons
supprimer les instances des sous-classes Transaction par le biais dun pointeur Transaction.
(Si nous lomettons, certains compilateurs mettent un avertissement.) Transaction possde
trois sous-classes concrtes : FlipTransaction, ResizeTransaction et ConvertDepthTransaction. Nous ntudierons que FlipTransaction, les deux autres classes tant similaires.
class FlipTransaction: public Transaction
{
public:
FlipTransaction(Qt::Orientation orientation);
QImage apply(const QImage &image);
QString message();

Qt 4 Livre Page 423 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 18

Environnement multithread

423

private:
Qt::Orientation orientation;
};

Le constructeur FlipTransaction reoit un paramtre qui prcise lorientation de la rotation


(horizontale ou verticale).
QImage FlipTransaction::apply(const QImage &image)
{
return image.mirrored(orientation == Qt::Horizontal,
orientation == Qt::Vertical);
}

La fonction apply() appelle QImage::mirrored() sur le QImage reu en tant que paramtre
et retourne le QImage rsultant.
QString FlipTransaction::message()
{
if (orientation == Qt::Horizontal) {
return QObject::tr("Flipping image horizontally...");
} else {
return QObject::tr("Flipping image vertically...");
}
}

La fonction message() retourne le message afficher dans la barre dtat pendant la progression de lopration. Cette fonction est appele dans TransactionThread::run() lors de
lmission du signal transactionStarted().

Utiliser les classes Qt dans les threads secondaires


Une fonction est dite thread-safe quand elle peut tre appele sans problme depuis plusieurs
threads simultanment. Si deux fonctions thread-safe sont appeles depuis des threads diffrents sur la mme donne partage, le rsultat est toujours dfini. Par extension, une classe est
dite thread-safe quand toutes ses fonctions peuvent tre appeles simultanment depuis des
threads diffrents sans quil y ait dinterfrences entre elles.
Les classes thread-safe de Qt sont QMutex, QMutexLocker, QReadWriteLock, QReadLocker,
QWriteLocker, QSemaphore, QThreadStorage<T>, QWaitCondition et certaines parties de
lAPI de QThread. En outre, plusieurs fonctions sont thread-safe, dont QObject::connect(),
QObject::disconnect(), QCoreApplication::postEvent(), QCoreApplication::removePostedEvent() et QCoreApplication::removePostedEvents().
La plupart des classes non GUI de Qt rpondent une exigence moins stricte : elles sont rentrantes. Une classe est rentrante si diffrentes instances de la classe peuvent tre utilises simultanment dans des threads diffrents. Cependant, laccs simultan au mme objet rentrant dans
plusieurs threads nest pas scuris, et un tel accs doit tre protg avec un mutex. Les classes

Qt 4 Livre Page 424 Jeudi, 7. dcembre 2006 12:14 12

424

Qt4 et C++ : Programmation dinterfaces GUI

rentrantes sont marques comme telles dans la documentation de rfrence Qt. En rgle gnrale, toute classe C++ ne faisant pas rfrence une donne globale ou partage est rentrante.
QObject est rentrant, mais trois contraintes doivent tre prises en considration :
Les QObject enfants doivent tre crs dans le thread de leur parent.
Ceci signifie que les objets crs dans un thread secondaire ne doivent jamais tre crs
avec le mme objet QThread que leur parent, car cet objet a t cr dans un autre thread
(soit dans le thread principal, soit dans un thread secondaire diffrent).
Nous devons supprimer tous les QObject crs dans un thread secondaire avant denvisager la
suppression de lobjet QThread correspondant.
Pour ce faire, nous pouvons crer les objets sur la pile dans QThread::run().
Les QObject doivent tre supprims dans le thread les ayant crs.
Si nous devons supprimer un QObject situ dans un thread diffrent, nous devons appeler
la fonction QObject::deleteLater(), qui publie un vnement "deferred delete"
(suppression diffre).
Les sous-classes QObject non GUI, telles que QTimer, QProcess et les classes de rseau, sont
rentrantes. Nous pouvons les utiliser dans tout thread, ceci tant que ce thread possde une
boucle dvnement. En ce qui concerne les threads secondaires, la boucle dvnement est
dmarre en appelant QThread::exec() ou par des fonctions utilitaires telles que QProcess::waitForFinished() et QAbstractSocket::waitForDisconnected().
QWidget et ses sous-classes ne sont pas rentrantes, cause des limites hrites des bibliothques sur lesquelles repose la prise en charge GUI de Qt. Par consquent, nous ne pouvons
pas appeler directement des fonctions depuis un thread secondaire sur un widget. Pour changer, par exemple, le texte dun QLabel depuis un thread secondaire, nous pouvons mettre un
signal connect QLabel::setText() ou appeler QMetaObject::invokeMethod() depuis
ce thread. Par exemple :
void MyThread::run()
{
...
QMetaObject::invokeMethod(label, SLOT(setText(const QString &)),
Q_ARG(QString, "Hello"));
...
}

De nombreuses classes non GUI de Qt, dont QImage, QString et les classes conteneur, utilisent le partage implicite comme technique doptimisation. Bien que cette optimisation ne rende
gnralement pas une classe rentrante, ceci ne pose pas de problme dans Qt. En effet, des
instructions de langage assembleur atomiques sont employes pour implmenter un dcompte
de rfrences thread-safe, qui rend les classes implicitement partages de Qt rentrantes.
Le module QtSql de Qt peut galement tre employ dans les applications multithread, mais il
prsente des restrictions qui varient dune base de donnes une autre. Vous trouverez plus de
dtails ladresse http://doc.trolltech.com/4.1/sql-driver.html.

Qt 4 Livre Page 425 Jeudi, 7. dcembre 2006 12:14 12

19
Crer des plug-in
Au sommaire de ce chapitre
Dvelopper Qt avec les plug-in
Crer des applications capables
de grer les plug-in
Ecrire des plug-in pour des
applications

Les bibliothques dynamiques (galement nommes bibliothques partages ou DLL)


sont des modules indpendants stocks dans un fichier spar sur le disque et auxquels
de multiples applications peuvent accder. Les programmes spcifient gnralement les
bibliothques dynamiques qui leur sont ncessaires au moment de la liaison, auquel cas
ces bibliothques sont automatiquement charges lors du dmarrage de lapplication.
Cette approche implique gnralement lajout de la bibliothque ainsi que de son
chemin daccs dinclusion au fichier .pro de lapplication. En outre, les en-ttes
adquats sont inclus dans les fichiers sources. Par exemple :
LIBS
INCLUDEPATH

+= -ldb_cxx
+= /usr/local/BerkeleyDB.4.2/include

Lalternative consiste charger dynamiquement la bibliothque quand elle est requise, puis
rsoudre les symboles que nous souhaitons utiliser. Qt fournit la classe QLibrary pour y
parvenir dune manire indpendante de la plate-forme. A partir dun nom de bibliothque,

Qt 4 Livre Page 426 Jeudi, 7. dcembre 2006 12:14 12

426

Qt4 et C++ : Programmation dinterfaces GUI

QLibrary examine les emplacements standard sur la plate-forme en question la recherche


dun fichier appropri. Pour le nom mimetype, par exemple, elle recherchera mimetype.dll
sous Windows, mimetype.so sous Linux et mimetype.dylib sous Mac OS X.
Les applications GUI modernes peuvent souvent tre dveloppes par lemploi des plug-in. Un
plug-in est une bibliothque dynamique qui implmente une interface particulire pour fournir
une fonctionnalit supplmentaire facultative. Par exemple, dans le Chapitre 5, nous avons
cr un plug-in pour intgrer un widget personnalis Qt Designer.
Qt reconnat son propre ensemble dinterfaces de plugin pour divers domaines, dont les formats
dimage, les pilotes de bases de donnes, les styles de widgets, le codage du texte et laccessibilit. La premire section de ce chapitre explique comment dvelopper Qt avec un plug-in.
Il est galement possible de crer des plug-in spcifiques des applications Qt particulires. Qt
facilite lcriture de tels lments par le biais de sa structure de plug-in, qui scurise galement
QLibrary contre les pannes et rend son utilisation plus aise. Dans les deux dernires sections
de ce chapitre, nous vous montrerons comment crer une application qui prend en charge les
plug-in et comment gnrer un plug-in personnalis pour une application.

Dvelopper Qt avec les plug-in


Qt peut tre dvelopp avec plusieurs types de plug-in, les plus courants tant les pilotes
de base de donnes, les formats dimages, les styles et les codecs de texte. Pour chaque type de
plug-in, nous avons gnralement besoin dun minimum de deux classes : une classe wrapper
(conteneur) de plug-in qui implmente les fonctions API gnriques, et une ou plusieurs classes
gestionnaires qui implmentent chacune lAPI dun type de plug-in particulier. Les gestionnaires
sont accessibles par le biais de la classe wrapper. (Voir Figure 19.1).
Classe de plug-in

Classe de base gestionnaire

QAccessibleBridgePlugin

QAccessibleBridge

QAccessiblePlugin

QAccessibleInterface

QIconEnginePlugin

QIconEngine

QImageIOPlugin

QImageIOHandler

QInputContextPlugin

QInputContext

QPictureFormatPlugin

N/A

QSqlDriverPlugin

QSqlDriver

QStylePlugin

QStyle

QTextCodecPlugin

QTextCodec

Figure 19.1
Les classes gestionnaire et de plug-in de Qt ( lexception de Qtopia Core)

Qt 4 Livre Page 427 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

427

Pour illustrer ceci, nous allons implmenter un plug-in capable de lire des fichiers curseur
(fichiers .cur) Windows monochromes. Ces fichiers peuvent contenir plusieurs images du
mme curseur des tailles diffrentes. Une fois le plug-in de curseur cr et install, Qt sera en
mesure de lire les fichiers .cur et daccder aux curseurs individuels (par le biais de QImage,
QImageReader ou QMovie, par exemple). Il pourra galement convertir les curseurs en tout
autre format de fichier dimage, tel que BMP, JPEG et PNG. Le plug-in pourrait aussi tre
dploy avec les applications Qt, car elles contrlent automatiquement les emplacements standard
des plug-in Qt et chargent ceux quelles trouvent.
Les nouveaux conteneurs de plug-in de format dimage doivent sous-classer QImageIOPlugin
et rimplmenter quelques fonctions virtuelles :
class CursorPlugin: public QImageIOPlugin
{
public:
QStringList keys() const;
Capabilities capabilities(QIODevice *device,
const QByteArray &format) const;
QImageIOHandler *create(QIODevice *device,
const QByteArray &format) const;
};

La fonction keys() retourne une liste de formats dimage pris en charge par le plug-in. Le
paramtre format des fonctions capabilities() et create() est suppos avoir une valeur
qui provient de cette liste.
QStringList CursorPlugin::keys() const
{
return QStringList() << "cur";
}

Notre plug-in ne prend en charge quun seul format dimage. Il retourne donc une liste avec un
nom unique. Idalement, le nom doit tre lextension de fichier utilise pour ce format. Dans le
cas de formats avec plusieurs extensions (telles que .jpg et .jpeg pour JPEG), nous pouvons
retourner une liste avec plusieurs entres, chacune correspondant une extension.
QImageIOPlugin::Capabilities
CursorPlugin::capabilities(QIODevice *device,
const QByteArray &format) const
{
if (format == "cur")
return CanRead;
if (format.isEmpty()) {
CursorHandler handler;
handler.setDevice(device);
if (handler.canRead())
return CanRead;
}
return 0;
}

Qt 4 Livre Page 428 Jeudi, 7. dcembre 2006 12:14 12

428

Qt4 et C++ : Programmation dinterfaces GUI

La fonction capabilities() retourne ce que le gestionnaire dimage est en mesure daccomplir


avec le format dimage donn. Il existe trois possibilits (CanRead, CanWrite et CanReadIncremental), et la valeur de retour est un oprateur de bits OR des possibilits qui sappliquent.
Si le format est "cur", notre implmentation retourne CanRead. Si aucun format nest fourni,
nous crons un gestionnaire de curseur et vrifions sil est capable de lire les donnes depuis le
priphrique en question. La fonction canRead() lit uniquement les donnes, essayant de
dterminer si elle reconnat le fichier, sans en changer le pointeur. Une possibilit de 0 indique
que ce gestionnaire ne peut ni lire ce fichier, ni y crire des donnes.
QImageIOHandler *CursorPlugin::create(QIODevice *device,
const QByteArray &format) const
{
CursorHandler *handler = new CursorHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}

Quand un fichier curseur est ouvert (par QImageReader, par exemple), la fonction create()
du conteneur de plug-in est appele avec le pointeur de priphrique et avec le format "cur".
Nous crons une instance de CursorHandler et la configurons avec le priphrique et le format
spcifis. Lappelant prend la proprit du gestionnaire et le supprimera quand il ne sera plus
utile. Si plusieurs fichiers doivent tre lus, un nouveau gestionnaire peut tre cr pour chacun.
Q_EXPORT_PLUGIN2(cursorplugin, CursorPlugin)

A la fin du fichier .cpp, nous utilisons la macro Q_EXPORT_PLUGIN2() pour sassurer que le
plug-in est reconnu par Qt. Le premier paramtre est un nom arbitraire que nous souhaitons
attribuer au plug-in. Le second paramtre est le nom de classe du plug-in.
Il est facile de sous-classer QImageIOPlugin. Le travail vritable du plug-in est effectu dans
le gestionnaire. Les gestionnaires de format dimage doivent sous-classer QImageIOHandler
et rimplmenter ses fonctions publiques en totalit ou en partie. Commenons par len-tte :
class CursorHandler: public QImageIOHandler
{
public:
CursorHandler();
bool canRead() const;
bool read(QImage *image);
bool jumpToNextImage();
int currentImageNumber() const;
int imageCount() const;
private:
enum State { BeforeHeader, BeforeImage, AfterLastImage, Error };
void readHeaderIfNecessary() const;

Qt 4 Livre Page 429 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

429

QBitArray readBitmap(int width, int height, QDataStream &in) const;


void enterErrorState() const;
mutable State state;
mutable int currentImageNo;
mutable int numImages;
};

Les signatures de toutes les fonctions publiques sont fixes. Nous avons omis quelques fonctions dont la rimplmentation est inutile pour un gestionnaire en lecture seulement, en particulier write(). Les variables de membre sont dclares avec le mot-cl mutable, car elles
sont modifies lintrieur des fonctions const.
CursorHandler::CursorHandler()
{
state = BeforeHeader;
currentImageNo = 0;
numImages = 0;
}

Lors de la construction du gestionnaire, nous commenons par dfinir son tat. Nous appliquons au premier curseur le numro dimage du curseur en cours. Ici, numImage tant dfini en
0, il est vident que nous navons pas encore dimage.
bool CursorHandler::canRead() const
{
if (state == BeforeHeader) {
return device()->peek(4) == QByteArray("\0\0\2\0", 4);
} else {
return state!= Error;
}
}

La fonction canRead() peut tre appele tout moment pour dterminer si le gestionnaire
dimages peut lire des donnes supplmentaires depuis le priphrique. Si la fonction est appele avant que nous ayons lu toute donne et alors que nous nous trouvons toujours dans ltat
BeforeHeader, nous vrifions la signature particulire qui identifie les fichiers curseur
Windows. Lappel QIODevice::peek() lit les quatre premiers octets sans changer le pointeur
du fichier du priphrique. Si canRead() est appele ultrieurement, nous retournons true
quand aucune erreur ne sest produite.
int CursorHandler::currentImageNumber() const
{
return currentImageNo;
}

Cette fonction retourne le numro du curseur au niveau duquel le pointeur de fichier du priphrique est positionn. Une fois le gestionnaire construit, il est possible pour lutilisateur
dappeler toutes ses fonctions publiques, dans un ordre quelconque. Cest un problme potentiel,
car nous devons partir du principe que nous ne pouvons effectuer quune lecture squentielle.

Qt 4 Livre Page 430 Jeudi, 7. dcembre 2006 12:14 12

430

Qt4 et C++ : Programmation dinterfaces GUI

Nous devons donc lire len-tte de fichier au moins une fois avant tout autre lment. Nous
rsolvons le problme en appelant la fonction readHeaderIfNecessary().
int CursorHandler::imageCount() const
{
readHeaderIfNecessary();
return numImages;
}

Cette fonction retourne le nombre dimages dans le fichier. Pour un fichier valide o aucune
erreur de lecture ne sest produite, elle retournera un dcompte dau moins 1 (voir
Figure 19.2).
Header
quint16 reserved (0x0000)

Image 1

Image n

quint32 size

quint32

quint16 type (0x0002)

quint32 width

quint32

quint16 image count

quint32 height
quint16 planes
quint16 bits per pixel

quint32

quint16

Figure 19.2
Le format
de fichier .cur

quint32 compression
-- 20) + 8 bytes color table
(size +

width height bytes XOR bitmap


width height bytes AND bitmap

La fonction suivante est importante. Nous ltudierons donc par fragments :


bool CursorHandler::read(QImage *image)
{
readHeaderIfNecessary();
if (state!= BeforeImage)
return false;

La fonction read() lit les donnes de toute image, en commenant lemplacement du pointeur de priphrique courant. Nous pouvons lire limage suivante si len-tte du fichier est lu
avec succs, ou aprs quune image ait t lue et que le pointeur de priphrique se dplace au
dbut dune autre image.
quint32 size;
quint32 width;
quint32 height;
quint16 numPlanes;
quint16bitsPerPixel;
quint32 compression;
QDataStream in(device());

Qt 4 Livre Page 431 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

431

in.setByteOrder(QDataStream::LittleEndian);
in >> size;
if (size!= 40) {
enterErrorState();
return false;
}
in >> width >> height >> numPlanes >> bitsPerPixel >> compression;
height /= 2;
if (numPlanes!= 1 || bitsPerPixel!= 1 || compression!= 0) {
enterErrorState();
return false;
}
in.skipRawData((size - 20) + 8);

Nous crons un QDataStream pour lire le priphrique. Nous devons dfinir un ordre doctets
correspondant celui mentionn par la spcification de format de fichier .cur. Il nest pas
besoin de dfinir un numro de version QDataStream, car le format de nombres entiers et de
nombres virgule flottante ne varie pas entre les versions de flux de donnes. Nous lisons
ensuite les divers lments de len-tte du curseur. Nous passons les parties inutiles de len-tte
et la table des couleurs de 8 octets au moyen de QDataStream::skipRawData(). Nous
devons revoir toutes les caractristiques du format par exemple en diminuant de moiti la
hauteur car le format .cur fournit une hauteur qui est deux fois suprieure celle de limage
vritable. Les valeurs bitsPerPixel et compression sont toujours de 1 et de 0 dans un
fichier .cur monochrome. Si nous rencontrons des problmes, nous pouvons appeler enterErrorState() et retourner false.
QBitArray xorBitmap = readBitmap(width, height, in);
QBitArray andBitmap = readBitmap(width, height, in);
if (in.status()!= QDataStream::Ok) {
enterErrorState();
return false;
}

Les lments suivants du fichier sont deux bitmaps, lune tant un masque XOR et lautre un
masque AND. Nous les lisons dans les QBitArray plutt que dans les QBitmap. Un QBitmap
est une classe conue pour tre dessine lcran, mais ici, nous avons besoin dun tableau
doctets ordinaire. Lorsque nous en avons fini avec la lecture du fichier, nous vrifions ltat du
QDataStream. Cette opration fonctionne correctement, car si un QDataStream entre dans un
tat derreur, il y reste et ne peut retourner que des zros. Si, par exemple, la lecture choue au
niveau du premier tableau doctets, la tentative de lecture du deuxime rsulte en un QBitArray vide.
*image = QImage(width, height, QImage::Format_ARGB32);
for (int i = 0; i < int(height); ++i) {
for (int j = 0; j < int(width); ++j) {
QRgb color;

Qt 4 Livre Page 432 Jeudi, 7. dcembre 2006 12:14 12

432

Qt4 et C++ : Programmation dinterfaces GUI

int bit = (i * width) + j;


if (andBitmap.testBit(bit)) {
if (xorBitmap.testBit(bit)) {
color = 0x7F7F7F7F;
} else {
color = 0x00FFFFFF;
}
} else {
if (xorBitmap.testBit(bit)) {
color = 0xFFFFFFFF;
} else {
color = 0xFF000000;
}
}
image->setPixel(j, i, color);
}
}

Nous construisons un nouveau QImage de la taille correcte et dfinissons image de faon quil
pointe ver celui-ci. Puis nous parcourons chaque pixel des tableaux doctets XOR et AND et nous
les convertissons en spcifications de couleur ARGB de 32 bits. Les tableaux doctets AND et
XOR sont utiliss comme prsent dans le tableau suivant pour obtenir la couleur de chaque
pixel du curseur :
AND

XOR

Rsultat

Pixel darrire-plan invers

Pixel transparent

Pixel blanc

Pixel noir

Les pixels noirs, blancs et transparents ne prsentent pas de problme, mais il nexiste aucun
moyen dobtenir un pixel darrire-plan invers au moyen dune spcification de couleur
ARGB sans connatre la couleur du pixel darrire-plan initial. En remplacement, nous utilisons
une couleur grise semi-transparente (0x7F7F7F7F).
++currentImageNo;
if (currentImageNo == numImages)
state = AfterLastImage;
return true;
}

Une fois la lecture de limage termine, nous mettons jour le numro de limage courante
ainsi que ltat si nous avons atteint la dernire image. A la fin de la fonction, le priphrique
sera positionn au niveau de limage suivante ou la fin du fichier.

Qt 4 Livre Page 433 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

433

bool CursorHandler::jumpToNextImage()
{
QImage image;
return read(&image);
}

La fonction jumpToNextImage() permet de passer une image. Pour des raisons de simplicit,
nous appelons simplement read() et ignorons le QImage rsultant. Une implmentation plus
efficace consisterait utiliser linformation stocke dans len-tte du fichier .cur pour passer
directement loffset appropri dans le fichier.
void CursorHandler::readHeaderIfNecessary() const
{
if (state!= BeforeHeader)
return;
quint16 reserved;
quint16 type;
quint16 count;
QDataStream in(device());
in.setByteOrder(QDataStream::LittleEndian);
in >> reserved >> type >> count;
in.skipRawData(16 * count);
if (in.status()!= QDataStream::Ok || reserved!= 0
|| type!= 2 || count == 0) {
enterErrorState();
return;
}
state = BeforeImage;
currentImageNo = 0;
numImages = int(count);
}

La fonction prive readHeaderIfNecessary() est appele depuis imageCount() et


read(). Si len-tte du fichier a dj t lu, ltat nest pas BeforeHeader et nous rendons
immdiatement le contrle. Dans le cas contraire, nous ouvrons un flux de donnes sur le priphrique, lisons quelques donnes gnriques (dont le nombre de curseurs dans le fichier) et
dfinissons ltat en BeforeImage. Le pointeur de fichier du priphrique est positionn avant
la premire image.
void CursorHandler::enterErrorState() const
{
state = Error;
currentImageNo = 0;
numImages = 0;
}

Qt 4 Livre Page 434 Jeudi, 7. dcembre 2006 12:14 12

434

Qt4 et C++ : Programmation dinterfaces GUI

Si une erreur se produit, nous supposons quil nexiste pas dimages valides et dfinissons
ltat en Error. Ltat du gestionnaire ne peut alors plus tre modifi.
QBitArray CursorHandler::readBitmap(int width, int height,
QDataStream &in) const
{
QBitArray bitmap(width * height);
quint8 byte;
quint32 word;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if ((j % 32) == 0) {
word = 0;
for (int k = 0; k < 4; ++k) {
in >> byte;
word = (word << 8) | byte;
}
}
bitmap.setBit(((height - i - 1) * width) + j,
word & 0x80000000);
word <<= 1;
}
}
return bitmap;
}

La fonction readBitmap() permet de lire les masques AND et XOR dun curseur. Ces masques
ont deux fonctions inhabituelles. En premier lieu, ils stockent les lignes de bas en haut, au lieu de
lapproche courante de haut en bas. En second lieu, lordre des donnes apparat invers par rapport
celui utilis tout autre emplacement dans les fichiers .cur. Nous devons donc inverser la
coordonne y dans lappel de setBit(), et nous lisons les valeurs bit par bit dans le masque.
Nous terminons ici limplmentation du plug-in de format dimage CursorHandler. Les
plug-in correspondant dautres formats dimage doivent suivre le mme schma, bien que
certains puissent implmenter une part plus importante de lAPI QImageIOHandler, en particulier les fonctions utilises pour crire les images. Les plug-in dautres types, tels que les
codecs de texte ou les pilotes de base de donnes, suivent le mme schma : un conteneur de
plug-in fournit une API gnrique que les applications peuvent utiliser, ainsi quun gestionnaire fournissant les fonctionnalits sous-jacentes.
Le fichier .pro diffre entre les plug-in et les applications. Nous terminerons avec ceci :
TEMPLATE
CONFIG
HEADERS
SOURCES
DESTDIR

= lib
+= plugin
= cursorhandler.h \
cursorplugin.h
= cursorhandler.cpp \
cursorplugin.cpp
= $(QTDIR)/plugins/imageformats

Qt 4 Livre Page 435 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

435

Par dfaut, les fichiers .pro font appel au modle app, mais ici, nous devons utiliser le modle
lib car un plug-in est une bibliothque, et non une application autonome. La ligne CONFIG
indique Qt que la bibliothque nest pas classique, mais quil sagit dune bibliothque de
plug-in. DESTDIR mentionne le rpertoire o doit tre plac le plug-in. Tous les plug-in Qt
doivent se trouver dans le sous-rpertoire plugins appropri o a t install Qt, et comme
notre plug-in fournit un nouveau format dimage, nous le plaons dans le sous-rpertoire
plugins/imageformats. La liste des noms de rpertoire et des types de plug-in est fournie
ladresse http://doc.trolltech.com/4.1/plugins-howto.html. Pour cet exemple, nous supposons
que la variable denvironnement QTDIR est dfinie dans le rpertoire o est install Qt.
Les plug-in crs pour Qt en mode release et en mode debug sont diffrents. Ainsi, si les deux
versions de Qt sont installes, il est conseill de prciser laquelle doit tre utilise dans le
fichier .pro, en ajoutant par exemple la ligne suivante :
CONFIG

+= release

Les applications qui utilisent les plug-in Qt doivent tre dployes avec ceux quelles sont
senses exploiter. Les plug-in Qt doivent tre placs dans des sous-rpertoires spcifiques
(imageformats, par exemple, pour les formats dimage). Les applications Qt recherchent les
plug-in dans le sous-rpertoire plugins situ dans le dossier o rside leur excutable. Ainsi,
pour les plug-in dimage elles recherchent dans application_dir/plugins/imageformats. Si nous souhaitons dployer les plug-in Qt dans un rpertoire diffrent, les directions de
recherche des plug-in peuvent tre multiplies en utilisant QCoreApplication::addLibraryPath().

Crer des applications capables de grer


les plug-in
Un plug-in dapplication est une bibliothque dynamique qui implmente une ou plusieurs
interfaces. Une interface est une classe qui est constitue exclusivement de fonctions purement
virtuelles. La communication entre lapplication et les plug-in seffectue par le biais de la table
virtuelle de linterface. Dans cette section, nous nous pencherons sur lemploi dun plug-in
dans une application Qt par lintermdiaire de ses interfaces, et dans la section suivante nous
verrons comment implmenter un plug-in.
Pour fournir un exemple concret, nous allons crer lapplication Text Art simple prsente en
Figure 19.3. Les effets de texte sont fournis par les plug-in. Lapplication rcupre la liste des
effets de texte fournis par chaque plug-in et les parcourt pour afficher chacun deux en tant
qulment dans un QListWidget (voir Figure 19.3).
Lapplication Text Art dfinit une interface :
class TextArtInterface
{

Qt 4 Livre Page 436 Jeudi, 7. dcembre 2006 12:14 12

436

Qt4 et C++ : Programmation dinterfaces GUI

public:
virtual ~TextArtInterface() { }
virtual QStringList effects() const = 0;
virtual QPixmap applyEffect(const QString &effect,
const QString &text,
const QFont &font, const QSize &size,
const QPen &pen,
const QBrush &brush) = 0;
};
Q_DECLARE_INTERFACE(TextArtInterface,
"com.software-inc.TextArt.TextArtInterface/1.0")

Figure 19.3
Lapplication Text Art

Une classe dinterface dclare normalement un destructeur virtuel, une fonction virtuelle qui
retourne un QStringList ainsi quune ou plusieurs autres fonctions virtuelles. Le destructeur
est avant tout destin satisfaire le compilateur, qui, sans sa prsence se plaindrait du manque
de destructeur virtuel dans une classe possdant des fonctions virtuelles. Dans cet exemple, la
fonction effects() retourne une liste deffets de texte fournis par le plug-in. Nous pouvons
envisager cette liste comme une liste de cls. A chaque fois que nous appelons une autre fonction, nous transmettons lune de ces cls en tant que premier argument, permettant ainsi
limplmentation deffets multiples dans un plug-in.
Nous faisons finalement appel la macro Q_DECLARE_INTERFACE() pour associer un identificateur linterface. Cet identificateur comprend gnralement quatre composants : un nom de
domaine inverse spcifiant le crateur de linterface, le nom de lapplication, le nom de linterface et un numro de version. Ds que nous modifions linterface (en ajoutant une fonction
virtuelle ou en changeant la signature dune fonction existante), nous devons incrmenter le
numro de version. Dans le cas contraire, lapplication risque de tomber brusquement en panne
en essayant daccder un plug-in obsolte.

Qt 4 Livre Page 437 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

437

Lapplication est implmente dans une classe nomme TextArtDialog. Nous ntudierons
ici que le code prsentant un intrt. Commenons par le constructeur :
TextArtDialog::TextArtDialog(const QString &text, QWidget *parent)
: QDialog(parent)
{
listWidget = new QListWidget;
listWidget->setViewMode(QListWidget::IconMode);
listWidget->setMovement(QListWidget::Static);
listWidget->setIconSize(QSize(260, 80));

loadPlugins();
populateListWidget(text);

Le constructeur cre un QListWidget pour rpertorier les effets disponibles. Il appelle la


fonction prive loadPlugins() pour rechercher et charger tout plug-in implmentant le
TextArtInterface et remplit le QListWidget en consquence, en appelant une autre fonction
prive, populateListWidget().
void TextArtDialog::loadPlugins()
{
QDir pluginDir(QApplication::applicationDirPath());
#if defined(Q_OS_WIN)
if (pluginDir.dirName().toLower() == "debug"
|| pluginDir.dirName().toLower() == "release")
pluginDir.cdUp();
#elif defined(Q_OS_MAC)
if (pluginDir.dirName() == "MacOS") {
pluginDir.cdUp();
pluginDir.cdUp();
pluginDir.cdUp();
}
#endif
if (!pluginDir.cd("plugins"))
return;
foreach (QString fileName, pluginDir.entryList(QDir::Files)) {
QPluginLoader loader(pluginDir.absoluteFilePath(fileName));
if (TextArtInterface *interface =
qobject_cast<TextArtInterface *>(loader.instance()))
interfaces.append(interface);
}
}

Dans loadPlugins(), nous tentons de charger tous les fichiers du rpertoire plugins de
lapplication. (Sous Windows, lexcutable de lapplication rside gnralement dans un sousrpertoire debug ou release. Nous remontons donc dun niveau. Sous Mac OS X, nous
prenons en compte la structure de rpertoire du paquet.)

Qt 4 Livre Page 438 Jeudi, 7. dcembre 2006 12:14 12

438

Qt4 et C++ : Programmation dinterfaces GUI

Si le fichier que nous essayons de charger est un plug-in qui utilise la mme version de Qt que
lapplication, QPluginLoader::instance() retournera un QObject* qui pointe vers un
plug-in Qt. Nous excutons qobject_cast<T>() pour vrifier si le plug-in implmente le
TextArtInterface. A chaque fois que la conversion est russie, nous ajoutons linterface la
liste dinterfaces (de type QList<TextArtInterface*>) de TextArtDialog.
Certaines applications peuvent vouloir charger plusieurs interfaces diffrentes, auquel cas le
code permettant dobtenir les interfaces doit tre similaire celui-ci :
QObject *plugin = loader.instance();
if (TextArtInterface *i = qobject_cast<TextArtInterface *>(plugin))
textArtInterfaces.append(i);
if (BorderArtInterface *i = qobject_cast<BorderArtInterface *>(plugin))
borderArtInterfaces.append(i);
if (TextureInterface *i = qobject_cast<TextureInterface *>(plugin))
textureInterfaces.append(i);

Les mmes plug-in peuvent tre convertis avec succs en plusieurs pointeurs dinterface, car il
est possible pour les plug-in de fournir plusieurs interfaces avec lhritage multiple.
void TextArtDialog::populateListWidget(const QString &text)
{
QSize iconSize = listWidget->iconSize();
QPen pen(QColor("darkseagreen"));
QLinearGradient gradient(0, 0, iconSize.width() / 2,
iconSize.height() / 2);
gradient.setColorAt(0.0, QColor("darkolivegreen"));
gradient.setColorAt(0.8, QColor("darkgreen"));
gradient.setColorAt(1.0, QColor("lightgreen"));
QFont font("Helvetica", iconSize.height(), QFont::Bold);
foreach (TextArtInterface *interface, interfaces) {
foreach (QString effect, interface->effects()) {
QListWidgetItem *item = new QListWidgetItem(effect,
listWidget);
QPixmap pixmap = interface->applyEffect(effect, text, font,
iconSize, pen,
gradient);
item->setData(Qt::DecorationRole, pixmap);
}
}
listWidget->setCurrentRow(0);
}

La fonction populateListWidget() commence en crant quelques variables transmette


la fonction applyEffect(), et en particulier un stylet, un dgrad linaire et une police. Elle
parcourt ensuit le TextArtInterface ayant t trouv par loadPlugins(). Pour tout effet

Qt 4 Livre Page 439 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

439

fourni par chaque interface, un nouveau QListWidgetItem est cr avec le nom de leffet
quil reprsente, et un QPixmap est gnr au moyen de applyEffect().
Dans cette section, nous avons vu comment charger des plug-in en appelant loadPlugins()
dans le constructeur, et comment les exploiter dans populateListWidget(). Le code dtermine lgamment combien il existe de plug-in fournissant TextArtInterface. En outre, des
plug-in supplmentaires peuvent tre ajouts ultrieurement : chaque nouvelle ouverture de
lapplication, celle-ci charge tous les plug-in fournissant les interfaces souhaites. Il est ainsi
possible de dvelopper la fonctionnalit de lapplication sans pour autant la modifier.

Ecrire des plug-in dapplication


Un plug-in dapplication est une sous-classe de QObject et des interfaces quil souhaite fournir. Le
CD daccompagnement de ce livre inclut deux plug-in pour lapplication Text Art prsente
dans la section prcdente. Ils visent dmontrer que lapplication gre correctement plusieurs
plug-in.
Dans cet ouvrage, nous ntudierons le code que de lun dentre eux, le plug-in Basic Effects. Nous
supposerons que le code source du plug-in est situ dans un rpertoire nomm basiceffectsplugin et que lapplication Text Art se trouve dans un rpertoire parallle nomm textart.
Voici la dclaration de la classe de plug-in :
class BasicEffectsPlugin: public QObject, public TextArtInterface
{
Q_OBJECT
Q_INTERFACES(TextArtInterface)
public:
QStringList effects() const;
QPixmap applyEffect(const QString &effect, const QString &text,
const QFont &font, const QSize &size,
const QPen &pen, const QBrush &brush);
};

Le plug-in nimplmente quune seule interface, TextArtInterface. En plus de Q_OBJECT,


nous devons utiliser la macro Q_INTERFACES() pour chaque interface sous-classe afin de
nous assurer dune coopration sans problmes entre moc et qobject_cast<T>().
QStringList BasicEffectsPlugin::effects() const
{
return QStringList() << "Plain" << "Outline" << "Shadow";
}

La fonction effects() retourne une liste deffets de texte pris en charge par le plug-in. Celuici supportant trois effets, nous retournons simplement une liste contenant le nom de chacun
deux.

Qt 4 Livre Page 440 Jeudi, 7. dcembre 2006 12:14 12

440

Qt4 et C++ : Programmation dinterfaces GUI

La fonction applyEffect() fournit la fonctionnalit du plug-in et savre assez importante.


Cest pourquoi nous ltudierons par fragments.
QPixmap BasicEffectsPlugin::applyEffect(const QString &effect,
const QString &text, const QFont &font, const QSize &size,
const QPen &pen, const QBrush &brush)
{
QFont myFont = font;
QFontMetrics metrics(myFont);
while ((metrics.width(text) > size.width()
|| metrics.height() > size.height())
&& myFont.pointSize() > 9) {
myFont.setPointSize(myFont.pointSize() - 1);
metrics = QFontMetrics(myFont);
}

Nous souhaitons nous assurer que le texte donn sadaptera si possible la taille spcifie.
Cest la raison pour laquelle nous utilisons les mtriques de la police afin de dterminer si texte
est trop gros pour sadapter. Si tel est le cas, nous excutons une boucle o nous rduisons la taille
en points jusqu parvenir une dimension correcte, ou jusqu parvenir 9 points, notre
taille minimale fixe.
QPixmap pixmap(size);
QPainter painter(&pixmap);
painter.setFont(myFont);
painter.setPen(pen);
painter.setBrush(brush);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::TextAntialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.eraseRect(pixmap.rect());

Nous crons un pixmap de la taille requise et un painter pour peindre sur le pixmap. Nous dfinissons aussi quelques conseils de rendu pour assurer les meilleurs rsultats possible. Lappel
eraseRect() efface le pixmap avec la couleur darrire-plan.
if (effect == "Plain") {
painter.setPen(Qt::NoPen);
} else if (effect == "Outline") {
QPen pen(Qt::black);
pen.setWidthF(2.5);
painter.setPen(pen);
} else if (effect == "Shadow") {
QPainterPath path;
painter.setBrush(Qt::darkGray);
path.addText(((size.width() - metrics.width(text)) / 2) + 3,
(size.height() - metrics.descent()) + 3, myFont,
text);
painter.drawPath(path);
painter.setBrush(brush);
}

Qt 4 Livre Page 441 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 19

Crer des plug-in

441

Pour leffet "Plain" aucun relief nest requis. En ce qui concerne leffet "Outline", nous ignorons le stylet initial et en crons un de couleur noire avec une largeur de 2,5 pixels. Quant
leffet "Shadow", il nous faut dabord dessiner lombre, de faon pouvoir inscrire le texte
par-dessus.
QPainterPath path;
path.addText((size.width() - metrics.width(text)) / 2,
size.height() - metrics.descent(), myFont, text);
painter.drawPath(path);
return pixmap;
}

A prsent, nous disposons du stylet et de lensemble des pinceaux adapts chaque effet de
texte. Nous sommes maintenant prts afficher le texte. Ce dernier est centr horizontalement
et suffisamment loign du bas du pixmap pour laisser de la place aux hampes infrieures.
Q_EXPORT_PLUGIN2(basiceffectsplugin, BasicEffectsPlugin)

A la fin du fichier .cpp, nous excutons la macro Q_EXPORT_PLUGIN2() afin de rendre le


plug-in disponible pour Qt.
Le fichier .pro est similaire celui que nous avons utilis pour le plug-in de curseur Windows
prcdemment dans ce chapitre.
TEMPLATE
CONFIG
HEADERS
SOURCES
DESTDIR

=
+=
=
=
=

lib
plugin
../textart/textartinterface.h \<RC>basiceffectsplugin.h
basiceffectsplugin.cpp
../textart/plugins

Si ce chapitre vous a donn lenvie den savoir plus sur les plug-in, vous pouvez tudier
lexemple plus avanc Plug & Paint fourni avec Qt. Lapplication prend en charge trois interfaces diffrentes et inclut une bote de dialogue Plugin Information rpertoriant les plug-in et
interfaces disponibles pour lapplication.

Qt 4 Livre Page 442 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 443 Jeudi, 7. dcembre 2006 12:14 12

20
Fonctionnalits
spcifiques
la plate-forme
Au sommaire de ce chapitre
Construire une interface avec les API
natives
ActiveX sous Windows
Prendre en charge la gestion de session
de X11

Dans ce chapitre, nous allons tudier quelques options spcifiques la plate-forme


disponibles pour les programmeurs Qt. Nous commencerons par examiner comment
accder aux API natives telles que lAPI Win32 de Windows, Carbon sous Mac OS X et
Xlib sous X11. Nous tudierons ensuite lextension ActiveQt, pour apprendre utiliser
les contrles ActiveX sous Windows. Vous dcouvrirez aussi que cette extension permet

Qt 4 Livre Page 444 Jeudi, 7. dcembre 2006 12:14 12

444

Qt4 et C++ : Programmation dinterfaces GUI

de crer des applications qui se comportent comme des serveurs ActiveX. Dans la dernire
section, nous expliquerons comment amener des applications Qt cooprer avec le gestionnaire de session sous X11.
Pour complter les fonctionnalits prsentes ci-dessus, Trolltech offre plusieurs solutions Qt
spcifiques la plate-forme, notamment les frameworks de migration Qt/Motif et Qt/MFC qui
simplifient la migration des applications Motif/Xt et MFC vers Qt. Une extension analogue est
fournie pour les applications Tcl/Tk par froglogic, et un convertisseur de ressource Microsoft
Windows est disponible auprs de Klarlvdalens Datakonsult. Consultez les pages Web suivantes
pour obtenir des dtails complmentaires :
http://www.trolltech.com/products/solutions/catalog/
http://www.froglogic.com/tq/
http://www.kdab.net/knut/
Pour tout ce qui concerne le dveloppement intgr, Trolltech propose la plate-forme dapplication Qtopia. Cette plate-forme est dtaille au Chapitre 21.

Construire une interface avec les API natives


LAPI trs dveloppe de Qt rpond la plupart des besoins sur toutes les plates-formes, mais
dans certaines circonstances, vous pourriez avoir besoin demployer les API locales. Dans
cette section, nous allons montrer comment utiliser ces API pour les diffrentes plates-formes
prises en charge par Qt afin daccomplir des tches particulires.
Figure 20.1
Une fentre doutil
Mac OS X avec la barre
de titre sur le ct

Sur toutes les plates-formes, QWidget fournit une fonction winId() qui renvoie lidentifiant
de la fentre ou le handle (descripteur). QWidget fournit aussi une fonction statique nomme
find() qui renvoie le QWidget avec un ID de fentre particulier. Nous pouvons transmettre
cet identifiant aux fonctions dAPI natives pour obtenir des effets spcifiques la plate-forme.

Qt 4 Livre Page 445 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

445

Par exemple, le code suivant sappuie sur winId() pour dplacer la barre de titre dune fentre
doutil vers la gauche laide des fonctions natives de Mac OS X :
#ifdef Q_WS_MAC
ChangeWindowAttributes(HIViewGetWindow(HIViewRef(toolWin.winId())),
kWindowSideTitlebarAttribute,
kWindowNoAttributes);
#endif

Sous X11, voici comment nous pourrions modifier une proprit de fentre :
#ifdef Q_WS_X11
Atom atom = XInternAtom(QX11Info::display(), "MY_PROPERTY", False);
long data = 1;
XChangeProperty(QX11Info::display(), window->winId(), atom, atom,
32, PropModeReplace,
reinterpret_cast<uchar *>(&data), 1);
#endif

Les directives #ifdef et #endif qui encadrent le code spcifique la plate-forme garantissent
la compilation de lapplication sur les autres plates-formes.
Pour une application uniquement destine Windows, voici un exemple dutilisation des
appels de GDI pour afficher un widget Qt :
void GdiControl::paintEvent(QPaintEvent * /* event */)
{
RECT rect;
GetClientRect(winId(), &rect);
HDC hdc = GetDC(winId());
FillRect(hdc, &rect, HBRUSH(COLOR_WINDOW + 1));
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, text.utf16(), text.size());
ReleaseDC(winId(), hdc);
}

Pour que ce code sexcute, nous devons aussi rimplmenter QPaintDevice::paintEngine()


de sorte quelle renvoie un pointeur nul et dfinisse lattribut Qt::WA_PaintOnScreen dans
le constructeur du widget.
Lexemple suivant montre comment combiner QPainter et des appels GDI dans le gestionnaire dvnement paint en excutant les fonctions getDC() et releaseDC():
void MyWidget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.fillRect(rect().adjusted(20, 20, -20, -20), Qt::red);
#ifdef Q_WS_WIN
HDC hdc = painter.paintEngine()->getDC();
Rectangle(hdc, 40, 40, width() - 40, height() - 40);
painter.paintEngine()->releaseDC();
#endif
}

Qt 4 Livre Page 446 Jeudi, 7. dcembre 2006 12:14 12

446

Qt4 et C++ : Programmation dinterfaces GUI

Le mlange des appels de QPainter avec les appels GDI comme dans ce code conduit quelquefois des rsultats tranges, en particulier quand les appels de QPainter se produisent
aprs les appels GDI. En effet, QPainter sappuie dans ce cas sur certaines hypothses
concernant ltat de la couche de dessin sous-jacente.
Qt dfinit un des quatre symboles de systme de fentre suivant : Q_WS_WIN, Q_WS_ X11,
Q_WS_MAC, et Q_WS_QWS (Qtopia). Nous devons inclure au moins un en-tte Qt pour tre en
mesure de les utiliser dans les applications. Qt fournit aussi des symboles de prprocesseur
destins identifier le systme dexploitation :
c Q_OS_AIX

c Q_OS_HPUX

c Q_OS_OPENBSD

c Q_OS_SOLARIS

c Q_OS_BSD4

c Q_OS_HURD

c Q_OS_OS2EMX

c Q_OS_ULTRIX

c Q_OS_BSDI

c Q_OS_IRIX

c Q_OS_OSF

c Q_OS_UNIXWARE

c Q_OS_CYGWIN

c Q_OS_LINUX

c Q_OS_QNX6

c Q_OS_WIN32

c Q_OS_DGUX

c Q_OS_LYNX

c Q_OS_QNX

c Q_OS_WIN64

c Q_OS_DYNIX

c Q_OS_MAC

c Q_OS_RELIANT

c Q_OS_FREEBSD

c Q_OS_NETBSD

c Q_OS_SCO

Nous pouvons supposer quau moins un de ces symboles sera dfini. Pour des raisons pratiques,
Qt dfinit aussi Q_OS_WIN ds que Win32 ou Win64 est dtect, et Q_OS_UNIX lorsquun
systme dexploitation de type UNIX (y compris Linux et Mac OS X), est reconnu. Au moment de
lexcution, nous pouvons contrler QSysInfo::WindowsVersion ou QSysInfo::MacintoshVersion pour identifier les diffrentes versions de Windows (2000, XP, etc.) ou de Mac OS X
(10.2, 10.3, etc.).
Des macros de compilateur viennent complter les macros du systme de fentrage et du
systme dexploitation. Par exemple, Q_CC_MSVC est dfinie si le compilateur est Microsoft
Visual C++. Elles peuvent se rvler trs pratiques pour rsoudre les erreurs de compilateur.
Plusieurs classes de Qt lies linterface graphique fournissent des fonctions spcifiques la
plate-forme qui renvoient des handles de bas niveau vers lobjet sous-jacent. Ces descripteurs
sont numrs en Figure 20.2.
Sous X11, QPixmap::x11Info() et QWidget::x11Info() renvoient un objet QX11Info qui
fournit divers pointeurs ou handles, tels que display(), screen(), colormap() et visual().
Nous pouvons les utiliser pour configurer un contexte graphique X11 sur un QPixmap ou un
QWidget, par exemple.
Les applications Qt qui doivent collaborer avec dautres toolkits ou bibliothques ont souvent
besoin daccder aux vnements de bas niveau sous X11, MSGs sous Windows, EventRef
sous Mac OS X, QWSEvents sous Qtopia) avant quils ne soient convertis en QEvents. Nous
pouvons procder en drivant QApplication et en rimplmentant le filtre dvnement
spcifique la plate-forme appropri, cest--dire x11EventFilter(), winEventFilter(),

Qt 4 Livre Page 447 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

Mac OS X

ATSFontFormatRef

QFont::handle()

CGImageRef

QPixmap::macCGHandle()

GWorldPtr

QPixmap::macQDAlphaHandle()

GWorldPtr

QPixmap::macQDHandle()

RgnHandle

QRegion::handle()

HIViewRef

QWidget::winId()

Windows
HCURSOR

QCursor::handle()

HDC

QPaintEngine::getDC()

HDC

QPrintEngine::getPrinterDC()

HFONT

QFont::handle()

HPALETTE

QColormap::hPal()

HRGN

QRegion::handle()

HWND

QWidget::winId()

X11
Cursor

QCursor::handle()

Font

QFont::handle()

Picture

QPixmap::x11PictureHandle()

Picture

QWidget::x11PictureHandle()

Pixmap

QPixmap::handle()

QX11Info

QPixmap::x11Info()

QX11Info

QWidget::x11Info()

Region

QRegion::handle()

Screen

QCursor::x11Screen()

SmcConn

QSessionManager::handle()

Window

QWidget::handle()

Window

QWidget::winId()

Figure 20.2
Fonctions spcifiques la plate-forme pour accder aux handles de bas niveau

447

Qt 4 Livre Page 448 Jeudi, 7. dcembre 2006 12:14 12

448

Qt4 et C++ : Programmation dinterfaces GUI

macEventFilter(), ou qwsEventFilter(). Nous pouvons aussi accder aux vnements


de plate-forme qui sont envoys un widget donn en rimplmentant un des filtres
x11Event(), winEvent(), macEvent(), et qwsEvent(). Cela pourrait prsenter de lintrt
pour la gestion de certains types dvnement qui seraient normalement ignors dans QT,
comme les vnements de manette de jeu.
Vous trouverez des informations complmentaires concernant les dtails spcifiques chaque
plate-forme, notamment comment dployer les applications Qt sur diffrentes plates-formes,
ladresse http://doc.trolltech.com/4.1/win-system.html.

ActiveX sous Windows


La technologie ActiveX de Microsoft permet aux applications dincorporer des composants
dinterface utilisateur fournis par dautres applications ou bibliothques. Elle est base sur
Microsoft COM et dfinit un jeu dinterfaces pour application qui emploie des composants et
un autre jeu dinterfaces pour application et bibliothque qui fournit les composants.
La version Qt/Windows Desktop Edition fournit le Framework ActiveQt et combine de faon
homogne ActiveX et Qt. ActiveQt est constitu de deux modules :
Le module QAxContainer nous permet dutiliser les objets COM et dintgrer des contrles
ActiveX dans les applications Qt.
Le module QAxServer nous permet dexporter des objets COM personnaliss et des contrles
ActiveX crits avec Qt.
Notre premier exemple va intgrer lapplication Windows Media Player dans une application
Qt laide du module QAxContainer. Lapplication Qt ajoute un bouton Open, un bouton
Play/Pause, un bouton Stop et un curseur au contrle ActiveX de Windows Media Player.
Figure 20.3
Lapplication Media
Player

La fentre principale de lapplication est de type PlayerWindow:

Qt 4 Livre Page 449 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

449

class PlayerWindow: public QWidget


{
Q_OBJECT
Q_ENUMS(ReadyStateConstants)
public:
enum PlayStateConstants { Stopped = 0, Paused = 1, Playing = 2 };
enum ReadyStateConstants { Uninitialized = 0, Loading = 1,
Interactive = 3, Complete = 4 };
PlayerWindow();
protected:
void timerEvent(QTimerEvent *event);
private
void
void
void
void
void

slots:
onPlayStateChange(int oldState, int newState);
onReadyStateChange(ReadyStateConstants readyState);
onPositionChange(double oldPos, double newPos);
sliderValueChanged(int newValue);
openFile();

private:
QAxWidget *wmp;
QToolButton *openButton;
QToolButton *playPauseButton;
QToolButton *stopButton;
QSlider *seekSlider;
QString fileFilters;
int updateTimer;
};

La classe PlayerWindow hrite de QWidget. La macro Q_ENUMS() (directement sous


Q_OBJECT) est indispensable pour signaler moc que le type ReadyStateConstants utilis
dans le slot onReadyStateChange() est un type enum. Dans la section prive, nous dclarons
la donne membre QAxWidget*.
PlayerWindow::PlayerWindow()
{
wmp = new QAxWidget;
wmp->setControl("{22D6F312-B0F6-11D0-94AB-0080C74C7E95}");

Dans le constructeur, nous commenons par crer un objet QAxWidget afin dencapsuler le
contrle ActiveX Windows Media Player. Le module QAxContainer se compose de trois classes : QAxObject englobe un objet COM, QAxWidget englobe un contrle ActiveX, et
QAxBase implmente la fonctionnalit COM pour QAxObject et QAxWidget.
Nous appelons setControl() sur QAxWidget avec lID de la classe du contrle Windows
Media Player 6.4. Une instance du composant requis va ainsi tre cre. A partir de l, toutes

Qt 4 Livre Page 450 Jeudi, 7. dcembre 2006 12:14 12

450

Qt4 et C++ : Programmation dinterfaces GUI

les proprits, vnements, et mthodes du contrle ActiveX vont tre disponibles sous la
forme de proprits, signaux et slots Qt, par le biais de lobjet QAxWidget.
Figure 20.4
Schma dhritage du
module QAxContainer

QObject
QAxBase
QAxObject

QWidget
QAxWidget

Les types de donnes COM sont automatiquement convertis vers les types Qt correspondants,
comme illustr en Figure 20.5. Par exemple, un paramtre dentre de type VARIANT_BOOL
prend le type bool, et un paramtre de sortie de type VARIANT_BOOL devient un type bool &.
Si le type obtenu est une classe Qt (QString, QDateTime, etc.), le paramtre dentre est une
rfrence const (par exemple, const QString &).
Types COM

Types Qt

VARIANT_BOOL

bool

char, short, int, long

int

unsigned char, unsigned short,

uint

unsigned int, unsigned long


float, double

double

CY

qlonglong, qulonglong

BSTR

QString

DATE

QDateTime, QDate, QTime

OLE_COLOR

QColor

SAFEARRAY(VARIANT)

QList<QVariant>

SAFEARRAY(BSTR)

QStringList

SAFEARRAY(BYTE)

QByteArray

VARIANT

QVariant

IFontDisp *

QFont

IPictureDisp *

QPixmap

User defined type

QRect, QSize, QPoint

Figure 20.5
Relations entre types COM et types Qt

Qt 4 Livre Page 451 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

451

Pour obtenir la liste de toutes les proprits, signaux et slots disponibles pour un QAxObject
ou un QAxWidget avec leurs types de donnes Qt, appelez QAxBase::generateDocumentation() ou faites appel loutil de ligne de commande de Qt dumpdoc, que vous trouverez
dans le rpertoire tools\activeqt\dumpdoc de Qt.
Examinons maintenant le constructeur de PlayerWindow:
wmp->setProperty("ShowControls", false);
wmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(wmp, SIGNAL(PlayStateChange(int, int)),
this, SLOT(onPlayStateChange(int, int)));
connect(wmp, SIGNAL(ReadyStateChange(ReadyStateConstants)),
this, SLOT(onReadyStateChange(ReadyStateConstants)));
connect(wmp, SIGNAL(PositionChange(double, double)),
this, SLOT(onPositionChange(double, double)));

Aprs lappel de QAxWidget::setControl(), nous appelons QObject::setProperty()


pour dfinir la proprit ShowControls du Windows Media Player en false, puisque nous
fournissons nos propres boutons pour manipuler le composant. QObject::setProperty()
peut tre utilise la fois pour les proprits COM et pour les proprits Qt normales. Son
second paramtre est de type QVariant.
Nous appelons ensuite setSizePolicy() de sorte que le contrle ActiveX occupe toute la
place disponible dans la disposition, et nous connectons trois vnements ActiveX depuis
le composant COM vers les trois slots.

stopButton = new QToolButton;


stopButton->setText(tr("&Stop"));
stopButton->setEnabled(false);
connect(stopButton, SIGNAL(clicked()), wmp, SLOT(Stop()));

La fin du constructeur PlayerWindow suit le modle habituel, sauf que nous connectons quelques
signaux Qt aux slots fournis par lobjet COM (Play(), Pause(), et Stop()). Les boutons
tant analogues, nous ne prsentons ici que limplmentation du bouton Stop.
Il est temps daborder la fonction timerEvent():
void PlayerWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == updateTimer) {
double curPos = wmp->property("CurrentPosition").toDouble();
onPositionChange(-1, curPos);
} else {
QWidget::timerEvent(event);
}
}

Qt 4 Livre Page 452 Jeudi, 7. dcembre 2006 12:14 12

452

Qt4 et C++ : Programmation dinterfaces GUI

La fonction timerEvent() est appele intervalles rguliers pendant la diffusion dun clip.
Nous lemployons pour faire avancer le curseur. Pour ce faire, nous appelons property() sur
le contrle ActiveX afin dobtenir la valeur de la proprit CurrentPosition en tant que
QVariant et nous appelons toDouble() pour le convertir en double. Nous appelons alors
onPositionChange() pour raliser la mise jour.
La suite du code prsente peu dintrt parce quelle ne concerne pas directement ActiveX et
quelle ne comporte rien qui nait dj t abord. Le code complet est inclus sur le CD-ROM.
Dans le fichier .pro, nous devons introduire lentre suivante pour tablir une liaison avec le
module QAxContainer :
CONFIG

+= qaxcontainer

Lorsquon gre des objets COM, on a souvent besoin dappeler directement une mthode
COM (et pas seulement de la connecter un signal Qt). La mthode la plus simple de procder
est dinvoquer QAxBase::dynamicCall() avec le nom et la signature de la mthode en
premier paramtre et les arguments de cette dernire comme paramtres supplmentaires.
Saisissez par exemple :
wmp->dynamicCall("TitlePlay(uint)", 6);

La fonction dynamicCall() reoit jusqu huit paramtres de type QVariant et renvoie un


QVariant. Si nous avons besoin de transmettre un IDispatch* ou un IUnknown* de cette
faon, nous pouvons encapsuler le composant dans un QAxObject et appeler asVariant() sur
ce dernier pour le convertir en QVariant. Si nous avons besoin dappeler une mthode COM qui
renvoie un IDispatch* ou un IUnknown*, ou si nous avons besoin daccder une proprit
COM appartenant lun de ces types, nous pouvons opter plutt pour querySubObject():
QAxObject *session = outlook.querySubObject("Session");
QAxObject *defaultContacts =
session->querySubObject("GetDefaultFolder(OlDefaultFolders)",
"olFolderContacts");

Si nous dsirons appeler des mthodes dont la liste de paramtres contient des types de
donnes non pris en charge, nous pouvons utiliser QAxBase::queryInterface()pour rcuprer linterface COM et appeler directement la mthode. Comme toujours avec COM, nous
devons appeler Release() quand nous navons plus besoin de linterface. Sil devient trs
frquent davoir appeler de telles mthodes, nous pouvons driver QAxObject ou QAxWidget
et fournir des fonctions membres qui encapsulent les appels de linterface COM. Vous ne devez
pas ignorer que les sous classes QAxObject et QAxWidget ne peuvent pas dfinir leurs
propres proprits, signaux, ou slots.
Nous allons maintenant dtailler le module QAxServer. Il nous permet de transformer un
programme Qt standard en serveur ActiveX. Le serveur peut tre une bibliothque partage ou
une application autonome. Les serveurs dfinis en tant que bibliothques partages sont
souvent appels serveurs in-process (in-process signifie "qui sexcute dans lespace de traitement
dun client") ; les applications autonomes sont nommes serveurs hors processus.

Qt 4 Livre Page 453 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

453

Notre premier exemple de QAxServer est un serveur in-process qui fournit un widget affichant
une bille qui se balance de gauche droite. Nous allons aussi expliquer comment intgrer le
widget dans Internet Explorer.
Voici le dbut de la dfinition de classe du widget AxBouncer:
class AxBouncer: public QWidget, public QAxBindable
{
Q_OBJECT
Q_ENUMS(SpeedValue)
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(SpeedValue speed READ speed WRITE setSpeed)
Q_PROPERTY(int radius READ radius WRITE setRadius)
Q_PROPERTY(bool running READ isRunning)

AxBouncer hrite la fois de QWidget et QAxBindable. La classe QAxBindable fournit


une interface entre le widget et un client ActiveX. Tout QWidget peut tre export sous forme
de contrle ActiveX, mais en drivant QAxBindable nous pouvons informer le client des
changements de valeur dune proprit, et nous pouvons implmenter des interfaces COM
pour complter celles dj implmentes par QAxServer.
Figure 20.6
Le widget AxBouncer
dans Internet Explorer

En prsence dhritage multiple impliquant une classe drive de QObject, nous devons toujours
positionner la classe drive de QObject en premier de sorte que moc puisque la rcuprer.
Nous dclarons trois proprits de lecture-criture et une proprit en lecture seulement. La
macro Q_ ENUMS() est ncessaire pour signaler moc que le type SpeedValue est un type
enum. Lnumration est dclare dans la section publique de la classe :
public:
enum SpeedValue { Slow, Normal, Fast };

Qt 4 Livre Page 454 Jeudi, 7. dcembre 2006 12:14 12

454

Qt4 et C++ : Programmation dinterfaces GUI

AxBouncer(QWidget *parent = 0);


void setSpeed(SpeedValue newSpeed);
SpeedValue speed() const { return ballSpeed; }
void setRadius(int newRadius);
int radius() const { return ballRadius; }
void setColor(const QColor &newColor);
QColor color() const { return ballColor; }
bool isRunning() const { return myTimerId!= 0; }
QSize sizeHint() const;
QAxAggregated *createAggregate();
public slots:
void start();
void stop();
signals:
void bouncing();

Le constructeur de AxBouncer est un constructeur standard pour un widget, avec un paramtre


parent. La macro QAXFACTORY_DEFAULT(), que nous utiliserons pour exporter le composant,
doit recevoir un constructeur avec sa signature.
La fonction createAggregate() est rimplmente dans QAxBindable. Nous ltudierons
un peu plus loin.
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
private:
int intervalInMilliseconds() const;
QColor ballColor;
SpeedValue ballSpeed;
int ballRadius;
int myTimerId;
int x;
int delta;
};

Les sections protges et prives de la classe sont identiques celles que nous aurions sil
sagissait dun widget Qt standard.
AxBouncer::AxBouncer(QWidget *parent)
: QWidget(parent)
{
ballColor = Qt::blue;
ballSpeed = Normal;
ballRadius = 15;
myTimerId = 0;
x = 20;
delta = 2;
}

Qt 4 Livre Page 455 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

455

Le constructeur de AxBouncer initialise les variables prives de la classe.


void AxBouncer::setColor(const QColor &newColor)
{
if (newColor!= ballColor && requestPropertyChange("color")) {
ballColor = newColor;
update();
propertyChanged("color");
}
}

La fonction setColor() dfinie la valeur de la proprit color. Elle appelle update() pour
redessiner le widget.
Les appels de requestPropertyChange() et propertyChanged() sont plus inhabituels.
Ces fonctions drivent de QAxBindable et devraient normalement tre appeles chaque fois
que nous modifions une proprit. requestPropertyChange() demande au client la permission de changer une proprit, et renvoie true si le client accepte. La fonction propertyChanged() signale au client que la proprit a chang.
Les mthodes daccs de proprit setSpeed() et setRadius() suivent galement ce modle,
ainsi que les slots start() et stop(), puisquils modifient la valeur de la proprit running.
La fonction membre AxBouncer ne doit pas tre oublie :
QAxAggregated *AxBouncer::createAggregate()
{
return new ObjectSafetyImpl;
}

La fonction createAggregate() est rimplmente dans QAxBindable. Elle nous permet


dimplmenter les interfaces COM que le module QAxServer nimplmente pas dj ou de
remplacer les interfaces COM par dfaut de QAxServer. Ici, nous le faisons pour fournir
linterface IObjectSafety, qui est celle partir de laquelle Internet Explorer accde aux
options de scurit du composant. Voici lastuce standard pour se dbarrasser du fameux
message derreur "Objet non scuris pour le script" dInternet Explorer.
Voici la dfinition de la classe qui implmente linterface IObjectSafety:
class ObjectSafetyImpl: public QAxAggregated, public IObjectSafety
{
public:
long queryInterface(const QUuid &iid, void **iface);
QAXAGG_IUNKNOWN
HRESULT WINAPI GetInterfaceSafetyOptions(REFIID riid,
DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions);

Qt 4 Livre Page 456 Jeudi, 7. dcembre 2006 12:14 12

456

Qt4 et C++ : Programmation dinterfaces GUI

HRESULT WINAPI SetInterfaceSafetyOptions(REFIID riid,


DWORD pdwSupportedOptions, DWORD pdwEnabledOptions);
};

La classe ObjectSafetyImpl hrite la fois de QAxAggregated et de IObjectSafety. La


classe QAxAggregated est une classe de base abstraite pour limplmentation dinterfaces
COM complmentaires. Lobjet COM tendu par QAxAggregated est accessible par le biais
de controllingUnknown(). Cet objet est cr en arrire-plan par le module QAxServer.
La macro QAXAGG_IUNKNOWN fournit les implmentations standard de QueryInterface(),
AddRef(), et Release(). Ces implmentations appellent simplement la mme fonction sur
lobjet COM contrleur.
long ObjectSafetyImpl::queryInterface(const QUuid &iid, void **iface)
{
*iface = 0;
if (iid == IID_IObjectSafety) {
*iface = static_cast<IObjectSafety *>(this);
} else {
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}

La fonction queryInterface() est une fonction virtuelle pure de QAxAggregated. Elle est
appele par lobjet COM contrleur afin doffrir laccs aux interfaces fournies par la sous classe
QAxAggregated. Nous devons renvoyer E_NOINTERFACE pour les interfaces que nous
nimplmentons pas et pour IUnknown.
HRESULT WINAPI ObjectSafetyImpl::GetInterfaceSafetyOptions(
REFIID /* riid */, DWORD *pdwSupportedOptions,
DWORD *pdwEnabledOptions)
{
*pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA
| INTERFACESAFE_FOR_UNTRUSTED_CALLER;
*pdwEnabledOptions = *pdwSupportedOptions;
return S_OK;
}
HRESULT WINAPI ObjectSafetyImpl::SetInterfaceSafetyOptions(
REFIID /* riid */, DWORD /* pdwSupportedOptions */,
DWORD /* pdwEnabledOptions */)
{
return S_OK;
}

Les fonctions GetInterfaceSafetyOptions() et SetInterfaceSafetyOptions() sont


dclares dans IObjectSafety. Nous les implmentons pour annoncer tous que notre objet
est bien scuris pour les scripts.

Qt 4 Livre Page 457 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

457

Examinons maintenant main.cpp:


#include <QAxFactory>
#include "axbouncer.h"
QAXFACTORY_DEFAULT(AxBouncer,
"{5e2461aa-a3e8-4f7a-8b04-307459a4c08c}",
"{533af11f-4899-43de-8b7f-2ddf588d1015}",
"{772c14a5-a840-4023-b79d-19549ece0cd9}",
"{dbce1e56-70dd-4f74-85e0-95c65d86254d}",
"{3f3db5e0-78ff-4e35-8a5d-3d3b96c83e09}")

La macro QAXFACTORY_DEFAULT() exporte un contrle ActiveX. Nous pouvons lutiliser


pour les serveurs ActiveX qui exportent un seul contrle. Lexemple suivant de cette section
montre comment exporter plusieurs contrles ActiveX.
Le premier argument destin QAXFACTORY_DEFAULT() est le nom de la classe Qt exporter.
Cest aussi le nom sous lequel le contrle est export. Les cinq autres arguments sont lID de la
classe, de linterface, de linterface de lvnement, de la bibliothque des types, et de lapplication. Nous pouvons gnrer ces identificateurs laide dun outil standard tel que guidgen
ou uuidgen. Le serveur tant une bibliothque, nous navons pas besoin de la fonction
main().
Voici le fichier .pro pour notre serveur ActiveX in-process :
TEMPLATE
CONFIG
HEADERS
SOURCES
RC_FILE
DEF_FILE

= lib
+= dll qaxserver
= axbouncer.h \
objectsafetyimpl.h
= axbouncer.cpp \
main.cpp \
objectsafetyimpl.cpp
= qaxserver.rc
= qaxserver.def

Les fichiers qaxserver.rc et qaxserver.def auxquels il est fait rfrence dans le fichier
.pro sont des fichiers standards que lon peut copier dans le rpertoire src\activeqt\control
de Qt.
Le make?le ou le fichier de projet Visual C++ gnr par qmake contient les rgles qui rgissent lenregistrement du serveur dans le registre de Windows. Pour enregistrer le serveur sur
les machines utilisateur, nous pouvons faire appel loutil regsvr32 disponible sur tous les
systmes Windows.
Nous incluons alors le composant Bouncer dans une page HTML via la balise <object>:
<object id="AxBouncer"
classid="clsid:5e2461aa-a3e8-4f7a-8b04-307459a4c08c">
<b>The ActiveX control is not available. Make sure you have built and
registered the component server.</b>
</object>

Qt 4 Livre Page 458 Jeudi, 7. dcembre 2006 12:14 12

458

Qt4 et C++ : Programmation dinterfaces GUI

Il est possible de crer des boutons qui invoquent des slots :


<input type="button" value="Start" onClick="AxBouncer.start()">
<input type="button" value="Stop" onClick="AxBouncer.stop()">

Nous pouvons manipuler le widget laide de JavaScript ou VBScript comme nimporte quel
autre contrle ActiveX. Le fichier demo.html propos sur le CD prsente une page rudimentaire qui utilise le serveur ActiveX.
Notre dernier exemple est une application Carnet dadresse. Elle peut se comporter comme une
application standard Qt/Windows ou comme un serveur ActiveX hors processus. Cette dernire
option nous permet de crer le script de lapplication en Visual Basic, par exemple.
class AddressBook: public QMainWindow
{
Q_OBJECT
Q_PROPERTY(int count READ count)
Q_CLASSINFO("ClassID", "{588141ef-110d-4beb-95ab-ee6a478b576d}")
Q_CLASSINFO("InterfaceID", "{718780ec-b30c-4d88-83b3-79b3d9e78502}")
Q_CLASSINFO("ToSuperClass", "AddressBook")
public:
AddressBook(QWidget *parent = 0);
~AddressBook();
int count() const;
public slots:
ABItem *createEntry(const QString &contact);
ABItem *findEntry(const QString &contact) const;
ABItem *entryAt(int index) const;
private slots:
void addEntry();
void editEntry();
void deleteEntry();
private:
void createActions();
void createMenus();
QTreeWidget *treeWidget;
QMenu *fileMenu;
QMenu *editMenu;
QAction *exitAction;
QAction *addEntryAction;
QAction *editEntryAction;
QAction *deleteEntryAction;
};

Qt 4 Livre Page 459 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

459

Le widget AddressBook correspond la fentre principale de lapplication. La proprit et les


slots fournis seront disponibles via le script. La macro Q_CLASSINFO() permet de spcifier la
classe et les identifiants dinterface associs cette dernire. Ceux-ci ont t gnrs avec un
outil tel que guid ou uuid.
Dans lexemple prcdent, nous avions spcifi la classe et les identifiants dinterface quand
nous avions export la classe QAxBouncer laide de la macro QAXFACTORY_DEFAULT().
Dans cet exemple, nous allons exporter plusieurs classes, nous ne pouvons donc pas excuter
QAXFACTORY_DEFAULT(). Nous avons deux options :
Driver QAxFactory, rimplmenter ses fonctions virtuelles pour fournir des informations
concernant les types exporter, et excuter la macro QAXFACTORY_EXPORT() pour enregistrer
le composant fabricant.
Excuter les macros QAXFACTORY_BEGIN(), QAXFACTORY_END(), QAXCLASS(), et
QAXTYPE() pour dclarer et enregistrer le composant fabricant. Cette approche exige de
spcifier la classe et lidentifiant dinterface laide de Q_CLASSINFO().
Voici la dfinition de la classe AddressBook: La troisime occurrence de Q_CLASSINFO()
pourrait vous sembler un peu bizarre. Par dfaut, les contrles ActiveX exposent non seulement leurs propres proprits, signaux, et slots leurs clients, mais aussi ceux de leurs superclasses jusqu QWidget. Lattribut ToSuperClass permet de spcifier la superclasse de
niveau suprieur (dans larbre dhritage) que nous dsirons exposer. Nous spcifions ici le
nom de la classe du composant (AddressBook) en tant que classe de niveau le plus haut
exporter, ce qui signifie que les proprits, signaux, et slots dfinis dans les superclasses
dAddressBook ne seront pas exports.
class ABItem: public QObject, public QTreeWidgetItem
{
Q_OBJECT
Q_PROPERTY(QString contact READ contact WRITE setContact)
Q_PROPERTY(QString address READ address WRITE setAddress)
Q_PROPERTY(QString phoneNumber READ phoneNumber WRITE setPhoneNumber)
Q_CLASSINFO("ClassID", "{bc82730e-5f39-4e5c-96be-461c2cd0d282}")
Q_CLASSINFO("InterfaceID", "{c8bc1656-870e-48a9-9937-fbe1ceff8b2e}")
Q_CLASSINFO("ToSuperClass", "ABItem")
public:
ABItem(QTreeWidget *treeWidget);
void setContact(const QString &contact);
QString contact() const { return text(0); }
void setAddress(const QString &address);
QString address() const { return text(1); }
void setPhoneNumber(const QString &number);
QString phoneNumber() const { return text(2); }
public slots:
void remove();
};

Qt 4 Livre Page 460 Jeudi, 7. dcembre 2006 12:14 12

460

Qt4 et C++ : Programmation dinterfaces GUI

La classe ABItem reprsente une entre dans le carnet dadresses. Elle hrite de QTreeWidgetItem pour pouvoir tre affiche dans un QTreeWidget et de QObject pour pouvoir tre
exporte sous forme dobjet COM.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!QAxFactory::isServer()) {
AddressBook addressBook;
addressBook.show();
return app.exec();
}
return app.exec();
}

Dans main(), nous vrifions si lapplication sexcute en autonome ou en tant que serveur.
Loption de ligne de commande -activex est reconnue par QApplication et excute
lapplication en tant que serveur. Si lapplication nest pas excute de cette faon, nous crons
le widget principal et nous laffichons comme nous le ferions normalement pour une application
Qt autonome.
En complment de -activex, les serveurs ActiveX comprennent les options de ligne de
commande suivantes :
-regserver enregistre le serveur dans le registre systme.
-unregserver annule lenregistrement du serveur dans le registre systme.
-dumpidl file inscrit lIDL (Interface Definition Langage) du serveur dans le fichier
spcifi.
Lorsque lapplication sexcute en tant que serveur, nous devons exporter les classes AddressBook et ABItem en tant que composants COM :
QAXFACTORY_BEGIN("{2b2b6f3e-86cf-4c49-9df5-80483b47f17b}",
"{8e827b25-148b-4307-ba7d-23f275244818}")
QAXCLASS(AddressBook)
QAXTYPE(ABItem)
QAXFACTORY_END()

Les macros prcdentes exportent un composant fabricant dobjets COM. Puisque nous devons
exporter deux types dobjets COM, nous ne pouvons pas nous contenter dexcuter
QAXFACTORY_DEFAULT() comme nous lavions fait dans lexemple prcdent.
Le premier argument de QAXFACTORY_BEGIN() correspond lID de bibliothque de types ;
le second est lID de lapplication. Entre QAXFACTORY_BEGIN() et QAXFACTORY_ END(),
nous spcifions toutes les classes pouvant tre instancies et tous les types de donnes qui ont
besoin dtre accessibles sous forme dobjets COM.
Voici le fichier .pro pour notre serveur ActiveX hors processus :
TEMPLATE
CONFIG

= app
+= qaxserver

Qt 4 Livre Page 461 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

HEADERS

SOURCES

FORMS
RC_FILE

Fonctionnalits spcifiques la plate-forme

461

= abitem.h \
addressbook.h \
editdialog.h
= abitem.cpp \
addressbook.cpp \
editdialog.cpp \
main.cpp
= editdialog.ui
= qaxserver.rc

Le fichier qaxserver.rc auquel il est fait rfrence dans le fichier .pro est un fichier standard
que lon peut copier dans le rpertoire src\activeqt\control de Qt.
Cherchez dans le rpertoire vb de lexemple un projet Visual Basic qui utilise le serveur de
carnet dadresses.
Nous en avons termin avec la prsentation du framework ActiveQt. La distribution de Qt
propose des exemples supplmentaires, et la documentation contient des informations concernant la faon de crer les modules QAxContainer et QAxServer et comment rsoudre les
problmes dinteroprabilit courants.

Prendre en charge la gestion de session X11


Lorsque nous quittons X11, certains gestionnaires de fentre nous demandent si nous dsirons
enregistrer la session. Si nous rpondons oui, les applications en cours dexcution seront automatiquement redmarres lors de la prochaine ouverture de session, au mme emplacement sur
lcran et, cerise sur le gteau, dans le mme tat.
Le composant X11 charg de lenregistrement et de la restauration de la session est le gestionnaire de session. Pour quune application Qt/X11 puisse tre prise en charge par ce gestionnaire, nous devons rimplmenter QApplication::saveState() afin denregistrer ltat de
lapplication.
Figure 20.7
Fermeture de session
sous KDE

Qt 4 Livre Page 462 Jeudi, 7. dcembre 2006 12:14 12

462

Qt4 et C++ : Programmation dinterfaces GUI

Windows 2000 et XP, et certains systmes Unix, proposent un autre mcanisme nomm Mise
en veille. Ds que lutilisateur active la mise en veille, le systme dexploitation sauvegarde
simplement la mmoire de lordinateur sur disque puis la recharge au moment de la ractivation.
Les applications ne sont pas sollicites et nont pas besoin dtre averties de lopration.
Lorsque lutilisateur demande larrt de lordinateur, nous pouvons prendre le contrle juste
avant lexcution de cette opration en rimplmentant QApplication::commitData().
Nous avons ainsi la possibilit denregistrer toute donne non sauvegarde et de dialoguer avec
lutilisateur si ncessaire. Cette partie de la gestion de session est prise en charge sur les deux
plates-formes X11 et Windows.
Notre tude de la gestion de session va seffectuer en analysant le code dune application
Tic-Tac-Toe compatible avec cette fonction. Commenons par examiner la fonction main():
int main(int argc, char *argv[])
{
Application app(argc, argv);
TicTacToe toe;
toe.setObjectName("toe");
app.setTicTacToe(&toe);
toe.show();
return app.exec();
}

Nous crons un objet Application. La classe Application hrite de QApplication et


rimplmente la fois commitData() et saveState() afin de prendre en charge la gestion
de session.
Nous crons ensuite un widget TicTacToe, que nous associons lobjet Application, puis
nous laffichons. Nous avons appel le widget TicTacToe. Nous devons attribuer des noms
dobjet uniques aux widgets de niveau suprieur si nous voulons que le gestionnaire de session
soit en mesure de restaurer les tailles et positions des fentres.
Figure 20.8
Lapplication Tic-Tac-Toe

Qt 4 Livre Page 463 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

463

Voici la dfinition de la classe Application:


class Application: public QApplication
{
Q_OBJECT
public:
Application(int &argc, char *argv[]);
void setTicTacToe(TicTacToe *tic);
void saveState(QSessionManager &sessionManager);
void commitData(QSessionManager &sessionManager);
private:
TicTacToe *ticTacToe;
};

La classe Application stocke un pointeur vers le widget TicTacToe dans une variable
prive.
void Application::saveState(QSessionManager &sessionManager)
{
QString fileName = ticTacToe->saveState();
QStringList discardCommand;
discardCommand << "rm" << fileName;
sessionManager.setDiscardCommand(discardCommand);
}

Sous X11, la fonction saveState() est appele au moment o le gestionnaire de session


veux que lapplication enregistre son tat. La fonction est aussi disponible sur dautres platesformes, mais elle nest jamais appele. Le paramtre QSessionManager nous permet de
communiquer avec le gestionnaire de session.
Nous commenons par demander au widget TicTacToe denregistrer son tat dans un
fichier. Nous affectons ensuite une valeur la commande dannulation du gestionnaire. Cette
commande est celle que le gestionnaire de session doit excuter pour supprimer toute information
stocke concernant ltat courant. Pour cet exemple, nous la dfinissons en
rm sessionfile

o sessionfile est le nom du fichier qui contient ltat enregistr pour la session, et rm est
la commande Unix standard pour supprimer des fichiers.
Le gestionnaire de session comporte aussi une commande de redmarrage. Il sagit de celle
que le gestionnaire excute pour redmarrer lapplication. Par dfaut, Qt fournit la commande
de redmarrage suivante :
appname -session id_key

Qt 4 Livre Page 464 Jeudi, 7. dcembre 2006 12:14 12

464

Qt4 et C++ : Programmation dinterfaces GUI

La premire partie, appname, est drive de argv[0]. Le composant id correspond lidentifiant de session fourni par le gestionnaire de session; dont lunicit est garantie au sein de
plusieurs applications et de diffrentes excutions de la mme application. La partie key est
ajoute afin didentifier de faon unique lheure laquelle ltat a t enregistr. Pour diverses
raisons, le gestionnaire de session peut appeler plusieurs fois saveState() au cours dune
mme session, et les diffrents tats doivent pouvoir tre distingus.
Etant donn les limites des gestionnaires de session existants, nous devons nous assurer que le
rpertoire de lapplication se trouve dans la variable denvironnement PATH si nous voulons
que lapplication puisse redmarrer correctement. Si vous dsirez en particulier tester lexemple Tic-Tac-Toe, vous devez linstaller dans le rpertoire /usr/bin par exemple et linvoquer
en tapant tictactoe.
Pour des applications simples, comme Tic-Tac-Toe, nous pourrions enregistrer ltat sous
forme dargument de ligne de commande supplmentaire de la commande de redmarrage.
Par exemple :
tictactoe -state OX-XO-X-O

Ceci nous viterait davoir stocker les donnes dans un fichier puis fournir une commande
dannulation pour supprimer le fichier.
void Application::commitData(QSessionManager &sessionManager)
{
if (ticTacToe->gameInProgress()
&& sessionManager.allowsInteraction()) {
int r = QMessageBox::warning(ticTacToe, tr("Tic-Tac-Toe"),
tr("The game hasnt finished.\n"
"Do you really want to quit?"),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape);
if (r == QMessageBox::Yes) {
sessionManager.release();
} else {
sessionManager.cancel();
}
}
}

La fonction commitData() est appele quand lutilisateur ferme la session. Nous pouvons la
rimplmenter de sorte dafficher un message davertissement qui signale lutilisateur le
risque de perte de donnes. Limplmentation par dfaut ferme tous les widgets de niveau le plus
haut, ce qui donne le mme rsultat que lorsque lutilisateur ferme les fentres lune aprs
lautre en cliquant sur le bouton de fermeture de leur barre de titre. Au Chapitre 3, nous avons
vu comment rimplmenter closeEvent() pour dtecter cette situation et afficher un message.
Pour cet exemple, nous allons rimplmenter commitData() et afficher un message demandant lutilisateur de confirmer la fermeture de session si un jeu est en cours dexcution et si
le gestionnaire de session nous permet de dialoguer avec lutilisateur. Si lutilisateur clique sur

Qt 4 Livre Page 465 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

465

Yes, nous appelons release() pour ordonner au gestionnaire de poursuivre la fermeture de


session ; sil clique sur No, nous appelons cancel() pour annuler lopration.
Figure 20.9
"Vous dsirez vraiment
quitter ?"

Examinons maintenant la classe TicTacToe:


class TicTacToe: public QWidget
{
Q_OBJECT
public:
TicTacToe(QWidget *parent = 0);
bool gameInProgress() const;
QString saveState() const;
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
enum { Empty = -, Cross = X, Nought = O };
void clearBoard();
void restoreState();
QString sessionFileName() const;
QRect cellRect(int row, int column) const;
int cellWidth() const { return width() / 3; }
int cellHeight() const { return height() / 3; }
bool threeInARow(int row1, int col1, int row3, int col3) const;
char board[3][3];
int turnNumber;
};

La classe TicTacToe hrite de QWidget et rimplmente sizeHint(), paintEvent(), et


mousePressEvent(). Elle fournit aussi les fonctions gameInProgress() et saveState()
que nous avions utilises dans notre classe Application.
TicTacToe::TicTacToe(QWidget *parent)
: QWidget(parent)
{

Qt 4 Livre Page 466 Jeudi, 7. dcembre 2006 12:14 12

466

Qt4 et C++ : Programmation dinterfaces GUI

clearBoard();
if (qApp->isSessionRestored())
restoreState();
setWindowTitle(tr("Tic-Tac-Toe"));
}

Dans le constructeur, nous effaons le tableau et si lapplication avait t invoque avec


loption -session, nous appelons la fonction prive restoreState() pour recharger lancienne
session.
void TicTacToe::clearBoard()
{
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
board[row][column] = Empty;
}
}
turnNumber = 0;
}

Dans clearBoard(), nous effaons toutes les cellules et nous dfinissons turnNumber 0.
QString TicTacToe::saveState() const
{
QFile file(sessionFileName());
if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column)
out << board[row][column];
}
}
return file.fileName();
}

Dans saveState(), nous enregistrons ltat du tableau sur disque. Le format est simple, avec
X pour les croix, O pour les ronds, et -+- pour les cellules vides.
QString TicTacToe::sessionFileName() const
{
return QDir::homePath() + "/.tictactoe_" + qApp->sessionId() + "_"
+ qApp->sessionKey();
}

La fonction prive sessionFileName() renvoie le nom de fichier pour lID et la cl de


session en cours. Cette fonction est exploite la fois par saveState() et par restoreState(). Le nom du fichier est constitu partir de ces ID et cl de session.
void TicTacToe::restoreState()
{
QFile file(sessionFileName());

Qt 4 Livre Page 467 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 20

Fonctionnalits spcifiques la plate-forme

467

if (file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
in >> board[row][column];
if (board[row][column]!= Empty)
++turnNumber;
}
}
}
update();
}

Dans restoreState(), nous chargeons le fichier qui correspond la session restaure et nous
remplissons le tableau avec ces informations. Nous dduisons la valeur de turnNumber
partir du nombre de X et O sur le tableau.
Dans le constructeur de TicTacToe, nous appelons restoreState() lorsque QApplication::isSessionRestored() renvoie true. Dans ce cas, sessionId() et sessionKey()
renvoient les mmes valeurs que lorsque ltat de lapplication tait enregistr, et sessionFileName() renvoie le nom de fichier pour cette session.
Les tests et le dbogage de la gestion de session peuvent tre pnibles, parce que vous devez
continuellement vous connecter puis vous dconnecter. Un moyen dviter ces oprations
consiste utiliser lutilitaire standard xsm fourni avec X11. Le premier appel de xsm ouvre
une fentre du gestionnaire de session et un terminal. Les applications dmarres dans ce
terminal vont toutes utiliser xsm comme gestionnaire de session plutt que celui du systme.
Nous pouvons alors nous servir de la fentre de xsm pour terminer, redmarrer, ou supprimer
une session, et voir si notre application se comporte normalement. Vous trouverez tous les
dtails de cette procdure ladresse http://doc.trolltech.com/4.1/session.html.

Qt 4 Livre Page 468 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 469 Jeudi, 7. dcembre 2006 12:14 12

21
Programmation
embarque
Au sommaire de ce chapitre
Dmarrer avec Qtopia
Personnaliser Qtopia Core

Le dveloppement des logiciels destins sexcuter sur des priphriques mobiles tels
que les PDA et les tlphones portables prsente des difficults bien spcifiques parce que
les systmes embarqus possdent gnralement des processeurs plus lents, une capacit de mmoire permanente rduites (mmoire flash ou disque dur), moins de mmoire
et un cran plus petit que les ordinateurs de bureau.
Qtopia Core (prcdemment nomm Qt/Embedded) est une version de Qt optimise
pour le systme dexploitation Linux embarqu. Qtopia Core fournit les mmes outils et
la mme API que les versions de bureau de Qt (Qt/Windows, Qt/X 11 et Qt/Mac)
complts des classes et outils requis pour la programmation embarque. Par le biais
dune double licence, ce systme est disponible la fois pour le dveloppement open
source et le dveloppement commercial.

Qt 4 Livre Page 470 Jeudi, 7. dcembre 2006 12:14 12

470

Qt4 et C++ : Programmation dinterfaces GUI

Qtopia Core peut sexcuter sur nimporte quel matriel quip de Linux (notamment les
architectures Intel x86, MIPS, ARM, StrongARM, Motorola 68000, et PowerPC). Il comporte
une mmoire dimage et prend en charge un compilateur C++. Contrairement Qt/X11, il na
pas besoin du systme XWindow ; en fait, il implmente son propre systme de fentrage
(QWS) ce qui permet doptimiser au maximum la gestion des mmoires. Pour rduire encore
ses besoins en mmoire, Qtopia Core peut tre recompil en excluant les fonctions non
utilises. Si les applications et composants exploits sur un priphrique sont connus par
avance, ils peuvent tre compils ensemble pour fournir un seul excutable avec des liens statiques
vers les bibliothques de Qtopia Core.
Qtopia Core bnficie galement de diverses fonctionnalits qui existent aussi dans les
versions bureau de Qt, notamment lusage extensif du partage de donnes implicite ("copie
lors de lcriture") pour ce qui concerne la technique doptimisation de la mmoire, la prise en
charge des styles de widget personnaliss via QStyle et un systme de disposition qui sadapte
pour optimiser lespace cran disponible.
Qtopia Core est au cur de loffre embarque de Trolltech, qui comprend galement Qtopia
Platform, Qtopia PDA, et Qtopia Phone. Ces versions fournissent les classes et applications
conues spcifiquement pour les priphriques portables et elles peuvent tre intgres avec
plusieurs machines virtuelles Java tiers.

Dmarrer avec Qtopia


Les applications de Qtopia Core peuvent tre dveloppes sur toute plate-forme quipe dune
chane doutils multiplate-forme. Loption la plus courante consiste installer un compilateur
crois GNU C++ sur un systme UNIX. Ce processus et simplifi par un script et un ensemble
de correctifs fournis par Dan Kegel ladresse http://kegel.com/crosstool/. Puisque Qtopia
Core contient lAPI de Qt, il est gnralement possible de travailler avec une version bureau de
Qt, telle que Qt/X11 ou Qt/Windows pour la plupart des dveloppements.
Le systme de configuration de Qtopia Core prend en charge les compilateurs croiss, via
loption -embedded du script configure. Par exemple, pour obtenir une gnration destine
larchitecture ARM, vous devriez saisir
./configure -embedded arm

Nous avons la possibilit de crer des configurations personnalises en ajoutant de nouveaux


fichiers dans le rpertoire mkspecs/ qws de Qt.
Qtopia Core dessine directement dans la mmoire dimage de Linux (la zone de mmoire associe avec laffichage vido). Pour accder cette mmoire dimage, vous devrez accorder des
permissions en criture au priphrique /dev/fb0.
Pour excuter les applications de Qtopia Core, nous devons commencer par dmarrer un
processus qui joue le rle de serveur. Celui-ci est charg dallouer des zones dcran aux clients
et de gnrer les vnements de souris et de clavier. Toute application Qtopia Core peut devenir

Qt 4 Livre Page 471 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 21

Programmation embarque

471

serveur si vous spcifiez -qws sur sa ligne de commande ou si vous transmettez QApplication::GuiServer comme troisime paramtre du constructeur de QApplication.
Les applications client communiquent avec le serveur Qtopia Core par le biais de la mmoire
partage. En arrire plan, les clients se dessinent eux-mmes dans cette mmoire partage et
sont chargs dafficher leurs propres dcorations de fentre. Cela permet dobtenir un niveau
de communication minimum entre les clients et le serveur tout en proposant une interface utilisateur soigne. Les applications de Qtopia Core sappuient normalement sur QPainter pour
se dessiner elles-mmes mais elles peuvent aussi accder au matriel vido directement
laide de QDirectPainter.
Les clients ont la possibilit de communiquer via le protocole QCOP. Un client peut couter sur
un canal nomm en crant un objet QCopChannel et en se connectant son signal received().
Par exemple :
QCopChannel *channel = new QCopChannel("System", this);
connect(channel, SIGNAL(received(const QString &, const QByteArray &)),
this, SLOT(received(const QString &, const QByteArray &)));

Un message QCOP est constitu dun nom et ventuellement dun QByteArray. La fonction
QCopChannel::send() statique diffuse un message sur le canal. Par exemple :
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out << QDateTime::currentDateTime();
QCopChannel::send("System", "clockSkew(QDateTime)", data);

Lexemple prcdent illustre un idiome connu : nous nous servons de QDataStream pour
coder les donnes, et pour garantir que le QByteArray sera correctement interprt par le
destinataire, nous joignons le format de donnes dans le nom du message comme sil sagissait
dune fonction C++.
Plusieurs variables denvironnement affectent les applications de Qtopia Core. Les plus importantes sont QWS_MOUSE_PROTO et QWS_KEYBOARD, qui spcifient le priphrique souris et le
type de clavier. Vous trouverez une liste complte des variables denvironnement sur la page
http://doc.trolltech.com/4.1/emb-envvars. html.
Si UNIX est la plate-forme de dveloppement, nous pouvons tester lapplication en utilisant la
mmoire dimage virtuelle de Qtopia (qvfb), une application X11 qui simule pixel par pixel
la mmoire dimage relle. Cela acclre considrablement le cycle de dveloppement. Pour
activer la prise en charge de la mmoire virtuelle dans Qtopia Core, vous transmettez loption
-qvfb au script configure. Noubliez pas que cette option nest pas destine un usage en
production. Lapplication de mmoire dimage virtuelle se trouve dans le rpertoire tools/
qvfb et peut tre invoque de la faon suivante :
qvfb -width 320 -height 480 -depth 32

Une autre option qui fonctionne sur la plupart des plates-formes consiste utiliser VNC
(Virtual Network Computing) pour excuter des applications distance. Pour activer la prise en

Qt 4 Livre Page 472 Jeudi, 7. dcembre 2006 12:14 12

472

Qt4 et C++ : Programmation dinterfaces GUI

charge de VNC dans Qtopia Core, vous transmettez loption -qt-gfx-vnc configure.
Lancez ensuite vos applications Qtopia Core avec loption de ligne de commande -display
VNC:0 et excutez un client VNC qui pointe sur lhte sur lequel vos applications sexcutent.
La taille et la rsolution de lcran peuvent tre spcifis en dfinissant les variables denvironnement QWS_SIZE et QWS_DEPTH sur lhte qui excute les applications Qtopia Core (par
exemple, QWS_SIZE=320x480 et QWS_DEPTH=32).

Personnaliser Qtopia Core


A linstallation de Qtopia Core, nous pouvons spcifier les fonctionnalits dont nous navons
pas besoin afin de rduire loccupation mmoire. Qtopia Core comprend plus dune centaine
de fonctionnalits configurables, chacune tant associe un symbole de prprocesseur.
QT_NO_FILEDIALOG, par exemple, exclut QFileDialog de la bibliothque QtGui, et
QT_NO_I18N renonce la prise en charge de linternationalisation. Les fonctionnalits sont
numres dans le fichier src/ corelib/qfeatures.txt.
Qtopia Core propose cinq configurations type (minimum, small, medium, large, et dist) qui
sont stockes dans les fichiers src/corelib/qconfig_xxx.h. Vous spcifiez ces configurations
via loption -qconfig xxx de configure, par exemple :
./configure -qconfig small

Pour crer des configurations personnalises, nous pouvons fournir manuellement un fichier
qconfig-xxx.h et lutiliser comme sil sagissait dune configuration standard. Nous pourrions
aussi nous servir de loutil graphique qconfig, disponible dans le sous-rpertoire tools de Qt.
Qtopia Core propose les classes suivantes pour le dialogue avec les priphriques dentre et
de sortie et pour personnaliser laspect et le comportement du systme de fentrage :
Classe

Classe de base pour

QScreen

Pilotes dcran

QScreenDriverPlugin

plug-in de pilote dcran

QWSMouseHandler

Pilotes de souris

QMouseDriverPlugin

Plug-in de pilotes de souris

QWSKeyboardHandler

Pilotes de clavier

QKbdDriverPlugin

Plug-in de pilote de clavier

QWSInputMethod

Mthodes dentre

QDecoration

Styles de dcoration de fentre

QDecorationPlugin

Plug-in fournissant des styles de dcoration de fentre

Qt 4 Livre Page 473 Jeudi, 7. dcembre 2006 12:14 12

Chapitre 21

Programmation embarque

473

Vous obtenez la liste des pilotes prdfinis, des mthodes dentre, et des styles de dcoration
de fentre en excutant le script configure avec loption -help.
Vous spcifiez le pilote vido laide de loption de ligne de commande -display au dmarrage du serveur Qtopia Core, comme expliqu dans la section prcdente, ou en dfinissant la
variable denvironnement QWS_DISPLAY. Vous spcifiez le pilote de souris et le priphrique
associ via la variable denvironnement QWS_MOUSE_PROTO, dont la valeur suit la syntaxe
type : device, o type est un des pilotes pris en charge et device le chemin daccs au priphrique (par exemple, QWS_MOUSE_PRO-TO=IntelliMouse:/dev/mouse). Les claviers sont
grs dune faon analogue dans la variable denvironnement QWS_ KEYBOARD. Les mthodes
dentre et dcorations de fentre sont dfinies par programme dans le serveur en excutant
QWSServer::setCurrentInputMethod() et QApplication::qwsSetDecoration().
Les styles de dcoration de fentre sont dfinis indpendamment du style de widget, qui hrite
de QStyle. Il est tout fait possible, par exemple, de dfinir Windows comme style de dcoration de fentre et Plastique comme style de widget. Si vous en avez envie, les dcorations
peuvent tre rgles fentre par fentre.
La classe QWSServer fournit diverses fonctions pour personnaliser le systme de fentrage.
Les applications qui sexcutent en tant que serveurs Qtopia Core peuvent accder linstance
unique QWSServer via la variable globale qwsServer, initialise dans le constructeur de
QApplication.
Qtopia Core prend en charge les formats de police suivants : TrueType (TTF), PostScript Type
1, Bitmap Distribution Format (BDF), et Qt Pre-rendered Fonts (QPF).
QPF tant un format brut, il est plus rapide et gnralement plus compact que des formats
vectoriels tels que TTF et Type 1 si le besoin se limite une ou deux tailles diffrentes. Loutil
makeqpf permet de crer des fichiers QPF partir de fichiers TTF ou Type 1. Une autre solution
consiste excuter nos applications avec loption de ligne de commande -savefonts.
Au moment dcrire ces lignes, Trolltech dveloppe une couche supplmentaire au-dessus de
Qtopia Core pour rendre le dveloppement des applications embarques encore plus rapide et
efficace. Une prochaine dition de cet ouvrage devrait contenir de plus amples informations
ce propos.

Qt 4 Livre Page 474 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 475 Jeudi, 7. dcembre 2006 12:14 12

Annexes
A

Installer Qt

Introduction au langage C++


pour les programmeurs Java et C#

Qt 4 Livre Page 476 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 477 Jeudi, 7. dcembre 2006 12:14 12

A
Installer Qt
Au sommaire de ce chapitre
A propos des licences
Installer Qt/Windows
Installer Qt/Mac
Installer Qt/X11
Cette annexe explique comment installer Qt sur votre systme partir du CD qui
accompagne cet ouvrage. Ce CD comporte les ditions de Qt 4.1.1 pour Windows,
Mac OS X, et X11 (pour Linux et la plupart des versions dUnix). Elles intgrent toutes
SQLite, une base de donnes du domaine public, ainsi quun pilote SQLite. Les ditions
de Qt fournies sur le CD vous sont proposes pour des raisons pratiques. Si vous envisagez srieusement le dveloppement logiciel, il est prfrable de tlcharger la dernire
version de Qt partir de http://www.trolltech.com/download/ ou dacheter une
version commercialise.
Trolltech fournit aussi Qtopia Core pour la cration dapplications destines aux priphriques embarqus quips de Linux tels que les PDA et les tlphones portables. Si
vous envisagez de crer des applications embarques, vous pouvez obtenir Qtopia Core
depuis la page Web de tlchargement de Trolltech.
Les exemples dapplication tudis dans cet ouvrage se trouvent dans le rpertoire
examples du CD. De plus, Qt propose de nombreux petits exemples dapplication dans
le sous rpertoire examples.

Qt 4 Livre Page 478 Jeudi, 7. dcembre 2006 12:14 12

478

Qt4 et C++ : Programmation dinterfaces GUI

A propos des licences


Qt est propos sous deux formes : open source et commerciale. Les ditions open source sont
disponibles gratuitement, alors que vous devez payer pour les ditions commerciales.
Le logiciel du CD convient pour crer des applications destines votre usage personnel ou
pour votre formation.
Si vous dsirez distribuer les applications que vous allez crer avec la version Open source de
Qt, vous devez respecter les termes et conditions de licence spcifiques au logiciel que vous
utilisez pour crer ces applications. Pour les ditions Open source, ces termes et conditions
impliquent lutilisation de la licence GNU General Public License (GPL). Les licences libres
telles que la licence GPL accordent des droits aux utilisateurs des applications, notamment
celui de visualiser et de modifier le code source et de distribuer les applications (sous les
mmes termes). Si vous dsirez distribuer vos applications sans le code source ou si vous
voulez appliquer vos propres conditions commerciales, vous devez acheter les ditions
commerciales du logiciel qui sert crer vos applications. Ces ditions vous permettent en
effet de vendre et de distribuer vos applications sous vos propres termes.
Le CD contient les versions GPL de Qt pour Windows, Mac OS X, et X11. Le texte lgal
complet des licences est inclus avec les packages sur le CD, ainsi que les informations concernant
la faon dobtenir les versions commerciales.

Installer Qt/Windows
Lorsque vous insrez le CD sur un ordinateur quip de Windows, le programme dinstallation
devrait dmarrer automatiquement. Sinon, ouvrez lexplorateur de fichiers pour localiser le
rpertoire racine du CD et double-cliquez sur install.exe. (Il est possible que ce
programme se nomme install selon la faon dont votre systme est configur.)
Si vous disposez dj du compilateur MinGW C++ vous devez prciser le rpertoire dans
lequel il est install ; sinon cochez la case afin dinstaller aussi ce programme. La version GPL
de Qt fournie sur le CD ne fonctionnera pas avec Visual C++, vous devez donc absolument
installer MinGW si vous ne lavez pas encore fait. Le programme dinstallation vous propose
galement dinstaller les exemples qui accompagnent cet ouvrage. Les exemples standard de
Qt sont eux automatiquement installs ainsi que la documentation.
Si vous choisissez dinstaller le compilateur MinGW, vous constaterez certainement un dlai
entre la fin de linstallation de ce dernier et le dbut de linstallation de Qt.
Aprs linstallation, un nouveau dossier apparatra dans le menu Dmarrer intitul "Qt by
Trolltech v4.1.1 (OpenSource)". Ce dossier propose des raccourcis vers Qt Assistant et Qt
Designer, et un troisime nomm "Qt 4.1.1 Command Prompt" qui dmarre une fentre de

Qt 4 Livre Page 479 Jeudi, 7. dcembre 2006 12:14 12

Annexe A

Installer Qt

479

console. Ds que vous ouvrez cette fentre, elle va dfinir les variables denvironnement pour
la compilation des programmes Qt avec MinGW. Cest dans cette fentre que vous allez excuter
qmake et make afin de gnrer vos applications Qt.

Installer Qt/Mac
Avant dinstaller Qt sur Mac OS X, vous devez dabord avoir install le jeu doutils Xcode
Tools dApple. Le CD (ou le DVD) contenant ces outils est gnralement fourni avec Mac OS
X ; vous pouvez aussi les tlcharger partir du site Apple Developer Connection, ladresse
http://developer.apple.com.
Si vous travaillez avec Mac OS X 10.4 (Tiger) et Xcode Tools 2.x (avec GCC 4.0.x), vous
pouvez excuter le programme dinstallation dcrit prcdemment. Si vous possdez une
version plus ancienne de Mac OS X, ou une version plus ancienne de GCC, vous devrez installer manuellement le package source. Celui-ci se nomme qt-mac-opensource4.1.1.tar.gz et il est stock dans le dossier mac sur le CD. Si vous linstallez, suivez les
instructions de la section suivante qui concernent linstallation de Qt sous X11.
Pour excuter le programme dinstallation, insrez le CD et double-cliquez sur le package
nomm Qt.mpkg. Le programme dinstallation, Installer.app, va se lancer et Qt sera
install avec les exemples standard, la documentation, et les exemples associs cet ouvrage.
Cette installation seffectue dans le rpertoire /Developer, et les exemples du livre sont enregistrs dans le rpertoire /Developer/Examples/Qt4Book.
Pour excuter des commandes telles que qmake et make, vous devrez avoir recours une fentre terminal, par exemple, Terminal.app dans /Applications/Utilities. Vous avez aussi
la possibilit de gnrer des projets Xcode laide de qmake. Pour gnrer, par exemple, un
projet Xcode pour lexemple hello, dmarrez une console telle que Terminal.app, placezvous sur le rpertoire /Developer/Examples/Qt4Book/chap01/hello, puis saisissez la
commande suivante :
qmake -spec macx-xcode hello.pro

Installer Qt/X11
Pour installer Qt sur son emplacement par dfaut sous X11, vous devez tre connect en tant
que root. Si vous navez pas ce niveau daccs, spcifiez largument -prefix de configure
pour indiquer un rpertoire dans lequel vous avez lautorisation dcrire.
1. Placez-vous sur un rpertoire temporaire. Par exemple :
cd /tmp

2. Dcompressez le fichier archive du CD :

Qt 4 Livre Page 480 Jeudi, 7. dcembre 2006 12:14 12

480

Qt4 et C++ : Programmation dinterfaces GUI

cp /cdrom/x11/qt-x11-opensource-src-4.1.1.tgz .
gunzip qt-x11-opensource-src-4.1.1.tgz
tar xvf qt-x11-opensource-src-4.1.1.tar

Vous allez ainsi crer le rpertoire /tmp/qt-x11-opensource-src-4.1.1, en supposant


que votre CD-ROM soit mont en /cdrom. Qt exige le logiciel GNU tar; qui se nomme
gtar sur certains systmes.
3. Excutez loutil configure avec vos options favorites afin de gnrer la bibliothque de
Qt et les outils qui accompagnent ce framework :
cd /tmp/qt-x11-opensource-src-4.1.1
./configure

Vous pouvez excuter ./configure -help pour obtenir une liste des options de configuration.
4. Pour gnrer Qt, saisissez
make

Cette commande va crer la bibliothque et compiler toutes les dmos, les exemples et les
outils. Sur certains systmes, make se nomme gmake.
5. Pour installer Qt, saisissez
su -c "make install"

puis saisissez le mot de passe root. Ceci va installer Qt dans /usr/local/Troll-tech/


Qt-4.1.1. Vous pouvez choisir un autre rpertoire de destination via loption -prefix de
configure, et si vous disposez dun accs en criture sur ce rpertoire il vous suffit de
saisir :
make install

6. Dfinissez certaines variables denvironnement pour Qt.


Si vous travailler avec le shell bash, ksh, zsh, ou sh, ajoutez les lignes suivantes dans votre
fichier .profile:
PATH=/usr/local/Trolltech/Qt-4.1.1/bin:$PATH
export PATH

Si vous travaillez avec le shell csh ou tcsh, ajoutez la ligne suivante dans votre fichier
.login:
setenv PATH /usr/local/Trolltech/Qt-4.1.1/bin:$PATH

Si vous aviez prcis -prefix avec configure, utilisez le chemin indiqu plutt que le
chemin par dfaut de la ligne prcdente.

Qt 4 Livre Page 481 Jeudi, 7. dcembre 2006 12:14 12

Annexe A

Installer Qt

481

Si votre compilateur ne prend pas en charge rpath, vous devez aussi tendre la variable
denvironnement LD_LIBRARY_PATH pour inclure /usr/local/ Trolltech/Qt-4.1.1/lib.
Ce nest pas ncessaire sous Linux avec GCC.
Qt est distribu avec une application de dmonstration, qtdemo, qui exploite de nombreuses
fonctionnalits de la bibliothque. Elle reprsente un bon point de dpart pour tester les
possibilits de Qt. Vous pouvez consulter la documentation de Qt soit en visitant le site
http://doc.trolltech.com, soit en excutant Qt Assistant, lapplication daide de Qt, que vous
obtenez en tapant assistant dans une fentre console.

Qt 4 Livre Page 482 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 483 Jeudi, 7. dcembre 2006 12:14 12

B
Introduction au
langage C++ pour les
programmeurs Java et C#
Au sommaire de ce chapitre
Dmarrer avec C++
Principales diffrences de langage
La bibliothque C++ standard
Cette annexe fournit une courte introduction au langage C++ pour les dveloppeurs qui
connaissent dj Java ou C#. Nous supposons que vous matrisez les concepts de
lorient objet tels que lhritage et le polymorphisme, et que vous dsirez tudier le
C++. Pour ne pas transformer cet ouvrage en bible de 1500 pages qui couvrirait la totalit du langage C++, cette annexe vous livre uniquement lessentiel. Elle prsente les
techniques et connaissances de base requises pour comprendre les programmes prsents dans le reste de cet ouvrage, avec suffisamment dinformations pour dvelopper des
applications graphiques C++ multiplateforme laide de Qt.
Au moment dcrire ces lignes, C++ est la seule option raliste pour crire des applications graphiques orientes objet performantes, multiplateforme. Les dtracteurs de ce
langage soulignent gnralement que Java et C#, qui ont abandonn la compatibilit
avec le langage C, sont plus pratiques utiliser ; en fait, Bjarne Stroustrup, linventeur

Qt 4 Livre Page 484 Jeudi, 7. dcembre 2006 12:14 12

484

Qt4 et C++ : Programmation dinterfaces GUI

du C++, signale dans son ouvrage The Design and Evolution of C++ que "dans le langage
C++, il y a beaucoup moins de difficults de langage viter".
Heureusement, lorsque nous programmons avec Qt, nous exploitons gnralement un sousensemble de C++ qui est trs proche du langage utopique envisag par Stroustrup, ce qui nous
laisse libre de nous concentrer sur le problme immdiat. De plus, Qt tend C++ sur plusieurs
points de vue, par le biais de son mcanisme "signal et slot" innovant, de sa prise en charge
dUnicode et de son mot-cl foreach.
Dans la premire section de cette annexe, nous allons voir comment combiner des fichiers de
code source C++ afin dobtenir un programme excutable. Nous serons ainsi amens tudier
les concepts de base de C++ tels que les units de compilation, les fichiers den-tte, les
fichiers dobjets, les bibliothques et nous apprendrons devenir familier avec le prprocesseur,
le compilateur et lditeur de liens C++.
Nous examinerons ensuite les diffrences de langage les plus importantes entre C++, Java et C# :
comment dfinir des classes, comment utiliser les pointeurs et rfrences, comment surcharger les
oprateurs, comment utiliser le prprocesseur et ainsi de suite. Mme si la syntaxe de C++ semble
analogue celle de Java ou C# au premier abord, les concepts sous-jacents diffrent de faon
subtile. Dautre part, en tant que source dinspiration pour Java et C#, le langage C++ comporte
de nombreux points communs avec ces deux langages, notamment les types de donnes similaires, les mmes oprateurs arithmtiques et les mmes instructions de contrle de flux de base.
La dernire section est ddie la bibliothque C++ standard qui fournit une fonctionnalit prt
lemploi que vous pouvez exploiter dans tout programme C++. Cette bibliothque rsulte de plus
de 30 annes de dveloppement, et en tant que telle fournit une large gamme dapproches comprenant les styles de programmation procdural, orient objet, et fonctionnel, ainsi que des macros
et des modles. Compare aux bibliothques Java et C#, la porte de la bibliothque C++ standard est relativement limite ; elle ne fournit aucun support au niveau de linterface graphique
GUI, de la programmation multithread, des bases de donnes, de linternationalisation, de la gestion
de rseau, XML et Unicode. Pour tendre la porte de C++ dans ces domaines, les dveloppeurs
C++ doivent avoir recours diverses bibliothques (souvent spcifiques la plate-forme).
Cest ce niveau que Qt apporte son bonus. Qt a dmarr comme "bote outils" GUI multiplateforme (un ensemble de classes qui permettent dcrire des applications interface utilisateur graphique portables) mais qui a rapidement volu en framework part entire qui tend
et remplace partiellement la bibliothque C++ standard. Bien que cet ouvrage traite de Qt, il
est intressant de connatre ce que la bibliothque C++ standard a offrir, puisque vous risquez
davoir besoin de travailler avec du code qui lutilise.

Dmarrer avec C++


Un programme C++ est constitu dune ou plusieurs units de compilation. Chacune de ces
units est un fichier de code source distinct, typiquement avec une extension .cpp (les autres
extensions courantes sont .cc et .cxx) que le compilateur traite dans le mme cycle dexcution.

Qt 4 Livre Page 485 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

485

Pour chaque unit de compilation, le compilateur gnre un fichier objet, avec lextension
.obj (sous Windows) ou .o (sous Unix et Mac OS X). Le fichier objet est un fichier binaire
qui contient le code machine destin larchitecture sur laquelle le programme va sexcuter.
Une fois que les fichiers .cpp ont t compils, nous pouvons combiner les fichiers objet fin de
crer un excutable laide dun programme particulier nomm diteur de liens. Cet diteur
de liens concatne les fichiers objet et rsout les adresses mmoire des fonctions et autres
symboles auxquels font rfrence les units de compilation.
Figure B.1
Le processus
de compilation de C++
(sous Windows)

unit1.cpp

unit2.cpp

unit3.cpp

Compilation
unit1.obj

unit2.obj

unit3.obj

Edition des liens


program.exe

Lorsque vous gnrez un programme, une seule unit de compilation doit contenir la fonction
main() qui joue le rle de point dentre dans le programme. Cette fonction nappartient
aucune classe; il sagit dune fonction globale.
Contrairement Java, pour lequel chaque fichier source doit contenir une classe exactement,
C++ nous laisse libre dorganiser nos units de compilation. Nous avons la possibilit dimplmenter plusieurs classes dans le mme fichier .cpp, ou de rpartir limplmentation dune
classe dans plusieurs fichiers .cpp. Nous pouvons aussi choisir nimporte quel nom pour nos
fichiers source. Quand nous effectuons une modification dans un fichier .cpp particulier, il
suffit de recompiler uniquement ce fichier puis dexcuter de nouveau lditeur de liens sur
application pour crer un nouvel excutable.
Avant de poursuivre, examinons rapidement le code source dun programme C++ trs simple
qui calcule le carr dun entier. Le programme est constitu de deux units de compilation :
main.cpp et square.cpp.
Voici square.cpp:
double square(double n)
{
return n * n;
}

1
2
3
4

Ce fichier contient simplement une fonction globale nomme square() qui renvoie le carr de
son paramtre.
Voici main.cpp:
1
2

#include <cstdlib>
#include <iostream>

Qt 4 Livre Page 486 Jeudi, 7. dcembre 2006 12:14 12

486

Qt4 et C++ : Programmation dinterfaces GUI

using namespace std;

double square(double);

5
6
7
8
9
10
11
12
13
14

int main(int argc, char *argv[])


{
if (argc!= 2) {
cerr << "Usage: square <number>" << endl;
return 1;
}
double n = strtod(argv[1], 0);
cout << "The square of " << argv[1] << " is " << square(n) << endl;
return 0;
}

Le fichier source de main.cpp contient la dfinition de la fonction main(). En C++, cette fonction reoit en paramtres un int et un tableau de char* (un tableau de chanes de caractres).
Le nom du programme est disponible en tant que argv[0] et les arguments de ligne de
commande en tant que argv[1], argv[2], , argv[argc -1]. Les noms de paramtre argc
("nombre darguments") et argv ("valeurs des arguments") sont conventionnels. Si le
programme na pas accs aux arguments de ligne de commande, nous pouvons dfinir main()
sans paramtre.
La fonction main() utilise strtod() ("string to double"), cout (flux de sortie standard de
C++), et cerr (flux derreur standard du C++) de la bibliothque Standard C++ pour convertir
largument de ligne de commande en double puis pour afficher le texte sur la console. Les
chanes, les nombres, et les marqueurs de fin de ligne (endl) sont transmis en sortie laide de
loprateur <<, qui est aussi utilis pour les oprations avec dcalage des bits. Pour obtenir
cette fonctionnalit standard, il faut coder les deux directives #include des lignes 1 et 2.
La directive using namespace de la ligne 3 indique au compilateur que nous dsirons importer tous les identificateurs dclars dans lespace de noms std da528ns lespace de noms
global. Cela nous permet dcrire strtod(), cout, cerr, et endl plutt que les versions
compltement qualifies std::strtod(), std::cout, std::cerr, et std::endl. En C++,
loprateur :: spare les composants dun nom complexe.
La dclaration de la ligne 4 est un prototype de fonction. Elle signale au compilateur quune
fonction existe avec les paramtres et la valeur de retour indiqus. La fonction relle peut se
trouver dans la mme unit de compilation ou dans une autre. En labsence de ce prototype,
le compilateur aurait refus lappel de la ligne 12. Les noms de paramtre dans les prototypes de
fonction sont optionnels.
La procdure de compilation du programme varie dune plate-forme lautre. Pour compiler sur
Solaris, par exemple, avec le compilateur C++ de Sun, il faudrait taper les commandes suivantes :
CC -c main.cpp
CC -c square.cpp
ld main.o square.o -o square

Qt 4 Livre Page 487 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

487

Les deux premires lignes invoquent le compilateur afin de gnrer les fichiers .o pour les
fichiers .cpp. La troisime ligne invoque lditeur de liens et gnre un excutable nomm
square, que nous pouvons ensuite appeler de la faon suivante :
./square 64

Le programme affiche le message suivant sur la fentre de console :


The square of 64 is 4096

Pour compiler le programme, vous prfrez peut -tre obtenir laide de votre gourou C++
local. Si vous ny parvenez pas, lisez quand mme la suite de cette annexe sans rien compiler et
suivez les instructions du Chapitre 1 pour compiler votre premire application C++/Qt. Qt
fournit des outils partir desquels il nest pas difficile de gnrer des applications pour
nimporte quelle plate-forme.
Revenons notre programme. Dans une application du monde rel, nous devrions normalement placer le prototype de la fonction square() dans un fichier spar puis inclure ce fichier
dans toutes les units de compilation dans lesquelles nous avons besoin dappeler la fonction.
Un tel fichier est nomm fichier den-tte et il comporte gnralement lextension .h (.hh,
.hpp, et .hxx sont galement courantes). Si nous reprenons notre exemple avec la mthode du
fichier den-tte, il faudrait crer le fichier square.h avec le contenu suivant :
1
2
3
4

#ifndef SQUARE_H
#define SQUARE_H
double square(double);
#endif

Le fichier den-tte runit trois directives de prprocesseur (#ifndef, #define, et #endif).


Elles garantissent que le fichier sera trait une seule fois, mme si le fichier den-tte est inclus
plusieurs fois dans la mme unit de compilation (une situation que lon retrouve lorsque des
fichiers den-tte incluent dautres fichiers den-tte). Par convention, le symbole de prprocesseur utilis pour obtenir ce rsultat est driv du nom de fichier (dans notre exemple,
SQUARE_H). Nous reviendrons au prprocesseur un peu plus loin dans cette annexe.
Voici la syntaxe du nouveau fichier main.cpp:
1 #include <cstdlib>
2 #include <iostream>
3 #include "square.h"
4 using namespace std;
5 int main(int argc, char *argv[])
6 {
7
if (argc!= 2) {
8
cerr << "Usage: square <number>" << endl;
9
return 1;
10
}

Qt 4 Livre Page 488 Jeudi, 7. dcembre 2006 12:14 12

488

Qt4 et C++ : Programmation dinterfaces GUI

11
double n = strtod(argv[1], 0);
12
cout << "The square of " << argv[1] << " is " << square(n) << endl;
13
return 0;
14 }

La directive #include en ligne 3 insre cet endroit le contenu du fichier square.h. Les
directives qui dbutent par un # sont prises en charge par le prprocesseur C++ avant mme
le dbut de la compilation. Dans le temps, le prprocesseur tait un programme spar que le
programmeur invoquait manuellement avant dexcuter le compilateur. Les compilateurs
modernes grent dsormais implicitement ltape du prprocesseur.
Les directives #include en lignes 1 et 2 insrent le contenu des fichiers den-tte cstdlib et
iostream, qui font partie de la bibliothque C++ standard. Les fichiers den-tte standard
nont pas de suffixe .h. Les crochets (< >) autour des noms de fichier indiquent que les
fichiers den-tte se situent un emplacement standard sur le systme, alors que les guillemets
doubles indiquent au compilateur quil doit examiner le rpertoire courant. Les inclusions sont
normalement insres au dbut du fichier .cpp.
Contrairement aux fichiers .cpp, les fichiers den-tte ne sont pas des units de compilation
proprement parler et ne produisent pas le moindre fichier objet. Ils ne peuvent contenir que des
dclarations qui permettent diverses units de compilation de communiquer entre elles. Il ne
serait donc pas appropri dintroduire limplmentation de la fonction square() dans un tel
fichier. Si nous lavions fait dans cet exemple, cela naurait dclench aucune erreur, parce que
nous incluons square.h une seule fois, mais si nous avions inclus square.h dans plusieurs
fichiers .cpp, nous aurions obtenu de multiples implmentations de square() (une par fichier
.cpp dans lequel elle est incluse). Lditeur signalerait alors la prsence de dfinitions multiples (identiques) de square() et refuserait de gnrer lexcutable. Inversement, si nous
dclarons une fonction mais que nous oublions de limplmenter, lditeur de liens signale un
"symbole non rsolu".
Jusqu prsent, nous avons suppos quun excutable tait uniquement constitu de fichiers
objet. En pratique, ils comportent aussi souvent des liaisons vers des bibliothques qui implmentent une fonctionnalit prte lemploi. Il existe deux principaux types de bibliothque :

Les bibliothques statiques qui sont directement insres dans lexcutable, comme sil
sagissait de fichiers objet. Cela annule les risques de perte de la bibliothque mais cela
augmente la taille de lexcutable.

Les bibliothques dynamiques (galement nommes bibliothques partages ou DLL) qui


sont stockes sur un emplacement standard sur lordinateur de lutilisateur et qui sont automatiquement charges au dmarrage de lapplication.

Pour le programme square, nous tablissons une liaison avec la bibliothque C++ standard,
qui est implmente en tant que bibliothque dynamique sur la plupart des plates-formes. Qt
lui-mme est une collection de bibliothques qui peuvent tre gnres en tant que bibliothques
statiques ou dynamiques (dynamique est loption par dfaut).

Qt 4 Livre Page 489 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

489

Principales diffrences de langage


Nous allons maintenant examiner de prs les domaines dans lesquels C++ diffre de Java et
C#. De nombreuses diffrences de langage sont dues la nature compile du C++ et ses obligations en termes de performances. Ainsi, le C++ ne contrle pas les limites de tableau au
moment de lexcution, et aucun programme de rcupration de la mmoire ne raffecte la
mmoire alloue dynamiquement aprs usage.
Pour rester concis, nous naborderons pas dans cette annexe les constructions C++ qui sont
pratiquement identiques leurs quivalents Java et C#. Certains sujets C++ ne sont pas non
plus couverts ici parce quils ne sont pas indispensables pour la programmation avec Qt. Nous
ne traitons pas, en particulier, la dfinition des fonctions et classes modles, celle des types
union, et les exceptions. Vous trouverez une description dtaille de lensemble des fonctionnalits du langage dans un ouvrage tel que Le langage C++ de Bjarne Stroustrup ou C++ pour
les programmeurs Java de Mark Allen Weiss.

Les types de donnes primitifs


Les types de donnes primitifs du C++ sont analogues ceux de Java ou C#. La Figure B.2
rpertorie ces types avec leur dfinition sur les plates-formes prises en charge par Qt 4.
Type C++

Description

bool

Valeur boolenne

char

Entier 8 bits

short

Entier 16 bits

int

Entier 32 bits

long

Entier 32 bits ou 64 bits

long long_

Entier 64 bits

float

Valeur virgule flottante 32 bits (IEEE 754)

double

Valeur virgule flottante 64 bits (IEEE 754)

Figure B.2
Les types primitifs de C++

Par dfaut, les types short, int, long, et long long sont signs, ce qui signifie quils
peuvent stocker aussi bien des valeurs ngatives que des valeurs positives. Si nous navons
besoin de stocker que des nombres positifs, nous pouvons coder le mot-cl unsigned devant le
type. Alors quun short est capable de stocker nimporte quelle valeur entre 32 768 et

Qt 4 Livre Page 490 Jeudi, 7. dcembre 2006 12:14 12

490

Qt4 et C++ : Programmation dinterfaces GUI

+32 767, un unsigned short stocke une valeur entre 0 et 65 535. Loprateur de dcalage
droite >> ("remplit avec des 0") a une smantique non signe si un des oprandes est non
sign.Le type bool peut avoir les valeurs true et false. De plus, les types numriques
peuvent tre utiliss partout o un bool est attendu, 0 tant interprt comme false et toute
valeur non nulle par true.
Le type char sert la fois pour le stockage des caractres ASCII et pour celui des entiers 8 bits
(octets). Lorsquil est utilis pour un entier, celui-ci peut tre sign ou non sign, selon la plateforme. Les types signed char et unsigned char sont des alternatives non ambiges au
char. Qt fournit un type QChar qui stocke des caractres Unicode 16 bits.
Les instances des types intgrs ne sont pas initialises par dfaut. Lorsque nous crons une
variable int, sa valeur pourrait tout aussi bien tre de 0 que de 209 486 515. Heureusement,
les compilateurs nous avertissent pour la plupart lorsque nous tentons de lire le contenu dune
variable non initialise. Nous pouvons faire appel des outils tels que Rational PurifyPlus et
Valgrind pour dtecter les accs de la mmoire non initialise et les autres problmes lis la
mmoire lors de lexcution.
En mmoire, les types numriques ( lexception des long) ont des tailles identiques sur les
diffrentes plates-formes prises en charge par Qt, mais leur reprsentation varie selon larchitecture du systme. Sur les architectures big-endian (telles que PowerPC et SPARC), la valeur
32 bits 0x12345678 est stocke sous la forme des quatre octets 0x12 0x34 0x56 0x78, alors
que sur les architectures little-endian (telles que Intel x86), la squence doctets est inverse.
La diffrence apparat dans les programmes qui copient des zones de mmoire sur disque ou
qui envoient des donnes binaires sur le rseau. La classe QDataStream de Qt, prsente au
Chapitre 12 (Entres/Sorties), peut tre utilise pour stocker des donnes binaires en restant
indpendant de la plate-forme.
Chez Microsoft, le type long long sappelle __int64. Dans les programmes Qt, qlonglong
est propos comme une alternative compatible avec toutes les plates-formes de Qt.

Dfinitions de classe
Les dfinitions de classe en C++ sont analogues celles de Java et C#, mais vous devez
connatre plusieurs diffrences. Nous allons les tudier avec une srie dexemples.
Commenons par une classe qui reprsente une paire de coordonnes (x, y) :
#ifndef POINT2D_H
#define POINT2D_H
class Point2D
{
Public
Point2D() {
xVal = 0;
yVal = 0;
}

Qt 4 Livre Page 491 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

491

Point2D(double x, double y) {
xVal = x;
yVal = y;
}
void setX(double
void setY(double
double x() const
double y() const

x) { xVal = x;
y) { yVal = y;
{ return xVal;
{ return yVal;

}
}
}
}

Private
double xVal;
double yVal;
};
#endif

La dfinition de classe prcdente devrait apparatre dans un fichier den-tte, typiquement


nomm point2d.h. Cet exemple illustre les caractristiques C++ suivantes :
Une dfinition de classe est divise en sections publique, protge et prive, et se termine
par un point-virgule. Si aucune section nest spcifie, la section par dfaut est prive.
(Pour des raisons de compatibilit avec le C, C++ fournit le mot-cl struct identique
class sauf que la section par dfaut est publique si aucune section nest spcifie.)
La classe comporte deux constructeurs (le premier sans paramtre et le second avec deux
paramtres). Si nous navions pas dclar de constructeur, C++ en aurait automatiquement
fourni un sans paramtre avec un corps vide.
Les fonctions daccs x() et y() sont dclares comme const. Cela signifie quelles ne
peuvent ni modifier les variables membres ni appeler des fonctions membres non-const
(telles que setX() et setY()).
Les fonctions ci-dessus ont t implmentes inline, dans la dfinition de classe. Vous avez
aussi la possibilit de ne fournir que les prototypes de fonction dans le fichier den-tte et
dimplmenter les fonctions dans un fichier .cpp. Dans ce cas, le fichier den-tte aurait la
syntaxe suivante :
#ifndef POINT2D_H
#define POINT2D_H
class Point2D
{
Public
Point2D();
Point2D(double x, double y);
void setX(double x);
void setY(double y);
double x() const;
double y() const;

Qt 4 Livre Page 492 Jeudi, 7. dcembre 2006 12:14 12

492

Qt4 et C++ : Programmation dinterfaces GUI

Private
double xVal;
double yVal;
};
#endif

Les fonctions seraient alors implmentes dans point2d.cpp:


#include "point2d.h"
Point2D::Point2D()
{
xVal = 0.0;
yVal = 0.0;
}
Point2D::Point2D(double x, double y)
{
xVal = x;
yVal = y;
}
void Point2D::setX(double x)
{
xVal = x;
}
void Point2D::setY(double y)
{
yVal = y;
}
double Point2D::x() const
{
return xVal;
}
double Point2D::y() const
{
return yVal;
}

Nous commenons par inclure point2d.h parce que le compilateur a besoin de la dfinition
de classe pour tre en mesure danalyser les implmentations de fonction membre. Nous
implmentons ensuite les fonctions, en prfixant leurs noms de celui de la classe suivi de
loprateur ::.
Nous avons dj expliqu comment implmenter une fonction inline et maintenant comment
limplmenter dans un fichier .cpp. Les deux mthodes sont quivalentes dun point de vue
smantique, mais lorsque nous appelons une fonction qui est dclare inline, la plupart des

Qt 4 Livre Page 493 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

493

compilateurs se contentent dinsrer le corps de la fonction plutt que de gnrer un vritable


appel de fonction. Vous obtenez gnralement un code plus performant, mais la taille de
lapplication est suprieure. Cest pourquoi seules les fonctions trs courtes devraient tre
implmentes de cette faon ; les autres devront plutt tre implmentes dans un fichier .cpp.
De plus, si nous oublions dimplmenter une fonction et que nous tentons de lappeler,
lditeur de liens va signaler un symbole non rsolu.
Essayons maintenant dutiliser la classe.
#include "point2d.h"
int main()
{
Point2D alpha;
Point2D beta(0.666, 0.875);
alpha.setX(beta.y());
beta.setY(alpha.x());
return 0;
}

En C++, les variables de tout type peuvent tre dclares directement sans coder new. La
premire variable est initialise laide du constructeur par dfaut Point2D (celui qui ne reoit
aucun paramtre). La seconde variable est initialise laide du second constructeur. Laccs
au membre de lobjet seffectue via loprateur "." (point).
Les variables dclares de cette faon se comportent comme les types primitifs de Java/C# tels
que int et double. Lorsque nous codons loprateur daffectation, par exemple, cest le
contenu de la variable qui est copi et non une simple rfrence lobjet. Si nous modifions
une variable par la suite, cette modification ne sera pas rpercute dans les variables qui on a
pu affecter la valeur de la premire.
En tant que langage orient objet, C++ prend en charge lhritage et le polymorphisme. Nous
allons tudier ces deux proprits avec lexemple de classe de base abstraite Shape et une
sous-classe nomme Circle. Commenons par la classe de base :
#ifndef SHAPE_H
#define SHAPE_H
#include "point2d.h"
class Shape
{
Public
Shape(Point2D center) { myCenter = center; }
virtual void draw() = 0;

Qt 4 Livre Page 494 Jeudi, 7. dcembre 2006 12:14 12

494

Qt4 et C++ : Programmation dinterfaces GUI

Protected
Point2D myCenter;
};
#endif

La dfinition se trouve dans un fichier den-tte nomm shape.h. Cette dfinition faisant rfrence la classe Point2D, nous incluons point2d.h.
La classe Shape ne possde pas de classe de base. Contrairement Java et C#, C++ ne fournit
pas de classe Object gnrique partir de laquelle toutes les classes hritent. Qt fournit
QObject comme classe de base naturelle pour toutes sortes dobjets.
La dclaration de la fonction draw() prsente deux caractristiques intressantes : elle comporte
le mot-cl virtual, et elle se termine par = 0. Ce mot-cl signale que la fonction pourrait tre
rimplmente dans les sous-classes. Comme en C#, les fonctions membres C++ ne sont pas rimplmentables par dfaut. La syntaxe bizarre =0 indique que la fonction est une fonction
virtuelle pure, cest--dire une fonction qui ne possde aucune implmentation par dfaut et qui
doit obligatoirement tre implmente dans les sous-classes. Le concept "dinterface" en Java
et C# correspond une classe constitue uniquement de fonctions virtuelles pures en C++.
Voici la dfinition de la sous-classe Circle :
#ifndef CIRCLE_H
#define CIRCLE_H
#include "shape.h"
class Circle: public Shape
{
Public
Circle(Point2D center, double radius = 0.5)
: Shape(center) {
myRadius = radius;
}
void draw() {
//excuter ici une action
}
Private
double myRadius;
};
#endif

La classe Circle hrite publiquement de Shape, ce qui signifie que tous les membres publics
de cette dernire restent publics dans Circle. C++ prend aussi en charge lhritage priv et
protg, qui limite laccs aux membres publics et protgs de la classe de base.
Le constructeur reoit deux paramtres : le second est facultatif et il prend la valeur 0.5
lorsquil nest pas spcifi. Le constructeur transmet le paramtre correspondant au centre au

Qt 4 Livre Page 495 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

495

constructeur de la classe de base en appliquant une syntaxe particulire entre la signature et le


corps de la fonction. Dans le corps, nous initialisons la variable membre myRadius. Nous
aurions pu aussi grouper linitialisation de la variable avec celle du constructeur de la classe de
base sur la mme ligne :
Circle(Point2D center, double radius = 0.5)
: Shape(center), myRadius(radius) { }

Dautre part, C++ nautorise pas linitialisation dune variable membre dans la dfinition de
classe, le code suivant est donc incorrect :
// ne compilera pas
Private
double myRadius = 0.5;
};

La fonction draw() possde la mme signature que la fonction virtuelle draw() dclare dans
Shape. Il sagit dune rimplmentation et elle sera invoque de faon polymorphique au
moment o draw() sera appele pour une instance de Circle par le biais dun pointeur ou
dune rfrence de Shape. C++ ne fournit pas de mot-cl pour une redfinition de fonction
comme en C#. Ce langage ne comporte pas non plus de mot-cl super ou base qui fasse rfrence la classe de base. Si nous avons besoin dappeler limplmentation de base dune
fonction, nous pouvons prfixer son nom avec celui de la classe de base suivi de loprateur ::.
Par exemple :
class LabeledCircle: public Circle
{
Public
void draw() {
Circle::draw();
drawLabel();
}
...
};

C++ prend en charge lhritage multiple, ce qui signifie quune classe peut tre drive de
plusieurs classes la fois. Voici la syntaxe :
class DerivedClass: public BaseClass1, public BaseClass2, ...,
public BaseClassN
{
...
};

Par dfaut, fonctions et variables dclares dans une classe sont associes avec les instances de
cette classe. Nous avons aussi la possibilit de dclarer des fonctions membres et des variables
membres statiques, que vous utilisez ensuite sans instance.

Qt 4 Livre Page 496 Jeudi, 7. dcembre 2006 12:14 12

496

Qt4 et C++ : Programmation dinterfaces GUI

Par exemple :
#ifndef TRUCK_H
#define TRUCK_H
class Truck
{
Public
Truck() { ++counter; }
~Truck() { --counter; }
static int instanceCount() { return counter; }
Private
static int counter;
};
#endif

La variable membre statique counter assure le suivi du nombre dinstances de Truck un instant
donn. Cest le constructeur de Truck qui lincrmente. Le destructeur, que vous reconnaissez
au prfixe ~, dcrmente cette valeur. En C++, le destructeur est automatiquement invoqu
lorsquune variable alloue de faon statique sort de la porte ou lorsquune variable alloue
avec new est supprime. Ce comportement est analogue celui de la mthode finalize() en
Java, sauf que nous pouvons lobtenir en y faisant appel un instant spcifique.
Une variable membre statique existe uniquement dans sa classe : il sagit de "variables de
classe" plutt que de "variables dinstance". Chaque variable membre statique doit tre dfinie
dans un fichier .cpp (mais sans rpter le mot-cl static). Par exemple :
#include "truck.h"
int Truck::counter = 0;

Si vous ne suivez pas cette rgle, vous obtiendrez une erreur de "symbole non rsolu" au
moment de ldition des liens. La fonction statique instanceCount() est accessible depuis
lextrieur de la classe, en la prfixant avec le nom de cette dernire. Par exemple :
#include <iostream>
#include "truck.h"
using namespace std;
int main()
{
Truck truck1;
Truck truck2;
cout << Truck::instanceCount() << " equals 2" << endl;
return 0;
}

Qt 4 Livre Page 497 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

497

Les pointeurs
Un pointeur en C++ est une variable qui stocke ladresse mmoire dun objet (plutt que
lobjet lui-mme). Java et C# ont un concept analogue, la "rfrence," mais avec une syntaxe
diffrente. Nous allons commencer par tudier un exemple (peu raliste) pour observer les
pointeurs en action :
1 #include "point2d.h"
2 int main()
3 {
4
Point2D alpha;
5
Point2D beta;
6

Point2D *ptr;

7
8
9

ptr = &alpha;
ptr->setX(1.0);
ptr->setY(2.5);

10
11
12

ptr = &beta;
ptr->setX(4.0);
ptr->setY(4.5);

13

ptr = 0;

14

return 0;

15 }

Cet exemple sappuie sur la classe Point2D de la sous-section prcdente. Les lignes 4 et 5
dfinissent deux objets de type Point2D. Ces objets sont initialiss (0, 0) par le constructeur
de Point2D par dfaut.
La ligne 6 dfinit un pointeur vers un objet Point2D. La syntaxe des pointeurs place un astrisque devant le nom de la variable. Ce pointeur nayant pas t initialis, il contient une adresse
mmoire alatoire. Ce problme est rgl ligne 7 par laffectation de ladresse dalpha ce
pointeur. Loprateur unaire & renvoie ladresse mmoire dun objet. Une adresse est typiquement une valeur entire sur 32 ou 64 bits qui spcifie le dcalage dun objet en mmoire.
En lignes 8 et 9, nous accdons lobjet alpha via le pointeur ptr. ptr tant un pointeur et
non un objet, nous devons coder loprateur -> (flche) plutt que loprateur . (point).
Ligne 10, nous affectons ladresse de beta au pointeur. A partir de l, toute opration sur le
pointeur va affecter lobjet beta.
Ligne 13, le pointeur est dfini en pointeur nul. C++ ne fournit pas de mot-cl pour reprsenter
un pointeur qui ne pointe pas vers un objet; cest pourquoi nous affectons la valeur 0 (ou la
constante symbolique NULL, qui reprsente 0). Lemploi dun pointeur nul entrane aussitt
une panne accompagne dun message derreur du type "Erreur de segmentation", "Erreur de

Qt 4 Livre Page 498 Jeudi, 7. dcembre 2006 12:14 12

498

Qt4 et C++ : Programmation dinterfaces GUI

protection gnrale", ou "Erreur de bus". Avec laide dun dbogueur, nous pouvons retrouver
la ligne de code lorigine de lerreur.
A la fin de la fonction, lobjet alpha contient la paire de coordonnes (1.0, 2.5), alors que
beta contient (4.0, 4.5).
Les pointeurs sont souvent employs pour stocker des objets allous dynamiquement laide
de new. En jargon C++, nous disons que ces objets sont allous sur le "tas", alors que les variables
locales (les variables dfinies dans une fonction) sont stockes sur la "pile".
Voici un extrait de code qui illustre lallocation de mmoire dynamique laide de new:
#include "point2d.h"
int main()
{
Point2D *point = new Point2D;
point->setX(1.0);
point->setY(2.5);
delete point;
return 0;
}

Loprateur new renvoie ladresse mmoire de lobjet qui vient dtre allou. Nous stockons
ladresse dans une variable pointeur et laccs lobjet seffectue via le pointeur. Quand nous
en avons termin avec lobjet, nous librons la mmoire associe laide de loprateur
delete. Contrairement Java et C#, C++ ne possde pas de programme de libration de la
mmoire (garbage collector) ; les objets allous dynamiquement doivent tre explicitement
librs laide de delete quand nous nen navons plus besoin. Le Chapitre 2 dcrit le
mcanisme parentenfant de Qt, qui simplifie largement la gestion de mmoire dans les
programmes C++.
Si nous oublions dappeler delete, la mmoire reste occupe jusqu ce que le programme se
termine. Cela ne poserait aucun problme dans lexemple prcdent, parce que nous nallouons
quun seul objet, mais dans le cas dun programme qui allouerait constamment de nouveaux
objets, les allocations de mmoire associes se produiraient jusqu saturation de la mmoire
de lordinateur. Une fois quun objet est supprim, la variable pointeur contient toujours
ladresse de cet objet. Ce pointeur devient un "pointeur dans le vide" et ne doit plus tre utilis.
Qt fournit un pointeur "intelligent," QPointer<T>, qui se dfinit automatiquement 0 si le
QObject sur lequel il pointe est supprim.
Dans lexemple ci-dessus, nous avons invoqu le constructeur par dfaut et nous avons appel
setX() et setY() pour initialiser lobjet. Nous aurions pu faire plutt appel au constructeur
deux paramtres :q
Point2D *point = new Point2D(1.0, 2.5);

Qt 4 Livre Page 499 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

499

Lexemple nexigeait pas lemploi de new et de delete. Nous aurions trs bien pu allouer
lobjet sur la pile de la faon suivante :
Point2D point;
point.setX(1.0);
point.setY(2.5);

La mmoire occupe par des objets allous de cette faon est automatiquement rcupre la
fin du bloc dans lequel ils apparaissent.
Si nous navons pas lintention de modifier lobjet par lintermdiaire du pointeur, nous
pouvons dclarer le pointeur const. Par exemple :
const Point2D *ptr = new Point2D(1.0, 2.5);
double x = ptr->x();
double y = ptr->y();
// la compilation va chouer
ptr->setX(4.0);
*ptr = Point2D(4.0, 4.5);

Le pointeur ptr const sert uniquement appeler des fonctions membres const telles que
x() et y(). Prenez lhabitude de dclarer vos pointeurs comme const quand vous ne
prvoyez pas de modifier lobjet par leur intermdiaire. De plus, si lobjet lui-mme est const,
nous navons de toute faon pas dautre choix que dutiliser un pointeur const pour stocker
son adresse. Le mot-cl const apporte des informations au compilateur qui est ainsi en mesure
de dtecter trs tt des erreurs et damliorer les performances. C# comporte un mot-cl const
trs similaire celui de C++. Lquivalent Java le plus proche est final, mais il protge les
variables uniquement contre les affectations, pas contre les appels de fonctions membres "nonconst" sur ces dernires.
Les pointeurs fonctionnent avec les types intgrs ainsi quavec les classes. Dans une expression,
loprateur unaire * renvoie la valeur de lobjet associ au pointeur. Par exemple :
int i = 10;
int j = 20;
int *p = &i;
int *q = &j;
cout << *p << " equals 10" << endl;
cout << *q << " equals 20" << endl;
*p = 40;
cout << i << " equals 40" << endl;
p = q;
*p = 100;
cout << i << " equals 40" << endl;
cout << j << " equals 100" << endl;

Qt 4 Livre Page 500 Jeudi, 7. dcembre 2006 12:14 12

500

Qt4 et C++ : Programmation dinterfaces GUI

Loprateur ->, partir duquel vous avez accs aux membres dun objet via un pointeur, a une
syntaxe trs particulire. Plutt que de coder ptr->membre, nous pouvons aussi crire
(*ptr).membre. Les parenthses sont ncessaires parce que loprateur "." (point) est prioritaire sur loprateur unaire *.
Les pointeurs avaient mauvaise rputation en C et C++, tel point que la promotion de Java
insiste souvent sur labsence de pointeur dans ce langage. En ralit, les pointeurs C++ sont
analogues dun point de vue conceptuel aux rfrences de Java et C# sauf que nous pouvons
nous servir de pointeurs pour parcourir la mmoire, comme vous le dcouvrirez un peu plus
loin dans cette section. De plus, linclusion des classes conteneur en "copie lcriture" dans
Qt, ainsi que la possibilit de C++ dinstancier une classe sur la pile, signifie que nous pouvons
souvent viter davoir recours aux pointeurs.

Les rfrences
En complment des pointeurs, C++ prend aussi en charge le concept de "rfrence".Comme un
pointeur, une rfrence C++ stocke ladresse dun objet. Voici les principales diffrences :

Les rfrences sont dclares laide de & plutt que *.

Les rfrences doivent tre initialises et ne peuvent tre raffectes par la suite.

Lobjet associ une rfrence est directement accessible ; il ny a pas de syntaxe particulire telle que * ou ->.

Une rfrence ne peut tre nulle.

Les rfrences sont surtout utilises pour dclarer des paramtres. Par dfaut, C++ applique le
mcanisme de transmission par valeur lors de la transmission des paramtres, ce qui signifie
que lorsquun argument est transmis une fonction, celle-ci reoit une copie entirement
nouvelle de lobjet. Voici la dfinition dune fonction qui reoit ses paramtres par le biais dun
appel par valeur :
#include <cstdlib>
using namespace std;
double manhattanDistance(Point2D a, Point2D b)
{
return abs(b.x() - a.x()) + abs(b.y() - a.y());
}

Nous pourrions alors invoquer la fonction comme suit :


Point2D broadway(12.5, 40.0);
Point2D harlem(77.5, 50.0);
double distance = manhattanDistance(broadway, harlem);

Qt 4 Livre Page 501 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

501

Les programmeurs C convertis vitent les oprations de copie inutiles en dclarant leurs paramtres sous forme de pointeurs plutt que sous forme de valeurs :
double manhattanDistance(const Point2D *ap, const Point2D *bp)
{
return abs(bp->x() - ap->x()) + abs(bp->y() - ap->y());
}

Ils doivent ensuite transmettre les adresses plutt que les valeurs lors de lappel de la fonction :
Point2D broadway(12.5, 40.0);
Point2D harlem(77.5, 50.0);
double distance = manhattanDistance(&broadway, &harlem);

C++ a introduit les rfrences afin de simplifier la syntaxe et dviter que lappelant ne transmette un pointeur nul. Si nous utilisons des rfrences plutt que des pointeurs, voici ce que
devient la fonction :
double manhattanDistance(const Point2D &a, const Point2D &b)
{
return abs(b.x() - a.x()) + abs(b.y() - a.y());
}

La dclaration dune rfrence est analogue celle dun pointeur, & remplaant *. Mais lorsque nous utilisons effectivement la rfrence, nous pouvons oublier quil sagit dune adresse
mmoire et la traiter comme une variable ordinaire. De plus, lappel dune fonction qui reoit
des rfrences en arguments nexige aucune attention particulire (pas doprateur &).
Dans lensemble, en remplaant Point2D par const Point2D & dans la liste de paramtres,
nous avons rduit la surcharge de traitement de lappel de fonction : plutt que de copier
256 bits (la taille de quatre double), nous copions seulement 64 ou 128 bits, selon la taille du
pointeur de la plate-forme cible.
Lexemple prcdent sappuyait sur les rfrences const, ce qui empchait la fonction de
modifier les objets associs aux rfrences. Lorsquon veut au contraire autoriser ce comportement,
nous pouvons transmettre une rfrence ou un pointeur non-const. Par exemple :
void transpose(Point2D &point)
{
double oldX = point.x();
point.setX(point.y());
point.setY(oldX);
}

Dans certains cas, nous disposons dune rfrence et nous avons besoin dappeler une fonction
qui reoit un pointeur, ou vice versa. Pour convertir une rfrence en pointeur, nous pouvons
tout simplement utiliser loprateur unaire &:
Point2D point;
Point2D &ref = point;
Point2D *ptr = &ref;

Qt 4 Livre Page 502 Jeudi, 7. dcembre 2006 12:14 12

502

Qt4 et C++ : Programmation dinterfaces GUI

Pour convertir un pointeur en rfrence, il y a loprateur unaire *:


Point2D point;
Point2D *ptr = &point;
Point2D &ref = *ptr;

Rfrences et pointeurs sont reprsents de la mme faon en mmoire, et ils peuvent souvent
tre utiliss de faon interchangeable, ce qui soulve la question de quel lment utiliser et
quand. Dun ct, la syntaxe des rfrences est plus pratique. Dun autre ct, tout moment
vous pouvez raffecter les pointeurs pour pointer sur un autre objet, ils peuvent stocker une
valeur nulle, et leur syntaxe plus explicite est plutt une bonne chose. Cest pour cette raison
que les pointeurs ont tendance lemporter, les rfrences tant plutt presque exclusivement
employe pour dclarer les paramtres de fonction, en conjonction avec const.

Les tableaux
Les tableaux en C++ sont dclars en spcifiant leur nombre dlments entre crochets dans la
dclaration de variable aprs le nom de variable. Il est possible de crer des tableaux deux
dimensions laide dun tableau de tableau. Voici la dfinition dun tableau une dimension
contenant 10 lments de type int:
int fibonacci[10];

On accde aux lments via la syntaxe fibonacci[0], fibonacci[1], , fibonacci[9].


Nous avons souvent besoin dinitialiser le tableau lors de sa dfinition :
int fibonacci[10] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };

Dans ce cas, nous pouvons alors omettre la taille de tableau puisque le compilateur peut la
dduire partir du nombre dinitialisations :
int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };

Linitialisation statique fonctionne aussi pour les types complexes, tels que Point2D:
Point2D triangle[] = {
Point2D(0.0, 0.0), Point2D(1.0, 0.0), Point2D(0.5, 0.866)
};

Si nous navons pas lintention de modifier le tableau par la suite, nous pouvons le rendre const:
const int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };

Pour trouver le nombre dlments dun tableau, il suffit dexcuter loprateur sizeof()
comme suit :
int n = sizeof(fibonacci) / sizeof(fibonacci[0]);

Qt 4 Livre Page 503 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

503

Cet oprateur renvoie la taille de ses arguments en octets. Le nombre dlments dans un
tableau correspond la taille de ce dernier en octets divise par la taille dun de ses lments.
Comme cest un peu fatiguant taper, une solution courante consiste dclarer une constante
et lutiliser pour dfinir le tableau :
enum { NFibonacci = 10 };
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };

Il aurait t tentant de dclarer la constante sous forme de variable const int.


Malheureusement, certains compilateurs acceptent mal les variables const pour spcifier les
tailles de tableau. Le mot-cl enum sera expliqu plus tard dans cette annexe. Le parcours dun
tableau est normalement ralis laide dun entier. Par exemple :
for (int i = 0; i < NFibonacci; ++i)
cout << fibonacci[i] << endl;

Il est aussi possible de parcourir le tableau laide dun pointeur :


const int *ptr = &fibonacci[0];
while (ptr!= &fibonacci[10]) {
cout << *ptr << endl;
++ptr;
}

Nous initialisons le pointeur avec ladresse du premier lment et nous bouclons jusqu ce
que nous atteignons llment "qui suit le dernier" (le "onzime" lment, fibonacci[10]).
A chaque itration, loprateur ++ positionne le pointeur sur llment suivant.
Au lieu de &fibonacci[0], nous aurions aussi pu crire fibonacci. En effet, le nom dun
tableau utilis seul est automatiquement converti en pointeur vers le premier lment du
tableau. De la mme faon, nous pourrions remplacer fibonacci + 10 par &fibonacci[10]. Cela marche aussi en sens inverse : nous pouvons rcuprer le contenu de
llment courant en codant soit *ptr soit ptr[0] et nous pourrions accder llment suivant
laide de *(ptr + 1) ou ptr[1].
On fait quelquefois rfrence ce principe en termes "dquivalence entre pointeurs et tableaux".
Pour empcher ce quil considre comme une perte defficacit gratuite, C++ nous interdit de
transmettre des tableaux des fonctions par valeur. Il faut plutt transmettre leur adresse.
Par exemple :
#include <iostream>
using namespace std;
void printIntegerTable(const int *table, int size)
{
for (int i = 0; i < size; ++i)
cout << table[i] << endl;
}

Qt 4 Livre Page 504 Jeudi, 7. dcembre 2006 12:14 12

504

Qt4 et C++ : Programmation dinterfaces GUI

int main()
{
const int fibonacci[10] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
printIntegerTable(fibonacci, 10);
return 0;
}

Ironiquement, alors que le C++ ne nous donne aucun choix concernant le mode de transmission dun tableau, par adresse ou par valeur, il nous accorde quelques liberts dans la syntaxe
employe pour dclarer le type de paramtre. Pour remplacer const int *table, nous
aurions pu coder const int table[] pour dclarer un paramtre pointeur-vers-constanteint. Dune faon analogue, le paramtre argv de main() peut tre dclar soit en tant que
char *argv[] soit en tant que char **argv.
Pour copier un tableau dans un autre tableau, une solution consiste boucler dans le tableau :
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
int temp[NFibonacci];
for (int i = 0; i < NFibonacci; ++i)
temp[i] = fibonacci[i];

Pour les types de base tel que int, nous pouvons aussi utiliser std::memcpy(), qui copie un
bloc de mmoire. Par exemple :
memcpy(temp, fibonacci, sizeof(fibonacci));

Lorsque nous dclarons un tableau C++, la taille doit tre une constante. Si nous dsirons crer
un tableau de taille variable, nous avons plusieurs solutions.
Nous pouvons allouer dynamiquement ce tableau :
int *fibonacci = new int[n];

Certains compilateurs autorisent les variables dans ce contexte, mais il ne faut pas exploiter ce
type de fonctionnalit pour crer un programme portable.
Loprateur new[] alloue un certain nombre dlments dans des emplacements mmoire
conscutifs et renvoie un pointeur vers le premier lment. Grce au principe de "lquivalence
des pointeurs et des tableaux," on peut accder aux lments par lintermdiaire dun pointeur
avec fibonacci[0], fibonacci[1], , fibonacci[n -1]. Ds que le tableau nest plus
ncessaire, nous devons librer la mmoire quil occupe en excutant loprateur delete[]:
delete [] fibonacci;

Nous pouvons appliquer la syntaxe standard std::vector<T> class:


#include <vector>
using namespace std;
vector<int> fibonacci(n);

Qt 4 Livre Page 505 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

505

Vous accdez aux lments laide de loprateur [], exactement comme dans un tableau
C++ ordinaire. Avec std::vector<T> (o T est le type des lments stocks dans le
vecteur), nous pouvons redimensionner le tableau tout moment en excutant resize() et
nous le copions avec loprateur daffectation. Les classes qui contiennent des crochets
(<>) dans leur nom sont appeles classes modle.
Nous pouvons utiliser la syntaxe QVector<T> class de Qt :
#include <QVector>
QVector<int> fibonacci(n);

LAPI de QVector<T> est trs proche de celle de std::vector<T>, mais elle prend aussi en
charge litration via le mot-cl foreach de Qt et elle utilise le partage de donnes ("copie
lcriture") afin doptimiser la mmoire et les temps de rponse. Le Chapitre 11 prsente les
classes conteneur de Qt et expose leurs relations avec les conteneurs standard du C++.
Vous pourriez tre tent dviter au maximum les tableaux intgrs et de coder plutt std::
vector<T> ou QVector<T>. Il est pourtant essentiel de bien comprendre le fonctionnement
des tableaux intgrs parce que tt ou tard vous en aurez besoin dans du code fortement optimis ou pour servir dinterface avec les bibliothques C existantes.

Les chanes de caractres


La faon la plus simple de reprsenter des chanes de caractres en C++ consiste utiliser un
tableau de caractres termin par un octet nul (0). Les quatre fonctions suivantes illustrent le
comportement de ce type de chanes :
void hello1()
{
const char str[] = {
H, e, l, l, o, , w, o, r, l, d, \0
};
cout << str << endl;
}
void hello2()
{
const char str[] = "Hello world!";
cout << str << endl;
}
void hello3()
{
cout << "Hello world!" << endl;
}

Qt 4 Livre Page 506 Jeudi, 7. dcembre 2006 12:14 12

506

Qt4 et C++ : Programmation dinterfaces GUI

void hello4()
{
const char *str = "Hello world!";
cout << str << endl;
}

Dans la premire fonction, nous dclarons la chane en tant que tableau et nous linitialisons de
faon basique. Notez le caractre 0 (zro) de fin, qui signale la fin de la chane. La deuxime
fonction comporte une dfinition de tableau similaire mais nous faisons cette fois appel un
littral chane pour initialiser le tableau. En C++, les littraux chane sont de simples tableaux
de const char avec un caractre 0 de fin implicite. La troisime fonction emploie directement un littral chane, sans lui donner de nom. Une fois traduit en instructions de langage
machine, ce code est identique celui des deux fonctions prcdentes.
La quatrime fonction est un peu diffrente parce quelle cre non seulement un tableau
(anonyme) mais aussi une variable pointeur appele str qui stocke ladresse du premier
lment du tableau. Malgr cela, la smantique de la fonction est identique aux trois fonctions
prcdentes, et un compilateur performant devrait liminer la variable str superflue.
Les fonctions qui reoivent des chanes C++ en arguments reoivent gnralement un char*
ou un const char*. Voici un programme court qui illustre lemploi de chacune delles :
#include <cctype>
#include <iostream>
using namespace std;
void makeUppercase(char *str)
{
for (int i = 0; str[i]!= \0; ++i)
str[i] = toupper(str[i]);
}
void writeLine(const char *str)
{
cout << str << endl;
}
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; ++i) {
makeUppercase(argv[i]);
writeLine(argv[i]);
}
return 0;
}

En C++, le type char stocke normalement une valeur 8 bits. Cela signifie que nous pouvons
facilement stocker des chanes ASCII, ISO 8859-1 (Latin-1), et autres codages 8 bits dans un
tableau de caractres, mais que nous ne pouvons pas stocker des caractres Unicode arbitraires
sans faire appel des squences multioctet. Qt fournit la puissante classe QString, qui stocke

Qt 4 Livre Page 507 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

507

des chanes Unicode sous forme de squences de QChars 16 bits et qui exploite en interne
loptimisation du partage de donnes implicite ("copie lcriture"). Cette classe est dtaille
dans les Chapitres 11 (Classes conteneur) et 17 (Internationalisation).

Les numrations
C++ comprend une fonctionnalit dnumration pour la dclaration dun jeu de constantes
nommes analogue celle fournie par C#. Supposons que nous voulions stocker les jours de la
semaine dans un programme :
enum DayOfWeek {
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
};

Normalement, nous devrions placer cette dclaration dans un fichier den-tte ou mme dans
une classe. La dclaration prcdente est quivalente en apparence aux dfinitions de constantes
suivantes :
const
const
const
const
const
const
const

int
int
int
int
int
int
int

Sunday = 0;
Monday = 1;
Tuesday = 2;
Wednesday = 3;
Thursday = 4;
Friday = 5;
Saturday = 6;

En exploitant la construction de lnumration, nous pouvons dclarer par la suite des variables
ou paramtres de type DayOfWeek et le compilateur garantira que seules les valeurs de
lnumration DayOfWeek leur seront affectes. Par exemple :
DayOfWeek day = Sunday;

Si la scurit des types ne nous concerne pas, nous pouvons aussi coder
int day = Sunday;

Notez que pour faire rfrence la constante Sunday dans lnumration DayOfWeek, nous
crivons simplement Sunday, et non DayOfWeek::Sunday.
Par dfaut, le compilateur affecte des valeurs dentiers conscutifs aux constantes dune
numration, en commenant 0. Nous pouvons spcifier dautres valeurs si ncessaire :
enum DayOfWeek {
Sunday = 628,
Monday = 616,
Tuesday = 735,
Wednesday = 932,
Thursday = 852,
Friday = 607,
Saturday = 845
};

Qt 4 Livre Page 508 Jeudi, 7. dcembre 2006 12:14 12

508

Qt4 et C++ : Programmation dinterfaces GUI

Si nous ne spcifions pas la valeur dun lment de lnumration, celui-ci prend la valeur de
llment prcdent, plus 1. Les numrations sont quelquefois employes pour dclarer
des constantes entires, auquel cas nous omettons normalement le nom de lnumration :
Enum
FirstPort = 1024,
MaxPorts = 32767
};

Un autre emploi frquent des numrations concerne la reprsentation des ensembles


doptions. Prenons lexemple dune bote de dialogue Find, avec quatre cases cocher contrlant lalgorithme de recherche (Wildcard syntax, Case sensitive, Search backward, et
Wrap around). Nous pouvons reprsenter ces cases laide dune numration dans laquelle
les constantes sont des puissances de 2 :
enum FindOption {
NoOptions = 0x00000000,
WildcardSyntax = 0x00000001,
CaseSensitive = 0x00000002,
SearchBackward = 0x00000004,
WrapAround = 0x00000008
};

Chaque option est souvent nomme "indicateur". Nous pouvons combiner ces indicateurs
laide des oprateurs bit bit | ou |=:
int options = NoOptions;
if (wilcardSyntaxCheckBox->isChecked())
options |= WildcardSyntax;
if (caseSensitiveCheckBox->isChecked())
options |= CaseSensitive;
if (searchBackwardCheckBox->isChecked())
options |= SearchBackwardSyntax;
if (wrapAroundCheckBox->isChecked())
options |= WrapAround;
Nous pouvons tester si un indicateur existe laide de loprateur
if (options & CaseSensitive) {
// rechercher avec casse significative
}

&

bit bit:

Une variable de type FindOption ne peut contenir quun seul indicateur la fois. La combinaison de plusieurs indicateurs laide de | donne un entier ordinaire. Dans ce cas, les types
ne sont malheureusement pas scuriss : le compilateur ne va rien signaler si une fonction qui
doit recevoir une combinaison de FindOptions dans un paramtre int reoit plutt Saturday. Qt utilise QFlags<T> pour scuriser les types de ses propres types dindicateur. La classe
est galement disponible lorsque nous dfinissons des types dindicateur personnaliss.
Consultez la documentation en ligne de QFlags<T> pour connatre tous les dtails.

Qt 4 Livre Page 509 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

509

TypeDef
Avec C++, nous pouvons dfinir un alias pour un type de donnes laide du mot-cl
typedef. Si nous codons souvent par exemple QVector<Point2D> et que nous dsirons conomiser les oprations de frappe, nous pouvons placer la dclaration de typedef suivante dans
un de nos fichiers den-tte :
typedef QVector<Point2D> PointVector;

A partir de l, PointVector peut tre cod en lieu et place de QVector<Point2D>. Notez que
le nouveau nom du type apparat aprs lancien. La syntaxe de typedef suit dlibrment
celle des dclarations de variable.
Avec Qt, les typedef sont surtout employs pour les raisons suivantes :
Laspect pratique : Qt dclare uint et QWidgetList en tant que typedef pour unsigned
int et QList<QWidget *> afin de simplifier la frappe.
Les diffrences entre plates-formes : certains types ont des dfinitions diffrentes sur des
plates-formes diffrentes. Par exemple, qlonglong est dfini comme __int64 sous
Windows et comme long long sur dautres plates-formes.
La compatibilit : la classe QIconSet de Qt 3 a t renomme en QIcon dans Qt 4. Pour
aider les utilisateurs de Qt 3 porter leurs applications vers Qt 4, QIconSet est fourni
comme typedef pour QIcon lorsque la compatibilit Qt 3 est active.

Conversions de type
C++ fournit plusieurs syntaxes pour la conversion des valeurs dun type lautre. La syntaxe
traditionnelle, hrite de C, implique de placer le type obtenu entre parenthses avant la valeur
convertir :
const double Pi = 3.14159265359;
int x = (int)(Pi * 100);
cout << x << " equals 314" << endl;

Cette syntaxe est trs puissante. Elle permet de changer le type des pointeurs, de supprimer
const, et plus encore. Par exemple :
short j = 0x1234;
if (*(char *)&j == 0x12)
cout << "The byte order is big-endian" << endl;

Dans lexemple prcdent, nous convertissons un short* en char* et nous utilisons loprateur
unaire * pour accder loctet situ lemplacement de mmoire donn. Sur les systmes bigendian, cet octet se trouve en 0x12; sur les systmes little-endian, il se trouve lemplacement
0x34. Les pointeurs et rfrences tant reprsents de la mme faon, vous ne serez pas surpris
dapprendre que le code prcdent peut tre rcrit laide dune conversion de rfrence :

Qt 4 Livre Page 510 Jeudi, 7. dcembre 2006 12:14 12

510

Qt4 et C++ : Programmation dinterfaces GUI

short j = 0x1234;
if ((char &)j == 0x12)
cout << "The byte order is big-endian" << endl;

Si le type de donnes est un nom de classe, un typedef, ou un type primitif qui peut sexprimer
sous la forme dun unique jeton alphanumrique, nous pouvons nous servir de la syntaxe du
constructeur comme dune conversion :
int x = int(Pi * 100);

La conversion des pointeurs et rfrences laide des conversions traditionnelles de style C est
assez prilleuse parce que le compilateur nous autorise convertir nimporte quel type de pointeur (ou rfrence) vers nimporte quel autre type de pointeur (ou rfrence). Cest pourquoi
C++ a introduit quatre conversions de style nouveau avec une smantique plus prcise. Pour
les pointeurs et rfrences, les nouvelles conversions sont prfrables aux conversions de style
C plus risques et sont utilises dans cet ouvrage.
static_cast<T>() convertit un pointeur-vers-A en pointeur-vers-B, en imposant que la
classe B hrite de la classe A. Par exemple :
A *obj = new B;
B *b = static_cast<B *>(obj);
b->someFunctionDeclaredInB();

Si lobjet nest pas une instance de B (mais quil hrite de A), lusage du pointeur obtenu
peut produire des pannes obscures.
dynamic_cast<T>() est analogue static_cast<T>(), sauf que les informations
de type lexcution (RTTI) servent contrler si lobjet associ au pointeur est bien
une instance de la classe B. Si ce nest pas le cas, la conversion renvoie un pointeur nul.
Par exemple :
A *obj = new B;
B *b = dynamic_cast<B *>(obj);
if (b)
b-> someFunctionDeclaredInB();

Avec certains compilateurs, dynamic_cast<T>() ne fonctionne pas au-del des limites de


la bibliothque dynamique. Cet lment sappuie galement sur la prise en charge de RTTI
par le compilateur, une fonctionnalit que tout bon programmeur doit dsactiver pour
rduire la taille des excutables. Qt rsout ces problmes en fournissant
qobject_cast<T>() pour les sous-classes de QObject.
const_cast<T>() ajoute ou supprime un qualificateur const sur un pointeur ou une rfrence. Par exemple :
int MyClass::someConstFunction() const
{
if (isDirty()) {
MyClass *that = const_cast<MyClass *>(this);

Qt 4 Livre Page 511 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

511

that->recomputeInternalData();
}
...
}

Dans lexemple prcdent, nous convertissons le qualificateur const du pointeur this afin
dappeler la fonction membre non-const recomputeInternalData(). Il nest pas recommand de procder de cette faon et vous pouvez lviter en codant mutable, comme expliqu
au Chapitre 4.
reinterpret_cast<T>() convertit tout type pointeur ou rfrence vers un autre type de
ce genre. Par exemple :
short j = 0x1234;
if (reinterpret_cast<char &>(j) == 0x12)
cout << "The byte order is big-endian" << endl;

En Java et C#, il est possible de stocker toute rfrence en tant que rfrence dObject. C++
ne comporte pas une telle classe de base universelle, mais il fournit un type de donnes particulier, void*, qui stocke ladresse dune instance de nimporte quel type. Un void* doit tre
reconvertit vers un autre type ( laide de static_cast<T>()) avant dtre exploit.
C++ offre de nombreuses mthodes de conversion entre types, mais nous avons rarement
besoin deffectuer une conversion dans ce langage. Lorsque nous utilisons des classes conteneurs telles que std::vector<T> ou QVector<T>, nous spcifions le type T et nous rcuprons les lments sans convertir. De plus, pour les types primitifs, certaines conversions
seffectuent implicitement (de char vers int par exemple), et pour les types personnaliss
nous pouvons dfinir des conversions implicites en fournissant un constructeur un paramtre.
Par exemple :
class MyInteger
{
Public
MyInteger();
MyInteger(int i);
...
};
int main()
{
MyInteger n;
n = 5;
...
}

Pour certains constructeurs un paramtre, la conversion automatique nest pas justifie. Il est
possible de la dsactiver en dclarant le constructeur avec le mot-cl explicit:
class MyVector
{

Qt 4 Livre Page 512 Jeudi, 7. dcembre 2006 12:14 12

512

Qt4 et C++ : Programmation dinterfaces GUI

Public
explicit MyVector(int size);
...
};

Surcharge doprateur
C++ nous permet de surcharger des fonctions, ce qui signifie que nous pouvons dclarer
plusieurs fonctions avec le mme nom dans la mme porte, partir du moment o leurs listes
de paramtres sont diffrentes. C++ prend aussi en charge la surcharge des oprateurs. Il sagit
de la possibilit daffecter une smantique particulire des oprateurs intgrs (tels que +, <<,
et []) lorsquils sont employs avec des types personnaliss.
Nous avons dj tudi quelques exemples doprateurs surchargs. Lorsque nous avions
utilis << pour transmettre du texte en sortie vers cout ou cerr, nous navions pas excut
loprateur de dcalage de C++, mais plutt une version particulire de cet oprateur qui recevait un objet ostream (tel que cout et cerr) gauche et une chane (ou bien un nombre ou un
manipulateur de flux tel que endl) droite et qui renvoyait lobjet ostream, ce qui permettait
deffectuer plusieurs appels par ligne.
Lintrt de la surcharge des oprateurs est que nous pouvons obtenir un comportement pour
nos types personnaliss identique celui des types intgrs. Pour illustrer ce point, nous allons
surcharger +=, -=, +, et - pour travailler avec des objets Point2D:
#ifndef POINT2D_H
#define POINT2D_H
class Point2D
{
Public
Point2D();
Point2D(double x, double y);
void setX(double x);
void setY(double y);
double x() const;
double y() const;
Point2D &operator+=(const Point2D &other) {
xVal += other.xVal;
yVal += other.yVal;
return *this;
}
Point2D &operator-=(const Point2D &other) {
xVal -= other.xVal;
yVal -= other.yVal;
return *this;
}
Private

Qt 4 Livre Page 513 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

513

double xVal;
double yVal;
};
inline Point2D operator+(const Point2D &a, const Point2D &b)
{
return Point2D(a.x() + b.x(), a.y() + b.y());
}
inline Point2D operator-(const Point2D &a, const Point2D &b)
{
return Point2D(a.x() - b.x(), a.y() - b.y());
}
#endif

Les oprateurs peuvent tre implments soit en tant que fonctions membres soit en tant que
fonctions globales. Dans notre exemple, nous avons implment += et -= en tant que fonctions
membres, et + et - en tant que fonctions globales.
Les oprateurs += et -= reoivent la rfrence dun autre objet Point2D et incrmentent ou
dcrmentent les coordonnes x et y de lobjet courant en fonction de lautre objet. Ils
renvoient *this, qui reprsente une rfrence lobjet courant (de type Point2D*). Puisquon
renvoie une rfrence, nous pouvons crire du code un peu exotique comme ci-aprs :
a += b += c;

Les oprateurs + et - reoivent deux paramtres et renvoient un objet Point2D par valeur (et
non la rfrence dun objet existant). Grce au mot-cl inline, nous avons la possibilit de
placer ces dfinitions de fonction dans le fichier den-tte. Si le corps de la fonction avait t
plus long, nous aurions insr le prototype de fonction dans le fichier den-tte et la dfinition
de fonction (sans le mot-cl inline) dans le fichier .cpp.
Lextrait de code suivant prsente les quatre oprateurs surchargs en action :
Point2D alpha(12.5, 40.0);
Point2D beta(77.5, 50.0);
alpha += beta;
beta -= alpha;
Point2D gamma = alpha + beta;
Point2D delta = beta - alpha;

Nous invoquons les fonctions oprateur comme nimporte quelle autre fonction :
Point2D alpha(12.5, 40.0);
Point2D beta(77.5, 50.0);
alpha.operator+=(beta);
beta.operator-=(alpha);

Qt 4 Livre Page 514 Jeudi, 7. dcembre 2006 12:14 12

514

Qt4 et C++ : Programmation dinterfaces GUI

Point2D gamma = operator+(alpha, beta);


Point2D delta = operator-(beta, alpha);

La surcharge des oprateurs en C++ est un sujet complexe, mais vous navez pas forcment
besoin den connatre tous les dtails. Il est surtout important de comprendre les principes de
base de la surcharge des oprateurs parce que plusieurs classes Qt (notamment QString et
QVector<T>) sappuient sur cette fonctionnalit pour fournir une syntaxe plus simple et plus
naturelle pour des oprations telles que la concatnation et lajout dlment.

Les types valeur


Java et C# diffrencient les types valeur et les types rfrence.
Types valeur : il sagit des types primitifs tels que char, int, et float, ainsi que les structures C#. Ils sont caractriss par le fait quils ne sont pas crs laide de new et que
loprateur daffectation copie la valeur stocke dans la variable. Par exemple :
int i = 5;
int j = 10;
i = j;

Types rfrence : Il sagit de classes telles que Integer (en Java), String, et MaClasseAmoi. Les instances sont cres en codant new. Loprateur daffectation copie uniquement
une rfrence de lobjet ; pour obtenir une copie intgrale, il faut appeler clone() (en Java)
ou Clone() (en C#). Par exemple :
Integer i = new Integer(5);
Integer j = new Integer(10);
i = j.clone();

En C++, tous les types peuvent tre employs en tant que "types rfrence," et ceux qui
peuvent tre copis peuvent aussi tre employs en tant que "types valeur".C++na pas besoin
dun classe Integer, par exemple, parce que vous pouvez vous servir des pointeurs et de new
comme dans les lignes suivantes :
int *i = new int(5);
int *j = new int(10);
*i = *j;

Contrairement Java et C#, C++ traite les classes dfinies par lutilisateur exactement comme
des types intgrs :
Point2D *i = new Point2D(5, 5);
Point2D *j = new Point2D(10, 10);
*i = *j;

Si nous dsirons rendre une classe C++ copiable, nous devons prvoir un constructeur de copie
et un oprateur daffectation pour cette dernire. Le constructeur de copie est invoqu lorsque

Qt 4 Livre Page 515 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

515

nous initialisons lobjet avec un autre objet de mme type. C++ fournit deux syntaxes quivalentes pour cette situation :
Point2D i(20, 20);
Point2D j(i); // premire syntaxe
Point2D k = i; // deuxime syntaxe

Loprateur daffectation est invoqu lorsque nous utilisons cet oprateur sur une variable
existante :
Point2D i(5, 5);
Point2D j(10, 10);
j = i;

Lorsque nous dfinissons une classe, le compilateur C++ fournit automatiquement un


constructeur de copie et un oprateur daffectation qui effectue une copie membre membre.
Pour la classe Point2D, cest comme si nous avions crit le code suivant dans la dfinition de
classe :
class Point2D
{
Public
...
Point2D(const Point2D &other)
: xVal(other.xVal), yVal(other.yVal) { }
Point2D &operator=(const Point2D &other) {
xVal = other.xVal;
yVal = other.yVal;
return *this;
}
...
Private
double xVal;
double yVal;
};

Pour certaines classes, le constructeur de copie par dfaut et loprateur daffectation ne


conviennent pas. Cela se produit en particulier lorsque la classe travaille en mmoire dynamique.
Pour la rendre copiable, nous devons alors implmenter nous-mmes le constructeur de copie
et loprateur daffectation.
Pour les classes qui nont pas besoin dtre copiables, nous avons la possibilit de dsactiver
le constructeur de copie et loprateur daffectation en les rendant privs. Si nous tentons
accidentellement de copier des instances de cette classe, le compilateur signale une erreur.
Par exemple :
class BankAccount
{
Public

Qt 4 Livre Page 516 Jeudi, 7. dcembre 2006 12:14 12

516

Qt4 et C++ : Programmation dinterfaces GUI

...
Private
BankAccount(const BankAccount &other);
BankAccount &operator=(const BankAccount &other);
};

En Qt, beaucoup de classes sont conues pour tre utilises en tant que classes de valeur. Elles
disposent dun constructeur de copie et dun oprateur daffectation, et elles sont normalement
instancies sur la pile sans new. Cest le cas en particulier pour QDateTime, QImage, QString,
et les classes conteneur telles que QList<T>, QVector<T>, et QMap<K,T>.
Les autres classes appartiennent la catgorie "type rfrence", notamment QObject et ses
sous-classes (QWidget, QTimer, QTcpSocket, etc.). Celles-ci possdent des fonctions virtuelles et
ne peuvent pas tre copies. Un QWidget reprsente par exemple une fentre spcifique ou un
contrle sur lcran. Sil y a 75 instances de QWidget en mmoire, il y a aussi 75 fentres et
contrles sur cet cran. Ces classes sont typiquement instancies laide de loprateur new.

Variables globales et fonctions


C++ autorise la dclaration de fonctions et de variables qui nappartiennent aucune classe et
qui sont accessibles dans nimporte quelle autre fonction. Nous avons tudi plusieurs exemples de fonctions globales, dont main(), le point dentre du programme. Les variables globales sont plus rares, parce quelles compromettent la modularit et la rentrance du thread. Il est
important de bien les comprendre parce que vous pourriez les retrouver dans du code crit par
danciens programmeurs C ou dautres utilisateurs de C++.
Nous allons illustrer le fonctionnement des fonctions et variables globales en tudiant un petit
programme qui affiche une liste de 128 nombres pseudo-alatoires laide dun algorithme
rapide et simple. Le code source du programme est rparti dans deux fichiers .cpp.
Le premier est random.cpp:
int randomNumbers[128];
static int seed = 42;
static int nextRandomNumber()
{
seed = 1009 + (seed * 2011);
return seed;
}
void populateRandomArray()
{
for (int i = 0; i < 128; ++i)
randomNumbers[i] = nextRandomNumber();
}

Qt 4 Livre Page 517 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

517

Dans ce fichier, deux variables globales (randomNumbers et seed) et deux fonctions globales
(nextRandomNumber() et populateRandomArray()) sont dclares. Deux dclarations
contiennent le mot-cl static; elles ne sont visibles que dans lunit de compilation
courante (random.cpp) et on dit quelles ont une liaison statique. Les deux autres sont
disponibles dans nimporte quelle unit de compilation du programme ; elles ont une liaison
externe.
La liaison statique est particulirement indique pour les fonctions assistantes et les variables
internes qui ne doivent pas tre utilises dans dautres units de compilation. Elle rduit le
risque de collision didentificateurs (variables globales de mme nom ou fonctions globales
avec la mme signature dans des units de compilation diffrentes) et empche des utilisateurs
malveillants ou trs maladroits daccder au code dune unit de compilation.
Examinons maintenant le second fichier, main.cpp, qui utilise les deux variables globales
dclares avec une liaison externe dans random.cpp:
#include <iostream>
using namespace std;
extern int randomNumbers[128];
void populateRandomArray();
int main()
{
populateRandomArray();
for (int i = 0; i < 128; ++i)
cout << randomNumbers[i] << endl;
return 0;
}

Nous dclarons les variables et fonctions externes avant de les appeler. La dclaration de variable
externe (qui rend la variable visible dans lunit de compilation courante) pour randomNumbers dbute avec le mot-cl extern. Sans ce mot-cl, le compilateur pourrait penser quil
sagit dune dfinition de variable, et lditeur de liens rapporterait une erreur pour la mme
variable dfinie dans deux units de compilation diffrentes (random.cpp et main.cpp). Vous
pouvez dclarer des variables autant de fois que vous voulez, mais elles ne doivent tre dfinies
quune seule fois. Cest partir de la dfinition que le compilateur rserve lespace requis pour
la variable.
La fonction populateRandomArray() est dclare avec un prototype de fonction. Le mot-cl
extern est facultatif pour les fonctions.
Nous pourrions typiquement placer les dclarations de fonction et de variable externes dans un
fichier den-tte et inclure ce dernier dans tous les fichiers qui en ont besoin :
#ifndef RANDOM_H
#define RANDOM_H

Qt 4 Livre Page 518 Jeudi, 7. dcembre 2006 12:14 12

518

Qt4 et C++ : Programmation dinterfaces GUI

extern int randomNumbers[128];


void populateRandomArray();
#endif

Nous avions dj vu comment coder static pour dclarer des fonctions et variables membres
qui ne sont pas associes une instance spcifique de la classe, et nous venons maintenant de
voir comme utiliser ce mot-cl pour dclarer des fonctions et variables avec une liaison statique. Notez quil existe un autre usage de static. En C++, nous pouvons dclarer une variable
locale avec ce mot-cl. De telles variables sont initialises la premire fois que la fonction est
appele et leur valeur est garantie entre plusieurs appels de fonction. Par exemple :
void nextPrime()
{
static int n = 1;
do {
++n;
} while (!isPrime(n));
return n;
}

Les variables locales statiques sont similaires aux variables globales, mais elles ne sont visibles
qu lintrieur de la fonction dans laquelle elles sont dfinies.

Espaces de noms
Les espaces de noms sont un mcanisme destin rduire les risques de collision de noms dans
les programmes C++. Ces collisions sont frquentes dans les gros programmes qui exploitent
plusieurs bibliothques tiers. Dans vos propres programmes, vous avez le choix de les utiliser
ou non.
Nous encadrons typiquement dans un espace de noms toutes les dclarations dun fichier dentte afin de ne pas "polluer" lespace de noms global avec les identificateurs quil contient.
Par exemple :
#ifndef SOFTWAREINC_RANDOM_H
#define SOFTWAREINC_RANDOM_H
namespace SoftwareInc
{
extern int randomNumbers[128];
void populateRandomArray();
}
#endif

Qt 4 Livre Page 519 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

519

(Notez que nous avons aussi renomm la macro de prprocesseur utilise pour viter les inclusions multiples, rduisant ainsi le risque de collision de nom avec un fichier den-tte de mme
nom mais situ dans un rpertoire diffrent.)
La syntaxe dun espace de noms est similaire celle dune classe, mais la ligne ne se termine
pas par un point-virgule. Voici le nouveau fichier random.cpp:
#include "random.h"
int SoftwareInc::randomNumbers[128];
static int seed = 42;
static int nextRandomNumber()
{
seed = 1009 + (seed * 2011);
return seed;
}
void SoftwareInc::populateRandomArray()
{
for (int i = 0; i < 128; ++i)
randomNumbers[i] = nextRandomNumber();
}

Contrairement aux classes, les espaces de noms peuvent tre "rouverts" tout moment.
Par exemple :
namespace Alpha
{
void alpha1();
void alpha2();
}
namespace Beta
{
void beta1();
}
namespace Alpha
{
void alpha3();
}

Vous avez ainsi la possibilit de dfinir des centaines de classes, situes dans autant de fichiers
den-tte, dans un seul espace de noms. La bibliothque standard C++ englobe ainsi tous ses
identificateurs dans lespace de noms std. Dans Qt, les espaces de noms servent pour des identificateurs de type global tels que Qt::AlignBottom et Qt::yellow. Pour des raisons historiques,
les classes de Qt nappartiennent aucun espace de noms mais sont prfixes de la lettre "Q".

Qt 4 Livre Page 520 Jeudi, 7. dcembre 2006 12:14 12

520

Qt4 et C++ : Programmation dinterfaces GUI

Pour se rfrer un identificateur dclar dans un espace de noms depuis lextrieur de cet
espace, nous le prfixons avec le nom de lespace de noms (et ::). Nous pouvons aussi faire
appel lun des trois mcanismes suivants, qui visent rduire la frappe.

Dfinir un alias despace de noms :


namespace ElPuebloDeLaReinaDeLosAngeles
{
void beverlyHills();
void culverCity();
void malibu();
void santaMonica();
}
namespace LA = ElPuebloDeLaReinaDeLosAngeles;

Aprs la dfinition de lalias, celui-ci peut tre employ la place du nom original.

Importer un identificateur unique depuis un espace de noms :


int main()
{
using ElPuebloDeLaReinaDeLosAngeles::beverlyHills;
beverlyHills();
...
}

La dclaration using nous permet daccder un identificateur donn dans un espace de


noms sans avoir besoin de le prfixer avec le nom de lespace de noms.

Importer un espace de noms complet via une unique directive :


int main()
{
using namespace ElPuebloDeLaReinaDeLosAngeles;
santaMonica();
malibu();
...
}

Cette approche augmente les risques de collision de noms. Ds que le compilateur signale un
nom ambigu (deux classes de mme nom, par exemple, dfinies dans deux espaces de noms
diffrents), nous sommes toujours en mesure de qualifier lidentificateur avec le nom de
lespace de noms lorsque nous avons besoin dy faire rfrence.

Le prprocesseur
Le prprocesseur C++ est un programme qui convertit un fichier source .cpp contenant des
directives #- (telles que #include, #ifndef, et #endif) en fichier source ne contenant
aucune directive de cette sorte. Ces directives effectuent des oprations de texte simples sur le

Qt 4 Livre Page 521 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

521

fichier source, comme une compilation conditionnelle, une inclusion de fichier et une expansion de macro. Le prprocesseur est normalement invoqu automatiquement par le compilateur, mais dans la plupart des systmes vous avez encore la possibilit de linvoquer seul
(souvent par lintermdiaire dune option de compilateur -E ou /E).

La directive #include insre localement le contenu du fichier indiqu entre crochets (<>)
ou guillemets doubles (""), selon que le fichier den-tte est install sur un emplacement
standard ou avec les fichiers du projet courant. Le nom de fichier peut contenir .. et / (que
les compilateurs Windows interprtent correctement comme tant des sparateurs de rpertoire). Par exemple :
#include "../shared/globaldefs.h"

La directive #define dfinit une macro. Les occurrences de la macro qui apparaissent aprs la
directive #define sont remplaces par la dfinition de la macro. Par exemple, la directive
#define PI 3.14159265359

demande au prprocesseur de remplacer toutes les futures occurrences du jeton PI dans lunit
de compilation courante par le jeton 3.14159265359. Pour viter les collisions entre noms de
variable et de classe, on affecte gnralement aux macros des noms en majuscules. Nous pouvons
trs bien dfinir des macros qui reoivent des arguments :
#define SQUARE(x) ((x) * (x))

Dans le corps de la macro, il est conseill dencadrer toutes les occurrences des paramtres par
des parenthses, ainsi que le corps complet, pour viter les problmes de priorit des oprateurs. Aprs tout, nous voulons que 7*SQUARE(2+3) soit dvelopp en 7*((2+3)*(2+3)), et
non en 7*2+3*2+3.
Les compilateurs C++ nous permettent normalement de dfinir des macros sur la ligne de
commande, via loption -D ou /D. Par exemple :
CC -DPI=3.14159265359 -c main.cpp

Les macros taient trs populaires avant larrive des typedef, numrations, constantes, fonctions inline et modles. Leur rle aujourdhui consiste surtout protger les fichiers den-tte
contre les inclusions multiple.

Vous annulez la dfinition dune macro tout moment laide de #undef:


#undef PI

Cest pratique si nous avons lintention de redfinir une macro, puisque le prprocesseur
interdit de dfinir deux fois la mme macro. a sert aussi contrler la compilation conditionnelle.

Qt 4 Livre Page 522 Jeudi, 7. dcembre 2006 12:14 12

522

Qt4 et C++ : Programmation dinterfaces GUI

Vous traitez des parties du code ou vous les sautez en codant #if, #elif, #else, et
#endif, en fonction de la valeur numrique des macros. Par exemple :
#define NO_OPTIM 0
#define OPTIM_FOR_SPEED 1
#define OPTIM_FOR_MEMORY 2
#define OPTIMIZATION OPTIM_FOR_MEMORY
...
#if OPTIMIZATION == OPTIM_FOR_SPEED
typedef int MyInt;
#elif OPTIMIZATION == OPTIM_FOR_MEMORY
typedef short MyInt;
#else
typedef long long MyInt;
#endif

Dans lexemple ci-dessus, seule la deuxime dclaration de typedef devra tre traite par le
compilateur, MyInt tant ainsi dfini comme un synonyme de short. En changeant la dfinition de la macro OPTIMIZATION, nous obtenons des programmes diffrents. Lorsquune
macro nest pas dfinie, sa valeur est considre comme tant 0.
Une autre approche de la compilation conditionnelle consiste tester si une macro est dfinie
ou non. Vous procdez en codant loprateur defined() comme suit :
#define OPTIM_FOR_MEMORY
...
#if defined(OPTIM_FOR_SPEED)
typedef int MyInt;
#elif defined(OPTIM_FOR_MEMORY)
typedef short MyInt;
#else
typedef long long MyInt;
#endif

Pour des raisons pratiques, le prprocesseur reconnat #ifdef X et #ifndef X comme des
synonymes de #if defined(X) et #if!defined(X). Vous protgez un fichier den-tte
des inclusions multiple en encadrant son contenu comme ci-aprs :
#ifndef MYHEADERFILE_H
#define MYHEADERFILE_H
...
#endif

Qt 4 Livre Page 523 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

523

La premire fois que le fichier den-tte est inclus, le symbole MYHEADERFILE_H nest pas
dfini, donc le compilateur traite le code entre #ifndef et #endif. La seconde et toutes les
fois suivantes o le fichier den-tte est inclus, MYHEADERFILE_ H est dj dfini, donc le bloc
#ifndef #endif complet est ignor.

La directive #error met un message derreur dfini par lutilisateur au moment de la


compilation. Elle est souvent associe une compilation conditionnelle pour signaler un
cas impossible. Par exemple :
class UniChar
{
Public
#if BYTE_ORDER == BIG_ENDIAN
uchar row;
uchar cell;
#elif BYTE_ORDER == LITTLE_ENDIAN
uchar cell;
uchar row;
#else
#error "BYTE_ORDER must be BIG_ENDIAN or LITTLE_ENDIAN"
#endif
};

Contrairement la plupart des autres constructions C++, pour lesquelles les espaces ntaient
pas significatifs, les directives de prprocesseur doivent apparatre sur leur propre ligne, sans
point-virgule la fin. Les directives trs longues peuvent occuper plusieurs lignes en terminant
chacune delles sauf la dernire par un slash invers (\).

La bibliothque C++ standard


Dans cette section, nous allons rapidement passer en revue la bibliothque C++ standard. La
Figure B.3 rpertorie les principaux fichiers den-tte C++. Les en-ttes <exception>, <limits>,
<new>, et <typeinfo> prennent en charge le langage C++ ; par exemple, <limits> nous
permet de tester les proprits du support arithmtique des types entier et virgule flottante sur
le compilateur, et <typeinfo> offre une introspection de base. Les autres en-ttes fournissent
gnralement des classes pratiques, notamment une classe chane et un type numrique
complexe. La fonctionnalit offerte par <bitset>, <locale>, <string>, et <typeinfo>
reprend largement celle des classes QBitArray, QLocale, QString, et QMetaObject de Qt.
Le C++ standard inclut aussi un ensemble de fichiers den-tte qui traitent les E/S, numrs
en Figure B.4. La conception des classes dentres/sorties standard datant des annes 80, elles
sont assez complexes et donc difficiles tendre. Lopration est dailleurs tellement complexe
que des livres complets ont t crits sur le sujet. Le programmeur hrite dailleurs avec ces
classes dun bon nombre de problmes non rsolus lis au codage des caractres et aux reprsentations binaires dpendantes de la plate-forme des types de donnes primitifs.

Qt 4 Livre Page 524 Jeudi, 7. dcembre 2006 12:14 12

524

Qt4 et C++ : Programmation dinterfaces GUI

Fichier den-tte

Description

<bitset>

Classe modle pour reprsenter des squences de bits de longueur fixe

<complex>

Classe modle pour reprsenter des nombres complexes

<exception>

Types et fonctions lis la gestion des exceptions

<limits>

Classe modle qui spcifie les proprits des types numriques

<locale>

Classes et fonctions lies la localisation

<new>

Fonctions qui grent lallocation de mmoire dynamique

<stdexcept>

Types dexception prdfinis pour signaler les erreurs

<string>

Conteneur de chanes modle et caractristiques des caractres

<typeinfo>

Classe qui fournit des informations de base concernant un type

<valarray>

Classes modle pour reprsenter des tableaux de valeurs

Figure B.3
Principaux fichiers den-tte de la bibliothque C++
Fichier den-tte

Description

<fstream>

Classes modle qui manipulent des fichiers externes

<iomanip>

Manipulateur de flux dE/S qui reoit un argument

<ios>

Classe de base modle pour les flux dE/S

<iosfwd>

Dclarations anticipes pour plusieurs classes modles de flux dE/S

<iostream>

Flux dE/S standard (cin, cout, cerr, clog)

<istream>

Classe modle qui contrle les entres en provenance dune mmoire tampon de flux

<ostream>

Classe modle qui contrle les sorties vers une mmoire tampon de flux

<sstream>

Classes modle qui associent des mmoires tampon de flux avec des chanes

<streambuf>

Classes modle qui placent en mmoire tampon les oprations dE/S

<strstream>

Classes pour effectuer des oprations de flux dE/S sur des tableaux de caractres

Figure B.4
Fichiers den-tte de la bibliothque dE/S C++

Qt 4 Livre Page 525 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

525

Le Chapitre 12 consacr aux entres/sorties dtaille les classes Qt correspondantes, qui prsentent les entres/sorties Unicode ainsi quun jeu important de codages de caractres nationaux et
une abstraction indpendante de la plate-forme pour stocker les donnes binaires. Les classes
dE/S de Qt forment la base des communications interprocessus, de la gestion de rseau, et du
support XML de Qt. Les classes de flux de texte et binaire de Qt sont trs faciles tendre pour
prendre en charge les types de donnes personnaliss.
La bibliothque STL (Standard Template Library) existe depuis les annes 90 et propose un
jeu de classes conteneur, itrateurs et algorithmes bass sur des modles, qui font maintenant
partie de la norme standard ISO C++.
La Figure B.5 numre les fichiers den-tte de la STL. La conception de cette bibliothque est
rigoureuse, presque mathmatique, et elle fournit une fonctionnalit gnrique scurise au
niveau des types. Qt fournit ses propres classes conteneur, dont la conception sinspire de celle
de la STL. Ces classes sont dtailles au Chapitre 11.
Fichier den-tte

Description

<algorithm>

Fonctions modle usage gnral

<deque>

Conteneur modle de file dattente double accs

<functional>

Modles daide la construction et manipulation des functeurs (objets fonction)

<iterator>

Modles daide la construction et manipulation des itrateurs

<list>

Conteneur modle de listes doublement chanes

<map>

Conteneurs modle de map valeur unique ou multiple

<memory>

Utilitaires pour simplifier la gestion de mmoire

<numeric>

Oprations numriques modles

<queue>

Conteneur modle de files dattente

<set>

Conteneurs modle densembles valeur unique ou multiple

<stack>

Conteneur modle de piles

<utility>

Fonctions modle de base

<vector>

Conteneur modle de vecteurs

Figure B.5
fichiers den-tte de la STL

C++ tant surtout une version amliore du langage de programmation C, les programmeurs
C++ disposent aussi de la bibliothque C complte. Les fichiers den-tte C sont disponibles

Qt 4 Livre Page 526 Jeudi, 7. dcembre 2006 12:14 12

526

Qt4 et C++ : Programmation dinterfaces GUI

soit sous leurs noms traditionnels (par exemple <stdio.h>) soit sous leur forme moderne avec
le prfixe c- et sans lextension .h (<cstdio>, par exemple). Lorsque nous codons avec le nom
moderne, les fonctions et types de donnes sont dclars dans lespace de noms std. (Cela ne
sapplique pas des macros telles que ASSERT(), parce que le prprocesseur ne connat pas les
espaces de noms.) La nouvelle syntaxe est recommande si votre compilateur la prend en charge.
La Figure B.6 rpertorie les fichiers den-tte C. La plupart proposent une fonctionnalit qui
recouvre celle de fichiers den-tte C++ ou de Qt plus rcents. Notez que <cmath> est une
exception, ce fichier dclare des fonctions mathmatiques telles que sin(), sqrt(), et pow().
Fichier den-tte

Description

<cassert>

La macro ASSERT()

<cctype>

Fonctions pour classer et faire correspondre les caractres

<cerrno>

Macros lies au signalement des conditions derreur

<cfloat>

Macros spcifiant les proprits des types virgule flottante primitifs

<ciso646>

Autres orthographes pour les utilisateurs du jeu de caractres ISO 646

<climits>

Macros spcifiant les proprits des types entiers primitifs

<clocale>

Fonctions et types lis la localisation

<cmath>

Fonctions et constantes mathmatiques

<csetjmp>

Fonctions pour excuter des branchements non locaux

<csignal>

Fonctions pour grer les signaux du systme

<cstdarg>

Macros pour implmenter les fonctions liste dargument variable

<cstddef>

Dfinitions courantes de plusieurs en-ttes standard

<cstdio>

Fonctions pour effectuer les E/S

<cstdlib>

Fonctions utilitaires gnrales

<cstring>

Fonctions pour manipuler les tableaux de caractres

<ctime>

Types et fonctions pour manipuler le temps

<cwchar>

Fonctions de manipulation de chanes de caractres tendus

<cwctype>

Fonctions pour classer et faire correspondre les caractres tendus

Figure B.6
Fichiers den-tte C++ pour les utilitaires de bibliothque C

Qt 4 Livre Page 527 Jeudi, 7. dcembre 2006 12:14 12

Annexe B

Introduction au langage C++ pour les programmeurs Java et C#

527

Cette rapide prsentation de la bibliothque C++ standard sachve prsent. Dinkumware


propose sur Internet une documentation de rfrence complte concernant la bibliothque C++
standard ladresse http://www.dinkumware.com/refxcpp.html, et SGI offre un guide du
programmeur STL ladresse http://www.sgi.com/tech/stl/. La dfinition officielle de cette
bibliothque se trouve avec les standard C et C++, disponibles sous forme de fichiers PDF ou
de copies papier demander auprs de lorganisation ISO (International Organization for
Standardization).
Cette annexe traite en trs peu de pages un sujet normalement fort tendu. Quand vous allez
commencer tudier Qt partir du Chapitre 1, vous allez dcouvrir que la syntaxe est beaucoup plus simple que cette annexe ne pouvait le laisser supposer. Vous pouvez trs bien
programmer en Qt en faisant uniquement appel un sous-ensemble de C++ et sans avoir
besoin de la syntaxe plus complexe que vous pouvez retrouver dans ce langage. Ds que vous
aurez commenc saisir du code et crer et lancer vos excutables, vous ne pourrez que
constater quel point lapproche de Qt est simple et claire. Et ds que vous aborderez des
programmes plus ambitieux, en particulier ceux qui ont besoin dun graphisme performant et
labor, la combinaison C++/Qt continuera vous donner toute satisfaction.

Qt 4 Livre Page 528 Jeudi, 7. dcembre 2006 12:14 12

Qt 4 Livre Page 529 Jeudi, 7. dcembre 2006 12:14 12

Index

Symboles
#define 487, 521
#endif 445, 487
#ifdef 445
#ifndef 487
#include 486, 521
#undef 521
& 18, 497
() 94
<algorithm> 276
<cmath> 125
<iostream> 86
<QtAlgorithms> 276
<QtGui> 18, 48

A
About Qt 53
about() (QMessageBox) 70
accept() 411
QDialog 31, 36
Acceptable (QSpinBox) 107
acceptProposedAction() 215
acquire() 414, 415
Action 51
About Qt 53
Auto-Recalculate 52
exitAction 53
New 51

Open 52
Save 52
Save As 52
Select All 52
Show Grid 52
activateWindow() 65
Active (groupe de couleurs) 115
activeEditor() 162
ActiveQt 448
ActiveX 460
sous Windows 448
addAction() (QMenu) 54
addBindValue() 313
addCd() 326
addChildSettings() 235
addDatabase() 311, 314
addItem() (QComboBox) 241
addLibraryPath() 435
addMenu() (QMenu) 54
AddRef() 456
addRow() 233
addStretch() 148
addTrack() 327
addTransaction() 420-421
addWidget() 147, 151
QStatusBar 57
adjust() 139
PlotSettings 134
adjustAxis() 139
adjusted() 130

adjustSize() 127
Aide en ligne 373
Assistant Qt 379
avec lassistant 379
infobulle 374
informations dtat 374
QTextBrowser 376
Quest-ce que cest ? 374
Algorithme
gnrique 276
qBinaryFind 277
qCopy 277
QCopyBackward()
285
qDeleteAll 278
qEqual() 285
qFill 277
qFind 276
qSort 277
qStableSort 278
qSwap 278
AlignHCenter 57
alignment 137
Anticrnelage 186, 189
bas sur X Render 197
AnyKeyPressed (QAbstractItemView) 231
API natives 444
append() 265
QString 279
Apple Developer Connection
479

Qt 4 Livre Page 530 Jeudi, 7. dcembre 2006 12:14 12

530

Qt4 et C++ : Programmation dinterfaces GUI

Application
Age 7
capable de grer les plug-in
435
Carnet dadresse 458
CD Collection 317
Cities 246
Color Names 240
Coordinate Setter 232
Currencies 243
Directory Viewer 238
crire des plug-in 439
Flowchart Symbol Picker
230
Image Converter 303, 353
Image Pro 419
implmenter la fonctionnalit
77
infobulle et information
dtat 374
Mail Client 154
MDI Editor 160
ouverte aux traductions 390
Project Chooser 216
Quit 7
Regexp Parser 251
Settings Viewer 234
Splitter 152
Spreadsheet 46, 92
Team Leaders 236
Tetrahedron 207-208
Threads 409
Tic-Tac-Toe 462
traduire 402
Trip Planner 343
Windows Media Player 448
apply() 422-423
applyEffect() 438-440
appTranslator 398, 401
Architecture modle/vue 228
arg() 279, 392
QString 62
ARGB32 197

arguments() 330
QApplication 168
Assistant Qt 10, 379
asVariant() 452
at() 271
Attribut
WA_DeleteOnClose 74,
162
WA_StaticContents 117
Auto-Recalculate 52, 71, 75, 79
avg() 104

B
BackgroundColorRole 242, 256
Barre dtat, configurer 56
Barre doutils
ancrable 157
crer 51
Base de donnes 309
connexion et excution de
requtes 310
formulaires 321
prsenter les donnes 317
beforeInsert() 320, 325
begin() 269
beginGroup() 71
Bibliothque
C++ standard 523
dynamique 425, 488
E/S C++ 524
OpenGL 207
statique 488
STL 63
bindValue() 313
Bote de dialogue
About 69
conception rapide 25
crer 15
de feedback 44
dynamique 39
tapes de cration 26
extensible 33
Find 16

Go-to-Cell 26
intgre 40
multiforme 32
multipage 39
QDialog (classe de base) 16
Sort 33
utiliser 64
bool 489
Boucle
dvnement 4, 178
foreach 271
while 417
boundingRect() (QPainter) 204
break 273
BufferSize 416
buttons() (QMouseEvent) 117

C
C#
diffrences avec C++ 489
diffrences avec Java 483
C++
chanes de caractres 505
dfinitions de classe 490
destructeur 496
diffrences avec Java et C#
489
numrations 507
espaces de noms 518
hritage 493
pointeurs 497
polymorphisme 493
prprocesseur 520
prsentation 483
surcharge doprateur 512
tableaux 502
typeDef 509
types valeur 514
cachedValue 97, 100
cacheIsDirty 97, 98
cacheIsValid 100
canRead() 428, 429

Qt 4 Livre Page 531 Jeudi, 7. dcembre 2006 12:14 12

Index

canReadLine() 352
capabilities() 427
CanRead 428
CanReadIncremental 428
CanWrite 428
Capuchon 185
cascade() 164
CaseSensitivity 17
cd() 333
cdModel 325
cdTableView 325, 327
Cell 79, 81, 90, 96
arbre dhritage 79
cell() 83, 90
cerr 486
Chanes de caractres 505
changeEvent() 401
char 386, 489
characters() 362-363
Chargement 85
charset 224
checkable 33
Classe
accs alatoire
QBuffer 288
QFile 288
QTemporaryFile 288
conteneur 263
itrateurs 267
QBrush 271
QByteArray 266, 271
QCache 275
QDateTime 266
QFont 271
QHash 264, 273
QImage 271
QLinkedList 264-266
QList 264
QList<T> 266
QMap 264, 273
QPair 285
QPixmap 271
QRegExp 266
QSet 275
QString 266, 271

QVariant 266
QVarLengthArray
<T,Prealloc> 285
QVector 264, 266
daffichage dlments 227
QListWidget 228
QTableWidget 228
QTreeWidget 228
utiliser 229
dlments 82
de modle 244
QAbstractItemModel
244
QAbstractListModel
244
QAbstractTableModel
244
de widgets 40
QByteArray 278
QString 278
QVariant 278
squentielle
QProcess 288
QTcpSocket 288
QUdpSocket 288
TextArtDialog 437
wrapper de plug-in 426
Cl 71
clear() 81, 82, 252, 278, 400
Spreadsheet 58
clearCurve() 129
clicked() 7, 170, 304
Client
FTP 330
HTTP 339
TCP 342
ClientSocket 349-350
clipboard() 88, 224
clone() 98, 514
close() 19, 61, 74, 164, 333
closeActiveWindow() 164
closeAllWindows() 74, 164
closeConnection(), TCP 348
closeEditor() 260
closeEvent() 70, 164, 167

531

MainWindow 74
QWidget 47, 61
codecForName() 389
CodeEditor 171
ColorGroup 115
ColorNamesDialog 240
column() (QModelIndex) 242
columnCount() 82, 244
columnSpan 148
COM, objets 448
commit() 313
commitAndCloseEditor() 259
commitData() 464
Communication inter-processus
303
Compagnon 18
compare() 68, 94-95
Compilateur
excution 486
macros 446
MinGW C++ 478
moc 22
uic 29
units 484
Composition (mode) 188, 198
CompositionMode_SourceOver
198
CompositionMode_Xor 199
connect() 19, 192, 419, 423
QObject 9, 23
connected() 344, 345
connectionClosedByServer() 349
connectToHost() 333, 345
connectToServer() 345
Connexion
aux bases de donnes 310
de requtes 310
tablir 6
signal/slot 304
const 499
const_cast<T>() 510
constData() 283
Constructeur de copie 514
contains() (QRect) 117

Qt 4 Livre Page 532 Jeudi, 7. dcembre 2006 12:14 12

532

Qt4 et C++ : Programmation dinterfaces GUI

Conteneur
associatif 264, 273
QCache 275
QHash<K,T> 264, 273
QMap<K,T> 264, 273
QSet 275
copie 270
squentiel 264
QLinkedList 266
QLinkedList<T> 264265
QList<T> 264, 266
QQueue<T> 266
QStack<T> 266
QVector 264
QVector<T> 264, 266
contentsChanged() 166-167
contextMenuEvent() (QWidget)
55, 67
ContiguousSelection 81, 89
continue 273
Contrle ActiveX 448
controllingUnknown() 456
Conversions de type 509
ConvertDepthTransaction 422
convertTo1Bit() 420
convertTo32Bit() 420
convertTo8Bit() 420
convertToFormat() 112
Coordonnes
conversion logiquesphysiques 190
systme par dfaut 188
copy() 88
CopyAction 218
copyAvailable() 162
count() 265
Courbe de Bzier 186
cout 486
Crayon 185
create() 121, 427-428
createActions() 49, 376, 398
createConnection() 311, 313
createContextMenu() 49
createEditor() 161, 258, 260

createIndex() 249
createLanguageMenu() 399-400
createMenus() 49, 164, 398
createStatusBar() 49, 56
createToolBars() 49
critical() (QMessageBox) 59
curFile 62, 167
currencyAt() 246
CurrencyModel 243
currentCdChanged() 324
currentCellChanged() 57
currentDateTime() (QDateTime)
193
currentFormula() 84
currentIndex() (QComboBox) 68
currentItem 364
currentLocation() 84
currentRow 151
currentRowChanged() 151
currentText 363
currentThread() 417
Curseur 452
CursorHandler 428, 434
curveMap 129
curZoom 127
CustomerInfoDialog 176
cut() 88
Editor 163

D
data() 99, 244-245, 283
QMimeData 219
QTableWidgetItem 96
dataChanged() 249
QClipboard 225
Datagramme UDP 353
DataSize 416
debug 5
Dclaration pralable 16
default 33
defined() 522
Dfinition de classe 490

Dgrad
circulaire 188
conique 188
linaire 187
del() 90
Dlgu 228
personnalis 256
delete 90, 252
deleteLater() 350
delta() 135
Drivation
QDialog 16
QMainWindow 46
QMimeData 224
QTableWidget 78
QTableWidgetItem 96
QWidget 108
Dessin
courbe de Bzier 186
crayon 185
dgrads
circulaires 188
coniques 188
linaires 187
formes gomtriques 186
pinceau 185
police 185
QPainter 184
rectangle sans anticrnelage
189
styles
de capuchon et jointure
185
de crayon 185
trac 187
viewport 189
DiagCrossPattern 187
Dinkumware 527
Directives 445
#include 486
de prprocesseur 487
Disabled (groupe de couleurs)
115
disconnect() 423

Qt 4 Livre Page 533 Jeudi, 7. dcembre 2006 12:14 12

Index

disconnected() 344, 349


DisplayRole 97, 99, 242, 245
Disposition 7, 28, 144
empile 150
grer 143
gestionnaires 9, 146
manuelle 145
positionnement absolu 144
distances 248
DLL 425, 488
Document
multipage 201
multiple 72
Documentation de rfrence 10
documentElement() 367
documentTitle() 378
documentWasModified() 166
DOM (Document Object Model)
359
DomParser 367
done() 231, 331
Donne
binaire
criture 288
lecture 288
les stocker en tant
qulments 82
prsentation sous forme
tabulaire 317
types primitifs 489
DontConfirmOverwrite
(QFileDialog) 61
double 489
mise en mmoire tampon 122
dragEnterEvent() 214-215
dragMoveEvent() 218
draw() 184, 194, 209
dessin dun ttradre 210
drawCurves() 136, 138
drawDisplay() 259
drawFocus() 259
drawGrid() 136
drawLine() 196
QPainter 114

drawPie() 186
drawPolygon() 195
drawPolyline() 138
drawPrimitive() 130
QStylePainter 130
drawRect() 189
drawText() 137, 196
conversion des coordonnes
190
dropEvent() 214
glisser-dposer 215
dumpdoc 451
duration() 193
dynamic_cast<T>() 510
dynamicCall() 452

E
E_NOINTERFACE 456
Echelon 140
Ecriture
code XML 370
donnes binaires 288
texte 294
Editeur
de connexion (Qt Designer)
37
de liens 485
editingFinished() 259
Editor 164
cut() 163
fonctions 165
save() 162
EditRole 97-99, 242
effects() 436, 439
Ellipse (dessiner) 186
emit 21
enabled 27
enableFindButton() 19, 21
end() 269
conteneur non const 271
endElement() 360, 362, 364
endGroup() 71
endl 486

533

endsWith() 281
enterErrorState() 431
Entres/sorties 287
lire et crire des donnes
binaires 288
lire et crire du texte 294
parcourir les rpertoires 300
entryHeight() 203, 204
entryList() 301
Enumrations 507
Equivalence pointeurs/tableaux
503
eraseRect() 440
error() 306, 344, 349
errorString() 363
escape() 221
Espace de noms 518
std 125
Espacement 20
evalExpression() 100-101, 104
evalFactor() 101, 104
evalTerm() 101-104
Evnement 4
boucle 178
close 164
drop 214
filtre 175, 177
gestionnaires, rimplmenter
170
key 170
paint 113, 118
propagation 178
resize 131
timer 172
traiter 169, 177
versus signal 170
wheel 135
event() 170
eventFilter() 176
exec() 66, 312, 418
QApplication 178
QDialog 66
QPrintDialog 200
requte SQL 311

Qt 4 Livre Page 534 Jeudi, 7. dcembre 2006 12:14 12

534

Qt4 et C++ : Programmation dinterfaces GUI

Excutable 485
intgrer des donnes 302
execute() 307
QProcess 307
exitAction 53
expand() 239
Expanding 149
explicit 511
Expression 101
rgulire 250
syntaxe 101
extern 517

F
faceAtPosition() 211-212
fatalError() 362, 364
Fentre
dapplication
QDialog 4
QMainWindow 4
dessin 188
modale 66
non modale 64
principale
configurer 49
crer 45
Fichier den-tte 18, 48
<QtAlgorithms> 276
<QtGui> 18
dfinition 487
Fichier objet 485
fill() (QPixmap) 136
fillRect() (QPainter) 116
Filtre
cin en cout 299
dvnement 446
installer 169, 175
entres du systme
de fichiers 238
find() 444
findChild<T>() (QObject) 40
findClicked() 19, 21

findNext() 17, 21, 65, 91


findPrevious() 17, 21, 65, 92
finished() 306, 422
firstChild() 369
flags() 247, 249
flipHorizontally() 420
FlipTransaction 422
flipVertically() 420
float 489
flush() 289
focusNextChild() 175, 177
Fonction
globale 485, 516
prototype 486
virtuelle pure 494
fontMetrics() (QWidget) 174
FontRole 242, 256
foreach 75, 505
foreground() (QPalette) 115
FOREIGN KEY 318
forever 347
formats() 222-223
ARGB32 prmultipli 197
formula() 83
Formulaire
crer laide de QWidget
108
dveloppement 26
disposition des widgets 28
insrer un HexSpinBox 118
matre/dtail 321
modifier la conception 32
nommer les widgets 35
Frameworks de migration
Qt/Motif et Qt/MFC 444
Froglogic 444
fromAscii() 283
fromLatin1() 283
fromPage() (QPrinter) 205
ftpDone() 331
FtpGet 330
ftpDone() 331
ftpListInfo(), urlInfo 336

G
generateDocumentation() 451
generateId() 320
generateRandomTrip() 351
geometry() (QWidget) 72
Gestion de session
tests et dbogage 467
X11 461
Gestionnaire
dvnements
endElement() 360
rimplmenter 170
startElement() 360
de disposition 9, 146
QGridLayout 9, 146
QHBoxLayout 9, 146
QVBoxLayout 9, 146
get() 330, 332-333, 337
oprations HTTP 339
getColor() (QColorDialog) 211
getDC() 445
getDirectory() 335
getFile() 330, 332
oprations HTTP 340
GetInterfaceSafetyOptions() 456
getOpenFileName()
(QFileDialog) 59
getSaveFileName()
(QFileDialog) 61
GL_SELECT 212
glClearColor() 209
glClearIndex() 209
glColor3d() 210
glIndex() 210
Glisser, types personnaliss 219
Glisser-dposer 213
activer 214
QTableWidget 219
GNU (General Public License)
478
Go to Cell 27
GoToCellDialog 27

Qt 4 Livre Page 535 Jeudi, 7. dcembre 2006 12:14 12

Index

Graphique
2D 183
3D 183
OpenGL 207
group() 120
Groupe de couleurs
Active 115
Disabled 115
Inactive 115
guidgen 457
GuiServer 471

H
Handle 444, 446
hasAcceptableInput()
(QLineEdit) 32
hasFeature() 313
hasLocalData() 418
hasNext() 267
hasPendingEvents() 181
hasPrevious() 268
head() 341
headerData() 244-245, 248, 256
Hello Qt 4
HelpBrowser 377, 380
Hritage 493
HexSpinBox 106, 108, 118
hide() 128
hideEvent() 173, 175
hostName 392

I
IANA (Internet Assigned
Numbers Authority) 215
icon() 120
IconEditor 109, 115, 120, 122
pixelRect() 115
IconEditorPlugin 120
iconForSymbol() 231
iconImage() 109-110, 113
Identifiant de la fentre 444

IgnoreAction 218
image() 110, 421
presse-papiers 224
imageCount() 433
imageSpace() 300-301
ImageWindow 420
Impression 199
QPainter 205
QTextDocument 202
Inactive (groupe de couleurs) 115
includeFile() 120
incomingConnection() 349-350
index() 253
de modle 249
indexOf() 150, 280
Infobulle 374
information() (QMessageBox) 59
initFrom() 130, 136, 198
initializeGL() 208-209
insert() 237, 281
avec itrateur 269
dans map 273
insertMulti() 274-275
insertRow() 233, 315
installEventFilter() 176-177
instance() (QPluginLoader) 438
Instruction
DELETE 314
GET 337
if 417
INSERT 313-314
SELECT 312, 314
UPDATE 314
int 312, 489
Interface avec les API natives 444
Intermediate 107
QSpinBox 107
Internationalisation 385
passer dynamiquement
dune langue lautre 396
traduire les applications 390,
402
Unicode 386

535

Internet Explorer
accs laide 380
intgrer un widget 453
options de scurit
du composant 455
Introspection 25
Invalid 107
QSpinBox 107
invokeMethod() 424
IObjectSafety 455
isActive() 312
isalpha() 387
isContainer() 121
isdigit() 387
isEmpty() 282
conteneur 269
isLetter() 387
isLetterOrNumber() 387
isLower() 387
isMark() 387
isNumber() 387
isPrint() 387
isPunct() 387
isSessionRestored() 467
isSpace() 387
isSymbol() 387
isUntitled 165, 167
isUpper() 387
item->text() 231
itemChanged() 81
ItemIsEditable 249
Itrateur
de style Java 267
de style STL 269
mutable 268
pour liste chane 265

J
Java
diffrences avec C# 483
diffrences avec C++ 489
machines virtuelles 470

Qt 4 Livre Page 536 Jeudi, 7. dcembre 2006 12:14 12

536

Qt4 et C++ : Programmation dinterfaces GUI

join() 282
Jointure 185
JournalView 401
jumpToNextImage() 433

K
KDE XIV
fermeture de session 461
projet XVIII
key() 138, 169, 246, 275-276
map 275
keyPressEvent() 135, 170-171,
175
Plotter 139
keyReleaseEvent() 170
keys() 274, 427
itrateur STL 276
killTimer() (QObject) 175
Klarlvdalens Datakonsult 444

L
LanguageChange 402
LargeGap 203
Latin-1 386
LD_LIBRARY_PATH 481
leaders() 238
Lecture
code XML 360, 365
donnes binaires 288
texte 294
left() 280, 283
LeftDockWidgetArea 158
length() 279, 282
LinkAction 218
list() 333
Liste chane 265
load() 394, 401
loadFile() 59, 64
loadPlugins() 437-438

localeAwareCompare() 281, 395


LocaleChange 401-402
localeconv() 395
lock() 411
login() 332, 333
long 489
long long 489
lrelease 402
lupdate 402, 403

M
Mac OS X
identifier la version 446
installer Qt 479
macEvent() 448
macEventFilter() 448
Macro
de compilateur 446
Q_ OBJECT 47
Q_DECLARE_INTERFACE
() 436
Q_DECLARE_METATYP
E() 284
Q_EXPORT_PLUGIN2()
121, 428, 441
Q_INTERFACES() 120,
439
Q_OBJECT 17-18, 21,
110, 390, 439
Q_PROPERTY() 109
qPrintable() 283, 289
QT_TR_NOOP() 392
QT_TRANSLATE_NOOP()
393
SIGNAL() 7, 23
signals 17
SLOT() 7, 23
slots 17
MagicNumber 86, 292
mainSplitter 154
MainWindow 399
changeEvent() 401
closeEvent() 74

drivation de QMainWindow
47
glisser-dposer 214
newFile() 82
sort() 69
switchLanguage() 401
updateStatusBar() 84
make 5, 479
Makefile 5, 22, 29
Manipulateur de flux 295
Masque
AND 431
XOR 431
Matrice world 190
dessin 188
MDI (Multiple Document
Interface) 75, 159
Mcanisme
des ressources 50
fentre-viewport 189
parent-enfant 31
MediumGap 204
Mmoire dimage 470
Menu
Bar() (QMainWindow) 53
crer 51
Edit 88
File 57
Options 92
Tools 92
Window 160
message() 423
metaObject(), dclaration avec
Q_OBJECT 25
Mta-objets 25
mid() 280, 283
QString 66
Migration de Motif/Xt et MFC
vers Qt 444
MIME (type) 215
mimeData() 224
MinGW C++ 478
minimumSizeHint() 124, 129,
149

Qt 4 Livre Page 537 Jeudi, 7. dcembre 2006 12:14 12

Index

mirrored() 423
Mise en veille 462
mkdir() 239, 333
moc 22, 25
Mode de composition 188, 198
Modle
arborescence 242
index 249
liste 242
personnalis 241
prdfini 236
tableau 242
modified() 80, 85
modifiers() (QKeyEvent) 135
Module
QtCore 18
QtGui 18
QtNetwork 18
QtOpenGL 18, 207
QtSql 18
QtSvg 18
QtXml 18
mouse 169, 208
MouseButtonPress 170
mouseMoveEvent() 117, 136,
210
mousePressEvent() 117, 136,
170, 192, 210, 465
QListWidget 217
QWidget 116
mouseReleaseEvent() 136, 139
collage avec le bouton du
milieu de la souris 225
move() (QWidget) 72
MoveAction 218
moveToThread() 419
Multithread 407
mutable 97, 101
Mutex 411
emploi 413
MVC (Modle-Vue-Contrleur)
228

N
name() 120
New 51
newFile() 51, 58, 161, 166
MainWindow 73, 82
newPage() 199, 200, 205
next() 267, 268
map 275
nextBlockSize() 344-345, 347,
350
nextSibling() 369
nmake 5
NoBrush 187
Node 250, 254
nodeFromIndex() 253-254
Nud 251
normalized() (QRect) 130, 133
notify() (QApplication) 178
number() (QString) 107
numCopies() (QPrinter) 205
numRowsAffected() 312
numTicks 140
numXTicks 125
numYTicks 125

537

OpenGL 183, 207


bibliothque 207
openRecentFile() 52, 64
Oprateur
() 94
+ 279
+= 279
. (point) 493
: 486, 495
<< 486
-> (flche) 497
bit bit | 508
bit bit|= 508
daffectation 514
sizeof() 502
unaire & 497
unaire * 499
operator 291
operator()(int) 95
operator==() 274
operator>>() 291
Ordre de tabulation 22, 28
OvenTimer 191

P
O
objectName 27
Objet
COM 448
compare 68, 94
QSettings 72
SpreadsheetCompare 68
offset 173, 174
offsetOf() 250
okToContinue() 58, 165, 167
on_browseButton_clicked() 304
on_convertButton_clicked() 304
on_lineEdit_textChanged() 32
Open 52
Open source 478
open() 59, 162, 166
QTemporaryFile 307

Page daccueil 75
paginate() 202-203
paint 113
paintEngine() 445
Painter, transformations 188
paintEvent()
anticrnelage 197
copie de pixmap 136
dfinition du viewport 194
IconEditor 113
implmentation de event()
170
OvenTimer 192
rimplmentation
TicTacToe 465
widget Ticker 174
paintGL() 208-209

Qt 4 Livre Page 538 Jeudi, 7. dcembre 2006 12:14 12

538

Qt4 et C++ : Programmation dinterfaces GUI

palette() (QWidget) 115


Paramtre
charset 224
parent 244, 247
preferredType 223
section 245
stocker 70
parent 244, 247
parent() (QModelIndex) 242
parse() 365
parseEntry() 368
Partage implicite 270
principe 272
paste() 90
PATH 5, 380
PdfFormat 199
peek() 294, 429
penColor() 109, 111, 113
pendingDatagramSize() 357
Pile 498
Pile de zoom
curZoom 127
zoomStack 127
Pilote
QODBC 310
QPSQL 310
QSQLITE 310
QSQLITE2 310
QTDS 310
Pinceau 185
de fond 188
origine 188
pixelRect() 116
IconEditor 115
pixmap() 224, 440
Plastique 12
PlotSettings 123, 125, 127, 139
adjust() 134
Plotter 122, 124
keyPressEvent() 139
Plug-in 119
cration 425
dapplication 435, 439
dveloppement de Qt 426

plugins 122
Pointeur
rticule 132
C++ 497
conteneur 252
dattente standard 86
de fichier 288
de nud 256
glisser-dposer 218
nul 17
QObject 64
QPointer 498
toupie 7
hexadcimal 106
vers la barre dtat 56
void 83
Police 185
Polymorphisme 493
pop() 266
populateListWidget() 437-438
pos 100
post() 339, 341
postEvent() 423
Preferred 149
preferredType 223
prepare() 312
prepend() 62
Prprocesseur 488, 520
Presse-papiers
de slection 224
grer 224
previous() 268
map 275
printBox() 206
printFlowerGuide() 202
printHtml() 201
printImage() 200
printPages() 202, 205
PrintWindow 200
pro (fichier) 122
processEvents() (QApplication)
179
processNextDirectory() 335, 337
processPendingDatagrams() 356

Programmation embarque 469


ProjectListWidget 216, 218
Promotion 118
propertyChanged() 455
Protocole
de transport
TCP 329
UDP 329
QCOP 471
Prototype de fonction 486
push() 266
put() 330, 333

Q
Q_ OBJECT 47
Q_CLASSINFO() 459
Q_DECLARE_INTERFACE()
436
Q_ENUMS() 449
Q_EXPORT_PLUGIN2() 121,
428
Q_INTERFACES() 120, 439
Q_OBJECT 17-18, 21, 110,
390, 439
Q_OS_UNIX 446
Q_OS_WIN 446
Q_PROPERTY() 109
Q_WS_ X11 446
Q_WS_MAC 446
Q_WS_QWS 446
Q_WS_WIN 446
qAbs() 278
QAbstractItemDelegate 258
QAbstractItemModel 244, 252
reset() 246, 250
QAbstractItemView 52, 156,
218
AnyKeyPressed 231
ContiguousSelection 81, 89
NoEditTriggers 233
selectAll() 91
setEditTriggers() 231
QAbstractListModel 244

Qt 4 Livre Page 539 Jeudi, 7. dcembre 2006 12:14 12

Index

QAbstractScrollArea 42, 82, 156


QAbstractSocket 342
waitForDisconnected() 424
QAbstractTableModel 244
QAction 55, 162, 165, 171-172,
374, 399, 400
conversion dun pointeur
QObject 64
setShortcutContext() 172
QActionGroup 52, 162-163, 400
triggered() 400
qApp 53
QApplication 4, 394, 418
arguments() 168
clipboard() 88, 224
commitData() 462
exec() 178
filtre dvnement 177
GuiServer 471
isSessionRestored() 467
notify() 178
processEvents() 179
quit() 61
setLayoutDirection() 394
setOverrideCursor() 132
translate() 392
QAssistantClient 379
showPage() 380
QAXAGG_IUNKNOWN 456
QAxAggregated 456
QAxBase 449
dynamicCall() 452
generateDocumentation()
451
queryInterface() 452
QAxBindable 453
QAXCLASS() 459
QAxContainer 448
schma dhritage 450
QAXFACTORY_BEGIN() 459
QAXFACTORY_DEFAULT()
454, 457
QAXFACTORY_END() 459

QAXFACTORY_EXPORT()
459
QAxObject 449
QAxServer 448, 453
qaxserver.def 457
qaxserver.rc 457
QAXTYPE() 459
QAxWidget 449
setControl() 451
qBinaryFind() 264, 277
QBitArray 431
QBrush 116, 186
QBuffer 86, 288
fichiers tlcharger 339
QByteArray 218-219, 222, 266,
278, 288, 346, 352
QCDEStyle 130
QChar 386
isDigit() 387
isLetter() 387
isLetterOrNumber() 387
isLower() 387
isMark() 387
isNumber() 387
isPrint() 387
isPunct() 387
isSpace() 387
isSymbol() 387
isUpper() 387
QCheckBox 40, 147, 178
QClipboard
dataChanged() 225
Selection 224
setText() 88
QCloseEvent, accept() 411
QColor 111, 116
QColorDialog 208
getColor() 211
QComboBox 158
addItem() 241
currentIndex() 68
QComboBoxes 176
qCompress() 294
qconfig 472

539

QCOP 471
QCopChannel 471
qCopy() 277
QCoreApplication 330, 418
addLibraryPath() 435
arguments() 330
postEvent() 423
removePostedEvent() 423
QDataStream 85-86, 288, 290,
351, 431, 490
numro de version 292
oprations TCP 342
Qt_4_1 86
skipRawData() 431
QDate (toString) 395
QDateEdit 395
QDateTime 266
currentDateTime() 193
toString 395
QDateTimeEdit 395
qDeleteAll() 252, 278
QDesignerCustomWidgetCollectionInterface 122
QDesignerCustomWidgetInterface 119
IconEditorPlugin 120
QDialog 4, 16, 31, 178
driver 16
exec() 66
Rejected 66
QDir 300, 301
entryInfoList() 301
entyList() 301
exists() 301
imageSpace() 301
mkdir() 301
rename() 301
rmdir() 301
QDirectPainter 471
QDirModel 236, 238, 240
mkdir() 239
QDockWidget 157
setFeatures() 157
QDockWindow 157

Qt 4 Livre Page 540 Jeudi, 7. dcembre 2006 12:14 12

540

Qt4 et C++ : Programmation dinterfaces GUI

QDomDocument
documentElement() 367
save() 370
setContent() 367
QDomElement 369
QDomNode 368
firstChild() 369
nextSibling() 369
tagName() 368
toElement 368
QDoubleValidator 31
QDrag 217
start() 218
QDragEnterEvent 218
QEvent 170
MouseButtonPress 170
type() 170
QFile 85-86, 288, 301, 336
exists() 301
remove() 301
QFileDialog
DontConfirmOverwrite 61
getOpenFileName() 59
getSaveFileName() 61
QFileInfo 301
qFill() 277
qFind() 276
QFont 186, 288, 292
QFontMetrics 174
size() 174
QFrame 41, 121
QFtp 329
cd() 333
close() 333
commandFinished() 333
commandStarted() 333
ConnectToHost 333
done() 332
ftpListInfo() 335
get() 330, 333
list() 333
listInfo() 335
login() 333
mkdir() 333

put() 330, 333


rawCommand() 333
readyRead() 339
remove() 333
rename() 333
rmdir() 333
stateChanged() 333
qglClearColor() 209
qglColor() 210
QGLWidget
dessin avec OpenGL 207
qglClearColor() 209
setFormat() 209
QGridLayout 9, 19, 143, 146147
QGroupBox 178
QHash 274
QHash() 275
QHBoxLayout 9, 19, 143, 146147
QHeaderView 82
QHostInfo
fromName() 355
lookupHost() 355
QHttp 329
done() 340, 342
get() 339, 341
getFile() 340
head() 341
httpDone() 340
post() 339, 341
QTcpSocket 341
QtSslSocket 341
read() 342
readAll() 342
readyRead() 342
request() 341
requestFinished() 342
requestStarted() 342
setHost() 341
setUser() 341
QIcon 231

QImage 111, 288, 427


affichage de haute qualit
197
CompositionMode_SourceOver 198
CompositionMode_Xor 199
format ARGB32 prmultipli 197
imprimer 199
mirrored() 423
rect() 117
setPixel() 117
QImageIOHandler 428
QImageIOPlugin 427-428
QImageReader 427-428
QInputDialog 42
QIntValidator 31
QIODevice 287, 293
crire dedans 300
oprations HTTP 341
peek() 294, 429
QBuffer 287
QFile 287
QProcess 287
QTcpSocket 287
QTemporaryFile 287
QUdpSocket 287
ReadOnly 87
seek() 294
unget-Char() 294
WriteOnly 87
QItemDelegate 258
drawDisplay() 259
drawFocus() 259
QItemSelectionModel 324
QKeyEvent 171, 177
modifiers() 135
QLabel 42
bote de dialogue Find File
147
fentre dapplication 4
Hello Qt 4
implmentation 108
indicateurs dtat 56

Qt 4 Livre Page 541 Jeudi, 7. dcembre 2006 12:14 12

Index

setText() 424
statut de la dernire
opration 343
QLatin1String() 393
QLayout 146
SetFixedSize 38
setMargin() 147
setSpacing() 147
QLibrary 425
QLineEdit
activer avec barre despace
175
bote de dialogue Find File
147
entres de donnes 42
hasAcceptableInput() 32
stocker une chane de filtre
240
text() 66
QLineEdits 355
QList 274
QListView 229, 237, 314
QListWidget 39, 151, 228, 231,
435
glisser-dposer 215
QListWidgetItem 231, 439
QLocale 395
System() 394
qlonglong 490
QMacStyle 130
QMainWindow 4, 78, 144, 157
driver 46
menuBar() 53
statusBar() 56
qmake 5, 21-22, 29, 479
QMatrix 191
qMax() 278
QMenu 53
addAction() 54
addMenu() 54
QMenuBar 4
QMessageBox
about() 70
critical() 59

Default 58
Escape 58
information() 59
question() 59
warning() 58, 70
QMetaObject 25
invokeMethod() 424
QMimeData 213, 217
conversion en TableMimeData 224
data() 219
driver 224
glisser-dposer 219
setData() 219
text() 219
urls() 215
qMin() 194, 278
QModelIndex 237, 240, 245,
253
column() 242
parent() 242
row() 242
QMotifStyle 130
QMouseEvent 170
buttons() 117
QMovie 427
qmPath 398
QMultiMap 274
QMutex 407, 409, 411, 416,
423
QMutexLocker 412, 423
QObject 24, 266, 424, 439
connect() 9, 419, 423
deleteLater() 424
disconnect() 423
event() 170
filtre dvnement 177
findChild() 40
IconEditorPlugin 120
killTimer() 175
mcanisme parent-enfant 31
moveToThread() 419
QProcess 424
QTimer 424

541

sender() 64
setProperty() 451
startTimer() 174
tr() 18, 390
qobject_cast() 64, 218, 224, 510
QPaintDevice 445
QPainter 114, 136, 183
boundingRect() 204
dessiner 184
sur QPrinter 200
draw...() 184
drawLine() 114
fillRect() 116
imprimer 205
programmation embarque
471
rotate() 191
scale() 191
setCompositionMode() 198
shear() 191
systme de coordonnes 188
translate() 191
QPainterPath 186
QPalette 115
ColorGroup 115
foreground() 115
QPen 186
QPixmap 124, 288, 439
fill() 136
QPlastiqueStyle 130
QPluginLoader 438
QPoint 114
QPointer 498
QPointF 138
qPrintable() 289
QPrintDialog 200
choix de limprimante 199
options dimpression 205
setEnabledOptions() 205
QPrinter 200
fromPage() et toPage() 205
newPage() 199
numCopies() 205
setPrintProgram() 199

Qt 4 Livre Page 542 Jeudi, 7. dcembre 2006 12:14 12

542

Qt4 et C++ : Programmation dinterfaces GUI

QProcess 86, 288, 303, 424


execute() 307
start() 305
waitForFinished() 424
QProgressBar 42, 343
QProgressDialog 42, 179-180
QPushButton 40
clicked() 7
effets 3D 187
implmentation 108
rpondre aux actions
utilisateur 6
setText() 39
show() 11
signal clicked() 170
QRadioButton 40
QReadLocker 413, 423
QReadWriteLock 411, 413, 423
QRect 115-117, 132
contains() 117
normalized() 130, 133
QRegExp 104, 266
PatternSyntax 241
QRegExpValidator 31, 67, 107
qRegisterMetaTypeStreamOperators<T>() 285
qRgb() 111
qRgba() 111
QRubberBand 136
QScrollArea 135, 144, 155-156
viewport() 155
widgets constitutifs 155
QScrollBar 82
QSemaphore 407, 411, 413, 423
QSessionManager 463
QSetting 291
QSettings 71-72, 234
QShortcut 172
setContext() 172
QSizePolicy 149
Expanding 126, 149
Fixed 149
Ignored 149
Maximum 149

Minimum 112, 149


MinimumExpanding 149
Preferred 126, 149
QSlider 7, 8
qSort() 264, 277
QSortFilterProxyModel 236, 240
QSpinBox 7, 8, 106, 118, 158
Acceptable 107
Intermediate 107
Invalid 107
textFromValue() 107
valueFromText() 107
QSpinBoxes 176
QSplashScreen 75
QSplitter 78, 144, 152, 159
setSizes() 154
sizes() 269
QSqlDatabase 310-311, 313
QSqlDriver 313
QSqlQuery 310-312, 314
first() 312
last() 312
previous() 312
seek() 312
QSqlQueryModel 236
QSqlRelationalDelegate 323
QSqlRelationalTableModel 236,
310
formulaires 322
QSqlTableModel 236, 310, 314315, 319
QSqlRelationalTableModel
317
qStableSort() 94-95, 278
QStackedLayout 150
QStackedWidget 39, 150-151
QStackLayout 143
QStatusBar 4
addWidget() 57
QString
append() 279
arg() 62, 392

chane
de caractres 507
Unicode 387
comparaison 277
concatnation 279
emploi comme conteneur
278
localeAwareCompare() 395
menu Edit 88
mid() 66
number() 107
partage implicite 272
replace() 221
split() 90, 282
sprintf() 279
toInt() 66, 107
toUpper() 107
type de valeur 266
QStringList 94, 168, 201
arguments de ligne
de commande 330
concatnation 282
fichiers tlcharger 338
recentFiles 63
takeFirst() 297
QStringListModel 237, 240
QStyle 130
PE_FrameFocusRect 130
QStyleOptionFocusRect 130
QStylePainter 130
qSwap() 250, 278
Qt
AlignHCenter 57
BackgroundColorRole 242,
256
CaseInsensitive 17
CaseSensitive 17
classe dlments 82
DiagCrossPattern 187
DisplayRole 231, 242
EditRole 231, 242
FontRole 242, 256
IconRole 231
ItemIsEditable 249

Qt 4 Livre Page 543 Jeudi, 7. dcembre 2006 12:14 12

Index

LeftDockWidgetArea 158
mcanisme des ressources
50
NoBrush 187
SolidPattern 187
StatusTipRole 242
StrongFocus 126
systme de mta-objets 25
TextAlignmentRole 242,
256
TextColorRole 242, 256
ToolTipRole 242
UserRole 231
WA_DeleteOnClose 166
WA_GroupLeader 378
WA_StaticContents 110
WaitCursor 132
WhatsThisRole 242
Qt Designer 26
crer
des widgets personnaliss 108
un formulaire 151
diteur de connexions 37
intgrer des widgets personnaliss 118
Qt Linguist
excution 403
prsentation 403
Qt Quaterly 12
Qt/Embedded 469
Qt/Mac (installer) 479
Qt/Windows (installer) 478
Qt/X11(installer) 479
qt_metacall(), dclaration avec
Q_OBJECT 25
QT_TR_NOOP() 392
QT_TRANSLATE_NOOP() 393
QTableView 93, 229, 314, 320
QTableWidget 78, 228, 232, 401
ajouter le glisser-dposer
219
attributs QTableWidgetItem
79

driver 78
implmentation 108
oprations TCP 343
QHeaderView 82
QScrollBar 82
selectColumn() 91
selectedRanges() 89
selectRow() 91
setCurrentCell() 66
setItem() 84, 232
setItemPrototype() 98
sous classe Spreadsheet 49
Track Editor 257
widgets constitutifs 82
QTableWidgetItem 79, 82, 90,
222, 232, 235
data() 96, 99
driver 96
text() 96
QTableWidgetSelectionRange
222
QTabWidget 39, 41
QtCore 18, 299
QTcpServer 329, 342
QTcpSocket 86, 288, 329, 341,
342
canReadLine() 352
connected() 345, 346
connectionClosedByServer() 349
disconnected() 349
error() 349
listen() 352
readClient() 351
readLine() 352
readyRead() 347
seek() 346
updateTableWidget() 347
write() 346
QTDIR 122
QTemporaryFile 288, 307
QTextBrowser 42, 379
moteur daide 376
QTextCodec 388

543

codecForName() 389
setCodecForCStrings() 389
setCodecForTr() 389, 403
QTextDocument 202
QTextEdit 42, 78, 149, 156, 307
glisser-dposer 214
QTextStream 86, 288, 294, 299,
388
AlignAccountingStyle 296
AlignCenter 296
AlignLeft 296
AlignRight 296
FixedNotation 296
ForcePoint 296
ForceSign 296
internationalisation 388
oprations
TCP 342
XML 370
readAll() 295
readLine() 295
ScientificNotation 296
setRealNumberNotation()
296
SmartNotation 296
UppercaseBase 296
UppercaseDigits 296
QtGui 18
QThread 407
currentThread() 417
exec() 424
run() 408
terminate() 409
wait() 411
QThreadStorage 423
QTime (toString) 395
QTimeEdit 259, 323, 326
Track Editor 260
QTimer 175, 192, 424
sendDatagram() 354
QtNetwork 18
QToolBar 4
QToolBox 41
QToolButton 40, 127, 158

Qt 4 Livre Page 544 Jeudi, 7. dcembre 2006 12:14 12

544

Qt4 et C++ : Programmation dinterfaces GUI

QtOpenGL 18, 207


Qtopia Core 469
formats de police 473
prise en charge de VNC 472
Qtopia PDA 470
Qtopia Phone 470
Qtopia Platform 470
QTranslator 398, 400, 404
load() 394
QTreeView 229, 239, 256
QTreeWidget 39, 147, 149, 158,
228, 233-234, 367
fichier dindex 361
oprations XML 363, 365
QTreeWidgetItem 369, 460
oprations XML 363
QtSql 18
QtSslSocket 341
QtSvg 18
qtTranslator 398, 401
QtXml 18
Quest-ce que cest ? 375
QUdpSocket 86, 288, 329, 353354
writeDatagram() 355
queryInterface() 452, 456
querySubObject() 452
question() (QMessageBox) 59
QUiLaoder 39
quint32 291
quit() (QApplication) 61
quitOnLastWindowClosed 61
qUncompress() 294
QUrl 334
QUrlInfo 336
QVariant 63, 71, 82, 99, 223,
266, 278, 283, 288, 291, 312
double 283
int 283
QBrush 283
QColor 283, 284
QCursor 283
QDateTime 283
QFont 283, 284

QIcon 284
QImage 284
QKeySequence 283
QPalette 283
QPen 283
QPixmap 283, 284
QPoint 283
QRect 283
QRegion 283
QSize 283
QString 283
QVBoxLayout 9, 19, 143, 146,
147
QVector 124, 138
qvfb 471
QWaitCondition 407, 411, 416,
423
wait() 417
QWhatThis, createAction() 376
QWidget 7, 78
changeEvent() 401
close() 19, 61
closeEvent() 47, 61
contextMenuEvent() 55, 67
driver 108
event() 171
find() 444
fontMetrics() 174
geometry() 72
mousePressEvent() 116
move() 72
palette() 115
QGLWidget 207
repaint() 113
resize() 72
scroll() 175
setCursor() 132
setGeometry() 72
setLayout() 9
setMouseTracking() 117
setStyle() 130
setTabOrder() 22
setToolTip() 374
setWindowIcon() 49

setWindowTitle() 8
sizeHint() 20, 38, 57
style() 130
update() 112
updateGeometry() 112
windowModified 62
winID() 444
QWindowsStyle 130
QWindowsXPStyle 130
QWorkspace 78, 144, 159, 167
QWriteLocker 413, 423
QWS_DEPTH 472
QWS_KEYBOARD 471
QWS_MOUSE_PROTO 471
QWS_SIZE 472
qwsEvent() 448
qwsEventFilter() 448
QWSServer 473
qwsServer (variable globale) 473
QXmlContentHandler 362, 365
endElement() 360
startElement() 360
QXmlDeclHandler 360
QXmlDefaultHandler 361-362
errorString() 363
QXmlDTDHandler 360
QXmlEntityResolver 360
QXmlErrorHandler 360, 362,
365
QXmlInputSource 365
QXmlLexicalHandler 360
QXmlSimpleReader 360, 365

R
range.rightColumn() 68
Rational PurifyPlus 490
rawCommand() 333
Ractivit et traitement intensif
178
read() 433
QHttp 342

Qt 4 Livre Page 545 Jeudi, 7. dcembre 2006 12:14 12

Index

readAll() 295
QHttp 342
QIODevice 293
readBitmap() 434
readDatagram() 357
readFile() 87
Spreadsheet 60
readHeaderIfNecessary() 430,
433
readLine() 295, 297, 352
ReadOnly (QIODevice) 87
readRawBytes() 290, 291
readSettings() 49, 70-71, 155,
234, 235
readyRead() 339, 342, 344
recalculate() 92, 93
recentFileActions 52
recentFiles 62, 63
record() 327
rect() (QImage) 117
Rfrence 500
refreshPixmap() 128, 131, 135
refreshTrackViewHeader() 324,
327
regExpChanged() 256
RegExpModel 250, 256
RegExpParser 250, 256
RegExpWindow 250
Registre systme
enregistrement
dun serveur ActiveX
460
du serveur 457
stockage des paramtres 71
regsvr32 457
reinterpret_cast() 511
reject() (QDialog) 31, 36
release() 5, 456
releaseDC() 445
remove() 281, 333
avec itrateur 268
removeAll() 62
removePostedEvents() 423
removeRows() 237, 316

rename() 333
repaint() (QWidget) 113
Rpertoire (parcours) 300
replace() 281
QString 221
Reprsentation binaire des types
86
requestFinished() 342
requestPropertyChange() 455
requestStarted() 342
Rseau
envoi et rception de datagrammes UDP 353
gestion 329
programmer
les application client/
serveur TCP 342
les clients FTP 330
les clients HTTP 339
reserve() 275
reset() (QAbstractItemModel)
246, 250
resize() (QWidget) 72, 127, 131
resizeEvent() 131, 145, 146
resizeGL() 208, 209
resizeImage() 420
ResizeTransaction 422
Ressources (intgration) 302
restore(), matrice de transformation 196
restoreState() (QMainWindow)
159
retranslateUi() 397, 398
retrieveData() 222, 223
right() 280, 283
rightSplitter 154
rmdir() 333
Rle 231, 241
DisplayRole 97
EditRole 97
rollback() 313
rotate() 191, 196
row() (QModelIndex) 242
rowCount() 82, 244

545

rowSpan 148
rubberBandIsShown 132
rubberBandRect 132
run() 422
excution multithread 408,
409, 412

S
Sauvegarde 85
save() 47, 52
Editor 162
fichier 60
matrice de transformation
196
module de rendu 188
oprations XML 370
saveAs() 47, 52, 60-61, 167
saveFile() 60, 165, 167
saveState() 463
QMainWindow 159
SAX (Simple API for XML) 359
SaxHandler 362
scale() 191
Script configure
-help 473
Qtopia 470
scroll() 139
QWidget 175
scrollTo() 239
SDI (Single Document Interface)
75
section 245
Scuriser pour les scripts 456
seek() 294
SELECT 312
Select All (action) 52
selectColumn() 91
selectedId() 230
selectedRange() 89
Selection 224
selectionAsString() 220

Qt 4 Livre Page 546 Jeudi, 7. dcembre 2006 12:14 12

546

Qt4 et C++ : Programmation dinterfaces GUI

selectRow() 91
Smaphore
emploi 414
freeSpace 414
usedSpace 414
sendDatagram() 354
sender() (QObject) 64
sendRequest() 346, 352
Sparateur 152
sessionFileName() 466
sessionId() 467
sessionKey() 467
setAcceptDrops() 221
setAllowedAreas() 158
setAutoDetectUnicode() 388
setAutoFillBackground() 126
setAutoRecalculate() 93
setBackgroundRole() 126, 136
setBit() 434
setBrush() 186
setByteOrder() 289
setChecked() 163
setClipRect() 138
setCodec() 295, 388
setCodecForCStrings() 389
setCodecForTr() 389
setColumnRange() 38, 68
SortDialog 69
setCompositionMode() 198
setContext() (QShortcut) 172,
367
setCurrentCell() 80
QTableWidget 66
setCurrentFile() 58, 60, 62, 165,
167
setCurrentIndex() 150-151
setCurrentRow() 151
setCursor() 132
setCurveData() 129
setData()
champ de base de donnes
315
index de modle 316
QMimeData 219

rimplmentation 247
setDefault() 19
setDirty() 93, 99
setDuration() 193
setEditorData() 258, 260
setEditTriggers() 233
QAbstractItemView 231
setEnabledOptions() (QPrintDialog) 205
setFeatures() (QDockWidget)
157
SetFixedSize (QLayout) 38
setFocus() 165
setFocusPolicy() 126
setFont() 186
setFormat() (QGLWidget) 209
setFormula() 84, 98, 298
setGeometry() (QWidget) 72
setHorizontalHeaderLabels() 232
setHost() 341
setIconImage() 110, 112
setImage() 421
presse-papiers 224
setImagePixel() 116, 117
SetInterfaceSafetyOptions() 456
setItem() 97
QTableWidget 84, 232
setItemPrototype() 81
QTableWidget 98
setLayout() (QWidget) 9
setLayoutDirection() 395
setlocale() 395
setMargin() (QLayout) 147
setMessage() 408, 410
setMimeData() 224
setModal() 66
setModelData() 258
setMouseTracking() (QWidget)
117
setNum() 280
setOutputFormat() 199
setOverrideCursor() 132
setPen() 115, 186
setPenColor() 112

setPixel() (QImage) 117


setPixmap()
presse-papiers 224
QDrag 218
setPlotSettings() 127
setPrintProgram() (QPrinter) 199
setRadius() 455
setRelation() 323, 325
setRenderHint() 186
setRootNode() 252
setSelectionMode() 221
setShortcutContext() (QAction)
172
setShowGrid() 80
setSingleShot() 192
setSizePolicy() 110, 112, 126
setSizes() (QSplitter) 154
setSourceModel() 241
setSpacing() (QLayout) 147
setSpeed() 455
setStatusTip() 374
setStretchFactor() 154
setStyle() (QWidget ) 130
setTabOrder() (QWidget) 22
setText()
code XML 369
presse-papiers 224
QClipboard 88
QLabel 424
QPushButton 39
widget Ticker 174
settings 235
setToolTip() 374
setupUi() 29, 31, 304
setValue() 9, 268, 276
setVisible() 36
setWidget() 155
setWidgetResizable() 156
setWindowIcon() (QWidget) 49
setWindowModified() 166
setWindowTitle() 62
QWidget 8
setZoomFactor() 113
shear() 191

Qt 4 Livre Page 547 Jeudi, 7. dcembre 2006 12:14 12

Index

short 489
Show Grid 52, 71, 75
show() 65, 128, 165
ShowControls 451
showEvent() 173-174
showPage() 379
Signal 7
clicked() 170, 237, 304305, 348
closeEditor() 260
connected() 344-346
connexion aux slots 23, 304
contentsChanged() 166
copyAvailable() 162
currentCellChanged() 57
currentRowChanged() 151
description 22
disconnected() 344, 349,
350
done() 331, 340, 342
editingFinished() 259
error() 344, 349
findNext() 65
findPrevious() 65
finished() 422
itemChanged() 81
listInfo() 335
modified() 80, 85
readyRead() 339, 342, 344,
347, 351
requestFinished() 342
requestStarted() 342
stateChanged() 333
timeout() 192
toggled 36
transactionStarted() 423
triggered() 51, 400
valueChanged() 9
versus vnement 170
windowActivated() 161
signals 7, 17, 23
simplified() 282-283
size() (QFontMetrics) 174
sizeConstraint 38

sizeHint (proprit) 35, 124, 129,


150, 167, 465
QWidget 20, 38, 57, 111
sizeof() 502
sizePolicy 112
sizes() (QSplitter) 269
skipRawData() 431
Slot 7
accept() 36
addRow() 233
allTransactionsDone() 420
close() 19
closeAllWindows() 74
commitAndCloseEditor()
259
connectionClosedByServer()
349
connectToHost 345
connectToServer() 345
connexion aux signaux 23,
304
convertTo1Bit() 420
convertTo3Bit() 420
convertTo8Bit() 420
copy() 88
currentCdChanged() 324
cut() 88, 163
del() 90, 237
description 22
documentWasModified()
166
edit() 306
enableFindButton() 19, 21
error() 349
findClicked() 19, 21
findNext() 91
findPrevious() 92
flipHorizontally() 420
flipVertically() 420
ftpDone() 331
ftpListInfo() 335
help() 379
httpDone() 340
insert() 237

547

newFile() 51, 58, 73, 161


on_browseButton_clicked()
304, 305
on_convertButton_clicked()
304
on_lineEdit_textChanged()
32
on_objectName_signalName
() 304
open() 59
openRecentFile() 64
paste() 90
processPendingDatagrams()
356
recalculate() 92
refreshTrackViewHeader()
327
regExpChanged() 256
reject() 36
resizeImage() 420
save() 60, 162
saveAs() 61
selectAll() 52
sendRequest() 346
setAutoRecalculate() 93
setColumnRange() 38
setFocus() 165
setValue() 9
setVisible() 36
show() 165
somethingChanged() 81, 85
Spreadsheet 65
spreadsheetModified() 57
stopSearch() 348
switchLanguage() 400
updateMenus() 163
updateOutputTextEdit() 306
updateStatusBar() 57
updateTableWidget() 347
updateWindowTitle() 378
zoomIn() 127
zoomOut() 127
Slot() 7, 23
mot cl 17

Qt 4 Livre Page 548 Jeudi, 7. dcembre 2006 12:14 12

548

Qt4 et C++ : Programmation dinterfaces GUI

SmallGap 204
Socket
QTcpSocket 341
QtSslSocket 341
SolidPattern 187
somethingChanged() 81, 85, 9394
sort() 68
MainWindow 69
Spreadsheet 81
SortDialog 68, 69
setColumnRange() 69
Sorties 287
source() (QDragEnterEvent) 218
split() 282
QString 90
Spreadsheet 49, 65
clear() 58
readFile() 60
setFormula() 98
sort() 68, 81
SpreadsheetCompare 68, 81, 94
arbre dhritage 79
spreadsheetModified() 57
sprintf() 279
Square 95
squeeze() 275
start()
QDrag 218
QProcess 305
slot 455
startDrag() 217, 220
startElement() 360, 364
startOrStopThreadA() 410
startOrStopThreadB() 411
startPos 217
startsWith() 281
startTimer() 174
stateChanged() 333
static_cast 510
static_cast<T>() 64, 511
status() 290

statusBar() (QMainWindow) 56
StatusTipRole 242
std 125
STL (Standard Template Library)
63
fichiers den-tte 525
Stockage
de donnes en tant
qulments 82
des caractres ASCII 490
format 288
local de thread 417
XML 359
stop() 408
excution multithread 412
stopped 408, 411
stopSearch() 348
strippedName() 62, 165
strtod() 486
Style
de widget 12, 130
Plastique 12
spcifique la plate-forme
12
style() 130
submitAll() 315
sum() 104
supportsSelection() 225
Surcharge doprateur 512
switchLanguage() 400, 401
Symbole
de prprocesseur 446
de systme de fentre 446
Synchronisation (des threads) 411
System() 394
Systmes embarqus 469

T
Table de hachage 274
Tableau
C++ 502
doctets 278

TableMimeData 224
tagName() 368
takeFirst() (QStringList) 297
Tas 498
tcpSocket 344
TeamLeadersDialog 238
terminate() 409
Tetrahedron 208
text() 27, 83, 137
presse-papiers 224
QLineEdit 66
QTableWidgetItem 96
TextAlignmentRole 99, 242,
245, 256
TextArtDialog 437-438
TextArtInterface 437-438
TextColorRole 242, 256
Texte
criture 294
lecture 294
textFromValue() 107
Thread
communication 418
consommateur 416
cration 408
producteur 416
setMessage() 408
stop() 408
synchronisation 411
utilisation des classes Qt 423
ThreadDialog 410
Ticker 172
tidyFile() 298
tile() 164
timeout() 175, 192
timer 169, 175
timerEvent() 173, 175, 181, 452
title 34
toAscii() 283
toBack() 268
toCsv() 221
toDouble() 100, 280
toElement() 368
toggled() 36

Qt 4 Livre Page 549 Jeudi, 7. dcembre 2006 12:14 12

Index

toHtml() 221
toInt() 280
QString 66, 107
toLatin1() 283, 387
toLongLong() 280
toLower() 281, 283
toolTip() 121
ToolTipRole 242
top() 266
toPage() (QPrinter) 205
toString() 99, 284, 395
toUpper() 281, 283
QString 107
tr()
dclaration avec Q_OBJECT
25
internationalisation 392
traduction de littraux 18
Trac de dessin 187
TrackDelegate 258, 323, 326
transaction() 313, 422
ajout la file dattente 421
ConvertDepthTransaction
422
excutions simultanes 314
FlipTransaction 422
ResizeTransaction 422
SQL 313
transactionStarted() 422, 423
TransactionThread 420
run() 423
translate() 191, 392-393
triggered() 51
trimmed() 282, 283
TripPlanner 344
TripServer 349
incomingConnection() 349
truncate() 272
type() 284
conversions 509
MIME 215
personnalis de glisser 219
primitifs 489

QEvent 170
reprsentation binaire 86
valeur 514
TypeDef 509

U
UDP 329
datagrammes 353
coute du port 355
envoi et rception de datagrammes 353
uic 29, 32, 120
unget-Char() 294
unicode() 386-387
Unit de compilation 484
unlock() 411
unsigned 489
update()
forcer un vnement paint
113
planifier un vnement paint
136
QRect 117
QWidget 112
viewport 93
updateGeometry() 113, 174
QWidget 112
updateGL() 211
updateMenus() 163
updateOutputTextEdit() 306
updateRecentFileActions() 62,
74-75
updateRubberBand() 136
updateRubberBandRegion() 132
updateStatusBar() 57
MainWindow 84
updateTableWidget() 348
updateWindowTitle() 378
UserRole 231
using namespace 486
uuidgen 457

549

V
Valeur 71
bitsPerPixel 431
compression 431
Valgrind 490
Validateur
QDoubleValidator 31
QIntValidator 31
QRegExpValidator 31
value()
champ de base de donnes
312, 314
courbe 138
itrateur STL 276
map 274, 275
valeur
dune cellule 100
dune map 275
valueChanged() 9
valueFromText() 107
values() 274
Variable
globale 516
mutable 97
sur la pile 67
Variable denvironnement
LD_LIBRARY_PATH 481
PATH 5, 380
QTDIR 122
QWS_KEYBOARD 471
QWS_MOUSE_PROTO
471
Variant 278
Vecteur
dlments 264
de coordonnes 122
de QChar 279
distances 248
initialisation 277
parcourir 265
Version de systme dexploitation
446

Qt 4 Livre Page 550 Jeudi, 7. dcembre 2006 12:14 12

550

Qt4 et C++ : Programmation dinterfaces GUI

viewport() 155
dessin 188
OpenGL 209
systme de coordonnes 189
transformation du painter
194
update() 93
widget 82
virtual 494
VNC (Virtual Network
Computing) 472
void* 511
Vue
QListView 229
QTableView 229
QTreeView 229

W
WA_DeleteOnClose 74, 162,
166
WA_GroupLeader 378
WA_StaticContents 117
wait() 411, 417
waitForDisconnected() 424
waitForFinished() 424
warning() (QMessageBox) 58,
70
WeatherBalloon 354
whatsThis() 121
WhatsThisRole 242
wheel 135
wheelEvent() 135
while 179
Widget
ancrable 157

bouton 40
QCheckBox 40
QPushButton 40
QRadioButton 40
QToolButton 40
central 78
classes 40
compagnon 18
conteneur 41
multipage 41
QFrame 41
QTabWidget 41
QToolBox 41
daffichage dlments 41
dentre 43
dfinition 4
disposer 7, 28, 144
HelpBrowser 377
OvenTimer 191, 194
palette de couleurs 115
personnalis 106
crer 105
et Qt Designer 108
intgrer avec
le Qt Designer 118
Plotter 122
style 12
viewport 82
windowActivated() 161
windowModified 57, 62
Windows
ActiveX 448
identifier la version 446
installer Qt 478
Media Player 449
windowTitle 27
winEvent() 448
winEventFilter() 448

winId() 444
write() 429
QIODevice 293
writeDatagram() 355
writeFile() 85
WriteOnly (QIODevice) 87
writeRawBytes() 290
writeSettings() 70, 71, 154

X
X Render 197
X11
gestion de session 461
installer Qt 479
x11Event() 448
x11EventFilter() 446
Xcode Tools 479
XML 359
criture 370
lecture
avec DOM 365
avec SAX 360
xsm 467

Z
Zone
daction 188
droulante 155
zoomFactor() 109, 113
zoomIn() 127, 134
zoomOut() 127
zoomStack 127

Chez le mme diteur

ISBN : 2-7440-2086-9 Parution : 07/06 Prix : 52

ISBN : 2-7440-2127-X Parution : 12/06 Prix : 49,90

Tous nos ouvrages sont disponibles sur www.pearsoneducation.fr

CampusPress Rfrence

Qt 4

et

C++

Programmation dinterfaces GUI


Un ouvrage unique sur le dveloppement dinterfaces
graphiques avec la bibliohque Qt, crit par des spcialistes de Trolltech.
Grce au framework Qt de Trolltech, vous pouvez crer des
applications C++ de niveau professionnel qui sexcutent
en natif sous Windows, Linux/UNIX, Mac OS 10 et Linux
intgr sans quaucune modification dans le code source
soit ncessaire.
Ce guide complet vous permettra dobtenir des rsultats
fantastiques avec la version la plus puissante de QT jamais
cre : QT 4.1. En sappuyant sur des exemples ralistes,
il prsente des techniques avances sur divers sujets depuis
le dveloppement de linterface graphique de base lintgration avance de XML et des bases de donnes.
Couvre l'ensemble des lments fondamentaux de Qt,
depuis les botes de dialogue et les fentres jusqu' l'implmentation de la fonctionnalit d'une application
Prsente des techniques avances que vous ne retrouverez dans aucun autre ouvrage, comme la cration de
plugins d'application et pour Qt, ou la cration d'interfaces avec les API natives
Contient des annexes dtailles sur la programmation
C++/Qt destine aux dveloppeurs Java expriments
A propos des auteurs
Jasmin Blanchette, responsable de la documentation chez Trolltech et dveloppeur
expriment travaille pour cette socit depuis 2001. Il intervient comme diteur de
Qt Quaterly, le bulletin dinformation technique de Trolltech.

TABLE DES MATIRES

Partie I : Qt : notions de base


Pour dbuter
Crer des botes de dialogue
Crer des fentres principales
Implmenter la fonctionnalit
dapplication
Crer des widgets
personnaliss
Partie II : Qt : niveau intermdiaire
Gestion des dispositions
Traitement des vnements
Graphiques 2D et 3D
Glisser-dposer
Classes daffichage dlments
Classes conteneur
Entres/sorties
Les bases de donnes
Gestion de rseau
XML
Aide en ligne
Partie III : Qt : niveau avanc
Internationalisation
Environnement multithread
Crer des plug-in
Fonctionnalits spcifiques la
plate-forme
Programmation embarque
Annexes
Installer Qt
Introduction au langage C++
pour les programmeurs Java
et C#

Mark Summerfield travaille comme consultant et formateur spcialis en C++, Qt


et Python. Il a assum la charge de responsable de la documentation chez Trolltech
pendant presque trois ans.

Niveau : Intermdiaire / Avanc


Programmation Configuration : Multiplate-forme

Pearson Education France


47 bis, rue des Vinaigriers
75010 Paris
Tl. : 01 72 74 90 00
Fax : 01 42 05 22 17
www.pearson.fr

ISBN : 978-2-7440-4092-4
Officially Approved
by Trolltech

Vous aimerez peut-être aussi