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-D. Personnalisation des contrôles
I-D-1. Choisir la brosse, la couleur de fond et la couleur du texte
I-D-2. Choisir la police des caractères
I-D-3. Dessiner soi-même ses contrôles
I-D-3-a. La théorie
I-D-3-b. Exemple : Un bouton personnalisé


I-D. Personnalisation des contrôles


I-D-1. Choisir la brosse, la couleur de fond et la couleur du texte

Au temps des versions 16 bits de Windows, le message WM_CTLCOLOR fut envoyé chaque fois qu'un contrôle est sur le point d'être dessiné. En traitant ce message, la fenêtre parent peut spécifier une brosse, une couleur de fond et une couler du texte à utiliser avec le contexte de périphérique du contrôle. Les paramètres de ce message sont :

  • Dans wParam : handle du contexte de périphérique du contrôle.
  • Dans lParam : handle du contrôle ayant envoyé le message.

Si l'application traite ce message, elle doit retourner le handle de la brosse à utiliser avec le contexte de périphérique du contrôle. Par contre on utilise tout simplement SetBkColor, SetBkMode et SetTextColor pour spécifier respectivement la couleur de fond, la transparence du fond et la couleur du texte.

Depuis les versions 32 bits, ce message a été remplacé par des messages plus spécifiques comme :

  • WM_CTLCOLORBTN (envoyé par un bouton)
  • WM_CTLCOLOREDIT (envoyé par un contrôle Edit)
  • WM_CTLCOLORLISTBOX (envoyé par une ListBox)
  • WM_CTLCOLORSTATIC (envoyé par un contrôle Static)
  • ...

Mais le principe est le même. A noter toutefois qu'un contrôle Edit, lorsqu'il est désactivé ou en lecture seule, envoie le message WM_CTLCOLORSTATIC à la place de WM_CTLCOLOREDIT (sachez également que le rectangle des cases à cocher et des boutons radio sont en fait de vrais contrôles Static). En outre, les boutons de commande n'utilisent pas la brosse retournée puisque leur apparence dépend de leur état (enfoncé / relâché). Pour personnaliser l'apparence des boutons de commande, il faudra utiliser les boutons personnalisés.


I-D-2. Choisir la police des caractères

Pour choisir la police à utiliser avec un contrôle, il suffit de lui envoyer le message WM_SETFONT avec le handle de la police dans WPARAM et MAKELPARAM(fRedraw, 0) dans lParam où fRedraw doit être égal à TRUE si on veut que le contrôle soit immédiatement redessiné dès qu'il aura reçu le message.


I-D-3. Dessiner soi-même ses contrôles


I-D-3-a. La théorie

La technique la plus puissante pour obtenir des contrôles très personnalisés c'est évidemment de les dessiner soi-même. Fondamentalement, cela se fait tout simplement en traitant le message WM_PAINT à l'intérieur de la procédure de fenêtre du contrôle. On peut spécifier une nouvelle procédure de fenêtre pour n'importe quelle fenêtre déjà créée en modifiant l'adresse indiquée dans sa "variable interne" GWL_WNDPROC à l'aide de la fonction SetWindowLong. En fait, SetWindowLong est une fonction obsolète car elle ne peut être utilisée que sur les plateformes 32 bits. Pour avoir du code compatible avec tous les processeurs (32 ou 64 bits), il faudra utiliser SetWindowLongPtr (et GWLP_WNDPROC).

En pratique, on n'aura pas qu'à traiter le message WM_PAINT pour dessiner le contrôle cela demande généralement trop de détails à prendre en charge. De plus, un contrôle doit généralement changer d'aspect en fonction de son état. Par exemple, pour un bouton, les états possibles sont normal, survolé par la souris, enfoncé, etc. Or les paramètres qui accompagnent le message WM_PAINT ne permettent pas de connaître dans quel état est actuellement le contrôle. C'est pourquoi, lorsqu'on veut dessiner soi-même ses contrôles, il est généralement préférable d'utiliser les « owner-draw controls ».

