这次来说说在窗口类里用到的按钮类的设计。
还是先看头文件:
#include "WzdImage.h"
class CWzdButton : public CButton
{
DECLARE_DYNAMIC(CWzdButton)
protected:
bool m_bExpand; // 能否拉伸
bool m_isHovering; // 是否悬停
COLORREF m_crTextColor; // 字体颜色
CWzdImage m_imageButton; // 按钮图片
public:
CWzdButton();
virtual ~CWzdButton();
// 加载位图
bool SetButtonImage(LPCTSTR pszFileName, bool bExpandImage = false);
bool SetButtonImage(HINSTANCE hInstance, LPCTSTR pszResourceName, bool bExpandImage = false);
bool SetTextColor(COLORREF crTextColor); // 设置字体颜色
bool FixButtonSize(); // 调整按钮大小
protected:
virtual void PreSubclassWindow(); // 子类化
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); // 界面绘画函数
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg LRESULT OnMouseLeave(WPARAM wparam, LPARAM lparam);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
};
同样需要加载图片,因此包含WzdImage.h。
m_bExpand控制按钮图片能否根据按钮大小拉伸,m_bHovering标记鼠标是否悬停。
接着先看看构造函数,默认不拉伸、没有悬停、字体颜色为黑色。
CWzdButton::CWzdButton()
: m_bExpand(false)
, m_isHovering(false)
, m_crTextColor(RGB(0,0,0))
{
}
同样有两种加载图片的方式,资源加载和路径加载,还有设置字体颜色的接口。
bool CWzdButton::SetButtonImage(LPCTSTR pszFileName, bool bExpandImage/*=false*/)
{
if (NULL == pszFileName) return false;
// 加载位图
m_bExpand = bExpandImage;
if (!m_imageButton.IsNull())
{
m_imageButton.DestroyImage();
}
m_imageButton.LoadImage(pszFileName);
if (!m_bExpand)
FixButtonSize(); // 调整按钮大小
if (GetSafeHwnd())
Invalidate(FALSE);
return true;
}
bool CWzdButton::SetButtonImage(HINSTANCE hInstance,LPCTSTR pszResourceName, bool bExpandImage/*=false*/)
{
if (NULL == pszResourceName) return false;
// 加载位图
m_bExpand = bExpandImage;
if (!m_imageButton.IsNull())
{
m_imageButton.DestroyImage();
}
m_imageButton.LoadImage(hInstance,pszResourceName);
if (!m_bExpand)
FixButtonSize(); // 调整按钮大小
if (GetSafeHwnd())
Invalidate(FALSE);
return true;
}
bool CWzdButton::SetTextColor(COLORREF crTextColor)
{
m_crTextColor = crTextColor;
if (GetSafeHwnd()) Invalidate(FALSE);
return true;
}
FixButtonSize(),用于在按钮不拉伸时,改变按钮的大小。为什么按钮背景图宽度要除去4呢?默认采用的图片都是4种按钮背景(正常、悬停、点击、无效)水平排列,所以按钮大小就是图片大小的四分之一。
bool CWzdButton::FixButtonSize()
{
if (!m_imageButton.IsNull() && GetSafeHwnd())
{
SetWindowPos(NULL, 0, 0, m_imageButton.GetWidth() / 4, m_imageButton.GetHeight(), SWP_NOMOVE);
return true;
}
return false;
}
可以看到有PreSubclassWindow(),它是在按钮不是动态创建的时候,在创建之前会调用到这个函数。在这里只改变风格,让按钮支持自绘。
void CWzdButton::PreSubclassWindow()
{
SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
那动态创建的时候自然就是在OnCreate()里改变风格。
int CWzdButton::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (-1 == CButton::OnCreate(lpCreateStruct)) return -1;
SetButtonStyle(GetButtonStyle() | BS_OWNERDRAW);
return 0;
}
最关键的就是重写DrawItem()绘制按钮。先绘制背景,因为在WzdDialog窗口中设置了WS_CLIPCHILDREN 属性,窗口不对子控件背景进行重绘。而这里使用的是PNG透明图片,不自绘背景会是默认画刷的颜色,而我们需要窗口的背景色(只支持单色背景)。接着是根据按钮状态绘画不同的图像,最后绘画文字。
void CWzdButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rcClient;
GetClientRect(rcClient);
//CRect ParRect;
//GetWindowRect(&ParRect);
//GetParent()->ScreenToClient(&ParRect);
// 获取父窗口的背景,自己绘制背景,防止窗口背景重绘时的闪烁
CDC* BkDc= GetParent()->GetDC();
pDC->BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
BkDc, 0, 0, SRCCOPY);
bool bDisable = !!((lpDrawItemStruct->itemState & ODS_DISABLED));
bool bButtonDown = !!((lpDrawItemStruct->itemState & ODS_SELECTED));
if (!m_imageButton.IsNull())
{
// 计算位图位置
int nWidth = m_imageButton.GetWidth() / 4;
int nDrawPos = 0;
if (bDisable)
nDrawPos = nWidth * 3;
else if (bButtonDown)
nDrawPos = nWidth * 2;
else if (m_isHovering)
nDrawPos = nWidth * 1;
// 绘画按钮
if (!m_bExpand)
m_imageButton.DrawImage(pDC , 0, 0, rcClient.Width(),
rcClient.Height(), nDrawPos, 0);
else
m_imageButton.DrawImage(pDC, 0, 0, rcClient.Width(),
rcClient.Height(), nDrawPos, 0, nWidth, m_imageButton.GetHeight());
}
// 绘画字体
CString strText;
GetWindowText(strText);
rcClient.top+=1;
pDC->SetBkMode(TRANSPARENT);
if (bDisable)
pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
else
pDC->SetTextColor(m_crTextColor);
pDC->DrawText(strText, strText.GetLength(), rcClient,
DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
}
响应鼠标移动,重载OnMouseMove。m_isHovering是鼠标悬停在按钮上,TrackMouseEvent那段代码是因为MouseLeave的消息发送存在问题,需要自己来触发MouseLeave消息,实现鼠标离开时m_isHovering状态的改变。
void CWzdButton::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_isHovering)
{
m_isHovering = true;
Invalidate(FALSE);
// 注册消息
TRACKMOUSEEVENT TrackMouseEvent;
TrackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
TrackMouseEvent.dwFlags = TME_LEAVE;
TrackMouseEvent.hwndTrack = GetSafeHwnd();
TrackMouseEvent.dwHoverTime = HOVER_DEFAULT;
_TrackMouseEvent(&TrackMouseEvent);
}
CButton::OnMouseMove(nFlags, point);
}
void CWzdButton::OnMouseLeave()
{
m_isHovering = false;
Invalidate(FALSE);
CButton::OnMouseLeave();
}