Version en ligne

Tutoriel : Apprentissage de l'API Windows

Table des matières

Apprentissage de l'API Windows
Les bases
Commencement
Votre première fenêtre
Elle est vide, ma fenêtre !
Les menus
Les boîtes de dialogue
Les contrôles
Les boutons (tous types)
Le contrôle d'édition
La liste déroulante
La liste de choix
La glissière
Le contrôle Static
L'affichage
Les contextes de périphériques
Le texte
Les formes géométriques
Les images
TP : Un lecteur audio
But
Nécessaire
Pistes
Correction
Améliorations
Les événements
Evénements "clavier"
Evénements "souris"
Esthétique avancée
Les régions
Gestion de la fenêtre
Documentation
Fonctions
Messages
Structures
Glossaire
Termes
Manipulation du registre
Quelques notions de BDR
Changement d'icône de fichiers
Lancement au démarrage de windows
Association de fichiers
Divers
Utiliser le style du Windows courant
La notation hongroise
Quelques codes...
Programmes
Neoblast, de Kidpaddle2 (ex-Mg++)
DdPop, de damien.09

Apprentissage de l'API Windows

Image utilisateur

Vous voudriez apprendre l'API Windows et vous trouvez les autres cours présents sur le net trop difficiles ou pas assez précis ? Vous avez frappé à la bonne porte.

Bienvenue dans ce tutoriel. Vous allez apprendre ici à vous servir d'une API très développée : l'API Win32. Sa proximité avec le système d'exploitation lui permet d'avoir un contrôle total sur n'importe quelle partie de l'OS, et un grand éventail de fonctions.

Je considère dans ce tutoriel que vous ne connaissez strictement rien à l'API Windows, ceci pour ne pas décourager les débutants ; néanmoins, vous aurez besoin des connaissances acquises tout au long du tuto de M@teo21. Vous trouverez donc ici des cours évolutifs, entrecoupés de TP réguliers, une documentation sur les différents messages, notifications, styles, fonctions, etc., et même un glossaire ;) .

Toutes ces parties seront progressivement remplies au fur et à mesure de la rédaction du tutorial en lui-même. J'ai pour cela des coéquipiers, respectivement pamaury et acryline.

Si vous avez des problèmes, des questions, ou encore des suggestions, vous pouvez nous envoyer un MP, auquel on se fera un plaisir de répondre. ;)

Sur ce, je vous souhaite une agréable lecture, et j'espère que ce tuto vous aidera le plus possible. :)

Les bases

Commencement

Ce chapitre va vous inculquer les bases du API Win32.

Commencement

Les bases Votre première fenêtre

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

Comme vous pouvez vous en douter, vous devrez inclure la bibliothèque appropriée pour utiliser cette API, à savoir :

#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 :

#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 :

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 :

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" :) .

Rédacteur : Mg++


Les bases Votre première fenêtre

Votre première fenêtre

Commencement Elle est vide, ma fenêtre !

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 :

#include <windows.h>

int WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance, LPSTR lignesDeCommande, int modeDAffichage)
{
   return 0;
}
Les variables nécessaires

Nous aurons besoin pour afficher la fenêtre de plusieurs variables :

HWND fenetrePrincipale;
MSG message;
WNDCLASS classeFenetre;

Étudions d'abord leur type respectif et leurs rôles :

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 :

Maintenant que nous avons rempli la classe de la fenêtre, encore faut-il la sauvegarder. Voici comment faire :

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 :

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 :

Et maintenant que vous connaissez chacun des arguments de la fonction CreateWindow, passons au codage pour créer une fenêtre :

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. ^^

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 :

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 :

BOOL GetMessage(LPMSG msg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

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 :

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 :

LRESULT CALLBACK procedureFenetrePrincipale(HWND fenetrePrincipale, UINT message, 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 :

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 :

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: :

#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++


Commencement Elle est vide, ma fenêtre !

Elle est vide, ma fenêtre !

Votre première fenêtre Les menus

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 :

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 à :

#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 :

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

Intégrez au préalable les définitions des ID des boutons au début de votre code :

#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 :

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 :

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 :

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 :

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.

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 :

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 :

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 :

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 :

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.

Donc, en utilisant cette fonction en guise de réponse à un clic sur "Parler", on obtient ce code :

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 :

#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++


Votre première fenêtre Les menus

Les menus

Elle est vide, ma fenêtre ! Les boîtes de dialogue

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 :

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 :

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 :

HMENU CreateMenu(VOID);

Une fois créés, nous pouvons les remplir selon leur type avec la fonction suivante :

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 :

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 :

//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 :

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 :-° .)

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.

Pour notre exemple, le code en ressource serait le suivant :

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).

Association du menu avec la fenêtre

Afin d'intégrer le menu à la fenêtre, vous avez trois méthodes.

Code complet
Contenu des autres fichiers (cas des ressources)

Resource.rc :

#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 :

#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
#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
#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
#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
Cocher / Décocher une entrée
Ajouter une icône à un élément de menu

Utilisez SetMenuItemBitmaps(), comme illustré dans l'exemple suivant :

SetMenuItemBitmaps(
                      hMenu, 
                      ID_PARLER, 
                      MF_BYCOMMAND,
                      LoadBitmap(hInstance, "talkBtnBmp_normal"),
                      LoadBitmap(hInstance, "talkBtnBmp_selected")
                  );

Rédacteur : Mg++


Elle est vide, ma fenêtre ! Les boîtes de dialogue

Les boîtes de dialogue

Les menus Les contrôles

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) :

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 :

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).

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 :

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).

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 :

icone1 ICON "chemin/icone.ico"

Je vous propose donc :

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

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.

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 :

Donc, si on choisit par exemple DialogBox comme fonction, on aura alors l'extrait de code suivant :

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 :

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 :

#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 :

#ifndef CONSTANTES_H
#define CONSTANTES_H

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

#endif

main.cpp :

#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

Rédacteur : Mg++


Les menus Les contrôles

Les contrôles

Les boîtes de dialogue Les boutons (tous types)

Vous en savez maintenant assez pour pouvoir utiliser les autres contrôles, qui vous sont expliqués ici :magicien: :

&lt;information&gt;Seuls les points importants sont abordés. Si vous ne trouvez pas votre bonheur, je vous invite à aller sur msdn.&lt;/information&gt;

Les boutons (tous types)

Les contrôles Le contrôle d'édition

Les boutons (tous types)

Ce contrôle est l'un des plus utiles, mais aussi le plus complet.
En effet, ce que l'on désigne par BUTTON est bien plus qu'un bouton, ce peut être :

Nous allons détailler tour à tour chaque utilisation.

Création

Pour créer un BUTTON, il faut appeler CreateWindow en spécifiant "BUTTON" comme classe, ou utiliser le mot-clé PUSHBUTTON en ressource. Néanmoins, cela ne suffit pas car, comme vous l'avez vu, il y a plusieurs possibilités. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD, WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles :

Comment déterminer les radio button qui fonctionnent ensemble ?

C'est très simple : à chaque fois que vous commencez un nouveau groupe de radio button fonctionnant ensemble, il vous faut ajouter le style WS_GROUP lors de la création avec CreateWindow (voir le code complet en fin de description).

Notifications

Je vais décrire ici les notifications envoyées par le contrôle, via le message WM_COMMAND (sauf mention contraire). Nous partirons du principe que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

.

BN_CLICKED

Cette notification est envoyée quand l'utilisateur clique sur un bouton.

BN_DBLCLK ou BN_DOUBLECLICKED

Cette notification est envoyée quand l'utilisateur double-clique sur un bouton.

WM_CTLCOLORBTN

Ce message est envoyé à la fenêtre parent juste avant que le contrôle ne soit dessiné.
Il est utile principalement pour changer la couleur du texte, ou du fond du contrôle pour le dessin.

Les paramètres valent alors:

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam, ainsi que la valeur retournée.

BCM_GETIDEALSIZE

Ce message permet de récupérer la taille du bouton permettant d'afficher correctement le texte et l'image (si présente).

BM_CLICK

Ce message permet de simuler un clic de souris sur le bouton.

BM_GETCHECK

Ce message permet de récupérer l'état d'un check box ou d'un radio button.

BM_GETIMAGE

Ce message permet de récupérer le handle de l'image, ou de l'icône associée au bouton.

BM_GETSTATE

Ce message permet de récupérer l'état d'un bouton ou d'une check box.

BM_SETCHECK

Ce message permet de définir l'état d'une check box ou un d'un radio button.

BM_SETIMAGE

Ce message permet de définir le handle de l'image, ou de l'icône associée au bouton.

BM_SETSTATE

Ce message permet de définir l'état de surbrillance d'un bouton. L'état de surbrillance est utilisé lorsque l'utilisateur est en train de cliquer sur le bouton.

Exemple

Ceci est le code complet d'un exemple utilisant les boutons.

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_PUSHBUTTON_1     110
#define ID_PUSHBUTTON_2     111
#define ID_PUSHBUTTON_3     112
#define ID_CHECKBOX_1       113
#define ID_CHECKBOX_2       114
#define ID_CHECKBOX_3       115
#define ID_CHECKBOX_4       116
#define ID_RADIOBUTTON_11   117
#define ID_RADIOBUTTON_12   118
#define ID_RADIOBUTTON_13   119
#define ID_RADIOBUTTON_21   120
#define ID_RADIOBUTTON_22   121
#define ID_RADIOBUTTON_23   122

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    // Premier groupe: le push button
    HWND hGroup=CreateWindow(
        "BUTTON",
        "Le push button",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        10,10,
        120,150,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // On le remplit avec un premier push button tout bête
        hControle=CreateWindow(
            "BUTTON",
            "Button 1",
            WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
            10+10,10+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_1,
            instance,
            NULL);
        // un second avec quelques flags : plat et plusieurs lignes
        hControle=CreateWindow(
            "BUTTON",
            "Button 2: texte long",
            WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_FLAT|BS_MULTILINE,
            10+10,10+50,
            100,40,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_2,
            instance,
            NULL);
        // et un troisième un peu plus exotique, c'est aussi le bouton par défaut de la fenêtre
        // on lui met aussi une image alignée à gauche
        hControle=CreateWindow(
            "BUTTON",
            "",
            WS_CHILD|WS_VISIBLE|BS_DEFPUSHBUTTON|BS_ICON|BS_LEFT,
            10+10,10+100,
            100,40,
            fenetrePrincipale,
            (HMENU)ID_PUSHBUTTON_3,
            instance,
            NULL);

        HANDLE hIcon=LoadIcon(NULL,IDI_ERROR);
        SendMessage(hControle,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);
    }
    // Deuxième groupe: les check box
    hGroup=CreateWindow(
        "BUTTON",
        "La check box",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        130,10,
        120,150,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // une check box automatique deux états
        hControle=CreateWindow(
            "BUTTON",
            "Check box 1",
            WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX,
            130+10,10+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_1,
            instance,
            NULL);
        // le même mais plat, et avec le texte à gauche de la case à cocher
        hControle=CreateWindow(
            "BUTTON",
            "Check box 2",
            WS_CHILD|WS_VISIBLE|BS_AUTOCHECKBOX|BS_FLAT|BS_LEFTTEXT,
            130+10,10+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_2,
            instance,
            NULL);

        // une autre check box simple automatique mais trois états
        hControle=CreateWindow(
            "BUTTON",
            "Check box 3",
            WS_CHILD|WS_VISIBLE|BS_AUTO3STATE,
            130+10,10+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_3,
            instance,
            NULL);

        // le même mais non automatique :)
        hControle=CreateWindow(
            "BUTTON",
            "Check box 4",
            WS_CHILD|WS_VISIBLE|BS_3STATE,
            130+10,10+110,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_CHECKBOX_4,
            instance,
            NULL);
    }
    // troisième groupe : les radio button
    hGroup=CreateWindow(
        "BUTTON",
        "Le radio button",
        WS_CHILD|WS_VISIBLE|BS_GROUPBOX,
        10,160,
        240,110,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    {
        HWND hControle;
        // trois radio button
        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.1",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON|WS_GROUP,
            10+10,160+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_11,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.2",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            10+10,160+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_12,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 1.3",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            10+10,160+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_13,
            instance,
            NULL);
        // trois autres, mais qui forment un autre groupe
        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.1",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON|WS_GROUP,
            130+10,160+20,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_21,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.2",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            130+10,160+50,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_22,
            instance,
            NULL);

        hControle=CreateWindow(
            "BUTTON",
            "Choix 2.3",
            WS_CHILD|WS_VISIBLE|BS_AUTORADIOBUTTON,
            130+10,160+80,
            100,20,
            fenetrePrincipale,
            (HMENU)ID_RADIOBUTTON_23,
            instance,
            NULL);
    }

}

VOID AfficheActionCheckBox(int index,HWND hCtl,HWND fenetrePrincipale)
{
    // test si la check box est cochée, non cochée ou inderterminée.
    LRESULT lRes=SendMessage(hCtl,BM_GETCHECK,0,0);
    CHAR lpMessage[128];

    sprintf(lpMessage,"La check box n°%d est maintenant %s",index,
        lRes==BST_CHECKED?"cochée":
        lRes==BST_UNCHECKED?"décochée":"inderterminée");

    MessageBox(fenetrePrincipale,lpMessage,"Vous avez modifié une check box",
        MB_ICONINFORMATION|MB_OK);
}

VOID DemandeActionCheckBox4(HWND hCtl,HWND fenetrePrincipale)
{
    // test si la check box est cochée, non cochée ou inderterminée.
    LRESULT lRes=SendMessage(hCtl,BM_GETCHECK,0,0);
    PCHAR lpProchainEtat;
    UINT iProchainEtat;
    UINT iChoix;
    CHAR lpMessage[128];

    if(lRes==BST_CHECKED)
    {
        lpProchainEtat="inderterminée";
        iProchainEtat=BST_INDETERMINATE;
    }
    else if(lRes==BST_INDETERMINATE)
    {
        lpProchainEtat="décochée";
        iProchainEtat=BST_UNCHECKED;
    }
    else
    {
        lpProchainEtat="cochée";
        iProchainEtat=BST_CHECKED;
    }

    sprintf(lpMessage,"Voulez-vous passer à l'état %s ?",lpProchainEtat);

    iChoix=MessageBox(fenetrePrincipale,lpMessage,"",MB_YESNO);
    if(iChoix==IDYES)
        SendMessage(hCtl,BM_SETCHECK,(WPARAM)iProchainEtat,0);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message, WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    HWND hCtl=(HWND)lParam;

    switch(iId)
    {
        case ID_PUSHBUTTON_1:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le premier bouton","",MB_OK);
            break;
        case ID_PUSHBUTTON_2:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le second bouton","",MB_OK);
            break;
        case ID_PUSHBUTTON_3:
            MessageBox(fenetrePrincipale,"Vous avez appuyé sur le troisième bouton","",MB_OK);
            break;
        case ID_CHECKBOX_1:
        case ID_CHECKBOX_2:
        case ID_CHECKBOX_3:
            AfficheActionCheckBox(iId-ID_CHECKBOX_1,hCtl,fenetrePrincipale);
            break;
        case ID_CHECKBOX_4:
            DemandeActionCheckBox4(hCtl,fenetrePrincipale);
            break;
    }
}

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

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,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,
        270,320,
        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;
}

Rédacteur : pamaury


Les contrôles Le contrôle d'édition

Le contrôle d'édition

Les boutons (tous types) La liste déroulante

Le contrôle d'édition

Image utilisateur
Définition

Le contrôle d'édition est un contrôle rectangulaire utilisé dans les boîtes de dialogue pour permettre à l'utilisateur d'entrer et d'éditer du texte au clavier. On peut également l'utiliser dans une fenêtre standard.

Création

Pour créer un Contrôle d'édition, il faut appeler CreateWindow en spécifiant "EDIT" comme classe. Dans un fichier ressource, on écrira par exemple :

CONTROL "", IDC_NOMDUCONTROLE, <couleur nom="bleu">"EDIT"</couleur>, WS_CHILD | WS_VISIBLE, x, y, largeur, hauteur

Néanmoins, cela ne suffit pas car comme vous vous en doutez, nous avons plusieurs possibilités pour créer notre contrôle. Ce qui va nous intéresser est donc l'attribut de style . En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez définir un ou plusieurs de ces styles spécifiques qui commencent tous par "ES_" :

Nous avons créé le contrôle Edit, nous allons maintenant étudier les façons de l'utiliser. Ainsi, dans un premier temps, nous traiterons des notifications envoyées par le contrôle à la fenêtre parent, puis nous verrons les messages envoyés par la fenêtre parent au contrôle.

Les notifications

L'utilisateur envoie des requêtes d'édition en utilisant le clavier ou la souris. Le système envoie chaque requête à la fenêtre parent du contrôle dans le message WM_COMMAND.
J'ai considéré que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notification.

EN_ALIGN_LTR_EC

Cette notification est envoyée quand l'utilisateur change de direction dans le contrôle d'édition de gauche vers la droite.

EN_ALIGN_RTL_EC

Cette notification est envoyée quand l'utilisateur change de direction dans le contrôle d'édition de droite vers la gauche.

EN_CHANGE

L'utilisateur a modifié le texte du contrôle. Le message est envoyé après que le texte soit affiché (différent de EN-UPDATE).

EN_HSCROLL

L'utilisateur a cliqué sur la barre horizontale de défilement du contrôle d'édition.

EN_MAXTEXT

Quand l'utilisateur insère du texte, et que le nombre de caractères entrés est supérieur à ce qui est possible pour ce contrôle, l'insertion est tronquée.

EN_UPDATE

L'utilisateur a modifié le texte dans le contrôle d'édition, et le système va afficher le nouveau texte.

EN_VSCROLL

L'utilisateur a cliqué sur la barre verticale de défilement, ou a utilisé la roulette de la souris au-dessus du contrôle d'édition.

WM_CTLCOLOREDIT

Ce message est envoyé par le système à la fenêtre parent avant que le contrôle ne soit dessiné. Ce message contient le handle du contexte d'affichage (DC) dans wparam, et le handle de la fenêtre fille dans lparam. La fenêtre parent peut utiliser ces handle pour changer les couleurs du texte et du fond du contrôle d'édition.

Les messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam et de la valeur retournée.

EM_GETCUEBANNER

Recherche une sélection textuelle dans le texte affiché.

EM_GETFIRSTVISIBLELINE

Ce message recherche l'index de la ligne visible la plus haute dans un contrôle d'édition multiligne.

EM_GETHANDLE

Ce message renvoie le handle de la mémoire courante allouée au texte d'un contrôle d'édition multiligne.

EM_GETLIMITTEXT

Recherche les limites du texte courant.

EM_GETLINE

Ce message copie une ligne de texte depuis un contrôle d'édition, et le place dans un buffer défini.

EM_GETLINECOUNT

Ce message recherche le nombre de lignes dans un contrôle d'édition multiligne.

EM_GETMODIFY

Ce message recherche l'état du flag de modification du contrôle d'édition. Le flag indique si le contenu du contrôle a été modifié.

EM_GETPASSWORDCHAR

Ce message recherche le caractère d'un mot de passe que l'utilisateur entre dans le contrôle d'édition.

EM_GETRECT

Ce message recherche le rectangle de formatage d'un contrôle d'édition. Le rectangle de formatage est le rectangle dans lequel le contrôle affiche le texte. Ce rectangle est indépendant de la taille de la fenêtre du contrôle d'édition.

