Aller au menu - Aller au contenu

Icône Les bases

Par Avatar Mg++
Mise à jour : 12/07/2009
1 327 visites depuis 7 jours, dont 284 sur ce chapitre classé 98/786
Ce chapitre va vous inculquer les bases du API Win32.
Sommaire du chapitre :
Icône du chapitre
Sommaire Chapitre suivant

Commencement

Le nom de ce que vous allez apprendre vous a sûrement laissés perplexes : je vous ai dit que nous allions vous apprendre l'API Windows, mais vous ne savez peut-être toujours pas de quoi il s'agit.

Une API, ou Application Programming Interface est, comme son nom l'indique, une interface de programmation : elle contient un ensemble de fonctions bas niveau, ici extrêmement bien documentée (comme vous pouvez le constater), permettant de programmer des applications haut niveau. Elles sont alors accessibles à partir de bibliothèques intégrées au projet.

Pour pouvoir utiliser le WinAPI, vous devez évidemment sélectionner un projet WinAPI dans votre IDE pendant la création du nouveau projet. Par exemple, sous code::blocks, vous devez sélectionner "Win32 GUI Application" comme sur cette photo :
Image utilisateur

Vous n'aurez rien à linker pour le moment.


Comme vous pouvez vous en douter, vous devrez inclure la bibliothèque appropriée pour utiliser cette API, à savoir :
Code : C
1
#include <windows.h>

Comme chacun sait, une seule fonction est commune à tous les programmes : le main. Le point d'entrée du programme est, dans cette API, un peu plus compliqué que dans un projet console ou SDL. Ainsi, le code minimal que vous pouvez faire en WinAPI est le suivant :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <windows.h>

int WinMain (
                HINSTANCE cetteInstance,
                HINSTANCE precedenteInstance,
                LPSTR lignesDeCommande,
                int modeDAffichage
            )
{
   return 0;
}

:waw: C'est quoi, tout ça ? Où diable sont passés les argc et argv tout bêtes ?

Pas de panique. ;) Étudions ensemble cette fonction. Vous connaissez forcément le type de retour de cette fonction : il s'agit d'un int. Passons maintenant aux arguments :
  • Le premier argument est l'instance actuelle de votre programme. C'est en quelque sorte une "boîte" représentant votre programme, et imbriquant tout ce qu'il contient (pour ne pas vous encombrer la tête) :Code : C
    1
    HINSTANCE cetteInstance
    

  • Le deuxième argument désigne l'instance précédente. En fait, ce paramètre date de Windows 16 bits, et donc obsolète. Il vaut donc toujours NULL :Code : C
    1
    HINSTANCE precedenteInstance
    

    Conséquence : vous n'êtes même pas obligés d'indiquer un nom de variable, bien que le type soit quand même nécessaire.

  • Le troisième argument remplace justement argv que vous connaissez déjà dans le prototype du main console ou SDL. Cependant, il est dans sa forme brute ; vous ne pourrez donc pas l'utiliser de suite (je vous expliquerai plus tard comment la découper et l'utiliser) : Code : C
    1
    LPSTR lignesDeCommande
    
    Il est d'un type que vous ne connaissez pas, puisqu'il s'agit d'une structure signée Microsoft. Celle-ci correspond à un bête char*.
  • Le petit dernier ne vous servira probablement pas souvent : il s'agit du mode d'affichage (visible, invisible, minimisé etc...) de la fenêtre principale : Code : C
    1
    int modeDAffichage
    

    Même remarque que pour le paramètre contenant l'instance précédente.


Pour ceux qui écoutent les conseils du rédacteur -à savoir moi ;) -, vous devriez au préalable savoir à quoi correspondent les types principaux utilisés par cette API :
  • LPSTR correspondant à un char *, et ses déclinaisons :
    • LPCSTR correspondant à un const char *.
    • LPTSTR correspondant à un TCHAR * (voir TCHAR plus bas).
      On l'initialise avec la macro TEXT, selon :
      Code : C
      1
      LPTSTR string = TEXT("exemple");
      

    • LPWSTR correspondant à un wchar_t *
      On l'initialise avec le préfixe ' L', selon : Code : C
      1
      LPWSTR string = L"exemple";
      

    Vous pouvez combiner les lettres définissant un caractère particulier de LPSTR pour obtenir des types adaptés (comme LPCTSTR).
  • WORD correspondant à un unsigned short
  • DWORD correspondant à un unsigned long
  • TCHAR correspondant à :
    • En compilant en Unicode : un wchar_t
    • Sinon : un char ;)
    Il est recommandé d'utiliser les TCHAR partout (même si ce n'est pas fait dans ce tuto), du moment que l'on n'utilise pas stdio, ou certaines fonctions de cette API (dont je vous mettrai en garde si c'est le cas), pour des raisons de compatibilité avec n'importe quel IDE.
    Seuls les utilisateurs de VS en général compilent en Unicode. Si n'en voulez pas (ça simplifie les choses), voici la manip' à exécuter : Project Properties -> Configuration Properties -> General -> Character set -> Changer l 'option de "unicode" à "multi-byte".
Et voilà pour une présentation restreinte ^^ .

Vous savez maintenant ne rien afficher sans erreur de compilation ! :lol: Génial, non ? Vous en voulez plus ? (pas étonnant ;) ) D'accord, direction "Votre première fenêtre" :) .

Pour ceux qui veulent absolument un "Hello World" avant de commencer (ça les frustre sûrement), voici la formule magique à rajouter dans le WinMain : Code : C
1
MessageBox(NULL, "Hello World", "Hello World", MB_OK);

Ou plus propre :
Code : C
1
MessageBox(NULL, TEXT("Hello World"), TEXT("Hello World"), MB_OK);

Rédacteur : Mg++

Votre première fenêtre

Vous êtes impatient de créer votre première fenêtre ? Tant mieux, c'est ici que ça se passe. :p

Le minimum



Premièrement, munissez-vous du code minimal que je vous ai donné tout à l'heure :
Code : C
1
2
3
4
5
6
#include <windows.h>

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance, LPSTR lignesDeCommande, int modeDAffichage)
{
   return 0;
}

Dans certains cas, vous serez contraints de rajouter les mots clés WINAPI ou APIENTRY entre 'int' et 'WinMain', cause d'une erreur de compilation de type "WinMain already defined in...". Si c'est le cas, adaptez mes codes à cette situation.

Les variables nécessaires



Nous aurons besoin pour afficher la fenêtre de plusieurs variables :
Code : C
1
2
3
HWND fenetrePrincipale;
MSG message;
WNDCLASS classeFenetre;

