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

La programmation de l'interface utilisateur

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


I-A. Vue d'ensemble
I-A-1. Généralités
I-A-2. Les classes de fenêtre prédéfinies
I-A-3. La boucle des messages revisitée


I-A. Vue d'ensemble


I-A-1. Généralités

Nous avons déjà vu que la création d'une fenêtre se fait toujours à partir d'une classe de fenêtre existante. Sous Windows, le concept de fenêtre est assez large. En effet, une fenêtre peut désigner aussi bien une fenêtre (telle que nous l'entendons le plus souvent) qu'un contrôle (bouton, zone de texte, etc.) ou même tout et rien ! Il y a deux grandes catégories de contrôles : les contrôles standard, inhérents à Windows et les contrôles communs, spécifiques de chaque version, implémentés respectivement dans user32.dll et comctl32.dll.

Pour un contrôle, le style WS_CHILD est donc obligatoire. La plupart du temps on spécifie également le style WS_VISIBLE pour que le contrôle soit initialement visible. Et enfin, un contrôle n'a pas de menu, à la place on spécifie un entier (un cast est donc nécessaire ...) qui nous permettra de le référencer. Cet entier est appelé l'ID (identifier) du contrôle. Chaque fois qu'un contrôle ou un menu est titillé par l'utilisateur, celui-ci envoie le message WM_COMMAND à sa fenêtre parent et place :

  • son ID dans le mot de poids faible de wParam
  • son handle dans lParam
  • et l'événement qui s'est produit (notification) dans le mot de poids fort de wParam

En fait pour les contrôles communs, c'est le message WM_NOTIFY qui est envoyé à la place du message WM_COMMAND. La structure de ce message est la suivante :

  • Dans wParam : l'ID du contrôle ayant envoyé le message.
  • Dans lParam : l'adresse d'une structure de type NMHDR (pour Notification Message Header) fournissant des informations supplémentaires sur le message.

typedef struct tagNMHDR {
    HWND hwndFrom; /* Handle du contrôle ayant envoyé le message */
    UINT idFrom; /* ID du contrôle */
    UINT code; /* Raison de ce message */
} NMHDR;

La communication avec les contrôles se fait largement via les messages. La fonction SendMessage permet d'envoyer un message à une fenêtre quelconque (donc y compris les contrôles ...) tandis que SendDlgItemMessage n'est utilisable que pour les contrôles.
LRESULT SendMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT SendDlgItemMessage(HWND hwndParent, int nCtrlID, UINT message, WPARAM wParam, LPARAM lParam);
Mais sachez qu'il existe des fonctions et/ou macros qui encapsulent un ou plusieurs SendMessages dans le but d'améliorer la lisibilité du code. En général, les fonctions sont déclarées dans winuser.h (inclus dans windows.h) et les macros dans windowsx.h (qu'il faut inclure après windows.h).

Et pour terminer avec les généralités, sachez que connaissant l'ID d'un contrôle, on peut récupérer son handle et vice versa.
HWND GetDlgItem(HWND hwndParent, int nCtrlID);
int GetDlgCtrlID(HWND hCtrl);

I-A-2. Les classes de fenêtre prédéfinies

Les classes de fenêtre suivantes sont définies pour toutes les applications fenêtrées et peuvent donc être utilisées pour créer un contrôle (elles sont enregistrées avant même que WinMain ne soit appelée).

Nom Classe
"BUTTON" Bouton
"COMBOBOX" Zone de liste combinée
"EDIT" Contrôle permettant à l'utilisateur de saisir du texte
"LISTBOX" Zone de liste
"RichEdit" Contrôle permettant à l'utilisateur de saisir du texte enrichi
RICHEDIT_CLASS Amélioration de la classe RichEdit (plus sophistiqué ...)
"SCROLLBAR" Barre de défilement
"STATIC" Etiquette

Ce sont les contrôles standard (il ne s'agit pas d'une liste exhaustive).

Par exemple :
CreateWindow( "BUTTON",                   /* Classe : bouton           */
              "OK",                       /* Texte du bouton           */
              WS_CHILD | WS_VISIBLE,      /* Styles                    */
              0,                          /* x                         */
              0,                          /* y                         */
              100,                        /* Largeur                   */
              20,                         /* Hauteur                   */
              hWnd,                       /* Fenêtre parent            */
              (HMENU)1,                   /* ID du bouton : 1          */
              hInstance,                  /* Instance de l'application */
              NULL                        /* Paramètres additionnels   */
            );
Bien que l'on puisse créer le contrôle à n'importe quel moment, le moment idéal pour le faire est pendant la création de sa fenêtre parent c'est-à-dire pendant le traitement du message WM_CREATE. Autre remarque : il est rarement nécessaire de récupérer le handle du contrôle (retourné par CreateWindow) car en général, on travaille avec l'ID du contrôle plutôt qu'avec son handle (parce que c'est plus facile : ça évite de déclarer 50 variables pour 50 contrôles ...) et de toute façon, au cas où on aurait besoin de son handle, il y a toujours la fonction GetDlgItem. N'oubliez pas non plus que DestroyWindow détruit non seulement une fenêtre mais également toutes ses fenêtres enfants. Il n'est donc pas nécessaire de détruire manuellement les contrôles d'une fenêtre ni avant ni après que celle-ci ait été détruite, puisque cela se fait automatiquement.

Voici un exemple qui montre comment profiter du message WM_CREATE pour créer notre contrôle :
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCE hInstance;
    
    switch (message)
    {
    case WM_CREATE:        
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
        
        CreateWindow("BUTTON", "OK", WS_CHILD | WS_VISIBLE, 0, 0, 100, 24,
        hwnd, (HMENU)1, hInstance, NULL);
        
        break;
    
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    
    return 0;
}
Maintenant on va tester si notre bouton fonctionne bien. Pour cela on va émettre un beep en réponse à chaque clic. La fonction Beep reçoit en arguments la fréquence en Hz et la durée en ms du son à émettre. Elle très pratique pour déboguer.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCE hInstance;
    
    switch (message)
    {
    case WM_CREATE:        
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
        
        CreateWindow("BUTTON", "OK", WS_CHILD | WS_VISIBLE, 0, 0, 100, 24,
        hwnd, (HMENU)1, hInstance, NULL);
        
        break;
        
    case WM_COMMAND:
        /***************************************************\
        * LOWORD(wParam) = ID du contrôle ou du menu        *
        * HIWORD(wParam) = Raison du message (notification) *
        \***************************************************/
        
        switch(LOWORD(wParam))
        {
        case 1:
            switch(HIWORD(wParam))
            {
            case BN_CLICKED:
                Beep(1000, 100);
                break;
                
            default:
                break;
            }
            
            break;
            
        default:
            break;
        }
        
        break; /* case WM_COMMAND */
        
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    
    return 0;
}

I-A-3. La boucle des messages revisitée

Un contrôle peut être atteint par l'utilisation de la touche TAB s'il possède le style WS_TABSTOP. Ce comportement (de même que la notion de bouton par défaut) est en fait typique des boîtes de dialogue (qui sont bien entendu également des fenêtres ...). Pour autoriser un tel comportement dans une fenêtre « normale », il suffit de passer tous les messages à IsDialogMessage qui effectue un traitement adéquat si le message est un « Dialog Message ». Si la fonction retourne TRUE, ce qui signifie que le message était bien un Dialog Message (et donc déjà été convenablement traité), il ne faut plus faire un nouveau traitement. La boucle des messages devient donc :
while (GetMessage(&msg, NULL, 0, 0))
{
    if (!IsDialogMessage(hWnd, &msg))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
 

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