EM_GETSEL

Message qui recherche les positions des caractères de début et de fin d'une sélection dans le contrôle d'édition.

EM_GETWORDBREAKPROC

Ce message recherche l'adresse de la fonction courante de retour à la ligne.

EM_HIDEBALLOONTIP

Cache toute info bulle associée au contrôle d'édition.

EM_LINEFROMCHAR

Ce message recherche l'index de la ligne contenant l'index d'un caractère défini, dans un contrôle d'édition. L'index d'un caractère appartient à une suite dont le premier terme est 0.

EM_LINEINDEX

Ce message définit la limite de texte d'un contrôle d'édition. Cette limite est la quantité maximum de texte, en TCHAR, que l'utilisateur peut entrer dans le contrôle d'édition.

EM_LINELENGTH

Ce message recherche la longueur, en caractères, d'une ligne dans un contrôle d'édition.

EM_LINESCROLL

Fait défiler le texte dans un contrôle d'édition multiligne.

EM_POSFROMCHAR

Ce message recherche les coordonnées dans la zone client d'un caractère dans le contrôle d'édition.

EM_REPLACESEL

Ce message remplace la sélection courante dans le contrôle d'édition avec le texte spécifié.

EM_SCROLL

Ce message fait défiler le texte verticalement dans un contrôle d'édition. Ce message est équivalent au message WM_VSCROLL.

EM_SCROLLCARET

Ce message fait avancer le curseur dans le contrôle d'édition.

EM_SETCUEBANNER

Le texte du contrôle est affiché comme information, et va s'en aller dès que l'utilisateur commencera à utiliser le contrôle.

EM_SETHANDLE

Message qui définit le handle de la mémoire qui va être utilisée par un contrôle d'édition.

EM_SETLIMITTEXT

Ce message définit la limite du texte d'un contrôle d'édition. La limite du texte est la quantité maximum de texte, en TCHAR, que l'utilisateur peut entrer dans le contrôle d'édition.
C'est le même message que EM_LIMITTEXT.

EM_SETMODIFY

Ce message applique ou annule le flag de modification pour un contrôle d'édition. Le flag de modification indique si le texte à l'intérieur d'un contrôle d'édition a été modifié.

EM_SETPASSWORDCHAR

Ce message définit ou annule le caractère de mot de passe pour un contrôle d'édition. Quand le caractère de mot de passe est défini, ce caractère est affiché à la place des caractères entrés par l'utilisateur.

EM_SETREADONLY

Ce message définit ou annule le style ES_READONLY d'un contrôle d'édition.

EM_SETRECT

Ce message définit le rectangle de formatage d'un contrôle d'édition multiligne. Le rectangle de formatage est le rectangle dans lequel le contrôle dessine le texte. Il est indépendant de la taille de la fenêtre du contrôle d'édition.

EM_SETRECTNP

Ce message définit le rectangle de formatage d'un contrôle d'édition. Ce message est le même que le message EM_SETRECT à ceci près qu'il ne redessine pas la fenêtre du contrôle d'édition.

EM_SETSEL

Ce message sélectionne une suite de caractères dans un contrôle d'édition.

EM_SHOWBALLOONTIP

Ce message affiche une info-bulle associée à un contrôle d'édition.

EM_UNDO

Ce message annule la dernière opération du contrôle d'édition dans la file des annulations du contrôle.

Exemple
#include <windows.h>
#include <stdio.h>

#define ID_BOUTON1      110
#define ID_BOUTON2      111
#define ID_BOUTON3      112

//variables globales
HINSTANCE inst;
HWND hEdit[2]; //handle des deux contrôles d'édition

//Afficher un contrôle d'édition d'une ligne
HWND afficheEditUneLigne (HWND hwnd)
{
HWND hEdit=CreateWindow(
                        "EDIT",
                        "Modifiez le texte SVP",
                         WS_VISIBLE|WS_CHILD|WS_BORDER|ES_CENTER ,
                         50,10,500,20,hwnd,NULL,inst,NULL);

return hEdit;
}

//Afficher un contrôle d'édition multiligne
HWND afficheEditMultiligne (HWND hwnd)
{
    char texte[2000]={0};
strncat (texte, "Vous voudriez apprendre l'API windows et vous trouvez les autres ",1000);
strncat (texte, "cours présents sur le net trop difficiles, ou pas assez précis ?",1000);
strncat (texte, "Vous avez frappé à la bonne porte. Bienvenue dans ce tutorial.",1000);
strncat (texte, "Vous allez apprendre ici à vous servir d'une API très développée :",1000);
strncat (texte, "l'API Win32. Sa proximité avec le système d'exploitation lui permet ",1000);
strncat (texte, "d'avoir un contrôle total sur n'importe quelle partie de l'OS et un ",1000);
strncat (texte, "grand éventail de fonctions. L'API Windows est, malheureusement et comme ",1000);
strncat (texte, "son nom l'indique, exclusivement dédié à Windows. Donc si vous ",1000);
strncat (texte, "possédez un autre OS, passez votre chemin. Je considère dans ce tutorial que ",1000);
strncat (texte, "vous ne connaissez strictement rien à l'API Windows, ceci pour ne pas ",1000);
strncat (texte, "décourager les débutants, néammoins, vous aurez besoin des connaissances ",1000);
strncat (texte, "acquises tout au long du tuto de M@teo. Vous trouverez donc ici des ",1000);
strncat (texte, "cours évolutifs, une documentation sur les différents messages, ",1000);
strncat (texte, "notifications, styles, fonctions etc., des projets grands, ",1000);
strncat (texte, "moyens ou petits ainsi que des bibliothèques statiques  ",1000);
strncat (texte, "ou dynamiques, et enfin des annexes. ",1000);




HWND hEdit=CreateWindow("EDIT",
                        texte,
                        WS_VISIBLE|WS_CHILD|ES_MULTILINE|ES_AUTOVSCROLL,
                        50,50,500,300,hwnd,NULL,inst,NULL);

return hEdit;
}

//Traitement des messages
void messageCtl(HWND hwnd,UINT message, WPARAM wParam, LPARAM lParam)
{
UINT motBas =  LOWORD(wParam);
HWND hCtl= (HWND) lParam;
int nombreLignes=0;
char lignes[70]={0},nLignes[5]={0};

        switch(motBas)
            {
            case ID_BOUTON1://nombre de lignes

            //envoyer le message approprié pour trouver le nombre de lignes dans le contôle
               nombreLignes = SendMessage( hEdit[1], EM_GETLINECOUNT,wParam,lParam);

            //transformer un integer en chaîne de caractères et afficher le résultat
                sprintf(nLignes,"%d",nombreLignes);
                strncat(lignes,"Il y a ",70);
                strncat(lignes,nLignes,70);
                strncat(lignes, " lignes dans ce texte. Supprimez ou ajoutez \n en et testez le message.",70);
                MessageBox(hwnd,lignes,"",MB_OK);
            break;
            case ID_BOUTON2: //marges de gauche
                wParam = EC_LEFTMARGIN ;
                LOWORD(lParam) = 100;
                //ou
                //lParam = (LPARAM)MAKELONG (100,0);
                //envoi du message au contrôle d'édition
                SendMessage( hEdit[1], EM_SETMARGINS,wParam,lParam);
                MessageBox(hwnd,"A vous de trouver comment réduire \nles lignes maintenant ! :)","",MB_OK);
            break;
            }
}

//Traitement des notifications
void NotificationControle(HWND hwnd,UINT message, WPARAM wParam, LPARAM lParam)
{
UINT motHaut= HIWORD(wParam);
HWND hCtl= (HWND) lParam;

switch(motHaut)
{
        //Notification EN_CHANGE envoyée par le contrôle
        case EN_CHANGE:
                if (hCtl == hEdit[0])
                {
                MessageBox(hwnd,"Vous avez modifié le texte d'une contrôle à une ligne !!","",MB_OK);
                }
        break;
        case BN_CLICKED: // cliquer sur un des boutons pour analyser les messages
               messageCtl((HWND)hwnd,(UINT)message,(WPARAM)wParam,(LPARAM)lParam);
        break;
}
}


//Boutons pour la démo
HWND boutonMessage(HWND hwnd,HMENU code,long x, long y, char * texte)
{
HWND hBouton=CreateWindow(
                        "BUTTON",
                        texte,
                        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                        x,y,
                        350,30,
                        hwnd,
                        code,
                        inst,
                        NULL);
return hBouton;
}

LRESULT CALLBACK  Procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    static HWND hBouton[3]={0};

switch (message)

    {
        //afficher les contrôles d'édition à la réception du message WM_CREATE
        case WM_CREATE:
               hEdit[0]=afficheEditUneLigne (hwnd);
               hEdit[1]=afficheEditMultiligne(hwnd);
        //affichage des boutons pour la démo:
               hBouton[0]= boutonMessage(hwnd, (HMENU)ID_BOUTON1,120,355,"Tester le message EM_GETLINECOUNT");
               hBouton[1]= boutonMessage(hwnd, (HMENU)ID_BOUTON2,120,395,"Test le EM_SETMARGINS (Gauche)");
               hBouton[2]= boutonMessage(hwnd, (HMENU)ID_BOUTON3,120,435,"Test le EM_SETMARGINS (Droite)");
        return 0;

        case WM_COMMAND:
            //Recevoir une notification
            NotificationControle(hwnd,message,wParam,lParam);
              return 0;

        case WM_CLOSE:
             DestroyWindow(hwnd);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

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

int WINAPI WinMain(HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
                                        LPSTR lignesDeCommande, int modeDAffichage)
{
       //Variables de la fonction principale
    MSG msg;
    WNDCLASS wc;
        HWND hwnd;
        inst = cetteInstance;
        // Structure de la classe de la fenêtre principale
    wc.style = 0 ;
    wc.lpfnWndProc = Procedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = cetteInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(1+ COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "ClassePrincipale";

        //Enregistrer la classe de fenêtre
    if(!RegisterClass(&wc)) return FALSE;
    hwnd = CreateWindow("ClassePrincipale", "Démo du contrôle d'édition",WS_OVERLAPPEDWINDOW   ,
                              200,100,600 ,500, NULL, NULL, cetteInstance, NULL);
        if (!hwnd)  return FALSE;
    ShowWindow(hwnd,SW_SHOW);
        UpdateWindow( hwnd );

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

Rédactrice : acryline


Les boutons (tous types) La liste déroulante

La liste déroulante

Le contrôle d'édition La liste de choix

La liste déroulante

Ce contrôle est appelé sous Windows une COMBOBOX, et permet d'afficher une liste déroulante de choix.

Image utilisateur
Création

Pour créer une combobox, il faut appeler CreateWindow en spécifiant "COMBOBOX" comme classe.

Néanmoins, cela ne suffit pas, car comme vous l'avez vu, il y a plusieurs possibilités. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles :

Notifications
CBN_CLOSEUP

Cette notification est envoyée lorsque la liste déroulante a été fermée.

CBN_DBLCLK

Cette notification est envoyée lorsque l'utilisateur double-clique sur un élément de la liste.

CBN_DROPDOWN

Cette notification est envoyée lorsque la liste déroulante a été ouverte.

CBN_EDITCHANGE

Cette notification est envoyée lorsque le texte (ou une portion de texte) de l'EDIT a été modifié (soit parce que l'utilisateur a sélectionné un élément, soit parce qu'il a entré du texte).

CBN_EDITUPDATE

Cette notification est envoyée lorsque le texte (ou une portion de texte) de l'EDIT a été modifié (soit parce que l'utilisateur a sélectionné un élément, soit parce qu'il a entré du texte).

CBN_SELCHANGE

Cette notification est envoyée lorsque la sélection en cours de la liste a changé.

CBN_SELENDCANCEL

Cette notification est envoyée lorsque l'utilisateur clique sur un élément, puis clique sur un autre contrôle, annulant alors la sélection.

CBN_SELENDOK

Cette notification est envoyée lorsque l'utilisateur clique sur un élément, puis ferme la liste, acceptant alors la sélection.

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam et lParam et de la valeur retournée.

CB_ADDSTRING

Ajoute un élément à la liste (une chaîne de caractères).

CB_DELETESTRING

Supprime un élément de la liste (une chaîne de caractères).

CB_FINDSTRING

Ce message permet de rechercher un élément qui commence par une chaîne de caractères spécifiée.

CB_FINDSTRINGEXACT

Ce message permet de rechercher une chaîne de caractères qui correspond exactement à celle spécifiée.

CB_GETCOUNT

Ce message permet de connaître le nombre d'éléments qu'il y a dans la liste.

CB_GETCURSEL

Ce message permet de connaître l'index de l'élément sélectionné.

CB_GETDROPPEDSTATE

Ce message permet de savoir si la liste est déroulée ou non.

CB_GETITEMDATA

Ce message permet de récupérer la valeur associée à un élément de la liste par le message CB_SETITEMDATA.

CB_GETLBTEXT

Ce message permet de récupérer le texte d'un élément.

CB_GETLBTEXTLEN

Ce message permet de récupérer la longueur du texte d'un élément.

CB_INSERTSTRING

Ce message permet d'insérer une chaîne de caractères dans la liste.

CB_LIMITTEXT

Ce message permet de limiter le texte écrit par l'utilisateur dans le contrôle EDIT.

CB_RESETCONTENT

Ce message permet de supprimer tous les éléments de la liste.

CB_SETCURSEL

Ce message permet de définir l'élément sélectionné.

CB_SETITEMDATA

Ce message permet d'associer une valeur à un élément de la liste.

CB_SHOWDROPDOWN

Ce message permet de montrer où cacher la liste.

Exemple

Ceci est le code complet d'un exemple utilisant la combobox :

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_COMBOBOX_1       110
#define ID_BUTTON_1         111
#define ID_BUTTON_2         112
#define ID_EDIT_1           113

HWND hComboBox1;
HWND hEdit1;

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    hComboBox1=CreateWindow(
        "COMBOBOX",
        "",
        WS_CHILD|WS_VISIBLE|CBS_DISABLENOSCROLL|CBS_DROPDOWNLIST,
        10,10,
        180,150,
        fenetrePrincipale,
        (HMENU)ID_COMBOBOX_1,
        instance,
        NULL);

     hEdit1=CreateWindow(
        "EDIT",
        "Entrez le texte à ajouter",
        WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL|WS_BORDER,
        10,40,
        180,20,
        fenetrePrincipale,
        (HMENU)ID_EDIT_1,
        instance,
        NULL);

    CreateWindow(
        "BUTTON",
        "Ajouter",
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
        10,70,
        70,20,
        fenetrePrincipale,
        (HMENU)ID_BUTTON_1,
        instance,
        NULL);

    CreateWindow(
        "BUTTON",
        "Supprimer",
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
        90,70,
        70,20,
        fenetrePrincipale,
        (HMENU)ID_BUTTON_2,
        instance,
        NULL);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message,WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    UINT iCode=HIWORD(wParam);
    HWND hCtl=(HWND)lParam;

   if(iId==ID_COMBOBOX_1)
   {

   }
   else if(iId==ID_BUTTON_1)
   {
        CHAR lpText[256];
        GetWindowText(hEdit1,lpText,256);

        SendMessage(hComboBox1,CB_ADDSTRING,0,(LPARAM)lpText);
   }
   else if(iId==ID_BUTTON_2)
   {
       LRESULT lRes=SendMessage(hComboBox1,CB_GETCURSEL,0,0);
       if(lRes==CB_ERR)
       {
           MessageBox(fenetrePrincipale,"Aucun élément sélectionné","Action annulée",MB_OK);
           return ;
       }

       SendMessage(hComboBox1,CB_DELETESTRING,(WPARAM)lRes,0);
   }
}

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

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,wParam,lParam);
    }
}


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

    instance = cetteInstance;

    classeFenetre.style = CS_DBLCLKS;// pour attraper les double-clics
    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,
        210,140,
        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;
}

Rédacteur : pamaury


Le contrôle d'édition La liste de choix

La liste de choix

La liste déroulante La glissière

La liste de choix

Une list-Box (liste de choix en français) est un contrôle qui contient une liste d'items que l'utilisateur peut choisir comme dans un menu déroulant. On utilise ce contrôle quand l'application doit présenter une liste de noms, des noms de fichiers par exemple, parmi lesquels l'utilisateur fait un choix. Il sélectionne une chaîne en cliquant dessus. La sélection est en surbrillance, et une notification est envoyée à la fenêtre parent.

Image utilisateur
Création

Pour créer une list-box, il faut appeler CreateWindow en spécifiant "LISTBOX" dans la classe. Ou bien dans un fichier ressource, on écrira par exemple :

CONTROL "", ID_NOMDELALISTBOX, "LISTBOX", WS_CHILD | WS_VISIBLE, x, y, largeur, hauteur

Néanmoins, cela ne suffit pas, car comme vous vous en doutez, nous avons plusieurs possibilités pour créer notre contrôle. Ce qui va nous intéresser est donc l'attribut de style. En plus de spécifier les styles habituels (WM_CHILD,WM_VISIBLE), vous pouvez spécifier un ou plusieurs de ces styles spécifiques qui commencent tous par "LSB_":

Les notifications

L'utilisateur envoie des requêtes d'édition en utilisant le clavier ou la souris. Le système envoie chaque requête à la fenêtre parent du contrôle dans le message WM_COMMAND.
J'ai considéré que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notification.

LBN_DBLCLK

Notification envoyée par la list-box quand l'utilisateur double-clique sur une chaîne dans la list-box.

LBN_SELCANCEL

Une application envoie cette notification quand l'utilisateur annule la sélection dans une list-box.

LBN_SELCHANGE

Une application envoie cette notification quand la sélection dans une list-box vient de changer.

LBN_SETFOCUS

Une application envoie cette notification quand la list-box reçoit le focus clavier.

Parmi les notifications, il existe également des messages de types WM_, qui seront reçus par la procédure de la fenêtre parent.

WM_CTLCOLORLISTBOX

Ce message est envoyé à la fenêtre parent de la list-box avant que le système ne dessine la list-box. En répondant à ce message, la fenêtre parent peut définir les couleurs du texte et du fond de la list-box en utilisant le dispositif de handle contextuels spécifique.
La list-box sera coloriée avec la brosse retournée par ce message.

Les messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam, de lParam et de la valeur retournée.

LB_ADDFILE

L'application envoie ce message pour ajouter le nom de fichier spécifique à la list-box qui contient une liste de répertoires.

LB_ADDSTRING

Une application envoie ce message pour ajouter une chaîne dans la list-box. Si la list-box n'a pas le style LBS_SORT, la chaîne est ajoutée à la fin de la liste. Autrement, la chaîne est insérée dans la liste, et la liste est réorganisée.

LB_DELETESTRING

Ce message est envoyé par l'application pour supprimer une chaîne dans la list-box.

LB_FINDSTRING

Ce message est envoyé par l'application pour trouver la première chaîne dans la list-Box contenant le préfixe précisé.

LB_FINDSTRINGEXACT

Ce message est envoyé par une application pour trouver la première chaîne associée à la chaîne précisée dans le paramètre lParam.

LB_GETCARETINDEX

Ce message est envoyé par une application pour déterminer l'index de l'item ayant le focus dans une list-box à sélection multiple. L'item peut ou ne peut pas être sélectionné.

