MFC提供的静态控件,仅满足文本显示的简单需求。如果要实现“类似于超链接”的功能,我们需要在原有控件的基础上进行扩展。具体实现步骤如下:
1. 从CStatic类派生一个子类CStaticEx
2. 重载虚函数PreSubclassWindow,添加控件风格SS_NOTIFY
说明:静态控件需要设置SS_NOTIFY风格,才能响应鼠标事件。
void CStaticEx::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
// 在缺省情况下,静态控件是不响应鼠标事件的
DWORD dwStyle = GetStyle();
if (!(dwStyle & SS_NOTIFY))
{
::SetWindowLong(m_hWnd, GWL_STYLE, dwStyle | SS_NOTIFY);
}
CStatic::PreSubclassWindow();
}
3. 添加消息ON_WM_MOUSEMOVE,WM_MOUSEHOVER,WM_MOUSELEAVE
说明:1. 函数TrackMouseEvent,用于侦测鼠标位置,并寄送盘旋(WM_MOUSEHOVER)或离开(WM_MOUSELEAVE)消息。
2. 盘旋(WM_MOUSEHOVER)和离开(WM_MOUSELEAVE)消息,需要手动添加。
void CStaticEx::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (!m_bIsTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = GetSafeHwnd();
tme.dwFlags = TME_HOVER|TME_LEAVE;
tme.dwHoverTime = 10;
m_bIsTracking = _TrackMouseEvent(&tme);
}
CStatic::OnMouseMove(nFlags, point);
}
LRESULT CStaticEx::OnMouseHover(WPARAM wParam, LPARAM lParam)
{
if (m_nMouseState != MOUSE_HOVER)
{
m_nMouseState = MOUSE_HOVER ;
m_bUnderLine = TRUE;
Invalidate();
}
return 0 ;
}
LRESULT CStaticEx::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
if (m_nMouseState != MOUSE_LEAVE)
{
m_nMouseState = MOUSE_LEAVE ;
m_bUnderLine = FALSE;
Invalidate() ;
}
m_bIsTracking = FALSE ;
return 0 ;
}
4. 添加反射消息ON_WM_CTLCOLOR_REFLECT,在响应函数里,设置画图模式、画刷类型、文本字体、文本颜色
HBRUSH CStaticEx::CtlColor(CDC* pDC, UINT nCtlColor)
{
DWORD dwStyle = GetStyle();
HBRUSH hbr = NULL;
// TODO: Change any attributes of the DC here
if ((GetStyle() & 0xFF) <= SS_RIGHT)
{
// 设置画图模式
pDC->SetBkMode(TRANSPARENT);
// 设置画刷类型
hbr = (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
// 选择文本字体
if (m_cTextFont.GetSafeHandle() != NULL)
{
m_cTextFont.Detach();
}
if (m_bUnderLine)
{
m_cTextFont.Attach(m_hHoverFont);
}
else
{
m_cTextFont.Attach(m_hLeaveFont);
}
pDC->SelectObject(&m_cTextFont);
// 设置文本颜色
if (m_nMouseState == MOUSE_HOVER)
{
pDC->SetTextColor(m_clrHoverColor);
}
else
{
pDC->SetTextColor(m_clrLeaveColor);
}
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
5. 添加消息ON_WM_ERASEBKGND,在响应函数里,绘制控件背景
说明:控件背景可能是纯色,也可能非纯色。纯色好处理,简单填充即可;如果是非纯色(比如渐变或贴图),则需要把父窗口的背景抠出,在子窗口重绘一次。
当然,最好的方法是利用父窗口DC,在子窗口辅助绘制(这是珽震的高招)。
BOOL CStaticEx::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CRect rcClient;
GetClientRect(&rcClient);
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
CBitmap cNewBmp;
cNewBmp.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height());
CBitmap*pOldBmp = NULL;
pOldBmp = dcMem.SelectObject(&cNewBmp);
CWnd* parent = GetParent() ;
if (parent != NULL && m_parent_dc != NULL)
{
CRect bound_in_parent ;
GetWindowRect(&bound_in_parent) ;
parent->ScreenToClient(&bound_in_parent) ;
dcMem.BitBlt(
0, 0,
rcClient.Width(), rcClient.Height(),
m_parent_dc,
bound_in_parent.left, bound_in_parent.top,
SRCCOPY
) ;
}
else
{
dcMem.FillSolidRect(0, 0, rcClient.Width(), rcClient.Height(), m_clrBkColor);
}
pDC->BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
// 选择旧设备
dcMem.SelectObject(pOldBmp);
// 释放资源
if (dcMem.GetSafeHdc() != NULL)
{
dcMem.DeleteDC();
}
if (cNewBmp.GetSafeHandle() != NULL)
{
cNewBmp.DeleteObject();
}
return TRUE;
// return CStatic::OnEraseBkgnd(pDC);
}
6. 添加消息ON_WM_SETCURSOR,设置鼠标在控件范围内的光标(通常为手型光标)
BOOL CStaticEx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
::SetCursor(m_hLinkCursor);
return TRUE;
}
7. 添加消息ON_WM_LBUTTONDOWN,设置点击后的动作
说明:m_lpClickCb是一个回调函数指针,由调用者传入。
void CStaticEx::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_lpClickCb != NULL)
{
m_lpClickCb();
}
CStatic::OnLButtonDown(nFlags, point);
}
1. 从CStatic类派生一个子类CStaticEx
2. 重载虚函数PreSubclassWindow,添加控件风格SS_NOTIFY
说明:静态控件需要设置SS_NOTIFY风格,才能响应鼠标事件。
void CStaticEx::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
// 在缺省情况下,静态控件是不响应鼠标事件的
DWORD dwStyle = GetStyle();
if (!(dwStyle & SS_NOTIFY))
{
::SetWindowLong(m_hWnd, GWL_STYLE, dwStyle | SS_NOTIFY);
}
CStatic::PreSubclassWindow();
}
3. 添加消息ON_WM_MOUSEMOVE,WM_MOUSEHOVER,WM_MOUSELEAVE
说明:1. 函数TrackMouseEvent,用于侦测鼠标位置,并寄送盘旋(WM_MOUSEHOVER)或离开(WM_MOUSELEAVE)消息。
2. 盘旋(WM_MOUSEHOVER)和离开(WM_MOUSELEAVE)消息,需要手动添加。
void CStaticEx::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (!m_bIsTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = GetSafeHwnd();
tme.dwFlags = TME_HOVER|TME_LEAVE;
tme.dwHoverTime = 10;
m_bIsTracking = _TrackMouseEvent(&tme);
}
CStatic::OnMouseMove(nFlags, point);
}
LRESULT CStaticEx::OnMouseHover(WPARAM wParam, LPARAM lParam)
{
if (m_nMouseState != MOUSE_HOVER)
{
m_nMouseState = MOUSE_HOVER ;
m_bUnderLine = TRUE;
Invalidate();
}
return 0 ;
}
LRESULT CStaticEx::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
if (m_nMouseState != MOUSE_LEAVE)
{
m_nMouseState = MOUSE_LEAVE ;
m_bUnderLine = FALSE;
Invalidate() ;
}
m_bIsTracking = FALSE ;
return 0 ;
}
4. 添加反射消息ON_WM_CTLCOLOR_REFLECT,在响应函数里,设置画图模式、画刷类型、文本字体、文本颜色
HBRUSH CStaticEx::CtlColor(CDC* pDC, UINT nCtlColor)
{
DWORD dwStyle = GetStyle();
HBRUSH hbr = NULL;
// TODO: Change any attributes of the DC here
if ((GetStyle() & 0xFF) <= SS_RIGHT)
{
// 设置画图模式
pDC->SetBkMode(TRANSPARENT);
// 设置画刷类型
hbr = (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
// 选择文本字体
if (m_cTextFont.GetSafeHandle() != NULL)
{
m_cTextFont.Detach();
}
if (m_bUnderLine)
{
m_cTextFont.Attach(m_hHoverFont);
}
else
{
m_cTextFont.Attach(m_hLeaveFont);
}
pDC->SelectObject(&m_cTextFont);
// 设置文本颜色
if (m_nMouseState == MOUSE_HOVER)
{
pDC->SetTextColor(m_clrHoverColor);
}
else
{
pDC->SetTextColor(m_clrLeaveColor);
}
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
5. 添加消息ON_WM_ERASEBKGND,在响应函数里,绘制控件背景
说明:控件背景可能是纯色,也可能非纯色。纯色好处理,简单填充即可;如果是非纯色(比如渐变或贴图),则需要把父窗口的背景抠出,在子窗口重绘一次。
当然,最好的方法是利用父窗口DC,在子窗口辅助绘制(这是珽震的高招)。
BOOL CStaticEx::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CRect rcClient;
GetClientRect(&rcClient);
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
CBitmap cNewBmp;
cNewBmp.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height());
CBitmap*pOldBmp = NULL;
pOldBmp = dcMem.SelectObject(&cNewBmp);
CWnd* parent = GetParent() ;
if (parent != NULL && m_parent_dc != NULL)
{
CRect bound_in_parent ;
GetWindowRect(&bound_in_parent) ;
parent->ScreenToClient(&bound_in_parent) ;
dcMem.BitBlt(
0, 0,
rcClient.Width(), rcClient.Height(),
m_parent_dc,
bound_in_parent.left, bound_in_parent.top,
SRCCOPY
) ;
}
else
{
dcMem.FillSolidRect(0, 0, rcClient.Width(), rcClient.Height(), m_clrBkColor);
}
pDC->BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &dcMem, 0, 0, SRCCOPY);
// 选择旧设备
dcMem.SelectObject(pOldBmp);
// 释放资源
if (dcMem.GetSafeHdc() != NULL)
{
dcMem.DeleteDC();
}
if (cNewBmp.GetSafeHandle() != NULL)
{
cNewBmp.DeleteObject();
}
return TRUE;
// return CStatic::OnEraseBkgnd(pDC);
}
6. 添加消息ON_WM_SETCURSOR,设置鼠标在控件范围内的光标(通常为手型光标)
BOOL CStaticEx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
::SetCursor(m_hLinkCursor);
return TRUE;
}
7. 添加消息ON_WM_LBUTTONDOWN,设置点击后的动作
说明:m_lpClickCb是一个回调函数指针,由调用者传入。
void CStaticEx::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (m_lpClickCb != NULL)
{
m_lpClickCb();
}
CStatic::OnLButtonDown(nFlags, point);
}
至此,一个“类似于超链接”的静态控件已完成。效果图如下: