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_MOUSELEAVE和WM_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,则绘制一定的图形进行标记。
如下图:
光标不在按钮之上:
光标位于按钮之上:
原文:http://www.cnblogs.com/UnGeek/p/3587069.html