一、 本文介绍一个CButton的派生类CLinkButton,用此派生类制作的按钮具有以下特点:
1、按钮的外观类似静态控件类CStatic 产生的对象。(参见图一)

图一
2、当鼠标的光标移到按钮上,但并未按下时,光标改变形状,字体改变形状;按钮类似应用在工具条和菜单上的扁平钮效果。(参见图二)
图二
3、当按钮按下的情形:(参见图三)
图三
二、下面具体描述这种按钮的实现方法和步骤:
在VC6的IDE环境中,生成一个基于对话框的PROJECT。 将对话框资源中按钮的属性页打开,在“Style”标签页中选取按钮的“Owner Draw”(自绘)属性。 将光标引入到应用程序的资源中。 利用CLASSWIZARD,用CButton为基类,派生一个新类:CLinkButton。 在派生类中重载基类CButton的虚函数:
<a href="http://www.vckbase.com/index.php/wv/8#viewSource">view source</a>
<code>1.</code><code>virtual</code> <code>void</code> <code>DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)</code>
之所以要重载这个函数是因为选择了按钮的 “Owner Draw”属性后,当按钮的可视行为发生变化时,应用程序的框架要调用这个函数来重新绘制按钮。
定制以下的消息处理:
<code>1.</code><code>afx_msg</code><code>void</code> <code>OnMouseMove(</code><code>UINT</code> <code>nFlags, CPoint point);</code>
<code>2.</code><code>afx_msg</code><code>BOOL</code> <code>OnSetCursor(CWnd* pWnd,</code><code>UINT</code> <code>nHitTest,</code><code>UINT</code> <code>message);</code>
<code>3.</code><code>afx_msg</code><code>void</code> <code>OnTimer(</code><code>UINT</code> <code>nIDEvent);</code>
<code>4.</code><code>afx_msg</code><code>void</code> <code>OnLButtonUp(</code><code>UINT</code> <code>nFlags, CPoint point);</code>
<code>5.</code><code>afx_msg</code><code>void</code> <code>OnLButtonDown(</code><code>UINT</code> <code>nFlags, CPoint point);</code>
<code>6.</code><code>afx_msg</code><code>int</code> <code>OnCreate(LPCREATESTRUCT lpCreateStruct);</code>
<code>7.</code><code>afx_msg</code><code>BOOL</code> <code>OnEraseBkgnd(CDC* pDC);</code>
声明类成员变量定义:
<code>1.</code><code>//定义字体变量</code>
<code>2.</code><code>CFont fUnderline;</code>
<code>3.</code><code>//定义光标变量</code>
<code>4.</code><code>HCURSOR</code> <code>hHand;</code>
<code>5.</code><code>//决定按钮是否按下</code>
<code>6.</code><code>bool</code> <code>bLBtnDown;</code>
<code>7.</code><code>//决定鼠标是否在按钮上</code>
<code>8.</code><code>bool</code> <code>bHighlight;</code>
三、 派生类CLinkButton 的具体实现:
1、重载函数 DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)。
<code>01.</code><code>void</code> <code>CLinkButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) </code>
<code>02.</code><code>{</code>
<code>03.</code><code> </code><code>// 获取一个CDC指针</code>
<code>04.</code><code> </code><code>CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);</code>
<code>05.</code><code> </code><code>//定义按钮区域并初始化</code>
<code>06.</code><code> </code><code>CRect rect(lpDrawItemStruct->rcItem);</code>
<code>07.</code><code> </code><code>//设置背景模式</code>
<code>08.</code><code> </code><code>COLORREF</code> <code>oc = pDC->GetTextColor();</code>
<code>09.</code><code> </code><code>int</code> <code>iObk = pDC->SetBkMode(TRANSPARENT);</code>
<code>10.</code><code> </code><code>//初始化按钮状态</code>
<code>11.</code><code> </code><code>UINT</code> <code>state = lpDrawItemStruct->itemState;</code>
<code>12.</code><code> </code><code>CFont * pOldFont = NULL;</code>
<code>13.</code><code> </code><code>int</code> <code>iYOffset = 0, iXOffset = 0;</code>
<code>14.</code><code> </code><code>CString strText;</code>
<code>15.</code><code> </code><code>GetWindowText(strText);</code>
<code>16.</code><code> </code><code>rect.top += iYOffset;</code>
<code>17.</code><code> </code><code>rect.left += iXOffset;</code>
<code>18.</code><code> </code>
<code>19.</code><code> </code><code>if</code> <code>(state & ODS_DISABLED)</code>
<code>20.</code><code> </code><code>{ </code>
<code>21.</code><code> </code><code>//按钮置灰(DISABLED)</code>
<code>22.</code><code> </code><code>CBrush grayBrush;</code>
<code>23.</code><code> </code><code>grayBrush.CreateSolidBrush (GetSysColor (COLOR_GRAYTEXT));</code>
<code>24.</code><code> </code><code>CSize sz = pDC->GetTextExtent(strText);</code>
<code>25.</code><code> </code><code>int</code> <code>x = rect.left + (rect.Width() - sz.cx)/2;</code>
<code>26.</code><code> </code><code>int</code> <code>y = rect.top + (rect.Height() - sz.cy)/2;</code>
<code>27.</code><code> </code><code>rect.top += 2;</code>
<code>28.</code><code> </code><code>rect.left += 2;</code>
<code>29.</code><code> </code><code>pDC->SetTextColor(GetSysColor(COLOR_3DHIGHLIGHT));</code>
<code>30.</code><code> </code><code>pDC->DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);</code>
<code>31.</code><code> </code><code>rect.top -= 2;</code>
<code>32.</code><code> </code><code>rect.left -= 2;</code>
<code>33.</code><code> </code><code>pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));</code>
<code>34.</code><code> </code><code>pDC->DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);</code>
<code>35.</code><code> </code><code>}</code>
<code>36.</code><code> </code><code>else</code>
<code>37.</code><code> </code><code>{</code>
<code>38.</code><code> </code><code>if</code> <code>(bHighlight)</code><code>//光标在按钮上</code>
<code>39.</code><code> </code><code>{</code>
<code>40.</code><code> </code><code>if</code> <code>(state & ODS_SELECTED)</code>
<code>41.</code><code> </code><code>//按下按钮</code>
<code>42.</code><code> </code><code>pDC->Draw3dRect(rect,GetSysColor(COLOR_3DSHADOW), GetSysColor(COLOR_3DHILIGHT));</code>
<code>43.</code><code> </code><code>else</code>
<code>44.</code><code> </code><code>//未按下按钮</code>
<code>45.</code><code> </code><code>pDC->Draw3dRect(rect,GetSysColor(COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));</code>
<code>46.</code><code> </code>
<code>47.</code><code> </code><code>//字体颜色</code>
<code>48.</code><code> </code><code>pDC->SetTextColor(RGB(0,0,255));</code>
<code>49.</code><code> </code>
<code>50.</code><code> </code><code>//加下画线(也可以用其他字体)</code>
<code>51.</code><code> </code><code>if</code> <code>(fUnderline.GetSafeHandle() == NULL)</code>
<code>52.</code><code> </code><code>{</code>
<code>53.</code><code> </code><code>CFont * pFont = GetFont();</code>
<code>54.</code><code> </code><code>ASSERT(pFont);</code>
<code>55.</code><code> </code><code>LOGFONT lf;</code>
<code>56.</code><code> </code><code>pFont->GetLogFont(&lf);</code>
<code>57.</code><code> </code><code>lf.lfUnderline = TRUE;</code>
<code>58.</code><code> </code><code>fUnderline.CreateFontIndirect(&lf); </code>
<code>59.</code><code> </code><code>}</code>
<code>60.</code><code> </code><code>pOldFont = pDC->SelectObject(&fUnderline);</code>
<code>61.</code><code> </code><code>}</code>
<code>62.</code><code> </code><code>else</code> <code>pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT));</code>
<code>63.</code><code> </code>
<code>64.</code><code> </code><code>pDC->DrawText(strText, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);</code>
<code>65.</code><code> </code>
<code>66.</code><code> </code><code>if</code> <code>(pOldFont) pDC->SelectObject(pOldFont);</code>
<code>67.</code><code> </code><code>}</code>
<code>68.</code><code>}</code>
2、定制的消息处理函数
<code>1.</code><code>void</code> <code>CLinkButton::OnMouseMove(</code><code>UINT</code> <code>nFlags, CPoint point) </code>
<code>2.</code><code>{</code>
<code>3.</code><code> </code><code>//设置一个定时器</code>
<code>4.</code><code> </code><code>SetTimer(1,10,NULL);</code>
<code>5.</code><code> </code>
<code>6.</code><code> </code><code>CButton::OnMouseMove(nFlags, point);</code>
<code>7.</code><code>}</code>
当鼠标光标移到按钮上时,执行此函数,定时器将发送一个 WM_TIMER消息到消息队列。由OnTimer(UINT nIDEvent)函数处理这个消息。
<code>01.</code><code>void</code> <code>CLinkButton::OnTimer(</code><code>UINT</code> <code>nIDEvent) </code>
<code>03.</code><code> </code><code>static</code> <code>bool</code> <code>pPainted =</code><code>false</code><code>;</code>
<code>04.</code><code> </code><code>POINT pt;</code>
<code>05.</code><code> </code><code>GetCursorPos(&pt);</code>
<code>06.</code><code> </code><code>CRect rect;</code>
<code>07.</code><code> </code><code>GetWindowRect (rect);</code>
<code>08.</code><code> </code><code>if</code> <code>(bLBtnDown) </code>
<code>09.</code><code> </code><code>{ </code>
<code>10.</code><code> </code><code>KillTimer (1);</code>
<code>11.</code><code> </code><code>if</code> <code>(pPainted) InvalidateRect (NULL); </code>
<code>12.</code><code> </code><code>pPainted = FALSE; </code>
<code>13.</code><code> </code><code>return</code><code>; </code>
<code>14.</code><code> </code><code>}</code>
<code>15.</code><code> </code>
<code>16.</code><code> </code><code>if</code> <code>(!rect.PtInRect (pt)) </code>
<code>17.</code><code> </code><code>{ </code>
<code>18.</code><code> </code><code>bHighlight =</code><code>false</code><code>;</code>
<code>19.</code><code> </code><code>KillTimer (1);</code>
<code>20.</code><code> </code>
<code>21.</code><code> </code><code>if</code> <code>(pPainted) </code>
<code>22.</code><code> </code><code>InvalidateRect(NULL);</code>
<code>23.</code><code> </code>
<code>24.</code><code> </code><code>pPainted =</code><code>false</code><code>;</code>
<code>25.</code><code> </code><code>return</code><code>; </code>
<code>26.</code><code> </code><code>}</code>
<code>27.</code><code> </code><code>else</code>
<code>28.</code><code> </code><code>{</code>
<code>29.</code><code> </code><code>bHighlight =</code><code>true</code><code>;</code>
<code>30.</code><code> </code><code>if</code> <code>(!pPainted)</code>
<code>31.</code><code> </code><code>{</code>
<code>32.</code><code> </code><code>pPainted =</code><code>true</code><code>;</code>
<code>33.</code><code> </code><code>InvalidateRect(NULL);</code>
<code>34.</code><code> </code><code>}</code>
<code>36.</code><code> </code>
<code>37.</code><code> </code><code>CButton::OnTimer(nIDEvent);</code>
<code>38.</code><code>}</code>
<code>39.</code><code> </code>
<code>40.</code><code> </code>
<code>41.</code><code>BOOL</code> <code>CLinkButton::OnSetCursor(CWnd* pWnd,</code><code>UINT</code> <code>nHitTest,</code><code>UINT</code> <code>message) </code>
<code>42.</code><code>{</code>
<code>43.</code><code> </code><code>if</code> <code>(bHighlight) </code>
<code>44.</code><code> </code><code>{</code>
<code>45.</code><code> </code><code>::SetCursor(hHand);</code>
<code>46.</code><code> </code><code>return</code> <code>true</code><code>;</code>
<code>47.</code><code> </code><code>}</code>
<code>48.</code><code> </code>
<code>49.</code><code> </code><code>return</code> <code>CButton::OnSetCursor(pWnd, nHitTest, message);</code>
<code>50.</code><code>}</code>
<code>51.</code><code> </code>
<code>52.</code><code> </code>
<code>53.</code><code>int</code> <code>CLinkButton::OnCreate(LPCREATESTRUCT lpCreateStruct) </code>
<code>54.</code><code>{</code>
<code>55.</code><code> </code><code>if</code> <code>(CButton::OnCreate(lpCreateStruct) == -1)</code>
<code>56.</code><code> </code><code>return</code> <code>-1;</code>
<code>57.</code><code> </code>
<code>58.</code><code> </code><code>CFont * pFont = GetFont();</code>
<code>59.</code><code> </code><code>ASSERT(pFont);</code>
<code>60.</code><code> </code>
<code>61.</code><code> </code><code>LOGFONT lf;</code>
<code>62.</code><code> </code><code>pFont->GetLogFont(&lf);</code>
<code>63.</code><code> </code><code>lf.lfUnderline = TRUE;</code>
<code>64.</code><code> </code>
<code>65.</code><code> </code><code>fUnderline.CreateFontIndirect(&lf);</code>
<code>66.</code><code> </code>
<code>67.</code><code> </code><code>return</code> <code>0;</code>
这个函数由框架在显示出按钮之前自动调用,我在这里初始化按钮上显示的字体。
<code>01.</code><code>void</code> <code>CLinkButton::OnLButtonUp(</code><code>UINT</code> <code>nFlags, CPoint point) </code>
<code>03.</code><code> </code><code>bLBtnDown =</code><code>false</code><code>;</code>
<code>04.</code><code> </code><code>if</code> <code>(bHighlight) </code>
<code>05.</code><code> </code><code>{</code>
<code>06.</code><code> </code><code>bHighlight =</code><code>false</code><code>;</code>
<code>07.</code><code> </code><code>InvalidateRect(NULL);</code>
<code>08.</code><code> </code><code>}</code>
<code>09.</code><code> </code>
<code>10.</code><code> </code><code>CButton::OnLButtonUp(nFlags, point);</code>
<code>11.</code><code>}</code>
当按下按钮又放开时调用这个函数。
<code>1.</code><code>void</code> <code>CLinkButton::OnLButtonDown(</code><code>UINT</code> <code>nFlags, CPoint point) </code>
<code>3.</code><code> </code><code>bLBtnDown =</code><code>true</code><code>;</code>
<code>4.</code><code> </code>
<code>5.</code><code> </code><code>CButton::OnLButtonDown(nFlags, point);</code>
<code>6.</code><code>}</code>
当按下按钮时调用这个函数。
<code>01.</code><code>BOOL</code> <code>CLinkButton::OnEraseBkgnd(CDC* pDC) </code>
<code>03.</code><code> </code><code>COLORREF</code> <code>cr = GetSysColor(COLOR_3DFACE);</code>
<code>04.</code><code> </code><code>int</code> <code>r = GetRValue(cr);</code>
<code>05.</code><code> </code><code>int</code> <code>g = GetGValue(cr);</code>
<code>06.</code><code> </code><code>int</code> <code>b = GetBValue(cr);</code>
<code>07.</code><code> </code><code>if</code> <code>(r > 1) r -= 2;</code>
<code>08.</code><code> </code><code>if</code> <code>(g > 1) g -= 2;</code>
<code>09.</code><code> </code><code>if</code> <code>(r < 3 && g < 3 && b < 253) b += 2;</code>
<code>10.</code><code> </code><code>COLORREF</code> <code>cr1 = RGB(r,g,b);</code>
<code>11.</code><code> </code><code>CRect rc;</code>
<code>12.</code><code> </code><code>GetClientRect(rc);</code>
<code>13.</code><code> </code><code>pDC->FillSolidRect(rc, cr1);</code>
<code>14.</code><code> </code>
<code>15.</code><code> </code><code>return</code> <code>CButton::OnEraseBkgnd(pDC);</code>
<code>16.</code><code>}</code>
当按钮的背景需要重画时,应用程序框架调用此函数。
其他实现细节请下载源代码。运行程序的效果图见图一、图二和图三。