zoukankan      html  css  js  c++  java
  • 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;
  • 相关阅读:
    解决Sqlite Developer过期的最简单办法(转自百度经验)
    (转)解决!Visual Studio 遇到了异常。这可能是由某个扩展导致的。
    【转】VC中的class“std::vector<_Ty>”需要有 dll 接口由 class“Test”的客户端使用错误
    切换日语输入法找不到MicrosoftIME键盘选项了
    ps如何拆分图片
    码农应该注意保持的习惯
    "指定的文件格式无法识别或为不支持的二进制"
    编译FlashDemo遇到问题:Error "pFlashUI未定义的标识符"
    GridView,GridLayout
    Android,ArrayList,List,Set等
  • 原文地址:https://www.cnblogs.com/lidabo/p/3701600.html
Copyright © 2011-2022 走看看