Base de la création d'une fenetre en api windows [c]

Pour toutes les personnes sur le forum qui demandent encore et toujours des exemples de code pour débuter en API et une documentation en français. Je n'ai pas la prétention de dire que c'est une documentation, mais pour un point de départ, pourquoi pas.
J'espère que ce tutoriel pourra vous être utile.

Introduction

Avant de commencer, il faut savoir que pour obtenir plus d'informations sur les différentes fonctions utilisées, il faut utiliser MSDN. Toutes les données concernant chaque fonctionnel sont référencées et expliquées.

Dans tout ce tutoriel, nous allons très régulièrement parlé de handle, nous allons donc dès maintenant définir ce que c'est. Il s'agit d'un pointeur sur une structure contenant un identifiant 32 bits unique pour chaque objet (fenêtre,bitmap, pinceau, police...) dont une table est maintenue par Windows

La base de tout: La création d'une fenêtre

Nous seront amenés dans cette partie à créer 2 fonctions:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCEhPrevInstance, LPSTR lpCmdLine,  int nCmdShow);

Cette fonction est le point d'entrée dans le programme (comme la fonction main dansles programmes consoles)

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

Tandis que celle-ci sera la fonction de traitement des messages

Méthode hard (sans ressource)

Pour cela, 4 variables s'imposent:

  • un handle sur la fenêtre (une sorte d'adresse sur la fenêtre qui sert à la retrouver parmi les autres) de type: HWND
  • une classe permettant de connaître les informations relatives à la fenêtre comme les actions demandant un redessinement de la fenêtre, le nom de la fonction de traitement des messages, le titre qui s'affichera dans le gestionnaire des taches... de type: WNDCLASSEX
  • une variable contenant les données du message devant être transmises à la fenêtre de type: MSG
  • une variable (appelées instance) qui permet d'identifier par groupe des fenêtres. Ainsi toutes les fenêtres d'un même projet auront la possibilité d'avoir la même instance et seront plus facilement accessible de type: HINSTANCE

La création de la fenêtre se fait au travers de la fonction CreateWindow ou de la fonction CreateWindowEx.

Une fois la fenêtre créée, rien ne se passe!!! Pourquoi?
Contrairement à la programmation en mode console, cette programmation est appelée événementielle. On doit donc envoyer des événements (sous forme de message) à notre fenêtre.

Ci-dessous,la boucle type d'envoie des messages à la fenêtre (nous verrons plus tard comment l'améliorer, mais celle-ci fonctionne dans la majorité des cas):

while(GetMessage(&msg, NULL, 0, 0))       //msg est défini comme suit: MSG msg;
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Cette boucle transmet à la fenêtre les messages, ces messages seront traités ensuite dans la fonction de traitement des messages dont le nom est spécifié dans la structure WNDCLASSEX.
Dans la fonction de traitement des messages, il faudra donc pouvoir déterminer le type du message, et ses paramètres.
La fonction de traitement des messages s'articule en général de la façon suivante:

switch(message)
{
 case WM_CREATE:
     //Votre code
     return TRUE;
case WM_COMMAND:     //Message si action sur un élément (bouton, combo...)
    //Votre code
    return TRUE;
case WM_CLOSE:
    DestroyWindow(hwnd);
    return TRUE;
case WM_DESTROY:
    PostQuitMessage(0);
    return TRUE;
default:
    return DefWindowProc(hwnd, message, wParam, lParam);
}
return FALSE;

Pour afficher une fenêtre, il faut donc traiter le message WM_CREATE qui est envoyé à la fenêtre lors de sa création.
Ici deux fonctions sont importantes:

  • ShowWindow(HWND, BOOL) qui permet de rendre visible (SW_SHOW) ou invisible une fenêtre (SW_HIDE).
  • SetForegroundWindow(HWND) qui place au premier plan la fenêtre.

D'autre fonctions peuvent également être utilisée pour placer la fenêtre.

Voila, après ça, vous venez de créer votre première fenêtre, vide certes, mais votre première fenêtre quand même.

Méthode avec ressource

Pour cela, créer dans l'éditeur de ressource de votre IDE une nouvelle fenêtre. Dans notre exemple nous dirons que l'identifiant de cette fenêtre est IDD_DIALOG1. Cette identifiant est un entier associé à chaque élément de la ressource pour pouvoir facilement naviguer dans la ressource.
Avec cette méthode, la création de la fenêtre se résume en une seule ligne:

DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG1), hwnd_parent, WndProc)

On trouve dans cette fonction 4 paramètres qu'il convient de bien identifier.

  • hInst est l'instance de votre application (il est conseillé d'utiliser ce paramètre en variable globale car il est utilisé par toutes les fenêtres de votre programme.
  • MAKEINTRESOURCE(IDD_DIALOG1) spécifie la ressource à charger et à utiliser
  • hwnd_parent est le handle de la fenêtre demandant la création de cette nouvelle fenêtre. Si il s'agit de la première fenêtre que vous créez ou d'une fenêtre qui n'a pas de fenêtre parente, mettez ce paramètre à NULL.
  • WndProc est le nom de votre fonction de traitement des messages pour votre fenêtre.

Il est à noter qu'il est également possible d'utiliser d'autres fonctions pour charger des ressources comme la fonction CreateDialog, mais après ces fonctions il faut réutiliser les boucles de message vues dans la partie précédente. Cette fonction est cependant nécessaire si l'on veut pouvoir modifier la boucle de traitement des messages.

Conclusion

Nous venons de voir 2 méthodes différentes pour la création d'une fenêtre. Mais laquelle de ces 2 méthodes choisir? Vous vous doutez bien que chacune de ces 2 méthodes a ses avantages et ses inconvénients.

La méthode hard permet de créer une fenêtre "modulable". Par exemple dans notre application nous créons une fenêtre avec des formes particulières, mais on pourrait aussi avoir des fenêtres dont le contenu change. Par exemple en fonction d'une variable, on doit afficher plus ou moins de composants... Cependant cette méthode est longue à programmer, et il faut sans cesse se ramener à MSDN pour savoir quels types donnés à la fenêtre.
La méthode utilisant les ressources est quand à elle moins modulable, mais beaucoup plus rapide. Dans l'éditeur de ressource il est aisé de créer une fenêtre, de placer des composants sur celle-ci, de paramétrer les options de la fenêtre...

Modifier et tester le comportement d'une fenêtre

Dans ce deuxième chapitre, nous allons voir quelques fonctions permettant de modifier le comportement de la fenêtre, son apparence...

Cette partie ne se veut pas une liste exhaustive de toutes les fonctions permettant un accès aux données de la fenêtre, mais liste quand même un certains nombres de fonctions et peut paraître assez rébarbative.

Passage Handle à Identifiant

Il est très important de comprendre cette notion, car les fonctions utilisent"indifféremment"dans leur paramètre soit un identifiant soit un handle pour désigner la fenêtre ou le composant sur lequel on souhaite travailler.

Récupération du code à partir de l'identifiant:
Grâce à l'utilisation de la fonction GetDlgItem(hwnd, identifiant) on récupère le code de la fenêtre ou du composant. Nous allons voir à quoi correspondent les deux paramètres de cette fonction. hwnd est le code de la fenêtre parent,et identifiant est l'identifiant (que l'on peut voir dans l'éditeur de ressource par exemple) du composant ou de la fenêtre.
Attention, cette méthode ne peut pas prendre en premier paramètre la valeur NULL. Vous me direz alors"Comment récupérer le code de notre première fenêtre?" La réponse est toute simple! Le code de la fenêtre est le premier paramètre de la fonction de traitement des messages, il suffit donc de le lire. Cette fonction est donc utile pour récupérer le code d'un composant d'une fenêtre, ou celui d'une fenêtre enfant par rapport à la fenêtre dans laquelle on travaille.

Récupération de l'identifiant à partir du code:
Cette fois-ci, nous utilisons une fonction beaucoup plus paramétrable, il s'agit de la fonction GetWindowLong(hwnd, index). Je dis que cette fonction est plus paramétrable, car selon la valeur d'index qu'on lui passe, elle nous renvoie une information différente. Dans notre cas, hwnd est le code de la fenêtre ou du composant dont on veut récupérer l'identifiant, et index doit prendre la valeur GWL_ID.

