Programmation Windows - Chap. 2 - La gestion des fenêtres


1. Introduction.

Nous avons vu dans le chapitre précédent un certain nombre de points fondamentaux concernant la manière dont windows fonctionne, concernant la notion de tâche, etc ... Maintenant, nous allons vraiment entrer dans le vif du sujet en abordant les Fenêtres. Windows 9x est un OS qui est souvent au centre de diverses polémiques concernant son manque évident de stabilité . Outre ces problèmes de stabilité, un autre point semble déranger, à justre titre à mon avis, un peu certaines personnes avec cet OS : il brouille les cartes quant a la definition d'un OS et concernant ses fonctionnalites de base. Windows 9x est, en effet, un OS liés ( ne serait ce que par son nom ) à son environnement graphique fenêtré. Avec ces fenêtres partout et en simplifiant la gestion globale des ressources et le contrôle que l'utilisateur peut avoir dessus , je dirais que Windows masque trop les vraies fonctionnalités de tout OS. Cela part d'un soucis évident pour rendre le PC et sa gestion accessibles à tous mais ça abouti à un masquage un peu extrême des choses. Bref, trop de choses se passent en sous-main. Mais bon, c'est un avis personnel.
Aller trève de blabla, travaillons un peu !

2. Le compilateur.

Je n'en ai pas parlé avant, je vais donc m'appesantir un peu sur cette question. Pour programmer sous Windows il existe plusieurs compilateurs qui sont à même de générer du code pour cette plate-forme. Il en existe des gratuits, des propriétaires et payants ( biensur ), des bons, des mauvais, etc ... Parmis les gratuits et bon, on peut trouver LCC ou CygWin qui sont tres bien. Parmis les payants, on trouve VisualC++, C++ Builder, etc ... J'utilise pour ma part VisualC++ version 6.0 qui est un excellent compilateur C/C++ et qui propose un environnement de developpement ( IDE, Integrated Development Environment ou encore Environnement de Development Integre ) très agréable et pratique.
Il ne suffit que de peu de temps pour faire le tour des fonctionnalités principales de VisualC++. Pour créer un projet pour Windows, il suffit d'aller dans le menu File>New. Une fenetre de dialogue apparait donc pour vous permettre de selectionner le type de projets que vous souhaitez. Les deux types de projets qui peuvent vous intéresser à priori sont "Win32 Application" et "Win32 Console Application". Le premier permet de créer des projets pour Windows. Le deuxième permet de créer des projets classiques en C ou C++. On le voit donc notamment ici, VisualC++ est un compilateur "comme les autres" en ce sens qu'il permet aussi la création de projet en C/C++ standard ( contrairement a ce que j'ai pu entendre des fois : "le langage VisualC++", etc ... ). En selectionnant "Win32 Application" comme type de projet et en lui donnat un nom ( ex. "Premier projet" ), nous allons pouvoir commencer à travailler. Ensuite, le compilateur doit vous demander si vous voulez un "Projet vide", "Un programme Win32 simple", etc ... Repondez "Projet Vide" ( Empty project ) pour vraiment voir les choses depuis la base.




Le projet est créé, il va falloir maintenant y inclure les fichiers que l'on veut y voir. Nous allons avoir besoin d'un fichier de type ".c", demandons le : Project>Add to project>New. Nous pouvons ici choisir n'importe quel type de fichier ou de ressources à inclure. Choisissez "C++ Source File" et donnez lui un nom : "main.cpp", par exemple. Notre nouveau fichier apparait, et vous pouvez constater sur l'image de votre WorkSpace ( à gauche ) que le fichier a été ajouté.



Nous sommes près !

3. Une structure de base.

Nous allons maintenant créer une fenêtre, notre première fenêtre ensemble !
Mais avant mettons en place un corps de programme essentiel à tout projet pour Windows :

#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.

4. Une première fenêtre.
4.1. La classe de notre fenêtre.

Une fenêtre est un élément graphique basique permettant l'interaction avec l'utilisateur à travers un ensemble de moyens. Cette notion est très large puisque Windows est un OS très très orienté graphique, les fenêtres y ont donc une place importante.
Comme je viens de le dire, chaque fenêtre est un élément de communication avec l'extérieur ( l'utilisateur ). Cette communication peut se traduire sous diverses formes : un dialogue (une interaction utilisateur-application), un message à caractere informatif ( messages ), etc ... En reflechissant un peu, une fenêtre va donc de toute façon devoir se préparer à recevoir un certain nombre de signaux traduisant les choix et les actions de l'utilisateur. Ok, on commence à voir le tout se préciser. En y regardant d'un peu plus près, notre fenêtre va devoir répondre à chaque stimulation par un comportement particulier ( qui peut être prédéfini par le programmeur ou être celui par défaut ). Bon résumons un peu, une fenètre c'est :