LB_GETCOUNT

Ce message est envoyé par l'application pour rechercher le nombre d'items qu'il y a dans une list-box.

LB_GETCURSEL

Ce message est envoyé par une application pour recherche l'index de l'item sélectionné, s'il y en a un, dans une list-box à sélection unique.

LB_GETITEMDATA

Ce message est envoyé par une application pour trouver la valeur 32 bit définie par l'application, et associée à l'item spécifié de la list-box.

LB_GETITEMRECT

Ce message est envoyé par l'application pour rechercher les dimensions du rectangle qui limite un item de la list-box quand il est normalement affiché dans la list-box.

LB_GETSELITEMS

Ce message est envoyé par une application pour remplir un buffer avec un tableau d'entiers (integer) qui définit les numéros des items dans la sélection d'items d'une list-box à sélection multiple.

LB_GETSELCOUNT

Ce message est envoyé par une application pour rechercher le nombre total d'items sélectionnés dans une list-box à sélection multiple.

LB_GETTEXT

Ce message est envoyé par une application pour rechercher une chaîne dans une list-box.

LB_GETTEXTLEN

Une application envoie le message LB_GETTEXTLEN pour retrouver la longueur d'une chaîne dans la list-box.

LB_INSERTSTRING

Une application envoie ce message pour insérer une chaîne dans une list-box. Différemment du message LB_ADDSTING, ce message ne produit pas de liste que l'on pourrait trier avec le style LBS_SORT.

LB_RESETCONTENT

L'application envoie ce message pour supprimer tous les items d'une list-box.

LB_SETCURSEL

Une application envoie ce message pour sélectionner une chaîne et la faire apparaître en la faisant défiler dans la zone visible, si nécessaire. Quand la nouvelle chaîne est sélectionnée, la list-box supprime la surbrillance de la chaîne sélectionnée préalablement.

LB_SETHORIZONTALEXTENT

Une application envoie ce message pour définir la distance de déplacement horizontal (scrolling), en pixels, d'une list-box. Si la largeur de la list-box est inférieure à cette valeur, la barre horizontale fait défiler les items horizontalement. Si la largeur de la list-box est égale ou supérieure à cette valeur, la barre horizontale de défilement est cachée.

LB_SETITEMDATA

Une application envoie ce message pour définir une valeur associée à l'item spécifié d'une list-box.

LB_SETSEL

Une application envoie ce message pour sélectionner une chaîne dans une list-box à sélection multiple.

Exemple
#include <windows.h>
#include <stdio.h>


#define ID_BOUTON1      110
#define ID_BOUTON2      111

//variables globales
HINSTANCE inst;
HWND hListBox[2]; //handle des deux contrôles d'édition

//Afficher une list-Box et la remplir
HWND afficheListBox (HWND hwnd)
{
HWND hListBox=CreateWindow(
                        "LISTBOX","",
                         WS_VISIBLE|WS_CHILD|WS_BORDER |LBS_NOTIFY,
                         50,50,150,200,hwnd,NULL,inst,NULL);

//Entrer des items dans la list-box
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Première entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Deuxième entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Troisième entrée");
SendMessage (hListBox, LB_ADDSTRING ,0,(LPARAM)"Quatrième entrée");

return hListBox;
}

//Boutons pour la démo
HWND boutonMessage(HWND hwnd,HMENU code,long x, long y, char * texte)
{
HWND hBouton=CreateWindow(
                        "BUTTON",
                        texte,
                        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
                        x,y,
                        350,30,
                        hwnd,
                        code,
                        inst,
                        NULL);
return hBouton;
}


LRESULT CALLBACK  Procedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    static HWND hBouton[1]={0};
    char texte[5]={0},texte2[50]={0};
switch (message)

    {
        //afficher les contrôles d'édition à la réception du message WM_CREATE
        case WM_CREATE:
               hListBox[0]=afficheListBox (hwnd);
               //afficher les boutons pour la démo
               hBouton[0]= boutonMessage(hwnd, (HMENU)ID_BOUTON1,220,90,"Message LB_GETCURSEL: item sélectionné ");
        return 0;

        case WM_COMMAND:

          switch (LOWORD(wParam))
          {
              //test du message LB_GETCURSEL
              case ID_BOUTON1 :
               //Trouver le numéro de la ligne sélectionnée, et transformer le résultat en chaîne
               sprintf(texte,"%d",(SendMessage(hListBox[0],LB_GETCURSEL,0,0)+1));
               //Afficher une boîte de dialogue
               //s'il y a une sélection
              if (SendMessage(hListBox[0],LB_GETCURSEL,0,0)!= LB_ERR)
              {
                strncat(texte2,"Vous avez sélectionné la ligne ",50);
                strncat(texte2,texte,50);
              }
              else //pas de sélection
              {
              strncat(texte2,"Vous n'avez sélectionné aucune ligne." ,50);
              }
              MessageBox(hwnd,(LPCTSTR )texte2,"",MB_OK|MB_ICONWARNING);
              break;
              //ajoutez d'autres tests et d'autres boutons si vous le voulez
              //case ID_BOUTON2:
              //autre test
              //breaks;
          }

           switch (HIWORD(wParam))
          {
              case LBN_SELCHANGE :
                char temp[256] = "";
                SendMessage(hListbox, LB_GETTEXT, SendMessage(hListbox[0], LB_GETCURSEL, 0, 0), temp);
                MessageBox(hwnd,temp, "Item sélectionné :", MB_ICONINFORMATION);
              break;
          }
              return 0;

        case WM_CLOSE:
             DestroyWindow(hwnd);
            return 0;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

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

int WINAPI WinMain(HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
                                        LPSTR lignesDeCommande, int modeDAffichage)
{
       //Variables de la fonction principale
    MSG msg;
    WNDCLASS wc;
        HWND hwnd;
        inst = cetteInstance;
        // Structure de la classe de la fenêtre principale
    wc.style = 0 ;
    wc.lpfnWndProc = Procedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = cetteInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =  (HBRUSH)(1+ COLOR_BTNFACE);
    wc.lpszMenuName =  NULL;
    wc.lpszClassName = "ClassePrincipale";

        //Enregistrer la classe de fenêtre
    if(!RegisterClass(&wc)) return FALSE;
    hwnd = CreateWindow("ClassePrincipale", "Démo du contrôle List-Box",WS_OVERLAPPEDWINDOW   ,
                              200,100,600 ,300, NULL, NULL, cetteInstance, NULL);
        if (!hwnd)  return FALSE;
    ShowWindow(hwnd,SW_SHOW);
        UpdateWindow( hwnd );

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

Rédactrice : acryline


La liste déroulante La glissière

La glissière

La liste de choix Le contrôle Static

La glissière

Le slider, ou trackbar, (glissière en français) est très utilisé dans la plupart des programmes d'aujourd'hui. Vous l'avez sûrement utilisé sans même savoir ce qui se passait derrière, chose que je propose de faire ici. :p

Image utilisateur

Il est loin d'être particulièrement difficile, et je ne vous cacherai pas que sa préparation est plus l'objet d'erreurs que son utilisation. En effet, il fait partie de la Common Controls Library, ou bibliothèque des contrôles communs, que vous devrez intégrer et lier au projet.

Préparation préalable
Intégration

Afin de déclarer le slider, ou plutôt sa classe, vous aurez besoin d'insérer dans votre projet :

#include <commctrl.h>
Linker

Et en ce qui concerne le linker, vous devrez ajouter l'entrée -lcomctl32, ou sous C::B comctl32.

En bref, rien de bien sorcier. La seule source d'erreurs est une potentielle faute d'orthographe. ;)

Création

Concernant sa création, vous pouvez passer comme d'habitude par CreateWindow, ou par les template de ressource. Le mot-clé à utiliser est alors CONTROL. Je vous l'avais dit qu'il était générique... c'est le mot-clé se rapprochant le plus de CreateWindow. Mais dans les deux cas, le nom de classe à utiliser est TRACKBAR_CLASS. En plus de spécifier cette constante prédéfinie au 2ème argument de CreateWindow (ou au 3ème de CONTROL), vous pouvez utiliser un ou plusieurs, par associations de flag, des styles suivants :

Sa création étant maintenant démystifiée, passons maintenant aux notifications que ce contrôle peut envoyer, et ensuite aux messages qu'il peut recevoir et traiter.

Notifications

Je vais décrire ici les notifications envoyées par le contrôle via le message WM_COMMAND (sauf mention contraire). Il est assumé que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

WM_HSCROLL ou WM_VSCROLL

Envoyés à la procédure de la fenêtre parent, ces notifications indiquent un drag du curseur par l'utilisateur :
- horizontal, pour un slider orienté horizontalement
- vertical, pour un slider orienté verticalement.

TB_THUMBTRACK

Cette notification est envoyée lorsque l'utilisateur bouge le curseur.

TB_THUMBPOSITION

Cette notification est envoyée lorsque l'utilisateur a bougé le curseur, puis relâché la souris (équivaut donc à TB_THUMBTRACK + WM_LBUTTONUP).

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message je décrirai le rôle et la signification de wParam et lParam, ainsi que la valeur retournée.

TBM_CLEARSEL

Ce message permet de nettoyer la sélection en cours.

TBM_CLEARTICS

Ce message permet de supprimer les marques du slider, exceptées la première et la dernière.

TBM_GETCHANNELRECT

Ce message permet de récupérer les dimensions du rectangle limite du slider, dans lequel il évolue.

TBM_GETPOS

Ce message permet de récupérer la position courante du curseur.

TBM_GETRANGEMAX ou TBM_GETRANGEMIN

Ces messages permettent de récupérer la valeur maximale ou minimale que peut prendre le slider (ses bornes).

TBM_GETTHUMBLENGTH

Ce message permet de récupérer la longueur du curseur du slider.

TBM_GETTHUMBRECT

Ce message permet de récupérer les coordonnées et dimensions du curseur.

TBM_GETTOOLTIPS

Ce message permet de récupérer un handle sur un ballon de message associé au slider s'il y a lieu.

TBM_SETPOS

Ce message permet de définir la position courante du curseur dans le slider.

TBM_SETRANGE

Ce message permet de définir les bornes du slider.

TBM_SETRANGEMAX ou TBM_SETRANGEMIN

Ces messages ont la même fonction que TBM_SETRANGE, mais sur une seule des bornes, spécifiée dans leur nom.

TBM_SETTHUMLENGTH

Ce message permet de définir la longueur du curseur du slider.

TBM_SETTICFREQ

Ce message permet de définir la fréquence des marques.

TBM_SETTIPSIDE

Ce message permet de définir le côté du slider où sera affiché le ballon d'infos.

TBM_SETTOOLTIPS

Ce message permet de définir le ballon d'informations associé au slider.

Exemple

Le présent exemple montre quelques fonctions permettant de créer ou manipuler le slider simplement (à utiliser, non compilable tel quel) :

HWND createSlider (HINSTANCE instance, HWND parentWindow)
{
    return CreateWindow(TRACKBAR_CLASS, "", WM_CHILD | WM_VISIBLE | TBS_NOTICKS, 20, 20, 300, 20, parentWindow, (HMENU)NULL, instance, NULL);
};

void setRange (HWND slider, int min, int max)
{
    SendMessage(slider, TBM_SETRANGE, TRUE, MAKELONG(min,max));

    return;
}

void setPos (HWND slider, int position)
{
    SendMessage(slider, TBM_SETPOS, TRUE, position);
          
    return;
}

int getPos (HWND slider)
{
    return SendMessage(slider, TBM_GETPOS, 0, 0);
}

Celui-ci est tout bête, mais je ne pense pas que vous vous servirez des autres avant un petit bout de temps. ;)
Rédacteur : Mg++


La liste de choix Le contrôle Static

Le contrôle Static

La glissière L'affichage

Le contrôle Static

Ce contrôle est l'un des plus simples et des plus utiles qui soit : il permet d'afficher un libellé de texte, une icône ou une image, mais qui n'interagit pas avec l'utilisateur, d'où son nom.

Image utilisateur
Création

Pour créer un STATIC, il faut appeler CreateWindow avec comme classe "STATIC", et ajouter en plus des styles classiques (WS_CHILD,WS_VISIBLE) un ou plusieurs de ces styles :

Notifications

Je vais décrire ici les notifications envoyées par le contrôle via le message WM_COMMAND (sauf mention contraire) . Il est supposé que vous savez comment interagir avec ce message, et je décrirai donc ici uniquement les codes de notifications.

STN_CLICKED

Ce message est envoyé lorsque l'utilisateur clique sur le contrôle.

STN_DBLCLK

Ce message est envoyé lorsque l'utilisateur double-clique sur le contrôle.

STN_DISABLE

Ce message est envoyé lorsque le contrôle est désactivé.

STN_ENABLE

Ce message est envoyé lorsque le contrôle est activé.

WM_CTLCOLORSTATIC

Ce message est envoyé à la fenêtre parent juste avant que le contrôle ne soit dessiné.
Ceci est principalement utile pour changer la couleur du texte, ou du fond du contrôle pour le dessin .

Les paramètres valent alors:

Messages

Ces messages peuvent être envoyés au contrôle avec la fonction :

SendMessage(hCtl,iCode,wParam,lParam)

Pour chaque message, je décrirai le rôle et la signification de wParam, lParam et de la valeur retournée.

STM_GETICON

Ce message permet de récupérer le handle de l'icône associée au contrôle.

STM_GETIMAGE

Ce message permet de récupérer le handle de l'image associée au contrôle.

STM_SETICON

Ce message permet de définir l'icône associée au contrôle.

STM_SETIMAGE

Ce message permet de définir l'image associée au contrôle.

Exemple

Ceci est le code complet d'un exemple utilisant les STATIC.

#include <windows.h>
#include <stdio.h>

HINSTANCE instance;

#define ID_STATIC_1     110

VOID RemplieFenetrePrincipale(HWND fenetrePrincipale)
{
    HWND hCtl=CreateWindow(
        "STATIC",
        "Double-\ncliquez\nsur moi !!",
        WS_CHILD|WS_VISIBLE|SS_CENTER|SS_NOTIFY,
        10,10,
        100,60,
        fenetrePrincipale,
        (HMENU)ID_STATIC_1,
        instance,
        NULL);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_ICON,
        10,80,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);

    HANDLE hIcon=LoadIcon(NULL,IDI_ERROR);
    SendMessage(hCtl,STM_SETICON,(WPARAM)hIcon,0);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_ICON,
        120,10,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);

    HANDLE hImage=LoadImage(NULL,MAKEINTRESOURCE(OCR_NORMAL),IMAGE_CURSOR,0,0,LR_SHARED);
    SendMessage(hCtl,STM_SETIMAGE,(WPARAM)IMAGE_CURSOR,(LPARAM)hImage);

    hCtl=CreateWindow(
        "STATIC",
        "",
        WS_CHILD|WS_VISIBLE|SS_BITMAP,
        120,60,
        0,0,
        fenetrePrincipale,
        NULL,
        instance,
        NULL);
    hImage=LoadImage(NULL,MAKEINTRESOURCE(OBM_CHECK),IMAGE_BITMAP,0,0,LR_SHARED);
    SendMessage(hCtl,STM_SETIMAGE,(WPARAM)IMAGE_BITMAP,(LPARAM)hImage);
}

VOID NotificationControle(HWND fenetrePrincipale,UINT message,WPARAM wParam, LPARAM lParam)
{
    UINT iId=LOWORD(wParam);
    UINT iCode=HIWORD(wParam);
    HWND hCtl=(HWND)lParam;

    switch(iId)
    {
        case ID_STATIC_1:
            if(iCode==STN_DBLCLK)
                MessageBox(fenetrePrincipale,"Vous avez double-cliqué sur le premier STATIC","",MB_OK);
            break;
    }
}

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

    switch (message)
    {
        case WM_CREATE:
            RemplieFenetrePrincipale(fenetrePrincipale);
            return 0;
        case WM_COMMAND:
            NotificationControle(fenetrePrincipale,message,wParam,lParam);
            return 0;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(fenetrePrincipale,message,wParam,lParam);
    }
}


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

    instance = cetteInstance;

    classeFenetre.style = CS_DBLCLKS;// pour attraper les double-clics
    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,
        200,200,
        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;
}

Rédacteur : pamaury

Ce sont les principaux contrôles. Les autres vous seront présentés dans un autre chapitre de ce cours. ;)


La glissière L'affichage

L'affichage

Le contrôle Static Les contextes de périphériques

Vous allez durant ce chapitre apprendre à gérer l'affichage manuel d'informations dans votre fenêtre : celles ci peuvent être du texte, des formes géométriques (dans ce cas ci on parlera de dessin) ou des images (en général des bitmaps).

Les contextes de périphériques

L'affichage Le texte

Les contextes de périphériques

L'API windows permet de dessiner dans n'importe quel périphérique (imprimante, écran etc.) sans se préoccuper de son fonctionnement (le reste du travail est effectué grâce aux drivers des périphériques), ce qui, évidemment, est très pratique ;) .

Afin de pouvoir dessiner, nous devons obtenir un handle de contexte de périphérique, traduit en anglais par "Handle on Device Context", ou HDC.

Théorique

Un HDC peut être assimilé, pour une meilleure compréhension, à une surface potentiellement dessinable obtenue par l'appel de GetDC. Cette fonction, prenant comme unique paramètre le handle de la fenêtre dont l'on veut récupérer le DC, retourne un HDC portant sur toute la surface de la fenêtre.

Toutes les fonctions de dessin ne nécessitent généralement qu'un contexte de périphérique. Vous vous demandez alors sûrement comment appliquer des personnalisations temporaires pour chaque dessin ? Eh bien toutes ces personnalisations se passent sur le DC lui-même. La fonction de dessin, utilisant le DC courant, sera lors affectée par les modifications appliquées.

Une fois le DC récupéré, chaque personnalisation possible (changements de couleurs, de fontes...) se fait à l'aide d'une sélection. Cette sélection d'un objet (personnalisation) dans un contexte de périphérique a pour effet de l'appliquer au DC. C'est alors qu'arrive SelectObject. Cette fonction, prenant comme deuxième paramètre l'objet à sélectionner dans le HDC spécifié en premier paramètre, retourne l'ancien objet correspondant avant l'application. Ainsi, pour qu'une couleur seule (par exemple, le vert) soit appliquée sur un HDC, on obtient :

brushDeCouleurAncienne = SelectObject(dcCourant, brushAppliquee);

//Où brushAppliquee = CreateSolidBrush(RGB(0,255,0));

Avant de commencer les cas particuliers, un petit cours sur les couleurs (utilisées partout) s'impose :

Les couleurs

Les couleurs sont gérées différemment de la logique actuelle. Je pense en particulier aux couleurs standards comme le RGB, ou Red Green Blue, avec la teneur de chaque couleur primaire variant entre 0 et 255 : en winAPI, les couleurs et motifs sont gérées par Brosses, ou Brushes, et donc HBRUSH.

D'accord, mais comment passer d'une couleur RGB à une Brush ?

Eh bien, Microsoft pense à nous. ;) Nous avons donc à notre disposition CreateSolidBrush prenant comme seul paramètre la valeur hexadécimale de la couleur, ou une variable COLORREF. Pour transformer la couleur RGB en valeur COLORREF afin de la passer en paramètre à CreateSolidBrush, nous pouvons utiliser la macro RGB. Au final, la syntaxe à utiliser afin de récupérer une brush de couleur RGB est la suivante :

