Cours Wxwidgets - 1ère Partie
Cours Wxwidgets - 1ère Partie
Cours Wxwidgets - 1ère Partie
Introduction
Vous vous apprêtez à suivre un tutoriel sur l'utilisation de la bibliothèque wxWidgets (http://www.wxwidgets.org).
Il convient donc avant tout d’en faire une brève présentation.
wxWidgets (au départ, connue sous le nom wxWindows) est une bibliothèque de développement multi-plateforme.
Elle est disponible pour Windows, Linux, Mac, et bien d'autres systèmes. Cela a l'avantage de réduire les temps de
programmation pour permettre à une application de pouvoir tourner sur tous ces systèmes d'exploitation.
Il est possible de l'utiliser en passant, bien sûr, par le C++, mais également Python, Perl, C#, ...
De plus, elle contient un grand nombre de classes simplifiant l'utilisation de certains contrôles.
Pour ce qui est de l'interface graphique, wxWidgets utilise, pour chaque plateforme, les API natives. Ce qui fait
qu'une application aura, sous Windows, le look Windows, et sous Linux, le look Linux.
Pour plus d'informations sur ce point, je vous invite à vous rendre sur le site officiel, où l'on peut trouver un bon
nombre de captures d'écran d'applications tournant sous plusieurs systèmes d'exploitation.
Pour ce tutoriel, nous allons utiliser l'IDE Code::Blocks, qui d'ailleurs est basé sur wxWidgets, et qui de ce fait a
l'avantage d'être disponible pour les principaux systèmes d'exploitations (Windows, Linux, Mac).
Il vous faut donc l'installer, ainsi que les fichiers de développement de wxWidgets.
Si le besoin s’en fait sentir, j’étofferai ce tutoriel afin d’expliquer la marche à suivre pour ces installations, mais ce
n’en n’est pas le but premier, alors, nous verrons cela plus tard.
#ifndef MAINFRAME_H_INCLUDED
#define MAINFRAME_H_INCLUDED
Il n'y a rien de particulier à dire sur ce fichier. Passons maintenant au contenu du fichier mainframe.cpp :
// Le constructeur
MainFrame::MainFrame() : wxFrame(NULL,wxID_ANY,_T("FileFinder"))
{
// Nous ajouterons ici la création des contrôles que contiendra notre fenêtre
}
// Le Destructeur
MainFrame::~MainFrame()
{
// Nous ajouterons ici les éventuelles libérations de mémoire nécessaires
// avant la fermeture de la fenêtre
}
Ici non plus, rien de particulier, si ce n'est l'appel au constructeur de wxFrame.
Il contient 3 paramètres :
• NULL : correspond au pointeur vers la fenêtre parente (comme il s'agit de la fenêtre principale de notre
application, elle n'en a pas).
• wxID_ANY : l'identifiant de notre fenêtre : dans notre cas, on laisse wxWidgets choisir la valeur. Nous
verrons plus tard que ce n'est pas toujours le cas.
• _T("FileFinder') : il s'agit du texte qui apparaîtra dans la barre de titre de notre fenêtre. La macro wxT("...")
permet de ne pas avoir à tenir compte du fait que notre application sera compilée en Ansi ou en Unicode.
Nous allons maintenant nous occuper de la classe FileFinderApp, dont je vous ai parlé un peu plus haut.
Créez d'abord les deux fichiers (vides) qui vont recevoir le code de cette classe : filefinderapp.h et
filefinderapp.cpp.
Pour le fichier filefinderapp.h, le code est :
#ifndef FILEFINDERAPP_H_INCLUDED
#define FILEFINDERAPP_H_INCLUDED
DECLARE_APP(FileFinderApp);
#endif // FILEFINDERAPP_H_INCLUDED
Vous noterez l'ajout de la macro DECLARE_APP(FileFinderApp) qui permet à wxWidgets de créer une fonction
wxGetApp(). Cette fonction nous permettra plus tard d'obtenir une référence vers notre classe d'application.
Elle n'est pas obligatoire, mais peut être utile dans bien des cas.
// La méthode "OnInit()"
bool FileFinderApp::OnInit()
{
// On crée un objet "MainFrame"
MainFrame *frame=new MainFrame();
// On l'affiche
frame->Show();
// On indique que l'application peut continuer
return true;
}
IMPLEMENT_APP(FileFinderApp);
Encore une fois, je pense que le code est suffisamment commenté. Il ne reste qu'une petite chose à expliquer : la
macro IMPLEMENT_APP(FileFinderApp).
En fait, cette macro sert à indiquer à wxWidgets que la classe FileFinderApp est la classe principale de
l'application, et que c'est en fonction de la valeur de retour de sa méthode OnInit() qu'il faudra ou non continuer à faire
tourner l'exécutable.
Voilà, nous avons la base de notre projet. Nous allons maintenant pouvoir compiler tout ça, et l'exécuter.
Normalement, si vous n'avez pas fait d'erreur lors de la recopie du code source, vous devriez obtenir une fenêtre
comme celle-ci :
J'en conviens, elle n'est pas très belle, mais c'est déjà un bon début.
Nous allons maintenant lui affecter une icône. Comme certains d'entre vous sont sous Linux, nous n'allons pas
utiliser un fichier ".ico", mais une image X-PixMap (http://fr.wikipedia.org/wiki/XPM).
Vous pouvez télécharger celle que nous allons utiliser à l'aide du lien suivant :
http://x.psoud.free.fr/localfiles/articles/tutowxwidgets1/wxwidgets16x16.xpm . Enregistrez le fichier dans le répertoire
du projet.
Pour utiliser un fichier XPM tel que celui-ci, il suffit de l'inclure dans la code source avec la directive #include.
Ensuite, dans le constructeur de la fenêtre, nous allons affecter cette icône à la fenêtre avec la méthode
SetIcon(......). Notre fichier mainframe.cpp devient donc :
// Le constructeur
MainFrame::MainFrame() : wxFrame(NULL,wxID_ANY,_T("FileFinder"))
{
// On affecte l'icône à la fenêtre
SetIcon(wxwidgets16x16_xpm);
// Nous ajouterons ici la création des contrôles que contiendra notre fenêtre
}
// Le Destructeur
MainFrame::~MainFrame()
{
// Nous ajouterons ici les éventuelles libérations de mémoire nécessaires
// avant la fermeture de la fenêtre
}
Voilà, c'est tout : vous pouvez compiler et exécuter, vous devriez obtenir la même fenêtre que précédemment, mais
avec une icône personnalisée :
Chapitre 2 : La mise en place des contrôles
Maintenant que nous possédons une fenêtre, nous allons pouvoir y ajouter des contrôles, afin de la rendre un peu
plus utile.
Pour cela, nous allons avoir besoin d'une partie un peu obscure de wxWidgets pour qui n'y est pas habitué : les
wxSizer, et leurs classes dérivées.
Mais tout d'abord, qu'est-ce qu'un sizer ?
Il s'agit en fait d'un contrôle "virtuel" servant à la mise en place et au redimensionnement automatique des autres
contrôles.
Par exemple, sans utiliser les sizers, si je veux placer sur ma fenêtre un label (un wxStaticText]), et sur la même
ligne, une zone de texte (un wxTextCtrl) qui occupera le reste de la fenêtre en largeur, avec un espace de 5 pixels entre
les deux, il va falloir que je procède de la façon suivante :
• Récupérer la largeur de la fenêtre (de sa zone cliente, en fait).
• Créer le contrôle wxStaticText à la position x0/y0.
• Mesurer sa largeur et y ajouter 5 pixels pour obtenir la position du contrôle suivant.
• Créer le contrôle wxTextCtrl à la bonne position, et de la bonne taille.
• Intercepter l'événement "OnSize" de la fenêtre.
• Refaire les calculs et repositionner les contrôles à chaque redimensionnement de la fenêtre.
Avec les sizers, tout cela se fait automatiquement :
• On crée le sizer en lui indiquant dans quel sens il faudra qu'il intervienne sur les contrôles (horizontal ou
vertical). Ainsi, pour l’exemple ci-dessus, il s’agirait d’un sizer horizontal.
• Créer le wxStaticText sans se soucier de sa taille et de sa position), et l’ajouter au sizer en lui indiquant que ce
contrôle a une taille fixe, et qu'il ne doit pas être redimensionné.
• Créer le wxTextCtrl et l’ajouter au sizer en indiquant qu'il doit être redimensionné pour occuper le reste de la
largeur de la fenêtre.
• Il est également possible de donner une valeur aux « marges » à laisser autour de chaque contrôle
• Il suffit d'affecter le sizer à la fenêtre, et c'est lui qui va se charger de gérer le redimensionnement.
Et si l'on a plusieurs lignes à gérer, il suffit de créer plusieurs sizers horizontaux, et de les ajouter à un sizer vertical.
De plus, il existe plusieurs sortes de sizers qui permettent d'obtenir des résultats différents :
• wxBoxSizer : c'est le sizer classique (en fait, la classe wxSizer n'est pas utilisable telle-quelle, elle sert
uniquement de base aux autres). Ce sizer crée en quelque sorte une « boite » virtuelle dans laquelle seront
placés et organisés les contrôles en fonctions des paramètres passés lors de leur ajout. C'est le premier sizer
que nous utiliserons, pour mettre en place correctement les parties gauche (critères de recherche) et droite
(résultats de la recherche) de notre interface
• wxStaticBoxSizer : ce sizer a un comportement identique au précédent, mais il est associé à un contrôle
« cadre » (wxStaticBox). C'est ce sizer que nous utiliserons pour organiser les contrôles à l'intérieur des deux
zones (gauche et droite) de notre interface.
• wxGridSizer : comme son nom l'indique, il permet d'organiser les contrôles sur une grille virtuelle, dont les
« cases » auront toutes la même taille.
• wxFlexGridSizer : comme le précédent, il va organiser les contrôles sur une grille, mais en offrant la
possibilité d'avoir des lignes et des colonnes de taille différentes.
• wxGridBagSizer : il s'agit d'un wxFlexGridSizer offrant la possibilité de placer un contrôle « à cheval » sur
plusieurs cellules, ou à une position donnée (par exemple, dans la 3ème cellule de la 2ème ligne, même si les
cellules précédentes ne sont pas encore affectées).
Comme je viens de vous le dire, nous n'utiliserons que les deux premiers types de sizer. Nous allons maintenant en
voir le fonctionnement.
Le wxBoxSizer
Lors de sa construction, on lui donne en paramètre la direction dans laquelle ce sizer doit agir. Deux constantes
wxWidgets sont disponibles pour cela : wxHORIZONTAL et wxVERTICAL (je suppose que vous avez deviné leur
signification).
Donc, si on veut créer un wxBoxSizer horizontal, il suffit d'écrire :
Le wxStaticBoxSizer
Son fonctionnement est identique à celui du wxBoxSizer que l'on vient de voir, mais il permet d'afficher en plus une
wxStaticBox, c'est à dire, un cadre avec une légende en haut à gauche.
Il suffit de lui ajouter un pointeur vers le contrôle parent de la wxStaticBox, et un wxString contenant le texte de
légende à afficher, et le tour est joué :
Voilà pour ce qui est des explications de base sur les sizers. Nous allons maintenant voir comment nous allons les
utiliser pour notre interface.
Il va d'abord falloir que l'on gère les deux zones « gauche » et « droite ». Nous allons donc créer un premier
wxBoxSizer horizontal.
Pour chacune de ces zones, nous utiliserons un wxStaticBoxSizer :
• pour la partie de gauche, ce sizer va devoir gérer plusieurs lignes de contrôles; il sera donc vertical. Ensuite,
pour chaque ligne, il faudra ensuite ajouter un wxBoxSizer horizontal dès qu'il y aura plus d'un contrôle sur la
ligne concernée.
• pour la partie de droite, le sizer n'ayant qu'un seul contrôle à gérer (la liste des résultats), il pourra être
indifféremment horizontal ou vertical.
Nous allons maintenant dresser la liste des contrôles dont nous aurons besoin, afin de déterminer quelles variables
nous allons devoir créer pour leur utilisation:
Commençons par la zone de gauche :
• Nous n'aurons pas besoin de modifier le texte de la wxStaticBox principale. Il est donc inutile de créer une
variable pour cela.
• De même pour le label « Nom du (des) fichier(s) : » (un wxStaticText).
• Par contre, pour la zone de texte servant à recevoir le nom ou les jockers de recherche (un wxTextCtrl), nous
allons avoir besoin d'un pointeur qui nous permettra d'en récupérer la valeur.
• Le label « Rechercher dans : » est logé à la même enseigne que le précédent : nous n'aurons pas besoin de le
modifier.
• Nous aurons besoin d'un pointeur vers la zone de texte servant à la saisie du dossier de démarrage, pour en
récupérer le contenu, ou pour le modifier.
• Nous aurons également besoin d'un « identifiant » pour le bouton « parcourir » situé à droite de cette dernière
zone de texte, ainsi que d'un pointeur nous permettant de l'activer ou de le désactiver. Cet identifiant est en fait
une valeur entière unique (que nous demanderons à wxWidgets de créer pour nous) et qui, comme son nom le
laisse supposer, sert à identifier un contrôle, et à le connecter à la table des événements (nous verrons cela plus
loin, rassurez-vous).
• Comme pour le bouton précédent, il nous faudra un identifiant et un pointeur pour le bouton « Démarrer ».
Pour la zone de droite :
• Il serait souhaitable de garder un pointeur vers la wxStaticBox principale, pour pouvoir afficher le nombre de
résultats trouvés.
• Il nous faut également un pointeur pour le contrôle wxListCtrl servant à l'affichage des résultats.
Nous allons, dans un premier temps, ajouter les variables et les consantes (pour les identifiants des boutons) à notre
classe MainFrame (fichier mainframe.h):
#ifndef MAINFRAME_H_INCLUDED
#define MAINFRAME_H_INCLUDED
#endif // MAINFRAME_H_INCLUDED
Vous remarquerez, au passage, qu'il a fallu inclure un header spécifique pour le wxListCtrl, car il ne fait pas partie
des contrôles de base de wxWidgets.
Nous allons passer à la création des contrôles. Tout se fait dans le constructeur de la fenêtre principale.
Il va également falloir que l'on ajoute la définition des deux constantes relatives aux boutons (les identifiants).
Comme je vous l'ai dit précédemment, ces constantes doivent avoir une valeur unique. En effet, si deux boutons ont le
même identifiant, on ne pourra pas savoir sur lequel l'utilisateur a cliqué.
Pour cette valeur unique, il est possible d'utiliser des valeurs prédéfinies par wxWidgets
(http://www.wxwidgets.org/manuals/stable/wx_eventhandlingoverview.html#windowids ) pour les actions les plus
courantes telles que « ouvir », « fermer », « aide »,....... Nous pouvons également définir ces valeurs nous même, en
prenant garde de ne pas utiliser une valeur présente dans la liste dont je viens de vous donner le lien. La dernière
possibilité est de demander à wxWidgets de nous créer une nouvelle valeur unique, par l'intermédiaire de la fonction
wxNewId().
Voici donc ce qu'il suffira de mettre avant le constructeur de la fenêtre principale :
Maintenant, avant la création des contrôles, il nous reste un petit détail à régler. Je ne sais pas si vous avez
remarqué, mais le fond de la fenêtre est d'une couleur lamentable. Nous allons remédier à cela en modifiant cette
couleur. Nous allons lui affecter une couleur « système » qui est la couleur par défaut pour la « face des boutons », et
dont nous pouvons récupérer la valeur grâce à wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE) :
Ensuite, vient le premier wxStaticBoxSizer (vertical) qui va contenir les contrôles pour les critères de recherche :
Puis le premier contrôle de la partie de gauche : un wxStaticText. Normalement, lors de la création de contrôles
avec wxWidgets, il faut donner un identifiant à chaque fois. Cela peut vite devenir pénible, d'autant plus que l’on n’en
a pas tout le temps besoin, comme maintenant.
Il existe pour cela un identifiant « passe partout » dont la constante est wxID_ANY (et dont la valeur est -1) pour
indiquer à wxWidgets de se débrouiller tout seul :
Et on l'ajoute au sizer vertical. Comme ce contrôle n'a pas besoin d'être redimensionné, on lui met la proportion « 0 ».
On va mettre une marge de 5 pixels partout, sauf en dessous (il sera collé au contrôle suivant). Ce qui donne :
// On l'ajoute au wxStaticBoxSizer
left_vsizer->Add(label,0,wxLEFT|wxRIGHT|wxTOP,5);
Maintenant, c'est au tour du premier wxTextCtrl, dont on a déclaré le pointeur comme variable membre privée de la
classe MainFrame (son nom est txtName) :
Et on l'ajoute au sizer, sachant que cette fois-ci, il devra être étendu pour tenir sur toute la largeur (flag
wxEXPAND), avoir une marge à gauche, à droite, et en bas (pas en haut, pour être collé au contrôle précédent), et ne
pas être redimensionné en hauteur (proportion « 0 ») :
// On l'ajoute au sizer
left_vsizer->Add(txtName,0,wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND,5);
On répète l'opération pour le deuxième wxStaticText, sachant que l'on peut réutiliser la variable du premier, qu'il ne
faut pas lui mettre de marge supérieure, car le contrôle précédent en a déjà une inférieure :
Bien, maintenant, on va corser un peu tout ça. En effet, pour la ligne suivante, nous allons devoir ajouter un
wxBoxSizer horizontal, pour placer sur la même ligne un wxTextCtrl et un wxButton :
Maintenant, on peut créer le wxTextCtrl et l'ajouter à ce dernier sizer (pas de marge car on en mettra une au sizer
plus tard, proportion = « 1 » car redimensionnable dans le sens du sizer) :
// Le bouton "Parcourir"
btnBrowse=new wxButton(this,ID_BTN_BRWSE,_T(". . ."),wxDefaultPosition,wxSize(30,-1));
// On l'ajoute au sizer horizontal
h_sizer1->Add(btnBrowse,0,wxLEFT,5);
Voilà, notre sizer horizontal est prêt. Nous pouvons l'ajouter au sizer vertical comme un contrôle normal. Il sera
non redimensionnable verticalement (proportion "0"), extensible horizontalement (flag wxEXPAND), et n'aura pas de
marges supérieure pour être collé au wxStaticText:
Il nous reste maintenant à créer le bouton "Démarrer", et à l'ajouter au sizer vertical, en lui donnant le flag
wxALIGN_RIGHT pour qu'il soit placé à droite de la zone (ça fait plus joli, je trouve):
// Le bouton "Démarrer"
btnStart=new wxButton(this,ID_BTN_START,_T("Démarrer"));
// On l'ajoute au sizer vertical
left_vsizer->Add(btnStart,0,wxALL|wxALIGN_RIGHT,5);
// On crée le wxListCtrl
lstResults=new wxListCtrl(this,wxID_ANY,wxDefaultPosition,wxDefaultSize,wxLC_REPORT);
// On ajoute les colonnes
lstResults->InsertColumn(0,_T("Nom"),wxLIST_FORMAT_LEFT);
lstResults->InsertColumn(1,_T("Type"),wxLIST_FORMAT_CENTER);
lstResults->InsertColumn(2,_T("Emplacement"),wxLIST_FORMAT_LEFT);
// On ajoute le wxListCtrl au wxStaticBoxSizer
right_vsizer->Add(lstResults,1,wxALL|wxEXPAND,5);
La zone de droite est maintenant terminée. On peut ajouter le sizer vertical au sizer principal :
Voilà, c'est terminé. Notre interface est prête. Vous pouvez compiler et exécuter, pour voir ce que ça donne :
Allez, puisque vous avez été très attentifs, je vais vous donner un petit bonus (enfin, aux utilisateurs de Windows).
Vous avez sans-doute remarqué deux choses :
• Premièrement, l'exécutable qui a été généré ne possède pas d'icône.
• Deuxièmement, les contrôles ne prennent pas en compte le thème de Windows.
Nous allons remédier à cela par l'intermédiaire d'un fichier « ressources ».
En effet, sous Windows, il est possible d'ajouter de nombreuses données dans l'exécutable (images, sons, ...).
Pour cela, nous allons d'abord créer le fichier ressources.
Sous Code::Blocks, créez un nouveau fichier vide (menu File → New → Empty file), répondez « Oui » lorsqu'on
vous demande si vous voulez ajouter ce fichier au projet courant, et enregistrez-le sous le nom « resources.rc ».
Nous allons tout d'abord ajouter l'icône de l'exécutable. Vous pouvez utiliser n'importe quel fichier « .ico » valide.
De mon côté, je vais utiliser le fichier icône « standard » wxWidgets. Il se trouve normalement dans l'arborescence
de votre installation des libs wxWidgets, dans le dossier « include\wx\msw » et se nomme « std.ico ».
Nous allons donc indiquer au compilateur qu'il doit l'intégrer à l'exécutable.
Dans le fichier « resources.rc » que vous venez de créer, ajouter la ligne :
A noter que le nom « appIcon » n'a pas été donné au hasard. Windows classe les icônes par ordre alphabétique, et
prend le premier pour l'affichage dans l'explorateur. Avec un tel nom, il y a de fortes chances que l'icône « std.ico »
soit placée en tête de liste, et soit utilisée pour représenter l’exécutable dans l’explorateur Windows.
Nous allons également intégrer le fichier Manifest à cet exécutable, pour que les contrôles aient le look Windows.
Ce fichier manifest se trouve normalement au même endroit que l'icône utilisée précédemment.
Ajoutez la ligne suivante au fichier « resources.rc » :
1 24 "wx/msw/wx.manifest"
Le « 1 » correspond à l'identifiant du « Manifest », et le « 24 » à son type. Ces deux valeurs doivent toujours être
celles-ci pour un fichier manifest.
Vous pouvez maintenant compiler. Votre fichier « FileFinder.exe » doit maintenant être doté d'une icône identique
à celle que nous avons affectée à la fenêtre. De plus, si vous exécutez une dernière fois l'application, les contrôles ont
maintenant le « look Windows » (enfin, pour ceux qui sont sous Windows XP ou Vista).
Chapitre 3 : La mise en place des méthodes événementielles
Nous avons créé une belle fenêtre, mais elle ne fait pour l'instant pas grand chose.
Il faut que l'on indique à notre application de réagir en fonction des actions de l'utilisateur:
• Lorsqu'il clique sur le bouton « Parcourir », il faut ouvrir une boite de dialogue de sélection de répertoire pour
lui permettre de sélectionner le dossier dans lequel va débuter la recherche.
• Lorsqu'il clique sur le bouton « Démarrer », il faut mettre en route la recherche des fichiers avec les critères
qu'il a fourni.
Nous allons donc dans un premier temps modifier notre classe MainFrame pour lui ajouter les méthodes qui vont
être appelées lors des clics sur les boutons « Parcourir » et « Démarrer ».
Ensuite, nous allons connecter ces méthodes à la table des événements, de façon à indiquer à l'OS où se trouve le
code à exécuter lorsque l'utilisateur clique sur un bouton. Comme nous avons deux événements à gérer, nous allons
pouvoir mettre en place deux méthodes différentes pour cette « connexion »
Nous allons donc ajouter deux méthodes à la classe MainFrame.
La première, que nous appellerons « OnButtonBrowseClicked » sera appelée chaque fois que l'utilisateur cliquera
sur le bouton « Parcourir ».
La deuxième, que nous appellerons « OnButtonStartClicked » sera appelée lors du clic sur le bouton « Démarrer »
Nous ne nous occuperons pas pour l'instant du contenu de ces méthodes. Nous y placerons simplement un appel à
la fonction wxMessageBox(...) afin de vérifier qu'elles sont bien appelées.
Si l'on jette un coup d'œil à la documentation officielle concernant le wxButton
(http://www.wxwidgets.org/manuals/stable/wx_wxbutton.html), à la rubrique « Event handling » (gestion des
événements), il est dit que ce contrôle réagit à un « wxEVT_COMMAND_BUTTON_CLICKED ». Le début de cette
constante nous permet de savoir que les méthodes que nous allons avoir à créer devront posséder un paramètre de type
wxCommandEvent, afin que l'on puisse récupérer quelques informations sur le contrôle qui a généré cet événement.
Ce type d'événement est celui que vous aurez le plus souvent à utiliser dans vos programmes wxWidgets (pour les
menus, les contrôles basiques tels que les boutons radio, les cases à cocher, ...).
Il y a malgré tout plusieurs types d'événements, et tous n'utilisent pas le wxCommandEvent. Ainsi, le wxListCtrl
utilise un wxListEvent, qui contiendra des informations spécifiques au wxListCtrl.
Pour plus d'infos sur la gestion des événements sous wxWidgets, je vous recommande vivement de lire l’article qui
y est consacré dans la documentation officielle (il est malheureusement en anglais, mais il permet d’en comprendre les
grands principes) : http://www.wxwidgets.org/manuals/stable/wx_eventhandlingoverview.html.
Une dernière petite chose : chaque événement est transmis à la méthode qui lui est associée par sa référence.
Pour ajouter ces deux méthodes à la classe MainFrame, il suffit d'ajouter leur déclaration dans le header cette
classe, et leur définition dans le fichier source, mais ça, vous le saviez déjà, en tant que bon programmeur.C++.
Ce qui donne, pour le fichier mainframe.h :
#ifndef MAINFRAME_H_INCLUDED
#define MAINFRAME_H_INCLUDED
Et, pour le fichier mainframe.cpp (je ne mets ici que les lignes à ajouter à la fin du fichier, pour ne pas surcharger le
cours) :
C'est tout. Vous pouvez compiler si ça vous chante, mais de toute façon, il ne va rien se passer, car nous n'avons
pas connecté les clics sur les boutons à leur méthodes respectives.
C'est ce que nous allons voir maintenant.
Pour que notre fenêtre puisse rediriger les événements vers les méthodes que l'on vient de créer, il faut
impérativement déclarer (dans le header) et définir (dans le fichier cpp) une « table des événements ».
• Pour la déclaration, il suffit d'ajouter la macro DECLARE_EVENT_TABLE() dans le header de notre classe
MainFrame (fichier mainframe.h).
#ifndef MAINFRAME_H_INCLUDED
#define MAINFRAME_H_INCLUDED
• Pour la définition, il faut utiliser deux macros : une première pour marquer le « début » de la table des
événements (BEGIN_EVENT_TABLE(...,...)), et qui prend en paramètres le nom de notre classe, ainsi que
celui de celle dont elle dérive, ainsi qu'une autre pour « fermer » cette table (END_EVENT_TABLE).
Comme je vous l'ai dit précédemment, nous allons utiliser deux techniques différentes pour « connecter » les
boutons aux méthodes de la fenêtre.
• La première consiste à ajouter une macro dans la définition de la table des événements. Cette macro, par son
nom, définira le type d'événement à gérer (clic sur un bouton dans notre cas), et prendra en paramètre
l'identifiant du bouton (que nous avons défini dans le chapitre 2), et le nom de la méthode à associer.
• La deuxième consistera à connecter le bouton au moment de sa création, par l'intermédiaire de la méthode
wxEventHandler::Connect(...) (notre fenêtre est dérivée de wxFrame, dérivée de wxTopLevelWindow, dérivée
de wxWindow, dérivée de wxEventHandler).
Nous allons donc utiliser la première solution (qui est d'ailleurs la plus souvent utilisée), pour connecter le bouton
« parcourir » à la méthode MainFrame::OnButtonBrowseClicked.
Nous voulons gérer l'événement « clic sur un bouton ». En regardant la documentation officielle, on peut voir que
cela se fait avec la macro EVT_BUTTON(identifiant,méthode_associée).
Voici ce que ça donne (fichier mainframe.cpp):
// Le constructeur
MainFrame::MainFrame() : wxFrame(NULL,wxID_ANY,_T("FileFinder"))
{
// .........
}
Vous pouvez désormais compiler, et tester le bouton parcourir : vous devriez obtenir un petit message vous
indiquant que l'appel de la méthode MainFrame::OnButtonBrowseClicked s'est bien effectué correctement :
// Le bouton "Démarrer"
btnStart=new wxButton(this,ID_BTN_START,_T("Démarrer"));
Connect(ID_BTN_START,wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MainFrame::OnButtonStartClicked));
// On l'ajoute au sizer vertical
left_vsizer->Add(btnStart,0,wxALL|wxALIGN_RIGHT,5);
Et voilà, c'est tout aussi simple (et aussi efficace) que la première solution.
L'avantage de celle-ci, c'est que l'on peut regrouper la création du contrôle, et sa connexion à une méthode.
Un autre avantage (et non des moindres) est que la connexion peut se faire en cours d'exécution du programme, ce
que ne permet pas la technique précédente.
Il est également possible de « déconnecter » un contrôle d'une méthode grâce à la classe wxEventHandler dont est
dérivée notre fenêtre.
Il n'empêche que même si vous décidez de n'utiliser que cette solution, vous devez quand même faire la déclaration
et la définition de la table des événements (qui dans ce cas sera vide).
Voilà, nous avons réussi à connecter nos deux boutons à leurs méthodes respectives.
Il ne nous reste plus qu'à placer le bon code dans ces méthodes, et notre petit utilitaire sera fonctionnel.
Cette sélection de dossier se fait par l'intermédiaire de la classe wxDirDialog qui a été créée spécialement pour cela
(http://www.wxwidgets.org/manuals/stable/wx_wxdirdialog.html).
Si l'on jette un coup d'œil à la documentation officielle concernant le constructeur de cette classe, on peut voir qu'il
lui faut comme paramètres :
• Un pointeur vers la fenêtre parente.
• Le message à afficher dans la boite de dialogue de sélection.
• Le répertoire présélectionné.
• Le style de cette boite (nous indiquerons le style wxDD_DIR_MUST_EXIST).
• Pour les autres paramètres, nous laisserons les valeurs par défaut.
Après avoir affiché cette boîte de dialogue, et si l'utilisateur n'a pas cliqué sur le bouton « Annuler » qu'elle
contient, il nous suffira de récupérer le chemin complet du répertoire sélectionné, et de l'afficher dans le wxTextCtrl
correspondant (dont nous avons stocké le pointeur dans la variable txtPath).
Ce qui donne comme code (en remplacement du simple appel à la fonction wxMessageBox dans le fichier
mainframe.cpp):
#include <wx/dirdlg.h>
void MainFrame::OnButtonBrowseClicked(wxCommandEvent &event)
{
// On crée le wxDirDialog
wxDirDialog dirdlg(this, // La fenêtre parente
_T("Sélectionnez le dossier dans lequel commencer la recherche"), // Le message
_T(""), // Le dossier par défaut
wxDD_DIR_MUST_EXIST); // Le style de la boite de dialogue
// On l'affiche jusqu'à ce que l'utilisateur ait cliqué sur "Ok" ou "Annuler"
int result=dirdlg.ShowModal();
// Si le bouton "Annuler" a été pressé, alors, on sort
if (result==wxID_CANCEL) return;
// Sinon, on récupère le chemin complet du dossier sélectionné
wxString sPath=dirdlg.GetPath();
// Et on l'affiche dans le wxTextCtrl correspondant
txtPath->SetValue(sPath);
}
Cette classe nous propose 3 possibilités pour obtenir le listing du contenu d'un dossier :
• La méthode GetFirst() - GetNext() : avec cette méthode, nous allons devoir nous débrouiller pour
lister le contenu des éventuels sous-dossiers.
• La méthode GetAllFiles() : l'inconvénient de cette méthode est qu'elle ne nous rend pas la main avant d'avoir
fini l'énumération du contenu. L'utilisateur ne pourrait pas annuler la recherche si celle-ci devient trop longue.
De plus, le résultat de l'énumération est placé dans un wxArrayString, ce qui peut vite prendre beaucoup de
mémoire inutilement.
• La méthode Traverse() : Très simple d'utilisation, cette méthode nécessite par contre la création d'une classe
supplémentaire : la classe wxDirTraverser. C'est la méthode que nous allons utiliser.
Pour utiliser cette dernière méthode, il nous faut d'abord créer une classe dérivée de wxDirTraverser()
(http://www.wxwidgets.org/manuals/stable/wx_wxdirtraverser.html). Cette classe contiendra les méthodes OnFile() et
OnDir() qui seront appelées pour chaque fichier/dossier que wxDir::Traverse() trouvera.
Ensuite, lorsqu'on lance la recherche, on passe une instance de cette classe à la méthode wxDir::Traverse().
Pour chaque fichier trouvé, un appel est fait à wxDirTraverser::OnFile().
Pour chaque dossier (vous l'aurez deviné), un appel est fait à wxDirTraverser::OnDir().
C'est donc à l'intérieur de ces deux fonctions que nous allons placer le code pour ajouter le résultat au wxListCtrl.
Nous y placerons également un bout de code pour vérifier si l'utilisateur a éventuellement demandé de stopper la
recherche.
A ce sujet, nous devons proposer à l'utilisateur cette possibilité. Pour ce faire, nous allons simplement changer le
texte du bouton « Démarrer » en « Arrêter ». Il suffira donc, dans l'appel de la méthode associée à ce bouton, de
regarder quel est le texte du bouton, pour savoir ce que l'on doit faire :
• Si le texte est « Démarrer », alors, il faut démarrer la recherche.
• Si le texte est « Arrêter », alors il faut stopper tout.
Pour demander l'arrêt (ou au contraire, autoriser la continuité) de la recherche, tout se passe dans les méthodes
wxDirTraverser::OnFile() et wxDirTraverser::OnDir(). En effet, ces deux méthodes doivent renvoyer une valeur de
type wxDirTraverseResult qui peut prendre les valeurs suivantes:
• wxDIR_IGNORE : Ignorer le contenu du répertoire qui vient d'être trouvé.
• wxDIR_STOP : Arrêter l'énumération (intéressant, non ?).
• wxDIR_CONTINUE : Vous avez deviné : continuer l'énumération.
Pour indiquer au wxDirTraverser qu'il doit stopper l'énumération, il suffit d'ajouter une variable statique de type
booléen dans la méthode MainFrame::OnButtonStartClicked() et de lui en passer le pointeur en paramètre, et le tour
est joué.
Voici, concrètement ce que ça va donner.
En premier lieu, nous devons créer la classe dérivée de wxDirTraverser. Nous l'appellerons DirTraverser, tout
simplement.
Commencez donc par ajouter deux fichiers vides (menu File → New → Empty file, répondez « Oui » lorsqu'on
vous demande si vous désirez les ajouter au projet courant, et nommez-les respectivement dirtraverser.h et
dirtraverser.cpp.
Voici leur contenu initial (nous compléterons par la suite).
La déclaration de la classe (fichier dirtraverser.h) :
#ifndef DIRTRAVERSER_H_INCLUDED
#define DIRTRAVERSER_H_INCLUDED
#include <wx/dir.h>
#include "dirtraverser.h"
DirTraverser::DirTraverser(wxListCtrl *lstResults, bool *stopFlag)
{
// On stocke les deux pointeurs passés en paramètres
m_lstResults=lstResults;
m_bStopFlag=stopFlag;
}
Tout d'abord, pour pouvoir utiliser le DirTraverser que nous venons de créer, il faut ajouter le header de cette
classe au début du fichier mainframe.cpp.
Puis, dans la méthode MainFrame::OnButtonStartClicked(), nous allons placer la déclaration de la variable statique
qui va nous servir pour demander l'arrêt de la recherche:
Viendra ensuite la condition pour savoir si l'on veut démarrer la recherche, ou l'arrêter :
if(btnStart->GetLabel()==_T("Démarrer"))
{
// Placer ici le code pour lancer la recherche
} else {
// Placer ici le code pour stopper la recherche
}
Occupons nous dans un premier temps de la portion de code pour stopper la recherche : nous avons dit plus haut qu'il
suffisait de modifier la valeur de la variable statique bStopFlag.
Ce qui nous donne :
bStopFlag=true;
C'est tout. Nous nous chargerons, à chaque appel de DirTraverser::OnFile() et DirTraverser::OnDir(), de regarder
la valeur de cette variable puisque nous disposerons de son pointeur.
Maintenant, la portion de code pour lancer la recherche :
Il faut d'abord vérifier que le répertoire dans lequel nous allons démarrer la recherche soit valide :
btnStart->SetLabel(_T("Arrêter"));
Ensuite, on peut construire l'objet wxDir :
wxDir dir(sFolder);
Puis finalement lancer la recherche en créant au passage un objet DirTraverser et en s'assurant que le booléen soit
bien réinitialisé :
bStopFlag=false;
DirTraverser traverser(lstResults,&bStopFlags);
dir.Traverse(traverser,txtName->GetValue(),wxDIR_DIRS|wxDIR_FILES);
Vous aurez remarqué, au passage, le deuxième paramètre qui correspond au masque de recherche.
Lorsque la fonction wxDir::Traverse() nous rend la main, c'est que la recherche est terminée, ou que l'on a
demandé son arrêt.
On peut donc remettre le label du bouton "Démarrer" à sa valeur d'origine :
btnStart->SetLabel(_T("Démarrer"));
Et c'est tout.
Voici donc un récapitulatif du code de la méthode MainFrame::OnButtonStartClicked():
Bien, maintenant, il ne nous reste plus qu'à écrire le code des methodes OnFile() et OnDir() de notre DirTraverser.
Elles doivent faire toutes les deux sensiblement la même chose :
• Ajouter le fichier/dossier qui leur est transmis en paramètre dans le wxListCtrl
• Vérifier l'état du booléen pour éventuellement arrêter la recherche en cours
La seule différence est le texte qui doit être affiché dans la deuxième colonne du wxListCtrl : « Fichier » ou
« Dossier ».
Pour ne pas avoir à retaper deux fois le même code, nous allons ajouter une méthode AddItemToListCtrl() au
DirTraverser, et lui passer en paramètre le nom du fichier/dossier à ajouter, ainsi que le texte à mettre dans la
deuxième colonne.
Cette méthode se chargera de séparer le nom du fichier/dossier de son emplacement, vérifiera l'état du booléen, et
renverra la valeur que les fonctions OnFile et OnDir doivent retourner pour la suite ou l'arrêt de la recherche.
Commençons donc par ajouter la déclaration de cette nouvelle méthode (fichier dirtraverser.h):
#ifndef DIRTRAVERSER_H_INCLUDED
#define DIRTRAVERSER_H_INCLUDED
#include <wx/dir.h>
#include <wx/listctrl.h>
class DirTraverser : public wxDirTraverser
{
public:
// Le constructeur : on lui passe en paramètres le pointeur vers le wxListCtrl
// pour l'ajout des résultats, ainsi que le pointeur vers la variable de type
// booléen pour l'arrêt de l'énumération
DirTraverser(wxListCtrl *lstResults, bool *stopFlag);
// La méthode appelée lorsqu'un fichier est trouvé
virtual wxDirTraverseResult OnFile(const wxString &filename);
// La méthode appelée lorsqu'un dossier est trouvé
virtual wxDirTraverseResult OnDir(const wxString &dirname);
private:
// La méthode qui va faire tout le travail pour les deux ci-dessus
wxDirTraverseResult AddItemToListCtrl(const wxString &item, const wxString &type);
// Les variables pour stocker les pointeurs passés en paramètres au constructeur
wxListCtrl *m_lstResults;
bool *m_bStopFlag;
};
#endif
Grâce à cette méthode, le code des deux fonction OnFile() et OnDir() est énormément simplifié :
Et comme je vous l'ai dit, c'est la méthode AddItemToListCtrl() qui va tout faire.
D'abord, séparer le nom du fichier de son emplacement. Pour cela, on recherche le dernier caractère correspondant
au séparateur de dossiers dans un chemin, à savoir « \ » pour Windows, et « / » pour les autres OS.
Et comme wxWidgets nous propose quelque chose de tout prêt pour obtenir ce caractère, on ne va pas se priver de
s'en servir. Il est récupérable gràce à wxFileName::GetPathSeparator(). Il faudra donc penser à ajouter le header de
wxFileName (<wx/filename.h>) au début du fichier dirtraverser.cpp pour y avoir accès.
int i=item.Find(wxFileName::GetPathSeparator(),true);
wxString sPath=item.Left(i-1);
wxString sName=item.Right(item.Length()-i);
long itemIndex=m_lstResults->InsertItem(0,sName);
m_lstResults->SetItem(itemIndex,1,type);
m_lstResults->SetItem(itemIndex,2,sPath);
Il ne reste plus qu'à vérifier l'état du booléen pour renvoyer la bonne valeur, mais avant, il faut que l'on insère un
petit appel à la fonction wxYield() pour permettre à l'interface de se mettre à jour :
wxYield();
return (*m_bStopFlag==true)?wxDIR_STOP:wxDIR_CONTINUE;
#include "dirtraverser.h"
#include <wx/filename.h>
Pour information, deux petits bugs se sont glissés dans le code (ils ne sont pas très méchants, mais ils font que les
résultats obtenus ne sont pas forcément ceux recherchés).
lstResults->DeleteAllItems();
Deuxième petite chose : souvenez-vous, lors de la création de l'interface, nous avions gardé un pointeur vers la
wxStaticBox entourant la liste des résultats, afin de pouvoir en changer le texte, et ainsi informer l'utilisateur du
nombre d'éléments trouvés.
Justement, wxDir::Traverse() (http://www.wxwidgets.org/manuals/stable/wx_wxdir.html#wxdirtraverse) va nous
donner en retour le nombre dont on a besoin. Il suffit donc de le récupérer, et de changer le texte de la wxStaticBox
(fichier mainframe.cpp):
// Et on lance la recherche
size_t count=dir.Traverse(traverser,txtName->GetValue(),wxDIR_DIRS|wxDIR_FILES);
// Quand on arrive ici, c'est que la recherche est terminée
// On peut donc remettre le label du bouton "Démarrer"
btnStart->SetLabel(_T("Démarrer"));
// ainsi que le texte de la wxStaticBox entourant la liste des résultats
stbNumRes->SetLabel(wxString::Format(_T("Résultats de la recherche : %0ld éléments
trouvés"),count));
#ifndef DIRTRAVERSER_H_INCLUDED
#define DIRTRAVERSER_H_INCLUDED
#include <wx/dir.h>
#include <wx/listctrl.h>
class DirTraverser : public wxDirTraverser
{
public:
// Le constructeur : on lui passe en paramètres le pointeur vers le wxListCtrl
// pour l'ajout des résultats, ainsi que le pointeur vers la variable de type
// booléen pour l'arrêt de l'énumération
DirTraverser(wxListCtrl *lstResults, const wxString mask, bool *stopFlag);
// La méthode appelée lorsqu'un fichier est trouvé
virtual wxDirTraverseResult OnFile(const wxString &filename);
// La méthode appelée lorsqu'un dossier est trouvé
virtual wxDirTraverseResult OnDir(const wxString &dirname);
private:
// La méthode qui va faire tout le travail pour les deux ci-dessus
wxDirTraverseResult AddItemToListCtrl(const wxString &item, const wxString &type);
// Les variables pour stocker les pointeurs passés en paramètres au constructeur
wxListCtrl *m_lstResults;
bool *m_bStopFlag;
// La variable pour stocker le masque de recherche
// (correction du premier bug de notre application)
wxString m_mask;
};
#endif
Le fichier dirtraverser.cpp
#include "dirtraverser.h"
#include <wx/filename.h>
Il reste maintenant à modifier le code créant une instance de cette classe, dans le fichier mainframe.cpp. en ajoutant
le masque de recherche aux paramètres de construction du DirTraverser.
Cela se passe dans la méthode MainFrame::OnButtonStartClicked(...).
Il faut remplacer
DirTraverser traverser(lstResults,&bStopFlag);
par
DirTraverser traverser(lstResults,txtName->GetValue(),&bStopFlag);
Il reste une dernière petite chose à régler : Nous avons utilisé la fonction wxYield() qui est depuis quelques versions
déclarée comme « dépréciée ». Il faut la remplacer par la méthode wxApp::Yield().
Si vous vous penchez une dernière fois sur la documentation officielle pour wxApp, et plus précisément sur la partie
concernant wxApp::Yield(), vous vous apercevrez que cette méthode n’est pas statique. Il faut donc appeler la méthode
Yield() membre de notre instance de classe d’application. Or, je ne sais pas si vous vous en souvenez, mais nous
n’avons jamais créé de variable de type FileFinderApp.
Nous avons seulement utilisé la macro IMPLEMENT_APP(FileFinderApp) qui s’est elle-même chargée d’en créer
une instance.
Malgré tout, les concepteurs de wxWidgets ont tout prévu. Dans le fichier header de la classe FileFinderApp, juste
après sa déclaration, nous avons ajouté une autre macro : DECLARE_APP(FileFinderApp).
Cette macro, comme je vous l’ai vaguement dit au début de ce cours, va se charger de créer une fonction
wxGetApp() qui va nous renvoyer une référence vers l’instance de notre classe d’application en cours d’utilisation.
Il nous suffit donc d’inclure le header contenant cette macro (le fichier filefinderapp.h) quand nous en aurons
besoin, et nous pourrons obtenir une variable de type FileFinderApp&.
La commande wxYield() que nous devons remplacer se trouve dans le fichier dirtraverser.cpp. Il faut donc ajouter
au début de ce fichier :
#include "filefinderapp.h"
FileFinderApp& myapp=wxGetApp();
Myapp.Yield();
Nous pouvons même faire plus simple, car il est inutile de créer une variable juste pour cela :
wxGetApp().Yield();
Conclusion
Voilà : cette première partie est maintenant terminée.
J’espère qu’elle vous aura donné l’envie d’user et d’abuser de wxWidgets.
Il est vrai qu’à l’origine, j’avais l’intention d’ajouter des fonctionnalités à notre petit utilitaire, comme la possibilité
de spécifier une chaine de caractères à rechercher dans les fichiers, et bien d’autres encore, mais je pense que nous
verrons cela dans la deuxième partie de ce cours.
Pour ceux que ça intéresse, il vous est possible de télécharger le projet associé à ce cours sur ma page perso :
• Le projet Code::Blocks Win32 (avec l'exécutable compilé en statique) : sous forme d’archive 7zip (505Ko)
http://x.psoud.free.fr/localfiles/articles/tutowxwidgets1/FileFinder_w32.7z
• Le projet Code::Blocks Linux : sous forme d’archive tarball (5Ko) :
http://x.psoud.free.fr/localfiles/articles/tutowxwidgets1/FileFinder_CB.tar.gz
• Le projet Linux "Makefile" : sous forme d’archive tarball (87Ko) :
http://x.psoud.free.fr/localfiles/articles/tutowxwidgets1/filefinder-1.0.tar.gz
Bien entendu, j’attends avec impatience vos remarques, et critiques sur ce cours.
De même, s’il y a un point précis de wxWidgets que vous souhaiteriez voir abordé, n’hésitez pas à m’en faire part,
afin que je puisse éventuellement l’intégrer dans les prochaines parties du cours.
Xav’