CHAPITRE 1 - Introduction à la programmation Windows

Première chose : n'oubliez pas qu'en tout temps, dans Visual C++, vous pouvez utiliser la touche F1 pour avoir de l'aide sur un mot-clef qui vous échappe. Cette touche sera votre amie lors de votre apprentissage de la programmation Windows. Dans ce premier tuteur, pas question d'aborder directement la programmation avec DirectX. Nous allons tout d'abord étudier la structure d'un programme Windows, qui diffèrent beaucoup de la programmation traditionnelle DOS. En effet, Windows est un système d'opération offrant une interface usager. Les programmes doivent donc savoir répondent aux interactions de cet usager.

PROGRAMMATION ÉVÈNEMENTS

Dans les programmes traditionnels, nous suivons une structure séquentielle, c'est à dire que le programme à un point d'entré 'A', et qu'il parcours, lignes par lignes, les instructions du programme, jusqu'au point ' B'. Dans le cas de programme Windows, les programmes interprètent et réagissent aux actions de l'usager. Par exemple, si l'usager clique sur un bouton de commande, Windows envoie à notre programme l'événement " clique sur bouton ". Notre programme devra donc, en un premier temps, intercepter ce signal et par la suite déclencher le code que nous avons associé à cet événement (clique sur bouton).

Si vous avez déjà fait de la programmation en Visual Basic, vous êtes déjà familier avec le concept d'événements. Sinon, il faudra vous habituer à cette nouvelle façon de penser. Par exemple, supposons que nous voulons écrire un programme pour faire une tasse de café, dans la programmation séquentielle, voici le pseudo-code qu'on obtiendrait :

1. Remplir la bouilloire avec de l'eau.
2. Allumer la bouilloire.
3. Mettre le café dans une tasse.
4. Mettre le lait dans la tasse.
5. Attendre que l'eau bouille.
6. Mettre l'eau dans la tasse.

Assez simple et directe comme méthode. Maintenant, la même opération en programmation par événements donnerait le code suivant :

1. Montrer le café, la bouilloire, le lait et la tasse à l'usager.
2. Laisser l'usager faire son café

L'usager peut utiliser chacun des objets présent, dans n'importe quel séquence qu'il désire, pour faire son café. En tant que programmeur dans cette situation, vous n'avez qu'a écrire des fragments de code pour gérer certains événements. Par exemple, quand l'usager allume la bouilloire, l'ordinateur exécutera le code que vous avez attacher à cet événement particulier.

PROGRAMMATION WIN32

Un programme Windows, en général, se compose principalement de 3 composantes: de gestionnaires d'événements, de la fonction WinMain, et de dialogues. Les gestionnaires d'événements s'occupent de traduire les messages que le système d'exploitation envoie (clique de souris, touche du clavier, déplacement de fenêtre, etc.). La fonction WinMain remplace la traditionnelle fonction main() en C/C++. Les dialogues sont les messages, les menus, les boîtes d'options, bref tout ce qui interagit directement avec l'usager. Les dialogues sont une partie très importante et vaste de la programmation Windows; cependant, nous allons nous contenter de la base, puisque nous ne voulons pas ici devenir des experts de programmation Windows, mais bien comprendre la base du fonctionnement d'un programme sous Win32.

- WinMain :

Dans la fonction WinMain, nous exécutons généralement 3 opérations : nous enregistrons la classe " fenêtre " (window), nous créons la fenêtre principale et nous exécutons le gestionnaire d'événement. Voici la déclaration de la fonction WinMain :

int WINAPI WinMain(HINSTANCE hinstance,
  HINSTANCE hprevinstance,
  LPSTR szCmdLine,
  int nCmdShow);

hInstance: Il s'agit d'une handle (une référence, un pointeur) sur l'instance du programme. Cette handle est utilisé pour enregistrer la classe window.

