Index du site
Google

Explication d'un programme Windows en C

Copie d'écran

L'archive zippée tec002.zip contient les fichiers de la page.

Ceci est un programme d'introduction relativement simple et c'est le seul à être expliqué en détails. Donc si vous n'êtes pas habitué à la programmation Windows en C, commencez par lire cette page en entier ainsi que les fichiers qui l'accompagnent. Il est facile de partir de ce programme et de l'améliorer en lui greffant les éléments traités dans d'autres pages du site - contrôles, glisser-déposer, ouverture de fichier, etc.

Ce programme est compilé en Unicode, un jeu de caractères international. L'interface graphique comprend une fenêtre principale, deux boutons, un menu simple, un contrôle static en fond et un contrôle d'édition multiligne. Cliquer sur un bouton (ou sélectionner la contrepartie dans le menu Outil) entraîne l'affichage ou l'effacement d'une chaîne de caractères dans le contrôle d'édition.

haut de page  Principe

Messages

Apple parle d'événements, Microsoft parle de messages, mais cela revient au même. Le programme démarre et se met en boucle, jusqu'à ce que quelque chose se produise: l'utilisateur interagit avec le programme (menu, bouton, etc.), et en fin de compte le programme est arrêté. Un message est envoyé par le système d'exploitation Windows à l'application à chaque fois que l'utilisateur clique sur un bouton, sélectionne un item dans un menu, déroule une combobox, écrit dans une zone d'édition, etc. Ce sont les messages les plus visibles, il y en a beaucoup d'autres: au lancement de l'application (WM_CREATE), à chaque fois que la fenêtre principale est redimensionnée (WM_SIZE), dès qu'il faut repeindre tout ou partie d'un contrôle (WM_PAINT), au moment ou nous quittons l'application (WM_CLOSE et/ou WM_DESTROY). Certains messages, pour des contrôles comme le tab ou les contrôles avec style owner-draw, sont plus complexes.

Une chaîne de messages typique est le clic sur un bouton, qui déclenche un message WM_COMMAND de Windows vers l'application concernée, accompagné de l'identifiant du bouton (par exemple IDC_BOUTON) et d'une notification de clic bouton BN_CLICKED - sans oublier une rafale de WM_PAINT.

Certains messages seront envoyés par l'application elle-même, charge à nous de les implémenter, par exemple: modifier le texte d'un bouton, mesurer puis récupérer le contenu d'un contrôle d'édition, vider ou dérouler une listbox, et quelques milliers d'autres.

Création des contrôles

Sur le modèle de la fenêtre principale - mais pas au même endroit - les boutons, contrôles d'édition, static et autres sont créés au moyen d'appels à la fonction CreateWindow (ou CreateWindowEx - Ex pour extended) en spécifiant la classe de contrôle (edit, button, static, etc.), ses dimensions et, entre autres informations, son style.

Le style est construit en combinant les constantes au moyen de l'opérateur "ou" bit à bit (|), par exemple un contrôle enfant et visible sera affecté de WS_CHILD | WS_VISIBLE, un bouton plat sera BS_FLAT, un contrôle d'édition multiligne sera ES_MULTILINE, une combobox que nous voulons toujours triée aura le style CBS_SORT, etc. Dans tous les programmes Windows de ce site, les dimensions et coordonnées (x, y, hauteur, largeur) sont à zéro. La raison en est simple et figure dans ce qui suit.

Dimensionnement des contrôles

Le message WM_SIZE est envoyé par Windows à l'application à chaque modification de la taille d'une fenêtre, et en particulier lors de sa création. C'est idéal pour calculer la taille relative d'un contrôle par rapport à la fenêtre parent, et traiter du même coup la question de la taille d'un bouton ou d'un contrôle d'édition tout au long du fonctionnement du programme. La première chose que l'utilisateur va faire après avoir démarré le programme, c'est modifier la hauteur ou la largeur de la fenêtre principale. Et ne songez même pas à écrire des programmes dont l'interface graphique n'est pas retaillable, ce serait une erreur.

En pratique, la largeur de la zone client de la fenêtre principale, qui est aussi la fenêtre parent des contrôles (édition, bouton, etc.), vaut LOWORD(lParam), sa hauteur vaut HIWORD(lParam) (les macros LOWORD et HIWORD servent à extraire des parties d'un entier long non signé, voir le fichier d'en-tête windef.h). Dans le fichier d'en-tête du programme nous avons défini des constantes pour la marge entre deux contrôles, la largeur d'un bouton et autres valeurs utiles. À partir de là, il est facile de régler la taille et la position des contrôles en fonction de ce qui précède, par des appels à la fonction MoveWindow.