Chaque fois qu'un owner-draw control doit être dessiné ou redessiné, Windows envoie à sa fenêtre parent le message WM_DRAWITEM. Si le message provient d'un contrôle, alors le paramètre wParam contient l'ID de ce contrôle sinon zéro. lParam quant à lui contient toujours l'adresse d'une structure de type DRAWITEMSTRUCT contenant les informations nécessaires permettant d'effectuer un traitement convenable.
typedef struct tagDRAWITEMSTRUCT {
    UINT CtlType;
    UINT CtlID;
    UINT itemID;
    UINT itemAction;
    UINT itemState;
    HWND hwndItem;
    HDC hDC;
    RECT rcItem;
    ULONG_PTR itemData;
} DRAWITEMSTRUCT;
La description complète de cette structure peut être évidemment trouvée dans l'aide de MSDN (elle n'est d'ailleurs pas compliquée), mais ce qu'il faut au moins retenir ce sont les champs CtlID (ID du contrôle), hDC (handle du DC à utiliser pour dessiner le contrôle) et rcItem (le rectangle qui décrit le contour du contrôle). Le champ itemState a également son importance car il spécifie l'état actuel du contrôle. Sa valeur peut être une ou une combinaison de constantes (indépendantes) définies dans winuser.h parmi lesquels ODS_SELECTED (l'élément est selectionné c'est-à-dire maintenu enfoncé), ODS_DISABLED (l'élément a été désactivé) et ODS_FOCUS (l'élément a le focus clavier).


I-D-3-b. Exemple : Un bouton personnalisé

Un owner-draw button (bouton personnalisé) est un bouton possédant le style BS_OWNERDRAW. Ce style ne peut être mélangé avec les autres styles de bouton (BS_XXX). Pour dessiner le bouton, il n'est généralement pas nécessaire de traiter le message WM_PAINT. Par contre, il est nécessaire d'ajouter un traitement sur réception des messages WM_MOUSEMOVE et WM_MOUSELEAVE pour le faire changer d'aspect respectivement quand la souris vient le survoler et quand la souris le quitte. Le dessin ne se fera cependant que dans le traitement du message WM_DRAWITEM, c'est-à-dire dans la procédure de fenêtre de la fenêtre parent.

A noter que WM_MOUSELEAVE n'est pas envoyé automatiquement. Pour recevoir ce message, il faut avoir préalablement appelé la fonction TrackMouseEvent qui n'existe que depuis Windows 2000.

Voici un exemple de création et d'utilisation d'un owner-draw button.
#define _WIN32_WINNT 0x0500 /* TrackMouseEvent n'existe que depuis Windows 2000 (Windows NT 5.0). */

#include <windows.h>

/* La structure MYAPP regroupe les variables globales utilisées par notre application. */

typedef struct _MYAPP {
    HWND hCtl; /* Handle du contrôle actuellement survolé par la souris. */
} MYAPP;

MYAPP MyApp;
WNDPROC DefButtonProc; /* "Pointeur" vers l'ancienne procédure de fenêtre de notre bouton. */

#define BUTTON_WIDTH 100
#define BUTTON_HEIGHT 24
#define BUTTON_TEXT "QUIT"

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnCreate(HWND hwnd);
LRESULT CALLBACK ButtonProc(HWND hButton, UINT message, WPARAM wParam, LPARAM lParam);
void OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis);
void OnCommand(HWND hwnd, int CtrlID, int Event);

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  = CreateSolidBrush(RGB(0x99, 0xCC, 0xFF));
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance      = hInstance;
    wc.lpfnWndProc    = WndProc;
    wc.lpszClassName  = "Classe 1";
    wc.lpszMenuName   = NULL;
    wc.style          = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&wc);

    hWnd = CreateWindow( "Classe 1", "Owner-Draw Button",
        WS_POPUP |WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
        100, 100, 200, 100, 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)
{
    switch(message)
    {
    case WM_CREATE:
        OnCreate(hwnd);
        break;

    case WM_DRAWITEM:
        OnDrawItem(hwnd, (LPDRAWITEMSTRUCT)lParam);
        break;

    case WM_COMMAND:
        OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam));
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

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

    return 0L;
}