const HBRUSH rouge = CreateSolidBrush(RGB(255,0,0));

Mais les brosses peuvent aussi être utilisées pour, par exemple, changer de motif. Pour cela, msdn nous propose plusieurs fonctions, parmi lesquelles (à titre d'exemple) CreateHatchBrush, permettant de créer une brosse de motif de pointillés, traits ou autres par le passage du type de motif en premier paramètre, et de sa couleur en deuxième.

Où l'intégrer ?

Tous les dessins peuvent être effectués n'importe où dans le code en récupérant un DC. Seulement, dans le cas d'une superposition d'une autre fenêtre sur celle-ci, tous les dessins se retrouvent effacés. Afin de le contrer, nous devons utiliser un message envoyé par le système quand la fenêtre a besoin d'être redessinée : WM_PAINT.

:( Un scintillement désagréable subsiste...

Exact, la cause en est que les dessins sont alors entièrement redessinés, même s'ils sont toujours visibles. Pour y remédier, il est préférable d'utiliser la fonction BeginPaint à la place de GetDC ; même si elle prend le même paramètre (avec en plus un deuxième : un pointeur sur une structure PAINTSTRUCT pour le stockage... pas importante pour nous) et renvoie aussi un HDC, elle permet de ne redessiner que le strict nécessaire.

N'oubliez pas ce que vous venez d'apprendre afin de l'appliquer à ce qui suit.
Rédacteur : Mg++


L'affichage Le texte

Le texte

Les contextes de périphériques Les formes géométriques

Le texte

Avant de l'afficher, une personnalisation du texte peut être exécutée (police, couleur).

Personnalisation du texte

Si vous ne savez pas encore ce que signifie le mot fonte, vous devez alors savoir qu'il s'agit d'un modèle d'écriture pour du texte. Pour vous faire une idée, "Times New Roman", "Arial", "Arial Black", etc... sont des fontes (connues qui plus est).
Si vous n'avez toujours pas compris, des fontes sont des polices de caractères :D .

Afin de pouvoir en utiliser lors de fonctions d'affichage de texte, nous devons récupérer un handle de fonte, ou HFONT. La récupération est semblable aux deux précédentes fonctions, et peut être effectuée grâce à CreateFont ou CreateFontIndirect. Comme CreateFont possède un prototype long et détaillé, je préfère présenter l'autre, qui passe indirectement (d'où son nom) par une structure détaillant les spécificités de la police (souligné, gras, italique, police, taille etc...) : LOGFONT.

Avant toute utilisation, certaines structures doivent être remises à zéro : ZeroMemory s'en occupe. Il faut lui passer pour son premier paramètre un pointeur de la structure ciblée, et en deuxième sa taille, donnée par sizeof.

LOGFONT, faisant partie de ce type de structures, doit être initialisée à zéro.
Ensuite, vous pouvez :

Une fois la structure définie, vous n'avez plus qu'à passer l'adresse de votre structure au seul paramètre possible de CreateFontIndirect, qui s'occupe de vous retourner le handle de fonte que vous devez récupérer. Exemple :

fonte = CreateFontIndirect(&structLogfont);
Affichage de texte

L'affichage du texte à l'écran s'exerce grâce à :

BOOL TextOut (HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
Pour aller plus loin

Vous pouvez :

Code d'exemple

En guise d'exemple, la fonction ci-dessous affiche la chaîne contenue dans chaine, avec les styles spécifiés en arguments, dans la fenêtre présente en argument n°1 :

void dessineTexte (HWND fenetreCiblee, char *chaine, RECT dimensions, BOOL souligne, char *police, COLORREF couleur)
{       
        //Déclarations préalables
        LOGFONT structFonte;
        PAINTSTRUCT ps;
        HFONT fonte;
        HDC dc;

        //Remplissage de la LOGFONT
        ZeroMemory(&structFonte, sizeof(LOGFONT));
        strcpy(structFonte.lfFaceName, police);
        structFonte.lfHeight = dimensions.top-dimensions.bottom;
        structFonte.lfWidth = 400;
        structFonte.lfUnderline = souligne;
        
        //Création de la fonte à partir de structFonte dans fonte
        fonte = CreateFontIndirect(&structFonte);
        
        //Récupération de la surface de dessin dans dc
        dc = BeginPaint(fenetreCiblee, &ps);
                
        //Permet au texte d'avoir un fond transparent
        SetBkMode(dc, TRANSPARENT);
        //Applique la fonte au dc de texte
        SelectObject(dc, fonte);
        //Applique la couleur de texte au dc
        SetTextColor(dc, couleur);

        //Affichage du texte 
        TextOut(dc, dimensions.left, dimensions.top, chaine, strlen(chaine));

        //Libération de la surface et du dc
        EndPaint(fenetreCiblee, &ps);

        return;
}

Vous avez sûrement noté la présence d'une structure RECT que vous ne connaissez pas.

Grâce à de petits calculs, je peux alors déterminer la hauteur du texte à afficher. Vous pouvez en inventer un pour la largeur, je ne vous cache pas que j'en ai la flegme :-° .
Rédacteur : Mg++


Les contextes de périphériques Les formes géométriques

Les formes géométriques

Le texte Les images

Les formes géométriques

Afin de créer des formes géométriques, vous avez besoin d'un crayon dans la vie réelle, non ? Eh bien en winAPI, c'est pareil.

Cours
Les crayons

Bon ! Maintenant que vous avez compris le truc (et que vous avez quelques notions de dessin depuis la maternelle :p ), que vous savez donc que le dessin se fait par outils facilement représentables visuellement, je vais vous parler des crayons. Comme vous le savez, un crayon peut être caractérisé par :

Ça tombe bien, ce sont exactement les paramètres de CreatePen (et dans l'ordre en plus, quelle chance :-° ). Celle-ci renvoie un handle sur un crayon, ou HPEN.

Les différentes formes

Maintenant que vous avez de quoi dessiner, je vais vous fournir des fonctions de dessin permettant de reproduire, avec le DC courant, une forme géométrique :

Codes d'exemples

Voici un ensemble de fonctions permettant de dessiner un rectangle, une ellipse ou une ligne, avec les options passées en paramètre :

Rédacteur : Mg++


Le texte Les images

Les images

Les formes géométriques TP : Un lecteur audio

Les images

Les images affichables en WinAPI sont généralement de type Bitmap. Pour les afficher, vous disposez de deux moyens : par ressources et en passant par un contrôle STATIC, ou par les contextes de périphériques. Voici donc les méthodes disponibles :

Affichage par contrôle STATIC

Cette méthode est la plus simple à mettre en place, et permet de récupérer des notifications à l'égard du contrôle STATIC. Cependant, c'est la plus gourmande en ressources, et n'est donc pas recommandée si vous n'avez aucune utilité des notifications.

Chargement du bitmap

Tout ce que vous avez à faire, c'est intégrer le bitmap en ressources en utilisant cette syntaxe :

img1 BITMAP "Images/bitmap.bmp"
Création du STATIC bitmap

La création de ce type de static s'effectue simplement : contentez-vous de créer un static "normal", de mettre l'ID du bitmap entre guillemets en guise de nom de fenêtre (2ème argument), et d'ajouter le style SS_BITMAP aux autres flag. Les dimensions (6ème et 7ème paramètres) peuvent être mises à 0 pour conserver la taille originelle de l'image.

Affichage par contextes de périphériques

C'est la méthode à suivre si vous préférez ne pas utiliser beaucoup les ressources système. Partez pour commencer d'un code simple comme celui-ci :

void afficheBitmap(HWND fenetreCiblee, char *chemin)
{
        HBITMAP bitmap;
        HDC hDC;
        
        //Chargement du bitmap
        hDC=GetDC(fenetreCiblee);
        
        //Affichage du bitmap  

        DeleteObject(bitmap);
        ReleaseDC(fenetreCiblee,hDC);
}

Si vous avez votre bitmap en ressource, rien de plus simple pour le chargement : LoadBitmap, prenant comme premier paramètre l'instance actuelle, et comme deuxième, son ID entre guillemets, retourne un handle de bitmap directement manipulable. Dans le cas contraire, nous devons utiliser :

HANDLE LoadImage(      
    HINSTANCE hinst,
    LPCTSTR lpszName,
    UINT uType,
    int cxDesired,
    int cyDesired,
    UINT fuLoad
);

Dont les arguments sont :

Donc, dans notre cas ce bout de code ressemblerait alors à ceci :

bitmap = (HBITMAP)LoadImage(NULL,"image.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

Ensuite, en ce qui concerne l'affichage, vous disposez de deux méthodes possibles, mais la deuxième vous sera présentée plus tard dans une autre de ses possibilités. Voici donc :

DrawState

Elle a comme prototype :

BOOL DrawState(HDC hdc, HBRUSH hbr, DRAWSTATEPROC lpOutputFunc, LPARAM lData, WPARAM wData, int x, int y, int cx, int cy, UINT fuFlags);

Cette fonction permet d'afficher l'image avec certains effets visuels (masques, etc.). Comme ce n'est pas ce que nous voulons faire ici, je passerai sur les paramètres inutiles pour nous, il vous incombera de les mettre à NULL (avec si nécessaire, un cast).

Alors, dans notre cas :

DrawState(dc,NULL,NULL,(long)bitmap,NULL,10,10,0,0,DST_BITMAP);
Code complet

Voici donc le code complet. Si vous avez votre image en ressources, inversez les commentaires et le code approprié :

case WM_PAINT :
{
        HBITMAP bitmap;
        HDC dc;
        PAINTSTRUCT ps;

        bitmap=LoadImage(NULL,"image.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

        dc = BeginPaint(fenetrePrincipale, &ps);

        DrawState(dc,NULL,NULL,(long)bitmap,NULL,10,10,0,0,DST_BITMAP);

        EndPaint(fenetrePrincipale, &ps);

        DeleteObject(bitmap);

        return 0;
}

Vous pouvez en faire une fonction si vous vous sentez d'attaque ;) .

Rédacteur : Mg++

Je pense que vous avez maintenant assez de bases pour .... oui, un TP ! :D

Euh ... :waw:

Pas de panique, si vous avez bien compris, rien ne sera difficile. Il s'agira probablement d'un lecteur audio sous Fmod ;)


Les formes géométriques TP : Un lecteur audio

TP : Un lecteur audio

Les images But

Voici venu le temps du premier TP. :) Ne vous affolez pas, je vous réserve quelque chose d'assez simple, et toutes les étapes un tantinet plus coriaces vous seront expliquées en guise de pistes.

But

TP : Un lecteur audio Nécessaire

But

Votre mission, si vous l'acceptez, sera de créer un petit lecteur audio comprenant les fonctions suivantes :

C'est pour l'instant tout ce que je vous propose, cela pour ne pas vous compliquer la vie en guise de premier TP WinAPI. Des propositions d'améliorations vous seront données en fin de tuto, avec quelques éléments utiles pour leur conception.

Je vous propose l'organisation suivante, mais libre à vous de faire la vôtre. Ce sera votre lecteur après tout ^^ :

Image utilisateur

(N'ayez pas peur, je l'ai fait sous Paint :-° .)

Donc, avec cette organisation, le contrôle sur la musique se fera à partir du menu Music (qui possèdera donc les entrées-commandes Play / Pause, et Stop), et le contrôle du player se fera à partir du menu File, qui contiendra donc les entrées-commandes Browse, et Quit.

Par conséquent, l'intérieur de la fenêtre ne comportera que les informations générales, et un contrôle de type slider permettant de "seeker" la musique.

Rédacteur : Mg++


TP : Un lecteur audio Nécessaire

Nécessaire

But Pistes

Nécessaire

Afin de réaliser le TP, vous avez obligatoirement besoin d'un gestionnaire du son et de la musique. A cet effet, j'ai choisi FMOD, puisqu'il a déjà été étudié dans le tuto de M@teo21.

De plus, une bonne connaissance du tuto vous sera nécessaire.

Pourquoi nous avoir appris tant de choses si seuls le slider et les menus sont utilisés, ainsi que l'affichage du texte ?

Ce TP est une occasion de vous entraîner. Ce que je vous propose est le strict minimum à son fonctionnement : si vous voulez l'améliorer (et ce sera sûrement le cas, comme je l'espère ;) ), vous aurez besoin de toutes les connaissances acquises : listbox et dialogbox pour la playlist, boutons, textes et j'en passe...

Rédacteur : Mg++


But Pistes

Pistes

Nécessaire Correction

Pistes

Vous aurez sûrement besoin de quelques pistes afin de le créer... je pense notamment au moyen de parcourir le disque dur à la recherche des musiques ;) . Je vais donc vous les détailler ci-après, afin de vous aider le plus possible.

Voici donc les fameuses pistes (en vrac :-° ) .

Le timer

Afin de changer les informations à chaque seconde, (pour le temps, le slider etc...) vous devrez utiliser un timer. Ceux-ci sont un peu différents de ceux de la SDL : un timer, en WinAPI, envoie à la fenêtre parent de celui-ci une notification toutes les X millisecondes. Très pratiques, ils permettent d'exécuter un bloc d'instructions à chaque intervalle de temps. De plus, ils n'ont pas particulièrement besoin de callback, et sont généralement utilisés sans. Vous devrez donc, à la création de votre fenêtre, initialiser le timer qui, par défaut, n'est pas présent (logique), par un appel à la fonction SetTimer.

Une fois le timer lancé, la fenêtre parent de celui-ci recevra la notification WM_TIMER à chaque intervalle de temps.
Pour arrêter le timer, utilisez KillTimer prenant comme premier argument le handle de la fenêtre parent, et en second l'ID du timer.

L'affichage des textes

L'affichage des textes en utilisant le dessin peut compliquer les choses inutilement. Je vous propose donc d'utiliser une méthode très pratique, qui consiste à créer des contrôles static (avec les positions et dimensions nécessaires bien sûr) pour chaque zone d'information (nom de la musique, son état, et son temps), avec un flag adapté à la zone (pour le temps : SS_RIGHT ; pour le nom : SS_CENTER ; pour le status : SS_LEFT). Et enfin, d'exécuter quand vous voulez changer leur texte SetWindowText. Cette fonction, prenant comme premier paramètre le handle de la fenêtre (donc du static ici), et comme deuxième, le texte à afficher, changera les textes des static en les justifiant à la position que vous avez spécifiée.

La sélection de musique

En ce qui concerne la fonction Browse (ou parcourir), la manière la plus efficace reste la boîte de dialogue d'ouverture. Vous la connaissez, n'est-ce pas ? C'est ce petit explorateur, vous permettant de sélectionner ce que vous voulez ouvrir :

Image utilisateur

Pour la faire apparaître, nous utiliserons la fonction GetOpenFileName. Cette dernière, qui prend comme seul paramètre une variable de type OPENFILENAME, retourne TRUE si l'utilisateur a cliqué sur "Open", ou FALSE sur "Cancel". (En gros, vous ne traiterez que le TRUE.) La structure OPENFILENAME n'a pas le même rôle que PAINTSTRUCT : vous devrez la remplir. Pour cela, je vais vous décrire les champs qui nous sont utiles.

Une fois ceci fait, vous devrez passer l'adresse de la variable OPENFILENAME remplie à GetOpenFileName. L'appel de cette fonction suffit à afficher la boîte de dialogue, et à remplir le champ pointé par lpstrFile dès que le bouton "Ouvrir" est cliqué.

Le nom de la musique

Afin de trouver le nom de la musique, je vous propose une solution toute bête, en attendant la gestion des tags : utilisez strrchr sur le chemin de la musique avec le caractère \ (doublé dans le code). Elle retournera alors la fin du chemin, ou la musique elle- même.

Le module de musiques

En ce qui concerne le module de son, fsound, vous aurez besoin de quelques fonctions pour coder votre lecteur. Voici les nécessaires :

Fin des pistes :p

Normalement, avec ces pistes et votre bagage intellectuel acquis durant ce tuto, vous pourriez y arriver sans aide.

Vous devez avoir vu au préalable parmi les contrôles : le static et le seeker. En outre, vous devez connaître toutes les bases, mais je suis sûr que vous les avez déjà. ;)

Allez, pour vous faire une idée du programme final :

Image utilisateur

Et maintenant, à vous de jouer ! :pirate:

Bonne chance ! :)

Rédacteur : Mg++


Nécessaire Correction

Correction

Pistes Améliorations

Correction

J'espère que vous vous êtes bien débrouillés. ^^ Dans le cas contraire (ou pour voir comment un autre aurait fait, tout simplement), voici une archive contenant le projet complet (.cbp et codes sources) pour vous faire les dents :magicien: :

Télécharger l'archive du projet !
Si jamais mon site est indisponible ou si le lien est rompu d'une quelconque manière, pour raison d'Etat par exemple, voici une recopie du code source :ninja: :

Rédacteur : Mg++


Pistes Améliorations

Améliorations

Correction Les événements

Améliorations

Ce lecteur est un bon début, mais il n'est pas parfait (nooon sans blague ? :p ). Je vous propose ici quelques idées d'améliorations avec, si besoin est, quelques astuces pour les concrétiser.

Sélection multiple

Vous pourriez permettre au lecteur de récupérer plusieurs musiques au lieu d'une dans la boîte de dialogue d'ouverture. Comme vous avez pu le voir, elle ne le permet pas. Pour cela, ajoutez à la liste de flags OFN_MULTISELECT, qui permettra de faire une sélection multiple. Mais... je ne vous en ai pas parlé car c'est un peu plus dur que pour la sélection unique. ;) En effet, vous ne disposez que d'une chaîne de sortie (lpszFile). Pas de panique, Microsoft est là. Dans le cas d'une sélection multiple, la chaîne se transforme alors en une multistring, dont je vous ai parlé plus haut. Le chemin du dossier où se trouvent les musiques, ainsi que chaque nom de musique sélectionnée qu'il précède, sont alors séparés par un caractère NULL, et le dernier caractère est doublé. Il va donc falloir que vous inventiez une fonction (je ne vais pas la donner, sinon ce n'est pas drôle :lol: ) permettant de découper la chaîne en un tableau de string, que vous stockerez alors. Voici alors son format, en guise de coup de pouce ^^ :

"C:\\Dossier1\\Dossier2\\Musique1.mp3\0Musique2.wav\0Musique3.ogg\0\0"
Gestion de plusieurs musiques

Après l'avoir intégré, un système de progression entre les musiques serait intéressant : l'ajout de 'Next', 'Previous' serait alors adapté, et un choix entre lecture normale ou aléatoire pourrait être ajouté.

Ajout d'une playlist

Une playlist pourrait alors être ajoutée, pour représenter visuellement les musiques chargées. Un double-clic sur une entrée de celle-ci lançerait alors la musique.

Fonctions habituelles

Après, pourraient être insérées les fonctions normales, tel que réglage du volume, etc.

Possibilités autres

D'autres possibilités seront vues plus en détail lors de la progression du cours, comme un skin élaboré dans une fenêtre non rectangulaire en utilisant les régions, la possibilité de minimiser l'application, etc.

Déjà, si vous intégrez toutes ces idées, cela prouvera que vous vous débrouillez bien avec le winAPI. Quoiqu'il arrive, gardez une organisation propre, et les ajouts se feront sans problème. :ange:

Rédacteur : Mg++

Et voilà, ce premier TP est fini :) Je vous retrouve après dans la suite du cours, et j'espère que ce TP vous a fait du bien. ;)
Rédacteur : kidpaddle2