La visibilité de la fenêtre

Comme vu au premier chapitre, pour modifier la visibilité de la fenêtre, il faut utiliser la fonction ShowWindow(hwnd, index). hwnd représente le code de la fenêtre et index permet de saisir l'action à effectuer. Nous nous limiterons dans ce tutorieil à l'utilisation de 2 valeurs d'index: SW_SHOW(affichage de la fenêtre) et SW_HIDE (masquer la fenêtre), mais il est aussi possible de maximiser/minimiser/iconifier/restaurer/... la fenêtre avec d'autres valeur d'index. Pour les connaître, se référer à MSDN.

Il est aussi possible de faire appel à la fonction SetWindowPos(...)

La position de la fenêtre

Comme évoquer au chapitre 1, cette modification est réalisable avec la fonction SetWindowPos(hwnd,hwndafter, x, y, w, h, flag). Cette fonction parait au premier abord un peu plus compliquée. Loin s'en faut! Selon la valeur de flag (possibilité de s'associer avec l'opérateur |), tous les champs ne sont pas nécessaires!
Etudions quelques valeurs de flag. En regardant dans MSDN, on se rend compte que flag n'indique les actions à effectuer, mais les actions à ne pas effectuer! Ainsi pour ne pas déplacer la fenêtre et donc ne pas avoir à tenir compte des paramètres x et y, flag doit avoir la valeur SWP_NOMOVE; pour ne pas modifier la taille de la fenêtre et donc ignorer w et h, flag doit avoir la valeur SWP_NOSIZE.
Vous aurez compris que pour déplacer la fenêtre ou la redimensionner, il ne faut pas utiliser ces paramètres.
Cette fonction est aussi très utile pour régler l'ordre des fenêtres. Essayer de vous imaginer votre écran en profondeur. Comment dire à une fenêtre son "altitude"? En utilisant cette fonction! Le second paramètre (hwndafter) permet de le spécifier. Quelques valeurs de hwndafter sont prédéfinies (voir MSDN) et permettent de placer la fenêtre à la plus haute ou la plus basse des "altitudes" par exemple.
Dans le cas d'un déplacement au sens propre sur la fenêtre (changement d'origine), on peut utiliser la fonction MoveWindow(...). Je vous laisse aller voir dans MSDN ses paramètres tant ils sont simples à comprendre
La fonction AdjustWindowRect(...) permet de modifier la taille de la fenêtre, et la fonction GetWindowRect(...) permet de la récupérer.

Activer une fenêtre

Une fenêtre peut être active ou inactive. Si une fenêtre est inactive,l'utilisateur ne peut pas interagir avec elle. On met souvent une fenêtre inactive quand on a ouvert une nouvelle fenêtre de paramétrage d'option par exemple.
Pour cela, la fonction EnableWindow(hwnd, BOOL) est à votre disposition.Si BOOL est à TRUE, la fenêtre sera activée, sinon, elle sera désactivée.Simple, non?

Modifier le style

Cette fois ci, il faut utiliser la fonction SetWindowLong(hwnd, flag,value).flag permet de spécifier quels types de modification apporter à la fenêtre.
Si flag prend la valeur GWL_STYLE, on peut modifier le style de lafenêtre. Pour rajouter un style à la fenêtre, ne pas oublier de rajouter lesanciens styles que l'on obtient avec GetWindowLong(hwnd,GWL_STYLE).

Si l'on veut retirer un style de la fenêtre, faire alors:

SetWindowLong(hwnd, GWL_STYLE, GetWindLong(hwnd,GWL_STYLE) & !styletodel);

Si l'on veut ajouter un style à la fenêtre faire:

SetWindowLong(hwnd,GWL_STYLE, GetWindLong(hwnd, GWL_STYLE) | styletoadd);

