1. 程式人生 > >VC按鈕自繪的簡單實現

VC按鈕自繪的簡單實現

  之前一直使用公司開發的介面庫來進行VC介面設計,今天偶然發現其中一個按鈕類不能實現特定需求,例如文字的位置不能隨意顯示在按鈕上。於是唯有重新定義了該按鈕類的自繪功能,新增新的屬性和操作。
  通過這次修改,感覺控制元件自繪也還是蠻有意思的,本來不能實現的功能,通過類的派生和自繪就能實現,感覺真爽,符合人性慾望無限擴充套件的本性。
  說起VC的介面設計,大家都應該很清楚,MFC提供了很多標準的控制元件,例如按鈕,下拉框等等,但是這些控制元件本身並不美觀。要想改變它們的形狀和外觀,就必須要進行控制元件的自繪。我們常常看到QQ,360的介面設計,之所以那麼狂吊炸天,就是因為他們有自己的介面庫,能實現控制元件的自繪。現以一個簡單的例子來介紹按鈕自繪的方法實現。(部分函式程式碼和實現原理還是要列出的,因為程式碼才是王道啊,步驟就略過哈。)

   1. 功能需求

   1) 實現按鈕文字任意位置的偏移。效果圖如下:

這裡寫圖片描述

這裡寫圖片描述

   2) 實現按鈕的幾種狀態
   Normal:
  Normal
    Over:
  這裡寫圖片描述
  Down:
  這裡寫圖片描述
  Disable:
  這裡寫圖片描述

  2. 方法實現

   首先定義一個CSkinButton類,它繼承於CButton類。
   1) 自繪屬性
  要想實現按鈕自繪,需要把按鈕風格修改為自繪屬性BS_OWNERDRAW,從而系統會發送WM_DRAWITEM訊息給CButton類,才會呼叫過載函式DrawItem實現自繪。可選擇在對話方塊屬性中或者在過載函式PreSubclassWindow中新增ModifyStyle(0,BS_OWNERDRAW);。

void CSkinButton::PreSubclassWindow() 
{
    // TODO: Add your specialized code here and/or call the base class
    ModifyStyle(0,BS_OWNERDRAW);
    CButton::PreSubclassWindow();
}

   2) 過載DrawItem()函式
   DrawItem()函式主要是根據按鈕的狀態來繪製自身的形狀和外觀,按鈕控制元件自繪的功能就是在這裡實現的。它主要包含了一個LPDRAWITEMSTRUCT的指標。宣告如下:

typedef
struct tagDRAWITEMSTRUCT { UINT CtlType; //控制元件的型別 UINT CtlID; //自繪控制元件ID UINT itemID; //選單項ID UINT itemAction; //繪製行為 UINT itemState; //當前繪製操作完成後,所繪項的可見狀態 HWND hwndItem; //指定了組合框、列表框和按鈕等自繪控制元件的視窗控制代碼 HDC hDC; //繪製操作所使用的裝置環境 RECT rcItem; //繪製的矩形區域。這個區域就是上面hDC的作用範圍。 ULONG_PTR itemData; //選單項資料 } DRAWITEMSTRUCT, NEAR *PDRAWITEMSTRUCT, FAR *LPDRAWITEMSTRUCT

  具體實現如下:

void CSkinButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
    // TODO: Add your code to draw the specified item
    CRect rcItem(lpDrawItemStruct->rcItem);
    CString strText,strPath;
    CString strWorkPath;

    //按鈕狀態
    int nStatus = CONTROL_STATUS_NORMAL;
    if ( !IsWindowEnabled() )
    {
        nStatus = CONTROL_STATUS_DISABLE;
    }
    else
    {
        if ( m_bTrackMouseEvent )
        {
            if ( m_bLButtonDown )
            {
                nStatus = CONTROL_STATUS_DOWN;
            }
            else
            {
                nStatus = CONTROL_STATUS_OVER;
            }
        }
        else
        {
//          if ( ::GetFocus()==m_hWnd )
//          {
//              nStatus = CONTROL_STATUS_FOCUS;
//          }
//          else
            {
                nStatus = CONTROL_STATUS_DISABLE/*CONTROL_STATUS_NORMAL*/;
            }
        }
    }

    if ( m_hBitmap[nStatus] )
    {
        //繪圖
        Draw9gridTransparent(lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem, m_rcBorder9grid, m_hBitmap[nStatus]);
    }
    //使用透明的輸出,也就是文字的背景是不改變的
    ::SetBkMode(lpDrawItemStruct->hDC, TRANSPARENT);