Correction Les événements

Les événements

Améliorations Evénements "clavier"

Vous avez jusqu'ici exclusivement travaillé avec des contrôles pour intéragir avec la fenêtre. Je vous propose ici de traiter manuellement les principaux événements du clavier et de la souris.

Evénements "clavier"

Les événements Evénements "souris"

Evénements "clavier"

Il y a trois événements vraiment utilisés :

Chacun de ces messages survient l'un après l'autre (dans l'ordre), à chaque fois qu'une touche est utilisée.

Voici un schéma résumant le tout :

Image utilisateur

Schéma des messages générés tout au long de l'utilisation d'une touche
Comme vous pouvez le voir, WM_KEYDOWN survient lors de l'enfoncement d'une touche, WM_CHAR si cette touche désigne un caractère, et WM_KEYUP lors du relâchement de la touche.

Tous ces messages prennent comme paramètre :

Le paramètre LPARAM n'est souvent pas utilisé. Par contre, le premier permet de déterminer la touche qui a généré l'événement, à travers une correspondance code/touche, dont le tableau est disponible à l'adresse suivante (je n'ai pas eu le courage de tout retranscrire dans un tableau zCode, mais si une âme charitable pouvait le faire, je lui en serait reconnaissant ^^ ).

Il n'y a pas grand-chose à dire d'autre, donc un code complet devrait suffire à vous mettre en tête leur utilisation.

Exemple

Voici donc un exemple de traitement des trois messages (non compilable), qui permet simplement de quitter le programme à l'appui de la touche "Echap", et d'ajouter à une string les éventuels caractères entrés.

//Soit wParam et lParam les WPARAM et LPARAM (normal ^^ )
//Soit buffer une instance de std::string

/* switch du MSG dans la callback de la fenêtre */
case WM_KEYDOWN:
//ou WM_KEYUP
    switch(wParam)
    {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;

        default:
            break;
    }
    return 0;

case WM_CHAR:
    buffer += (char)wParam;
    return 0;

C'est tout bête, n'est-ce pas ?
Rédacteur : Mg++


Les événements Evénements "souris"

Evénements "souris"

Evénements "clavier" Esthétique avancée

Evénements "souris"

Les boutons

La souris génère deux messages consécutifs à l'appui d'un de ses bouton, chacun décliné en trois messages, selon le bouton concerné. La nomenclature de tels message est alors WM_BOUTONMESSAGE où :

On retrouve ici les suffixes -DOWN et -UP, montrant que durant l'utilisation d'un bouton, WM_(L, R ou M)BUTTONDOWN est envoyé quand il est enfoncé, et WM_(L, R ou M)BUTTONUP quand il est relâché.

Ces messages ont tous les mêmes paramètres WPARAM et LPARAM : le paramètre WPARAM contient un masque de bouton, c'est-à-dire le code virtuel de touches/boutons étant enfoncés lorsque l'événement survient (équivalent à la traduction d'appuis simultanés), dont les valeurs peuvent être :

Par ailleurs, le paramètre LPARAM contient les coordonnées du curseur lors de l'émission du message : le LOWORD contient son abscisse, et le HIWORD son ordonnée. Ces coordonnées peuvent êtres récupérées à l'aide des macros GET_X_LPARAM(LPARAM) (pour l'abscisse) et GET_Y_LPARAM(LPARAM) (pour l'ordonnée), ou encore MAKEPOINTS(LPARAM) (pour récupérer une structure POINTS).

La molette

L'événement WM_MOUSEWHEEL, généré lors de l'utilisation de la molette, possède les mêmes paramètres, excepté le fait que le masque de bouton expliqué plus haut est contenu par le HIWORD du WPARAM.

Pourquoi ?

J'y viens :p : le LOWORD est en fait utilisé par l'angle parcouru par la molette, défini à partir de la constante WHEEL_DELTA (valant 120, et correspondant en général à un "cran" de la molette). Si ce paramètre est positif, c'est que la molette a été tournée vers le "haut", ou à l'opposé de l'utilisateur. A l'inverse, une valeur négative traduit un mouvement vers le "bas", ou vers l'utilisateur.

Le déplacement

Nous avons vu jusqu'ici les événements des boutons, et de la molette de la souris. Mais qu'en est-il de son mouvement ?

Quand la souris est déplacée, le message WM_MOUSEMOVE est envoyée à la fenêtre située sous le curseur. Celui-ci (chance ! :p ) possède exactement les mêmes paramètres que les messages correspondant à l'utilisation des boutons de la souris, à savoir les boutons pressés simultanément, et les coordonnées du curseur.

Divers

Vous pouvez récupérer la position de la souris à n'importe quel endroit de votre code, avec la fonction GetCursorPos(), prenant comme paramètre un pointeur de POINT. Vous l'aurez deviné, la fonction SetCursorPos() permettrait de définir la position du curseur, en prenant un POINT en paramètre.

Exemple

Voici un code récapitulatif tout simple, déduisant simplement la distance parcourue par le curseur (quand le bouton gauche de la souris est enfoncé), et par la molette (non compilable, comme d'habitude ;) ).

#include <math.h>

#define RADIUS (2*PI)

double linearDistance(POINT start, POINT end)
{
    return sqrt(pow((end.x - start.x), 2) + pow((end.y - start.y), 2))
}

int DEG_TO_RAD(int deg)
{
    return (deg*PI)/180
}

double angularDistance(int angle)
{
    return abs(angle)*RADIUS;
}

/* Soit cursorDistance et wheelDistance des static double, représentant respectivement
   la distance parcourue par le curseur et la molette (en pixels), ainsi que 
   BOOL lBtnDown l'état du bouton gauche de la souris. (TRUE pour
   enfoncé, FALSE pour relâché) (FALSE par défaut) */
   
//WindowProc, switch MSG (avec LPARAM lParam et WPARAM wParam)

case WM_LBUTTONDOWN:
    lBtnDown = TRUE;
    return 0;
    
case WM_LBUTTONUP:
    lBtnDown = FALSE;
    return 0;

case WM_MOUSEMOVE:
{
    if(lBtnDown)
    {
        static POINT old = {0,0};
        POINT current;
        current.x = GET_X_LPARAM(lParam);
        current.y = GET_Y_LPARAM(lParam);

        cursorDistance += linearDistance(old, current);

        old = current;
    }
    
    return 0;
}

case WM_MOUSEWHEEL:
    wheelDistance += angularDistance(DEG_TO_RAD(LOWORD(lParam)));
    return 0;

Rédacteur : Mg++

Ce fut une courte introduction aux événements principaux du clavier et de la souris. Un prochain chapitre sera dédié en partie aux événements système, comme le Drag And Drop.


Evénements "clavier" Esthétique avancée

Esthétique avancée

Evénements "souris" Les régions

Je suis certain que vous avez déjà éprouvé le désir de concevoir des applications ayant un design plus évolué à base de skins, formes fantaisistes etc., comme le proposent la plupart des logiciels sur la toile.

Eh bien, je vous propose ici de le réaliser ;) . Vous verrez donc ici comment tailler votre fenêtre comme bon vous semble. Pour cela, il est bien entendu necessaire que vous ayiez lu auparavant les autres chapitres de ce tutorial, et en particulier L'affichage (pour les images), et Les événements (surtout pour les tests relatifs à la souris, les boutons étant pour le moment incrustés au skin*).

Commençons tout d'abord par tailler fenêtre, non pas avec un burin, mais avec les régions.

Les régions

Esthétique avancée Gestion de la fenêtre

Les régions

En premier lieu, il est nécessaire de définir ce qu'est une région. En fait, vous pouvez vous la modéliser comme un moule. Je ne sais pas si l'image vous interpelle, mais c'est comme les formes métalliques que vous appliquez sur un gâteau pour qu'il en prenne justement la forme ^^ .

L'usage de ces régions se définit en plusieurs étapes :

Procédons dans l'ordre (tant qu'à faire :p ), et commençons dont par la création des régions proprement dite.

Création d'une région

A l'instar des contrôles, une région possède un handle permettant de la manier. Celui-ci, dont le type est HRGN, contient les informations relatives à la région visée. Cette structure est remplie à partir de fonctions de création de région, autorisant un large éventail de formes possibles, à travers différentes fonctions.

Voyez-plutôt :

Celles ci-dessus étant les principales, nous ne verrons que celles-ci (la liste complète se trouvant bien entendu sur msdn).

CreateRectRgn()

CreateRectRgn() permet de créer une région de forme rectangulaire, dont les coordonnées des coins supérieur gauche et inférieur droit sont données.

Le prototype de CreateRectRgn() est le suivant :

HRGN CreateRectRgn(
  int nLeftRect,   //Abscisse du coin supérieur gauche de la région
  int nTopRect,    //Ordonnée du coin supérieur gauche de la région 
  int nRightRect,  //Abscisse du coin inférieur droit de la région
  int nBottomRect  //Ordonnée du coin inférieur droit de la région
);
CreateRoundRectRgn()

CreateRoundRectRgn() permet de créer une région de la forme d'un rectangle ayant des coins arrondis, et dont les coordonnées des coins supérieur gauche et inférieur droit, ainsi que les dimensions de l'éllipse (permettant d'obtenir ces arrondis) sont données.

Le prototype de CreateRoundRectRgn() est le suivant :

HRGN CreateRoundRectRgn(
  int nLeftRect,      //Abscisse du coin supérieur gauche de la région
  int nTopRect,       //Ordonnée du coin supérieur gauche de la région 
  int nRightRect,     //Abscisse du coin inférieur droit de la région
  int nBottomRect     //Ordonnée du coin inférieur droit de la région
  int nWidthEllipse,  //Largeur de l'ellipse inscrite
  int nHeightEllipse  //Hauteur de l'ellipse inscrite
);
CreateEllipticRgn()

CreateEllipticRgn() permet de créer une région de forme élliptique, dont les coordonnées des coins supérieur gauche et inférieur droit du rectangle dans lequel elle est inscrite sont données.

Le prototype de CreateEllipticRgn() est le suivant :

HRGN CreateEllipticRgn(
  int nLeftRect,   //Abscisse du coin supérieur gauche du rectangle circonscrit
  int nTopRect,    //Ordonnée du coin supérieur gauche du rectangle circonscrit
  int nRightRect,  //Abscisse du coin inférieur droit du rectangle circonscrit
  int nBottomRect  //Ordonnée du coin inférieur droit du rectangle circonscrit
);
CreatePolygonRgn()

CreatePolygonRgn() n'est certes pas la fonction la plus pratique de celles exposées, mais reste la plus intéressante. En effet, en procurant à la fonction un tableau de coordonnées de points (sous la forme d'un tableau de POINT (CONST) POINT*), leur nombre ainsi que le mode de remplissage (voir ci-après), vous pouvez créer exactement la région que vous souhaitez.

Son prototype est le suivant :

HRGN CreatePolygonRgn(
  CONST POINT *lppt,  //Tableau de points
  int cPoints,        //Nombre de points dans le tableau
  int fnPolyFillMode  //Mode de remplissage du polygone
);

Une petite mise au point s'impose quant au mode de remplissage. Celui-ci peut prendre les valeurs ALTERNATE, et WINDING. ALTERNATE est souvent conseillé, et décrit le procédé suivant : selon l'algorithme scanline, la zone comprise entre les arêtes impaires et paires du polygone correspondra à la région créée. (pour plus amples informations, cherchez sur la Toile :-° )

Opérations diverses

Parfois, ces fonctions ne suffisent pas pour obtenir les formes dont vous avez envie (par exemple quand vous n'avez pas envie de récupérer des points pour créer votre région >_ ). Dans ces cas là, on les complète par d'autres fonctions, comme CombineRgn(), pour étendre encore plus les possibilités.

Celle-ci permet de combiner deux régions, selon le mode de combinaison dont les valeurs possibles sont :

Voici donc son prototype :

int CombineRgn(
  HRGN hrgnDest,      //Handle de la région de destination
  HRGN hrgnSrc1,      //Handle de la première des deux régions à combiner
  HRGN hrgnSrc2,      //Handle de la deuxième des deux régions à combiner
  int fnCombineMode   //Mode de combinaison des deux régions
);

La fonction peut alors retourner :

Le but de ce tuto, c'est un peu d'avoir une fenêtre de la forme de son skin, non ? Je vais mettre des années à créer ma région !

Pas de panique, ces fonctions ne sont que des bases (pouvant néanmoins vous servir ultérieurement) à une fonction parfaitement adaptée : BmpToRgn() :ange:

Cette fonction (que j'ai découverte sur cppfrance.com à mes débuts, et créée par anthraxx) permet de récupérer une région à partir d'une image bitmap (donc d'une image 32bits) et d'une couleur de transparence (par défaut, noir).

Voici le code complet de la fonction :magicien: :

HRGN BmpToRgn (HBITMAP hBmp, COLORREF cTransparentColor)
{
 #define ALLOC_UNIT 100

    int y, x;
    HRGN hRgn = NULL;

    if (!hBmp) return 0; // si bitmap invalide retourne

    BITMAP bm;
    GetObject(hBmp, sizeof(bm), &bm); // met les infos d'en tete du bitmap dans bm
    UINT siz=bm.bmWidth*bm.bmHeight*4; // enregistre la taille des donnes de l'image

    char *lpBmpBits=(char*)LocalAlloc(LMEM_FIXED,siz); // fait de la place pour les bits du bitmap
    GetBitmapBits(hBmp,siz,lpBmpBits); // obtient les bits de l'image dans l'espace qu'on a reservé
    bm.bmBits=lpBmpBits; // complete la strucutre bm avec les bits
    
    while (bm.bmWidthBytes % 4) bm.bmWidthBytes++; // bmWidthBytes doit être divisible par 4

    DWORD maxRects = ALLOC_UNIT;
    HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects));
    RGNDATA *pData = (RGNDATA *)GlobalLock(hData);
    pData->rdh.dwSize = sizeof(RGNDATAHEADER);
    pData->rdh.iType = RDH_RECTANGLES;
    pData->rdh.nCount = pData->rdh.nRgnSize = 0;
    SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);

    BYTE *p32 = (BYTE *)bm.bmBits;
    for (y = 0; y < bm.bmHeight; y++) // parcourt toutes les lignes de l'image, de haut en bas
    {
        for (x = 0; x < bm.bmWidth; x++) // parcourt tous les pixels de la ligne, de gauche à droite
        {
            // Recherche une suite continue de pixels non transparents
            int x0 = x;
            LONG *p = (LONG *)(p32 + 4*x);
            while (x < bm.bmWidth)
             {
                if ((unsigned)*p==cTransparentColor)
                  break; // ce pixel est transparent
                p++;
                x++;
            }
            if (x > x0)
            {
                // ajoute les pixels (de (x0, y) à (x, y+1)) à la region en tant que rectangle
                if (pData->rdh.nCount >= maxRects)
                {
                   GlobalUnlock(hData);
                   maxRects += ALLOC_UNIT;
                   hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) +
                         (sizeof(RECT) * maxRects), GMEM_MOVEABLE);
                   pData = (RGNDATA *)GlobalLock(hData);
                }
                RECT *pr = (RECT *)&pData->Buffer;
                SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1);
                if (x0 < pData->rdh.rcBound.left)
                   pData->rdh.rcBound.left = x0;
                if (y < pData->rdh.rcBound.top)
                   pData->rdh.rcBound.top = y;
                if (x > pData->rdh.rcBound.right)
                   pData->rdh.rcBound.right = x;
                if (y+1 > pData->rdh.rcBound.bottom)
                   pData->rdh.rcBound.bottom = y+1;
                pData->rdh.nCount++;

                // Il parait que sous Windows 98, ExtCreateRegion() ne marche pas s'il y a trop de rectangles
                // Pas de panique: on construit la region en deux fois
                if (pData->rdh.nCount == 2000)
                {
                    HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
                    if (hRgn)
                    {
                       CombineRgn(hRgn, hRgn, h, RGN_OR);
                       DeleteObject(h);
                    }
                    else
                        hRgn = h;
                    pData->rdh.nCount = 0;
                    SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
                }
            }
         }
        // On passe à la ligne suivante
        p32 += bm.bmWidthBytes;
    }
    // On cree la region
    // (et, s'il y avait plus de 2000 rectangles, on la combine avec celle créee precedemment)
    HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData);
    if (hRgn)
    {
        CombineRgn(hRgn, hRgn, h, RGN_OR);
        DeleteObject(h);
    }
    else hRgn = h;
    LocalFree((HLOCAL)lpBmpBits);
    return hRgn;
}

Je ne vous demande evidemment pas de la comprendre, mais juste de savoir l'utiliser (c'est pour votre bien, hein :p ) : le handle de bitmap doit bien entendu être obtenu par le chargement d'une image avec LoadImage() ou LoadBitmap, et la structure COLORREF (la couleur de transparence) par un appel à RGB(). (voir L'affichage)

Application de la région à une fenêtre

Alors là, il n'y a rien de plus simple, un appel de SetWindowRgn() avec comme paramètres le handle de la fenêtre à laquelle on veut appliquer la région, le handle de la région à appliquer, et enfin le "Redraw Flag" (booléen à fixer à TRUE si la fenêtre doit être redessinée, ou FALSE sinon).

Exemple

Voici un code illustratif de la manipulation des régions (non compilable) :

HRGN rectRegion, 
     roundRectRegion, 
     ellipticRegion, 
     polygonRegion, 
     combinedRegion, 
     bitmapRegion;
RECT rect = {10,10,300,100};
POINT pointArray[5];
//Soit bitmap ayant été chargé au préalable
//Soit hwnd ayant été créé au préalable

rectRegion = CreateRectRgnIndirect(&rect);

/* La plupart du temps, les deux dernières valeurs sont déterminées "au feeling" ;) */
roundRectRegion = CreateRoundRectRgn(10,10,300,100,10,10); 

ellipticRegion = CreateEllipticRgnIndirect(&rect);

pointArray[0].x = 0; pointArray[0].y = 1;
pointArray[1].x = 3; pointArray[1].y = 3;
pointArray[2].x = 6; pointArray[2].y = 1;
pointArray[3].x = 5; pointArray[3].y = -2;
pointArray[4].x = 2; pointArray[4].y = -2;

polygonRegion = CreatePolygonRgn(pointArray, 5, ALTERNATE);

//RGN_XOR, par exemple
CombineRgn(combinedRegion, rectRegion, ellipticRegion, RGN_XOR);

//Le blanc est la couleur transparente
bitmapRegion = BmpToRgn(bitmap, RGB(255,255,255));

//Et enfin :
SetWindowRgn(hwnd, /* region */);

Rédacteur : Mg++


Esthétique avancée Gestion de la fenêtre

Gestion de la fenêtre

Les régions Documentation

Gestion de la fenêtre

Maintenant que vous avez une fenêtre taillée par vos soins (dans la majorité des cas, sans barre de titre), peut-être voudriez-vous donner la possiblité à l'utilisateur de la bouger ? :-°

