我们首先来看看我们要实现的效果,如下图所示:
【按钮自绘实现单击按钮变色文字变色】
从图中可以看到,未单击按钮时,按钮是浅灰色的背景颜色,文字是黑色的。单击了按钮之后,按钮是深灰色背景颜色,文字是黄色的。这是按钮自绘后的效果。我们来看看如何实现的。
我们在文章《控件自绘的原理流程图解深入浅出分析》中,详细的分析了控件自绘的原理。如果你没有阅读,请先阅读。
控件自绘消息是WM_DRAWITEM。我们在父窗口的窗口过程中处理这个消息,就可以完成控件自绘。我们的程序中,首先创建一个按钮,然后给按钮指定BS_OWNERDRAW风格。含有BS_OWNERDRAW风格就需要由程序员来绘制按钮了。加入这个风格后,你就有义务绘制按钮了。也就是说,自绘按钮必须要你来绘制,系统不会再使用默认的按钮样式来绘制按钮。如果你不绘制按钮,那么按钮就是一个灰色的矩形而已。没有单击的界面效果,没有单击后弹起的界面效果。而且,默认的按钮样式完全不存在了,你要从头到尾来负责按钮的绘制。
按钮的绘制,分为不单击时的界面和单击按下时的界面。所以,我们分为两种情况来绘制按钮。同时我们还要知道,WM_DRAWITEM不只是为按钮控件卖命,也会为其他具有自绘风格的控件卖命哦。所以,我们需要根据参数中的控件类型和控件ID来区分。
而这些参数就在DRAWITEMSTRUCT结构体中。我们先来看看这个结构体的声明,我们这里不涉及菜单的问题,不做说明,详见MSDN:
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType; //控件类型
UINT CtlID; //控件ID
UINT itemID; //列表框的项的索引值或者组合框
UINT itemAction;//绘制动作标志
UINT itemState; //项的状态
HWND hwndItem; //控件的句柄
HDC hDC; //控件传递过来绘图的DC句柄
RECT rcItem; //控件的矩形大小
ULONG_PTR itemData; //菜单相关,这里不讨论
} DRAWITEMSTRUCT;
从DRAWITEMSTRUCT结构体的声明中我们可以看到我们需要的参数都在结构体里了。在WM_DRAWITEM消息的WPARAM参数中,携带了控件的ID,和结构体里的控件ID是一样的。LPARAM参数中携带了指向DRAWITEMSTRUCT结构体变量的地址。所以,我们在WM_DRAWITEM消息中,首先要将这些必要的参数提取或者转换过来。下面就是准备参数的代码:
UINT iCtlID = wParam;//如果此消息是菜单发送的,此值就是0,否则就是发送此消息的控件ID
LPDRAWITEMSTRUCT lpDrawItemStruct = (LPDRAWITEMSTRUCT)lParam;
HDC hdc = lpDrawItemStruct->hDC;
提取了这些参数,我们后面写代码就方便了。我们这里就单独对按钮控件进行重绘,所以可以不用判断控件的类型。不过我们还是要用到控件的ID来判断一下。如果不是我们指定的ID的控件发过来的自绘消息,我们不予处理。我们在创建按钮控件时,给按钮指定了ID为1,所以,我们就将参数中得到的控件ID与1比较即可。如果在正式开发中,请使用要给宏来命名控件ID,如IDC_BTN,这样人家更好阅读你的代码。否则可能不直观,造成代码阅读困难,在团队开发时不是好的现象。
因为DC是由控件传过来的,那么我们对于DC的设置,如设置字体的颜色等等,都会存留与DC中。建议修改了DC的设置,在绘制完毕后,恢复原来的设置。这是一个良好的习惯哦。
为了不看到文字的背景色,就将文字的背景模式设置为透明的。接下来,我们需要根据按钮的状态来绘制按钮了。按钮的绘制不难,就是绘制一下边框和填充一下矩形。问题是如何区分按钮的状态。
下压按钮(普通按钮)的状态有按下和没按下两种,按下的状态标识为ODS_SELECTED。如果是按下时要求的重绘,结构体中的itemState对应的位被设置为1。所以,我们将itemState与ODS_SELECTED进行位与,如果结果为1,则表示itemState状态对应的位被设置为了1,也就是按下了按钮,否则就是未按下按钮。
而填充矩形、画矩形边框需要的DC句柄和绘制的矩形区域都在DRAWITEMSTRUCT结构体中有,我们拿来用即可。至于画刷,我们自己创建或者直接获取系统画刷即可。FrameRect函数和FillRect函数的使用,请阅读《FillRect、FrameRect与Rectangle矩形绘制函数使用对比分析》。
下面是完整的代码:
#include "windows.h"
#include <tchar.h>
TCHAR tip[]=_T("C++技术网http://www.cjjjs.com");
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
CreateWindow(_T("button"),_T("C++技术网\r\nhttp://www.cjjjs.com"),WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,0,0,130,50,hwnd,(HMENU)1,(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),0);
return 0;
}
case WM_DRAWITEM:
{
UINT iCtlID = wParam;//如果此消息是菜单发送的,此值就是0,否则就是发送此消息的控件ID
LPDRAWITEMSTRUCT lpDrawItemStruct = (LPDRAWITEMSTRUCT)lParam;
HDC hdc = lpDrawItemStruct->hDC;
if (iCtlID==1)
{
SetBkMode(hdc,TRANSPARENT);//设置文字背景模式为透明的
if (lpDrawItemStruct->itemState&ODS_SELECTED)//检测按钮是否是被单击按下时要求的重绘
{
//单击按钮时
FrameRect(hdc,&lpDrawItemStruct->rcItem,(HBRUSH)GetStockObject(WHITE_BRUSH));
FillRect(hdc,&lpDrawItemStruct->rcItem,(HBRUSH)GetStockObject(DKGRAY_BRUSH));
SetTextColor(hdc,RGB(255,255,0));
}
else
{
FrameRect(hdc,&lpDrawItemStruct->rcItem,(HBRUSH)GetStockObject(LTGRAY_BRUSH));
FillRect(hdc,&lpDrawItemStruct->rcItem,(HBRUSH)GetStockObject(LTGRAY_BRUSH));
SetTextColor(hdc,RGB(0,0,0));
}
DrawText(hdc,tip,-1,&lpDrawItemStruct->rcItem,DT_WORDBREAK|DT_CENTER|DT_VCENTER);
SetBkMode(hdc,OPAQUE);//恢复文字背景模式
SetTextColor(hdc,RGB(0,0,0));//恢复文字颜色
}
}
return FALSE;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;//跳出到默认处理
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
TCHAR ClassName[] = _T("MyClass");
TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
WNDCLASS wndClass;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WinProc;
wndClass.lpszClassName = ClassName;
wndClass.lpszMenuName=NULL;
wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
if(!RegisterClass(&wndClass))return 0;
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,10,100,600,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}