Étudions d'abord leur type respectif et leurs rôles :
  • fenetrePrincipale est de type HWND, qui veut dire handle de fenêtre. Si vous connaissez un minimum l'anglais, vous remarquerez que l'on pourrait le traduire par poignée.

    Quel est le rapport ?

    Réfléchissez : fenetrePrincipale sera une sorte de "poignée" nous permettant d'agir sur la fenêtre principale. En outre, cette structure possède sur la fenêtre toutes les informations nécessaires à son affichage. Bien sûr, ce handle ayant un nom, cela nous permet d'identifier, par le nom d'une variable, la fenêtre créée (bref : c'est bien pratique :) ).
  • message, quant à lui, est comme son nom l'indique un message système, utilisé pour communiquer entre l'utilisateur et les fenêtres : il s'agit en fait du descriptif d'un événement. Si vous connaissez la SDL, vous pourriez faire la correspondance avec 'SDL_event'.
  • classeFenetre est la classe de fenêtre (décidément, je choisis bien les noms ;) ) nécessaire à la construction de la fenêtre principale. C'est en fait une liste d'informations permettant par la suite de créer la fenêtre selon notre bon vouloir. (Je précise que la "classe" n'a pas ici la même définition que pour le C++.)


Remplissage de la classe de fenêtre



La création de la fenêtre demande le remplissage de sa classe, ou WNDCLASS. Nous allons donc nous en occuper :
  • Le champ 1 définit le style à affecter à la fenêtre. Ici, il n'y en a pas :Code : C
    1
    classeFenetre.style=0;
    

  • Le champ 2 définit la fonction callback à utiliser pour cette fenêtre (je vous explique plus loin) :Code : C
    1
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    

  • Le champ 3 définit combien de bytes en plus seront alloués à la suite de la structure (cela nous est inutile, donc 0 ^^ ) :Code : C
    1
    classeFenetre.cbClsExtra = 0;
    

  • Le champ 4 définit la même chose, mais suite à l'instance (donc aussi inutile) :Code : C
    1
    classeFenetre.cbWndExtra = 0;
    

  • Le champ 5 définit l'instance à laquelle appartient la fenêtre :Code : C
    1
    classeFenetre.hInstance = cetteInstance;
    

  • Le champ 6 définit l'icône à utiliser pour la fenêtre (en haut à gauche). Elle doit être chargée soit en passant par les ressources (nous verrons cela plus tard), soit, comme suit, avec une icône inclue dans Windows, possédant un ID spécifique (la liste sera peut-être proposée dans notre documentation) :Code : C
    1
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    

  • Le champ 7 définit le curseur par défaut à utiliser pour cette fenêtre (même chose que pour les icônes : le curseur utilisé ici est inclus dans Windows, et choisi parmi une petite liste) :Code : C
    1
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    

  • Le champ 8 définit la couleur à utiliser pour le fond de la fenêtre. Son type est HBRUSH, (traduit par brosse), et peut être changé grâce à une fonction de conversion, RGB->HBRUSH, que nous verrons plus tard, dans le chapitre sur l'affichage : Code : C
    1
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    

  • Le champ 9 définit le menu associé à cette fenêtre (peut être ajouté d'une autre manière) : ici, il n'y en aura aucun :Code : C
    1
    classeFenetre.lpszMenuName =  NULL;
    

  • Et enfin le le champ 10, qui sera le nom de la classe en question :Code : C
    1
    classeFenetre.lpszClassName = "classeF";
    

Maintenant que nous avons rempli la classe de la fenêtre, encore faut-il la sauvegarder. Voici comment faire : Code : C
1
RegisterClass(&classeFenetre);


Création proprement dite de la fenêtre principale



Enfin ! Nous sommes prêts à créer notre fenêtre. C'est le travail de la fonction, dont voici le prototype :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
HWND CreateWindow(
                        LPCTSTR lpClassName,  
                        LPCTSTR lpWindowName,
                        DWORD dwStyle, 
                        int x, 
                        int y, 
                        int nWidth,  
                        int nHeight, 
                        HWND hWndParent, 
                        HMENU hMenu,  
                        HANDLE hInstance, 
                        LPVOID lpParam
               );

:euh: Ne me dis pas que l'on doit retenir toutes ces fonctions ?


Bon d'accord, elles sont lourdes... mais non, vous n'êtes pas obligés de les retenir. Mais à force de les utiliser (vous verrez ;) ), vous les connaîtrez par coeur. "C'est en forgeant qu'on devient forgeron" à ce qu'il paraît. :p

Bon. Déjà, vous pouvez remarquer que son type de retour est HWND. Vous pouvez faire la correspondance avec fenetrePrincipale, et deviner que c'est celle-ci qui va contenir le retour de CreateWindow ^^ . En fait, cette fonction remplit la structure HWND avec les informations propres à la fenêtre, et la crée par la même occasion. (CreateWindow ne crée pas vraiment que des fenêtres au sens connu, mais toutes sortes de contrôles, que ce soit : fenêtre, bouton, listbox, progressbar, etc.)

Maintenant, occupons-nous de ses arguments :
  • L'argument 1 désigne la classe de fenêtre à partir de laquelle la fenêtre va être créée :Code : C
    1
    LPCTSTR lpClassName
    
    (Vous avez peut être remarqué qu'il est de type LPCTSTR... C'est en fait la même chose que LPSTR mais constant, donc égal à const char[].)
  • L'argument 2 désigne le nom de la fenêtre, c'est-à-dire, dans le cas d'une fenêtre, la string qui va être affichée en guise de titre :Code : C
    1
    LPCTSTR lpWindowName
    

  • L'argument 3 désigne les styles à donner à la fenêtre :
    Code : C
    1
    DWORD dwStyle
    

    (Vous avez sûrement remarqué que le type de cet argument vous est inconnu : en fait, il est utilisé un peu partout, et correspond à un unsigned long. Il est souvent utilisé pour les styles / flags, et sûrement construit à partir d'une association de bits.)
  • Les arguments 4 à 7 désignent respectivement : les coordonnées du coin supérieur gauche, la largeur, ainsi que la hauteur de la fenêtre :Code : C
    1
    2
    3
    4
    int x, 
    int y, 
    int nWidth, 
    int nHeight
    

  • L'argument 8 désigne le handle de la fenêtre parent :Code : C
    1
    HWND hWndParent
    

  • L'argument 9 désigne, dans le cas d'une fenêtre, le menu associé à celle-ci, et dans le cas d'un contrôle, son ID (pour pouvoir le manipuler) :Code : C
    1
    HMENU hMenu
    

  • L'argument 10 désigne l'instance du programme :Code : C
    1
    HANDLE hInstance
    

    (HINSTANCE dérive de HANDLE, donc son passage en tant que HANDLE se déroule sans histoire.)
  • L'argument 11 désigne les éventuels paramètres à envoyer à la fenêtre, à sa création :Code : C
    1
    LPVOID lpParam
    

    (LPVOID est en fait un équivalent à void*. Pour ceux ayant fait de la SDL, cet argument possède le même rôle que le paramètre optionnel de type void* des timers.)

Et maintenant que vous connaissez chacun des arguments de la fonction CreateWindow, passons au codage pour créer une fenêtre :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fenetePrincipale = CreateWindow("classeF", 
"Première fenêtre en winAPI !", 
WS_OVERLAPPEDWINDOW, /*Style qui permet d'avoir une fenêtre tout ce qu'il y a de plus normale : barre de titre, menu système (réduire, maximiser, fermer), bordure etc...*/
CW_USEDEFAULT, // Permet à l'OS de déterminer dynamiquement la position de la fenêtre
CW_USEDEFAULT, //Idem
400,
300,
NULL, // Pas de fenêtre parent, puisque c'est la principale
NULL, //Aucun menu ne lui est associé
cetteInstance,
NULL); // Aucun paramètre à lui envoyer en plus

Mais la fenêtre ne s'affichera pas pour autant. :o Elle est créée, mais c'est tout. ^^

Notez qu'elle aurait pu l'être en possédant le style WS_VISIBLE.

Pour l'afficher, il faut appeler la fonction ShowWindow ! Celle-ci prend en premier paramètre le handle de la fenêtre dont on veut changer le mode d'affichage, et un integer déterminant son mode. Vous pouvez soit utiliser le paramètre de WinMain modeDAffichage, soit utiliser la valeur qu'il prend d'habitude : SW_SHOW. Donc :
Code : C
1
ShowWindow(fenetrePrincipale,SW_SHOW);

Maintenant que la fenêtre est en mode "affichée", il faut rafraîchir l'écran afin de la montrer à l'utilisateur : c'est le rôle de UpdateWindow, prenant comme seul paramètre le handle de la fenêtre. Vous l'utiliserez sûrement beaucoup après avoir créé des contrôles enfants d'une fenêtre, afin de rafraîchir l'écran juste après leur création.

Boucle événementielle



Afin d'interfacer l'utilisateur avec la fenêtre, nous avons besoin d'une boucle événementielle traitant les messages créés par l'utilisateur (qui ne le sait même pas :p ) et destinés aux fenêtres concernées.
Pour cela, nous allons utiliser une nouvelle fonction que voici :
Code : C
1
BOOL GetMessage(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

  • L'argument 1 désigne le message récupérateur des événements : Code : C
    1
    LPMSG msg
    

    (Comme vous l'avez peut être deviné, les deux lettres 'LP' désignent un pointeur sur le type adjacent...)
  • L'argument 2 désigne le handle de fenêtre dont GetMessage va récupérer les événements (s'il est égal à NULL, GetMessage traitera toutes les fenêtres de l'instance) : Code : C
    1
    HWND hwnd
    


Les deux autres paramètres ne sont pas très importants pour le moment (je ne voudrais pas que vous partiez avant votre première fenêtre ;) ). Mettez-les donc à zéro.

Donc, mettons en place la boucle :
Code : C
1
2
3
4
5
while (GetMessage(&message, NULL, 0, 0)) /*Récupération des évènements de toutes les fenêtres de l'instance dans message*/
{
    TranslateMessage(&message); // Traduction de l'événement
    DispatchMessage(&message); // Envoi du message correspondant à la fenêtre concernée
}


La fonction callback


Cf glossaire

Vous vous rappelez le champ de structure de classeFenetre où on a donné le nom d'une fonction callback ? Eh bien définissons-la. ^^ Voilà déjà son prototype :
Code : C
1
LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam);


Son type de retour est assez spécial, mais j'ai choisi de ne pas vous le détailler ici, pour ne pas vous encombrer la tête avec des choses inutiles.

  • Le paramètre 1 désigne le handle de la fenêtre concernée :Code : C
    1
    HWND fenetrePrincipale
    

  • Le paramètre 2 désigne le message envoyé par la boucle événementielle traitée :Code : C
    1
    UINT message
    

  • Les paramètres 3 et 4 désignent des pointeurs d'int chargés d'apporter des précisions sur l'origine du message :
    Code : C
    1
    WPARAM wParam, LPARAM lParam
    

Ces paramètres vous seront décrits un peu plus tard, quand nous parlerons plus en détail des messages créés par l'utilisateur.

La fonction callback étant un traitement des messages envoyés à la fenêtre, elle est chargée de faire telle ou telle action en fonction de tel ou tel message. C'est heureusement nous qui devons la coder. :p

Dans un premier temps, je vous propose de gérer les messages à l'intérieur de cette fonction de la manière suivante :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

Le switch détermine quel message a été perçu par GetMessage :
  • WM_CREATE est envoyé à la création de la fenêtre. (Vous pourrez par ailleurs récupérer l'argument optionnel n°11 de CreateWindow à cet endroit.)
  • WM_DESTROY est envoyé lors de la destruction de la fenêtre. Par défaut (vous verrez que nous pouvons le changer), il est déclenché par WM_CLOSE, correspondant à un clic sur la croix en haut à droite que vous connaissez si bien ;) . Ici, il est traité, et PostQuitMessage est exécuté : il s'agit d'une fonction permettant de quitter simplement et proprement le programme. Elle prend comme unique argument 0.
  • Si aucun message déclaré explicitement n'est perçu, on retourne la procédure par défaut fournie par DefWindowProc.


Code complet



Et voilà : vous êtes prêts pour créer votre première fenêtre ! En combinant le tout, vous devriez avoir ce code-ci :magicien: :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <windows.h>

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

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName =  NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma première fenêtre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
                                                   NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}


Et vous obtenez cette oeuvre magnifique (il ne faut pas exagérer quand même :-° ) :
Image utilisateur

Enfin, presque ;) . Vous aurez sûrement une barre de titre différente, à cause du thème de Windows.

