二、Windows控件与颜色—拥有者绘制按钮
如果您想对按钮的所有可见部分实行全面控制,而不想被键盘和鼠标消息处理所干扰,那么您可以建立BS_OWNERDRAW样式的按钮,如程序9-2所展示的那样。
OWNDRAW.C
/*------------------------------------------------------------------------
OWNDRAW.C -- Owner-Draw Button Demo Program
(c) Charles Petzold, 1996
-------------------------------------------------------------------------*/
#include <windows.h>
#define ID_SMALLER 1
#define ID_LARGER 2
#define BTN_WIDTH ( 8 * cxChar)
#define BTN_HEIGHT ( 4 * cyChar)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("OwnDraw") ;
MSG msg ;
HWND hwnd ;
WNDCLASS wndclass ;
hInst = hInstance ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Owner-Draw Button Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
void Triangle (HDC hdc, POINT pt[])
{
SelectObject (hdc, GetStockObject (BLACK_BRUSH)) ;
Polygon (hdc, pt, 3) ;
SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static HWND hwndSmaller, hwndLarger ;
static int cxClient, cyClient, cxChar, cyChar ;
int cx, cy ;
LPDRAWITEMSTRUCT pdis ;
POINT pt[3] ;
RECT rc ;
switch (message)
{
case WM_CREATE :
cxChar = LOWORD (GetDialogBaseUnits ()) ;
cyChar = HIWORD (GetDialogBaseUnits ()) ;
// Create the owner-draw pushbuttons
hwndSmaller = CreateWindow (TEXT ("button"), TEXT (""),
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
0, 0, BTN_WIDTH, BTN_HEIGHT,
hwnd, (HMENU) ID_SMALLER, hInst, NULL) ;
hwndLarger = CreateWindow (TEXT ("button"), TEXT (""),
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
0, 0, BTN_WIDTH, BTN_HEIGHT,
hwnd, (HMENU) ID_LARGER, hInst, NULL) ;
return 0 ;
case WM_SIZE :
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// Move the buttons to the new center
MoveWindow ( hwndSmaller, cxClient / 2 - 3 * BTN_WIDTH / 2,
cyClient / 2 - BTN_HEIGHT / 2,
BTN_WIDTH, BTN_HEIGHT, TRUE) ;
MoveWindow ( hwndLarger, cxClient / 2 + BTN_WIDTH / 2,cyClient / 2 - BTN_HEIGHT / 2,
BTN_WIDTH, BTN_HEIGHT, TRUE) ;
return 0 ;
case WM_COMMAND :
GetWindowRect (hwnd, &rc) ;
// Make the window 10% smaller or larger
switch (wParam)
{
case ID_SMALLER :
rc.left += cxClient / 20 ;
rc.right -= cxClient / 20 ;
rc.top += cyClient / 20 ;
rc.bottom -= cyClient / 20 ;
break ;
case ID_LARGER :
rc.left -= cxClient / 20 ;
rc.right += cxClient / 20 ;
rc.top -= cyClient / 20 ;
rc.bottom += cyClient / 20 ;
break ;
}
MoveWindow ( hwnd, rc.left, rc.top, rc.right - rc.left,
rc.bottom - rc.top, TRUE) ;
return 0 ;
case WM_DRAWITEM :
pdis = (LPDRAWITEMSTRUCT) lParam ;
// Fill area with white and frame it black
FillRect (pdis->hDC, &pdis->rcItem,
(HBRUSH) GetStockObject (WHITE_BRUSH)) ;
FrameRect ( pdis->hDC, &pdis->rcItem,
( HBRUSH) GetStockObject (BLACK_BRUSH)) ;
// Draw inward and outward black triangles
cx = pdis->rcItem.right - pdis->rcItem.left ;
cy = pdis->rcItem.bottom - pdis->rcItem.top ;
switch (pdis->CtlID)
{
case ID_SMALLER :
pt[0].x = 3 * cx / 8 ; pt[0].y = 1 * cy / 8 ;
pt[1].x = 5 * cx / 8 ; pt[1].y = 1 * cy / 8 ;
pt[2].x = 4 * cx / 8 ; pt[2].y = 3 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 7 * cx / 8 ; pt[0].y = 3 * cy / 8 ;
pt[1].x = 7 * cx / 8 ; pt[1].y = 5 * cy / 8 ;
pt[2].x = 5 * cx / 8 ; pt[2].y = 4 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 5 * cx / 8 ; pt[0].y = 7 * cy / 8 ;
pt[1].x = 3 * cx / 8 ; pt[1].y = 7 * cy / 8 ;
pt[2].x = 4 * cx / 8 ; pt[2].y = 5 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 1 * cx / 8 ; pt[0].y = 5 * cy / 8 ;
pt[1].x = 1 * cx / 8 ; pt[1].y = 3 * cy / 8 ;
pt[2].x = 3 * cx / 8 ; pt[2].y = 4 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
break ;
case ID_LARGER :
pt[0].x = 5 * cx / 8 ; pt[0].y = 3 * cy / 8 ;
pt[1].x = 3 * cx / 8 ; pt[1].y = 3 * cy / 8 ;
pt[2].x = 4 * cx / 8 ; pt[2].y = 1 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 5 * cx / 8 ; pt[0].y = 5 * cy / 8 ;
pt[1].x = 5 * cx / 8 ; pt[1].y = 3 * cy / 8 ;
pt[2].x = 7 * cx / 8 ; pt[2].y = 4 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 3 * cx / 8 ; pt[0].y = 5 * cy / 8 ;
pt[1].x = 5 * cx / 8 ; pt[1].y = 5 * cy / 8 ;
pt[2].x = 4 * cx / 8 ; pt[2].y = 7 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
pt[0].x = 3 * cx / 8 ; pt[0].y = 3 * cy / 8 ;
pt[1].x = 3 * cx / 8 ; pt[1].y = 5 * cy / 8 ;
pt[2].x = 1 * cx / 8 ; pt[2].y = 4 * cy / 8 ;
Triangle (pdis->hDC, pt) ;
break ;
}
// Invert the rectangle if the button is selected
if (pdis->itemState & ODS_SELECTED)
InvertRect (pdis->hDC, &pdis->rcItem) ;
// Draw a focus rectangle if the button has the focus
if (pdis->itemState & ODS_FOCUS)
{
pdis->rcItem.left += cx / 16 ;
pdis->rcItem.top += cy / 16 ;
pdis->rcItem.right -= cx / 16 ;
pdis->rcItem.bottom-= cy / 16 ;
DrawFocusRect (pdis->hDC, &pdis->rcItem) ;
}
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
该程序在其显示区域的中央包含了两个按钮,如图9-2所示。左边的按钮有四个三角形指向按钮的中央,按下该按钮时,窗口的尺寸将缩小10%。右边的按钮有四个向外指的三角形,按下此按钮时,窗口的尺寸将增大10%。
如果您只需要在按钮中显示图标或位图,您可以用BS_ICON或BS_BITMAP样式,并用BM_SETIMAGE消息设定位图。但是,对于BS_OWNERDRAW样式的按钮,它允许完全自由地绘制按钮。

在处理WM_CREATE消息处理期间,OWNDRAW建立了两个BS_OWNERDRAW样式的按钮;按钮的宽度是系统字体的8倍,高度是系统字体的4倍(在使用预先定义好的位图绘制按钮时,这些尺寸在VGA上建立的按钮为64图素宽64图素高,知道这些数据将非常有用)。这些按钮尚未就定位,在处理WM_SIZE消息处理期间,通过呼叫MoveWindow函数,OWNDRAW将按钮位置放在显示区域的中心。
按下这些按钮时,它们就会产生WM_COMMAND消息。为了处理这些WM_COMMAND消息,OWNDRAW呼叫GetWindowRect,将整个窗口(不只是显示区域)的位置和尺寸存放在RECT(矩形)结构中,这个位置是相对于屏幕的。然后,根据按下的是左边还是右边的按钮,OWNDRAW调节这个矩形结构的各个字段值。程序再通过呼叫MoveWindow来重新确定位置和尺寸。这将产生另一个WM_SIZE消息,按钮被重新定位在显示区域的中央。
如果这是程序所做的全部处理,那么这完全可以,只不过按钮是不可见的。使用BS_OWNERDRAW样式建立的按钮会在需要重新着色的任何时候都向它的父窗口发送一个WM_DRAWITEM消息。这出现在以下几种情况中:当按钮被建立时,当按钮被按下或被放开时,当按钮得到或者失去输入焦点时,以及当按钮需要重新着色的任何时候。
在处理WM_DRAWITEM消息处理期间,lParam消息参数是指向型态DRAWITEMSTRUCT结构的指针,OWNDRAW程序将这个指针储存在pdis变量中,这个结构包含了画该按钮时程序所必需的消息(这个结构也可以让自绘清单方块和菜单使用)。对按钮而言非常重要的结构字段有hDC (按钮的设备内容)、rcItem(提供按钮尺寸的RECT结构)、CtlID(控件窗口ID)和itemState (它说明按钮是否被按下,或者按钮是否拥有输入焦点)。
呼叫FillRect用白色画刷抹掉按钮的内面,呼叫FrameRect在按钮的周围画上黑框,由此OWNDRAW便启动了WM_DRAWITEM处理过程。然后,通过呼叫Polygon,OWNDRAW在按钮上画出4个黑色实心的三角形。这是一般的情形。
如果按钮目前被按下,那么DRAWITEMSTRUCT的itemState字段中的某位将被设为1。您可以使用ODS_SELECTED常数来测试这些位。如果这些位被设立,那么OWNDRAW将通过呼叫InvertRect将按钮翻转为相反的颜色。如果按钮拥有输入焦点,那么itemState的ODS_FOCUS位将被设立。在这种情况下,OWNDRAW通过呼叫DrawFocusRect,在按钮的边界内画一个虚线的矩形。
在使用拥有者绘制按钮时,应该注意以下几个方面:Windows获得设备内容并将其作为DRAWITEMSTRUCT结构的一个字段。保持设备内容处于您找到它时所处的状态,任何被选进设备内容的GDI对象都必需被释放。另外,当心不要在定义按钮边界的矩形外面进行绘制。
