天天看點

VC++ WIN32 sdk實作按鈕自繪詳解 之二.

網上找了很多,可隻是給出代碼,沒有詳細解釋,不便初學者了解.我就抄回冷飯.把這個再拿出來說說.

執行個體圖檔:

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");

接下來建立主視窗

hMainWnd=CreateWindow("OwnerDraw","MyOwnerDraw",WS_OVERLAPPEDWINDOW,

需要注意的是我們這裡并不調用ShowWindow()和UpdateWindow();因為我們不需要顯示主視窗

hDlgNow = DoMainDlg(hMainWnd);

這裡調用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)) 

其中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,

                                        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)//如果按鈕被禁用

                    {

                        SetTextColor(dc, ::GetSysColor(COLOR_3DHILIGHT));

                        DrawText(dc, sTitle, -1, &captionRect, DT_WORDBREAK | DT_CENTER);

                        OffsetRect(&captionRect, -1, -1);

                        SetTextColor(dc, ::GetSysColor(COLOR_3DSHADOW));

                    } 

                    else //如果沒被禁用正常畫

                        SetTextColor(dc, ::GetSysColor(COLOR_BTNTEXT));

                        SetBkColor(dc, ::GetSysColor(COLOR_BTNFACE));

                // 畫按鈕得到焦點時的虛線方框

                if (bIsFocused && bDrawFocusRect)

                    RECT focusRect = itemRect;

                    InflateRect(&focusRect, -3, -3);

                    DrawFocusRect(dc, &focusRect);

                } // if

                return (TRUE);

            }

到此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,

        (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);

    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;