Voilà, ce premier chapitre est terminé, j'espère qu'il vous a plu :) . Ne vous découragez surtout pas face à la longueur du code, car cela en vaut la peine, et cela va rentrer progressivement en tant qu'automatisme.

Rédacteur : Mg++

Elle est vide, ma fenêtre !

Pour le moment, votre fenêtre vous paraît sûrement vide et terne ... nous allons l'animer un peu ;) . Nous allons voir ici comment créer des boutons, comment les manipuler, et comment faire apparaître un message à l'utilisateur.

Déjà, incorporez ou chargez le code obtenu dans la dernière sous-partie afin d'afficher une fenêtre (changez aussi les dimensions de 300 de hauteur à 110, cela sera plus esthétique :) ).

Création des boutons



Je vous propose dans cette sous-partie de créer deux boutons : un permettant d'afficher une information à l'utilisateur, et un autre pour quitter (ce n'est pas pour rien que je l'incorpore, vous allez pouvoir avoir plus de précisions sur la gestion des messages).

Les boutons sont créés à partir d'une fonction que vous connaissez bien désormais : CreateWindow.

:-° Mais je croyais que c'était pour faire des fenêtres ?

Oui, mais les contrôles sont considérés comme des fenêtres, enfants (donc "attachées" si vous voulez) de la fenêtre parent. Seules les classes de fenêtres sont différentes selon le contrôle. Vous vous rappelez de la classe que nous avons créée pour la fenêtre principale, classeFenetre ? Eh bien, pour les contrôles, les classes sont prédéfinies. On trouve donc comme classes :
  • BUTTON : permettant de créer un bouton.
  • LISTBOX : permettant de créer une liste de choix.
  • STATIC : permettant toutes sortes de choses, comme des images, des groupbox, etc.
  • Et bien d'autres...

Vous verrez ces contrôles basiques lors du prochain chapitre.


Comme vous vous en doutez, nous allons donc utiliser la classe BUTTON. Avant de les créer, vous devez savoir une chose : les contrôles sont gérés (grâce aux messages, notifications [servant à avertir d'un événement secondaire]...) à partir soit d'un handle (pratique n'est ce pas ? ^^ ), soit d'un ID, défini grâce à :Code : C
1
#define ID 1

Où ID est généralement un mot l'identifiant bien (par convention, en majuscule), et suivi d'un nombre le remplaçant lors de la compilation. (Il faut avouer que recevoir le message ' CLIC ' est plus pratique que ' 02123 ' par exemple. Surtout quand les IDs se multiplient...)

Donc, nous allons d'abord créer les handles de boutons, dans un tableau pour plus d'accessibilité. Les contrôles sont déclarés au début du callback de la fenêtre à laquelle ils appartiennent :
Code : C
1
2
3
LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

Veillez à bien mettre le mot-clé static lors de la déclaration d'une variable permanente : celui-ci permet à la variable d'avoir la même portée, soit, mais surtout une durée de vie commune à celle de la fenêtre.

C'est une méthode quelque peu barbare, surtout quand on a beaucoup de variables permanentes, mais j'ai choisi de vous en parler, car vous connaissez normalement déjà ce mot-clé ;) . Cependant, j'envisage de vous parler d'une autre méthode (meilleure) plus tard. Pour les curieux, il s'agit de la combinaison de SetWindowLong() avec GetWindowLong() (et leur déclinaison ~Ptr), permettant d'utiliser les membres de la WindowClass cbClsExtra et cbWndExtra.


Intégrez au préalable les définitions des ID des boutons au début de votre code :
Code : C
1
2
#define ID_B_PARLER 0
#define ID_B_QUITTER 1


Dernière chose : vous vous rappelez le prototype de CreateWindow ? Cette fonction demandait l'instance. Or, la fonction callback n'entre pas dans la portée de cette variable. Nous sommes alors contraints d'en faire une variable globale.
Déclarez une variable globale comme suit :Code : C
1
HINSTANCE instance;

Et, en guise de première instruction (de préférence après les déclarations de variables) de WinMain, copiez cetteInstance dans instance comme suit :
Code : C
1
instance = cetteInstance;

Bien. Maintenant, nous sommes prêts à créer les boutons. Cette opération se fait en général dans le WM_CREATE, puisqu'ils sont créés en même temps que la fenêtre parent :
Code : C
1
2
3
4
5
6
case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, ID_B_QUITTER, instance, NULL);
            return 0;

Comme vous pouvez le voir, les styles WS_CHILD et WS_VISIBLE sont appliqués aux boutons :
  • WS_CHILD solidarise le contrôle à la fenêtre parent. Sans lui, le contrôle flotterait "en l'air", en dehors de la fenêtre.
  • WS_VISIBLE affiche le contrôle (donc, pas besoin de ShowWindow).

Vous pouvez voir alors que les ID sont, comme je l'ai dit pendant la description de cette fonction, à la place du menu normalement associé à une fenêtre.

Si une erreur de type "Invalid conversion from...to..." se présente, effectuez un cast de la variable passée en paramètre en type attendu. En effet, le typage est plus strict en C++ qu'en C.

Les boutons sont maintenant créés, mais n'ont aucune action.

Gestion de l'enfoncement du bouton



Maintenant que les boutons existent, il nous faut les associer à des actions particulières. Tout d'abord, un peu de théorie. Je vous ai dit que le switch du message déterminait le message perçu : celui-ci a pu être dans le cas précédent WM_CREATE, ou WM_DESTROY. Mais ce message est complété par wParam et lParam, passés à la fonction callback. Ceux-ci contiennent des messages complémentaires, ou des notifications. Ces paramètres étant des doubles mots, on peut récupérer le mot bas et le mot haut de chacun de ces deux paramètres grâce aux macros LOWORD() ou HIWORD().

Justement, quand un bouton est enfoncé, le message vaut WM_COMMAND, et le mot bas de wParam détermine l'ID du bouton qui a été cliqué, et qui a donc envoyé ce message. En l'utilisant dans notre code, on obtient donc :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
case WM_COMMAND:
    switch(LOWORD(wParam))
    {
       case ID_B_PARLER:
          //Ici l'instruction associée à "Parler"
          break;

       case ID_B_QUITTER:
           //Là l'instruction associée à "Quitter"
           break;
    }
    return 0;

Ainsi, si le bouton "Parler" a été cliqué, le message vaudra WM_COMMAND, et un test du mot bas du paramètre wParam indiquera l'ID de celui ci, ou ID_B_PARLER.

Peut-on, nous (à travers du code), envoyer des messages ?

Ça tombe bien, je voulais l'utiliser pour le bouton "Quitter". :p En effet, ce bouton a le même effet que "la croix". Comme son message associé est WM_DESTROY, nous allons donc envoyer le message WM_DESTROY à la fenêtre principale quand le bouton "Quitter" a été cliqué.

La fonction permettant d'y arriver s'appelle SendMessage (quelle coïncidence ^^) ; son prototype est le suivant :
Code : C
1
LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

Vous l'avez deviné, SendMessage ne fait qu'exécuter la fonction callback avec le message spécifié dans msg, les paramètres spécifiés dans wParam et lParam, et retourne ce que retourne le callback (logique).

Très pratique, vous vous en servirez beaucoup, je vous l'assure ^^ .

En l'adaptant à notre code, nous obtenons ceci :
Code : C
1
SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);

Les paramètres n'étant pas utilisés, nous les mettons à zéro. Ce bout de code est évidemment à mettre dans le ' case ID_B_QUITTER '.

Le bouton "Quitter" est donc opérationnel. Occupons-nous maintenant du bouton "Parler". Ce bouton doit avoir pour effet d'afficher quelque chose à l'utilisateur : par exemple, de l'informer que le bouton a été cliqué (bon d'accord, ce n'est pas très original, mais ce sont les bases :p ). Et pour cela, rien de mieux que MessageBox.

MessageBox



Cette fonction, dont le prototype est le suivant :Code : C
1
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

affiche une petite messagebox portant comme titre la chaîne spécifiée par lpCaption, comme texte celui spécifié par lpText, étant rattachée à la fenêtre spécifiée par hWnd et ayant comme style ceux spécifiés dans uType.

Vraiment, vous ne connaissez pas ? Vous avez été bercé aux doux sons des erreurs, ou aux warnings de non-sauvegarde en quittant, tous deux rapportés grâce aux messages boxes. :p

Les icônes et boutons de celles-ci sont déterminés grâce au paramètre uType, mais la liste ne sera pas donnée ici.

Vous trouverez la liste complète dans notre mini-documentation.

Donc, en utilisant cette fonction en guise de réponse à un clic sur "Parler", on obtient ce code :
Code : C
1
MessageBox(fenetrePrincipale, "Clic !", "Bonjour.", MB_ICONINFORMATION);