void OnCreate(HWND hwnd)
{
    RECT r;
    int x, y;
    HWND hButton;

    /* Centrer le bouton. */
    
    GetClientRect(hwnd, &r);
    x = (r.right - BUTTON_WIDTH) / 2;
    y = (r.bottom - BUTTON_HEIGHT) / 2;

    hButton = CreateWindow( "BUTTON", "", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
        x, y, BUTTON_WIDTH, BUTTON_HEIGHT, hwnd, (HMENU)1, NULL, NULL
    );

    /* On modifie la procédure de fenêtre du bouton. */

    DefButtonProc = (WNDPROC)SetWindowLongPtr(hButton, GWLP_WNDPROC, (LONG_PTR)ButtonProc);
}

LRESULT CALLBACK ButtonProc(HWND hButton, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT ret = 0L;

    /* A la fin du traitement d'un message, il reste parfois nécessaire d'appeler l'ancienne  */
    /* procédure de fenêtre. Il n'est cependant pas garanti que DefButtonProc soit réellement */
    /* un pointeur de fonction. Il faut appeler CallWindowProc pour utiliser DefButtonProc.   */

    switch(message)
    {
    case WM_MOUSEMOVE: /* Quand la souris survole le bouton ... */

        if (MyApp.hCtl != hButton)
        {
            TRACKMOUSEEVENT tme;

            MyApp.hCtl = hButton; /* Le contrôle actuellement survolé par la souris est hButton. */

            /* Générer WM_MOUSELEAVE quand la souris quitte le bouton. */

            tme.cbSize = sizeof(tme);
            tme.dwFlags = TME_LEAVE;
            tme.hwndTrack = hButton;
            TrackMouseEvent(&tme);

            InvalidateRect(hButton, NULL, TRUE); /* Redessiner le bouton. */
        }

        ret = CallWindowProc(DefButtonProc, hButton, message, wParam, lParam);

        break;

    case WM_MOUSELEAVE: /* Quand la souris quitte le bouton ... */

        MyApp.hCtl = NULL; /* Aucun contrôle est actuellement survolé par la souris. */

        InvalidateRect(hButton, NULL, TRUE); /* Redessiner le bouton. */

        ret = CallWindowProc(DefButtonProc, hButton, message, wParam, lParam);

        break;

    default:

        ret = CallWindowProc(DefButtonProc, hButton, message, wParam, lParam);
    }

    return ret;
}

void OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis)
{
    if (lpdis->CtlID == 1) /* 1 est l'ID de notre bouton. */
    {
        HDC hDC = lpdis->hDC;
        LPRECT lprcItem = &lpdis->rcItem;
        COLORREF Color;
        HBRUSH hBrush;

        if (lpdis->itemState & ODS_SELECTED) /* Le bouton est pressé. */
            Color = RGB(0x33, 0x33, 0xCC);
        else if (MyApp.hCtl == lpdis->hwndItem) /* Le bouton est survolé par la souris. */
            Color = RGB(0x66, 0x99, 0xFF);
        else /* Le bouton est dans son état normal. */
            Color = RGB(0x66, 0xCC, 0xFF);

        hBrush = CreateSolidBrush(Color);
    
        SelectObject(hDC, hBrush);
        SetBkMode(hDC, TRANSPARENT);
        Rectangle(hDC, lprcItem->left, lprcItem->top, lprcItem->right, lprcItem->bottom);
        DrawText(hDC, BUTTON_TEXT, (int)strlen(BUTTON_TEXT), lprcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        
        DeleteObject(hBrush);
    }
}

void OnCommand(HWND hwnd, int CtrlID, int Event)
{
    if (CtrlID == 1 && Event == BN_CLICKED)
        SendMessage(hwnd, WM_CLOSE, 0, 0);
}
 

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.