hPrevInstance: Ce paramètre était utilisé sous Win16, par Windows 3.x. Aujourd'hui, elle est obsolète, et ce handle est toujours égal à NULL.

szCmdline: Il s'agit d'une chaîne de caractère contenant les arguments du programme.

nCmdShow: Cet argument défini l'état initial de la fenêtre. Il existe plusieurs état mais en voici quelques uns:

SW_SHOWNORMAL - Ouvre la fenêtre avec la taille et position par défaut. SW_SHOWMAXIMIZED - Ouvre la fenêtre maximisée.
SW_SHOWMINIMIZED - Ouvre la fenêtre minimisée

- Gestionnaire d'événements

Il existe 2 types de gestionnaire d'événements : pour les fenêtres et pour les dialogues. Dans les 2 cas, leurs buts est d'interpréter et de réagir aux messages que Windows envoie au programme. En voici la déclaration :

LRESULT CALLBACK WindowProc(HWND hwnd,
     UINT msg,
     WPARAM wparam,
     LPARAM lparam)

Regarder le type de retour et le modificateur de type. LRESULT est le type de retour de la fonction (un entier non-signé sur 32-bit), et CALLBACK signifie que cette fonction sera appelée par Windows quand l'événement sera déclenché (sans entrer dans les détails). Regardons maintenant la liste des paramètres

hWnd : Il s'agit du handle de la fenêtre qui envoie le message.

msg : Un code ou un identificateur du message de Window étant traité.

wParam, lParam : Permet de préciser le message. Il peut s'agir d'un nombre, d'un identificateur, d'une sous-classe, etc.

Un gestionnaire d'événement pour les dialogues fonctionnent exactement de la même manière, sauf qu'il reçoit le handle de la boîte de dialogue plutôt que celui de la fenêtre. Nous verrons un peu plus loin comment créer des dialogues avec Visual C++.

PREMIER PROGRAMME WINDOWS

Afin de garder contact avec le réel, il serait temps à présent d'essayer d'écrire un premier programme Windows. Nous allons faire un petit programme très simple qui affiche un dialogue permettant la création d'une fenêtre, de jouer un fichier .WAV ou de quitter. Tout d'abord, il faudra crée un nouveau projet. Démarrer Visual C++, et aller choisir dans le menu " Fichier " l'option " Nouveau ". Dans ce nouveau menu, choisissez l'onglet " Projet ", puis créez une nouvelle application Win32. VC++ va ensuite créer un espace de travail ( à gauche ) et votre nouveau projet. Pour crée un fichier source, choisissez le menu " Projet ", puis l'options " Ajouter au projet ". Dans l'onglet " Nouveau ", choisissez " Source C++ ". À droite apparaîtra alors une page blanche, dans laquelle vous pourrez entrer votre code source.

Création des dialogues

Il est préférable de créer l'interface usager avant de commencer un programme. L'interface sera logée dans une ressource, c'est à dire que lors de la compilation, le menu sera ajouté à la fin de l'exécutable. Vous pouvez crée des ressources pour n'importe quel fichier externe à votre programme (bitmap, sons, vidéo, dialogues, etc.) Nous allons tout d'abord créer un dialogue, que nous allons incorporer comme ressource dans notre projet. Choisissez le menu " Projet ", " Ajouter au projet ", puis " Nouveau " -> Resource Script. Ensuite, dans le menu " Insert ", choisissez " Ressource " (ou tout simplement, Ctrl-R). Vous aurez ensuite le choix entre plusieurs type de ressources, sélectionnez " Dialogues ", puis " Nouveau ".

Vous devriez par la suite voir un éditeur de dialogue apparaître à votre droite, dans l'espace de travail. Il vous permettra de créer rapidement des boîtes de dialogues, un peu comme dans Visual Basic. Vous sélectionnez dans la palette à droite l'objet que vous voulez ajouter, puis voulez cliquez sur la boite de dialogue pour le crée, pour le déplacer et pour changer sa taille. Crée un champs texte, ainsi que 3 boutons de commandes, afin que votre boîte de dialogue ressemble à ceci :


