Windows平台提供了丰富的控件,可是在使用中咱们不会使用它提供的默认风格,有时候须要对控件进行改写,让它展示出更友好的一面,此次主要是说明三态按钮的实现。函数
三态按钮指的是按钮在鼠标移到按钮上时显示一种状态,鼠标在按下时展示一种状态,在鼠标移开时又展示出另一种状态,总共三种。固然鼠标按下和移出按钮展现的状态系统本身提供的有,这个时候在处理这两种状态只须要贴相应的图片就好了,三态按钮的实现关键在于如何判断鼠标已经移动到按钮上以及鼠标移出按钮,而后根据鼠标的位置将按钮作相应的调整。字体
判断鼠标在按钮的相应位置,系统提供了一个函数_TrackMouseEvent用户处理鼠标移出、移入按钮。函数原型以下: spa
BOOL _TrackMouseEvent( LPTRACKMOUSEEVENT lpEventTrack );
函数须要传入一个TRACKMOUSEEVENT类型的指针,该结构的原型以下:指针
1 typedef struct tagTRACKMOUSEEVENT { 2 DWORD cbSize;//该结构体所占空间大小 3 DWORD dwFlags;//指定服务的请求(指定它须要侦听的事件),此次主要用到的是TME_HOVER和TME_LEAVE(侦听鼠标移开和移入事件) 4 HWND hwndTrack;//指定咱们须要侦听的控件的句柄 5 DWORD dwHoverTime;//HOVER消耗的时间,能够用系统提供的一个常量HOVER_DEFAULT由系统默认给出,也能够本身填写,单位是毫秒 6 } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;
在使用该函数时须要包含头文件commctrl.h和lib文件comctl32.libcode
解决了鼠标行为的检测以后,就是针对不一样的鼠标行为重绘相应的按钮。重绘按钮须要在消息WM_DRAWITEM中,这个消息的处理是在相应控件的父窗口中实现的,而在通常状况下父窗口不会收到该消息,须要咱们手工指定控件资源的属性为的OWNERDRAW为真,或者在建立相应的按钮窗口时将样式设置为BS_OWNERDRAW 。blog
设置完成后就能够在对应的父窗口处理函数中接收并处理WM_DRAWITEM,在该消息中重绘按钮事件
该消息中主要使用的参数是lpParam它里面包含的是一个指向DRAWITEMSTRUCT的结构体:图片
typedef struct tagDRAWITEMSTRUCT { UINT CtlType; //控件类型 UINT CtlID; //控件ID UINT itemID; //子菜单项的ID主要用于菜单 UINT itemAction; //控件发出的动做,如ODA_SELECT表示控件被选中 UINT itemState; //控件状态,此次须要用到的状态为ODS_SELECTED表示按钮被按下 HWND hwndItem; //控件句柄 HDC hDC; RECT rcItem;//控件的矩形区域 ULONG_PTR itemData; } DRAWITEMSTRUCT;
//该结构体中的一些成员须要根据控件类型赋值,同时结构体中的itemAction、itemState是能够由多个值经过位或组成在判断是否具备某种状态时须要使用位与运算
而绘制控件时咱们可使用函数DrawFrameControl,该函数能够根据指定的控件类型、控件所处的状态来绘制控件的样式,绘制出来的任然是系统的以前的标准样式,处理WM_DRAWITEN消息的具体代码以下:资源
LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; char szBuf[50]; GetWindowText(lpdis->hwndItem,szBuf,50); if (ODT_BUTTON ==lpdis->CtlType)
{ UINT uState = DFCS_BUTTONPUSH; if (lpdis->itemState & ODS_SELECTED) { uState |= DFCS_PUSHED; } DrawFrameControl(lpdis->hDC,&(lpdis->rcItem),DFC_BUTTON,uState); SetTextColor(lpdis->hDC,RGB(255,0,0)); DrawText(lpdis->hDC,szBuf,strlen(szBuf) + 1,&(lpdis->rcItem),DT_CENTER | DT_VCENTER | DT_SINGLELINE); }
函数_TrackMouseEvent根据其检测的鼠标状态不一样能够返回不一样的消息,此次主要用的是WM_MOUSEHOVER(表示鼠标移动到按钮上)、WM_MOUSELEAVE(鼠标移出按钮),还须要注意的是这个函数每次检测完成返回后不会再次检测,须要咱们本身主动调用函数检测鼠标状态,因为要屡次调用,而每次调用都须要初始化所须要的结构体指针,因此咱们封装一个函数专门用于调用_TrackMouseEvent:原型
void Track(HWND hWnd) { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.dwHoverTime = 10; tme.hwndTrack = hWnd; _TrackMouseEvent(&tme); }
消息WM_MOUSEHOVER和消息WM_MOUSELEAVE的处理是在对应的窗口过程当中处理的,而按钮的窗口过程由系统提供咱们并不知道,因此只有使用子类化的方法在咱们的窗口过程当中处理这两个消息。在按钮建立后立马要检测鼠标因此能够按钮对应的父窗口完成建立后子类化,对于窗口能够在它的WM_CREATE消息中处理,对于对话框能够在WM_INITDIALOG消息中处理,子类化调用函数SetWindowLong:
g_OldProc = (LRESULT*)SetWindowLong(GetDlgItem(hDlg,IDC_BUTTON1),GWL_WNDPROC,(LONG)BtnProc); return 0;
在新的窗口过程当中处理消息,完成三态按钮:
switch (uMsg) { case WM_MOUSEMOVE: Track(hBtn);//当鼠标移动时检测 break; case WM_MOUSEHOVER: { char szBuf[50]; RECT rtBtn;
GetClientRect(hBtn,&rtBtn); HDC hDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); HBRUSH hBr = CreateSolidBrush(RGB(255,255,255)); FillRect(hDc,&rtBtn,hBr); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT); DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; case WM_MOUSELEAVE: { char szBuf[50]; RECT rtBtn; GetClientRect(hBtn,&rtBtn); HDC hDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT);//设置字体背景为透明 DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; default: return CallWindowProc((WNDPROC)g_OldProc,hBtn,uMsg,wParam, lParam);//在处理完咱们感兴趣的消息后必定要记得将按钮的窗口过程还原 } return 0;
到这个地方为止,已经实现了三态按钮的基本样式,经过检测鼠标的位置设置按钮样式,上述代码只是改变了按钮的背景颜色和文字颜色,可能效果很差看。