似乎程序员和用户都比较喜欢按钮进行接口交互.似乎WINDOWS也热衷于此,所以自打WINDOWS图形界面产生那天以来,按钮类型好像多于其他任何子控件.
按钮的控件风格真多,在DELPHI表现的就有,Button,CheckBox,RadioBox,BitBtn等.
WINDOWS定义了十二种类型风格的按钮:
#define MAX_BTN_TYPE 12
static const WORD maxCheckState[MAX_BTN_TYPE] =
{
BUTTON_UNCHECKED, /* BS_PUSHBUTTON */
BUTTON_UNCHECKED, /* BS_DEFPUSHBUTTON */
BUTTON_CHECKED, /* BS_CHECKBOX */
BUTTON_CHECKED, /* BS_AUTOCHECKBOX */
BUTTON_CHECKED, /* BS_RADIOBUTTON */
BUTTON_3STATE, /* BS_3STATE */
BUTTON_3STATE, /* BS_AUTO3STATE */
BUTTON_UNCHECKED, /* BS_GROUPBOX */
BUTTON_UNCHECKED, /* BS_USERBUTTON */
BUTTON_CHECKED, /* BS_AUTORADIOBUTTON */
BUTTON_UNCHECKED, /* Not defined */ //除开这种共有十一种风格
BUTTON_UNCHECKED /* BS_OWNERDRAW */
};
按钮类信息:
/*********************************************************************
* button class descriptor
*/
static const WCHAR buttonW[] = {'B','u','t','t','o','n',0};
const struct builtin_class_descr BUTTON_builtin_class =
{
buttonW, /* name */
CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC, /* style */ 类风格
ButtonWndProcA, /* procA */
ButtonWndProcW, /* procW */
NB_EXTRA_BYTES, /* extra */ //窗口扩展数据
IDC_ARROW, /* cursor */
0 /* brush */
};
因为按钮风格共有十一种,但因为他毕竟是单窗口控件,所以应该还是较简单的一控件,理解他的关键是很多细节,比如什么时候SetCapture,他的窗口过程函数也是主要放在各种风格的处理,下面看下他的窗口过程.
static LRESULT ButtonWndProc_common(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL unicode )
{
RECT rect;
POINT pt;
LONG style = GetWindowLongPtrW( hWnd, GWL_STYLE );
UINT btn_type = get_button_type( style );
LONG state;
HANDLE oldHbitmap;
pt.x = (short)LOWORD(lParam);
pt.y = (short)HIWORD(lParam);
switch (uMsg)
{
case WM_GETDLGCODE: //控件控制代码
switch(btn_type)
{
case BS_USERBUTTON:
case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
case BS_RADIOBUTTON:
case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
case BS_GROUPBOX: return DLGC_STATIC;
default: return DLGC_BUTTON;
}
case WM_ENABLE: //激活
paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
break;
case WM_CREATE: //初始化
if (!hbitmapCheckBoxes)
{
BITMAP bmp;
hbitmapCheckBoxes = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_CHECKBOXES));
GetObjectW( hbitmapCheckBoxes, sizeof(bmp), &bmp );
checkBoxWidth = bmp.bmWidth / 4;
checkBoxHeight = bmp.bmHeight / 3;
}
if (btn_type >= MAX_BTN_TYPE)
return -1; /* abort */
set_button_state( hWnd, BUTTON_UNCHECKED ); //设置按钮的状态,未选中
button_update_uistate( hWnd, unicode );
return 0;
case WM_ERASEBKGND:
if (btn_type == BS_OWNERDRAW) //如果是自画方式的话才会调用FillRect擦除背景
{
HDC hdc = (HDC)wParam;
RECT rc;
HBRUSH hBrush;
HWND parent = GetParent(hWnd);
if (!parent) parent = hWnd;
hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
if (!hBrush) /* did the app forget to call defwindowproc ? */
hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
(WPARAM)hdc, (LPARAM)hWnd);
GetClientRect(hWnd, &rc);
FillRect(hdc, &rc, hBrush);
}
return 1;
case WM_PRINTCLIENT:
case WM_PAINT:
if (btnPaintFunc[btn_type])
{
PAINTSTRUCT ps;
HDC hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
int nOldMode = SetBkMode( hdc, OPAQUE ); //强制设置不透明
(btnPaintFunc[btn_type])( hWnd, hdc, ODA_DRAWENTIRE );
SetBkMode(hdc, nOldMode); /* reset painting mode */
if( !wParam ) EndPaint( hWnd, &ps );
}
break;
case WM_KEYDOWN:
if (wParam == VK_SPACE)
{
SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
SetCapture( hWnd ); //可以看到KEYDOWN,当前还是拥有鼠标
}
break;
case WM_LBUTTONDBLCLK:
if(style & BS_NOTIFY ||
btn_type == BS_RADIOBUTTON ||
btn_type == BS_USERBUTTON ||
btn_type == BS_OWNERDRAW)
{
BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED); //双击通知父窗体
break;
}
/* fall through */
case WM_LBUTTONDOWN:
SetCapture( hWnd ); //拥有鼠标
SetFocus( hWnd );
set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED ); //设置按下状态
SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
break;
case WM_KEYUP:
if (wParam != VK_SPACE)
break;
/* fall through */
case WM_LBUTTONUP:
state = get_button_state( hWnd );
if (!(state & BUTTON_BTNPRESSED)) break;
state &= BUTTON_NSTATES;
set_button_state( hWnd, state );
if (!(state & BUTTON_HIGHLIGHTED))
{
ReleaseCapture();
break;
}
SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
ReleaseCapture(); //释放鼠标
GetClientRect( hWnd, &rect );
if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
{
state = get_button_state( hWnd );
switch(btn_type)
{
case BS_AUTOCHECKBOX:
SendMessageW( hWnd, BM_SETCHECK, !(state & BUTTON_CHECKED), 0 );
break;
case BS_AUTORADIOBUTTON:
SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
break;
case BS_AUTO3STATE:
SendMessageW( hWnd, BM_SETCHECK,
(state & BUTTON_3STATE) ? 0 : ((state & 3) + 1), 0 );
break;
}
BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED); //鼠标弹起,也会通知父窗体
}
break;
case WM_CAPTURECHANGED:
TRACE("WM_CAPTURECHANGED %p\n", hWnd);
state = get_button_state( hWnd );
if (state & BUTTON_BTNPRESSED)
{
state &= BUTTON_NSTATES;
set_button_state( hWnd, state );
if (state & BUTTON_HIGHLIGHTED) SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
}
break;
case WM_MOUSEMOVE:
if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
{
GetClientRect( hWnd, &rect );
SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
}
break;
case WM_SETTEXT:
{
/* Clear an old text here as Windows does */
HDC hdc = GetDC(hWnd);
HBRUSH hbrush;
RECT client, rc;
HWND parent = GetParent(hWnd);
if (!parent) parent = hWnd;
hbrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC,
(WPARAM)hdc, (LPARAM)hWnd);
if (!hbrush) /* did the app forget to call DefWindowProc ? */
hbrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
(WPARAM)hdc, (LPARAM)hWnd);
GetClientRect(hWnd, &client);
rc = client;
BUTTON_CalcLabelRect(hWnd, hdc, &rc);
/* Clip by client rect bounds */
if (rc.right > client.right) rc.right = client.right;
if (rc.bottom > client.bottom) rc.bottom = client.bottom;
FillRect(hdc, &rc, hbrush);
ReleaseDC(hWnd, hdc);
if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
InvalidateRect( hWnd, NULL, TRUE );
else
paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
return 1; /* success. FIXME: check text length */
}
case WM_SETFONT:
set_button_font( hWnd, (HFONT)wParam );
if (lParam) InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_GETFONT:
return (LRESULT)get_button_font( hWnd );
case WM_SETFOCUS:
TRACE("WM_SETFOCUS %p\n",hWnd);
set_button_state( hWnd, get_button_state(hWnd) | BUTTON_HASFOCUS );
paint_button( hWnd, btn_type, ODA_FOCUS );
if (style & BS_NOTIFY)
BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
break;
case WM_KILLFOCUS:
TRACE("WM_KILLFOCUS %p\n",hWnd);
state = get_button_state( hWnd );
set_button_state( hWnd, state & ~BUTTON_HASFOCUS );
paint_button( hWnd, btn_type, ODA_FOCUS );
if ((state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
ReleaseCapture();
if (style & BS_NOTIFY)
BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
break;
case WM_SYSCOLORCHANGE:
InvalidateRect( hWnd, NULL, FALSE ); //改变系统颜色时重画
break;
case BM_SETSTYLE: //动态设置按钮风格
if ((wParam & 0x0f) >= MAX_BTN_TYPE) break;
btn_type = wParam & 0x0f;
style = (style & ~0x0f) | btn_type;
SetWindowLongPtrW( hWnd, GWL_STYLE, style );
/* Only redraw if lParam flag is set.*/
if (lParam)
paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
break;
//以下是按钮私有消息了
case BM_CLICK: //单击
SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 ); //简单发送一个鼠标按下和弹起消息
SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
break;
case BM_SETIMAGE:
/* Check that image format matches button style */
switch (style & (BS_BITMAP|BS_ICON)) //设置图像,只有BS_BITMAP|BS_ICON风格才有效
{
case BS_BITMAP:
if (wParam != IMAGE_BITMAP) return 0;
break;
case BS_ICON:
if (wParam != IMAGE_ICON) return 0;
break;
default:
return 0;
}
oldHbitmap = (HBITMAP)SetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET, lParam );
InvalidateRect( hWnd, NULL, FALSE );
return (LRESULT)oldHbitmap;
case BM_GETIMAGE:
return GetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET );
case BM_GETCHECK: //是否选中
return get_button_state( hWnd ) & 3;
case BM_SETCHECK: //选中
if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
state = get_button_state( hWnd );
if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
{
if (wParam) style |= WS_TABSTOP;
else style &= ~WS_TABSTOP;
SetWindowLongPtrW( hWnd, GWL_STYLE, style );
}
if ((state & 3) != wParam)
{
set_button_state( hWnd, (state & ~3) | wParam );
paint_button( hWnd, btn_type, ODA_SELECT );
}
if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BUTTON_CHECKED) && (style & WS_CHILD))
BUTTON_CheckAutoRadioButton( hWnd );
break;
case BM_GETSTATE:
return get_button_state( hWnd ); //取得状态
case BM_SETSTATE:
state = get_button_state( hWnd );
if (wParam)
{
if (state & BUTTON_HIGHLIGHTED) break;
set_button_state( hWnd, state | BUTTON_HIGHLIGHTED );
}
else
{
if (!(state & BUTTON_HIGHLIGHTED)) break;
set_button_state( hWnd, state & ~BUTTON_HIGHLIGHTED );
}
paint_button( hWnd, btn_type, ODA_SELECT );
break;
case WM_UPDATEUISTATE: //更新UI
if (unicode)
DefWindowProcW(hWnd, uMsg, wParam, lParam);
else
DefWindowProcA(hWnd, uMsg, wParam, lParam);
if (button_update_uistate(hWnd, unicode))
paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
break;
case WM_NCHITTEST:
if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
/* fall through */
default:
return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
DefWindowProcA(hWnd, uMsg, wParam, lParam);
}
return 0;
}
按钮画图函数:
static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
{
PB_Paint, /* BS_PUSHBUTTON */
PB_Paint, /* BS_DEFPUSHBUTTON */
CB_Paint, /* BS_CHECKBOX */
CB_Paint, /* BS_AUTOCHECKBOX */
CB_Paint, /* BS_RADIOBUTTON */
CB_Paint, /* BS_3STATE */
CB_Paint, /* BS_AUTO3STATE */
GB_Paint, /* BS_GROUPBOX */
UB_Paint, /* BS_USERBUTTON */
CB_Paint, /* BS_AUTORADIOBUTTON */
NULL, /* Not defined */
OB_Paint /* BS_OWNERDRAW */
};
虽然有十一种风格的画按钮函数,但是很多重叠的,可以看只有三个函数:PB_PAINT,CB_PAINT,GB_PAINT,OB_PAINT
其中PB_PAINT和CB_PAINT都调用了DrawFrameControl( hDC, &rc, DFC_BUTTON, uState )画按钮.
----------Henry 20100712