VC++ WIN32 sdk实现按钮自绘详解.

网上找了很多 , 可只是给出代码 , 没有详细解释 , 不便初学者理解 . 我就抄回冷饭 . 把这个再拿出来说说 .
实例图片 :
  
首先建立一个标准的 Win32 Application  工程 . 选择 a simple Win32 Application.
然后建立我们的资源文件首先新建一个对话框资源 , 资源 ID 改为 IDD_MAIN_DLG
然后在其上新建一个按钮控件资源 ID 改为 IDC_ODBUTTON, 此按钮的 styles 中必须选中 owenerdraw 属性 .
然后将其保存为 .rc 的资源文件 . 并将其导入我们的工程 . 同理新建一个图标文件资源 ID 改为 IDI_OWNERDRAW 保存为 .ico 的图标然后导入 .
准备工作做完了下面开始写代码 .
首先声明如下全局变量.
#include "stdafx.h"
#include "resource.h"
   HINSTANCE odInst = NULL;   // 接收程序实例的句柄
HWND hMainWnd = NULL;      // 接收主窗口的句柄
HWND hDlgNow = NULL;       // 接收对话框的句柄
static  HICON hOwnerDrawIcon = NULL;   // 用作自绘按钮的图标
static  LONG prev_proc;                  // 储存按钮先前的回调函数
static  HICON hIcon = NULL;            // 对话框图标句柄
然后开始写WinMain()函数
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     
int       nCmdShow)
{
     
// TODO: Place code here.
    odInst = hInstance;
    
    WNDCLASS  wc;
    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC)ODWndProc; 
//
定义一个窗口默认函数 , 这里我们会交由默认窗口函数处理
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(odInst,MAKEINTRESOURCE(IDI_OWNERDRAW));
    wc.hCursor       = NULL;
    wc.hbrBackground = 0;
    wc.lpszClassName = "OwnerDraw";
    wc.lpszMenuName     = NULL;

    RegisterClass(&wc);

MSG msg;
    
    HWND onlywin= FindWindow("OwnerDraw","MyOwnerDraw");

    
if  (onlywin)
        
{
        ExitProcess(1);
        }
    
    hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);

    
    
if  (!hMainWnd)
        
{
            
return  FALSE;
        }

    
    hDlgNow = DoMainDlg(hMainWnd);
    ShowWindow(hDlgNow, nCmdShow);


    
while (GetMessage(&msg, NULL, 0, 0)) 
    
{
        
if  (NULL == hDlgNow || !IsDialogMessage(hDlgNow, &msg))
        
{
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    
return  msg.wParam;
}
 
首先注册一个标准的窗口类 , WNDCLASS 结构体 , 默认的窗口过程为 ODWndProc. 其定义如下 .
LRESULT CALLBACK ODWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    
return DefWindowProc(hWnd, message, wParam, lParam);//
返回系统默认的窗口过程
}
   然后判断有无相同实例存在如有则结束之
HWND onlywin= FindWindow("OwnerDraw","MyOwnerDraw");

    if (onlywin)
        {
        ExitProcess(1);
        }
 
接下来创建主窗口
hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);
需要注意的是我们这里并不调用ShowWindow()和UpdateWindow();因为我们不需要显示主窗口
  
hDlgNow = DoMainDlg(hMainWnd);
    ShowWindow(hDlgNow, nCmdShow);
这里调用 DoMainDlg 函数创建一个对话框并显示之 . DoMainDlg 函数实现如下 .
HWND DoMainDlg(HWND parent)
{
    DWORD dwErr;
    HWND hRet = CreateDialog(odInst, (LPCTSTR)IDD_MAIN_DLG, parent, (DLGPROC)MainDlgProc);
    
if(hRet == NULL)
        dwErr = GetLastError();

    
return hRet;

}
 
最后为消息循环
while(GetMessage(&msg, NULL, 0, 0)) 
    