MB_ICONINFORMATION est une constante permettant d'ajouter un bouton 'OK' à la messagebox, et une icône en forme de bulle.
Vous n'avez plus qu'à placer ce code dans le 'case ID_B_PARLER' du callback.

Code complet



Eh bien j'ai l'impression que l'on a fini notre modeste programme ! En voici donc le code complet :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <windows.h>

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

HINSTANCE instance;

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

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName =  NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 110,
                                                   NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}


Résultat :Image utilisateur

Puis, avec "Parler" : Image utilisateur

Rédacteur : Mg++

Les menus

Vous en voulez toujours plus, n'est-ce pas ? ;) Eh bien vous allez apprendre dans ce chapitre à créer des menus. Pour cela, deux manières de coder :
  • en passant par les ressources
  • dynamiquement en passant par des fonctions.

Nécessaire



En premier lieu, reprenez le code du chapitre précédent. En guise de test, nous créerons un menu "Actions" comportant les deux actions présentes sous la forme de boutons "Parler" et "Quitter".

Peu importe la méthode utilisée, des handles de menus seront nécessaires pour chaque menu, et chaque sous-menu associé à la fenêtre. Ceux-ci comportent le type HMENU :
Code : C
1
HMENU menu, sousMenu;


Création du menu



En utilisant des fonctions



Les handles de menus peuvent être alors créés en vue d'un remplissage avec la fonction CreateMenu, ne comportant aucun paramètre :
Code : C
1
HMENU CreateMenu(VOID);


Une fois créés, nous pouvons les remplir selon leur type avec la fonction suivante :
Code : C
1
BOOL AppendMenu(HMENU hMenu, UINT uFlags, UINT_PTR uIDNewItem, LPCSTR lpNewItem);

Cette fonction, dont le rôle est d'ajouter une entrée, que cela soit une commande ou un sous-menu, comporte les arguments suivants :
  • L'argument 1 désigne le menu auquel est associée l'entrée :Code : C
    1
    HMENU hMenu
    

  • L'argument 2 désigne le type de l'entrée, mais aussi par association de flags, des options facultatives (désactivé, coché, illustré, etc.) : Code : C
    1
    UINT uFlags
    

  • L'argument 3 désigne, dans le cas d'une commande, son ID (ayant le même effet que ceux des boutons), ou dans le cas d'une entrée popup, le handle du sous-menu associé :Code : C
    1
    UINT_PTR uIDNewItem
    

  • L'argument 4 désigne le nom affiché par l'entrée : Code : C
    1
    LPCSTR lpNewItem
    

Si vous voulez créer une entrée popup (donc un sous-menu), le uFlags doit correspondre à la valeur MF_POPUP. Dans le cas d'une commande, il doit être égal à MF_STRING.
Les séparateurs peuvent, eux, être créés avec MF_SEPARATOR (les deux derniers paramètres seront alors ignorés).

Bien sûr, les sous-menus doivent être définis avant d'être associés à un menu, ou, dans le cas contraire, celui-ci n'aurait rien à intégrer.

Dans notre cas, la création du menu serait comme ceci :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//Déclarations :
HMENU menu, sousMenu;

//...

sousMenu = CreateMenu();
AppendMenu(sousMenu, MF_STRING, ID_B_PARLER, "Parler");
AppendMenu(sousMenu, MF_SEPARATOR, (UINT)NULL, "");
AppendMenu(sousMenu, MF_STRING, ID_B_QUITTER, "Quitter");

menu = CreateMenu();
AppendMenu(menu, MF_POPUP, (UINT)sousMenu, "Actions");


En utilisant les ressources



Les ressources servent à énormément de choses en WinAPI. Elles servent, comme vous le savez, à spécifier les fichiers stockés dans l'exécutable en précisant son ID, type et chemin, mais aussi à définir des accélérateurs clavier (que nous verrons plus tard), des schémas de boîtes de dialogue (dont nous aborderons l'utilisation dans le chapitre suivant), ou encore des schémas de menus (qui nous intéressent ici :p ), et bien d'autres choses.

La création d'un menu en ressource est plus logique qu'en utilisant AppendMenu. Elle observe donc une hiérarchie sous cette forme, qu'on appelle communément template :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ID_MENU MENU
BEGIN
    POPUP "Menu"
    BEGIN
        POPUP "Sous Menu"
        BEGIN
                MENUITEM "Truc", ID_TRUC
                MENUITEM SEPARATOR
                MENUITEM "Machin", ID_MACHIN
        END
        MENUITEM "Groar", ID_GROAR
    END
    POPUP "Menu2"
    BEGIN
        MENUITEM "Miaou", ID_MIAOU
    END
END

(Bon d'accord, vive les noms... mais c'était pour vous montrer les différents cas possibles :-° .)

Vous ne connaissez sûrement pas les mots-clés BEGIN et END : en fait, il s'agit d'une autre notation utilisée en ressources pour les accolades ouvrantes et fermantes.


Comme vous pouvez le voir, ID_MENU n'est pas visible : il s'agit de l'identificateur du menu. (D'ailleurs, bien qu'il soit un ID, il n'a pas besoin d'être défini.) Seules les entrées entre ses BEGIN et END sont visibles : POPUP désigne les menus déroulants, et MENUITEM une commande (je suppose que vous savez ce que signifie SEPARATOR désormais ;) ). Les ID de chaque commande doivent être spécifiés après le nom affiché et une virgule.

On peut, tout comme AppendMenu, lui appliquer des flags permettant de cocher l'entrée, la désactiver, etc. ; dans ce cas-ci, elles sont spécifiées à la suite après l'ID de la commande et une virgule séparatrice.


Pour notre exemple, le code en ressource serait le suivant :
Code : C
1
2
3
4
5
6
7
8
ID_MENU MENU
BEGIN
        POPUP "Actions"
        BEGIN
                MENUITEM "Parler", ID_B_PARLER
                MENUITEM "Quitter", ID_B_QUITTER
        END
END


Bien. Après avoir créé le menu, encore faut-il le récupérer pour l'associer à la fenêtre. Pour cela, on utilise LoadMenu. Cette fonction, prenant comme premier paramètre l'instance actuelle, et en deuxième l'ID du menu, se charge de renvoyer un handle de menu. En utilisant ce handle de menu, vous pourrez l'utiliser lors de CreateWindow ou SetMenu (voir méthodes d'associations ci-après).

Si vous utilisez la classe de fenêtre pour l'association, vous pouvez vous passer de ce passage, et indiquer de suite l'ID du menu au champ de la classe correspondante.


Association du menu avec la fenêtre



Afin d'intégrer le menu à la fenêtre, vous avez trois méthodes.
  • Dans le cas d'une définition sans ressources (ou avec, mais en utilisant LoadMenu), à la création de la fenêtre, vous devez passer le nom du menu parent (ici menu) au paramètre n°9 de CreateWindow, étant hMenu, sans guillemets. Exemple :
    Code : C
    1
    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, <gras>menu</gras>, cetteInstance, NULL);
    

  • Dans le cas d'une définition utilisant les ressources, à la définition de la classe de fenêtre, vous devez passer le nom identificateur du menu au paramètre lpszMenuName de la structure WNDCLASS entre guillemets. Exemple :Code : C
    1
    classeFenetre.lpszMenuName = "MENU";
    

  • Dans les deux cas, dans du reste du code, vous pouvez utiliser la fonction SetMenu, prenant comme premier paramètre le nom de la fenêtre à laquelle le menu (précisé dans le paramètre 2 sous HMENU) est associé. Exemple (cas utilisant les ressources) :Code : C
    1
    SetMenu(fenetrePrincipale, LoadMenu(instance, "ID_MENU"));
    