Nous allons maintenant passer en revue les fichiers du programme, histoire de mettre les principes qui précèdent en perspective par rapport au code.

haut de page  Fichier d'en-tête tec002.h

Le fichier tec002.h sert, comme tout fichier d'en-tête, à définir des variables et fonctions globales, ainsi que des macros. Définir la macro __TEC002_H__ au début permet d'inclure ce fichier une seule fois, quel que soit le nombre de #include "tec002.h" rencontrés par le compilateur. De nombreuses chaînes de caractères sont définies sous forme de macros dans le fichier d'en-tête, donc nous retrouvons peu ou pas de texte dans les fichiers source. C'est un peu plus compliqué mais très pratique si vous voulez traduire un programme dans une autre langue. Windows offre aussi la possibilité de déclarer des chaînes de caractères sous forme de ressources (string resources). La fonction MessageBox est substituée par "msgb", plus commode à écrire dans le code source mais limitée car affichant une boite avec OK et un message.

haut de page  Fichier makefile tec002.mak

Le fichier tec002.mak sert à fabriquer le programme en une fois, en spécifiant les options. Le début du makefile contient la déclaration des fichiers source, du fichier exécutable, le compilateur, les fichiers objet, l'inclusion du fichier d'en-tête, sous forme de variables - par exemple CC que nous utiliserons plus loin pour nommer le compilateur par la syntaxe $(CC):

SRC = tec002.c tec002_msg.c
EXE = tec002.exe
CC = gcc
OBJ = tec002.o tec002_res.o tec002_msg.o
INCL = -include tec002.h

Les trois options suivantes obligent le compilateur à (i) vérifier le maximum d'informations (-Wall), à (ii) traiter le texte des fichiers à compiler comme composés d'un jeu de caractères latin (iso-8859-1) qu'on retrouve d'ailleurs en haut du code source de certaines pages web dans la balise <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" /> , et à (iii) lier la bibliothèque des common controls, comctl32. L'option -g est importante si on veut compiler une version du programme à déboguer (voir l'onglet Outils); elle figure derrière un # (dièse) dans notre exemple, car elle est désactivée - le caractère # transforme une ligne en commentaire:

# (debug) CFLAGS = -g -Wall
CFLAGS = -Wall
UNICODEFLAGS = -finput-charset=iso-8859-1
LIB = -lcomctl32

Ensuite, la règle all est déclarée comme suit:

all: res
	$(CC) $(UNICODEFLAGS) $(CFLAGS) -c $(INCL) $(SRC)
	$(CC) -mwindows -o $(EXE) $(OBJ) $(LIB)

Cette règle génère le code objet puis l'exécutable, en un appel au compilateur de ressources puis par deux commandes gcc. Notez que les tabulations en début d'instruction sont indispensables.

La compilation du fichier de ressources, repérable par la règle res, est faite non par gcc mais par windres:

res:
	windres -o tec002_res.o tec002.rc

La règle clean permet simplement de supprimer tous les fichiers objet par une commande del (similaire à la commande Unix rm):

clean:
	del $(OBJ)

Voici la commande pour générer le programme, à lancer soit sous "Invite de commandes" Invite de commandes, soit à partir de l'éditeur de texte:

mingw32-make all -f tec002.mak

De même, la règle clean de nettoyage est appelée comme ceci:

mingw32-make clean -f tec002.mak

Quand le programme est plus complexe et nécessite par exemple de compiler de gros fichiers source, il est possible d'effectuer des compilations partielles, en transformant en fichiers objet les fichiers source récemment modifiés.

haut de page  Fichier de ressources tec002.rc

Le fichier tec002.rc contient les déclarations du menu, de l'accélérateur et de l'icône du programme. Le langage de déclaration de ce type de fichier est particulier, voici par exemple les éléments du menu, à base de BEGIN / END:

ID_MENU MENU
BEGIN
    POPUP CH_MENUITEM_FICHIER
    BEGIN
        MENUITEM CH_MENUITEM_QUITTER, IDM_QUITTER
    END
    POPUP CH_MENUITEM_OUTIL
    BEGIN
        MENUITEM CH_MENUITEM_AFFICHER, IDM_AFFICHER
        MENUITEM CH_MENUITEM_EFFACER, IDM_EFFACER
    END
    POPUP CH_MENUITEM_AIDE
    BEGIN
        MENUITEM CH_MENUITEM_APROPOS, IDM_HELPABOUT
    END
