当前位置:C++技术网 > 资讯 > win32按钮自绘实现单击按钮变色文字变色

win32按钮自绘实现单击按钮变色文字变色

更新时间:2016-02-16 16:02:55浏览次数:1+次

    我们首先来看看我们要实现的效果,如下图所示:

按钮自绘实现单击按钮变色文字变色

【按钮自绘实现单击按钮变色文字变色】
    从图中可以看到,未单击按钮时,按钮是浅灰色的背景颜色,文字是黑色的。单击了按钮之后,按钮是深灰色背景颜色,文字是黄色的。
    这是按钮自绘后的效果。我们来看看如何实现的。
    我们在文章《控件自绘的原理流程图解深入浅出分析》中,详细的分析了控件自绘的原理。如果你没有阅读,请先阅读。
    控件自绘消息是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;
}