Code complet



Contenu des autres fichiers (cas des ressources)



Resource.rc :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <windows.h>

#include "constantes.h"

ID_MENU MENU
BEGIN
    POPUP "Actions"
    BEGIN
        MENUITEM "Parler", ID_B_PARLER
        MENUITEM SEPARATOR
        MENUITEM "Quitter", ID_B_QUITTER
    END
END


constantes.h :
Code : C
1
2
3
4
5
6
7
#ifndef CONSTANTES_H
#define CONSTANTES_H

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

#endif

(Protection contre les inclusions multiples facultative pour le moment.)

1ère méthode



Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <windows.h>

#define ID_B_PARLER 0
#define ID_B_QUITTER 1

HINSTANCE instance;

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

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;
    HMENU menu, sousMenu;

    instance = cetteInstance;

    sousMenu = CreateMenu();
    AppendMenu(sousMenu,MF_STRING, ID_B_PARLER, "Parler");
    AppendMenu(sousMenu,MF_SEPARATOR, (UINT)NULL, "");
    AppendMenu(sousMenu,MF_STRING,ID_B_QUITTER,"Quitter");

    menu = CreateMenu();
    AppendMenu(menu, MF_POPUP, (UINT)sousMenu, "Actions");

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, menu /* Ou LoadMenu(instance, "ID_MENU")*/, cetteInstance, NULL);
    
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}


2ème méthode



Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

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

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = "ID_MENU";
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, NULL, cetteInstance, NULL);
    
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}


3ème méthode



Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

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

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 130, NULL, NULL, cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
                SetMenu(fenetrePrincipale, LoadMenu(instance, "ID_MENU"));
                /*Ou dans le cas manuel, SetMenu(fenetrePrincipale, menu); où menu a été d"jà créé*/  
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}


Résultat : Image utilisateur


Pour aller plus loin



Activer / désactiver une entrée



  • Dès la création du menu : spécifier le flag MF_DISABLED pour désactiver l'entrée (MF_ENABLED, permettant de l'activer, étant par défaut), ou MF_GRAYED pour le griser. Exemple :Code : C
    1
    MENUITEM "Parler", ID_B_PARLER, MF_GRAYED
    

  • Dans le reste du code : utiliser la fonction EnableMenuItem, prenant comme premier paramètre le handle de menu, comme deuxième soit l'emplacement (basé sur 0) de l'entrée, soit sa commande (le choix étant spécifié par la valeur du troisième paramètre), et comme troisième son mode, par combinaison de son mode de détermination (MF_BYCOMMAND implique d'utiliser la commande pour le deuxième paramètre, et MF_BYPOSITION l'inverse) et du mode d'affichage, étant les mêmes valeurs possibles que pour les flags des entrées décrits ci-dessus. Exemple :Code : C
    1
    EnableMenuItem(menu, ID_B_PARLER, MF_BYCOMMAND | MF_GRAYED);
    

Cocher / Décocher une entrée



  • Dès la création du menu, on peut spécifier le flag MF_CHECKED (pour coché) ou MF_UNCHECKED (valeur par défaut, non coché). Exemple : Code : C
    1
    MENUITEM "Parler", ID_B_PARLER, MF_CHECKED
    

  • Dans le reste du code : utiliser la fonction CheckMenuItem, prenant comme premier paramètre le handle de menu concerné, et pour les deuxième et troisième paramètres, le même système que pour EnableMenuItem, excepté le fait que les états possibles sont ceux spécifiés ci-dessus. Exemple :Code : C
    1
    CheckMenuItem(menu, ID_B_PARLER, MF_BYCOMMAND | MF_CHECKED);
    
MF_BYCOMMAND permet d'utiliser la commande de l'élément de menu concerné comme moyen de le déterminer, mais rien ne vous empêche d'utiliser MF_BYPOSITION pour son index.

Ajouter une icône à un élément de menu



Utilisez SetMenuItemBitmaps(), comme illustré dans l'exemple suivant :
Code : C
1
2
3
4
5
6
7
SetMenuItemBitmaps(
                      hMenu, 
                      ID_PARLER, 
                      MF_BYCOMMAND,
                      LoadBitmap(hInstance, "talkBtnBmp_normal"),
                      LoadBitmap(hInstance, "talkBtnBmp_selected")
                  );


Rédacteur : Mg++

Les boîtes de dialogue

Nous allons maintenant aborder les boîtes de dialogue. Pour ce chapitre, l'objectif sera juste d'ajouter une boîte de dialogue simple (comportant une icône et du texte) faisant office d'"A propos".

Préparation



Pour faire apparaître la boîte de dialogue, nous avons besoin d'une commande. Pour cela, nous allons rajouter un menu popup, au même niveau que "Actions", mais ayant pour nom "Aide". Dans celui-ci, une entrée-commande sera ajoutée, portant "A propos" comme nom, et ID_B_APROPOS comme ID.

Pour cela, ajoutez (exemple utilisant les ressources : adaptez en conséquence pour les autres méthodes) :
Code : C
1
2
3
4
POPUP "Aide"
BEGIN
        MENUITEM "A propos", ID_B_APROPOS
END

dans le template du menu en ressources.

ID_B_APROPOS devra être par ailleurs défini dans constantes.h.

Enfin et bien sûr, ajoutez un nouveau 'case' portant cet ID dans le switch du mot faible du message WM_COMMAND. Nous le remplirons plus tard.

La création d'une boîte de dialogue exige deux parties distinctes : la définition de son template, et sa création proprement dite. Voyons chacune de ces étapes.

Template ressource



Vous vous souvenez du template d'un menu en ressources ? C'est à peu près pareil pour une boîte de dialogue, excepté le fait que le mot-clé MENU est remplacé par DIALOG (ou DIALOGEX, si vous utilisez des styles étendus), et des informations supplémentaires comme sa position / largeur / hauteur, ses styles et son titre sont rajoutés juste avant la définition. Un schéma basique de boîte de dialogue peut alors être le suivant :
Code : C
1
2
3
4
5
6
ID_DIALOGUE DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
    STYLE WS_OVERLAPPEDWINDOW
        CAPTION "Titre"
BEGIN
END

