OwnerDraw Button(1)
通过所有者绘制实现按钮绘制完全自行处理绘制的过程称为所有者绘制。
所有者绘制 即使不使用所有者绘制,也可以很容易地更改文字字体。对于静态控件和复选框等,可以通过处理WM_CTLCOLORSTATIC消息,相对容易地更改文字颜色和背景色。相比之下,对于推送按钮,更改文字颜色和背景色(按钮颜色)并不容易。这就需要使用所有者绘制或控件的子类化。 本页将介绍通过所有者绘制的方法。下面展示了推送按钮的所有者绘制程序。 button01.c
#include
HWND hWnd, hBtn;
HFONT hFont;
void onDrawItem(WPARAM wP, LPARAM lP) {
DRAWITEMSTRUCT *pDIS = (DRAWITEMSTRUCT*)lP;
if (pDIS->hwndItem == hBtn) {
char buf[256];
if (pDIS->itemState & ODS_SELECTED)
DrawEdge(pDIS->hDC, &pDIS->rcItem, EDGE_SUNKEN, BF_RECT);
else
DrawEdge(pDIS->hDC, &pDIS->rcItem, EDGE_RAISED, BF_RECT);
if (pDIS->itemState & ODS_FOCUS) {
InflateRect(&pDIS->rcItem, -4, -4);
DrawFocusRect(pDIS->hDC, &pDIS->rcItem);
}
SetTextColor(pDIS->hDC, 0xff);
GetWindowText(pDIS->hwndItem, buf, sizeof(buf));
DrawText(pDIS->hDC, buf, -1, &pDIS->rcItem, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
}
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wP, LPARAM lP) {
switch (uMsg) {
case WM_DRAWITEM: onDrawItem(wP, lP); return TRUE;
case WM_DESTROY: PostQuitMessage(0); return 0;
}
return DefWindowProc(hwnd, uMsg, wP, lP);
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPInst, LPSTR lpCmdLine, int nCmdShow) {
MSG msg;
LOGFONT lf = { 16 }; // lfHeight
WNDCLASS wc = { 0, WindowProc, 0, 0, hInst, NULL, LoadCursor(NULL,IDC_ARROW),
(HBRUSH)(COLOR_WINDOW+1), NULL, "mh" };
if (!RegisterClass(&wc)) return FALSE;
hWnd = CreateWindowEx(0, "mh", "Button", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
10, 10, 200, 80, NULL, NULL, hInst, NULL);
if(!hWnd) return FALSE;
hFont = CreateFontIndirect(&lf); // 创建逻辑字体
hBtn = CreateWindowEx(0, "BUTTON", "ABC", WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
20, 10, 70, 30, hWnd, (HMENU)1, hInst, NULL);
SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, 0);
while (GetMessage(&msg,NULL,0,0) > 0){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
所有者绘制的步骤如下:
首先,在使用CreateWindowEx函数创建推送按钮时,要在样式中添加BS_OWNERDRAW。
添加BS_OWNERDRAW后,在绘制按钮之前,父窗口会收到WM_DRAWITEM消息。WindowProc函数会根据存储在DRAWITEMSTRUCT结构体中的信息来进行按钮的绘制。 下面展示了DRAWITEMSTRUCT结构体的内容。
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType;
UINT CtlID;
UINT itemID;
UINT itemAction;
UINT itemState;
HWND hwndItem;
HDC hDC;
RECT rcItem;
DWORD itemData;
} DRAWITEMSTRUCT;
在所有者绘制中,还必须自行绘制按钮本身。onDrawItem函数的大部分用于按钮的绘制。 SetTextColor
(pDIS->hDC, 0xff);
设置文字颜色为红色。 GetWindowText
(pDIS->hwndItem, buf, sizeof(buf));
读取要在按钮上显示的字符串(在这个例子中是"ABC"),并将其设置到buf中。 DrawText
(pDIS->hDC, buf, -1, &pDIS->rcItem, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
将字符串(在这个例子中是"ABC")绘制在按钮的客户区域中央。 尽管与所有者绘制相关的程序行数本身大约只有20行,但必须自行完成按钮的绘制可能会让人感到困扰。
OwnerDraw Button(2)
自己编写程序来绘制按钮 不是将按钮作为子窗口,而是直接在客户端窗口上绘制。
绘制按钮 通过使用所有者绘制或子类化,可以根据喜好自定义按钮的外观和行为。但是,这样做通常会使程序的代码行数增加。下面的程序没有使用任何按钮控件或子窗口,而是直接在客户端窗口上绘制按钮。 当鼠标光标移动到按钮上时,会将文字变成红色。因为没有子窗口,所以始终可以轻松知道光标的位置。如果按钮是子窗口,鼠标光标的信息会被发送到子窗口,因此需要进行子类化或鼠标捕获等相当繁琐的处理。 例如,如果将表编辑器的表选择标签设置为这种自制的按钮,与使用所有者绘制或子类化相比,代码行数可能相同,而程序可能会更简单。
button03.c
#include
BOOL fSelected, fHover;
RECT rcBtn = { 50, 10, 150, 50 };
BOOL isInside(RECT *prc, int x, int y) {
return !(x < prc->left || x > prc->right || y < prc->top || y > prc->bottom);
}
void drawButton(HDC hdc, RECT *prc, UINT flag, char *text) {
DrawEdge(hdc, prc, fSelected ? EDGE_SUNKEN : EDGE_RAISED, flag);
SetTextColor(hdc, (fHover && !fSelected) ? 0xff : 0);
DrawText(hdc, text, -1, prc, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
}
void onPaint(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
drawButton(hdc, &rcBtn, BF_RECT|BF_SOFT, "Test");
EndPaint(hwnd, &ps);
}
void onMouseMove(HWND hwnd, LPARAM lp) {
HDC hdc = GetDC(hwnd);
fHover = isInside(&rcBtn, lp&0xffff, lp>>16);
drawButton(hdc, &rcBtn, BF_RECT|BF_SOFT, "Test");
ReleaseDC(hwnd, hdc);
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp) {
switch (uMsg) {
case WM_PAINT:
onPaint(hwnd);
return 0;
case WM_LBUTTONDOWN:
fSelected = isInside(&rcBtn, lp&0xffff, lp>>16);
if (fSelected) InvalidateRect(hwnd, NULL, TRUE);
return 0;
case WM_LBUTTONUP:
if (fSelected) InvalidateRect(hwnd, NULL, TRUE);
fSelected = FALSE;
return 0;
case WM_MOUSEMOVE:
onMouseMove(hwnd, lp);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wp, lp);
}
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPInst, LPSTR lpCmdLine, int nCmdShow) {
MSG msg;
WNDCLASS wc = { 0, WindowProc, 0, 0, hInst, NULL, LoadCursor(NULL,IDC_ARROW),
(HBRUSH)(COLOR_WINDOW+1), NULL, "Demo" };
if (!RegisterClass(&wc)) return FALSE;
if (!CreateWindowEx(0, "mh", "Button", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
10, 10, 200, 100, NULL, NULL, hInst, NULL)) return FALSE;
while (GetMessage(&msg,NULL,0,0) > 0){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}