Pour résumer ce que nous savons sur les controles :
Nous avons déjà vu que les boites de dialogues sont des fenêtres à part entières.
Ces dialogues sont elles memes composées de controles qui eux aussi sont des fenetres
(particulières biensur mais elles n'en restent pas moins des fenetres).
Un controle est une entité à part entiere qui a un comportement et une apparence
par défaut. Nous allons truffer nos GUI (Graphic User Interface ou Interface Graphique
Utilisateur) de controles et nous allons pouvoir connaitre leur états
grace à un mecanisme de type messages (cf. tutoriaux precedents).
En schématisant un peu les choses, tout se résume en un simple dialogue entre nous et le controle
à travers un ensemble de mots qui lui sont spécifiques (les messages) et dont la sémantique (la signification) ne sera
partagée que par nous et le controle (et l'OS aussi mais bon).
2.1. Les check-box.
Nous avons vu la fois précédente les boutons et leurs usages. Les check box (ou "cases à cocher") sont un autre type de controle apparenté aux boutons et facile à manipuler. Si vous ne voyez pas de quoi je veux parler, vous avez surement du en rencontrer déjà, voici à quoi ils ressemblent :
SendMessage(hControlWnd, BM_GETCHECK, 0, 0);BM_GETCHECK est le message en question qui est adressé à la fenetre consituée par le check-box. Comme on l'a vu dans le précédent tutorial, ce handle va pouvoir etre récupére grace à la fonction :
GetDlgItem(HWND hdlg, int controlID );La valeur de retour du SendMessage nous indiquera l'état de ce controle. Si la valeur est egale à la constante BST_CHECKED et bien le check box est coché sinon il ne l'est pas. Vous devez vous douter que pour forcer l'état d'un check box il suffit de lui envoyer un message BM_SETCHECK avec dans wParam l'etat final voulu : BST_CHECKED (coché) ou BST_UNCHECKED (non coché).
IsDlgButtonChecked( HWND hwndDlg, int hButtonID);ou en plus généraliste cette fois puisque cette fonction peut s'adapter à tout type de controle :
SendDlgItemMessage( HWND hDeNotreDialogue, WORD idDeNotreControle, WORD notreMessage, WPARAM wParam, LPARAM lParam );Ces deux manières de faire renvoient l'état du bouton en question parmi : BST_CHECKED, BST_UNCHECKED et BST_UNDETERMINATE.
Nous avons déjà regardé un peu les controles de type Edit. Bon je voudrais juste rajouter une chose pour vous permettre de les utiliser de manière peut etre un peu plus poussée. Il est possible de faire en sorte que les EditBox acceptent plusieurs lignes et donc puissent servir de zone permettant d'afficher une grande quantité de texte (jusqu'a 32 ko si ma mémoire est bonne). La manoeuvre à effectuer est simple. Tout d'abord il faut spécifier a Windows que l'edit box a la capacité d'accepter du texte sur plusieurs lignes. Pour cela, l'éditeur de ressource est votre ami : cochez "Multiligne" dans les propriétés de votre EditBox.
Les menus sont importants dans une application. Ils apportent de la convivialité
et sont de nos jours tellement repandus qu'il est inconcevable de ne pas savoir
les utiliser. Un menu peut etre créé entièrement
à partir de l'éditeur de ressources de Visual C++ (ou de tout autre EDI).
Pour cela suivez la méthode habituelle pour ajouter une nouvelle ressource à votre projet
et sélectionnez "Menu" en tant que nouvelle resource.
Un menu standard et nu apparaitra alors. Chaque entrée du menu est appelée un "Item", de meme que
chaque selection possible de vos sous menus. Chaque item va avoir une ID particuliere qui
va permettre de le distinguer des autres. Cette ID ainsi que le texte associé a chaque entrée
du menu peuvent etre indiqués grace à la boite "Propriété" associée à l'item. En gros le mode de fonctionnement d'un
menu est le meme dans l'esprit que celui d'un GUI au complet avec ses controles
standards. Vous aller simplement, à travers ce menu, fournir à l'utilisateur une
interface visuelle supplémentaire proposant un certain nombre de choix possibles et il ne vous
reste plus qu'à le laisser faire.
Pour pouvoir répondre à chaque solicitation de l'utilisateur, nous allons
ainsi devoir etre informé les differentes actions qu'il va effectuer sur le menu
et réagir en conséquence. Et, une fois encore, nous allons le faire grace à des messages
(ca devient habituel non ?). Ces messages vont etre directement envoyés à la Window Procedure
( cf chapitre precedent ) de la fenetre ou du dialog auquel seront attaché le menu.
Et oui, un menu ne pourra se balader tout seul comme ca dans la nature, il lui faudra
un élément maitre auquel se raccrocher. Cet élément sera en general une fenetre (dialogue ou
fenetre classique). Le terme attaché entend "qui sera responsable de traiter les messages
résultant de l'interaction de l'utilisateur sur les items du menu".
Bon treve de blabla, passons à l'action.
Plusieurs méthodes sont possibles pour rattacher un menu à une entité graphique (fenetre,
boite de dialogue). Nous pouvons le spécifier dés la création de la classe dans le cas
d'une fenetre (souvenez vous de la structure WNDCLASS (cf chap. 2)) ou bien charger le
menu par la suite en mémoire et l'attacher à l'entité souhaitée.
La manipulation d'un menu se fait à travers son handle (identificateur) qui est de
type HMENU. Pour ce qui est du chargement dynamique, nous allons pouvoir le
faire grace aux fonctions : LoadMenu() et SetMenu().
LoadMenu nous permet de charger en mémoire une ressource de type menu et nous
renvoi un handle sur ce menu chargé en mémoire qui va nous permettre de le
tripoter par la suite.
SetMenu va nous permettre d'attacher un menu à une entité graphique. Dans
ce cas, c'est la WindowProc (ou DlgProc) de l'entité qui va recevoir les
messages indiquant que l'utilisateur tripote le menu. Ces messages seront
de type WM_COMMAND avec LOWORD(wParam) qui contiendra l'ID de l'item qui aura
été selectionn, tout simplement.
Il vous est possible de modifier à volonté vos menus grace a quelques fonctions
spécifiques. Vous pouvez ainsi rajouter des items, des sous menus, cocher un
item de votre menu ... Ces fonctions sont AppendMenu(), InsertMenuItem(),
CheckMenuItem(), etc ...
De plus, il est parfois utile de savoir qu'on peut créer des menus à la volée
("on-the-fly" en anglais) à l'aide de quelques fonctions win32. Meme si l'utilité
de ce type de manipulations peu paraitre negligeable, il est tres important de savoir
comment le faire dans le cas de menus contextuels par exemple (cf. ci dessous).
Les fonctions CreateMenu() et CreatePopupMenu() vont nous aider dans cette
tache. Comme nous pouvons nous y attendre, chacune de ces fonctions va nous
renvoyer un handle sur un menu de type : HMENU. Ce handle pourra par la suite
etre utilise pour rajouter des Items au menus etc ...
Pour fixer les idees, un menu dit "pop-up" est menu déroulant (ou "drop down menu")
qui peut etre soit etre un sous-ensemble d'un menu déjà existant ou qui peut etre
utilisé en tant que menu contextuel (cf. plus bas).
Les menus contextuels sont des menus comme les autres sauf qu'ils apparaissent en reaction à une action spécifique. Si vous ne voyez pas de quoi je parle, cliquez avec la bouton droit de votre souris sur votre browser. Et voila, normalement un menu doit apparaitre à l'endroit où vous avez cliquer.
Pour tenter d'approfondir la notion de menus contextuels, reflechissons un peu. D'apres nos connaissances, les fenetres doivent reagir a un certain nombre de solicitations de la part de l'utilisateur. Ces solicitations peuvent etre de types tres divers et Windows utilise les messages pour informer qu'il s'est passe quelque chose. Dans notre cas, nous venons de cliquer sur une fenetre (ou sur un controle quelconque ce qui est intrinssèquement la meme chose), et il vient de se passer quelque chose. Il y a du y avoir un peu d'agitation derrière et tout le monde a du s'affoler pour informer la fenetre (ou le controle) qu'on vient violement de lui cliquer dessus ! Et bien un message a été envoyé à cette fenetre. Ce message est spécifique à ce type d'action : WM_CONTEXTMENU. Il est envoyé quand l'utilisateur clique droit sur une fenetre. Il nous suffit donc de rajouter une entrée WM_CONTEXTMENU dans la DlgProc ou la WndProc qui convient (celle sur lequel l'utilisateur est censée cliquer pour avoir accès au menu contextuel).
Une fois que nous avons recu ce message, il nous faut effectivement afficher le menu. Il ne sort pas de nulle part ! Le menu que nous voulons afficher est, comme nous l'avons vu au dessus, un menu deroulant. C'est donc un "popup menu" qui sera créé par nous meme au moment du click, par exemple, avec la fonction CreatePopupMenu(). Ensuite, il faut simplement le remplir ce menu en lui ajoutant des entrées grace aux fonctions classiques de gestion de menu du type AppendMenu(), ...
Maintenant, l'affichage du menu est géré par la fonction TrackPopupMenu() qui va prendre un certain nombre de paramètres tels que la position du menu, le handle du menu à afficher, des flags de création, ... Il est a noter que l'API win32 fournit une macro bien utile quand il s'agit de récupérer les coordonnées en x et en y de l'endroit ou l'utilisateur a cliqué : GET_X_LPARAM(lParam) et GET_Y_LPARAM(lParam). Ces deux macros prennent le paramètre classique déjà vu (cf. tutoriaux précédents) des DlgProc ou bien WndProc : lParam.
Il est a signaler que la fonction TrackPopupMenu() a deux comportement possibles (qui sont définissables à travers un paramètre de flags passé à cette fonction) : bloquante ou non bloquante. J'ai deja parlé des fonctions bloquantes dans le cas des deux fonctions GetMessage() et PeekMessage(). Dans le cas bloquant, la fonction TrackPopupMenu() ne va pas retourner tant que l'utilisateur n'a rien fait. Elle attend qu'il choisisse une entrée du menu ou bien clique à coté pour l'annuler. Dans le cas non bloquant, le choix de l'utilisateur est envoye en tant que message a la fenetre sur lequel l'utilisateur a cliquer. C'est ensuite a vous de traiter correctement le message. Le comportement de cette fonction est defini par les flags TMP_RETURNCMD et TMP_NONOTIFY.
Une derniere chose pour ce qui est des menus contextuels, il y a un bug dans la
fonction TrackPopupMenu(). Un bug bien connu et génant puisque le comportement
actuel de cette fonction oblige la selection d'un item du menu contextuel lorsque
celui la apparait. Il n'est donc pas possible d'annuler notre action. Il existe
cependant un moyen de contrer ce probleme (moyen bien connu aussi) mettre un appel
bidon a PostMessage() apres celui à TrackPopupMenu(). Exemple :
SetForegroundWindow(hwndDlg);
cmdID = TrackPopupMenu(handleNotreMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, xPos, yPos, 0, hwndDlg, NULL);
PostMessage(hwndDlg, WM_NULL, 0, 0);
2.5. Les Combo Box.
Nous allons maintenant nous intéresser aux combo box. Ce sont en fait des sortes
de menus déroulants améliorés. Encore une fois une image vaut mieux que tous les discours :
|
|
Configuration permettant de regler la hauteur en jouant sur le carre bleu au bas. | Configuration permettant de regler la largeur en jouant sur les carres bleus sur le cote. |
Les list box sont plus ou moins apparentées, dans l'idée, aux combos box dans le sens où elles mettent en jeu des listes d'items (chaines de caracteres ou images ou les deux ) que l'on peut enlever ou retirer du controle. L'utilisation des ListBox est relativement facile quand on a vu les combo-box. Elles fonctionnent globalement pareil et il est relativement facile de les utiliser. Nous allons une fois de plus faire intervenir un certain nombre de messages qui vont nous servir pour communiquer avec le controle. Ces messages vont tous etre perfixés par un LB_. Vous pouvez donc dore et déjà aller voir un peu dans le MSDN ce que vous pouvez tirer d'une list box. Les messages ayant un nom relativement explicite vous pourrez facilement en avoir un apercu rapide. Un element dans une listbox est appele un item.
Voici une description rapide de quelques messages parmis les plus commun :
LB_ADDSTRING | Ajoute une chaine de caractere dans la liste. Attention, il faut que la list box ait le style LBS_HASSTRING. |
LB_DELETESTRING | Efface une chaine de caractere dans la liste. Attention, il faut que la list box ait le style LBS_HASSTRING. |
LB_FINDSTRING | Trouve une chaine de caractere dans la liste. Attention, il faut que la list box ait le style LBS_HASSTRING |
LB_GETCOUNT | Retourne le nombre d'items dans la liste. |
LB_GETCURSEL | Retourne la selection actuelle dans la listbox (index). |
LB_GETSEL | Retourne l'etat d'un item (selectionne ou pas). |
LB_GETTEXT | Retourne le texte associe a un item. Attention la listbox doit avoir le style LBS_HASSTRING. |
LB_INSERTSTRING | Permet d'inserer une chaine de caractere dans la listbox. Attention la listbox doit avoir le style LBS_HASSTRING. |
LB_SETCURSEL | Force la selection dans la listbox. |
LBN_DBLCLK | Message envoye quand l'utilisateur double-click sur un item de la listbox. Ce message est recu a travers un WM_COMMAND par la fenetre mere de la listbox. |
Windows dispose d'un ensemble de boites de dialogues standards dans ce sens que chacune
repond a un besoin precis et peut etre appelee et manipulee a travers une serie d'appel
de fonctions faisant parties de l'API win32. Ces boites de dialogues vous devriendrons
tres vite indispensables dans la plupart de vos applications GUI. Vous les avez deja
rencontre, au moins certaines d'entres elles. Ainsi, a chaque fois que vous faites un
"Save As ..." dans l'un de vos menu, une boite de dialogue type apparait. Il s'agit d'une des
boites de dialogue dite "Commong Dialog", idem pour la boite de dialogue de configuration
d'imprimante, etc ...
Ouverture/fermeture de fichiers.
Demander le lancement de la boite de dialogue permettant la selection d'un (ou plusieurs)
fichier(s) a ouvrir ou a sauvegarder se fait grace aux fonction de l'API Win32 :
GetOpenFileName() et GetSaveFileName(). Il faut passer a ces deux fonction une structure
de type OPENFILENAME qui va decrire le comportement du dialogue et le type de
services qu'on veut offrir a l'utilisateur. Il va ainsi etre possible de proposer
un filtre sur les fichiers a ouvrir/sauver au niveau du type d'extension attendue,
de configurer l'apparence et les possibilites de la boite de dialogue,
etc ... Voici la description de quelques champs de la structure que vous pourrez etre
susceptibles de rencontrer le plus souvent :
lStructSize | Correspond a la taille de la structure en octets. |
hwndOwner | Handle de la fenetre dont ce dialogue depend. |
lpstrFilter | Filtre sur le type de fichier accepté par l'application. Nous allons ainsi pouvoir accepter par exemple que les fichiers texte (".txt") et le dire explicitement dans le dialogue (commentaire : "Fichier texte"). Ce champ est une chaine de caractere formattée d'une manière particulière permettant d'entrer un ou (plusieurs) filtre(s) associé(s) à une (ou plusieurs) extension(s). Un filtre, dans ce contexte précis, correspond à une description du type de fichier suivie d'une série de chaines de caractères décrivant les extensions de fichiers associées à la description précédente. Par exemple, si jamais nous ne voulons accepter que les fichiers texte avec les extensions ".txt" ou ".diz" nous allons spécifier : "Text files\0*.diz;*.txt\0\0" Comme vous le voyez il est possible de mettre plusieurs extensions en les séparant de ";". Il est aussi commun de mettre la liste des extensions de fichiers autorisées entre parenthèses dans la description du type de fichier : "Text files (*.txt, *.diz)\0*.diz;*.txt\0\0". Il est de plus facile de permettre plusieurs types de fichiers, simplement en les chainant : "Text files\0*.diz;*.txt\0Source files\0*.cpp;*.h\0\0". Nous acceptons donc dans ce dernier exemple, les fichiers textes (".txt" ou bien ".diz") et les fichiers sources (".cpp" ou ".h"). |
lpstrFile | Un pointeur sur un tableau de caractere suscpetible de recevoir le nom du fichier (chemin complet+ nom de fichier + extension ). |
nMaxFile | Taille en octets du tableau ci-dessus. |
lpstrFileTitle | Idem que lpstrFile mais contient le nom du fichier ainsi que son extension. |
nMaxFileTitle | La taille en octets du tableau ci-dessus. |
Flags | Un ensemble de flags que l'on peut combiner en semble grace à un simple ou logique ("|") et qui vont déterminer le comportement de la boite de dialogue (save, load file, style du dialogue, ... ). |
La fonction ChooseColor() va nous permettre d'afficher une boite
de dialogue permettant le choix d'une couleur par l'utilisateur.
Je ne vais pas m'étendre là encore sur la signification de
chaque parametre mais voici un exemple d'utilisation.
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
CHOOSECOLOR cc;
COLORREF ar[16];
ZeroMemory ( &cc, sizeof(cc) );
cc.lStructSize = sizeof(cc);
cc.hwndOwner = NULL;
cc.Flags = CC_ANYCOLOR;
cc.lpCustColors = &ar[0];
ChooseColor( &cc);
}
NOTE: une petite remarque pour vous signaler l'existance de la macro ZeroMemory qui
comme son nom l'indique nous est fournie par l'API win32 pour mettre a zero une
zone de memoire particuliere.
Choix d'une imprimante.
La fonction PrintDlg() va nous permettre d'afficher un dialog de
configuration d'impression. Je ne vais pas detailler tous les parametres mais
simplement vous donner un exemple qui fonctionne :
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
// structure de base decrivant notre boite de dialogue
PRINTDLG p;
ZeroMemory( &p, sizeof(p) );
p.lStructSize = sizeof(PRINTDLG);
p.hwndOwner = NULL;
p.Flags = PD_PRINTSETUP | PD_RETURNDC;
PrintDlg( &p );
}
NOTE: une petite remarque pour vous signaler l'existance de la macro ZeroMemory qui
comme son nom l'indique nous est fournie par l'API win32 pour mettre a zero une
zone de memoire particuliere.
Il existe d'autres CommonDialog qui fonctionnent sur le meme principe. C'est a vous de les decouvrir
(cf. MSDN encore une fois ).
Avant de vous quitter j'aimerais revenir sur une chose qui me semble importante : la notion
de boite de dialogue modale. Je vais pas trop m'étendre dessus vu que le tutorial s'est pas mal
allongé par rapport à ce que je voulais qu'il soit au départ ( à force de vouloir y rajouter
des détails et encores des remarques :). Cette notion se retrouve aussi quand on utilise les MFCs
donc au moins à ce niveau là vous ne serez pas completement dans le flou.
Alors donc qu'est ce qu'une boite de dialogue modale ?
Il s'agit d'une boite de dialogue bloquante. En fait c'est pas la boite de dialogue en elle
meme qui est bloquante mais l'appel a la fonction permettant de lancer la boite de dialogue.
Pour avoir des précisions sur la notion de boite de dialogue bloquante réferez vous aux tutoriaux
précédents. Pour en avoir un exemple simple
cliquez dans le menu de votre navigateur internet dans "Fichier">"Enregistrer sous ...". Une
boite de dialogue que vous etes maintenant censé connaitre (cf boite de dialogue commune au dessus)
apparaitra. Maintenant, essayez de faire revenir au premier plan l'interface de votre navigateur
qui doit etre maintenant juste sous le dialogue de sauvegarde de fichier. Et bien vous ne pouvez pas.
Voila, une boite de dialogue modale. Elle va empecher l'acces a tout le reste de votre GUI
(Graphic User Interface = "Interface Utilisateur Graphique") et bloquer en attente d'une réponse.
Vous vous doutez bien qu'une boite de dialogue non modale est tout le contraire. Vous aller pouvoir
passer allegrement de votre boite de dialogue au reste de vos éléments de GUI.
On appel ce type de dialogue "modeless" en anglais.
Pour créer une boite de dialogue d'un type ou d'un autre, il faut simplement choisir entre
deux fonctions de l'API Win32 : CreateDialog() ou bien DialogBox() que nous avons deja vu.
Ces deux fonctions prennent des parametres quasi-identiques je n'ai donc pas besoin de m'étendre
dessus. CreateDialog() permet de créer une boite de dialogue modeless (non modale donc) alors
que vous l'aurez deviner DialogBox() est une macro qui permet de creer des boites de
dialogues modales.
Voila pour ce tutorial, j'espere encore une fois qu'il aura ete aussi clair que possible
et dans le cas contraire n'hesitez pas a me le faire savoir. Je recois regulierement
des emails et je m'efforce d'y repondre au plus vite et du mieux que je peux. Ca fait
toujours plaisir de recevoir 2 ou 3 mots, ca motive pour la suite quoi. Bref, maintenant
c'est a vous de jouer ... a vos lignes de code !
MSDN consultable sur le net : | http://msdn.microsoft.com/library/default.asp |
VisualC++ 6 service pack 5 : | http://msdn.microsoft.com/vstudio/sp/vs6sp5/vcfixes.asp |
DevC++ (EDI sous Windows qui utilise mingw comme compilateur) : | http://www.bloodshed.net/devcpp.html |