L'ordre des ajouts n'est pas important, excepté le fait que les positions (les quatre paramètres entre virgules) de la boîte de dialogue doivent être les premiers à être définis.
WS_OVERLAPPEDWINDOW rend la boîte de dialogue modale, parfaitement banale : barre de titre, menu système (réduire, fermer, etc.)... tous les styles applicables aux fenêtres principales (contenus donc dans le champ uStyle de la WNDCLASS) le sont ici. Quant à CAPTION (dont la traduction française donne "Titre"), il indique la chaîne de caractères à afficher dans la barre de titre (d'où son nom).

Vous pouvez consulter tous les styles applicables aux boîtes de dialogue dans la documentation ci-jointe.


Chaque ajout de contrôle s'exécute par l'intermédiaire de mots-clés. Sauf exception (informations en surplus nécessaires, etc.), les lignes de "déclaration" de contrôles en utilisant ces mots-clés possèdent une organisation de ce type :
Code : C
1
MOTCLE TEXTE_ASSOCIE, ID, x, y, w, h, STYLES

Où x et y sont les coordonnées du coin supérieur gauche du contrôle, et w et h les dimensions du contrôle.

Par exemple, un bouton possède comme mot clé PUSHBUTTON. S'il est le bouton par défaut, il est alors DEFPUSHBUTTON.
Autre exemple (je ne les choisis pas au hasard ;) ) : une icône possède le même mot-clé que pendant une intégration à l'exécutable de celle-ci, en passant par les ressources, à savoir ICON.
Enfin, du texte peut être affiché avec le mot clé LTEXT (justifié à gauche) ou RTEXT (justifié à droite).

D'autres mots-clés seront présentés en même temps que le cours sur le contrôle concerné (prochaine partie).


Maintenant que vous savez tout ceci, vous êtes prêts à définir le template de votre boîte de dialogue. En premier lieu, intégrez à l'exécutable l'icône voulue avec, comme vous le savez, la syntaxe suivante :Code : C
1
icone1 ICON "chemin/icone.ico"


Je vous propose donc :
Code : C
1
2
3
4
5
6
7
8
9
APROPOS DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
        CAPTION "Titre"
        STYLE WS_OVERLAPPEDWINDOW
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 96, 90, 42, 12
    ICON icone1, -1, 60, 55, 32, 32
    LTEXT "A propos test des boîtes de dialogue", -1, 100, 58, 100, 10
END


IDOK est déjà déclaré avec l'API, tout comme IDCANCEL et d'autres.

-1 est spécifié quand le contrôle n'a pas besoin d'ID (comme les icônes ou ici les textes).


Maintenant que le schéma est créé, passons à la création proprement dite.

Création



Pour créer la boîte de dialogue, deux méthodes : soit vous en faites une indépendante, de déco, etc., soit vous voulez la manipuler.

Les fonctions utilisées auront chacune un but spécifique, mais rien ne vous empêche d'utiliser l'une ou l'autre tout le temps. Ce sont juste des questions de goûts et de besoins.


Vous pouvez donc utiliser soit CreateDialog, soit DialogBox. Leur prototype est presque pareil : elles possèdent toutes deux les mêmes paramètres, mais CreateDialog permet de récupérer un handle de fenêtre sur la boîte de dialogue créée (ce qui vous fait donc deviner que CreateDialog est la fonction à utiliser, si vous voulez manipuler la boîte de dialogue à partir d'une autre fenêtre, récupérer des infos, etc.).
Voici donc leurs paramètres :
  • Le premier désigne l'instance actuelle :Code : C
    1
    HINSTANCE hInstance
    

  • Le deuxième désigne le template à utiliser, donc l'ID (plutôt une chaîne identifiante) de la boîte de dialogue entre guillemets :Code : C
    1
    LPCTSTR lpTemplate
    

  • Le troisième désigne le handle de la fenêtre parent :Code : C
    1
    HWND hWndParent
    

  • Le dernier désigne la fonction callback de la boîte de dialogue (un cast sera peut être nécessaire) : Code : C
    1
    DLGPROC lpDialogFunc
    

L'intérêt de CreateDialog subsiste dans le fait que la boîte de dialogue peut être créée au tout début, être manipulée entre temps et affichée quand on la demande. Mais tout comme DialogBox, elle s'affiche juste après être créée. Vous allez donc peut être avoir recours à ShowWindow.

Vous pouvez aussi passer des paramètres aux boîtes de dialogue créées, en utilisant DialogBoxParam ou CreateDialogParam permettant d'envoyer une information (structure) dans le paramètre lParam du message WM_INITDIALOG (celui-ci étant donc passé dans le 5ème paramètre de ces deux fonctions).


Donc, si on choisit par exemple DialogBox comme fonction, on aura alors l'extrait de code suivant :Code : C
1
2
3
4
5
6
case WM_COMMAND:
  switch(LOWORD(wParam))
  {
      case ID_B_APROPOS:
         DialogBox(instance, "APROPOS", fenetrePrincipale, (DLGPROC)aPropos_procedure);
         break;

Or, aPropos_procedure n'existe pas. Nous allons donc nous occuper de sa fonction callback.

La fonction callback



Similaire à la procédure de la fenêtre principale, elle possède néanmoins un type de retour différent : BOOL APIENTRY. Je ne vais pas non plus le décrire ici.

Quelques différences notoires : WM_CREATE a été remplacé par WM_INITDIALOG, et WM_DESTROY n'est pas envoyé : EndDialog s'occupe de fermer la fenêtre (donc à ne pas utiliser de suite avec CreateDialog) en prenant comme paramètre : le handle de la boîte de dialogue, et 0.

On peut donc avoir la procédure suivante :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
BOOL APIENTRY aPropos_procedure(HWND boiteDeDialogue,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch (message)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
         {
           EndDialog(boiteDeDialogue,0);
           return TRUE;
         }
         return 0;

      default:
         return FALSE;
    }
}


Votre boîte de dialogue est alors achevée !

Code complet



Voici maintenant le code complet :

ressource.rc :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <windows.h>

#include "constantes.h"

icone1 ICON "Icones/icone.ico"

ID_MENU MENU
BEGIN
    POPUP "Actions"
    BEGIN
        MENUITEM "Parler", ID_B_PARLER
        MENUITEM SEPARATOR
        MENUITEM "Quitter", ID_B_QUITTER
    END
    POPUP "Aide"
    BEGIN
        MENUITEM "A propos", ID_B_APROPOS
    END
END

APROPOS DIALOG
    CW_USEDEFAULT, CW_USEDEFAULT, 200, 120
        CAPTION "Titre"
        STYLE WS_OVERLAPPEDWINDOW
BEGIN
    DEFPUSHBUTTON "Ok", IDOK, 76, 90, 42, 12
    ICON icone1, -1, 30, 50, 32, 32
    LTEXT "A propos test des boîtes de dialogue", -1, 60, 38, 200, 10
END


constantes.h :
Code : C
1
2
3
4
5
6
7
8
#ifndef CONSTANTES_H
#define CONSTANTES_H

#define ID_B_PARLER 0
#define ID_B_QUITTER 1
#define ID_B_APROPOS 2

#endif


main.cpp :
Code : C
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#include <windows.h>

#include "constantes.h"

HINSTANCE instance;

LRESULT CALLBACK procedureFenetrePrincipale(HWND, UINT, WPARAM, LPARAM);
BOOL APIENTRY aPropos_procedure(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam);

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
    HWND fenetrePrincipale;
    MSG message;
    WNDCLASS classeFenetre;

    instance = cetteInstance;

    classeFenetre.style = 0;
    classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
    classeFenetre.cbClsExtra = 0;
    classeFenetre.cbWndExtra = 0;
    classeFenetre.hInstance = NULL;
    classeFenetre.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
    classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
    classeFenetre.lpszMenuName = NULL;
    classeFenetre.lpszClassName = "classeF";

    // On prévoit quand même le cas où ça échoue
    if(!RegisterClass(&classeFenetre)) return FALSE;

    fenetrePrincipale = CreateWindow("classeF", "Ma premiere fenetre winAPI !", WS_OVERLAPPEDWINDOW,
                                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 130,
                                                   NULL, LoadMenu(instance, "ID_MENU"), cetteInstance, NULL);
    if (!fenetrePrincipale) return FALSE;

    ShowWindow(fenetrePrincipale, modeDAffichage);
    UpdateWindow(fenetrePrincipale);


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

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND boutons[2] = {NULL};

    switch (message)
    {
        case WM_CREATE:
                boutons[0] = CreateWindow("BUTTON", "Parler", WS_CHILD | WS_VISIBLE,
        5, 5, 383, 30, fenetrePrincipale, (HMENU)ID_B_PARLER, instance, NULL);
                boutons[1] = CreateWindow("BUTTON", "Quitter", WS_CHILD | WS_VISIBLE,
        5, 45, 383, 30, fenetrePrincipale, (HMENU)ID_B_QUITTER, instance, NULL);
            return 0;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_B_APROPOS:
                    DialogBox(instance, "APROPOS", fenetrePrincipale, (DLGPROC)aPropos_procedure);
                    break;

                case ID_B_PARLER:
                    MessageBox(fenetrePrincipale, "Clic", "Bonjour.", MB_ICONINFORMATION);
                    break;

                case ID_B_QUITTER:
                    SendMessage(fenetrePrincipale, WM_DESTROY, 0, 0);
                    break;
            }
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        default:
            return DefWindowProc(fenetrePrincipale, message, wParam, lParam);
    }
}