- 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,

Et bien, il nous suffit maintenant de tout mettre ca sous forme de code.
Dans un premier temps, définissons une macro permettant notamment d'accélérer les temps de compilation et d'éviter toute ajout de composante spécifique aux MFCs.

// on demande de ne rien inclure en relation avec les MFCs
#define WIN32_LEAN_AND_MEAN


Nous allons, aussi, avoir besoin de deux variables globales supplémentaires :

HWND g_hWnd;
HINSTANCE g_hInst;

La première variable correspond à un handle ( un identificateur ) pour notre fenêtre. Cette variable nous permettra de stocker cet identificateur des qu'il sera alloué par Windows. Il sera indispensable lors des phases de traitement de messages et de gestion de cette fenêtre particulière d'y avoir acces. Il va ainsi nous permettre de distinguer notre fenetre des autres.
La deuxième variable correspond, elle, à un handle sur l'instance de notre application. Cet identificateur va nous permettre de distinguer notre application parmis les autres. Ce handle nous est donné par le WinMain() et nous remplirons donc cette variable globale dés l'entrée dans le WinMain().
Je voudrais avant toute chose bien clarifier le fait que ces deux handles ne sont pas a confondre. Ils recouvrent bien deux choses differentes. Nous avons un handle unique pour notre application alors que nous pourrions avoir plusieures handle pour la partie VISIBLE de notre application a savoir les fenetres. La notion d'application est differente de cette de fenetre : qui dit application dit zero ou plusieurs fenetres alors que l'inverse n'est pas vrai.

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

Ces caracteristiques peuvent etre combinées ensemble grace a un "|" ( ou logique ). Exemple :
WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW va créer une fenetre fille, visible et classique.

int x : la position initiale en x de la fenetre dans l'ecran, en choisissant CW_USEDEFAULT windows va prendre des coordonnées par défaut,
int y : la position initiale en y de la fenetre dans l'ecran, idem pour CW_USEDEFAULT,
int nWidth : la largeur de la fenetre,
int nHeight : la hauteur de la fenetre,
HWND hWndParent : un handle sur la fenetr du parent si il y a,
HMENU hMenu : handle sur un menu,
HANDLE hInstance : handle sur l'instance de l'application,
LPVOID lpParam : pointeur sur des données devant être transmises lors de l'initialisation de la fenetre,


Voila le type de code qu'il va donc falloir rajouter :
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;
}

La fonction CreateWindow() renvoi 0 si jamais il y a eu un problème lors de la création de la fenêtre.

4.3. Et la WndProc alors ?

