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.cnblogs.com/lidabo/p/3701199.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM