|
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.
PrincipeApple 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.
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.
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.
Fichier d'en-tête tec002.hLe 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.
Fichier makefile tec002.makLe 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"
,
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.
Fichier de ressources tec002.rcLe 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
Fichiers sourceIl 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);
}
}
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]