MFC下按鈕自繪的實現


MFC下按鈕自繪的實現(一)

       MFC下編程,很多時候對於標准的按鈕控件不是很滿意,想要弄的美觀點。這就需要按鈕自繪。這里簡單記錄一下方法,以免過個十天半個月的,自己又忘的一干二凈了。

       首先給工程添加一個MFC類,基類為CButton。要想讓按鈕具備自繪功能,就要為按鈕添加BS_OWNERDRAW屬性。為類CButton重載PreSubclassWindow虛函數。在該函數中添加如下一行代碼:

       ModifyStyle(0, BS_OWNERDRAW);

       當按鈕控件具有了自繪功能之后,每次控件狀態改變都會觸發DrawItem函數,在該函數中來繪制按鈕的形態外觀,所以第二步就要重載DrawItem虛函數。在這個函數中就可以自由發揮了,比如繪制外邊框,底色,按鈕標題,內邊框等等。

       一般都會為按鈕定義幾種不同狀態時的外觀,比如光標滑過時的狀態,按鈕按下時的狀態,按鈕禁用時的狀態,以及按鈕的正常狀態等等。這就要為新的按鈕添加幾種重要的消息響應。比如WM_MOUSELEAVE消息,WM_MOUSEHOVER消息和WM_MOUSEMOVE消息等等,值得一提的是前兩個消息的響應函數需要自己手動添加,微軟提供了一個TrackMouseEvent函數在光標離開一個窗口時投遞WM_MOUSELEAVE消息,光標滑過窗口時投遞WM_MOUSEHOVER消息。一般來說可以在WM_MOUSEMOVE消息響應函數中調用TrackMouseEvent函數來投遞WM_MOUSELEAVE消息和WM_MOUSEHOVER消息。然后在WM_MOUSELEAVE消息的響應函數中標記“光標已經離開按鈕”,然后調用InvalidateRect函數讓按鈕重繪。在WM_MOUSEHOVER消息的響應函數中標記“光標正在按鈕上方”,並調用InvalidateRect函數讓按鈕重繪。

       典型代碼:

       if (!m_bTracking)  // 判斷此時按鈕是否已經按下

       {

              TRACKMOUSEEVENT tme;

              tme.cbSize = sizeof(tme);

              tme.hwndTrack = m_hWnd;

              tme.dwFlags = TME_LEAVE | TME_HOVER;

              tme.dwHoverTime = 1;

              m_bTracking = _TrackMouseEvent(&tme);

       }

這幾天都是這么困,不知道是怎么搞的,呆會接着寫。


MFC下按鈕自繪的實現(二)

       上篇文章中提到使用WM_MOUSELEAVE消息,但是在Windows CE操作系統下,手動添加WM_MOUSELEAVE消息響應函數之后,編譯會發現WM_MOUSELEAVE沒有定義。之前在Windows XP操作系統下執行的程序沒有這個提示。找到原來的程序,發現WM_MOUSELEAVE的定義在\microsoft visual studio 8\vc\platformsdk\include\winuser.h文件中。

#if(WINVER >= 0x0500)

#define WM_NCMOUSEHOVER                 0x02A0

#define WM_NCMOUSELEAVE                 0x02A2

#endif /* WINVER >= 0x0500 */

據此手動添加如下代碼:

#ifndef WM_MOUSELEAVE

#define WM_MOUSELEAVE                   0x02A3

#endif

對於WM_MOUSEHOVER消息也是一樣:

#ifndef WM_MOUSEHOVER

#define WM_MOUSEHOVER                   0x02A1

#endif

重新編譯即可。

另外上篇文章中說道TrackMouseEvent函數用來投遞WM_MOUSELEAVEWM_MOUSEHOVER消息。貌似這個函數在Windows CE操作系統下也找不到。找不到就不用它了,自己直接調用PostMessage投遞出去算了。

比如:

::PostMessage(m_hWnd, WM_MOUSELEAVE, 0, 0);

那么當光標滑過按鈕時,會觸發WM_MOUSEMOVE消息,在這個函數中如何判斷光標是在按鈕上停留着還是離開了,從而是發送WM_MOUSELEAVE消息還是WM_MOUSEHOVER消息呢?這個不難吧,至少PtInRect函數可以搞定。

自己測試了一下,完全可以。


MFC下按鈕自繪的實現(三)

       按鈕的繪制主要在DrawItem函數中來完成,下面來簡單的繪制一下。