BOOL APIENTRY aPropos_procedure(HWND boiteDeDialogue,UINT message,WPARAM wParam,LPARAM lParam)
{
    switch (message)
    {
      case WM_INITDIALOG:

         return TRUE;

      case WM_COMMAND:
         if (LOWORD(wParam) == IDCANCEL || LOWORD(wParam) == IDOK)
         {
           EndDialog(boiteDeDialogue,0);
           return TRUE;
         }
         return 0;

      default:
         return FALSE;
    }
}


Résultat : (pas d'icône chez moi, je n'en avais pas sous la main ;) )

Image utilisateur

et

Image utilisateur

Pour aller plus loin


  • Vous pouvez récupérer le handle d'un contrôle d'une boîte de dialogue (pour pouvoir lui envoyer des messages) avec GetDlgItem, prenant comme premier paramètre le handle de la boîte de dialogue parent du contrôle, et en deuxième son ID.
  • Vous pouvez changer dynamiquement le texte des XTEXT avec SetDlgItemText, (ou Int, rajoutant un quatrième paramètre étant un booléen si l'int est signé ou non) prenant les mêmes paramètres que GetDlgItem, mais avec en plus le texte à afficher.
  • A l'inverse, vous pouvez utiliser GetDlgItemText (ou Int, où le troisième paramètre est un pointeur d'int qui va récupérer les éventuelles erreurs, et un quatrième spécifiant si l'int est signé ou non. La valeur est alors retournée.) pour en récupérer la valeur. Le troisième paramètre est alors une variable qui va contenir la valeur, et un quatrième est ajouté pour spécifier la taille de la chaîne à récupérer (pour éviter les dépassements de tampon).

Vous avez maintenant acquis toutes les bases (de chez base :p ), et pourrez alors étendre vos connaissances avec le prochain chapitre, lequel portera sur les différents contrôles.

Rédacteur : Mg++

Q.C.M.

Comment s'appelle la fonction "point-d'entrée" d'un programme WinApi ?
Quels sont les paramètres de la fonction principale du programme ?
Qu'est ce qu'un handle ?
Quand un bouton est enfoncé, le message envoyé est :
On peut créer un menu avec
On peut créer une boîte de dialogue avec :

Statistiques de réponses au QCM

Sommaire Chapitre suivant

Partager

37 commentaires pour "Les bases"
Note moyenne : 3.35 / 4 (69 votes)
Pseudo Commentaire
Hors ligne Beul # Posté le 02/09/2010 à 00:42:19

Bonjour tout le monde. Est-ce que quelqu'un pourrait m'expliquer comment utiliser SetMenuItemBitmaps(...)
si j'ai utilisé la façon n°2 du tutoriel (avec le fichier .rc).
Merci.
Connecté la colombe noir # Posté le 30/10/2010 à 10:24:50
Avatar

j'aimerais précisée que moi sous dev je doit notée WINAPI avant la fonction main

http://hatokuro.comuf.com/ >(m)< °o0(mon site) Image utilisateur
 
Hors ligne Gakuse78 # Posté le 03/02/2011 à 22:51:10
Avatar

Bonsoir,

Je ne comprends pas un truc. :(
J'ai fait ma première fenêtre. Mais plutôt que le titre "Première fenêtre en winAPI !", j'ai du texte en caractère chinois. :o
Quelqu'un a une idée de comment corriger cela ?

Sinon, merci pour ce tuto qui va m'apprendre beaucoup de chose. :D

Developpeur débutant (en C ?). ^^
Visual Studio 2010
Windows 7 SP1 - 64bits
 
Hors ligne DeiDara57 # Posté le 10/10/2011 à 09:01:43
Avatar

Comme Polele la dit, http://chgi.developpez.com/windows/ Ce tuto n'est pas si mal non plus.
D'ailleurs certains codes sources sont identiques seules les noms des variables changent, Si tu t'en ai inspiré l’éthique veut que tu site tes sources.
Je te cite pas d'appropriation, et les modifications entraîneront obligatoirement une citation du créateur originel

Btw, Chapitre 1 Les bases, tu déclare cette variable :
Code : C++
1
HINSTANCE instance;

Sa portée est globale, et les variables globales c'est mal !
Cette fonction remplace ta variable, elle est ultra-connue :
Code : C++
1
GetModuleHandle(NULL);

Je te laisse feuilleter la doc ;)
Hors ligne cal188 # Posté le 28/03/2012 à 09:36:17

Avis : Mitigé

Bonjour,

J'ai un petit soucis car quoi que je fasse, je n'ai aucun bouton de contrôle qui apparaît...
Et pourtant le tout compile sans problème (Exactement le même code).
D'autre part, tous ce qui est texte s'affiche en police chinoise -_-'

Est ce que quelqu'un aurait une idée d'où peut provenir le soucis ?

ps: je suis en visual studio 2011 beta
merci.

Voir tous les commentaires