Pour cela, nous allons utiliser une notification très pratique (car ne servant fort heureusement pas qu'à ça), qui n'est autre que WM_NCHITTEST.

Cette notification est envoyée lorsqu'un événement, dont la source est la souris, survient sur la fenêtre d'un programme. Il s'agit d'un test antérieur aux messages WM_MOUSEMOVE, WM_LBUTTONDOWN etc. (tout comme WM_KEYDOWN est antérieur à WM_CHAR après test), pour déterminer dans quelle zone de la fenêtre se trouve la souris. Une fois traitée (si c'est le cas), nous devons renvoyer une constante indiquant la zone de la fenêtre correspondante (après test des coordonnées, forcément).

Ici, la constante à retourner est HTCAPTION (caption pour titre). Cependant, d'autres constantes peuvent être envoyées, représentant d'autres zones de la fenêtre :

Par contre, si la zone visée n'est tout simplement qu'une zone client, vous devez renvoyer HTCLIENT (logique).

Comment déterminer dans quelle zone le curseur se trouve ?

Je l'ai dit plus haut, il faut tester les coordonnées du curseur. Pour cela, pas besoin de GetCursorPos(), elles se trouvent dans le LPARAM du message (le WPARAM étant non-utilisé). Par conséquent, vous pourrez les récupérer à l'aide des macros GET_X_LPARAM() et GET_Y_LPARAM(), ou MAKEPOINTS(), expliquées antérieurement.

Exemple
/* Soit hwnd le handle de la fenêtre, et lParam le LPARAM de la callback */

//WindowProc avec switch du MSG
case WM_NCHITTEST:
{
    LRESULT ret = HTCLIENT;

    //Coordonnées de la fenêtre
    RECT wCoord;
    GetWindowRect(hwnd, &wCoord);

    //Coordonnées relatives à la zone client de la fenêtre
    POINT cCoord;
    cCoord.x = GET_X_LPARAM(lParam) - wCoord.left;
    cCoord.y = GET_Y_LPARAM(lParam) - wCoord.top;

    //tests des coordonnées
    if(/* zone dite "de déplacement" */)
        ret = HTCAPTION;

    //...

    return ret;
}

Avec cette méthode, votre fenêtre devrait pouvoir bouger fluidement (en tout cas, autant qu'avec des barres de titre "normales" ;) )
Rédacteur : Mg++

Vous êtes d'orénavant capables de tailler vos fenêtres comme bon vous semble, tout en personnalisant sa gestion (redimensionnements, déplacements etc.). Dans une prochaine mise à jour du chapitre, nous verrons quelque chose de nettement plus intéressant, à savoir les listbox dites "ownerdraw", les boutons hover (boutons changeant d'état en fonction du stade de la souris), en utilisant les masques etc., et sûrement à partir de l'élaboration d'une hiérarchie de classes, etc.

Le cours n'est pas terminé, d'autres leçons suivront ;)


Les régions Documentation

Documentation

Gestion de la fenêtre Fonctions

Vous trouverez ici une mini-documentation remplie au fur et à mesure de l'avancement du tuto.

Fonctions

Documentation Messages

Fonctions

Voici la liste de toutes les fonctions de l'API Windows que vous avez découvertes dans ce tutoriel.
Pour chaque fonction, j'explique le rôle, ses paramètres et sa valeur de retour.

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

Cette fonction est le point d'entrée de tout programme Windows, c'est elle qui est appelée en premier dans votre programme. C'est en quelque sorte l'équivalent du main en programme console.

Paramètres
HINSTANCE hInstance

Ce paramètre est l'instance de votre programme, c'est-à-dire une sorte d'identifiant unique qui représente votre programme. Ce paramètre est très important, puisqu'il est utilisé dans de nombreuses fonctions ou structures comme WNDCLASS ou CreateWindow.

HINSTANCE hPrevInstance

Ce paramètre est obsolète depuis la fin de Windows 16-bit, il est toujours mis à NULL.

LPSTR lpCmdLine

Ce paramètre est une chaîne de caractères qui contient une copie conforme de la ligne de commande. Ainsi, contrairement aux programmes console, la ligne de commande n'est pas découpée en arguments mais conservée telle quelle.

int nCmdShow

Ce paramètre a un lien avec ShowWindow, puisqu'il représente l'état dans lequel doit être affichée la fenêtre du programme : maximisée, minimisée,...

Valeur de retour

Si le WinMain se termine avant la boucle des messages (voir GetMessage), alors elle doit retourner 0.
Sinon, lorsqu'elle reçoit le message WM_QUIT, elle doit renvoyer la valeur contenue dans wParam (voir l'exemple dans la première partie).

RegisterClass
ATOM RegisterClass(      
    CONST WNDCLASS *lpWndClass
);
Description

Cette fonction enregistre une classe de fenêtre pour une utilisation avec CreateWindow ou CreateWindowEx.
Cette fonction a maintenant été remplacée par une autre, plus puissante : RegisterClassEx, mais elle peut très bien continuer à être utilisée.

Paramètres

Le seul paramètre de cette fonction est un pointeur vers une structure de type WNDCLASS. Pour plus de précision, voir la description de la structure WNDCLASS.

Valeur de retour

Si la fonction réussit, la valeur retournée est un identifiant unique qui représente cette classe.
Si elle échoue, elle renvoie 0.

CreateWindow
HWND CreateWindow(      
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);
Description

Cette fonction, comme son nom l'indique, sert à créer une fenêtre.

Paramètres
LPCTSTR lpClassName

Ce paramètre est le nom d'une classe précédemment enregistrée avec RegisterClass.

LPCTSTR lpWindowName

Ce paramètre est le nom de la fenêtre lorsque l'on crée une "vraie" fenêtre.
Lorsqu'il s'agit d'un contrôle, ce paramètre peut être utile (ou non). Voilà une liste non exhaustive :

DWORD dwStyle

Ce paramètre représente le style de la fenêtre, il résulte en fait de la combinaison de nombreuses styles.
Vous pourrez trouver une liste en annexe.
Il existe une série de styles qui s'appliquent à toutes les fenêtres, et d'autres qui ne s'appliquent qu'à certains contrôles.

int x,
    int y,
    int nWidth,
    int nHeight

Ces paramètres représentent la position et la taille initiales de la fenêtre.
Si x=CW_USEDEFAULT, alors le système choisit automatiquement la position de la fenêtre.

Si nWidth=CW_USEDEFAULT, alors le système choisit automatiquement la taille par défaut de la fenêtre.

HWND hWndParent

Ce paramètre est l'identifiant du parent de la fenêtre à créer.
Si ce paramètre vaut NULL, la fenêtre a pour parent le Bureau.

HMENU hMenu

Ce paramètre, lors de la création d'une "vraie" fenêtre, est l'identifiant d'un menu qui s'affichera en haut de la fenêtre (vous savez comme 'Fichier', 'Édition', 'Aide', ...).
Lors de la création d'un contrôle, celui-ci représente un entier utilisé pour identifier le contrôle sans son HWND.
Par exemple:

HMENU identifiantControle=(HMENU)1000;

Si ce paramètre vaut NULL, la fenêtre n'a pas de menu, ou pas d'identifiant selon le cas.

HINSTANCE hInstance

Ce paramètre est l'instance du programme qui crée la fenêtre ; ici en l'occurrence, le vôtre qui est passé en paramètre à WinMain.

LPVOID lpParam

Ce paramètre est un pointeur vers ce que vous voulez, il est utilisé pour passer un paramètre supplémentaire lors de la création de la fenêtre.
Voir la notification WM_CREATE en annexe.

Valeur de retour

Cette fonction renvoie un handle (poignée) sur votre fenêtre, c'est-à-dire une sorte d'identifiant unique, qui vous servira à communiquer avec votre fenêtre pour (par exemple) créer des contrôles dans votre fenêtre, ou changer sa taille.
Si la valeur de retour vaut NULL, alors la création a échoué.

ShowWindow
BOOL ShowWindow(      
    HWND hWnd,
    int nCmdShow
);
Description

Cette fonction sert à modifier la visibilité d'une fenêtre.

Paramètres
HWND hWnd

Ce paramètre est le handle sur la fenêtre dont vous voulez modifier la visibilité.
Vous l'obtenez lors de l'appel à CreateWindow par exemple.

int nCmdShow

Ce paramètre peut prendre plusieurs valeurs selon la visibilité que vous voulez appliquer.

Valeur de retour

Cette fonction renvoie TRUE si elle a réussi, et FALSE si elle a échoué.

UpdateWindow
BOOL UpdateWindow(
  HWND hWnd
);
Description

Cette fonction met à jour la zone client de la fenêtre, en la forçant à être redessinée en envoyant un message WM_PAINTdirectement à la fenêtre, au lieu de placer le message dans la file des messages.

Paramètres
HWND hWnd

handle de la fenêtre dont la zone client est à redessiner.

Valeur de retour

La fonction renvoie TRUE en cas de succès, et FALSE en cas d'échec.

GetMessage
BOOL GetMessage(      
    LPMSG lpMsg,
    HWND hWnd,
    UINT wMsgFilterMin,
    UINT wMsgFilterMax
);
Description

Cette fonction récupère un message dans la file du thread appelant.

Paramètres
LPMSG lpMsg

Ce paramètre est un pointeur vers une structure de type MSG (voir annexe) qui contiendra, après l'appel à la fonction, le message récupéré.

HWND hWnd

Ce paramètre représente l'identifiant de la fenêtre dont on veut récupérer le message.
S'il ce paramètre vaut NULL, alors la fonction récupère un message qui appartient à n'importe quelle fenêtre créée par le thread.

UINT wMsgFilterMin,UINT wMsgFilterMax

Ces paramètres représentent respectivement les identifiants des messages les plus "bas" et les plus "hauts" à faire passer.
Si ces paramètres valent 0, tous les messages sont passés.
Valeurs particulières :

Valeur de retour

Cette fonction retourne TRUE si elle réussit, et FALSE si elle échoue.

translatemessage
BOOL TranslateMessage(      
    const MSG *lpMsg
);
Description

Cette fonction est uniquement utile pour les messages de type clavier, elle traduit les événements de type Touche Virtuelle (voir note) en événement de types Caractère (voir note), et poste le message obtenu dans la file des messages.

Paramètres
const MSG *lpMsg

Ce paramètre est un pointeur vers le message récupéré par GetMessage.

Valeur de retour

Si le message a été traduit, cette fonction renvoie TRUE.
Si le message est WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN ou WM_SYSKEYUP, cette fonction renvoie TRUE.
Sinon, elle renvoie FALSE.

DispatchMessage
LRESULT DispatchMessage(
    const MSG *lpmsg
);
Description

Cette fonction envoie le message à la fonction de callback de la fenêtre concernée.
En quelque sorte, c'est elle qui appelle votre fonction qui gère les messages.

Paramètres
const MSG *lpMsg

Ce paramètre est un pointeur vers le message récupéré par GetMessage.

Valeur de retour

Cette fonction renvoie la valeur retournée par votre fonction de callback.
Par exemple, si dans votre de fonction de gestion des messages vous faites : "return 1000;",
alors, cette fonction retournera 1000.

WindowProc
LRESULT CALLBACK WindowProc
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
);
Description

Ceci n'est pas vraiment une fonction de l'API Windows à proprement parler : c'est la fonction de gestion des messages envoyée à votre (vos) fenêtre(s).

Paramètres
HWND hwnd

Ceci est le handle de la fenêtre concernée par le message.

UINT uMsg

Ceci est l'identifiant du message.

WPARAM wParam,
    LPARAM lParam

Ces paramètres sont entièrement dépendant du message envoyé.
Voir l'annexe sur les messages.

Valeur de retour

Ceci est la valeur retournée par vous-mêmes, ou la fonction DefWindowProc.

DefWindowProc
LRESULT DefWindowProc(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);
Description

Cette fonction appelle la fonction de gestion des messages par défaut du système. Elle est utile lorsque vous ne gérez pas vous-mêmes le message.
En pratique, à chaque fois que vous ne gérez pas un message, vous appelez cette fonction.

Paramètres

Vous devez passer les paramètres de votre fonction de gestion des messages (voir WindowProc) tels quels..

Valeur de retour

Ceci est la valeur retournée par la fonction de gestion des messages. Vous devez retourner cette valeur :

return DefWindowProc(hwnd,uMsg,wParam,lParam);
SendMessage
LRESULT SendMessage(      
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
)
Description

Cette fonction permet d'envoyer un message à une fenêtre ou à un contrôle. C'est-à-dire qu'elle va appeler la fonction de gestion des messages de la fenêtre avec les paramètres que vous spécifiez.
Par exemple, si vous faites :

SendMessage(hwnd,WM_QUIT,0,0);

Ceci va appeler une fonction de type WindowProc, associée à hwnd, avec exactement les mêmes paramètres que vous avez spécifiés.

Paramètres

Ce sont les même que pour WindowProc sauf que hWnd est l'identifiant de la fenêtre à laquelle vous envoyez le message.

Valeur de retour

Cette fonction renvoie la valeur retournée par la fonction de gestion des messages.

MessageBox
int MessageBox(      
    HWND hWnd,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);
Description

Cette fonction extrêmement pratique permet d'afficher une petite fenêtre avec du texte dedans pour informer l'utilisateur, ou lui demander quelque chose.

Paramètres
HWND hWnd

Ce paramètre est le handle de la fenêtre qui sera le parent du message affiché.

LPCTSTR lpText

Ce paramètre est la chaîne de caractères à afficher dans le message.

LPCTSTR lpCaption

Ce paramètre est la chaîne de caractères à afficher comme titre de la fenêtre.

UINT uType

Ce paramètre est le type de message à afficher.
Il peut être la combinaison de plusieurs paramètres grâce au OU binaire(|).
Le premier groupe de paramètres spécifie les boutons à afficher :

Le deuxième groupe spécifie l'icône à afficher :

Le troisième groupe spécifie le bouton par défaut.

Le quatrième groupe spécifie le type de fenêtre :

Le dernier groupe spécifie d'autres options :

Valeur de retour

Si la fonction échoue, elle renvoie 0.
Si elle réussit, elle renvoie:

CreateMenu
HMENU CreateMenu(VOID);
Description

Cette fonction permet de créer un menu. Ce menu est initialement vide.

Paramètres
Valeur de retour

Le handle du menu en cas de succès, et NULL en cas d'échec.

AppendMenu
BOOL AppendMenu(      
    HMENU hMenu,
    UINT uFlags,
    UINT_PTR uIDNewItem,
    LPCTSTR lpNewItem
);
Description

Cette fonction permet d'ajouter un élément à un menu existant.

Paramètres
HMENU hMenu

Le handle du menu auquel on va ajouter l'élément.

UINT uFlags

Ce paramètre peut être la combinaison d'un ou plusieurs de ces paramètres :

Les groupes suivants ne peuvent être utilisés ensemble :

UINT_PTR uIDNewItem

Spécifie soit l'identifiant du menu, ou si uFlags est mis à MF_POPUP, le handle du sous-menu.

LPCTSTR lpNewItem

Ce paramètre dépend de la valeur de lpNewItem :

Valeur de retour

La fonction renvoie TRUE en cas de succès, et FALSE en cas d'échec.

SetMenu
BOOL SetMenu(      
    HWND hWnd,
    HMENU hMenu
);
Description

Cette fonction permet de définir le menu associé à une fenêtre.

Paramètres
HWND hWnd

handle de la fenêtre à laquelle on assigne un nouveau menu.

HMENU hMenu

handle du nouveau menu, ou NULL pour que la fenêtre n'ait plus de menu associé.

Valeur de retour

Elle renvoie TRUE en cas de succès, et FALSE en cas d'erreur.

EnableMenuItem
BOOL EnableMenuItem(      
    HMENU hMenu,
    UINT uIDEnableItem,
    UINT uEnable
);
Description

Cette fonction permet d'activer, désactiver et griser un élément d'un menu.

Paramètres
HMENU hMenu

handle du menu auquel appartient l'élément à modifier.

UINT uIDEnableItem

Ce paramètre est l'indice ou l'identifiant de l'élément du menu à modifier selon le paramètre uEnable.

UINT uEnable

Ce paramètre peut prendre une ou plusieurs de ces valeurs :

Valeur de retour

La fonction retourne l'état précédent de l'élément modifié (MF_DISABLED, MF_ENABLED ou MF_GRAYED), ou -1 en cas d'échec.

CheckMenuItem
DWORD CheckMenuItem(
    HMENU hmenu,
    UINT uIDCheckItem,
    UINT uCheck
);
Description

Cette fonction permet de définir l'état de la boîte à cocher de l'élément d'un menu.

Paramètres
HMENU hmenu

handle de l'élément auquel appartient le menu.

UINT uIDCheckItem

Ce paramètre est l'indice ou l'identifiant de l'élément du menu à modifier selon le paramètre uEnable.

UINT uEnable

Ce paramètre peut prendre une ou plusieurs de ces valeurs :

Valeur de retour

La fonction retourne l'état précédent de l'élément modifié (MF_CHECKED ou MF_UNCHECKED), ou -1 en cas d'échec.

LoadMenu
HMENU LoadMenu(      
    HINSTANCE hInstance,
    LPCTSTR lpMenuName
);
Description

Cette fonction permet de charger un menu contenu dans une ressource de l'exécutable.

Paramètres
HINSTANCE hInstance

Instance du programme qui contient la ressource à charger.

LPCTSTR lpMenuName

Nom de la ressource à charger.
Peut être une chaîne de caractères, ou un identifiant formé avec la macro MAKEINTRESOURCE.

Valeur de retour

Elle renvoie le handle du menu en cas de succès, et NULL en cas d'échec.

CreateDialog
HWND CreateDialog(      
    HINSTANCE hInstance,
    LPCTSTR lpTemplate,
    HWND hWndParent,
    DLGPROC lpDialogFunc
);
Description

Cette fonction permet de créer une boîte de dialogue stockée en ressource dans l'exécutable.

Paramètres
HINSTANCE hInstance

Instance du programme auquel appartient la ressource.

LPCTSTR lpTemplate

Nom de la ressource de type boîte de dialogue.
Peut être un pointeur sur une chaîne de caractères, ou une valeur formée par la macro MAKEINTRESOURCE.

HWND hWndParent

handle de la fenêtre parent de la boîte de dialogue.

DLGPROC lpDialogFunc

Pointeur vers la fonction de gestion des messages de la boîte de dialogue.

Valeur de retour

Cette fonction renvoie le handle de la boite de dialogue, ou NULL en cas d'échec.

DialogBox
INT_PTR DialogBox(      
    HINSTANCE hInstance,
    LPCTSTR lpTemplate,
    HWND hWndParent,
    DLGPROC lpDialogFunc
);

Cette fonction (qui en fait est une macro) fait la même chose que CreateDialogBox, sauf qu'elle ne rend le contrôle que lors de la destruction de la boîte de dialogue avec la fonction EndDialog.

Paramètres

Ceux sont les mêmes que pour la fonction CreateDialogBox.

Valeur de retour

Cette fonction renvoie la valeur passée au paramètre nResult de la fonction EndDialog.

EndDialog
BOOL EndDialog(      
    HWND hDlg,
    INT_PTR nResult
);
Description

Cette fonction détruit la boîte de dialogue, et rend le contrôle au thread lorsque celle-ci a été créée avec la fonction DialogBox qui est bloquante.

Paramètres
HWND hDlg

handle de la boîte de dialogue à détruire.

INT_PTR nResult

