参考文章:
白乔原创:VC之美化界面篇
CButtonST的源码
源码下载:
Demo Project
当一个具有Owner-Draw风格的button control, combo-box control, list-box control, or menu需要显示外观时,会发送一条WM_DRAWITEM消息至它的隶属窗口。
前面讲的WM_ERASEBKGND, WM_CTLCOLOR, NM_CUSTOMDRAW消息,只能对控件的外观进行小打小闹的修改。而WM_DRAWITEM消息则是Windows将控件的外观完全交由程序员绘制,所以可以完全发挥天马行空的Idea。
WM_DRAWITEM消息为通告消息,所以可以通过消息反射机制交由控件类本身来处理,而MFC的CButton类,CComboBox类,CListBox类对WM_DRAWITEM反射消息交由虚函数DrawItem处理(可由CButton::OnChildNotify, CComboBox::OnChildNotify, CListBox::OnChildNotify源码看出)。所以,一般而言我们会override这些控件类的DrawItem函数实现对WM_DRAWITEM消息的处理。
以下为通过WM_DRAWITEM消息自绘和标准MFC Push Button一模一样的按钮。下图中的Button1就是通过以下代码绘制的:
通过MFC类向导增加MFC Class: CMyButton,继承自CButton。
class CMyButton : public CButton
{
DECLARE_DYNAMIC(CMyButton)
public:
CMyButton();
virtual ~CMyButton();
protected:
DECLARE_MESSAGE_MAP()
protected:
virtual void PreSubclassWindow();
void DrawBorder(CDC *pDC, CRect *pRect);
public:
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/);
};
1.override PreSubclassWindow
将控件更改为Owner-Draw风格。
void CMyButton::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
ModifyStyle(BS_TYPEMASK, BS_OWNERDRAW, SWP_FRAMECHANGED);
CButton::PreSubclassWindow();
}
2.override PreTranslateMessage
将鼠标双击消息解释为两次鼠标单击消息。
BOOL CMyButton::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if (pMsg->message == WM_LBUTTONDBLCLK)
pMsg->message = WM_LBUTTONDOWN;
return CButton::PreTranslateMessage(pMsg);
}
3.override DrawItem
void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
//
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
pDC->SetBkMode(TRANSPARENT);
CRect rcBtn(lpDrawItemStruct->rcItem);
//
BOOL bIsFocused = lpDrawItemStruct->itemState & ODS_FOCUS;
BOOL bIsPressed = lpDrawItemStruct->itemState & ODS_SELECTED;
BOOL bIsDisabled = lpDrawItemStruct->itemState & ODS_DISABLED;
// Draw background
CBrush brushBk(::GetSysColor(COLOR_BTNFACE));
if (bIsFocused)
{
rcBtn.DeflateRect(1, 1);
pDC->FillRect(&rcBtn, &brushBk);
rcBtn.DeflateRect(-1, -1);
CBrush brushFrame(RGB(0, 0, 0));
pDC->FrameRect(&rcBtn, &brushFrame);
}
else
{
pDC->FillRect(&rcBtn, &brushBk);
}
// Draw border
if (bIsPressed)
{
rcBtn.DeflateRect(1, 1);
CBrush brBtnShadow(::GetSysColor(COLOR_BTNSHADOW));
pDC->FrameRect(&rcBtn, &brBtnShadow);
rcBtn.DeflateRect(-1, -1);
}
else if (bIsFocused)
{
rcBtn.DeflateRect(1, 1);
DrawBorder(pDC, &rcBtn);
rcBtn.DeflateRect(-1, -1);
}
else
{
DrawBorder(pDC, &rcBtn);
}
//// Draw focus rect
if (bIsFocused)
{
rcBtn.DeflateRect(4, 4);
pDC->DrawFocusRect(&rcBtn);
rcBtn.DeflateRect(-4, -4);
}
// Draw text
CString strText;
GetWindowText(strText);
CRect rcText(rcBtn);
if (bIsPressed)
{
rcText.left += 2;
}
else
{
rcText.bottom -= 2;
}
if (bIsDisabled)
{
rcText.OffsetRect(1, 1);
pDC->SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
pDC->DrawText(strText, rcText, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
rcText.OffsetRect(-1, -1);
pDC->SetTextColor(::GetSysColor(COLOR_3DSHADOW));
pDC->DrawText(strText, rcText, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
}
else
{
pDC->DrawText(strText, &rcText, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
}
}
void CMyButton::DrawBorder(CDC *pDC, CRect *pRect)
{
CPen penBtnHiLight(PS_SOLID, 0, GetSysColor(COLOR_BTNHILIGHT)); // White
CPen pen3DLight(PS_SOLID, 0, GetSysColor(COLOR_3DLIGHT)); // Light gray
CPen penBtnShadow(PS_SOLID, 0, GetSysColor(COLOR_BTNSHADOW)); // Dark gray
CPen pen3DDKShadow(PS_SOLID, 0, GetSysColor(COLOR_3DDKSHADOW)); // Black
// Draw top-left borders
// White line
CPen* pOldPen = pDC->SelectObject(&penBtnHiLight);
pDC->MoveTo(pRect->left, pRect->bottom-1);
pDC->LineTo(pRect->left, pRect->top);
pDC->LineTo(pRect->right, pRect->top);
// Light gray line
pDC->SelectObject(pen3DLight);
pDC->MoveTo(pRect->left+1, pRect->bottom-1);
pDC->LineTo(pRect->left+1, pRect->top+1);
pDC->LineTo(pRect->right, pRect->top+1);
// Draw bottom-right borders
// Black line
pDC->SelectObject(pen3DDKShadow);
pDC->MoveTo(pRect->left, pRect->bottom-1);
pDC->LineTo(pRect->right-1, pRect->bottom-1);
pDC->LineTo(pRect->right-1, pRect->top-1);
// Dark gray line
pDC->SelectObject(penBtnShadow);
pDC->MoveTo(pRect->left+1, pRect->bottom-2);
pDC->LineTo(pRect->right-2, pRect->bottom-2);
pDC->LineTo(pRect->right-2, pRect->top);
//
pDC->SelectObject(pOldPen);
}
解释:
9~11行 对于PushButton而言,有四种状态:Normal, Disabled, Focused, Pressed.
Normal状态下,lpDrawItemStruct->itemState = 0x0000.
Disabled状态下,lpDrawItemStruct->itemState = ODS_DISABLED.
Focused状态下,lpDrawItemStruct->itemState = ODS_FOCUS.
Pressed状态下,lpDrawItemStruct->itemState = ODS_FOCUS | ODS_SELECTED.
因为按下按钮肯定是拥有焦点。
13~27行 绘制按钮的背景
如果按钮拥有焦点,先在按钮大小上下左右各收缩1的矩形内刷背景(17~19行),再在按钮外围绘制黑色矩形边框(21~22行)。
如果按钮不拥有焦点,则在按钮大小范围内刷背景(26行)。
30~47行 绘制外围边框
如果按钮被按下,则在按钮大小上下左右各收缩1的矩形上绘制灰色边框。
如果按钮拥有焦点,则在按钮大小上下左右各收缩1的矩形上绘制3D边框,具体见函数DrawBorder。
否则,则在按钮大小矩形上绘制3D边框,具体见函数DrawBorder。
49~55行 绘制焦点矩形,在按钮大小上下左右各收缩4的矩形上绘制。
57~81行 绘制按钮Caption
67行意在使Caption在垂直方向上居中。
63行:如果按钮被按下,则Caption相对于正常状态下向左及下各偏1。(67行未执行则保证向下偏移1)
69~77行:对Disabled按钮,使Caption呈阴影灰色状态。
80行:绘制Caption.