IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)


API Windows en C

1 - La fenêtre principale.

Par CGi

Le 28 mai 2005




Introduction :

Windows est un système d'exploitation proposant une interface graphique. Dans ce premier article, nous allons créer une application composée d'une simple fenêtre. Le but est de montrer l'architecture d'un programme Windows avec interface graphique.

Prenez garde au terme de fenêtre ! Dans Windows, il signifie bien souvent objet visuel, par exemple, un bouton est une fenêtre ! On emploie aussi pour ces objets le terme de contrôles. La fenêtre apparaissant au démarrage de l'application est souvent appelée fenêtre principale. Dans ce tutoriel, nous emploierons le terme de fenêtre pour désigner une fenêtre du type de la fenêtre principale et le mot contrôle pour désigner les objets qu'elles peuvent contenir tels les boutons...


La fonction WinMain :

Le point d'entrée d'une application Windows est la fonction WinMain. C'est l'équivalent de la fonction main des applications consoles. Elle est appelée par le système d'exploitation au lancement du programme. Il lui fournit 4 paramètres.


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

Le premier paramètre est le handle d'instance de l'application. C'est un numéro unique attribué par le système d'exploitation qui lui permet de l'identifier.
Le second paramètre (qui vient de l'antiquité) est toujours NULL pour les applications Win32 (IDEM pour les Win64).
Le troisième paramètre est un pointeur sur la ligne de commande.
Et enfin le quatrième et dernier paramètre est une constante qui indique comment Windows désire afficher la fenêtre (normal, caché, minimisé ...).
C'est donc dans cette fonction (WinMain) que nous allons créer la fenêtre principale.


Création de la fenêtre :

La fonction qui permet de créer une fenêtre se nomme CreateWindow.
Cette fonction permet de créer des fenêtres, mais aussi des controles tel des boutons... Nous aurons l'occasion de l'utiliser plusieurs fois dans les différents chapitres de ce turoriel.
Voici le prototype de la fonction :

HWND CreateWindow(
    LPCTSTR lpClassName,  // Pointeur sur une classe de fenêtre.
    LPCTSTR lpWindowName,  // Pointeur sur le texte de la fenêtre.
    DWORD dwStyle,  // Style de la fenêtre.
    int x,  // Position horizontale de la fenêtre.
    int y,  // Position verticale de la fenêtre.
    int nWidth,  // Largeur de la fenêtre.
    int nHeight,  // Hauteur de la fenêtre.
    HWND hWndParent,  // Handle de la fenêtre parent.
    HMENU hMenu,  // Handle de menu ou ID de contrôle.
    HANDLE hInstance,  // Handle d'instance de l'application.
    LPVOID lpParam   // Pointeur sur des données passées à WM_CREATE.
   );

Et un exemple d'utilisation :

    HWND hwnd;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);

Le premier paramètre qu'elle reçoit est un pointeur sur une chaîne de caractères identifiant la classe de fenêtre (les classes de fenêtres sont identifiées par des chaînes de caractères). Les classes de fenêtre sont des modéles pour construire les fenêtres (le terme de classe n'a rien à voir avec les classes du C++). Pour les contrôles standard nous avons des classes de fenêtres prédéfinies et globale, par exemple pour un bouton, c'est la chaîne de caratère "button". Malheureusement pour la fenêtre principale ce n'est pas le cas, ça aurait été trop simple. Nous devrons donc en créer une. Nous devons pour cela remplir une structure de type WNDCLASS (définie dans winuser.h)

    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

Les champs qu'il nous importe d'affecter pour l'instant à cette structure sont :
Le champ lpszClassName avec un pointeur sur la chaîne de caractères identifiant la classe de fenêtre. Celle que l'on a passé comme paramètre à la fonction CreateWindow.
Le champ hInstance sera initialisé avec le handle d'instance hinstance que nous a fourni la fonction WinMain.
Le champ hIcon avec le handle d'une icône standard de Windows qui nous est retournée par la fonction LoadIcon.
Le champ hCursor avec le handle d'un curseur prédéfini de Windows (en l'occurrence la flèche) qui nous est retourné par la fonction LoadCursor.
Le champ hbrBackground avec le handle d'un pinceau qui servira à peindre la surface client de la fenêtre. Dans cet exemple nous lui affecterons une couleur prédéfinie du système ((l'identificateur de la couleur) + 1).
Le champ lpfnWndProc de type WNDPROC reçoit un pointeur de fonction sur la procédure de fenêtre que nous allons voir plus loin dans ce document.
Nous passons sous silence pour l'instant les autres champs de cette structure.
Remplir cette structure ne suffit pas, il faut aussi l'enregistrer au niveau du sytème (souvenez-vous qu'elles sont identifiées par une chaîne de caractères). Cet enregistrement sera effectué par la fonction RegisterClass qui reçoit comme paramètre l'adresse de la variable wc de type WNDCLASS créée précédemment :

     RegisterClass(&wc)