    GetWindowText(strText);
    if (m_unDrawTextStyle==BTN_DRAW_TEXT_CENTER)
    {
        ::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), rcItem, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
    }
    else if (m_unDrawTextStyle==BTN_DRAW_TEXT_LEFT)
    {
        //根據偏移值來繪製
        rcItem.left = rcItem.left+m_unDrawTextSize;
        ::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), rcItem, DT_LEFT|DT_VCENTER|DT_SINGLELINE);
    }
    else if (m_unDrawTextStyle==BTN_DRAW_TEXT_RIGHT)
    {
        rcItem.right = rcItem.right-m_unDrawTextSize;
        ::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), rcItem, DT_RIGHT|DT_VCENTER|DT_SINGLELINE);
    }
}

   3) 文字繪製
  文字的繪製使用DrawText函式,偏移值只要根據矩形區域的left和right來設定就行了,具體可參看DrawItem函式:

        //根據偏移值來繪製
        rcItem.right = rcItem.right-m_unDrawTextSize;  
        ::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), rcItem, DT_RIGHT|DT_VCENTER|DT_SINGLELINE);

   4) Mouse相關響應
  想要按鈕隨著滑鼠移動來動態顯示狀態,需要觸發WM_MOUSEMOVE,WM_MOUSELEAVE和WM_MOUSEHOVER訊息。由於WM_MOUSEMOVE訊息是標準的Windows訊息,只需要在類嚮導中新增即可觸發,而WM_MOUSELEAVE和WM_MOUSEHOVER兩個訊息需要通過WM_MOUSEMOVE訊息來啟動觸發。

OnMouseLeave實現定義如下:
//CSkinButton.h定義
afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
//CSkinButton.cpp定義訊息
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
//CSkinButton.cpp實現
LRESULT CSkinButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
if ( m_bTrackMouseEvent )
    {
        m_bTrackMouseEvent = FALSE;
        Invalidate();
    }
    SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));

    return 1;
}

OnMouseMove實現如下:
void CSkinButton::OnMouseMove(UINT nFlags, CPoint point) 
{
// TODO: Add your message handler code here and/or call default
    CRect rect;
    GetWindowRect(rect);
    ScreenToClient(rect);

    if ( !m_bTrackMouseEvent )
    {
        m_bTrackMouseEvent = TRUE;

        TRACKMOUSEEVENT tme;
        tme.cbSize          = sizeof(TRACKMOUSEEVENT);
        tme.dwFlags         = TME_LEAVE;
        tme.dwHoverTime     = HOVER_DEFAULT;
        tme.hwndTrack       = m_hWnd;
        _TrackMouseEvent(&tme);
        Invalidate();
    }
    ::SetCursor(AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32649)));

    CButton::OnMouseMove(nFlags, point);
}

   5) 圖片繪製