Pour modifier les styles étendus (que l'on a pu créer uniquement avec la fonction CreateWindowEx) utilisé le flag GWL_EXSTYLE.

Modifier la fonction de traitement des messages

Il peut arriver dans certain casque vous ayez besoin de changer la fonction de traitement des messages de la fenêtre, ceci s'appelle du sous classement. Nous reviendrons plus tard dans ce tutoriel sur cette méthode et surtout sur son utilité. Sachez cependant que pour changer de fonction de traitement des messages, nous utilisons la fonction SetWindowLong() avec le paramètreflag à GWL_WNDPROC (pour une fenêtre) ou DWL_WNDPROC (pour une dialogbox).
Pour ne pas modifier la fonction de traitement des messages, mais en appeler une autre connue juste dans un cas précis, utilisé la fonction CallWindowProc(...).Pas de panique si vous ne comprenez pas à quoi cela peut bien servir, nous reviendrons dessus plus tard et fournirons des exemples. Nous citons ici cette fonction pour montrer son existence.

Modifier l'instance

Pour modifier l'instance d'une fenêtre on utilise encore la même fonction SetWindowLong() avec son paramètre flag à la valeur GWL_HINSTANCE.

Modifier/récupérer le titre

Pour modifier le titre d'une application, nous allons voir la une nouvelle fonction.Il s'agit de la fonction SetWindowText(hwnd, titre).titre est une chaîne de caractères contenant le titre à donner à la fenêtre ou au composant. Il faut remarquer que par exemple le titre d'un bouton est le texte qui apparaît sur le bouton. SetWindowText ne se restreint donc pas seulement aux fenêtres,elle travaille aussi sur les composants.
Pour récupérer le titre, utiliser la fonction GetWndowText(...)

Modifier le curseur/l'icône/le menu/...

Bien que la fonction permettant de le faire soit très simple d'emploi, si on veut comprendre ce qui se passe,on se mélange facilement les pinceaux entre code,instance et classe. Aussi nous n'entrerons pas dans les détails.
Ici, une ou deux possibilités s'offrent à nous. A la création de la fenêtre, si on a utilisé la méthode hard pour la créer, on peut sélectionner le curseur/icône/menu/... à utiliser dans les différents champs de la structure WNDCLASSEX.
Je ne compte pas cette méthode comme vrai puisque le but de ce chapitre est de modifier un curseur/icône/menu/...,pas d'en mettre un dès le début du programme.
Pour cela, il faut utiliser la fonction SetClassLong(hwnd, flag, value).Al'image de la fonction SetWindowLong(...) cette fonction offre de nombreuses possibilités, à vous d'aller les voir dans MSDN.
A savoir qu'il existe son homologue, la fonction GetClassLong(...) qui au lieu de modifier une donnée permet de la récupérer.

Récupérer/modifier le code de la fenêtre parent

Pour récupérer le code de la fenêtre parent, utilisé la fonction GetParent(hwnd).
Pour la modifier, utiliser la fonction SetParent(hwnd, hwndparent).

Tester la visibilité de la fenêtre

Dans ce cas ci, utiliser la fonction IsWindowVisible(hwnd) qui renvoie TRUE si la fenêtre est visible, FALSE autrement.

Tester si une fenêtre est active

De la même façon que précédemment, utilisé la fonction IsWindowEnable(hwnd)

Tester si un code représente une fenêtre

Comme précédemment, cela se fait en utilisant la fonction IsWindow(hwnd)

Tester si une fenêtre est une fenêtre enfant d'une autre

Toujours à l'identique des tests précédents, cela se fait avec la fonction IsChild(hwndparent,hwnd)

Création et communication avec des composants

Ici,nous allons étudier une des parties les plus importantes (tant en taille qu'en intérêt). Nous allons essayer de voir comment fonctionne réellement l'événementiel, et nous verrons comment faire communiquer plusieurs éléments entre eux.

Création des composants - Méthode hard

Comme pour créer une fenêtre, on peut créer un composant en les programmant. Pour cela on utilise encore une fois les fonctions CreateWindow ou CreateWindowEx.
Il faut rentrer le type de composant à créer dans le champ lpClassName d'une des 2 fonctions citées ci-dessus.
Listons les composants qu'il est possible de créer et la chaîne de caractères àentrer pour le faire:

  • Bouton: "BUTTON"
  • Combo: "COMBOBOX"
  • Edit: "EDIT"
  • ListBox: "LISTBOX"
  • ScrollBar: "SCROLLBAR"
  • Static: "STATIC"
  • ListView: WC_LISTVIEW
  • Progress: PROGRESS_CLASS

Ce sont les principaux composants existants.
A savoir que pour les checkbox et les radiobox sont des boutons, et que pour les créés il faut mettre dans le champ dwStyle des fonctions CreateWindow(Ex) respectivement les valeurs BS_AUTOCHECKBOX et BS_RADIOBUTTON.
On peut aussi créer une sorte de délimiteur dans une fenêtre avec le style BS_GROUPBOX associé à la création d'un bouton.
Pour plus de styles possibles pour chacun de ces composants, se référer à l'aide disponible dans MSDN pour les fonctions CreateWindow(Ex).
Juste un dernier petit mot sur les listviews et les progressbars. Ce sont des composants que l'on appelle complexes. Ils nécessitent l'importation d'une librairie: comctl32.lib, et celle d'un nouveau header: commctrl.h.
Pour importer la librairie dans le projet, deux solutions, soit l'importer à partir de la rubrique settings du menu project, soit indiquer au précompilateur d'importer cette librairie. Cela se fait par l'intermédiaire d'une directive appelée pragma: #pragma comment(lib,"comctl32.lib")
Ce n'est pas tout, ces composants nécessitent l'initialisation d'une dll externe (installée par windows et donc forcément présente).

Nous verrons comment cela se fait au travers d'un exemple (exemple2).

Exemple1: Création d'un composant "simple": le bouton

CreateWindow(\"BUTTON\", chaine, WS_CHILD |  BS_PUSHBUTTON | BOUTONVISIBLE | WS_TABSTOP, xpos, ypos, width, heigth,  windowlocation, NULL, 0, NULL);

Avec ce code, on vient de créer un bouton classique sur lequel est marqué le texte contenu dans la chaîne de caractère chaîne. Ce bouton est à la position indiquée par les entiers xpos et ypos et mesure width de largeur et heigth de hauteur. Les types donnés permettent à ce bouton d'apparaître visible, de pouvoir être sélectionné lors d'un appui sur la touche TAB. Ce bouton fait parti de la fenêtre code windowlocation.

Exemple2: Création d'un composant "complexe": la progressbar

INITCOMMONCONTROLSEX InitCtrlEx;

HWND progress;
InitCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);

InitCtrlEx.dwICC  =  ICC_PROGRESS_CLASS;           // ICC_LISTVIEW_CLASSES  pour les listviews

if (!InitCommonControlsEx(&InitCtrlEx))
    exit(1);

progress = CreateWindowEx(CS_DBLCLKS, PROGRESS_CLASS, NULL,  WS_CHILD | WS_VISIBLE | PBS_SMOOTH, xpos, ypos, width,  heigth, windowlocation, NULL, 0, NULL);

Dans un premier temps, on doit initialiser la dll de gestion des composants complexes. Si cela ne marche pas, on n'a plus qu'à quitter le programme. Ensuite on crée la progressbar de la même façon que pour un bouton. On a utilisé ici la fonction CreateWindowEx car on a voulu indiquer que la progressbar réagirait aux doubles clics. PBS_SMOOTH est un style propre des progressbars qui indique que son contenu sera continu, et pas hachurer.

Création de composants - Méthode "ressource"

Lors de la création d'une fenêtre dans une ressource, on peut à ce moment la dessiner les composants constituants cette fenêtre, et leur donner des styles.Ces composants sont alors connus par un identifiant dans la ressource.Souvenez-vous que nous avons au chapitre précédent comment passer d'un identifiant à un code
Si vous utilisez cette méthode,vous remarquerez la présence de composants dont je ne vous ai pas parlé plus haut. En effet, ce sont des composants moins utilisés (mais utilisé quand même)et si un jour vous en avez besoin, et bien vous chercherez dans MSDN comment les utiliser.

Communication avec les composants

Nous allons voir là la fonction autour de laquelle s'organise la programmation événementielle, il s'agit de la fonction SendMessage(...).

Voicile prototype de cette fonction:
SendMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
Etudions maintenant chacun de ces paramètres un par un:

  • hwnd: Il s'agit du code de la fenêtre ou du composant à qui le message est destiné.
  • message: Identifiant du message que l'on envoie (WM_QUIT, WM_COMMAND, WM_USER...), c'est autour de ce paramètre que doit se faire le switch principal des fonctions de traitement des messages (voir chapitre 1 pour un exemple)
  • wParam: Un paramètre du message, on peut lui donner n'importe quel type, à condition de le caster avant.
  • lParam: Un paramètre du message, on peut lui donner n'importe quel type, à condition de le caster avant.

Comme les messages envoyés à la fenêtre ou au composant sont très nombreux, il serait très pénible de devoir tous les traiter, particulièrement pour les fenêtres"simples".

L'API Windows fournit la fonction DefWindowProc(...) qui propose un traitement par défaut de tous les messages envoyés à la fenêtre. Cette fonction s'avère très pratique pour la plupart des messages, mais il faut bien en gérer quelques-uns pour que votre programme fasse quelque chose. Ainsi par exemple lors de l'appui par l'utilisateur sur un bouton vous devrez spécifier au programme ce qu'il doit faire. Mais lorsque l'utilisateur déplace la souris dans votre fenêtre, peut-être n'avez rien à faire suite à ce message, et il est pénible de devoir traiter tous les messages de ce style, sachant que DefWindowProc peut le faire à votre place. Voir l'exemple donné au chapitre 1 pour l'utilisation de cette fonction dans une boucle de traitement des messages.

Ci-dessous,une liste non exhaustive des principaux messages:

  • WM_CREATE: Message envoyé à une fenêtre lors de sa création
  • WM_INITDIALOG: Envoyé lors de la création d'une fenêtre à partir
  • WM_PAINT: Message envoyé à une fenêtre lorsqu'une partie de sa zone client doit être redessinée
  • WM_ERASEBKGND: Demande d'effacement d'une partie de la zone client
  • WM_SYSCOMMAND: Envoyé pour le traitement de différents événements pouvant survenir (fenêtre minimisée, restaurée...)
  • WM_ACTIVATE: Envoyé à la fenêtre lorsqu'elle est activée / désactivée
  • WM_MOVE: Envoyé lorsque la fenêtre est déplacée
  • WM_SIZE: Envoyé lorsque la taille de la fenêtre à été modifiée
  • WM_CLOSE: Envoyé lorsque l'utilisateur demande la fermeture de la fenêtre (croix ou raccourci ALT+F4)
  • WM_DESTROY: Destruction de la fenêtre
  • WM_QUIT: Met fin à l'application (on peut l'appeler avec la fonction PostQuitMessage(valuesortie) )
  • WM_COMMAND: Message envoyé par un contrôle, un menu... lorsqu'une action est effectué sur lui
  • WM_KEYDOWN/UP:Message envoyé lorsqu'une touche du clavier est enfoncée/relevée
  • WM_CHAR: Indique le caractère ASCII correspondant à la touche pressée
  • WM_SYSKEYUP/DOWN/CHAR: Utilisé pour identifier les événements dus aux touches systèmes
  • WM_MOUSEMOVE: Envoyé à chaque déplacement de souris
  • WM_LBUTTONDOWN/UP:Envoyé lorsque le bouton gauche de la souris est pressé ou relevé
  • WM_RBUTTONDOWN/UP:Envoyé lorsque le bouton droit de la souris est pressé ou * relevé
  • WM_MBUTTONDOWN/UP:Envoyé lorsque le bouton central de la souris est pressé ou * relevé
  • WM_L/R/MBUTTONDBLCLK: Envoyé lorsqu'un double clic a été détecté. Pour que ce message soit envoyé, il faut que la fenêtre/composant soit le style CS_DBLCLKS
  • WM_TIMER: Envoyé lorsqu'un timer envoie un message

Nous nous arrêterons là pour les messages, mais un bref coup d'oeil dans MSDN vous montrera la quantité de message qu'il existe.
Rajoutons à ces messages que l'on peut qualifier de système, les messages que chaque composant peut envoyer, et nous arrivons à une quantité non négligeable.Alors on peut dire merci à la fonction DefWindowProc qui nous évite de tous les traiter.
On pourra noter en allant voir dans MSDN les différents messages envoyés parles composants que la deuxième lettre du message est soit un M, soit un N. Le M signifie MODIFY, et le N signifie NOTIFY. En général, les messages avec un N sont envoyés par le composant à la fenêtre parent pour indiquer une modification.
Je pense qu'après toute cette théorie, un petit exemple s'impose!
Imaginons que nous avons créé une ressource d'identifiant IDD_DIALOG1 qui a à l'intérieur un bouton d'identifiant IDCANCEL. Nous souhaitons afficher la fenêtre et lorsqu'un clic sur le bouton intervient, nous voulons la fermer.Comment allons-nous procéder? Et bien regardez ci-dessous!

#include <windows.h> 
#include "resource.h"

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
    switch(message)
    {
    case WM_INITDIALOG:
        ShowWindow(hwnd,SW_SHOW);
        SetForegroundWindow(hwnd);
        return TRUE;
    Case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDCANCEL:
            SendMessage(hwnd,WM_DESTROY, 0, 0);
            return TRUE;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
    case WM_CLOSE:
        DestroyWindow(hwnd);
        return TRUE;
    caseWM_DESTROY:
        PostQuitMessage(0);
        return TRUE;
    default:
        return 0; //et pas DefWindowProc (car sinon la fenêtre ne se déplace plus, si certains savent, je suis intéressé)
    }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCEhPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC) WndProc);
}