第一步先繪制按鈕的外邊框。定義了一個成員變量:

       CPen m_OutBorderPen;

       這是一個用來繪制按鈕外邊框的畫筆,在類的構造函數中創建它,在類的析構函數中銷毀之。然后在DrawItem函數中開始繪制按鈕的外邊框:

       CRect rect = lpDrawItemStruct->rcItem;

       CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

       int nSavedDC = pDC->SaveDC();

 

       // 繪制按鈕的外邊框

       POINT pt;

       pt.x = 5;

       pt.y = 5;

       CPen *pOldPen = pDC->SelectObject(&m_OutBorderPen);

       pDC->RoundRect(&rect, pt);

       pDC->SelectObject(pOldPen);

       編譯之后執行一下程序,看下效果:

      

       按鈕的輪廓出來了。

       第二步繪制按鈕的底色。

       // 繪制按鈕的底色

       rect.DeflateRect(3, 3, 3, 3);

       CBrush *pOldBrush = pDC->SelectObject(&m_BackgroundBrush);

       pDC->Rectangle(rect);

       pDC->SelectObject(pOldBrush);

       這里只是簡單的示范一下而已,畫出來的按鈕不一定好看。這里將按鈕的底色設置為純白色。程序的執行效果如下:

      

       第三步繪制按鈕的文本。

       // 繪制按鈕文本

       TCHAR strButtonText[MAX_PATH + 1];

       ::GetWindowText(m_hWnd, strButtonText, MAX_PATH); // 獲取按鈕文本

       if (strButtonText != NULL)

       {

              CFont *pFont = GetFont();

              CFont *pOldFont = pDC->SelectObject(pFont);

              CSize szExtent = pDC->GetTextExtent(strButtonText, _tcslen(strButtonText));

              CRect rectText = lpDrawItemStruct->rcItem;

              rectText.DeflateRect(rect.CenterPoint().x - szExtent.cx / 2, rect.CenterPoint().y - szExtent.cy / 2, rect.CenterPoint().x - szExtent.cx / 2, rect.CenterPoint().y - szExtent.cy / 2);

              int nOldBkMode = pDC->SetBkMode(TRANSPARENT);

              pDC->DrawText(strButtonText, -1, rectText, DT_WORDBREAK | DT_CENTER);

              pDC->SelectObject(pOldFont);

              pDC->SetBkMode(nOldBkMode);

       }

       重新編譯,執行效果如下:

      

       按鈕的基本外觀已經繪制出來了。接下來還要繪制按鈕按下時,光標滑過時,光標離開時等等狀態下按鈕的外觀。當然還要探索一下圓形按鈕,三角形按鈕,以及不規則圖形按鈕的繪制。累了,先寫到這里。

      

 
MFC下按鈕自繪的實現(四)

    接下來為自繪的按鈕繪制不同狀態下的外觀,首先繪制按鈕按下時的狀態。一般在按鈕按下時,按鈕的文本會向右下方移動一個微小的距離,使其看起來有被“壓下”的視覺。

通過如下代碼獲取按鈕的狀態:

UINT state = lpDrawItemStruct->itemState;

然后在DrawItem函數中繪制按鈕文本之前(DrawText)添加如下代碼:

if (state & ODS_SELECTED)

{

    rectText.OffsetRect(1, 1);

}

    編譯運行程序,看下效果。

    按鈕正常狀態:

   

    按鈕被按下時:

 

對比一下,可以看出按鈕按下時,按鈕上文本向右下方移動一小段距離。測試了一下,效果還不錯。

    接下來繪制當光標位於按鈕之上但按鈕並沒有被按下時的狀態。這里僅僅大致的介紹一下我的方法。

    首先在OnMouseMove函數中,添加如下代碼:

    if (!m_bOver)

    {

        m_nTimerId = SetTimer(1, 50, TimerProc);

        m_bOver = TRUE;

    }  

    其中m_bOver是用來標記光標此時是否在按鈕之上的BOOL類型的變量。當光標經過按鈕時,觸發OnMouseMove函數,在此函數中設置一個定時器,定時時間為50ms,定時時間到了之后將觸發TimerProc回調函數。在TimerProc函數中不斷的判斷此時光標停留在按鈕之上,還是已經離開了按鈕。

    POINT CursorPos;

    RECT ButtonRect;

    ::GetCursorPos(&CursorPos);

    ::ScreenToClient(hwnd, &CursorPos);

    ::GetClientRect(hwnd, &ButtonRect);

    if (!::PtInRect(&ButtonRect, CursorPos))

    {

        ::PostMessage(hwnd, WM_MOUSELEAVE, 0, 0);

    }

    else

    {

        ::PostMessage(hwnd, WM_MOUSEHOVER, 0, 0);

    }

    如果光標停留在按鈕之上,投遞出WM_MOUSEHOVER消息之后將觸發OnMouseHover函數,在該函數中將m_bOver設置為TURE,並調用InvalidateRect函數更新窗口;如果光標已經離開了按鈕,投遞出WM_MOUSELEAVE消息,觸發OnMouseLeave函數,在該函數中將m_bOver設置為FALSE,然后調用InvalidateRect函數更新窗口,接着關閉定時器,因為此時已經不必再連續判斷光標是否還停留在按鈕之上了。最后在DrawItem函數中判斷如果m_bOver為TURE,則繪制一定的圖形進行標記。

    如下圖:

    光標不在按鈕之上:

   

    光標位於按鈕之上:

   


免責聲明!

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



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