Revenons à notre fonction CreateWindow. Son deuxième paramètre reçoit un pointeur sur une chaîne de caractères qui sera le texte de la fenêtre. Dans le cas de la fenêtre principale, ce sera le texte du titre (celui affiché en haut de la fenêtre sur ce que l'on appelle la barre de titre). Si notre fenêtre avait été un bouton, le texte se serait affiché sur le bouton tout simplement.
Le troisième paramètre est le style de la fenêtre. Ce sont des identificateurs sous forme de constantes qui sont définie dans le fichier winuser.h. Nous ne les passerons pas en revue car ils sont trop nombreux. Dans l'exemple, nous lui donnerons la valeur WS_OVERLAPPEDWINDOW. Ce paramètre permet d'avoir une fenêtre avec une barre de titre, un menu system, les boutons minimiser et maximiser et que l'on peut redimensionner. Pour en avoir la liste, vous pouvez consulter le site msdn
Les deux paramètres suivants sont la position à l'écran de la fenêtre par rapport au coin haut gauche du bureau. Dans l'exemple, ils ont la valeur par défaut fournie par Windows : CW_USEDEFAULT.
Les deux paramètres suivants sont la taille de la fenêtre : largeur et hauteur. Ils peuvent aussi avoir la valeur par défaut CW_USEDEFAULT.
Le paramètre suivant désigne le parent de la fenêtre. Il est utile pour les contrôles ou fenêtres enfants afin de désigner leur parent. En ce qui concerne la fenêtre principale nous l'initialiserons à NULL.
Le paramètre suivant désigne un menu pour la fenêtre, il peut être à NULL s'il n'y a pas de menu ou si le menu a été référencé dans le champ lpszMenuName de la classe de fenêtre.
L'avant-dernier paramètre reçoit le handle d'instance de l'application qui crée la fenêtre, celui qui est fourni par WinMain.
Le dernier paramètre sert à passer des données à la procédure de fenêtre quand elle reçoit un message WM_CREATE. Dans cet exemple, nous nous contenterons de l'initialiser à NULL.
Après s'être exécutée, la fonction CreateWindow nous retourne le handle de la fenêtre qu'elle vient de créer. Les handles de fenêtre sont des identificateurs uniques attribués par Windows qui désignent chaque fenêtres. Ils sont de type HWND. Nous les rencontrerons très souvent, il nous servirons d'accés aux fenêtres aux contrôles afin de les manipuler. Comme nous allons le voir tout de suite.
Notre fenêtre étant maintenant créée, il faut la rendre visible par l'appel de la fonction ShowWindow :

     ShowWindow(hwnd, nCmdShow);

Cette fonction reçoit comme paramètre le handle de la fenêtre et un identificateur qui indique comment on désire l'afficher (normal, caché, minimisé ...). En général, on lui donne celui fourni en paramètre à la fonction WinMain (voir plus haut dans ce document).

Ensuite, on appelle UpdateWindow avec comme paramètre le handle de la fenêtre. Cette fonction sert à rafraîchir l'affichage de la zone client. Dans cet exemple, elle n'est pas vraiment utile puisque la zone client est vide.

     UpdateWindow(hwnd);

La boucle de messages :

Revenons au fonctionnement de Windows. Pour communiquer avec une application ou ses diverses fenêtres, Windows leurs envoie des messages. Par exemple, si vous cliquez sur le bouton fermeture (en haut à droite de la fenêtre), Windows va créer un message approprié qu'il va envoyer dans la file d'attente de l'application. La file d'attente est un tampon où sont stockés les messages en attente de traitement.
C'est à nous de coder l'extraction des messages de la file d'attente :

    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

C'est le rôle de la fonction GetMessage, dont le premier paramètre est un pointeur sur une variable de type MSG où elle doit stocker les données du message qu'elle vient de récupérer.
Nous devons donc définir cette variable de type MSG, qui est une structure composé d'un champ hwnd qui contient le handle de la fenêtre à qui il est destiné, un champ message identifiant le message (par exemple WM_CLOSE pour demander à la fenêtre de ce fermer) et de deux champs wParam et lParam contenant des données ayant rapport au message et de significations différentes selon le message.
Revenons à GetMessage, son deuxième paramètre est le handle de fenêtre dont on veut récupérer les messages. Il sera mis à NULL car on veut récupérer tous les messages de l'application (destinés à toutes ses fenêtres).
Les deux derniers paramètres seront mis à zéro, ils servent au filtrage des messages.
GetMessage renvoie toujours la valeur TRUE sauf si elle vient de récupérer le message WM_QUIT, ce qui met fin à la boucle de message donc à l'application.
Ensuite on passe le message à la fonction TranslateMessage qui traduit les messages WM_KEYDOWN (appui des touches clavier) en message WM_CHAR avec le code de caractère correspondant dans le champ wParam du message.
Ensuite, c'est à la fonction DispatchMessage de traiter le message. Son rôle est de l'envoyer à la procédure de fenêtre. Celle à qui il est destiné.

La procédure de fenêtre :

Voici la procédure de fenêtre de notre fenêtre principale dont nous avions passé un pointeur à la classe de fenêtre au début de ce document (on passe ce pointeur à la classe de fenêtre car c'est nous qui la créons, mais le système d'exploitation qui l'appelle, il doit donc la localiser).

LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);


LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:

            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Les paramètres qu'elle reçoit sont les données du message en cours de traitement. Celui qui a été envoyé par DispatchMessage. C'est à nous, développeurs, d'implémenter des actions en fonction du message que la procédure de fenêtre reçoit. Dans cet exemple, un seul message est traité : WM_DESTROY. Il nous indique que la fenêtre est fermée et a été détruite. Comme il s'agit de la fenêtre principale de l'application, il est d'usage de fermer l'application. Ce que nous ferons en appelant la fonction PostQuitMessage qui a pour fonction de poster un message WM_QUIT dans la file d'attente. Qui comme nous l'avons vu plus haut dans ce document, met fin à la boucle de messages et donc à l'application. Les messages non traités doivent l'être par la fonction DefWindowProc. Fonction qui implémente le comportement par défaut d'une fenêtre.

Code complet :

#include <windows.h>


LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                                                LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;
    MSG msg;
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "MaWinClass";

    if(!RegisterClass(&wc)) return FALSE;

    hwnd = CreateWindow("MaWinClass", "Titre", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, hinstance, NULL);
    if (!hwnd) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);


    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