Sous classement: pourquoi et comment

Dans un premier temps, répondons à la question pourquoi! Pour ceci, nous allons utiliser un exemple.
Nous voulons créer une fenêtre contenant des progressbars. Par un double clic sur une progressbar, nous souhaitons ouvrir un MessageBox contenant en titre l'identifiant de la progressbar et en message, le code de la progressbar.
Avec ce que nous avons vu précédemment, normalement pas de problèmes.
Et bien si! Les progressbars renvoient bien des messages de notification sur certaines actions, mais pas s'il s'agit d'un double clic!
Il faut alors spécifier à la progressbar notre propre fonction de traitement des messages! Allons-nous devoir traiter tous les messages qu'une progressbar peut recevoir et envoyer?Heureusement que non! Nous allons juste traiter le message WM_LBUTTONDBLCLK, et laisseront le reste à traiter par la fonction par défaut de traitement des messages d'une progressbar. C'est ce que l'on appelle du sous traitement! Une fonction de gestion des messages en appelle une autre!
Voyons maintenant au travers du code suivant comment mettre en place cette fonction.
Nous allons créer une fenêtre de façon hard, puis une progressbar de façon hard également. Cet exemple utilise des notions non encore abordées, comme les timers, que nous verrons plus loin dans ce cours. Il s'agit en gros d'un décompteur dont on fixe une valeur initiale, et chaque fois qu'il arrive à 0,un message WM_TIMER intervient.

#pragma comment(lib, "comctl32.lib")        //Inclusion de la librairie pour la progressbar
 
#include <windows.h>
#include <commctrl.h>    //Pour les progress bars
#include <resource.h>