{
        
if (NULL == hDlgNow || !IsDialogMessage(hDlgNow, &msg))
        
{
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
 
其中 IsDialogMessage(hDlgNow, &msg) 主要作用是把指定对话框的消息 , 路由给其处理 .
 
 
下面是对话框窗口的默认消息响应回调函数 MainDlgProc
我这里主要讲响应 WM_DRAWITEM 消息与 WM_INITDIALOG..
首先是响应 WM_INITDIALOG
case WM_INITDIALOG:
            
            
if(hIcon == NULL)
                hIcon = LoadIcon(odInst, MAKEINTRESOURCE(IDI_OWNERDRAW));
            
            
if(hOwnerDrawIcon == NULL)
                hOwnerDrawIcon = (HICON)LoadImage(odInst, 
                                        MAKEINTRESOURCE(IDI_OWNERDRAW), 
                                        IMAGE_ICON, 
                                        38,
                                        38,
                                        0);
            prev_proc = SetWindowLongPtr(GetDlgItem(hDlg, IDC_ODBUTTON), GWLP_WNDPROC, (LONG)ButtWindProc);
            
            SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);

            SetFocus(GetDlgItem(hDlg, IDC_ODBUTTON));
            
            
            
break;
 
首先为对话框加载一个图标 , 我这里图省事全部都用了一个图标 , 在实际应用中 . 可以随需要更换 .
然后是为自绘按钮加载图标 . 接下来改变默认的自绘按钮的窗口过程 . 将原按钮过程存与 prev_proc .
最后发送 WM_SETICON 消息设置对话框图标和设置焦点 .
 
接下来是响应 WM_DRAWITEM 消息 , 需要说明的是这个消息必须要设置了 BS_OWNERDRAW
我们用记事本打开我们的对话框资源文件会看到类似下面的设置
IDD_MAIN_DLG DIALOG DISCARDABLE  0, 0, 250, 142
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 10, "System"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,193,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,193,24,50,14
    CONTROL         "OwnerDraw",IDC_ODBUTTON,"Button",BS_OWNERDRAW | 
                    WS_TABSTOP,49,31,79,26
END
 
此处资源文件中的 BS_OWNERDRAW 即对应创建按钮时选中的 Ownerdraw 属性 . 之所以这样作是因为只有这样
对话框才能响应WM_DRAWITEM消息.下面为代码.
  
case WM_DRAWITEM:
            
{
                LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT) lParam;
                
//
声明一个指向 DRAWITEMSTRUCT 结构体的指针并将其指向存储着按钮构造信息的 lParam

                
if (lpDIS->CtlID != IDC_ODBUTTON)
                    
return  (0);
                
                HDC dc = lpDIS->hDC; 
// 用于按钮绘制的 DC
                BOOL bIsPressed  = (lpDIS->itemState & ODS_SELECTED);
                BOOL bIsFocused  = (lpDIS->itemState & ODS_FOCUS);
                BOOL bIsDisabled = (lpDIS->itemState & ODS_DISABLED);
                BOOL bDrawFocusRect = !(lpDIS->itemState & ODS_NOFOCUSRECT);
                
// 判断按钮各种状态的 BOOL
                RECT itemRect = lpDIS->rcItem;  // 按钮的矩形区域
                
                SetBkMode(dc, TRANSPARENT); 
// 设置绘制按钮时的背景状态
                 if  (bIsFocused)   // 判断按钮是否获得了焦点并对其边框进行处理
                 {
                    HBRUSH br = CreateSolidBrush(RGB(0,0,0));  
                    FrameRect(dc, &itemRect, br);
                    InflateRect(&itemRect, -1, -1);
                    DeleteObject(br);
                } 
// if        
                
                COLORREF crColor = GetSysColor(COLOR_BTNFACE);
// 得到系统按钮颜色
                
                HBRUSH    brBackground = CreateSolidBrush(crColor);
// 创建画刷
                
                FillRect(dc, &itemRect, brBackground);
// 绘制按钮
                
                DeleteObject(brBackground);
                
                
//  这里画被按下去的按钮
                 if  (bIsPressed)
                
{
                    HBRUSH brBtnShadow = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
                    FrameRect(dc, &itemRect, brBtnShadow);
                    DeleteObject(brBtnShadow);
                }
                
                
else  // 如果没有被按下就这样画
                 {
                    UINT uState = DFCS_BUTTONPUSH |
                        ((bIsPressed) ? DFCS_PUSHED : 0);
                    
                    DrawFrameControl(dc, &itemRect, DFC_BUTTON, uState);
                }
                
                
char  sTitle[100];
                GetWindowText(GetDlgItem(hDlg, IDC_ODBUTTON), sTitle, 100);
// 得到按钮的文本
                
                RECT captionRect = lpDIS->rcItem;
// 把文本的区域设置为按钮区域
                
                
                BOOL bHasTitle = (sTitle[0] !='/0');
// 按钮上是否有文本存在
                
//
这里画按钮上的图标 , 具体实现见下面
                 (GetDlgItem(hDlg, IDC_ODBUTTON), &dc, bHasTitle, 
                    &lpDIS->rcItem, &captionRect, bIsPressed, bIsDisabled);
                
                
                
                
if  (bHasTitle) // 如果按钮有文本标题
                 {
                    
//  按钮被按下的处理
                     if  (bIsPressed)
                        OffsetRect(&captionRect, 1, 1);
                    
                    
//  将文本居中
                    RECT centerRect = captionRect;
                    DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CALCRECT|DT_CENTER);
                    LONG captionRectWidth = captionRect.right - captionRect.left;
                    LONG captionRectHeight = captionRect.bottom - captionRect.top;
                    LONG centerRectWidth = centerRect.right - centerRect.left;
                    LONG centerRectHeight = centerRect.bottom - centerRect.top;
                    OffsetRect(&captionRect, (centerRectWidth - captionRectWidth)/2, (centerRectHeight - captionRectHeight)/2);
                    
                    
                    
                    SetBkMode(dc, TRANSPARENT);
                    
                    
if  (bIsDisabled) // 如果按钮被禁用
                     {
                        OffsetRect(&captionRect, 1, 1);
                        SetTextColor(dc, ::GetSysColor(COLOR_3DHILIGHT));
                        DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
                        OffsetRect(&captionRect, -1, -1);
                        SetTextColor(dc, ::GetSysColor(COLOR_3DSHADOW));
                        DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
                    } 
                    
else  // 如果没被禁用正常画
                     {
                        SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));
                        SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));
                        DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);
                    } 
                    
                }
                
                