/******************************************************************************/

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CREATE:

            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}



Ce code a été testé sous VC++, CodeBlocks(Mingw), C++ Builder.

A vos PC.

CGi

Avec la contribution de Cerberes pour la relecture.





Sommaire



C/C++
  Les pointeurs du C/C++.   Les listes chaînées.             Liste simple.             Liste triée.             Liste double.   Les arbres.   Les tas.   Le C orienté objets ?

  1 - La fenêtre principale.   2 - Contrôles et messages.   3 - Les commandes.   4 - Dialogue std.   5 - Contexte de périph.   6 - Dessiner.   7 - Les ressources.   8 - Dialogue perso.   9 - Dialogue comm.   10 - Les accélérateurs.

Assembleur
  Assembleur sous Visual C++.

C++ BUILDER
  Trucs et astuces.   Composant.   TRichEdit.   TDrawGrid.   Application MDI.   TThread.   wxWidgets.   Style Win XP.

  Première application.   Construire un menu.   Dessiner.   Sisers, Timers...   Dialogues standards.   Dialogues perso.

DotNet
  Composant C# Builder.   Contrôle WinForm.   Application MDI.

Java
  Applet java.





Copyright 2002-2020 CGi - Tous droits réservés CGi. Toutes reproduction, utilisation ou diffusion de ce document par quelque moyen que ce soit autre que pour un usage personnel doit faire l'objet d'une autorisation écrite de la part de l'auteur, propriétaire des droits intellectuels.
Les codes sources de ce document sont fournis en l'état. L'utilisateur les utilise à ses risques et périls, sans garantie d'aucune sorte de la part de l'auteur. L'auteur n'est responsable d'aucun dommage subi par l'utilisateur pouvant résulter de l'utilisation ou de la distribution des codes sources de ce document.
De la même façon, l'auteur n'est en aucun cas responsable d'une quelconque perte de revenus ou de profits, ou de données, ou de tous dommages directs ou indirects, susceptibles de survenir du fait de l'utilisation des codes sources de ce document, quand bien même l'auteur aurait été averti de la possibilité de tels dommages. L'utilisation des codes sources de ce document vaut acceptation par l'utilisateur des termes de la licence ci-dessus.