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
);
}