Developpez.com - Windows
X

Choisissez d'abord la catégorieensuite la rubrique :

La programmation de l'interface utilisateur

Date de publication : 22 mai 2008. Date de mise à jour : 23 mars 2009.


III. Les menus
III-A. Création dynamique d'un menu
III-B. Création à l'aide d'un fichier de ressources
III-C. Les menus contextuels
III-D. Autres fonctions
EnableMenuItem
CheckMenuItem
III-E. Les accélérateurs claviers


III. Les menus


III-A. Création dynamique d'un menu

La fonction qui permet de créer un menu est CreateMenu. Après qu'on ait créé un menu, on peut lui ajouter des éléments à l'aide de la fonction AppendMenu. Evidemment un menu peut comporter d'autres menus. Par exemple les menus Fichier, Edition, Affichage etc. sont des sous menus du menu principal. Chacun de ces menus peuvent ensuite encore comporter d'autres menus et ainsi de suite. Le problème c'est que les éléments « terminaux » (tels que Enregistrer, Fermer, Quitter, etc.) sont également appelés menus ce qui est généralement source de confusion. On se réfère donc aux termes anglais « popup » pour désigner un menu qui peut en comporter d'autres (mais qui n'est pas le menu principal) et « menu item » pour désigner un élément terminal d'un menu. En résumé, « menu » est un terme générique (et confus) qui peut être à la fois utilisé pour désigner un menu principal, un popup menu ou un menu item. Notez également qu'un menu principal a toutes les fonctionnalités d'un popup menu mais ce n'est pas un popup menu (c'est un menu principal !).

Le fonctionnement des menus est très similaire à celui des boutons au sens où le fait de cliquer sur un menu (terminal) provoque l'émission du message WM_COMMAND avec comme paramètres l'identificateur du menu (LOWORD(wParam)) et NULL (lParam).

Dans l'exemple suivant, on va créer un menu (le menu principal) qui contiendra juste un sous menu : Fichier, qui contiendra à son tour 5 éléments terminaux à savoir Nouveau, Ouvrir, Enregistrer, Enregistrer sous et Quitter. Un séparateur (qui se présente sous forme d'une ligne horizontale) sera placé entre les 4 premiers et la dernière. On peut créer le menu avant ou après la fenêtre, tout dépend de la manière dont on veut s'y prendre. La première solution permet de spécifier le handle du menu directement dans l'argument hMenu de CreateWindow. Dans cet exemple, c'est la deuxième méthode que nous allons utiliser.
#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
    
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance      = hInstance;
    wc.lpfnWndProc    = WndProc;
    wc.lpszClassName  = "Menu Demo";
    wc.lpszMenuName   = NULL;
    wc.style          = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&wc);

    hWnd = CreateWindow("Menu Demo", "M-D", WS_OVERLAPPEDWINDOW, 100, 100, 600, 300, NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

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

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HMENU hMenu, hFichier;
    
    switch(message)
    {
    case WM_CREATE:
        hMenu = CreateMenu();
        hFichier = CreateMenu();
        
        AppendMenu(hFichier, MF_STRING, (UINT_PTR)1, "&Nouveau");
        AppendMenu(hFichier, MF_STRING, (UINT_PTR)2, "&Ouvrir");
        AppendMenu(hFichier, MF_STRING, (UINT_PTR)3, "&Enregistrer");
        AppendMenu(hFichier, MF_STRING, (UINT_PTR)4, "En&registrer sous ...");
        AppendMenu(hFichier, MF_SEPARATOR, (UINT_PTR)-1, "");
        AppendMenu(hFichier, MF_STRING, (UINT_PTR)5, "&Quitter");
        AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFichier, "&Fichier");
        
        SetMenu(hwnd, hMenu);
        
        break;
        
    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case 1:
            MessageBox(hwnd, "Crée un nouveau document", "M-D", MB_OK);
            break;
            
        case 2:
            MessageBox(hwnd, "Ouvre un document", "M-D", MB_OK);
            break;
            
        case 3:
            MessageBox(hwnd, "Enregistre le document", "M-D", MB_OK);
            break;
            
        case 4:
            MessageBox(hwnd, "Enregistre sous un autre nom", "M-D", MB_OK);
            break;
            
        case 5:
            DestroyWindow(hwnd);
            break;
        }
        
        break;
        
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
        
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    
    return 0;
}
Vous-vous demandez certainement pourquoi il n'est pas nécessaire de détruire le menu avant de quitter le programme. La réponse est : si, il faut le détruire (avec DestroyMenu). Cependant, lorsqu'un menu est attaché à une fenêtre, il sera automatiquement détruit en même temps que cette fenêtre. D'accord, mais qu'en est-il des sous menus ? Ils ont également été détruits en même temps que le menu car DestroyMenu est récursive, c'est-à-dire que la destruction d'un menu entraîne également la destruction de ses sous-menus.


III-B. Création à l'aide d'un fichier de ressources

On peut également créer un menu en ressource. Dans le programme, il ne restera plus qu'à charger cette ressource avec la fonction LoadMenu, qui retournera le handle du menu en question.
HMENU LoadMenu(HINSTANCE hInstance, LPCTSTR lpMenuName);
Les éditeurs de ressources simplifient beaucoup la création de menu mais à défaut, on peut toujours taper le code nous-mêmes. Par exemple :
MyMenu MENU
{
    POPUP "&Fichier"
    {
        MENUITEM "&Nouveau", 1
        MENUITEM "&Ouvrir ...", 2
        MENUITEM "&Enregistrer", 3
        MENUITEM "En&registrer sous ...", 4
        MENUITEM SEPARATOR
        MENUITEM "&Quitter", 5
    }
}
On peut également directement spécifier le nom du menu (ici "MyMenu") dans le membre lpszMenuName de la structure WNDCLASS et dans ce cas on n'a plus besoin ni de LoadMenu, ni de SetMenu. En outre, l'argument hMenu de CreateWindow doit être tout simplement NULL.


III-C. Les menus contextuels

Dans une application standard, on affiche normalement un menu contextuel lorsqu'on reçoit le message WM_CONTEXTMENU qui, rappelons-le, est envoyé lorsque l'utilisateur a cliqué avec le bouton droit à l'intérieur de la fenêtre.

Un menu contextuel est un menu comme tous les autres. N'importe quel popup peut être affiché en tant que menu contextuel. Notez bien cependant que le menu doit être obligatoirement un popup menu, pas n'importe quel menu. Il faut donc utiliser CreatePopupMenu à la place de CreateMenu. A noter également que dans l'exemple précédent (création dynamique), on aurait aussi pu créer le menu Fichier à l'aide de CreatePopupMenu (puisqu'il s'agit bien d'un popup menu) par contre on ne peut pas utiliser cette fonction pour créer le menu principal (qui n'est pas un popup menu ...). Et enfin, sachez que, puisqu'ils sont reconnus par le système comme étant de vrais popup menus, les sousmenus, peu importe la manière dont ils on été créés (en ressource, avec CreateMenu, avec CreatePopupMenu, etc.), peuvent également être utilisés. Si on n'a pas le handle du sous menu (ce qui est fréquemment le cas lorsque le menu a été créé en ressource), on peut toujours le récupérer avec GetSubMenu. La fonction GetMenu, qui retourne le handle du menu d'une fenêtre, peut également s'avérer utile.
HMENU GetMenu(HWND hWnd);
HMENU GetSubMenu(HMENU hMenu, int nPos);
La fonction TrackPopupMenu permet d'afficher le menu.
BOOL TrackPopupMenu(HMENU hPopup, UINT uFlags, int x, int y, int nReserved, HWND hWnd, const RECT * prcIgnored);
Le dernier paramètre est de la décoration. Il est ignoré par Windows ! L'extrait de code suivant permet d'afficher un menu contextuel :
case WM_CONTEXTMENU:
    TrackPopupMenu(hFichier, 0, LOWORD(lParam), HIWORD(lParam), 0, hwnd, NULL);
    break;