Une fois ceci en place, double-cliquez sur le dialogue (pas sur un bouton ou le champs texte, mais à côté!). Un petit menu devrait apparaître et vous pourrez ensuite changer son " ID ", un identificateur dans la ressource. Quand vous allez référez dans votre code source à cet objet, vous utiliserez cet identificateur. Je l'ai appelé " IDD_MENU ". J'ai changer aussi sa propriété " Caption ", le texte qui est écrit dans la barre supérieur de la fenêtre. Faites de même pour les boutons (ID_WIN, ID_SND, ID_QUIT), ainsi que pour le champs texte (IDC_TEXT).

À présent, si vous regardez dans votre espace de travail à gauche en bas, vous allez remarquer qu'un onglet s'est rajouté. L'onglet " ResourceView ". Dans cet onglet, vous pouvez voir la liste des ressource que votre programme contient, et vous pouvez en tout temps les consulter ou les modifier à partir de ce menu.

Code source

Nous allons à présent commencer l'étude du code source. Nous allons étudier lignes par lignes les étapes nécessaires à la création de ce programme très simple. Commençons tout d'abord par les fichiers à inclure :

#include <windows.h>       // Le include de base de tout programme Windows
#include "resource.h      // Le fichier ressource que Visual C a crée pour nous
#define MESSAGE "Voici une nouvelle fenêtre!" // Message contenu dans la fenêtre

La ressource que nous avons crée avec Visual C++ nous a générer un header, resource.h, qui contient les identificateurs des différents composant de notre ressource. Vous devez absolument inclure cet entête pour que la ressource fonctionne correctement. Le fichier " windows.h " est le fichier standard pour la programmation windows, contenant un éventails de .h très utiles.

HINSTANCE    hInst   = NULL;     // Variable qui contient l'instance de notre programme
HWND hwnd    = NULL;            // Le 'handle' de notre fenêtre
WNDCLASS  fenetre;             // Notre objet fenêtre

Ensuite vient la déclaration de 3 variables globales. hInst va contenir un handle sur l'instance de notre programme, qui servira à l'instanciation de notre fenêtre. hWnd est une référence à notre future fenêtre principale, et fenêtre est une instanciation de la classe fenêtre standard de Windows. Regardons ensuite la fonction principale du programme, WinMain() :

// Procedure principale (Win)Main
int WINAPI WinMain(			
     HINSTANCE hInstance,         // handle sur l'instance présente
     HINSTANCE hPrevInst,        // handle sur l'instance précédente (Win 3.1, obsolète)
     LPSTR lpCmdLine,           // ptr sur la ligne de commande (ie: argv[], argc)
     int nCmdShow)             // l'état de la fenêtre

{
	                        // définition des paramètre de la classe fenêtre
	fenetre.style          = CS_DBLCLKS | CS_OWNDC | 
    CS_HREDRAW | CS_VREDRAW;
	fenetre.lpfnWndProc    = WindowProc;
	fenetre.cbClsExtra     = 0;
	fenetre.cbWndExtra     = 0;
	fenetre.hInstance      = hInstance;
	fenetre.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
	fenetre.hCursor        = LoadCursor(NULL, IDC_ARROW);
	fenetre.hbrBackground  = GetStockObject(WHITE_BRUSH);
	fenetre.lpszMenuName   = NULL;
	fenetre.lpszClassName  = "Classe fenetre";

	                 // enregistrer la classe fenêtre
	if (!RegisterClass(&fenetre)) return(0);
     
	              // sauvegarder l'instance de notre programme
	hInst = hInstance; 
	            // Activer la boîte de dialogue
DialogBox(hInst, MAKEINTRESOURCE(IDD_MENU), NULL, DlgProc);
  
return (0);
}

