I. Introduction▲
Windows est un système d'exploitation proposant une interface graphique. Dans ce premier article, nous allons créer une application composée seulement d'une fenêtre principale, dont le but et de montrer l'architecture d'un programme Windows.
Prenez garde au terme de fenêtre, dans Windows. Il signifie objet visuel, donc un bouton est une fenêtre ! Celle apparaissant au démarrage de l'application est souvent appelée fenêtre principale. Nous emploierons tout de même le mot fenêtre pour désigner une fenêtre du type de la fenêtre principale et le mot contrôle pour désigner les objets visuels qu'elles peuvent contenir…
II. La fonction WinMain▲
Le point d'entrée d'une application Windows est la fonction WinMain. C'est l'équivalent de la fonction main des applications classiques. Elle est appelée par le système d'exploitation au lancement du programme. Il lui fournit quatre paramètres.
int
WINAPI WinMain
(
HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int
nCmdShow);
- le premier paramètre est le handle d'instance de l'application. C'est un numéro unique attribué par le système d'exploitation qui lui permet de l'identifier ;
- le deuxième paramètre est toujours NULL pour les applications Win32 ;
- le troisième paramètre est un pointeur sur la ligne de commande ;
- le quatrième et dernier paramètre est un identificateur qui indique comment Windows désire afficher la fenêtre (normal, caché, minimisé…).
C'est donc dans cette fonction (WinMain) que nous allons créer la fenêtre principale.
III. Création de la fenêtre▲
La fonction qui permet de créer une fenêtre se nomme CreateWindow :
HWND CreateWindow
(
LPCTSTR lpClassName, // Pointeur sur une classe de fenêtre.
LPCTSTR lpWindowName, // Pointeur sur le texte de la fenêtre.
DWORD dwStyle, // Style de la fenêtre.
int
x, // Position horizontale de la fenêtre.
int
y, // Position verticale de la fenêtre.
int
nWidth, // Largeur de la fenêtre.
int
nHeight, // Hauteur de la fenêtre.
HWND hWndParent, // Handle de la fenêtre parent.
HMENU hMenu, // Handle de menu ou ID de contrôle.
HANDLE hInstance, // Handle d'instance de l'application.
LPVOID lpParam // Pointeur sur des données passées à WM_CREATE.
);
En voici un exemple d'utilisation :
HWND hwnd;
hwnd =
CreateWindow
(
"
MaWinClass
"
, "
Titre
"
, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400
, 300
,
NULL
, NULL
, hinstance, NULL
);
Le premier paramètre qu'elle reçoit est un pointeur sur une chaîne de caractères identifiant la classe de fenêtre. Les classes de fenêtre sont des modèles pour construire les fenêtres (le terme de classe n'a rien à voir avec les classes du C++). Si pour les contrôles standard nous avons des classes de fenêtres prédéfinies et globales, nous devrons en créer une pour la fenêtre principale. Nous devons pour cela remplir une structure de type WNDCLASS (définie dans winuser.h)
WNDCLASS wc;
wc.style =
0
;
wc.lpfnWndProc =
MainWndProc;
wc.cbClsExtra =
0
;
wc.cbWndExtra =
0
;
wc.hInstance =
hinstance;
wc.hIcon =
LoadIcon
(
NULL
, IDI_APPLICATION);
wc.hCursor =
LoadCursor
(
NULL
, IDC_ARROW);
wc.hbrBackground =
(
HBRUSH)(
1
+
COLOR_BTNFACE);
wc.lpszMenuName =
NULL
;
wc.lpszClassName =
"
MaWinClass
"
;
Les champs qu'il nous importe d'affecter pour l'instant à cette structure sont :
- le champ lpszClassName avec un pointeur sur la chaîne de caractères identifiant la classe de fenêtre. Celle que l'on a passée comme paramètre à la fonction CreateWindow ;
- le champ hInstance sera initialisé avec le handle d'instance hinstance que nous a fourni la fonction WinMain ;
- le champ hIcon avec le handle d'une icône standard de Windows qui nous est retournée par la fonction LoadIcon ;
- le champ hCursor avec le handle d'un curseur prédéfini de Windows (en l'occurrence la flèche) qui nous est retourné par la fonction LoadCursor ;
- le champ hbrBackground avec le handle d'un pinceau qui servira à peindre la surface client de la fenêtre. Dans cet exemple nous lui affecterons une couleur prédéfinie du système (1 + (l'identificateur de la couleur)) ;
- le champ lpfnWndProc de type WNDPROC reçoit un pointeur de fonction sur la procédure de fenêtre que nous allons voir plus loin dans ce document.
Nous passons sous silence pour l'instant les autres champs de cette structure.
Remplir cette structure ne suffit pas, il faut aussi l'enregistrer au niveau du système (souvenez-vous qu'elles sont identifiées par une chaîne de caractères). Cet enregistrement sera effectué par la fonction RegisterClass qui reçoit comme paramètre l'adresse de la variable wc de type WNDCLASS créée précédemment :
RegisterClass
(&
wc)
Revenons-en maintenant à la fonction CreateWindow.
- Son deuxième paramètre reçoit un pointeur sur le texte de la fenêtre. Dans le cas de la fenêtre principale, ce sera le texte du titre (si cela avait été un bouton, le texte se serait affiché sur le bouton).
- Le troisième paramètre est le style de la fenêtre. Ce sont des identificateurs qui sont définis dans le fichier winuser.h. Nous ne les passerons pas en revue, car ils sont trop nombreux. Dans l'exemple, nous lui donnerons la valeur WS_OVERLAPPEDWINDOW. Ce paramètre permet d'avoir une fenêtre avec une barre de titre, un menu system, les boutons minimiser et maximiser et que l'on peut redimensionner.
- Les deux paramètres suivants sont la position à l'écran de la fenêtre par rapport au coin haut gauche du bureau. Dans l'exemple, ils ont la valeur par défaut fournie par Windows : CW_USEDEFAULT.
- Les deux paramètres suivants sont la taille de la fenêtre : largeur et hauteur. Ils peuvent aussi avoir la valeur par défaut CW_USEDEFAULT.
- Le paramètre suivant désigne le parent de la fenêtre. Il est utile pour les contrôles enfants. En ce qui concerne la fenêtre principale, nous l'initialiserons à NULL.
- Le paramètre suivant désigne un menu pour la fenêtre, il peut être à NULL s'il n'y a pas de menu ou si le menu a été référencé dans le champ lpszMenuName de la classe de fenêtre.
- L'avant-dernier paramètre reçoit le handle d'instance de l'application qui crée la fenêtre, celui qui est fourni par WinMain.
- Le dernier paramètre sert à passer des données à la procédure de fenêtre quand elle reçoit un message WM_CREATE. Dans cet exemple, il sera initialisé à NULL.
Après s'être exécutée, la fonction CreateWindow nous retourne le handle de la fenêtre qu'elle vient de créer. Les handles de fenêtre sont des identificateurs uniques attribués par Windows qui désignent chaque fenêtre. Ils sont de type HWND.
Notre fenêtre étant maintenant créée, il faut la rendre visible par l'appel de la fonction ShowWindow :
ShowWindow
(
hwnd, nCmdShow);
Cette fonction reçoit comme paramètre le handle de la fenêtre et un identificateur qui indique comment on désire l'afficher (normal, caché, minimisé …). En général, on lui donne celui fourni par WinMain (voir plus haut dans ce document).
Ensuite, on appelle UpdateWindow avec comme paramètre le handle de la fenêtre. Cette fonction sert à rafraîchir l'affichage de la zone client. Dans cet exemple, elle n'est pas vraiment utile puisque la zone client est vide.
UpdateWindow
(
hwnd);
IV. La boucle de messages▲
Revenons au fonctionnement de Windows. Pour communiquer avec une application ou ses diverses fenêtres, Windows leur envoie des messages. Par exemple, si vous cliquez sur le bouton fermeture (en haut à droite de la fenêtre), Windows va créer un message approprié qu'il va envoyer dans la file d'attente de l'application. La file d'attente est un tampon où sont stockés les messages en attente de traitement.
C'est à nous de coder l'extraction des messages de la file d'attente :
MSG msg;
while
(
GetMessage
(&
msg, NULL
, 0
, 0
))
{
TranslateMessage
(&
msg);
DispatchMessage
(&
msg);
}
C'est le rôle de la fonction GetMessage, dont le premier paramètre est un pointeur sur une variable de type MSG où elle doit stocker les données du message qu'elle vient de récupérer.
Nous devons donc définir cette variable de type MSG, qui est une structure composée d'un champ hwnd qui contient le handle de la fenêtre à qui il est destiné, un champ message identifiant le message (par exemple WM_CLOSE pour demander à la fenêtre de ce fermer) et de deux champs wParam et lParam contenant des données ayant rapport au message et de significations différentes selon le message.
Revenons à GetMessage.
- Son deuxième paramètre est le handle de fenêtre dont on veut récupérer les messages. Il sera mis à NULL, car on veut récupérer tous les messages de l'application (destinés à toutes ses fenêtres).
- Les deux derniers paramètres seront mis à zéro, ils servent au filtrage des messages.
GetMessage renvoie toujours la valeur TRUE sauf si elle vient de récupérer le message WM_QUIT, ce qui met fin à la boucle de message donc à l'application.
Ensuite on passe le message à la fonction TranslateMessage qui traduit les messages WM_KEYDOWN (appui des touches clavier) en message WM_CHAR avec le code de caractère correspondant dans le champ wParam du message.
Ensuite, c'est à la fonction DispatchMessage de traiter le message. Son rôle est de l'envoyer à la procédure de fenêtre. Celle à qui il est destiné.
V. La procédure de fenêtre▲
Voici la procédure de fenêtre de notre fenêtre principale dont nous avions passé un pointeur à la classe de fenêtre au début de ce document (on passe ce pointeur à la classe de fenêtre, car c'est nous qui la créons, mais le système d'exploitation qui l'appelle, il doit donc la localiser).
LRESULT CALLBACK MainWndProc
(
HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK MainWndProc
(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch
(
uMsg)
{
case
WM_CREATE:
return
0
;
case
WM_DESTROY:
PostQuitMessage
(
0
);
return
0
;
default
:
return
DefWindowProc
(
hwnd, uMsg, wParam, lParam);
}
}
Les paramètres qu'elle reçoit sont les données du message en cours de traitement. Celui qui a été envoyé par DispatchMessage. C'est à nous, développeurs, d'implémenter des actions en fonction du message que la procédure de fenêtre reçoit. Dans cet exemple, un seul message est traité : WM_DESTROY. Il nous indique que la fenêtre est fermée et a été détruite. Comme il s'agit de la fenêtre principale de l'application, il est d'usage de fermer l'application. Ce que nous ferons en appelant la fonction PostQuitMessage qui a pour fonction de poster un message WM_QUIT dans la file d'attente. Qui comme nous l'avons vu plus haut dans ce document, met fin à la boucle de messages et donc à l'application. Les messages non traités doivent l'être par la fonction DefWindowProc. Fonction qui implémente le comportement par défaut d'une fenêtre.
VI. Code complet▲
#include <windows.h>
LRESULT CALLBACK MainWndProc
(
HWND, UINT, WPARAM, LPARAM);
int
WINAPI WinMain
(
HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int
nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc;
wc.style =
0
;
wc.lpfnWndProc =
MainWndProc;
wc.cbClsExtra =
0
;
wc.cbWndExtra =
0
;
wc.hInstance =
hinstance;
wc.hIcon =
LoadIcon
(
NULL
, IDI_APPLICATION);
wc.hCursor =
LoadCursor
(
NULL
, IDC_ARROW);
wc.hbrBackground =
(
HBRUSH)(
1
+
COLOR_BTNFACE);
wc.lpszMenuName =
NULL
;
wc.lpszClassName =
"
MaWinClass
"
;
if
(!
RegisterClass
(&
wc)) return
FALSE;
hwnd =
CreateWindow
(
"
MaWinClass
"
, "
Titre
"
, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 400
, 300
,
NULL
, NULL
, hinstance, NULL
);
if
(!
hwnd) return
FALSE;
ShowWindow
(
hwnd, nCmdShow);
UpdateWindow
(
hwnd);
while
(
GetMessage
(&
msg, NULL
, 0
, 0
))
{
TranslateMessage
(&
msg);
DispatchMessage
(&
msg);
}
return
msg.wParam;
}
/**
*************************************************************************
*/
LRESULT CALLBACK MainWndProc
(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch
(
uMsg)
{
case
WM_CREATE:
return
0
;
case
WM_DESTROY:
PostQuitMessage
(
0
);
return
0
;
default
:
return
DefWindowProc
(
hwnd, uMsg, wParam, lParam);
}
}
Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder.
À vos PC.