#define  WM_GETDEFAULTID                      WM_USER
LONG ProgressProc(HWND hwnd, UINT message, WPARAM wParam, LPARAMlParam)
{
    static WNDPROC DefProgressProc;

     switch(message)
     {
     case WM_LBUTTONDBLCLK:
        {
            char titre[20], msg[20];
            sprintf(titre,"ID:%i", GetWindowLong(hwnd, GWL_ID));
            sprintf(msg, "Handle:0x%x", hwnd);
            MessageBox(NULL,msg, titre, MB_OK);
            return TRUE;
        }
        case WM_GETDEFAULTID:
             DefProgressProc= (WNDPROC) wParam;
             return TRUE;
         default:
            return CallWindowProc(DefProgressProc, hwnd, message, wParam, lParam);
     }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
    static HWND hProgress;
    switch(message)
    {
        case WM_CREATE:
        {
            INITCOMMONCONTROLSEX InitCtrlEx;
            WNDPROC DefProgressProc;
            InitCtrlEx.dwSize= sizeof(INITCOMMONCONTROLSEX);
            InitCtrlEx.dwICC  = ICC_PROGRESS_CLASS;

            if(!InitCommonControlsEx(&InitCtrlEx))
                PostQuitMessage(1);

            hProgress= CreateWindow(PROGRESS_CLASS, NULL, WS_CHILD | WS_VISIBLE | PBS_SMOOTH , 10, 10, 100, 100, hwnd, NULL, 0, NULL);

            SetClassLong(hProgress,GCL_STYLE, GetClassLong(hProgress, GCL_STYLE) | CS_DBLCLKS);

            DefProgressProc= (WNDPROC) SetWindowLong(hProgress, GWL_WNDPROC,(LONG) ProgressProc);

            SendMessage(hProgress,WM_GETDEFAULTID, (WPARAM) DefProgressProc, 0);
            SendMessage(hProgress,PBM_SETRANGE, 0, MAKELPARAM(0, 100));
            SendMessage(hProgress,PBM_SETSTEP, (LPARAM) 1, 0);
            SetTimer(hwnd,0x10, 100, NULL);
            ShowWindow(hwnd,SW_SHOW);
            SetForegroundWindow(hwnd);
            returnTRUE;
         }
         case WM_TIMER:
         {
            int pos;
            pos= SendMessage(hProgress, PBM_GETPOS, 0, 0);
            if(pos >= 100)  pos= 0;
            SendMessage(hProgress,PBM_SETPOS, ++pos, 0);
            return TRUE;
         }
         case WM_CLOSE:
            DestroyWindow(hwnd);
            return TRUE;
         case WM_DESTROY:
             KillTimer(hwnd,0x10);
            PostQuitMessage(0);
             return TRUE;
          default:
            return DefWindowProc(hwnd, message, wParam, lParam);
         }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    HWND Dlg;
    WNDCLASSEXconfig;
    config.cbSize= sizeof(WNDCLASSEX);
    config.style= CS_HREDRAW | CS_VREDRAW;
    config.lpfnWndProc= WndProc;
    config.cbClsExtra= 0;
    config.cbWndExtra= 0;
    config.hInstance= hInstance;
    config.hIcon= NULL;
    config.hCursor= LoadCursor(NULL, IDC_ARROW);
    config.hbrBackground= (HBRUSH) COLOR_APPWORKSPACE + 1;
    config.lpszClassName= "My Test Window";
    config.lpszMenuName= NULL;
    config.hIconSm= NULL;
    if (!RegisterClassEx(&config))
        exit(-1);
    
    Dlg =CreateWindow (config.lpszClassName, "Test", WS_OVERLAPPEDWINDOW, 0,0, 200, 200, NULL, NULL, hInstance, NULL);
    if (!Dlg)
        exit(-1);

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

Reprenons calmement cet exemple! Pas d'affolement, ce n'est pas si compliqué que ça!
Tout d'abord la fonction WinMain. Rien de bien dur, on crée une fenêtre de façon classique. La fonction RegisterClassEx(...) permet juste d'enregistrer la classe de fenêtre que l'on vient de créer. En cas d'échec, on quitte le programme.
Dans la fonction CreateWindow, le style WS_OVERLAPPEDWINDOW permet de créer une fenêtre des plus classiques (titre, menu système,bordure...).
Si on n'a pas réussi à créer la fenêtre on quitte le programme.

Ensuite,voyons la fonction WndProc. Là, ça se complique un peu.
Etudions pour commencer le traitement du message WM_CREATE. A la création de la fenêtre, nous commençons par créer une progressbar (n'oublions que c'est un composant complexe, et qu'il faut donc initialiser la dll). Ensuite, nous mettons le style étendu CS_DBLCLKS à cette progressbar à l'aide de la fonction SetClassLong. C'est ensuite que l'on fait le sous classement.
A ce moment la, la progressbar à encore sa fonction de traitement des messages par défaut.
Avec la fonction SetWindowLong et en lui donnant le paramètre GWL_WNDPROC,on change cette fonction de traitement des messages. Il faut savoir (en allant dans MSDN) que dans ces conditions, SetWindowLong renvoie l'ancienne valeur de la fonction de traitement des messages. Donc on vient de récupérer dans DefProgressProc la valeur de la fonction de traitement des messages par défaut d'une progressbar. On s'empresse de l'envoyer à la fonction de traitement des messages des progressbars que l'on a créée pour la sauvegarder.
Nous reviendrons là-dessus par la suite.

Dans le reste du message WM_CREATE, on initialise la progressbar (intervalle,pas d'incrément) et le timer (100ms entre chaque message WM_TIMER), et on affiche notre fenêtre.
Dans le message WM_TIMER, on ne fait qu'augmenter la position de la progressbar, et quand on est rendu à la fin de la progressbar,on repart de 0.
Avant de continuer avec la fonction ProgressProc, une petite explication sur le

#define WM_GETDEFAULTID    WM_USER

Avec l'API, on a la possibilité de créer nos propres messages. Leur identifiant parte de la valeur WM_USER(0x0400) jusqu'à la valeur 0x7fff. Ces messages respectent la même casse que les messages systèmes, c'est-à-dire un identifiant et deux paramètres.
Dans notre cas, nous nous servons de ce message pour envoyer à la fonction ProgressProc la valeur de la fonction de traitement des messages des progressbars par défaut, et nous mettons cette donnée dans le paramètre wParam.
Maintenant la fonction la plus intéressante, notre fonction de traitement des messages, la fonction ProgressProc.
En fait rien de bien particulier! Lorsqu'elle détecte un double clic de souris sur la progressbar elle affiche un MessageBox, sinon elle appelle la fonction de traitement par défaut. Ainsi quand la progressbar reçoit le message PBM_SETPOS,elle regarde s'il s'agit du message WM_LBUTTONDBLCLK et commence n'est pas le cas, elle transfert le message à la fonction par défaut.
Si vous n'avez pas compris, le jour ou vous en aurez besoin, vous comprendrez! Il faut rencontrer le problème pour se rendre compte de l'intérêt.

Le dessin sur une fenêtre

Dans ce chapitre, pas de long discours théorique, mais du code, encore du code toujours du code!
Rien ne sert de déblatérez sur le sujet, tant de livres en parle, alors nous allons faire des exemples!

Le Device Context

Un contexte d'affichage (DC) identifie une zone dans laquelle l'application peut afficher tous types de données. Pour récupérer un DC, on peut utiliser la fonction GetDC(hwnd). Si le paramètre fournit est NULL, GetDC renvoie un DC sur la totalité de l'écran. Lorsque l'on modifie une propriété du DC, on dit que l'on sélectionne un objet dans le DC, cela se fait par l'intermédiaire de la fonction SelectObject(...). Différents types d'objets peuvent être saisis dans un DC (police, image, pinceaux de dessin...). Les DC sont représentés par des HDC (Handle to Device Context)
Ainsi, pour dessiner sur une fenêtre, vous devez récupérer un HDC sur la fenêtre avec la fonction GetDC, puis dessiner dessus à l'aide des fonctions GDI (Graphic Device Interface) comme BitBlt pour coller des bitmaps, TextOut pour dessiner du texte ou encore LineTo pour tracer des lignes.

A noter qu'il vaut mieux utiliser BeginPaint dans le cas d'un WM_PAINT que GetDC pour récupérer le DC.

Les bitmaps

Chargement d'un bitmap: LoadImage

HBITMAP BITMAPLoad(HINSTANCE hInst, LPCTSTR bitmap, BOOLINRESOURCE, BOOL TRANSP)
{
    unsigned int type = LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS;
    if(!INRESOURCE)
        type|= LR_LOADFROMFILE;
    if (TRANSP)
        type|= LR_LOADTRANSPARENT;

    return (HBITMAP) LoadImage(hInst, bitmap, IMAGE_BITMAP, 0, 0, type);
}

Un HBITMAP est un code sur un bitmap, en résumé, c'est un pointeur sur une zone mémoire qui contient les données du bitmap.
La fonction donnée permet donc de charger un bitmap soit à partir d'une ressource, soit à partir d'un fichier *.bmp se situant sur votre disque.

  • Appel 1: hBmp = BITMAPLoad(hInst, MAKEINTRESOURCE(IDB_BITMAP1), TRUE, TRUE);
  • Appel 2: hBmp =BITMAPLoad(hInst, "monbitmap.bmp", FALSE, TRUE);

Affichage d'un bitmap sur un HDC: BitBlt; BeginPaint; SelectObject...

BOOL BITMAPDraw(HWND hWindow, HINSTANCE hInst, LPCTSTR imagename,int xdest, int ydest, int xsrc, int ysrc,int width, int heigth, BOOL INRESOURCE, BOOL TRANSPARENCE)
{
    HDC hDC,MemDCExercising;
    PAINTSTRUCT Ps;
    HBITMAP bmpExercising;
    BOOL ret;

    hDC =BeginPaint(hWindow, &Ps);

    // Chargement du bitmap
    if (NULL ==(bmpExercising = BITMAPLoad(hInst, imagename, INRESOURCE, TRANSPARENCE)))
        return FALSE;

    // Création d'un DC compatible avec le précédent
    if (NULL ==(MemDCExercising = CreateCompatibleDC(hDC)))
        return FALSE;

     // Sélection du bitmap
     SelectObject(MemDCExercising, bmpExercising);

    //Copie les bits du DC en mémoire vers le DC courant
    ret =BitBlt(hDC, xdest, ydest, width, heigth, MemDCExercising, xsrc, ysrc, SRCCOPY);

    // Suppressiondes données temporaires
    DeleteDC(MemDCExercising);
    DeleteObject(bmpExercising);

    EndPaint(hWindow,&Ps);
    DeleteDC(hDC);

    return ret;
}

Comment cela se fait-il? Nous créons une surface sur laquelle on peut dessiner sur toute la surface de notre application à l'aide de la fonction BeginPaint. Ensuite on charge le bitmap avec la fonction vue précédemment,et on le met sur une surface temporaire avec la fonction SelectObject. Pourquoi fait-on ça? La réponse est simple, on ne veut pas forcément charger tout le bitmap, ou on veut le mettre à un endroit précis sur le HDC, or la fonction SelectObject le met en (0, 0). Aussi, avec la fonction BitBlt, nous sélectionnons la partie du HDC temporaire que nous voulons garder et nous la collons à l'endroit où nous le souhaitons sur le HDC de notre fenêtre.
Avant de boucler cette partie, une petite explication sur la structure PAINTSTRUCT et la fonction BeginPaint. PAINTSTRUCT est une structure qui contient des informations à propos de la fenêtre qui va être repeinte. BeginPaint permet de sélectionner la totalité de la fenêtre sur laquelle on veut dessiner.La fonction EndPaint marque la fin du dessin, son appel est OBLIGATOIRE après un appel à BeginPaint.

Il y a possibilité d'agrandir ou de rétrécir le bitmap en utilisant la fonction StretchBlt à la place de BitBlt.

Affichage d'un bitmap dans un staticbox

void BITMAPSetInStatic(HWND hstatic, HBITMAP bitmap)
{
    SendMessage(hstatic,STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) bitmap);
}

Pour effectuer cette opération, on utilise un des messages spécifiques des staticbox. Avec cette méthode, on peut se passer des HDC pour afficher des images dans une fenêtre. On ne peut pas travailler sur le bitmap, mais on arrive à l'afficher où on veut tant que l'on met le staticbox là où il faut afficher le bitmap.

Affichage d'un bitmap sur un bouton

BOOL BOUTONSetBitmape(HWND button, HBITMAP hBitmap)
{
    if (hBitmap ==  NULL)
        returnFALSE;

    SendMessage(button,BM_SETIMAGE, TYPE, (LPARAM)(HANDLE) hBitmap);
    return TRUE;
}

Comme dans l'exemple précédent, utilisation des messages spécifiques au bouton pour afficher une image sur le bouton.

Les polices

Création d'une police: CreateFont

La fonction CreateFont() crée une police selon les caractéristiques spécifiées en arguments. Cette police doit ensuite être sélectionnée par SelectObject() pour devenir la fonte courante du contexte d'affichage.

HFONT MaPolice; 
MaPolice = CreateFont(20, 10, 0, 0, 800 ,FALSE, FALSE, FALSE, 0, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH | FF_DONTCARE,"Arial");

Ecriture sur DC avec la police: TextOut; SetTextAlign

BOOL DCWriteString(HDC hDc, HFONT police, char *string, int xpos,in typos)
{
    SelectObject(hDc, police);
    SetTextAlign(hDc, TA_BASELINE | TA_LEFT);
    return TextOut(hDc, xpos, ypos , string, lstrlen(string));
}

Dans un premier temps, on associe la police au contexte d'affichage, puis on définit l'alignement du texte et on écrit le texte. Avec TextOut on spécifie un point d'ancrage du texte, avec SetTextAlign,on dit à quoi correspond le point d'ancrage (ici c'est le coin en haut à gauche du texte)

Les pinceaux et les crayons

Création d'un pinceau et d'un crayon: Create(Hatch)Brush; CreatePen

HBRUSH hBrush1  = CreateHatchBrush(HS_DIAGCROSS, 0x00FF5050);
HBRUSH hBush2 =  CreateSolidBrush(0x00FFBB00);
HPEN hPen= CreatePen(PS_SOLID, 1, 0x00FF0000);

Ces trois fonctions permettent de créer respectivement:

  • Un pinceau qui dessine de façon hachurée, avec la possibilité de définir la hachure et la couleur
  • Un pinceau plein pour lequel on peut définir une couleur.
  • Un crayon dont on peut régler le type de trait (plein, pointillé...), l'épaisseur et la couleur

Pour définir la couleur, il y a deux possibilités. On peut utiliser la notation d'un COLORREF qui est: 0x00RRGGBB, ou une macro qui convertira en COLORREF par la suite: RGB(RR,BB,GG)

Colorier une surface avec un pinceau: FillRect; FillRgn; Ellipse; Polygon...

FillRect(hDc,&Rect,hBrush);
FillRgn(hDc,&Rgn,hBrush);

Ces deux fonctions remplissent respectivement un rectangle ou une région avec le pinceau que l'on a sélectionné. Pour ces deux instructions, pas besoin de SelectObject.

Par contre, il est également possible de créer des surfaces plus "libres" à l'aide des fonctions Ellipse et Polygon. Pour remplir ces surfaces, il faut avant tout sélectionner les pinceaux et les crayons à utiliser avec SelectObject. Le pinceau servira à remplir l'intérieur dela surface ainsi créée, et le crayon en fera le tour. On peut créer plusieurssurfaces sur un HDC en utilisant plusieurs fois ces fonctions.

Tracer des traits: MoveTo; LineTo; PolyBezier; ArcTo; PolylineTo...

Avec la fonction MoveTo, on sélectionne un point de départ pour notre trait, puis selon la fonction utiliser, on peut tracer des lignes droites, courbes, des arcs de cercle...
Avant de tracer le trait, il faut sélectionner le crayon à utiliser avec SelectObject.(Je sais je me répète, mais ça ne fait pas de mal!)

Les Timers

Alors,là, juste un petit chapitre pour expliquer le fonctionnement d'un composant fort sympathique si l'on veut créer des actions qui se répètent à intervalle de temps régulier.

Création d'un timer

SetTimer(hwnd, ID, time, callbackfunc);

Une petite ligne de code et ça y est, on a notre timer, n'est-ce pas magique tout ça?

Bon,je vous dois quand même quelques explications. Tout d'abord, il faut se rendre compte que lorsque l'on crée un timer, il faut que celui ne communique qu'ave cnotre application, pas avec les autres, d'où la nécessité de lui donner en paramètre le code de notre fenêtre (car rappelez-vous, ce code est unique!).Ensuite, il y a la possibilité de créer plusieurs timers pour une seule et même application, donc il faut pouvoir les identifier (ceci est fait par l'intermédiaire d'un identifiant). Et comment spécifions-nous le temps d'attente du timer? Et bien en le donnant en paramètre. Attention, il est à noter que time s'exprime en millisecondes.
Reste le dernier paramètre. Il est possible de définir notre propre fonction de traitements des messages pour les messages émanant d'un timer. Si ce paramètre est à NULL, le timer enverra le message WM_TIMER à la fonction de traitement des messages de la fenêtre. Ne vous inquiétez pas, nous verrons un exemple plus loin dans ce chapitre.

Destruction d'un timer

Tout cela est bien beau, mais moi j'ai besoin de créer un timer que je peux arrêter par un clic sur un bouton, ou que sais-je encore comme action.
La fonction suivante fera alors votre bonheur. De toute façon, lorsque vous quittez votre programme, il faut détruire le timer, donc vous devrez utiliser cette instruction dans le WM_DESTROY ou WM_QUIT de votre appli.

KillTimer(hwnd, ID);

Rien de bien sorcier, juste préciser le code de votre fenêtre et l'identifiant de votre timer.
Attention cependant, cette fonction ne supprime pas les messages WM_TIMER en attente dans la liste des messages à traiter.

Un petit exemple

Ci-dessous,un peu de code. Dans une ressource, nous créons 3 boutons (IDOK, IDCANCEL et IDSTOP), un combobox (IDC_COMBOTIME) et une progressbar (IDC_PROGRESS).
On souhaite remplir la progressbar par intervalle de temps régulier(utilisation de timer). Cet intervalle de temps sera choisi à partir d'un combobox. Lorsque l'on changera la valeur de la progressbar, on en changera aussi la couleur.
Cet exemple montrera donc l'utilisation des timers, des boutons, des combos et des progressbar, aussi essayez de bien le comprendre.

#pragma comment(lib,"comctl32.lib")
 
#include <windows.h>
#include <commctrl.h>
#include <time.h>
#include "resource.h"

#define IDTIMER       0x10
#define MIN_PROGRESS  0
#define MAX_PROGRESS  1000

//Ajout d'une chaine de caractère dans un combobox
BOOL COMBOSetValue(HWND hCombo, char *value)
{
    unsignedlong error; 

    //Envoi du message d'ajout d'une chaine au combo
    error =SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR) value);
    if (error ==CB_ERR || error == CB_ERRSPACE)
    {
       MessageBox(NULL,"Impossible d'ajouter du texte au combobox", "Erreur",MB_OK |MB_ICONERROR);
       return FALSE;
    }
    return TRUE;
}

//Récupération de l'index d'une chaine (CB_ERR si la chaine n'est pas trouvée)
unsigned long COMBOGetStringIndex(HWND hCombo, char *chaine)
{
    return SendMessage(hCombo, CB_FINDSTRINGEXACT, -1, (LPARAM)(LPCTSTR) chaine);
} 

//Pour sélectionner une valeur dans le combo
int COMBOSetInitialValue(BOOL ISVALUEANINDEX, HWND hCombo,unsigned long index, char *nom)
{
    if(!ISVALUEANINDEX)
        index= COMBOGetStringIndex(hCombo, nom);
    return SendMessage(hCombo, CB_SETCURSEL, index, 0);
}

//Récupère l'index de l'item sélectionné dans un combobox
unsigned long COMBOGetIndexSelectedItem(HWND hCombo)
{
    return SendMessage(hCombo, CB_GETCURSEL, 0, 0);
}

//Récupère la taille de l'item sélectionné
unsigned long COMBOGetLenIndex(HWND hCombo, unsigned long index)
{
    return SendMessage(hCombo, CB_GETLBTEXTLEN, index, 0);
}

//Récupère le texte de l'item sélectionné
char* COMBOGetStringFromIndex(HWND hCombo, unsigned long index)
{
    char* buffer; 

    buffer =(char*) malloc((1 + COMBOGetLenIndex(hCombo, index)) * sizeof(char));
    SendMessage(hCombo,CB_GETLBTEXT, index, (LPARAM)(LPCTSTR) buffer);
    return strdup(buffer);
}

//Définit l'intervalle d'une progressbar
int PROGRESSSetRange(HWND hProgress, int minimum, int maximum)
{
    return SendMessage(hProgress, PBM_SETRANGE, 0, MAKELPARAM(minimum, maximum));
} 

//Définit la position dans la progressbar
int PROGRESSSetPos(HWND hProgress, int pos)
{
    return SendMessage(hProgress, PBM_SETPOS, pos, 0);
}

//Récupère la position dans une progressbar
int PROGRESSGetPos(HWND hProgress)
{
    return SendMessage(hProgress, PBM_GETPOS, 0, 0);
}

//Met la progressbar de la couleur donnée
int PROGRESSSetColor(HWND hProgress, COLORREF color)
{
    return SendMessage(hProgress, PBM_SETBARCOLOR, 0, (LPARAM) (COLORREF) color);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
    switch(message)
    {
    //A la creation de la fenetre
    case WM_INITDIALOG:
        {
            INITCOMMONCONTROLSEX InitCtrlEx;
            const char *datatime[] = {"10", "100", "1000","10000"};
            int i;
            time_t date; 

            time(&date);
            srand(date);
            for(i = 0; i < 4;i++)
                COMBOSetValue(GetDlgItem(hwnd,IDC_COMBOTIME), (char*) datatime[i]);
            COMBOSetInitialValue(TRUE,GetDlgItem(hwnd, IDC_COMBOTIME), 0, NULL);
            InitCtrlEx.dwSize= sizeof(INITCOMMONCONTROLSEX);
            InitCtrlEx.dwICC  = ICC_PROGRESS_CLASS;
            if(!InitCommonControlsEx(&InitCtrlEx))
                PostQuitMessage(1);
            PROGRESSSetRange(GetDlgItem(hwnd,IDC_PROGRESS), MIN_PROGRESS, MAX_PROGRESS);
            PROGRESSSetPos(GetDlgItem(hwnd,IDC_PROGRESS), MIN_PROGRESS);
            ShowWindow(hwnd,SW_SHOW);
            SetForegroundWindow(hwnd);
            returnTRUE;
        }

    case WM_COMMAND:
        {
            switch(LOWORD(wParam)) //LOWORD(wParam)) contient l'ID du contrôle
            {
            case IDOK:
                {
                   char* buffer;
                   unsigned int timer; 

                   //Récupération de la chaîne du combo
                   buffer= COMBOGetStringFromIndex(GetDlgItem(hwnd,IDC_COMBOTIME), COMBOGetIndexSelectedItem(GetDlgItem(hwnd,IDC_COMBOTIME)));
                   timer= (unsigned int) atoi(buffer);
                   //Suppression de l'ancien TIMER puis création du nouveau
                   KillTimer(hwnd,IDTIMER);
                   SetTimer(hwnd,IDTIMER, timer, NULL);
                   free(buffer);
                   return TRUE;
                }
           case IDSTOP:
                KillTimer(hwnd,IDTIMER);
                return TRUE;
           case IDCANCEL:
                SendMessage(hwnd,WM_DESTROY, 0, 0);
                return TRUE;
           default:
                return 0;
            }
        }
    case WM_TIMER:
        {
            unsigned long pos;
            //Récupération de la position dans la progressbar
            pos= PROGRESSGetPos(GetDlgItem(hwnd, IDC_PROGRESS));

            if(pos == MAX_PROGRESS)
               pos =MIN_PROGRESS;

            PROGRESSSetColor(GetDlgItem(hwnd,IDC_PROGRESS), RGB(rand() % 0x100,rand() % 0x100, rand() % 0x100));
            //Nouvelle position = Ancienne position + 1
            PROGRESSSetPos(GetDlgItem(hwnd,IDC_PROGRESS), ++pos);
            return TRUE;
        }
    Case WM_CLOSE:
        DestroyWindow(hwnd);
        return TRUE;
    Case WM_DESTROY:
        KillTimer(hwnd,0x10); //Ne pas oublier de killer le timer en quittant
        PostQuitMessage(0);
        return TRUE;
    default:
        return 0;
    }
}

intAPIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevinstance, LPSTR lpCmdLine,int nCmdShow)
{
    return DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, WndProc);
}

