La programmation de l'interface utilisateur
Date de publication : 22 mai 2008. Date de mise à jour : 23 mars 2009.
I-A. Vue d'ensemble
I-A-1. Généralités
I-A-2. Les classes de fenêtre prédéfinies
I-A-3. La boucle des messages revisitée
I-A. Vue d'ensemble
I-A-1. Généralités
Nous avons déjà vu que la création d'une fenêtre se fait toujours à partir d'une classe
de fenêtre existante. Sous Windows, le concept de fenêtre est assez large. En effet,
une fenêtre peut désigner aussi bien une fenêtre (telle que nous l'entendons le plus souvent)
qu'un contrôle (bouton, zone de texte, etc.) ou même tout et rien ! Il y a deux grandes
catégories de contrôles : les contrôles standard, inhérents à Windows et les contrôles
communs, spécifiques de chaque version, implémentés respectivement dans user32.dll et
comctl32.dll.
Pour un contrôle, le style WS_CHILD est donc obligatoire. La plupart du temps on spécifie
également le style WS_VISIBLE pour que le contrôle soit initialement visible. Et enfin, un
contrôle n'a pas de menu, à la place on spécifie un entier (un cast est donc nécessaire ...) qui
nous permettra de le référencer. Cet entier est appelé l'ID (identifier) du contrôle. Chaque fois
qu'un contrôle ou un menu est titillé par l'utilisateur, celui-ci envoie le message
WM_COMMAND à sa fenêtre parent et place :
- son ID dans le mot de poids faible de wParam
- son handle dans lParam
- et l'événement qui s'est produit (notification) dans le mot de poids fort de wParam
En fait pour les contrôles communs, c'est le message WM_NOTIFY qui est envoyé à la
place du message WM_COMMAND. La structure de ce message est la suivante :
- Dans wParam : l'ID du contrôle ayant envoyé le message.
- Dans lParam : l'adresse d'une structure de type NMHDR (pour Notification Message
Header) fournissant des informations supplémentaires sur le message.
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
|
La communication avec les contrôles se fait largement via les messages. La fonction
SendMessage permet d'envoyer un message à une fenêtre quelconque (donc y compris les
contrôles ...) tandis que SendDlgItemMessage n'est utilisable que pour les contrôles.
LRESULT SendMessage (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT SendDlgItemMessage (HWND hwndParent, int nCtrlID, UINT message, WPARAM wParam, LPARAM lParam);
|
Mais sachez qu'il existe des fonctions et/ou macros qui encapsulent un ou plusieurs SendMessages
dans le but d'améliorer la lisibilité du code. En général, les fonctions sont déclarées dans winuser.h
(inclus dans windows.h) et les macros dans windowsx.h (qu'il faut inclure après windows.h).
Et pour terminer avec les généralités, sachez que connaissant l'ID d'un contrôle, on
peut récupérer son handle et vice versa.
HWND GetDlgItem (HWND hwndParent, int nCtrlID);
int GetDlgCtrlID (HWND hCtrl);
|
I-A-2. Les classes de fenêtre prédéfinies
Les classes de fenêtre suivantes sont définies pour toutes les applications fenêtrées et peuvent
donc être utilisées pour créer un contrôle (elles sont enregistrées avant même que WinMain ne soit
appelée).
Nom |
Classe |
"BUTTON" |
Bouton |
"COMBOBOX" |
Zone de liste combinée |
"EDIT" |
Contrôle permettant à l'utilisateur de saisir du texte |
"LISTBOX" |
Zone de liste |
"RichEdit" |
Contrôle permettant à l'utilisateur de saisir du texte enrichi |
RICHEDIT_CLASS |
Amélioration de la classe RichEdit (plus sophistiqué ...) |
"SCROLLBAR" |
Barre de défilement |
"STATIC" |
Etiquette |
Ce sont les contrôles standard (il ne s'agit pas d'une liste exhaustive).
Par exemple :
CreateWindow ( " BUTTON " ,
" OK " ,
WS_CHILD | WS_VISIBLE,
0 ,
0 ,
100 ,
20 ,
hWnd,
(HMENU)1 ,
hInstance,
NULL
);
|
Bien que l'on puisse créer le contrôle à n'importe quel moment, le moment idéal pour le faire
est pendant la création de sa fenêtre parent c'est-à-dire pendant le traitement du message
WM_CREATE. Autre remarque : il est rarement nécessaire de récupérer le handle du contrôle
(retourné par CreateWindow) car en général, on travaille avec l'ID du contrôle plutôt qu'avec son handle
(parce que c'est plus facile : ça évite de déclarer 50 variables pour 50 contrôles ...) et de toute façon,
au cas où on aurait besoin de son handle, il y a toujours la fonction GetDlgItem. N'oubliez pas non
plus que DestroyWindow détruit non seulement une fenêtre mais également toutes ses fenêtres enfants.
Il n'est donc pas nécessaire de détruire manuellement les contrôles d'une fenêtre ni avant ni après que
celle-ci ait été détruite, puisque cela se fait automatiquement.
Voici un exemple qui montre comment profiter du message WM_CREATE pour créer notre contrôle :
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
switch (message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT)lParam)- > hInstance;
CreateWindow (" BUTTON " , " OK " , WS_CHILD | WS_VISIBLE, 0 , 0 , 100 , 24 ,
hwnd, (HMENU)1 , hInstance, NULL );
break ;
case WM_DESTROY:
PostQuitMessage (0 );
break ;
default :
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0 ;
}
|
Maintenant on va tester si notre bouton fonctionne bien. Pour cela on va émettre un beep en
réponse à chaque clic. La fonction Beep reçoit en arguments la fréquence en Hz et la durée
en ms du son à émettre. Elle très pratique pour déboguer.
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
switch (message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT)lParam)- > hInstance;
CreateWindow (" BUTTON " , " OK " , WS_CHILD | WS_VISIBLE, 0 , 0 , 100 , 24 ,
hwnd, (HMENU)1 , hInstance, NULL );
break ;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case 1 :
switch (HIWORD (wParam))
{
case BN_CLICKED:
Beep (1000 , 100 );
break ;
default :
break ;
}
break ;
default :
break ;
}
break ;
case WM_DESTROY:
PostQuitMessage (0 );
break ;
default :
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0 ;
}
|
I-A-3. La boucle des messages revisitée
Un contrôle peut être atteint par l'utilisation de la touche TAB s'il possède le style
WS_TABSTOP. Ce comportement (de même que la notion de bouton par défaut) est en fait
typique des boîtes de dialogue (qui sont bien entendu également des fenêtres ...). Pour
autoriser un tel comportement dans une fenêtre « normale », il suffit de passer tous les
messages à IsDialogMessage qui effectue un traitement adéquat si le message est un
« Dialog Message ». Si la fonction retourne TRUE, ce qui signifie que le message était bien
un Dialog Message (et donc déjà été convenablement traité), il ne faut plus faire un nouveau traitement.
La boucle des messages devient donc :
while (GetMessage (& msg, NULL , 0 , 0 ))
{
if (! IsDialogMessage (hWnd, & msg))
{
TranslateMessage (& msg);
DispatchMessage (& msg);
}
}
|
Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2008 Melem. Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation
expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et
jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.