Introduction :
Windows est un système d'exploitation proposant une interface graphique.
Dans ce premier article, nous allons créer une application composée
d'une simple fenêtre. Le but est de montrer l'architecture d'un programme
Windows avec interface graphique.
Prenez garde au terme de fenêtre ! Dans Windows, il signifie bien souvent objet visuel,
par exemple, un bouton est une fenêtre ! On emploie aussi pour ces objets le terme de contrôles.
La fenêtre apparaissant au démarrage de l'application est souvent appelée fenêtre principale.
Dans ce tutoriel, nous emploierons le terme de 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 qu'elles peuvent contenir tels les boutons...
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 consoles.
Elle est appelée par le système d'exploitation au lancement du programme.
Il lui fournit 4 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 second paramètre (qui vient de l'antiquité) est toujours NULL pour les applications Win32 (IDEM pour les Win64).
Le troisième paramètre est un pointeur sur la ligne de commande.
Et enfin le quatrième et dernier paramètre est une constante 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.
Création de la fenêtre :
La fonction qui permet de créer une fenêtre se nomme CreateWindow.
Cette fonction permet de créer des fenêtres, mais aussi des controles tel des boutons...
Nous aurons l'occasion de l'utiliser plusieurs fois dans les différents chapitres de ce turoriel.
Voici le prototype de la fonction :
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.
);
Et 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êtres sont identifiées par des chaînes de caractères).
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++).
Pour les contrôles standard nous avons des classes de fenêtres prédéfinies
et globale, par exemple pour un bouton, c'est la chaîne de caratère "button".
Malheureusement pour la fenêtre principale ce n'est pas le cas, ça aurait été trop simple.
Nous devrons donc en créer une.
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)(COLOR_3DFACE + 1);
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é 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 ((l'identificateur de la couleur) + 1).
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 sytè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 à notre fonction CreateWindow. Son deuxième paramètre reçoit un pointeur sur une chaîne de caractères
qui sera le texte de la fenêtre.
Dans le cas de la fenêtre principale, ce sera le texte du titre (celui affiché en haut de la fenêtre sur ce que l'on appelle la barre de titre).
Si notre fenêtre avait été un bouton, le texte se serait affiché sur le bouton tout simplement.
Le troisième paramètre est le style de la fenêtre. Ce sont des identificateurs sous forme de constantes qui sont définie
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. Pour en avoir la liste, vous pouvez consulter le site msdn
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 ou fenêtres
enfants afin de désigner leur parent. 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, nous nous contenterons de l'initialiser à 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êtres.
Ils sont de type HWND. Nous les rencontrerons très souvent,
il nous servirons d'accés aux fenêtres aux contrôles afin de les manipuler.
Comme nous allons le voir tout de suite.
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 en paramètre à la fonction 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);
La boucle de messages :
Revenons au fonctionnement de Windows.
Pour communiquer avec une application ou ses diverses fenêtres,
Windows leurs 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é
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é.
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.
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)(COLOR_3DFACE + 1);
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.
A vos PC.
CGi
Avec la contribution de Cerberes pour la relecture.
|