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; /* Handle du contrôle ayant envoyé le message */
UINT idFrom; /* ID du contrôle */
UINT code; /* Raison de ce message */
}
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
"
, /* Classe : bouton */
"
OK
"
, /* Texte du bouton */
WS_CHILD |
WS_VISIBLE, /* Styles */
0
, /* x */
0
, /* y */
100
, /* Largeur */
20
, /* Hauteur */
hWnd, /* Fenêtre parent */
(
HMENU)1
, /* ID du bouton : 1 */
hInstance, /* Instance de l'application */
NULL
/* Paramètres additionnels */
);
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:
/**
*************************************************\
* LOWORD(wParam) = ID du contrôle ou du menu *
* HIWORD(wParam) = Raison du message (notification) *
\**************************************************
*/
switch
(
LOWORD
(
wParam))
{
case
1
:
switch
(
HIWORD
(
wParam))
{
case
BN_CLICKED:
Beep
(
1000
, 100
);
break
;
default
:
break
;
}
break
;
default
:
break
;
}
break
; /* case WM_COMMAND */
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);
}
}