Spécifie une valeur à retourner à l'application lors de la destruction.


Documentation Messages

Messages

Fonctions Structures

Messages

Voici la liste de tous les messages de l'API Windows que vous avez découverts dans ce tutoriel, et qui sont envoyés à la fonction de gestion des messages .
Pour chaque message, j'expliquerai sa signification ainsi que celle des paramètres wParam et lParam et de la valeur retournée.

WM_CREATE
Description

Ce message est envoyé lors de la création d'une fenêtre avec CreateWindow et CreateWindowEx.

wParam

Ce paramètre est inutilisé.

lParam

Pointeur vers une structure de type CREATESTRUCT.
Voir Annexe sur les structures.

Valeur de retour

Retournez 0 pour continuer la création de la fenêtre, ou -1 pour annuler la création.

WM_DESTROY
Description

Ce message est envoyé lorsqu'une fenêtre va être détruite.

wParam

Ce paramètre est inutilisé.

lParam

Ce paramètre est inutilisé.

Valeur de retour

Retournez 0.

WM_COMMAND
Description

Ce message est utilisé lorsque l'utilisateur interagit avec un contrôle.

wParam

Le mot haut (récupérable avec HIWORD) est le code de notification du contrôle. Celui-ci est spécifique à chaque classe de contrôle. Il vaut 1 si le message vient d'un raccourci clavier, et 0 si le message vient d'un menu.
Le mot bas (récupérable avec LOWORD) est l'identifiant de la fenêtre spécifié lors de la création avec CreateWindow et le paramètre hMenu.

lParam

handle du contrôle (fils de la fenêtre qui reçoit le message) qui a envoyé le message.
Il vaut NULL si ce n'est pas un contrôle.

Valeur de retour

Retournez 0.


Fonctions Structures

Structures

Messages Glossaire

Structures

Voici la liste de toutes les structures de l'API Windows que vous avez découvertes dans ce tutoriel.
Pour chaque structure, j'expliquerai le rôle de chacun des membres.

WNDCLASS
typedef struct {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
Description

Cette structure est utilisée lors de la création d'une classe de fenêtre.
Elle est passée en paramètre à RegisterClass.

Membres
UINT style

Spécifie les styles de la classe, ce peut être une combinaison avec le OU binaire (|) de n'importe quel paramètre :

WNDPROC lpfnWndProc

Ce membre est un pointeur vers la fonction de gestion des messages arrivant à toutes les fenêtres de cette classe.

int cbClsExtra

Spécifie le nombre d'octets supplémentaires à allouer dans la structure de la classe.
Cela permet de stocker des données supplémentaires associées à cette classe.

int cbWndExtra

Spécifie le nombre d'octets supplémentaires à allouer dans la structure d'une fenêtre.
Cela permet de stocker des données supplémentaires associées à chaque fenêtre.

HINSTANCE hInstance

Instance du programme qui crée la classe.
Cette variable est passée en paramètre au WinMain.

HICON hIcon

handle de l'icône à afficher dans la barre de titre des fenêtres de cette classe.

HCURSOR hCursor

handle du curseur à afficher quand le pointeur de la souris est au-dessus de la fenêtre.

HBRUSH hbrBackground

Brosse à utiliser pour dessiner le fond de la fenêtre (c'est en rapport avec le GDI).
Il y en a quelques-unes prédéfinies :

Si ce paramètre est NULL, le programme doit dessiner lui-même le fond de la fenêtre lors des notification WM_PAINT ou WM_ERASEBKGND.

LPCTSTR lpszMenuName

Nom de la ressource à utiliser comme menu pour les fenêtres de la classe (voir note).

LPCTSTR lpszClassName

Nom de la classe. Il doit être unique dans le programme pour que la classe soit enregistrée avec succès.

MSG
typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;
Description

Cette structure représente un message dans la file des messages envoyés à une fenêtre.

Membres
HWND hwnd

Ceci est le handle de la fenêtre qui doit recevoir le message.

UINT message

Identifiant du message (c'est le même que celui passé à la fonction de gestion des messages).

WPARAM wParam
LPARAM lParam

Ce sont des paramètres supplémentaires passés à la fonction de gestion des messages.

DWORD time

L'heure à laquelle le message a été posté.

POINT pt

Position du curseur lorsque le message a été posté.

CREATESTRUCT
typedef struct tagCREATESTRUCT {
    LPVOID lpCreateParams;
    HINSTANCE hInstance;
    HMENU hMenu;
    HWND hwndParent;
    int cy;
    int cx;
    int y;
    int x;
    LONG style;
    LPCTSTR lpszName;
    LPCTSTR lpszClass;
    DWORD dwExStyle;
} CREATESTRUCT, *LPCREATESTRUCT;
Description

Cette structure est passée en paramètre avec le message WM_CREATE.
Elle permet de connaître les paramètres exacts avec lesquels la fenêtre a été créée.

Membres

Les membres sont exactement les mêmes que pour CreateWindow.
Seules précisions :

int x int y int cx int cy

Si, lors de la création, vous avez spécifié CW_USEDEFAULT ou 0, la valeur vaudra alors la taille réelle de la fenêtre.

RECT
typedef struct _RECT { 
  LONG left; 
  LONG top; 
  LONG right; 
  LONG bottom; 
} RECT;
Description

Cette structure permet de stocker différents paramètres qui ont à voir avec des rectangles comme des dimensions, des marges ou des positions...

Membres
LONG left

Coordonnée en abscisse du coin supérieur gauche.

LONG top

Coordonnée en ordonnée du coin supérieur gauche.

LONG right

Coordonnée en abscisse du coin inférieur droit.

LONG bottom

Coordonnée en ordonnée du coin inférieur droit.

La documentation sera remplie au fur et à mesure de l'avancement du tuto, et de notre temps libre. ;)


Messages Glossaire

Glossaire

Structures Termes

Voici donc les termes que vous ne connaissez peut être pas. N'hésitez pas à regarder si vous avez des doutes.

Termes

Glossaire Manipulation du registre

Termes

Le glossaire n'est pas fini, il est rempli au fur et à mesure de l'avancement du tuto ;)


Glossaire Manipulation du registre

Manipulation du registre

Termes Quelques notions de BDR

Vous allez ici apprendre à manier les clés registre en utilisant des fonctions définies dans l'API win32.

A la fin de ce chapitre, vous saurez, en modifiant manuellement (avec le winAPI) ou semi-manuellement (en modifiant les clés à la main) :

Pour ce faire, lisez ce qui suit attentivement et ne modifiez surtout pas ce que vous ne connaissez pas.

Quelques notions de BDR

Manipulation du registre Changement d'icône de fichiers

Quelques notions de BDR

Avant de commencer, nous allons vous inculquer quelques notions de BDR.

Mais que diable est-ce, la BDR ?

La BDR est une base de données où sont stockées les informations nécessaires au bon fonctionnement de Windows. On y trouve :

Ainsi, chaque fois que vous modifiez ne serait-ce qu'une chose (si petite soit elle) dans votre ordinateur, la BDR s'en trouve sensiblement changée.

Les informations sont classées dans des dossiers dans l'arborescence, et se présentent sous la forme de clés registre. Celles-ci sont des informations, possédant un type, un ID et une valeur.

Pour ceux qui ne l'ont jamais vue, voici sous quelle forme se présente la BDR :

Image utilisateur

Comme vous pouvez le voir sur cette capture d'écran, les répertoires à la racine de la BDR sont :

Bien ! Je pense que vous savez maintenant le minimum pour débuter. Direction la prochaine partie.

Rédacteur : Mg++


Manipulation du registre Changement d'icône de fichiers

Changement d'icône de fichiers

Quelques notions de BDR Lancement au démarrage de windows

Changement d'icône de fichiers

Commençons progressivement. Nous allons voir comment, à partir du répertoire racine HKEY_CLASS_ROOT, changer l'icône associée à un type de fichier.

1ère méthode : méthode semi-manuelle (manipulation au travers de regedit)

Voilà comment est organisée la BDR de ce côté-ci : un dossier portant comme nom une extension contient une clé registre (default) portant comme valeur le nom descriptif du fichier. Alors, le dossier portant ce nom descriptif à l'intérieur de HKEY_CLASSES_ROOT (donc un niveau en arrière) contient un dossier intitulé DefaultIcon. Dans celui-ci, la clé (default) porte comme valeur le chemin de l'icône associée à ce type de fichiers.

Voici concernant le descriptif de son organisation. Concrètement, les étapes permettant de changer l'icône peuvent être représentées sur un schéma comme ceci :

Image utilisateur

Comme voir est mieux que croire, je vais vous indiquer pas à pas comment changer l'icône d'un fichier XML (par exemple).

En premier lieu, ouvrez la BDR et rendez-vous dans HKEY_CLASSES_ROOT.
Vous devriez voir ceci :

Image utilisateur

Ensuite, recherchez le dossier portant comme nom '.xml', et ouvrez-le.

Mémorisez ensuite la valeur du (default) contenue dedans. Il s'agit du nom descriptif du fichier concerné.

Vous aurez ceci :

Image utilisateur

Puis, rendez-vous au dossier portant ce nom descriptif, sachant qu'il est contenu au même niveau de HKEY_CLASSES_ROOT que les extensions, et ouvrez-le. Vous verrez à l'intérieur un dossier intitulé DefaultIcon, le dossier que nous cherchons.
Rendez-vous y et vous pourrez alors contempler le chemin de l'icône concernée, contenu dans la valeur du (default) de ce dossier.

En fait, vous voyez normalement ceci :

Image utilisateur

Pour modifier l'icône, faites un clic droit sur la clé (default) et sélectionnez Modify. Une boîte de dialogue s'ouvre, et vous pourrez alors sélectionner le chemin de l'icône voulue dans le champ Valeur avant de quitter.

Une illustration :

Image utilisateur

L'icône est ainsi changée. :)

2ème méthode : méthode manuelle (utilisation du winAPI) dans un code en C

Pour modifier le registre Windows dans votre code, vous aurez besoin d'inclure la bibliothèque nécessaire dans votre code, à savoir :

#include <windows.h>

Celle-ci, seule bibliothèque à intégrer au code pour se servir du winAPI, définit les fonctions permettant de manipuler le registre dont nous aurons besoin :

Ces fonctions agissent sur une variable de type "handle de clé" ou HKEY.

Maintenant que vous avez les informations nécessaires, allons créer notre code ;) .
Une fonction sera beaucoup plus pratique, pour la maintenance et la lisibilité du code. Je vous propose donc cette forme de fonction :

void changeIcon(const char *ext, const char *icon);

Voyons maintenant son contenu :

Les déclarations de variables

Etapes

Nous avons maintenant fait le tour, je pense... Voici donc le code complet de la fonction :

#include <windows.h>

void changeIcon(const char *ext, const char *icon)
{
   HKEY key;
   DWORD kSize;
   char mem[MAX_PATH] = "";

   RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_ALL_ACCESS, &key);
   
   RegQueryValueEx(key, NULL, 0, NULL, (BYTE*)mem, &kSize);
   
   strcat(mem, "\\DefaultIcon");
   RegOpenKeyEx(HKEY_CLASSES_ROOT, mem, 0, KEY_ALL_ACCESS, &key);
   
   RegSetValueEx(key, NULL, 0, REG_SZ, (BYTE*)icon, strlen(icon)+1);
   
   RegCloseKey(key);

   return;
}

Rédacteur : Mg++


Quelques notions de BDR Lancement au démarrage de windows

Lancement au démarrage de windows

Changement d'icône de fichiers Association de fichiers

Lancement au démarrage de windows

Maintenant que vous avez normalement compris comment manipuler, que cela soit à partir de regedit ou du winAPI, la BDR, inutile de détailler autant. (C'est surtout que je n'ai plus de place pour les images. :p )

Alors dans cette partie, je vais vous apprendre comment lancer votre application au démarrage de Windows. C'est pas bien long, et pas bien compliqué, mais si vous n'avez pas entièrement compris le principe de la BDR ou comment la manier, je vous renvoie à la sous-partie n°2.

1ère méthode : semi-manuelle (au travers de regedit)

Afin d'ajouter un programme à la liste de démarrage (au startup, dans le jargon), il faut juste ouvrir le dossier Software\Microsoft\Windows\CurrentVersion\Run, situé dans la branche d'arborescence HKEY_CURRENT_USER (pour l'utilisateur actuel seulement, c'est préférable), et ajouter une clé registre portant comme ID votre nom de programme, et comme valeur son chemin d'accès.

Pour cela, placez-vous dans le dossier Run dont la place a été citée, faites un clic droit sur la zone d'énumération des clés registre (partie droite), sélectionnez New -> Key et spécifiez comme dit plus haut :

2ème méthode : manuelle (en utilisant le winAPI)

Pareil ici, rien de compliqué ou de long. Pour faire ce travail, je vous propose la fonction suivante :

2ème méthode : manuelle (en utilisant le winAPI)

Pareil ici, rien de compliqué ou de long. Pour faire ce travail, je vous propose la fonction suivante :


Changement d'icône de fichiers Association de fichiers

Association de fichiers

Lancement au démarrage de windows Divers

Association de fichiers

Dans cette partie, je vais vous apprendre comment associer votre programme avec un type défini de fichiers.

1ère méthode : semi-manuelle (à l'aide de regedit)

Ici, retour à la branche d'arborescence HKEY_CLASSES_ROOT, puisque c'est là que la gestion des extensions est faite.

Le processus est assez semblable au changement d'icône : un dossier portant l'extension du fichier comporte un (default) pointant vers un autre dossier (contenu au même endroit que l'extension) étant le nom descriptif du type de fichier. Mais comme vous avez pu le voir sur les captures d'écran, il y a d'autres dossiers dans celui-ci, autres que DefaultIcon. Dans un de ceux-ci, Shell, se trouve Open, et dedans se trouve enfin command.

C'est sa valeur (default) qui va déterminer quel programme va lancer ce type de fichier à la commande Open du clic droit d'un ou plusieurs fichiers. En effet, il comporte le chemin d'accès au programme, suivi de soit "%1", si le programme n'ouvre qu'un fichier à la fois, soit "%L", s'ils sont plusieurs et donc listés.

Maintenant que vous avez vu l'organisation des associations, voici un exemple de nouvelle association de fichiers. Je vais vous expliquer comment associer Notepad++ aux fichiers .txt par exemple.

Voici la marche à suivre, comme vous l'avez deviné ;) :

Voilà, l'association est terminée :)

2ème méthode : manuelle (en utilisant le winAPI)

Passons maintenant au codage.

Cette fois-ci, je vous propose la fonction suivante :

void associate(const char *ext, const char *path);

Vous aurez besoin comme d'habitude de :

Pour transcrire cette manoeuvre en code, vous n'aurez ni plus ni moins besoin que des fonctions citées précédemment.

Voici les étapes requises :

Et voici la fonction complète (incluant la prise en compte des erreurs) :

void associate(const char *ext, const char *path)
{
   HKEY key;
   DWORD kSize;
   char mem[MAX_PATH] = "";

   if(RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS)
   {
        RegQueryValueEx(key, NULL, NULL, NULL, (BYTE*)mem, &kSize);

        strcat(mem, "\\Shell\\open\\command");

        if(RegOpenKeyEx(HKEY_CLASSES_ROOT, mem, 0, KEY_ALL_ACCESS, &key) == ERROR_SUCCESS)
        {
            sprintf(mem,"\"%s\" \"%1\"", path); 
            RegSetValueEx(key, NULL, 0, REG_SZ, (BYTE*)mem, strlen(mem)+1);
        }
   }
   RegCloseKey(key);

   return;
}

Et vous avez votre association. :)

Rédacteur : Mg++

Si vous avez des questions de précision ou des problèmes, n'hésitez pas à poster dans le forum, il est fait pour ça ;) .

Les fonctions n'ont pas été testées mais marchent sans doute possible. Néammoins dans le cas contraire, veuillez m'adresser un MP.


Lancement au démarrage de windows Divers

Divers

Association de fichiers Utiliser le style du Windows courant

Utiliser le style du Windows courant

Divers La notation hongroise

Utiliser le style du Windows courant

Vous allez ici apprendre à donner le style de votre Windows actuel à votre application.

Téléchargement préalable

Pour cela, vous aurez besoin d'un fichier manifeste, se présentant sous la forme d'un fichier xml.

Commencez donc par télécharger celui-ci.
(Clic droit + Enregistrer sous...)

Si le téléchargement échoue, sachez qu'il s'agit seulement d'un fichier codé en ASCII (donc avec NotePad, par exemple) et renommé en .xml, contenant le code suivant :

<assembly manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="CompanyName.ProductName.YourApplication" type="win32"/>
    <description>Description de votre application ici.</description>
    <dependency>
        <dependentAssembly>
          <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
</assembly>
Association

Pour associer le manifeste à votre application, vous disposez de deux méthodes :

Dynamiquement

En utilisant cette méthode, l'exécutable utilisera le manifeste situé dans le même dossier que celui-ci.
Commencez par renommer votre manifeste avec le nom de votre exécutable comme radical, et avec ".manifest" comme extension. Par exemple, si votre exécutable s'appelle "Programme.exe", alors renommez "manifest.xml" en "Programme.exe.manifest".

Vous n'avez plus qu'à placer le manifeste renommé dans le répertoire contenant votre exécutable.

En utilisant les ressources

Vous avez peut-être envie de vous débarrasser de quelques fichiers entourant l'exécutable, en les intégrant aux ressources ? Comme le manifeste, par exemple ? :p
Bien. Alors tout se passe à la compilation. Rajoutez à votre script ressource (*.rc) le code d'intégration suivant :

1 24 "manifest.xml"

Dans ce cas-ci, 1 est l'ID du manifeste, 24 son type, et "manifest.xml" son chemin.

Vous n'avez plus qu'à contempler votre application disposer du style du Windows courant. ;)

Rédacteur : Mg++


Divers La notation hongroise

La notation hongroise

Utiliser le style du Windows courant Quelques codes...

La notation hongroise

Les avis sont partagés à son sujet : à savoir si elle est utile ou non, productive ou non, mais toujours est-il qu'elle reste employée, et qu'on a tout intérêt à la comprendre.

Principe

Nous allons donc ici décrire les sigles qui reviennent le plus souvent.
En fait, elle consiste à préfixer en minuscules le nom de la variable (qui commence alors par une majuscule) par le sigle de son type.

Exemple:

HANDLE hWnd;
INT iX;
CHAR cCode;
Les préfixes

Les préfixes utilisés sont:

Les modificateurs

Il existe aussi quelque modificateurs:

Exemples

Ainsi, en mélangeant les deux, on peut obtenir ceci par exemple :

LPSTR g_szName;// variable globale qui est une chaîne de caractères terminée par zéro.

struct Truc
{
   PPOINT m_lpptPosition;// variable membre d'une structure, pointeur sur une structure de type point.
};

INT cbExtra;// nombre(le 'c') d'octets(le 'b') supplémentaires (à allouer par exemple)

Évidemment, il ne faut pas en abuser. Dans l'exemple précédent, cela alourdit inutilement l'expression ; on pourra omettre le "pt".

Rédacteur : Pamaury


Utiliser le style du Windows courant Quelques codes...

Quelques codes...

La notation hongroise Programmes

Quelques codes...

