1. 程式人生 > >MFC 之 重繪按鍵

MFC 之 重繪按鍵

上次我們學習瞭如何美化對話方塊的介面,這次我們為上次的對話方塊新增兩個按鈕,一個是關閉按鈕,另一個是最小化按鈕,好,現在我們先看一下效果:

是不是很難看,因為我們的對話方塊美化了,所以我們的按鈕也要美化,因為採用貼圖的方式來美化,所以,我先給出這兩個按鈕的PNG格式的圖片,該圖片支援透明色,具體如下:

關閉按鈕效果圖:

最小化按鈕效果圖:

這兩張效果圖是我自己從網上找的,可能不是很合適,但是用來教學,完全沒有問題,它們的尺寸都是108*21,每張圖片都有四個小圖片,第一張和第四張小圖片都是透明的,所以看不見效果,我們使用這兩張圖片來完成按鈕的美化,每張圖片從左向右有四張小圖片,我們只用前三張,分別對應預設狀態,焦點狀態,按下狀態。

下面,我們來說一下如何美化按鈕?

第1步,我們先在對話方塊上放置兩個按鈕,一個是關閉按鈕,另一個是最小化按鈕,它們對應的ID分別是IDC_BUTTON_CLOSE和IDC_BUTTON_MIN,然後將我們的按鈕設定為自繪製模式,方法如下:

選擇按鈕,右鍵屬性,在屬性列表中找到Owner Draw選項,將其設定為True,效果圖如下:

再為它們新增兩個成員變數,具體如下:

CButton m_btnClose;
CButton m_btnMin;
第2步,我們新建一個類,繼承自CButton,我們取名為CMyButton,為其新增3個成員變數,分別如下:
//按鈕背景影象
CImage m_imgButton;
//按鈕png路徑,包括焦點,正常,按下3個狀態
CString m_strImgPath;
//父視窗背景圖片背景路徑,透明png需要使用
CString m_strImgParentPath;
第3步,我們為CMyButton新增3個成員函式,分別如下:
//設定按鈕背景圖片路徑,父視窗背景圖片路徑
void SetImagePath(CString strImgPath, CString strParentImgPath);
//初始化按鈕,主要是調整按鈕的位置,處理透明色
bool InitMyButton(int nX/*左上角X座標*/, int nY/*左上角Y座標*/,int nW/*影象寬*/, int nH/*影象高*/, bool bIsPng/*是否是PNG圖片*/);
//自繪製函式
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
CMyButton的宣告最終如下:
class CMyButton : public CButton
{
    DECLARE_DYNAMIC(CMyButton)
 
public:
    CMyButton();
 
    virtual ~CMyButton();
    
    //按鈕背景影象
    CImage m_imgButton;
 
    //按鈕png路徑,包括焦點,正常,按下3個狀態
    CString m_strImgPath;
 
    //父視窗背景圖片背景路徑,透明png需要使用
    CString m_strImgParentPath;
 
    //設定按鈕背景圖片路徑,父視窗背景圖片路徑
    void SetImagePath(CString strImgPath, CString strParentImgPath);
 
    //初始化按鈕,主要是調整按鈕的位置,處理透明色
    bool InitMyButton(int nX/*左上角X座標*/, int nY/*左上角Y座標*/,int nW/*影象寬*/, int nH/*影象高*/, bool bIsPng/*是否是PNG圖片*/);
 
    //自繪製函式
    void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
 
protected:
    DECLARE_MESSAGE_MAP()
};
第4步,我們實現SetImagePath函式,它的功能是為按鈕背景圖片和父視窗背景圖片成員函式初始化,具體如下:
void CMyButton::SetImagePath(CString strImgPath, CString strParentImgPath)
{
    m_strImgPath = strImgPath;
    m_strImgParentPath = strParentImgPath;
}
第5步,我們實現InitMyButton函式,它的功能是調整按鈕在對話方塊上的位置,其中的引數代表該按鈕在父視窗的左上角X座標,Y座標,寬度,高度,最後一個引數是為PNG格式圖片準備的,如果是PNG帶透明色的圖片,需要對它進行特殊處理,具體定義如下:
bool CMyButton::InitMyButton(int nX, int nY, int nW, int nH, bool bIsPng)
{
    HRESULT hr = 0;
    if (m_strImgPath.IsEmpty())
        return false;
    hr = m_imgButton.Load(m_strImgPath);
 
    if (FAILED(hr))
        return false;
 
    if (bIsPng)
    {
        if (m_imgButton.GetBPP() == 32)
        {
            int i = 0;
            int j = 0;
            for (i = 0; i < m_imgButton.GetWidth(); i++)
            {
                for (j = 0; j < m_imgButton.GetHeight(); j++)
                {
                    byte * pbyte = (byte *)m_imgButton.GetPixelAddress(i, j);
                    pbyte[0] = pbyte[0] * pbyte[3] / 255;
                    pbyte[1] = pbyte[1] * pbyte[3] / 255;
                    pbyte[2] = pbyte[2] * pbyte[3] / 255;
                }
            }
        }
    }
 
    MoveWindow(nX,nY,nW,nH);
 
    return true;
}
其中MoveWindow函式是用來調整按鈕位置的函式,其中的引數分別代表其在父視窗的左上角X座標,左上角Y座標,寬度,高度。
第6步,我們實現DrawItem函式,它是美化Button的核心函式,當我們將Button設定為自繪製後,每次按鈕需要重新整理,重新繪製的時候,MFC框架會呼叫它的DrawItem函式,在這個函式中,我們可以根據按鈕當前的狀態為其貼上相應的背景圖。當我們按鈕按鈕的時候,為其貼上被按下的背景圖;當我們的按鈕獲取焦點的時候,為其貼上獲取焦點的背景圖;當我們的按鈕沒有焦點,我們為其貼上預設的背景圖片,它們對應的位置前面已經說過。為了避免閃爍,我們採用雙緩衝的方式,具體程式碼如下:

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    if (!lpDrawItemStruct)
        return;
    HDC hMemDC;
    HBITMAP bmpMem;
    HGDIOBJ hOldObj;
    bmpMem = CreateCompatibleBitmap(lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem.right - lpDrawItemStruct->rcItem.left, lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top);
    if (!bmpMem)
        return;
    hMemDC = CreateCompatibleDC(lpDrawItemStruct->hDC);
    if (!hMemDC)
    {
        if (bmpMem)
        {
            ::DeleteObject(bmpMem);
            bmpMem = NULL;
        }
        return;
    }
 
    hOldObj = ::SelectObject(hMemDC, bmpMem);
 
    RECT rectTmp = { 0 };
 
    rectTmp = lpDrawItemStruct->rcItem;
 
    MapWindowPoints(GetParent(), &rectTmp);
 
    int nW = lpDrawItemStruct->rcItem.right - lpDrawItemStruct->rcItem.left;
 
    int nH = lpDrawItemStruct->rcItem.bottom - lpDrawItemStruct->rcItem.top;
 
    if (lpDrawItemStruct->itemState & ODS_SELECTED)
    {
        //按鈕被選擇
        m_imgButton.BitBlt(hMemDC, 0, 0, nW, nH, nW*2, 0, SRCCOPY);
    }
    else if (lpDrawItemStruct->itemState & ODS_FOCUS)
    {
               //焦點狀態
              m_imgButton.BitBlt(hMemDC, 0, 0, nW, nH, nW, 0, SRCCOPY);        
    }
    else
    {
        //預設狀態
        CImage imgParent;
 
        imgParent.Load(m_strImgParentPath);
 
        imgParent.Draw(hMemDC, 0, 0, nW, nH, rectTmp.left, rectTmp.top, nW, nH);
 
        m_imgButton.AlphaBlend(hMemDC, 0, 0, nW, nH, 0, 0, nW, nH);
 
        imgParent.Destroy();
        
    }
 
    ::BitBlt(lpDrawItemStruct->hDC, 0, 0, nW, nH, hMemDC, 0, 0, SRCCOPY);
 
    SelectObject(hMemDC, hOldObj);
 
    if (bmpMem)
    {
        ::DeleteObject(bmpMem);
        bmpMem = NULL;
    }
 
    if (hMemDC)
    {
        ::DeleteDC(hMemDC);
        hMemDC = NULL;
    }
    return;
}
這裡我們重點說一下預設狀態的背景圖,因為它是透明的,並且我們採用的是雙緩衝,所以,為了避免最終透明色變成黑色,我們先在記憶體DC上貼上按鈕在父視窗位置的背景圖,這樣可以解決透明色變成黑色的問題,如果你採用GDI+,就不用這麼做,但是我們採用的是GDI。
第7步,用CMyButton替代對話方塊標頭檔案中的CButton。

第8步,在對話方塊的InitDialog中,對兩個按鈕進行初始化,具體如下:

m_btnMin.SetImagePath(_T("./res/btn_min.png"), _T("./res/Background.png"));
m_btnMin.InitMyButton(516, 8, 27, 21, true);
m_btnClose.SetImagePath(_T("./res/btn_close.png"),_T("./res/Background.png"));
m_btnClose.InitMyButton(545,8,27,21,true);

第9步,編譯程式,最終效果圖如下:


今天,我們已經為它添加了最小化,關閉按鈕,下次,我們為其新增編輯框!