Nous ne regarderons pas les différentes fonctions pour gérer le combobox et la progressbar, une recherche rapide dans MSDN sur le nom du message que l'on envoie vous fournira toutes les explications nécessaires.
Non, nous nous allons regarder les actions effectuer par les boutons d'identifiant IDOK et IDSTOP.
Le bouton IDOK permet de créer un timer à un intervalle de temps régulier. Ne pas oublier de supprimer le précédent timer si on n'est pas passé par le bouton stop, sinon, nous ne verrons aucune différence si nous passons d'un timer rapide à un timer court, l'ancien timer continuant de s'exécuter.
Pour arrêter un timer lors de l'appui sur le bouton IDSTOP, il n'y a qu'à tuer ce timer.
Le reste du code n'est que de la mise en forme pour vous montrer ce qu'il est possible de réaliser.

Les menus

Il existe deux types de menus. Les menus que nous allons dire"classiques" qui se trouvent sous la barre de titre de l'application et les menus pop up qui sont affichables par exemple lors d'un clic droit avec la souris.
Dans ces menus, certains items peuvent être grisés, d'autre peuvent être marqués d'un petit "v" devant pour indiquer un état.
Mais comment cela fonctionne-t-il?

Les menus que nous utiliseront par la suite seront créés à partir d'une ressource, mais il faut savoir qu'il y a moyen des programmer manuellement.