裝載點陣圖:
HBITMAP  hImageBitmap = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),strImagePath, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE);
按九個方向進行圖片繪製:
BOOL CSkinButton::Draw9gridTransparent(HDC hSrcDC, CRect rcItem, CRect rcBorder, HBITMAP hBitmap)
{
    if (hBitmap)
    {
        BITMAP bitmap;
        ::GetObject(hBitmap, sizeof(BITMAP), &bitmap);
        int m_nWidth    = bitmap.bmWidth;
        int m_nHeight   = bitmap.bmHeight;

        HDC memdc;
        memdc = CreateCompatibleDC(hSrcDC);
        ::SelectObject(memdc, hBitmap);

        //左上角
        CRect rcLeftTop;
        rcLeftTop.left      = rcItem.left;
        rcLeftTop.top       = rcItem.top;
        rcLeftTop.right     = rcLeftTop.left+m_rcBorder9grid.left;
        rcLeftTop.bottom    = rcLeftTop.top+m_rcBorder9grid.top;
        ::TransparentBlt(hSrcDC, rcLeftTop.left, rcLeftTop.top, rcLeftTop.Width(), rcLeftTop.Height(), memdc, 0, 0, m_rcBorder9grid.left, m_rcBorder9grid.top, RGB(255,0,255));

        //右上角
        CRect rcRightTop;
        rcRightTop.left     = rcItem.right-m_rcBorder9grid.right;
        rcRightTop.right    = rcItem.right;
        rcRightTop.top      = rcItem.top;
        rcRightTop.bottom   = rcRightTop.top+m_rcBorder9grid.top;
        ::TransparentBlt(hSrcDC, rcRightTop.left, rcRightTop.top, rcRightTop.Width(), rcRightTop.Height(), memdc, m_nWidth-m_rcBorder9grid.right, 0, m_rcBorder9grid.right, m_rcBorder9grid.top, RGB(255,0,255));

        //右下角
        CRect rcRightBottom;
        rcRightBottom.left  = rcItem.right-m_rcBorder9grid.right;
        rcRightBottom.right = rcItem.right;
        rcRightBottom.bottom= rcItem.bottom;
        rcRightBottom.top   = rcRightBottom.bottom-m_rcBorder9grid.bottom;
        ::TransparentBlt(hSrcDC, rcRightBottom.left, rcRightBottom.top, rcRightBottom.Width(), rcRightBottom.Height(), memdc, m_nWidth-m_rcBorder9grid.right, m_nHeight-m_rcBorder9grid.bottom, m_rcBorder9grid.right, m_rcBorder9grid.bottom, RGB(255,0,255));

        //左下角
        CRect rcLeftBottom;
        rcLeftBottom.left   = rcItem.left;
        rcLeftBottom.right  = rcLeftBottom.left+m_rcBorder9grid.left;
        rcLeftBottom.bottom = rcItem.bottom;
        rcLeftBottom.top    = rcLeftBottom.bottom-m_rcBorder9grid.bottom;
        ::TransparentBlt(hSrcDC, rcLeftBottom.left, rcLeftBottom.top, rcLeftBottom.Width(), rcLeftBottom.Height(), memdc, 0, m_nHeight-m_rcBorder9grid.bottom, m_rcBorder9grid.left, m_rcBorder9grid.bottom,RGB(255,0,255));

        //左邊
        CRect rcLeft;
        rcLeft.left         = rcItem.left;
        rcLeft.right        = rcLeft.left+m_rcBorder9grid.left;
        rcLeft.top          = rcItem.top+m_rcBorder9grid.top;
        rcLeft.bottom       = rcItem.bottom-m_rcBorder9grid.bottom;
        ::StretchBlt(hSrcDC, rcLeft.left, rcLeft.top, rcLeft.Width(), rcLeft.Height(), memdc, 0, m_rcBorder9grid.top, m_rcBorder9grid.left, m_nHeight-m_rcBorder9grid.top-m_rcBorder9grid.bottom, SRCCOPY);

        //頂邊
        CRect rcTop;
        rcTop.left          = rcItem.left+m_rcBorder9grid.left;
        rcTop.right         = rcItem.right-m_rcBorder9grid.right;
        rcTop.top           = rcItem.top;
        rcTop.bottom        = rcTop.top+m_rcBorder9grid.top;
        ::StretchBlt(hSrcDC, rcTop.left, rcTop.top, rcTop.Width(), rcTop.Height(), memdc, m_rcBorder9grid.left, 0, m_nWidth-m_rcBorder9grid.left-m_rcBorder9grid.right, m_rcBorder9grid.top, SRCCOPY);

        //右邊
        CRect rcRight;
        rcRight.right       = rcItem.right;
        rcRight.left        = rcRight.right-m_rcBorder9grid.right;
        rcRight.top         = rcItem.top+m_rcBorder9grid.top;
        rcRight.bottom      = rcItem.bottom-m_rcBorder9grid.bottom;
        ::StretchBlt(hSrcDC, rcRight.left, rcRight.top, rcRight.Width(), rcRight.Height(), memdc, m_nWidth-m_rcBorder9grid.right, m_rcBorder9grid.top, m_rcBorder9grid.right, m_nHeight-m_rcBorder9grid.top-m_rcBorder9grid.bottom, SRCCOPY);

        //底邊
        CRect rcBottom;
        rcBottom.left       = rcItem.left+m_rcBorder9grid.left;
        rcBottom.right      = rcItem.right-m_rcBorder9grid.right;
        rcBottom.bottom     = rcItem.bottom;
        rcBottom.top        = rcBottom.bottom-m_rcBorder9grid.bottom;
        ::StretchBlt(hSrcDC, rcBottom.left, rcBottom.top, rcBottom.Width(), rcBottom.Height(), memdc, m_rcBorder9grid.left, m_nHeight-m_rcBorder9grid.bottom, m_nWidth-m_rcBorder9grid.left-m_rcBorder9grid.right, m_rcBorder9grid.bottom, SRCCOPY);

        //中心
        CRect rcCenter;
        rcCenter.left       = rcItem.left+m_rcBorder9grid.left;
        rcCenter.right      = rcItem.right-m_rcBorder9grid.right;
        rcCenter.top        = rcItem.top+m_rcBorder9grid.top;
        rcCenter.bottom     = rcItem.bottom-m_rcBorder9grid.bottom;
        int nMode = ::SetStretchBltMode(hSrcDC,HALFTONE);
        ::StretchBlt(hSrcDC, rcCenter.left, rcCenter.top, rcCenter.Width(), rcCenter.Height(), memdc, m_rcBorder9grid.left, m_rcBorder9grid.top, m_nWidth-m_rcBorder9grid.left-m_rcBorder9grid.right, m_nHeight-m_rcBorder9grid.top-m_rcBorder9grid.bottom, SRCCOPY); 
        ::SetStretchBltMode(hSrcDC,nMode);
        ::DeleteDC(memdc);
    }

    return TRUE;
}

  為了更清楚,現把頂邊程式碼去掉看看:
這裡寫圖片描述

  通過控制元件自繪,你能夠按照自己的需要和想法來實現一些酷炫的介面外觀。人靠衣裝,佛靠金裝,一個好的軟體產品也需要精緻的外表,至少在審美上要滿足使用者的需求,提高使用者體驗。