Avant de pouvoir savourer le résultat visuel de notre programme, il faut rajouter une chose très très importante. Vous vous souvenez de la fonction WndProc dont on a rapidement parlé lors de l'initialisation de la classe ? Et bien il va falloir regarder ca de plus près. Je vous ai deja dit précédement qu'une fenêtre est un élément qui doit interagir avec l'extérieur. L'exterieur designe tout ce qui est actions de l'utilisateur, messages provenant de Windows même, etc ... Ces interactions se font à travers des messages dont le type varie en fonction du type d'interaction qui l'a provoqué. Chaque fenêtre possede une file d'attente des messages qui lui sont adressés et qui doivent donc etre traités. Elle va chercher ses propres messages dans une file d'attente globale contenant tous les messages courant, toutes fenêtres confondues. Et le comportement de la fenetre en fonction du message va etre défini par la fonction WndProc dont on a parlé. Cette fonction est une fonction de type CALLBACK. Derrière ce mot barbare se cache une notion qu'on retrouve partout quelquesoit le système d'exploitation ( mutli-tâche ) à partir du moment ou celui-ci doit prendre en compte des événements et, donc, des actions automatiques à faire lorsqu'un événement est survenu. On retrouve encore une fois la notion de programmation événementielle. Ces actions peuvent etre définies par l'utilisateur ou bien prise en charges avec un comportement par defaut par l'OS. Notre fonction WndProc est donc la fonction qui sera appelée lorsqu'un message sera envoyé à notre fenètre.
Voici le prototype de cette fonction :

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 ... ).
Voici, comme à l'habitude un descriptif des differents parametres de cette fonction :
HWND hwnd : handle sur la fenetre concernee par ce message,
UINT uMsg : indenticateur du message. Les messages peuvent etre de plusieurs types selon l'événement qui les a provoqué. Voici quelques messages typiques :

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

Je voudrais m'attarder un peu sur quelques messages très importants : WM_QUIT, WM_DESTROY et WM_CLOSE. La gestion de ces message doit être reglée comme du papier a musique si vous ne voulez pas des problemes lorsque vous essayez de quitter votre application.
Pour commencer, je vais un peu clarifier les choses pour ce qui est des fermetures de fenetres. Quand on desire fermer une fenetre plusieurs moyens sont mis a notre dispositions. Mais quelques soient les choix effectues, les messages concernes sont plus ou moins les memes. Ainsi, dans le cas ou on essaierait de fermer une fenetre en appuyant sur la croix sur la barre de caption en haut a gauche de la fenetre, un message WM_CLOSE est envoye a la WndProc de la fenetre en question. Il indique et signifie que l'utilisateur a emis le desir a travers un element systeme (la croix) de fermer la fenetre. Ce message arrive AVANT que quoique ce soit ne soit fait pour fermer la fenetre et avant que windows ai commence le processus de fermeture. Ce message est donc la pour information et pour permettre au programmeur de placer des demandes/requetes particulieres a l'utilisateur a ce moment precis du type : confirmer la fermeture, sauver un fichier non sauve, etc ... et ce AVANT que la fenetre ne commence a se fermer. Si jamais WM_CLOSE n'est pas intercepte par la WndProc ou bien que le programmeur appel la fonction DestroyWindow() (cf ci-dessous), le processus continue et un message WM_DESTROY est envoye a la WndProc. Ce message indique que la fenetre est entrain de se faire detruire. Windows a donc commence son processus de fermeture et de liberation des ressources allouees par la fenetre et nous donne avec ce message une derniere (ou presque ) de liberer nos propres ressources associees a cette fenetre (timers, ...). Si on arrive a ce point la, c'est ici qu'on va placer le PostQuitMessage (cf ci dessous) si jamais la fenetre qui vient d'etre ferme est la fenetre principale de notre application.
Un petit schema pour clarifier le tout :

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 :




5. Conclusion.

Et voila, nous avons un programme valide qui lance une fenêtre classique et qui ne fait pas grand chose d'autres. Le plus important est que vous testiez vous même ces programmes et que vous les modifiez. La programmation sous Windows, une fois qu'on a les quelques bases nécessaires, se fait seul avec l'aide MSDN, par exemple, ou en regardant les sources des autres pour y découvrir différentes fonctionnalités nouvelles et les creuser nous mêmes par la suite.

La prochaine fois nous verrons les fenêtres de dialogues ( avec boutons, etc ... ) et leur utilisation.
Pour toutes questions, remarques, critiques, ... n'hésitez pas à me contacter.

A++



Liens interessants.

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