Depuis la parution de mon tutoriel, j'ai répondu à diverses questions, parfois en fournissant un code fonctionnel. J'ai donc décidé de répertorier ici-même ces différents codes afin que vous puissiez en profiter ;)

Gestion du presse-papier

Le presse-papier revient souvent sur les forums, en particulier en ce qui concerne les futurs traitements de texte en WinAPI. C'est pour cela que j'ai créé deux petites fonctions toutes simples permettant sa gestion, GetClipboardText() et SetClipboardText(), qui conviendront aux moins exigeants, tout en inspirant les plus exigeants :) :

#include <windows.h>

/* goto peut être utilisé si la redirection est dans le sens direct de la lecture du code (on évite le code spaguetti), et que son utilisation est claire... Pour moi, ça l'est. */
#define REDIRECT_IF(condition, label) if(condition) goto label
 
#define EXIT_LABEL local_exit
#define EXIT_IF(condition) REDIRECT_IF(condition, EXIT_LABEL)
 
/* Quand "on colle" : */
char *GetClipboardText()
{
    char *text = NULL;
 
    EXIT_IF(!IsClipboardFormatAvailable(CF_TEXT));
 
    EXIT_IF(!OpenClipboard(NULL));
 
    {
        char *textMem = (char*)GetClipboardData(CF_TEXT);
 
        /* free devra être utilisé une fois le texte utilisé */
        text = malloc((lstrlen(textMem)+1) * sizeof *text);
        lstrcpy(text, textMem);
    }
 
    CloseClipboard();
 
    EXIT_LABEL:
        return text;
}

/* Quand "on copie" : */
int SetClipboardText(char const *text)
{
    int ret = 0;
 
    HANDLE hGlobalMem = GlobalAlloc(GHND, lstrlen(text)+1);
    char *lpGlobalMem = NULL;
 
    EXIT_IF(hGlobalMem == NULL);
 
    lpGlobalMem = (char*)GlobalLock(hGlobalMem);
 
    EXIT_IF(lpGlobalMem == NULL);
 
    lstrcpy(lpGlobalMem, text);
 
    GlobalUnlock(hGlobalMem);
 
    EXIT_IF(!OpenClipboard(NULL));
 
    EmptyClipboard();
 
    if(SetClipboardData(CF_TEXT, hGlobalMem) != NULL)
        ret = 1;
 
    CloseClipboard();
 
    EXIT_LABEL:
        return ret; /* 1 : Succeed, 0 : Failure */
}
Gestion du drag and drop

La question du drag-and-drop est aussi quelque peu récalcitrante, puisque l'API Windows ne fournit pas de système direct pour s'en charger. J'en ai jadis proposé un, qui se résume à une unique fonction GetDroppedFilesPaths() :

Citation : Moi

Un petit exemple censé écrire les chemins des fichiers lâchés sur la fenêtre dans un fichier (situé dans le même répertoire que l'exécutable) nommé "Result.txt" (pas compilable tel quel).

/* Callback procedure : Soit hwnd (HWND) et wParam (WPARAM), passés en paramètre à la procédure. */ 
case WM_CREATE:
    /* Ne pas oublier d'accepter les drag-and-drop ;) */
    DragAcceptFiles(hwnd, TRUE);
    return 0;

case WM_DROPFILES:
{
    CHAR **paths = NULL;

    UINT count = GetDroppedFilesPaths((HDROP)wParam, paths);
    
    WritePathsToFile(paths, count, "Result.txt");


    /* On n'oublie pas de libérer la mémoire allouée */
    {
        UINT i = 0;
        while(i < count)
            free(paths[i++]);

        free(paths);
    }

    /* Ne pas oublier de finir la procédure de drag */
    DragFinish((HDROP)wParam);

    return 0;
}

Avec :

/* Il aurait mieux fallu utiliser le système de gestion des fichiers de l'API, mais bon... */
#include <stdio.h>
#include <windows.h>

VOID WritePathsToFile(
                          CONST CHAR ** CONST paths, 
                          CONST UINT count, 
                          LPCSTR resPath
                      )
{
    FILE *file = NULL;
    file = fopen(resPath, "a");

    if(file != NULL)
    {
        for(UINT i = 0 ; i < count ; i++)
            fputs(paths[i], file);
        
        fclose(file);
    }
}

static UINT GetDroppedFilesCount(HDROP hDrop)
{
    return DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
}

static UINT GetDroppedFileLen(HDROP hDrop, CONST UINT index)
{
    return DragQueryFile(hDrop, index, NULL, 0);
}

UINT GetDroppedFilesPaths(HDROP hDrop, CHAR **paths)
{
    const UINT count = GetDroppedFilesCount(hDrop);

    paths = malloc(count * sizeof *paths);
    if(paths == NULL)
        return 0;

    for(UINT i = 0 ; i < count ; i++)
    {
        const UINT droppedFilePathLen = GetDroppedFileLen(hDrop, i);

        paths[i] = malloc(droppedFileLen * sizeof *paths[i]);
        if(paths[i] == NULL)
            return i;

        DragQueryFile(hDrop, i, paths[i], droppedFileLen);
    }
    return count;
}
Gestion d'une icône dans la barre des tâches

Voici deux fonctions toutes simples permettant de minimiser et de restaurer une application :

/* Cache ton application et place une icône dans la barre de tâches,
dont le handle est hIcon, szTip le message affiché dans une petite bulle (due au
survol prolongé de la souris), et msg le message callback, permettant de traiter
les événements */
NOTIFYICONDATA *Minimize(HWND handler, HICON hIcon, LPCTSTR szTip, UINT msg)
{
    NOTIFYICONDATA *nim = malloc(sizeof *nim);

    nid->cbSize = sizeof(NOTIFYICONDATA);
    nid->hWnd = handler;
    nid->uID = 0;
    nid->hIcon = hIcon;
    nid->uCallbackMessage = msg;
    nid->uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

    strcpy(nid->szTip, szTip);

    Shell_NotifyIcon(NIM_ADD, nid);
    ShowWindow(handler, SW_HIDE);

    return nid;
}

void Restore(NOTIFYICONDATA *data)
{
    ShowWindow(data->hWnd, SW_SHOW);
    Shell_NotifyIcon(NIM_DELETE, data);
}

Et voilà un exemple d'utilisation :

/* Par exemple, la zone utilisateur étant située après WM_USER */
#define TI WM_USER+1

/* On minimise la fenêtre en récupérant une structure qui servira à la restaurer */
NOTIFYICONDATA *data = Minimize(hwnd, LoadIcon(hInstance, "Icon"), "Je suis une TrayIcon", TI);

/* ... */

/* On restaure la fenêtre */
Restore(data);
/* Et on n'oublie pas de libérer la mémoire... */
free(data);

/* ... */

/* Quant au switch du low-word du message : */
case TI:
    /* Traitement, par exemple avec TrackPopupMenuEx(), voir msdn */
Une idée à fouiller lors de l'encapsulation

Un jour on m'a demandé si c'était possible de spécifier une méthode comme callback, et voici ce que je lui ai répondu :

Citation : Moi

Le fait est que... C'est impossible ^^ La callback doit être statique. Edit: Question de signature... Ca paraît logique avec la structure WNDCLASSEX

Tu dois donc contourner le problème. Voici ce que je te propose : Utilise SetWindowLongPtr() après création de ta fenêtre pour passer l'objet courant (obtenu à travers la structure CREATESTRUCT passée par le LPARAM de la callback statique) en tant que pointeur récupérable grâce à GetWindowLong() (par exemple, à l'emplacement GWLP_USERDATA) dans la callback statique qui se chargera alors de rediriger les événements perçus vers les callbacks virtuelles correspondantes.

Fouille cette idée, si tu as du courage :

class Wnd
{
    HWND m_handle;
 
    //...
 
    static LRESULT StaticProc(HWND, UINT, WPARAM, LPARAM);
    static LRESULT DefProc(HWND, UINT, WPARAM, LPARAM);
 
    //...
 
    virtual LRESULT virtualProc(UINT, WPARAM, LPARAM); //Pure ou pas
 
    //...
};
 
class MyWnd : public Wnd
{
    //...
 
    virtual LRESULT virtualProc(UINT, WPARAM, LPARAM); //Override
};
 
LRESULT Wnd::StaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{    
    LRESULT ret = 0;    
    
    //Si la fenetre est en creation, le pointeur n'a pas ete encore initialise
    if(uMsg == WM_NCCREATE)
    {
        /* SetWindowLongPtr() de 'this' contenu dans lParam grâce au dernier paramètre
           de CreateWindowEx() -> Voir CREATESTRUCT structure */
        SetObjectFromCreateStruct(hWnd, lParam); //A toi de la créer...
    }
 
    Wnd *pObj = reinterpret_cast<Wnd*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));   
    if(pObj != NULL)
        ret = pObj->virtualProc(uMsg, wParam, lParam); 
    else       
        ret = DefProc( hWnd, uMsg, wParam, lParam);
 
    return ret;
}

Cela reste primitif, mais c'est une solution à envisager lorsqu'on est nous-même Orienté Objet ( ^^ ) et que l'on est réticent à utiliser MFC. Cela permet en outre de bien contrôler les rouages d'une API ainsi simplifiée.

Ou DLL qui vous permettent de découper le code dans d'autres fichiers joints à l'exécutable, soit afin de l'alléger (on lie tout de même statiquement pour s'affranchir des contraintes de chargement dynamiques, tout se passe comme s'il s'agissait d'une bibliothèque statique [.a, .lib] à la génération, mais les bibliothèques sont stockées à part dans des DLL), soit liées dynamiquement (chargement de la DLL et de ses fonctions à l'exécution, dans le code, aucune opération de linker). Les DLL permettent une maintenance facilitée, pour par exemple des mises à jour sur des points particuliers, simplement en remplaçant uniquement des DLL, sans toucher au reste. Elles sont particulièrement utiles pour les plugin.

DavySharp974, que je remercie au passage, nous propose une bibliothèque qui regroupe le code de liaison dynamique des DLL dans 3 fonctions. On ne détaille pas la création desdites DLL et d'une manière générale leur fonctionnement, mais cela peut vous faciliter la vie. Et qui sait, cela sûrement complété.

Citation : DavySharp974

Dans cette partie, vous pourrez apprendre comment charger dynamiquement une DLL sous Windows :) Pour ce faire, il vous faut télécharger libDLL

Dézippez les fichiers et mettez-les dans les bons emplacements (excepté le fichier 'test_dll.dll' qui doit être dans le même répertoire que votre projet de test).
Au cas où le lien soit cassé, ou pour simple information, voilà le code de la bibliothèque :

sqdll.h

/*
    Library for DLL loading.
    by Davy Sharp

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __SQ_EXT_DLL_H
#define __SQ_EXT_DLL_H

#if !defined _WIN32 && !defined __Win32__
    #error "libDLL was designed to be used uner Windows only !"
#endif

#ifdef __cplusplus
extern "C"
{
#endif

    typedef struct sq_dll_struct_t* sqdll_t;

    sqdll_t dll_load(const char* file_name);
    void dll_free(sqdll_t dll_ptr);

    void* dll_function(sqdll_t dll_ptr, const char* function_name);

#ifdef __cplusplus
}
#endif

#endif // __SQ_EXT_DLL_H

sqdll.c

#include <windows.h>

#include "sqdll.h"

struct sq_dll_struct_t
{
    HINSTANCE h_dll;
};

void* dll_function(sqdll_t dll_ptr, const char* function_name)
{
    if (dll_ptr != NULL)
    {
        return GetProcAddress(dll_ptr->h_dll, function_name);
    }
    else
    {
        return NULL;
    }
}

sqdll_t dll_load(const char* file_name)
{
    sqdll_t ptr = malloc(sizeof(*ptr));

    if (ptr != NULL)
    {
        ptr->h_dll = LoadLibrary(file_name);

        if (ptr->h_dll == NULL)
        {
            free(ptr);

            ptr = NULL;

            return ptr;
        }
    }

    return ptr;
}

void dll_free(sqdll_t dll_ptr)
{
    if (dll_ptr != NULL)
    {
        FreeLibrary(dll_ptr->h_dll);

        free(dll_ptr);
    }
}

Nos pouvons à présent commencer à coder notre programme de test.
Ecrivez un main vide, avec un 'return 0' à la fin.

int main(int argn, char** argv)
{
     return 0;
}

Dans le header 'libdll.h', libDLL définit trois fonctions, les trois seules dont vous aurez besoin pour utiliser une DLL.

la première fonction s'appelle sqdll_t* dll_load(const char* nomDuFichierDLL). Comme son nom l'indique (où pas :p ), elle sert à charger la DLL.

Nous avons donc:

int main(int argn, char** argv)
{
     sqdll_t maDLL = dll_load("test_dll.dll"); // Chargement de la dll 'test_dll.dll' qui devrait être dans le répertoire de votre projet

     return 0;
}

Ensuite, la fonction la plus importante, celle qui permet d'utiliser les fonctions de la DLL, s'appelle void* dll_function(sqdll_t*, const char* nomDeLaFonction)

Elle prend en premier paramètre le pointeur de Handle de DLL et en second paramètre le nom de la fonction à charger.
Son retour est de type void* car elle ne sait pas le type de retour et celui des arguments de la fonction qu'on charge.

En revanche, nous, on le connait :) la DLL 'test_dll.dll' contient une fonction int ajouter(int a, int b)

Nous déclarons donc le pointeur sur la fonction ajouter comme ceci: int (*ajouter)(int, int)

Je récapitule, finalement, le code écrit est

int main(int argn, char** argv)
{
     sqdll_t maDLL = NULL; // On initialise à NULL
     int (*ajouter)(int, int) = NULL; // On initialise à NULL

     maDLL =  dll_load("test_dll.dll"); // Chargement de la dll 'test_dll.dll' qui devrait être dans le répertoire de votre projet

     if (!maDLL) // Si le chargement a échoué...
     {
         printf("Erreur, impossible de trouver 'test_dll.dll' !"); // On affiche un message d'erreur...

         return -1; // Et on renvoie un code d'erreur
     }

     ajouter = dll_function(maDLL, "ajouter"); // On charge la fonction 'ajouter' depuis la DLL

     if (!ajouter) // Si le chargement a échoué...
     {
          printf("Erreur, impossible de trouver la fonction 'ajouter' dans 'test_dll.dll'); // On affiche un message...

          dll_free(maDLL); // Ne pas oublier de libérer la DLL, même si la fonction n'a pas été trouvée dedans

          return -2; // Et on renvoie un code d'erreur
     }

     // Si on arrive jusqu'ici, c'est que tout s'est bien passé

     printf("La somme de %d et %d est %d", 429, 763, ajouter(429, 763)); // On utilise la fonction 'ajouter'

     dll_free(maDLL); // on n'oublie pas de libérer la DLL en quittant

     return 0;
}

Il me semblait en avoir d'autres en réserve, mais je vous avoue que j'ai un peu la flegme de me plonger dans mon courrier dans une chasse aux codes :-°
D'autres surgiront peut-être ?

Rédacteur : Mg++

Les annexes ne sont pas finies, elles sont avancées au fur et à mesure de la progression du tuto ;)


La notation hongroise Programmes

Programmes

Quelques codes... Neoblast, de Kidpaddle2 (ex-Mg++)

Ici seront recensés les programmes créés en WinAPI, dont vous m'aurez fait part.

Neoblast, de Kidpaddle2 (ex-Mg++)

Programmes DdPop, de damien.09

Neoblast, de Kidpaddle2 (ex-Mg++)

Image utilisateur

Bonjour !

Je poste ici pour vous présenter un programme que j'ai jadis créé : NeoBlast.

Qu'est ce que c'est ?

NeoBlast (en français : "Souffle Nouveau") est un lecteur audio avancé pouvant lire la totalité des fichiers audios présents sur vos ordinateurs respectifs :

Comment ?

J'ai utilisé la bibliothèque fmod afin d'avoir le plus de fonctions possible permettant de manipuler les fichiers audio pour les implémenter dans mon programme.

[Malheureusement, même si celle-ci est portable, la bibliothèque graphique utilisée (GUI) étant le windows API ou API win32 n'est pas portable, et ne fonctionnera que sous Windows.]

Que fait-il de spécial ?

NeoBlast ne fait rien de vraiment "spécial", mais se contente d'avoir la plupart des fonctionnalités des lecteurs audio "pro" présents sur le net :

Parlons-en des skin... ils sont comment ?

Pour vous faire une idée, voici :

Le skin principal :

Image utilisateur

Le skin de la playlist :

Image utilisateur

Un grand merci à Musethebest pour la conception des skin... d'ailleurs un peu de pub :p .

[Le noir est utilisé pour la présentation : l'utilisation des régions m'a permis de pouvoir avoir des fenêtres ayant la forme de ces skins (avec la couleur noire comme transparente) sans barre de titre.]

Pour bouger les fenêtres modélisées par les skins, exécutez un drag avec le bouton droit de la souris. (voir "Help" pour quelques précisions).

Génial ! Où le télécharger ?

(Bin quoi, c'est possible non ? :-° )

Vous pourrez le télécharger (en étant équipé d'un extracteur de rar / zip) à partir des liens ci-dessous :
(N'oubliez pas de lire le fichier "Help" situé dans le menu Démarrer, et s'il vous plaît, ne sautez pas la licence.)

Liens :
Place aux tests

Voilà la présentation est terminée, place aux tests !

Vous pourrez consulter les codes sources (C) présents dans le répertoire d'install de NeoBlast ; attention, ils sont très indigestes, je l'ai créé à mes débuts, n'étant à cette époque pas rigoureux.

Si vous avez des avis, des commentaires, des critiques [constructives] ou encore des suggestions, n'hésitez pas à écrire dans mon livre d'or situé sur mon site e-Synapse, où une présentation dudit logiciel est aussi présente (au cas où).

Remerciements :

___________________________________|

Image utilisateur

|_________________________________


Programmes DdPop, de damien.09

DdPop, de damien.09

Neoblast, de Kidpaddle2 (ex-Mg++)

DdPop, de damien.09

Qu'est ce que c'est ?
En quoi cela consiste t'il : (principales caractéristiques)
Points n'ayant pu être développés :
Un aperçu
Image utilisateur
Lien et contact

Voici l'adresse ou vous pouvez télécharger DDpop : http://damien09.programmes.site.voila.fr. Il est conseillé de télécharger également le module DDpop-Serveur (ça va avec DDpop) comme ça vous pourrez être 8 à dialoguer ;) .
Les téléchargements (DDpop et DDpopServeur) incluent une Documentation expliquant le fonctionnement du programme correspondant, n'hésitez pas à la consulter.

Infos

Les caractéristiques indiquées ci-dessus ne sont peut être pas celles de la dernière version, si vous voulez les caractéristiques de la version actuelle référez vous à cette page.
DDpop et DDpop-Serveur sont des programmes codés en C++ avec l'API Windows par Dando.D.

N'hésitez pas à laisser des commentaires ici ou à me poser des questions :) et prevenez moi si vous trouvez des bugs.

La liste n'est pas complète, n'hésitez pas à rajouter les votres !

Sauf mention contraire, tous les projets sont sous licence GPL, et peuvent donc être étudiés (pas d'appropriation, et les modifications entraîneront obligatoirement une citation du créateur originel).

Pour en savoir plus :Image utilisateur


Neoblast, de Kidpaddle2 (ex-Mg++)