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;

本文部分内容参考自http://www.codeproject.com/buttonctrl/nativewin32xpthemes.asp

本实例源代码http://geniusdot.googlepages.com/new.rar

时间: 2024-11-08 07:42:31

VC++ WIN32 sdk实现按钮自绘详解 之二.的相关文章

Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能

Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSDK分享组件的,奈何需要去注册各平台的账号,还要审核,有些审核还挺久,就没办法,改为写这个Bmob了,相信大家对Bmob都是挺期待的吧,因为他作为Android后端的实现很好的支持,国内很多软件都在使用它,他的功能也是特别神奇,这里就不一一细说了,我们用实际的例子来见证他的神奇 官网:http://w

Android 自定义返回按钮的实例详解

Android 自定义返回按钮的实例详解 程序中我们有时候想让放回按钮按照自己的需求调整页面而不是单纯的按照系统返回上一级,这个问题很简单,重写 onKeyDown 方法即可. 下面方法,包含了 webview 中的返回上一页和普通 activity 的单击设置和双击退出程序. @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //如果我们用的是webview页面,想返回网页的上一页设置这里就可以了 if (key

Ajax基础详解教程(二)_AJAX相关

在上篇文章给大家介绍了Ajax基础详解教程(一),讲到Ajax中open方法的第三个参数异步和同步的问题,今天呢,就来继续往下唠,先接着上回的代码 var oBtn = document.getElementById('btn'); oBtn.onclick = function(){ var xhr = null; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Mic

WebService教程详解(二)_java

在上篇文章给大家介绍了WebService教程详解(一) 使用工具的原因: 1. 使用工具可以更好的了解WebService请求的过程 2. 使用工具WsExplore可以获取SOAP数据发送和接收的格式 3. 使用工具Tcp/Ip Monitor可以监控拦截器请求头和响应头的具体数据 什么是SOAP? SOAP是一种基于XML编码规范的文本协议,简单的说SOAP就是在HTTP的基础上传输XML数据,以实现远程调用[无论你的服务端是什么语言书写的,只要接收SOAP协议的XML数据,并返回SOAP

Java 多线程实例详解(二)_java

本文承接上一篇文章<Java多线程实例详解(一)>. 四.Java多线程的阻塞状态与线程控制 上文已经提到Java阻塞的几种具体类型.下面分别看下引起Java线程阻塞的主要方法. 1.join() join -- 让一个线程等待另一个线程完成才继续执行.如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行. public class ThreadTest { public static void main(String[] args) {

CMake 手册详解(二十)

SirDigit   CMake 手册详解(二十) CMD#51: list 列表操作命令. list(LENGTH <list> <output variable>) list(GET <list> <element index> [<element index> ...] <output variable>) list(APPEND <list> <element> [<element> ...

C++高级进阶 第四季:const详解(二) 常量折叠

一.文章来由 const详解之二 二.const 代替 #define const最初动机就是代替 #define. const 优于 #define: (1) #define没有类型检查,const在编译期(而不是预编译期)做类型检查: (2)const方便调试和定位bug. 所以应该完全用const代替#define 三.头文件中的const (1)要使用const代替#define,同样需要把const定义放进头文件(或其他格式文件,include即可).这样通过包含头文件,可把const

[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)

原文:[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)  [顶]ORACLE PL/SQL编程详解之二:   PL/SQL块结构和组成元素(为山九仞,岂一日之功)     继上四篇:ORACLE PL/SQL编程之八:把触发器说透                ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)                [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不

微信支付PHP SDK —— 公众号支付代码详解_php实例

在微信支付 开发者文档页面 下载最新的 php SDK http://mch.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1 这里假设你已经申请完微信支付 1. 微信后台配置  如图 我们先进行测试,所以先把测试授权目录和 测试白名单添加上.测试授权目录是你要发起微信请求的哪个文件所在的目录. 例如jsapi 发起请求一般是jsapi.php所在目录 为测试目录,测试白名单即开发人员的微信号. 正式的支付授权目录不能和测试的一样否则会报错.不填