III-D. Autres fonctions


EnableMenuItem

BOOL EnableMenuItem(HMENU hPopup, UINT uItem, UINT uEnable);
Le paramètre uEnable peut prendre l'une des valeurs suivantes :

En fait, on peut combiner ces valeurs avec MF_BYCOMMAND (par défaut) ou MF_BYPOSITION. MF_BYCOMMAND entraîne que uItem fait référence à l'ID du menu que l'on veut activer ou désactiver et MF_BYPOSITION que uItem fait référence à l'indice du menu à activer / désactiver (le premier élément possède l'indice 0).


CheckMenuItem

BOOL CheckMenuItem(HMENU hPopup, UINT uItem, UINT uCheck);
Les valeurs utilisables dans l'argument uCheck MF_CHECKED, MF_UNCHECKED, MF_BYCOMMAND et MF_BYPOSITION.



III-E. Les accélérateurs claviers

Les accélérateurs (ou raccourcis) claviers sont des combinaisons de touches qui permettent à l'utilisateur d'émettre une commande (c'est-à-dire le message WM_COMMAND) à l'aide du clavier. Ils sont généralement utilisés comme raccourcis vers une commande proposée par un menu ou un bouton par exemple. On va par exemple créer les raccourcis suivants pour notre menu Fichier : Ctrl + N (Nouveau), Ctrl + O (Ouvrir), Ctrl + S (Enregistrer) et F12 Enregistrer sous.

Traiter le message WM_KEYDOWN puis tester l'état de la touche Ctrl est inefficace car ce message n'est émis que si la fenêtre a le focus du clavier. Dès que le focus passe à un contrôle, ce message peut ne pas être envoyé. C'est pourquoi il faut utiliser les accélérateurs. Tout ce qu'on a à faire c'est d'associer les combinaisons de touches Ctrl + N, Ctrl + O, Ctrl + S et F12 aux commandes 1 (Nouveau), 2 (Ouvrir), 3 (Enregistrer) et 4 (Enregistrer sous). On peut faire cette association dynamiquement à l'aide de la fonction CreateAcceleratorTable mais c'est plus simple et plus vite de la créer en ressource.
#include <windows.h> /* pour VK_F12 (défini dans winuser.h) */

MyAccels ACCELERATORS
{
    "N",     1,  CONTROL,  VIRTKEY
    "O",     2,  CONTROL,  VIRTKEY
    "S",     3,  CONTROL,  VIRTKEY
    VK_F12,  4,            VIRTKEY
}
Le flag VIRTKEY spécifie que le code de la touche de l'accélérateur est un code de touche virtuelle et non le code d'un caractère ASCII. Si on omet ce flag, Windows suppose qu'il s'agit d'un code de caractère ASCII (donc "N" par exemple correspondra au caractère "N" majuscule, c'est-à-dire Shift + N, et non la touche N). Pour ce qui est des touches d'accompagnement, on peut utiliser n'importe quelle combinaison des flags CONTROL, ALT et SHIFT ou aucune.

Il faut ensuite charger la ressource.
HACCEL hAccel = LoadAccelerators(hInstance, "MyAccels");
Et enfin, adapter la boucle des messages.
while (GetMessage(&msg, NULL, 0, 0))
{
    if (!TranslateAccelerator(hWnd, hAccel, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
La fonction TranslateAccelerator examine un message et si ce dernier provient du clavier et correspond à une entrée dans la table des accélérateurs, il sera traité comme il se doit et la fonction retourne TRUE, sinon FALSE est retournée. Si la fonction réussit, le message a donc déjà été traité et il ne faut plus appeler ni Translate ni Dispatch Message.

 

Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Melem. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Contacter le responsable de la rubrique Windows