En premier lieu, nous allons définir les paramètres de notre classe fenêtre, qui va servir lors de sa création. Voici la liste des champs qui vont définir cette fenêtre :

Style : Défini les propriétés générales de la fenêtre. Ce champ contrôle l'apparence de la fenêtre, si on peut la déplacer, changer sa taille, quel genre d'événements elle peut gérer, etc. Dans ce cas, on utilise les plus standard, car il en existe des dizaines. Nous disons à la fenêtre d'envoyer des messages quand il y a double-click, quand elle est déplacer, et on lui assigne son propre device context, qui accélère le processus de rafraîchissement de la fenêtre.

LpfnWindowProc : Pointeur sur le gestionnaire d'événement de cette fenêtre. Cette fonction va traiter tous les messages envoyés par Windows. Dans notre cas, nous l'assignons à WindowProc, que nous étudierons un peu plus loin.

CbClsExtra et cbWndExtra : Des paramètres supplémentaires que Microsoft a laissé libre aux programmeurs, bien que personne ne s'en servent vraiment. Généralement initialisé à 0.

hInstance : Utilisé pour sauvegarder l'instance de notre application, initialisé au même paramètre hInstance de WinMain.

hIcon et hCursor : Il s'agit de l'icône de la fenêtre et du curseur de la souris. Il existe plusieurs standards qui définissent leurs apparences, et les fonctions LoadIcon et LoadCursor reçoivent un identificateur afin de reconnaître quel utiliser. Remarquez qu'il est possible, grâce aux ressources, d'inclure des icônes et des curseurs personnalisés.

hbrBackGround : Il s'agit de la palette de couleur choisi pour afficher la fenêtre. GetStockObjet nous retourne la palette désiré, un objet par défaut du système d'exploitation (WHITE_BRUSH).

lpszMenu : Nom de la ressource contenant un menu attaché à cette fenêtre. Dans notre cas, nous n'avons aucuns menu, donc nous l'initialisons à NULL.

LpszClassName : Tout simplement le nom de la classe, afin d'identifier la bonne classe si votre programme en contient plusieurs.

Une fois notre classe initialisé, nous devons annoncer sa présence à Windows en enregistrant cette classe. La fonction RegisterClass reçoit l'adresse de notre classe et si tout est beau, enregistre notre classe fenêtre. Par la suite, comme nous en aurons souvent besoin, il est utile de sauvegarder l'instance de notre programme dans une variable globale, et c'est ce que nous faisons.

La dernière étape de WinMain consiste, dans notre cas, à activer la boîte de dialogue que nous avons créer avec l'éditeur de VC++. La fonction se nomme judicieusement DialogBox, et elle reçoit en paramètre l'instance de notre application, la ressource ou le fichier contenant notre dialogue, ainsi qu'un pointeur sur le gestionnaire d'événement de ce dialogue (DlgProc). Remarquer le deuxième paramètre est précédé par la macro MAKEINTRESOURCE, qui permet de référencer un objet dans la ressource, grâce à son identificateur. Par exemple, dans notre cas, nous avons nommé le dialogue ID_MENU, et la macro se sert de cet ID pour ajouter la ressource à l'intérieur de l'éxécutable. Regardons maintenant le gestionnaire d'événements du dialogue de plus prêt :

LRESULT CALLBACK DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)  
{
  switch(msg)
  {
  // Un bouton de commande (le # du bouton est contenu dans le low 8-bit de wParam)
  case WM_COMMAND: 
  {                                                       
switch(LOWORD(wParam))
	{
	// Nouvelle fenêtre
	case ID_WIN:
	{
		CreeFenetre();     
		break; 
	}
	// Son .WAV
	case ID_SND:
	{
PlaySound(MAKEINTRESOURCE(IDR_WAV),hInst,SND_RESOURCE | 
        SND_ASYNC);
	break;
	}
	// Quitter
	case ID_QUIT: 
	{
		EndDialog(hwnd, FALSE);
		break;
	}
      }
      break; 
   }
   default:  return FALSE; 
  }
  return TRUE; 
}

Comme vous voyez, nous utilisons un énoncé switch pour interpréter le message reçut en paramètre par le gestionnaire. Ces messages sont regroupés sous la forme de constante, en voici une liste partielle :

CONSTANTE	EXPLICATION
WM_ACTIVATE  Envoyé lorsqu'une fenêtre est activée ou qu'elle reçoit le focus
WM_CLOSE     Envoyé quand une fenêtre est fermée
WM_MOVE      Envoyé quand une fenêtre est déplacée
WM_CREATE    Envoyé quand une fenêtre est crée
WM_PAINT     Envoyé quand la fenêtre à besoin d'être rafraîchie
WM_SIZE      Envoyé quand la fenêtre change de taille
WM_QUIT      Envoyé quand une application est terminée
WM_COMMAND   Envoyé lorsqu'un bouton de commande est enfoncé
WM_USER      Message envoyé et défini par le programmeur

Dans notre gestionnaire d'événements, nous ne sommes pas obligé de gérer l'ensemble des messages que Windows envoient. Il existe un gestionnaire par défaut dans Windows. Cependant, nous redéfinissions les événements que nous voulons gérer nous-mêmes, comme par exemple si un bouton de commande est enfoncé. En utilisant leurs ID dans la ressource, nous pouvons réagir correctement aux événements de l'usager. Examinons le premier de ces événements, la création d'une fenêtre :

void CreeFenetre()
{
                      // Si elle existe déjà
  if (hwnd)
  {
                   // Affiche une boîte de dialogue
   MessageBox(hwnd,"Vous ne pouvez pas créer plus qu'une fenêtre!",
   "Erreur",MB_OK | MB_ICONEXCLAMATION);
   return;
  }
              // Création de la fenêtre et assignation de l'handle de la fenêtre
  hwnd = CreateWindow("Classe fenetre",       // nom de la classe
        "Creation de fenêtre",               // titre de la fenêtre
WS_OVERLAPPEDWINDOW | WS_VISIBLE,           // apparence
0,0,                  // x,y
320,240,             // largeur, hauteur
NULL,               // handle a son parent
NULL,              // handle a son menu
hInst,            // instance
NULL);           // paramètres de création
}

Tout d'abord, nous nous assurons que la fenêtre n'existe pas déjà, en vérifiant si son handle est NULL. Dans ce cas, nous affichons un MessageBox, et nous quittons la fonction. Sinon, la handle de la fenêtre sera initialisée par la fonction qui crée la fenêtre, nommé naturellement CreateWindow(). La liste des paramètres s'explique d'elle-même. Premièrement, le nom de notre classe enregistrée, ensuite le titre de la fenêtre (ce qui sera écrit en haut de celle-ci), le style de la fenêtre, sa position, sa taille, un handle sur la fenêtre parent (en cas d'une application à multiples fenêtres, MDI), un handle sur son menu (si c'est le cas), et l'instance de WinMain (notre application).

Une fois la fenêtre crée, le gestionnaire d'événement qui lui est associé se mets en marche. Regardons ce gestionnaire ainsi que les événements qu'ils gèrent :

LRESULT CALLBACK WindowProc(HWND hwnd,              // Handle de la fenêtre
     UINT msg,                                     // message du système
                          WPARAM wparam,          // paramètre supplém
     LPARAM lparam)                              // paramètre supplém
{
  PAINTSTRUCT    ps;                           // utiliser par l'évènement WM_PAINT
  HDC            hdc;                         // handle sur un 'device context'

                                            // analyse des messages
  switch(msg)
  {
    case WM_PAINT:
    {
hdc = BeginPaint(hwnd,&ps);
TextOut(hdc,70,90,MESSAGE,strlen(MESSAGE));
EndPaint(hwnd,&ps);
return(0);
   } break;

  default:break;
  }

  // les messages que nous ne traitons pas sont dirigés par Window
  return (DefWindowProc(hwnd, msg, wparam, lparam));
}

