#include <windows.h> #include <windowsx.h> int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { return(0); }Voila ! Une structure de base pour commencer à programmer sous Windows.
- Un élément d'interaction avec l'exterieur,
- Un élément pouvant recevoir ou envoyer des signaux,
- Un élément devant etre à même de réagir à ces signaux,
Nous avons vu précédement qu'il pouvait exister plusieurs types de fenêtres servant à plusieurs
types d'intéractions. Les différentes caractéristiques de chaque fenêtre doivent être indiquées dans
une structure de type WNDCLASS. Cette structure va nous permettre de définir un type de fenêtre
à part entière dont on pourra créer des exemplaires à volonté. Vous pouvez, par exemple, voir cette structure
comme un patron sur papier pour une maison définissant complètement les caractéristiques de votre maison
et permettant de créer autant d'exemplaire de cette maison que vous le désirez.
Voici la description de cette structure :
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
Ok, ca a l'air compliqué comme ça mais on va débrousailler tout ca ensemble.
En parcourant rapidement les champs de cette structure, on peut y voir des choses
HCURSOR, HICON, UINT style, etc ... Ces noms sont évocateurs et on devine qu'il
s'occupe de definir des handles ("H" en prefixe) sur curseur et icône, un style, etc ...
pour cette structure.
Vous pouvez facilement trouver une description détaillée des valeurs de ces champs
dans le MSDN (cf le lien au bas de cette page).
Remplissons les champs un par un :
- style : on choisi le style de notre fenêtre parmis un choix donné, j'ai choisi
de demander une fenetre qui redessine la fenêtre entière si jamais sa taille
change horizontalement et verticalement change,
wnd.style = CS_VREDRAW | CS_HREDRAW;
- lpfnWndProc : un pointeur sur la Wnd Proc (cf après),
wnd.lpfnWndProc = (WNDPROC) WndProc;
- lpszClassName : le nom de notre type de fenêtre,
wnd.lpszClassName = "Test";
- lpszMenuName : un pointeur optionnel sur une chaine de caractère désignant un menu associé à la fenêtre,
wnd.lpszMenuName = NULL;
- cbClsExtra : le nombre d'octets qui ont pu être rajoutés à la fin de la structure,
wnd.cbClsExtra = 0;
- cbWndExtra : le nombre d'octets rajoutés après l'instance de notre fenêtre,
wnd.cbWndExtra = 0;
- hbrBackground : un handle sur la couleur ou la brosse que doit avoir le fond de notre fenetre, je
la met a noir;
wnd.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH );
- hCursor : un handle sur un curseur a charger pour travailler avec notre fenetre, je charge
ici le curseur par defaut;
wnd.hCursor = (HCURSOR) LoadCursor(g_hInst, MAKEINTRESOURCE(IDC_ARROW));
- hIcon : un handle sur l'icone qui correspondra a notre application lorsqu'elle sera minimisée,
je choisi une icone par defaut,
wnd.hIcon = (HICON) LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_APPLICATION));
- hInstance : un handle sur l'instance de notre application;
wnd.hInstance = g_hInst;
Voila pour l'instant la tête de notre programme :
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
HINSTANCE g_hInst;
HWND g_hWnd;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
NDCLASS wnd;
nd.style = CS_VREDRAW | CS_HREDRAW;
nd.lpfnWndProc = (WNDPROC) WndProc;
nd.lpszClassName = "Test";
nd.lpszMenuName = NULL;
nd.cbClsExtra = 0;
nd.cbWndExtra = 0;
nd.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH );
nd.hCursor = (HCURSOR) LoadCursor(g_hInst, MAKEINTRESOURCE(IDC_ARROW));
nd.hIcon = (HICON) LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_APPLICATION));
nd.hInstance = g_hInst;
return(0);
}
Bon, maintenant que nous avons défini notre "type" de fenêtre, il va falloir l'enregistrer dans
aupres de Windows. Pourquoi l'enregistrer ? Et bien tout simplement pour informer Windows
que nous allons vouloir utiliser la chaine de caractere que nous avons associe a
notre Classe de Fenetre pour designer une fenetre ayant ces caracteristiques. Cet enregistrement
ou cette validation se fait simplement grace à la fonction RegisterClass qui prend en paramètre
un pointeur sur notre structure et qui renvoi zéro si un probleme est survenu :
if( !RegisterClass(&wnd) )
{
return(0);
}
Nous venons de définir notre propre "type" de fenètre mais il en existe un certain nombre
de prédéfini pour lesquels les opérations précédentes ont deja été faites et qui peuvent être
concernées par les points suivants.
4.2. La création de la fenetre.
Voila, donc pour notre enregistrement. Il nous faut maintenant créer notre fenêtre.
La création de cette fenêtre va se faire grâce à la fonction CreateWindow(). A l'aide de cet
appel de fonction, nous allons pouvoir "instancier" en quelque sorte un "objet" de la classe
de fenêtre que nous venons de définir au dessus. Ou encore, et plus simplement, créer une fenètre
visible à l'écran à partir du "patron" que nous venons de définir ci-dessus.
Cette fonction nous retournera un handle ( i.e. un identificateur unique ) sur notre fenêtre, la variable
globale definie précédement g_hWnd va nous permettre de stocker cet handle pour pouvoir l'utiliser
dans toutes les fonctions en relation avec notre fenetre.
Voici les paramètres que prend la fonction CreateWindow() :
HWND CreateWindow( LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hInstance,
LPVOID lpParam
);
Voici une explication plus détaillée des différents paramètres de cette fontion :
LPCTSTR lpClassName : pointeur sur le nom de la classe à laquelle appartient cette fenètre,
nous allons mettre ici la chaine de caractere qui nous a permis de designer notre nouvelle classe
precedement,
LPCTSTR lpWindowName : pointer sur une chaine de caractere correspondant au nom de notre fenetre,
il s'agit en fait de la chaine de caractere que vous pouvez lire en haut de chaque fenetre, par exemple :
DWORD dwStyle : les caracteristiques visuelles de notre fenetre, cela correspond a la maniere dont elle va apparaitre
pour l'utilisateur. On distingue plusieurs caracteristiques qui peuvent etre combinées dont voici les plus classiques :
WS_OVERLAPPED | Fenetre classique possedant une bande bleue au dessus ( CAPTION ) et une bordure autour |
WS_OVERLAPPEDWINDOW | Fenetre classique identique à la prédédente avec en plus des boutons minimiser/maximiser et un menu eventuel |
WS_POPUP | Fenetre "sans rien autour" : sans CAPTION, sans menu possible, etc ... On a juste la zone de visualisation centrale |
WS_POPUPWINDOW | Fenetre du type POPUP mais avec un bord et eventuellement un menu |
WS_MAXIMIZEBOX | Ajoute a une fenetre une petite boite maximiser |
WS_MINIMIZEBOX | Ajoute a une fenetre une petite boite minimiser |
WS_VISIBLE | Ajoute une fenetre qui est initialement visible |
WS_SIZEBOX | Fenetre ayant la possibilite de modifier sa taille |
WS_CHILD | Fenetre qui est affiliée a une autre par une relation mère-fille |
g_hWnd = CreateWindow("Test", "Ma première fenètre", WS_OVERLAPPEDWINDOW, 0,0, CW_USERDEFAULT, CW_USERDEFAULT, 0, 0, g_hInst, NULL ); if( !g_hWnd ) { return 0; }
LRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );Le CALLBACK indique bien qu'il ne s'agit pas d'une fonction comme les autres mais qu'il s'agit d'une fonction de "rappel" de type événementielle. Les parametres quant a eux nous renseignent sur le type de message recu et les parametres qui peuvent lui etre associés ( ex. : le numero de la touche pressee etc ... ).
WM_DESTROY | Indique que la fenetre a été detruite |
WM_CREATE | Indique que la fenetre vient d'être crée. Ce message est envoyé par Windows lors de la création de la fenètre. |
WM_COMMAND | Indique que la fenetre a recu un événement de type interaction ( clique, pression de touche, .. ) |
WM_CLOSE | Indique que l'utilisateur a emis le desir de quitter l'application |
WM_QUIT | Indique que l'application doit se terminer |
WM_DESTROY indique que la fenetre courante a été detruite mais ATTENTION, votre application est toujours
présente !!!! Une fenètre est crée par une application pour faire partie d'un ensemble de structures de visualisation
et d'interaction, elle n'est pas une application en soit et ce n'est certainement pas parce qu'on vient de la detruire
classiquement ( genre en cliquant sur la croix en haut a droite de la fenetre ) que l'application qui lui
est associée l'est, loin de la ( la raison vient de la boucle infinie de recupérartyion de message cf. plus loin §.4.4 ).
L'application continue de tourner en fond et va encombrer votre temps de CPU
car elle va, donc, continuer à s'éxécuter !!! Il faut donc que vous, oui vous, en tant que programmeur quittiez votre programme
lorsqu'il le faut. Donc, dans le cas ou vous souhaitez que vore application se termine lorsque
sa fenetre principale est detruite (vous n'etes pas obligez du tout ...si vous savez ce que vous faites)
, il vous faut intercepter le message WM_DESTROY et indiquer que votre application doit
se terminer. L'indication de fin d'application se fera avec un message WM_QUIT que vous devrez envoyer à votre application
au moment voulu.
Mais voyons ca de plus près pour que ca devienne plus clair.
Voici la tête de notre fonction de callback WndProc :
LRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch(uMsg){
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Lorsqu'on recoit un message de type WM_DESTROY, on appel la fonction PostQuitMessage(0) qui va
envoyer a notre application un message de type WM_QUIT pour lui indiquer qu'elle doit se terminer
car sa fenetre principale vient d'etre detruite. La fonction DefWindowProc() prend les memes parametres
que notre WndProc et correspond à un comportement par défaut pour tous les évènements reçus par notre
fenêtre. Tous les messages non traités par notre fonction de callback seront pris en charge avec un comportement
par defaut par cette fonction.
4.4. Mais comment récuperer ces messages ?
Il nous reste a regarder comment on peut récuperer les messages qui sont adressés a notre fenetres.
Cela va se faire a l'aide de quelques fonction simple d'utilisation et d'une nouvelle variable. Cette
variable va contenir un descriptif complet du message dernierement recu et va etre de type MSG. MSG est
une structure contenant un ensemble d'information sur le message reçu.
La majeur partie des programmes Windows va posseder une boucle de traitement devant prendre en charge
les messages provenant de l'exterieur. Cette boucle va etre infini : tant qu'on nous ne dit pas de
sortir, on continu a recevoir des messages et a les traiter ( et a faire les choses pour lesquelles
sont prevues notre programme quand même ). A l'intérieur de cette boucle nous allons trouver notre structure
de récupération des messages adressées a notre fenètre. Voici la tête que pourra avoir cette section du programme :
while(1){
if( PeekMessage(&msg, 0,0,0,PM_REMOVE) )
{
if( msg.message == WM_QUIT )
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
La fonction PeekMessage va aller chercher dans la file d'attente globale si un message pour notre
fenetre nou y attend. Si tel est le cas, elle va le ramener et placer des informations le concernant
dans la variable msg de type MSG. Voici une description rapide des paramètres de cette fonction :
LPMSG lpMsg : un pointeur sur une variable de type MSG,
HWND hwnd : un handle sur la fenetre dont on doit aller chercher les messages,
UINT wMsgFilterMin : numéro du premier message de la file devant etre examiné,
UINT wMsgFilterMax : numéro du dernier message de la file devant etre examiné,
UINT wRemoveMsg : indique si le message soit etre retiré de la file d'attente apres avoir ete lu : PM_REMOVE ou PM_NOREMOVE,
Comme vous les voyez, nous avons choisi d'ignorer les possibilités de filtrage, et de retirer le message
de la file après l'avoir lu.
Je voudrais vous signaler un autre point tres important : cette fonction est non bloquante. Cela veut
dire que si aucun message n'est adressé a notre fenetre, elle va retourner. Donc notre programme
va en somme consommer beaucoup de temps CPU dans sa boucle puisqu'il ne rendre pas gentillement
la main au systeme quand il n'a rien a faire. En gros, meme si notre programma n'a rien a faire
(aucun evenement en provenance de notre fenetre), il va tourner et consommer du temps. Une autre
alternative serait de gentillement rendre la main a Windows en lui laissant le soin de nous
la redonner quand quelquechose nous concernant survient. Ce mode de fonctionnement est tres important.
Dans ce dernier cas de figure, nous informons simplement Windows que nous attendons le prochain
message nous concernant. En attendant que celui ci arrive, Windows nous mets dans une file d'attente
dans laquelle on peut s'endormir tranquillement en sachant que le grand frere veille sur nous :).
Des qu'il le faut nous somme reveille et nous ouvons agir. Cette attente dite dynamique est realisee
a l'aide de la fonction : GetMessage().
BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax );
Comme vous le voyez les arguments sont a peu pres les memes que pour PeekMessage() a l'exception du dernier.
Vous allez vous demander en quoi la fonction PeekMessage() est interessante ? Et bien elle l'est dans le
cas d'une application qui ne peut perdre du temps et qui est toujours active, qui a toujours quelques
chose a faire. Ce type d'application va faire ses petites affaires dans son coin et une fois celle-ci
finie va de maniere plus ou moins periodique venir voir si il y a des messages qui l'attendent.
Ce type de comportement concerne tout ce qui est jeux, calcul scientifique, etc ...
La fonction utilisée est laissée au choix du programmeur.
La ligne suivante, le "if", nous permet de SORTIR de la boucle infinie lorsque notre fenetre principale a
été détruite. C'est cette ligne qui va nous garantir que nous allons pouvoir sortir de la boucle
infinie ( le "while(1)" ). En effet, dés que notre fenetre est détruite sa fonction Callback WndProc()
envoi un message WM_QUIT ( grâce au PostQuitMessage(0) ) qui nous permet de savoir que notre application doit
effectivement se terminer.
La fonction suivante permet, comme son nom l'indique, de translater le message reçu pour pouvoir, dans
certain cas le faire rentrer dans une categorie plus générale de message ( ex. WM_COMMAND, ... ).
La fonction DispatchMessage() , quant à elle, envoi le message reçu a notre WndProc(). C'est elle qui
place ce message dans la file d'attente et qui va réveiller notre WndProc().
Voici donc notre programme au complet :
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
HINSTANCE g_hInst;
HWND g_hWnd;
LRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch(uMsg){
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
WNDCLASS wnd;
MSG msg;
g_hInst = hInstance;
wnd.style = CS_VREDRAW | CS_HREDRAW;
wnd.lpfnWndProc = (WNDPROC) WndProc;
wnd.lpszClassName = "Test";
wnd.lpszMenuName = NULL;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH );
wnd.hCursor = (HCURSOR) LoadCursor(g_hInst, MAKEINTRESOURCE(IDC_ARROW));
wnd.hIcon = (HICON) LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_APPLICATION));
wnd.hInstance = g_hInst;
if( !RegisterClass(&wnd) )
{
return 0;
}
g_hWnd = CreateWindow("Test", "Ma première fenètre",
WS_OVERLAPPEDWINDOW, 0,0,
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, g_hInst, NULL );
if( !g_hWnd )
{
return 0;
}
ShowWindow(g_hWnd, SW_NORMAL);
UpdateWindow(g_hWnd);
while(1)
{
if( PeekMessage(&msg, 0,0,0,PM_REMOVE) )
{
if( msg.message == WM_QUIT )
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (0);
}
Un petit aperçu de ce que ça doit donner :
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 |
--
Document ecrit par ABREU Alexandre : wiss1976@yahoo.fr
Libre reproduction et diffusion autorisée - modifications interdites sans autorisation de l'auteur.
Précédent Suivant