END

haut de page  Fichiers source

Le fichier tec002.c

Il contient deux fonctions. La première sert au programme à traiter les messages et notifications:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

Les paramètres, assez obscurs de prime abord, sont interprétés dans le code et décortiquées par des macros comme LOWORD(wParam) qui fournit, si le message est WM_COMMAND, l'identifiant du contrôle concerné par le message (IDC_BOUTON par exemple, défini dans le fichier d'en-tête de notre application) ou HIWORD(wParam) qui peut être interprété comme une notification. D'une manière générale, les lParam et wParam peuvent fournir toutes sortes d'informations utiles et qui varient selon la nature du message. Ces messages peuvent être WM_CREATE qui est une constante qui vaut 1, WM_PAINT qui vaut 15 ou encore WM_COMMAND égal à 273 et bien d'autres, définis dans winuser.h, un des nombreux fichiers d'en-tête de l'environnement de développement.

La deuxième est l'équivalent d'une fonction main et constitue le point d'entrée et de sortie de notre programme:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

Ici, la classe de la fenêtre est enregistrée, avec les informations nécessaires à son fonctionnement - style de classe, icône(s), pointeur de souris, couleur d'arrière-plan:

wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = WndProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ID_APP_ICON));
wcx.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(ID_APP_ICON));
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wcx.lpszMenuName = NULL;
wcx.lpszClassName = sNomApp;

if (!RegisterClassEx(&wcx)) return(0);

La fenêtre principale est créée, affichée et mise à jour, avec des coordonnées et dimensions calculées à partir des caractéristiques de l'écran - par exemple, GetSystemMetrics(SM_CXSCREEN) renvoie la largeur de l'écran en pixels, GetSystemMetrics(SM_CYSCREEN) sa hauteur. Le style WS_OVERLAPPEDWINDOW est utilisé pour créer la fenêtre principale - les deux autres styles étant la popup (voir l'onglet Dialogue) et le contrôle:

hwndPrinc = CreateWindow(
    sNomApp,
    sNomApp,
    WS_OVERLAPPEDWINDOW,
    GetSystemMetrics (SM_CXSCREEN) / 4,
    GetSystemMetrics (SM_CYSCREEN) / 3,
    GetSystemMetrics (SM_CXSCREEN) / 2,
    GetSystemMetrics (SM_CYSCREEN) / 3,
    HWND_DESKTOP,
    NULL,
    hInstance,
    NULL);
if (!hwndPrinc) return(0);

ShowWindow(hwndPrinc, nCmdShow);
UpdateWindow(hwndPrinc);

En pratique, GetSystemMetrics veut dire récupère les mesures système, SM_CXSCREEN signifie System Metrics X (abscisse) Screen (écran) - et pour SM_CXSCREEN c'est l'ordonnée. Dans notre cas de figure, la fenêtre principale au démarrage fait la moitié de l'écran en largeur, un tiers de l'écran en hauteur et elle est centrée dans les deux axes.

Le menu et l'accélérateur (pour les raccourcis clavier) sont chargés:

hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(ID_MENU));
SetMenu(hwndPrinc, hMenu);
hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(ID_ACCEL));

La boucle de messages est lancée:

while((iRetourGetMessage = GetMessage(&Msg, NULL, 0, 0)) != 0) {
    // Si erreur, par exemple une valeur de hwndPrinc ou de Msg non valide:
    if (-1 == iRetourGetMessage) break;
    else if (0 == TranslateAccelerator (hwndPrinc, hAccel, &Msg)) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}

Le fichier tec002_msg.c

Il sert simplement à segmenter le code, surtout dans la mesure où les fonctions qu'il contient peuvent devenir rapidement très volumineuses. Ces fonctions traitent le message de création des contrôles soit WM_CREATE, et le message de redimensionnement des contrôles soit WM_SIZE:

int f_WM_CREATE(HWND hWnd, HINSTANCE hInst);
int f_WM_SIZE(long cxClient, long cyClient);

Les paramètres de ces fonctions sont les handles (poignées) de la fenêtre principale (hWnd) et de l'instance de l'application pour l'une, et les dimensions (largeur, hauteur) de la zone client de la fenêtre principale de l'application pour l'autre.

[màj 15 avril 2008]

Copyright © 2008 B. Challier • légalcontact Valid XHTML 1.0 Valid CSS 2haut