参考文章: 白乔原创: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 DrawItemvoid 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.