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)