La création

Deux façons de créer un menu existent.
La première façon consiste lors d'une création en hard à spécifier à la classe de la fenêtre qu'elle a un menu, et de lui dire quel menu elle doit afficher en lui fournissant un identifiant.
Pour cela, il faut renseigner le champ lpszMenuName de la structure WNDCLASSEX.

WNDCLASSEX config;

config.cbSize= sizeof(WNDCLASSEX);
config.style= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
config.lpfnWndProc= WndProc;
config.cbClsExtra= 0;
config.cbWndExtra= 0;
config.hInstance= hInstance;
config.hIcon= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
config.hCursor= LoadCursor(NULL, IDC_ARROW);
config.hbrBackground= (HBRUSH) COLOR_APPWORKSPACE + 1;
config.lpszClassName= TITREAPPLI;
config.lpszMenuName= (LPCSTR) IDR_MENU1;
config.hIconSm= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
if(!RegisterClassEx(&config))
    exit(1);

La deuxième méthode revient à utiliser la fonction LoadMenu.
Nous allons donner ci-dessous un exemple permettant de charger un menu pop up à l'endroit où se situe la souris.

POINT position_souris;
HMENU hMenu,hMenuPopup;
DWORD selection 

GetCursorPos(&position_souris);
hMenu=LoadMenu((HINSTANCE),GetModuleHandle(NULL),MAKEINTRESOURCE(IDR_MENUSYSTRAY));
//Récupération du code du menu à afficher (on doit afficher le sous-menu du menu principal
hMenuPopup =GetSubMenu(hMenu, 0);
if(hMenuPopup == NULL)
    SendMessage(window,WM_DESTROY, 0, 0);
//Permet d'éviter un bug windows : si on ne clique sur aucun choix du menu, il s'en va tout seul
SetForegroundWindow(window);
selection = TrackPopupMenu(hMenuPopup, TPM_NONOTIFY | TPM_LEFTALIGN | TPM_LEFTBUTTON |TPM_RETURNCMD, position_souris.x, position_souris.y, 0, window, NULL);