//  画按钮得到焦点时的虚线方框
                 if  (bIsFocused && bDrawFocusRect)
                
{
                    RECT focusRect = itemRect;
                    InflateRect(&focusRect, -3, -3);
                    DrawFocusRect(dc, &focusRect);
                } 
// if
                 return  (TRUE);
            }
            
break ;
 
到此 WM_DRAWITEM 消息响应完毕 . 下面我们看看 DrawTheIcon 这个函数 .
static void DrawTheIcon(HWND hButtonWnd, HDC* dc, BOOL bHasTitle, RECT* rpItem, RECT* rpTitle, BOOL bIsPressed, BOOL bIsDisabled)
{
    RECT    rImage;
    PrepareImageRect(hButtonWnd, bHasTitle, rpItem, rpTitle, bIsPressed, 38, 38, &rImage);
    
    
// 
调用 API 函数按准备好的形式将图片画到按钮上
    DrawState(    *dc,
        NULL,
        NULL,
        (LPARAM)hOwnerDrawIcon,
        0,
        rImage.left,
        rImage.top,
        (rImage.right - rImage.left),
        (rImage.bottom - rImage.top), 
        (bIsDisabled ? DSS_DISABLED : DSS_NORMAL) | DST_ICON);
}
还有其中的 PrepareImageRect 函数
 
static void PrepareImageRect(HWND hButtonWnd, BOOL bHasTitle, RECT* rpItem, RECT* rpTitle, BOOL bIsPressed, DWORD dwWidth, DWORD dwHeight, RECT* rpImage)
{
    RECT rBtn;
    
    CopyRect(rpImage, rpItem);
    
    
    GetClientRect(hButtonWnd, &rBtn);
    
if (bHasTitle == FALSE)//
如果按钮上有文本内容
     {
        
//  使图片水平居中
        LONG rpImageWidth = rpImage->right - rpImage->left;
        rpImage->left += ((rpImageWidth - (
long )dwWidth)/2);
    }
    
else
    
{    // 控制图片与焦点方框内部
        LONG rpTitleWidth = rpTitle->right - rpTitle->left;
        rpTitle->right = rpTitleWidth - dwWidth - 30;
        rpTitle->left = 30;
        rpImage->left = rBtn.right - dwWidth - 22;
        
        LONG rpImageHeight = rpImage->bottom - rpImage->top;
        rpImage->top += ((rpImageHeight - (
long )dwHeight)/2);
    }
    
if  (bIsPressed) // 按钮被按下的处理
        OffsetRect(rpImage, 1, 1);
    
}
 
行了到这里主要的工作都作完了还要说明的就是 ButtWindProc 这个按钮窗口的回调函数 . 写它的主要目的是为了
实现按钮的连续单击 , 在此函数中我们处理了 WM_LBUTTONDBLCLK 鼠标双击事件 , 并将其转化为一个单击事件 . 像这样 :
LRESULT CALLBACK ButtWindProc(
                              HWND hWnd,                            
//window handle                   
                              UINT message,                         // type of message                 
                              WPARAM wParam,                        // additional information          
                              LPARAM lParam)                        //additional information          
{
    
switch (message)
    
{
    
case WM_LBUTTONDBLCLK:
        PostMessage(hWnd, WM_LBUTTONDOWN, wParam, lParam);
        
break;
    
    }
    
//
将不做处理的消息路由给原默认函数
     return  CallWindowProc((WNDPROC)prev_proc, hWnd, message, wParam, lParam);
    
}
 
下面只需再响应 WM_SETICON, WM_SYSCOMMAND, WM_COMMAND. 这三个没什么好说的 . 前两个交由系统默认过程处理 , 最后一个对应对话框上的确定和取消 , 都是销毁窗口的行为 .
case WM_SETICON:
                DefWindowProc(hDlg, message, wParam, lParam);
            
break;
    
    
case WM_SYSCOMMAND:
                
{
                    
return DefWindowProc(hDlg, message, wParam, lParam);
                }
    
case WM_COMMAND:
                
switch (LOWORD(wParam))
                
{
                
case IDCANCEL:
                
case IDOK:
                    
                    DestroyIcon(hOwnerDrawIcon);
                    
                    PostQuitMessage(0);
                    
return TRUE;
                }
//switch
                break;

你可能感兴趣的:(Win32)