Nous ne traitons qu'un seul message dans notre cas, WM_PAINT, qui est activé lorsque la fenêtre à besoin d'être rafraichîe (un déplacement, une fenêtre par dessus elle, etc.) Remarquer que tous les messages qui ne sont pas traités par notre fonction le sera par celle par défaut de Windows, DefWindowProc. Lors de l'événement WM_PAINT, nous allons recevoir une référence sur une méthode nommé BeginPaint, qui permet de dessiner sur notre fenêtre. Nous allons simplement envoyé du texte sur cette fenêtre, et lorsque nous avons terminé, nous libérons le handle avec EndPaint.

JOUER DES SONS .WAV

Le deuxième bouton de commande permet de jouer un fichier sonore .WAV qui est contenu comme ressource dans notre projet. Il faudra donc commencer par ajouter un son dans notre ressource, ce que nous faisons avec Ctrl-R. Choisissez " Import ", et aller cherche un fichier .WAV. Nous avons maintenant ajouter un fichier sonore dans notre ressource. Nommez-le IDR_WAV, et nous sommes maintenant prêt à l'utiliser lorsque le bouton ID_SND sera enfoncer. Voici la fonction PlaySound qui fait jouer ce .WAV :

PlaySound(MAKEINTRESOURCE(IDR_WAV),hInst,SND_RESOURCE | SND_ASYNC);

Vous remarquerez encore une fois la macro MAKEINTRESOURCE, qui va cherche notre IDR_WAV. Ensuite viens l'instance de notre application, une constante qui indique que le son se trouve dans une ressource (SND_RESOURCE, SND_FILENAME), ainsi que des flags gérants le playback de ce son. Utiliser F1 pour voir la liste des flags et leurs explications.

Le programme est presque terminé. Cependant, il y a 2 autres petits détails à régler. Premièrement, la fonction PlaySound se trouve dans une librairie multimédia de Windows, nommé winmm.lib. Il faudra spécifier à VC++ que nous allons en avoir besoin. Faite Alt-F7 (ou Projet->Settings). Dans l'onglet " Link ", vous aurez dans Objets/Librairie Module une liste de .lib (kernel32.lib, gd32.lib, etc.) Ajoutez en premier " winmm.lib ", et VC++ l'incorporera dans votre projet.

Par défaut, vous compilez votre programme en mode " debugging ", c'est à dire que le compilateur ajoute des informations de debug pour vous permettre d'utiliser son débuggeur pour corriger vos programme. Cependant, une fois votre programme tester et terminer, utiliser la configuration de debug résulte en du code beaucoup plus gros (inutilement). Pour changer de configuration active, choisissez le menu " Build ". Ensuite, choisissez " Set Active Configuration ". Sélectionnez " Win32 Release ", et votre programme ne contiendra pas d'information superflu, donc sera plus petit.

CONCLUSION

Pour compiler le code source inclus avec ce tuteur, utiliser le fichier .DSW. Il s'agit de mon fichier WorkSpace qui vous permettra alors de compiler facilement le code source (si vous possédez VC++ version 6.0 et +, sinon il se peut que vous aillez à créer vous même un fichier projet et à inclure le .CPP et les ressources). Il vous sera sûrement nécessaire de relire ce documents quelques fois et d'expérimenter avec le code source avant de tout saisir. Comme ces informations sont la base de la programmation Windows, il est essentiel de bien les comprendre.

En résumé :

· La programmation par événements
· La procédure WinMain()
· Les gestionnaires d'événements (WindowProc, DlgProc, DefWindowProc)
· La création de dialogues et de ressources
· Les configurations et l'inclusion de librairie (winmm.lib, ddraw.lib)