AvecGetCursorPos, on récupère la position de la souris, puis avec LoadMenu et GetSubMenu, on récupère le menu pop up à afficher. La fonction TrackPopupMenu permet d'afficher le menu et de récupérer l'identifiant du menu cliquer. Par la suite, dans un switch, il n'y a plus qu'à traiter les différentes valeurs possibles de la valeur retournée.
TrackPopupMenu ne marche que pour les menus pop up, pour un menu classique, on n'a pas besoin de l'appeler, il est déjà présent (si ce n'est pas le cas,utilisé SetMenu après avoir au préalable chargé le menu avec un LoadMenu).

Si on a crée un menu "classique", un clic sur le menu envoie un message WM_COMMAND à l'application, à ce moment la, LOWORD(wParam) contient l'identifiant du contrôle qui a envoyé le message, on peut donc trouver sur quel menu on a cliquer par un switch(LOWORD(wParam)).

Activer/désactiver/valider...un élément du menu

Toutes les opérations de modification de l'état d'un menu se font à travers une seule fonction: SetMenuItemInfo. Mais pour pouvoir l'utiliser, il faut comprendre le fonctionnement de la structure MENUITEMINFO.
SetMenuItemInfo permet aussi de rajouter des éléments à un menu.
MENUITEMINFO est relativement simple à comprendre. Voici sa définition (merci MSDN)

typedef struct tagMENUITEMINFO {
    UINT    cbSize;
    UINT    fMask;
    UINT    fType;
    UINT    fState;
    UINT    wID;
    HMENU   hSubMenu;
    HBITMAPhbmpChecked;
    HBITMAPhbmpUnchecked;
    DWORD   dwItemData;
    LPTSTR  dwTypeData;
    UINT    cch;
} MENUITEMINFO, FAR *LPMENUITEMINFO;

Elle s'articule principalement autour du champ fMask qui permet de définir les champs à prendre en compte. Le champ wID permet de définir l'identifiant de l'item à modifier.
Imaginons que nous voulions dans le menu précédemment chargé désactivé l'item d'identifiant ID_ITEM1 si le booléen TEST est à TRUE.

MENUITEMINFO menu; 

menu.cbSize = sizeof(MENUITEMINFO);
menu.fMask = MIIM_STATE;
menu.fState =  TEST ?MFS_ENABLED : MFS_GRAYED;
SetMenuItemInfo(hMenuPopup, ID_DRAW, FALSE, &menu);

Vous voyez, rien de bien compliqué!

Il est même possible d'accéder au menu système (vous savez, le menu qui apparaît quand vous cliquez droit sur la barre de titre de l'appli) avec la fonction GetSystemMenu et d'en modifier les données ensuite.

Destruction d'un menu

Et oui, comme pour tous les objets, une fois qu'ils sont utilisés, plus besoin de les garder en mémoire. La fonction DestroyMenu permet de les détruire.

Le systray

Ce qu'il faut bien comprendre, c'est que le systray n'est qu'une zone dans laquelle on peut afficher des icônes. Ces icônes sont un peu particulières, dans le sens qu'elles peuvent envoyer des messages de notification à votre application comme "on m'a cliqué dessus".
Voyons comment cela fonctionne.

Affichage d'une icône dans le systray

Pour ce faire, il faut correctement initialiser la structure NOTIFYICONDATA, et demander à l'icône de s'afficher.

#define WM_ICONSYSTRAY WM_USER 

NOTIFYICONDATA icone; 

icone.cbSize = sizeof(icone);
icone.uID = IDI_ICON1;
icone.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
icone.uCallbackMessage = WM_ICONSYSTRAY;
icone.hIcon = LoadIcon(NULL, NULL);
icone.szTip = 'icône dans le systray"';
icone.hWnd = hwnd;
icone.hIcon = LoadIcon((HINSTANCE) GetModuleHandle (NULL),MAKEINTRESOURCE(IDI_ICON1));
Shell_NotifyIcon(NIM_ADD, &icone);

Comme vu dans un chapitre précédent, on associe à l'icône un type de message. Quand une action sera effectuée, l'icône enverra à notre application le message WM_ICONSYSTRAY.
On dit également à la structure quelle icône utiliser, et on la lui associe.
La fonction Shell_NotifyIcon est utilisée lors du dialogue application vers icône du systray.
On peut donc ajouter une icône, modifier une icône, où supprimer une icône avec cette fonction.

Modification de l'icône du systray

Cela se fait de la même façon que pour la créer, à la seule différence près qu'au lieu de passer le paramètre NIM_ADD à la fonction Shell_IconNotify, on lui donne le paramètre NIM_MODIFY. On peut donc tout modifier (icône, tooltip,fonction de traitement...)

icone.hIcon = LoadIcon((HINSTANCE) GetModuleHandle(NULL),MAKEINTRESOURCE(IDI_ICON2));
Shell_NotifyIcon(NIM_MODIFY, &icone);

Suppression d'une icône du systray

Vous devez vous en doutez maintenant, on procède encore de la même façon. Cette fois-ci, nous utiliserons le paramètre NIM_DELETE avec la fonctionShell_NotifyIcon. N'oubliez pas de supprimer votre icône lorsque vous quittez votre application.

Les accélérateurs de clavier

Alors comment faire ces raccourcis claviers?
La encore, nous ne nous baserons que sur des ressources, même si il est possible de les programmer manuellement. Mais la encore, d'un point de vue purement personnel, je dirais que c'est une perte de temps!

HACCEL hAccelTable;
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDR_ACCELERATOR1);

En gros, on crée un nouvel objet de type accélérateur de table, et on le charge avec LoadAccelerators.
A chaque raccourci clavier que vous créez, vous associer l'identifiant d'un menu ou d'un bouton, donc lorsque vous utilisez le raccourci que vous venez de créer, vous recevrez le même message que si l'utilisateur avait cliqué sur votre menu ou votre bouton.
Pour supprimer un accélérateur de table, utiliser la fonction DestroyAcceleratorTable.
Vous voyez, rien de bien extraordinaire, voir même plutôt trop simple! Ce n'est pas marrant de n'avoir rien à faire!

Un peu de code

Dans cette partie, pas d'explication, juste des fonctions qui peuvent vous être utiles!

Eteindre votre ordinateur

void StopComputer(void)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp; 

    OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
    tkp.PrivilegeCount= 1;  
    tkp.Privileges[0].Attributes= SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken,FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);
    ExitWindowsEx(EWX_POWEROFF| EWX_FORCE, 0);
}

Possibilité de redémarrez l'ordinateur en mettant EWX_REBOOT dans ExitWindowEx ou de fermer une session en utilisant EWX_LOGOFF.

Ouvrir un fichier avec le programme par défaut

Quand je dis programme par défaut, j'entends le programme qui doit ouvrir ce type de fichier.

ShellExecute(NULL, "open"L, "monfichier.doc",NULL, NULL, SW_SHOW);

Récupération de la hauteur de la barre des taches

long GetHeigthTray(BOOL VIEWERROR)
{
    RECT tray;
    HWND hWnd; 

    if (NULL ==(hWnd = FindWindow("Shell_TrayWnd", NULL)))
    {
        if(VIEWERROR)
            VoirErreur(GetLastError());
        return -1;
    }
    if(!GetWindowRect(hWnd, &tray))
    {
        if(VIEWERROR)
            VoirErreur(GetLastError());
        return -1;
    }
    return tray.bottom - tray.top;
}

Affichage du message d'erreur

void VoirErreur(unsigned long error)
{
    LPVOID lpMsgBuf; 
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER| FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
    MessageBox(NULL,(LPCTSTR )lpMsgBuf, "Erreur", MB_OK | MB_ICONERROR);
    LocalFree(lpMsgBuf);
}

Cette fonction est utile quand une fonction échoue, et que l'on peut récupérer le numéro de l'erreur par un appel à GetLastError(). Si la fonction renvoie un code d'erreur, c'est indiqué dans MSDN. Un appel classique de cette fonction est:

VoirErreur(GetLastError());

Récupérer la taille de composant système

Utiliser la fonction GetSystemMetric.
On peut récupérer la taille de l'écran, le nombre de boutons de la souris, la largeur des bordures de fenêtres, la taille des icônes, si la machine est en réseau...
Pour les différentes possibilités, vois dans MSDN, elles sont trop nombreuses pour les expliquer.

A voir également
Ce document intitulé « Base de la création d'une fenetre en api windows [c] » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous