手持PDA,手持数据采集器,盘点 机,数据采集器 服务热线:400-6636-496

国内专业的行业手持终端提供商,产品与解决方案广泛应用于医药,物流快递、零售、制造行 业。

MFC下按钮自绘的实现

来源: 时间:2014-05-28

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_bOverTURE,则绘制一定的图形进行标记。

    如下图:

    光标不在按钮之上:

    

    光标位于按钮之上: