一、實現方法
首先讓我們先來了解一下畫控件的基本原理和過程,也許這個纔是本文的原意。大家都知道Windows中所有可視的東西都是畫出來的,那麼這個畫畫的內部過程又是怎樣的呢?一般畫Windows控件的過程分爲三大部分:一是在WM_MEASUREITEM消息影射函數中設置當前要畫的Item的大小尺寸;二是在WM_DRAWITEM消息影射函數中根據Item的大小尺寸來畫該Item(圖標/位圖/字符串等);三是在WM_PAINT消息映射函數中不斷的繪製當前的控件內容。下面我們針對CBSColorComboBox類的這幾個過程來做個簡單的介紹:
在WM_MEASUREITEM消息影射函數中設定Item的大小尺寸的時候,我們只需要設置Item的高度即可。這裏的高度我們設置爲2倍的系統小圖標(SMALL ICON)的高度,其尺寸用::GetSystemMetrics(SM_CXSMICON)取得。
Visual C++的程序開發人員可以在Item的矩形區域內畫各種各樣的信息,例如:圖標/位圖/字符串等等。那麼有人會疑問:"我們用什麼來畫?我們在哪裏畫?又如何來畫呢?"。答案其實都在這個LPDRAWITEMSTRUCT結構中。hDC成員爲設備上下文環境(HDC),獲得了該設備句柄也就意味着我們擁有了畫任何位圖/圖標/文本的能力;那麼接下來的問題就是:我們在哪裏來畫呢?答案也很簡單:獲得LPDRAWITEMSTRUCT結構中Item的矩形區域(rcItem),那麼這就是你施展才華的空間了,要充分利用它哦!
最後一步就是如何來畫的問題了,說白了就是如何分配每個元素的空間,如何在它們各自的空間上畫出你想要的東西。按照常規一般分別計算出ICON所佔的矩形區域/文本所佔的矩形區域/位圖的矩形區域,如果你還有其他元素那麼也應該計算出該元素所佔的矩形區域/位圖所佔的矩形區域。接下來的一切都很簡單了,不外乎CDC類的幾個常用函數:畫圖標用DrawIcon()、畫位圖用BitBlt()、畫文字用DrawText()等函數。如果你覺得視覺上還不夠COOL,你還可以來設置各個Item的文本顏色,背景顏色,以及圖標的突起和凹陷的視覺效果。
不過在上述過程中需要注意三個問題,一是爲了消除不斷繪製所帶來的閃爍現象,需要在WM_ERASEBKGND消息響應中作些特殊處理;在WM_PAINT消息中直接把組合框的客戶區當成一幅位圖來不斷更新,而不是對ICON區域和文本區域分別重繪。二是每當用戶改變了組合框的當前內容後,在畫新的Item之前一定要記得清除前次組合框內的內容。三是如果想選擇更多的顏色,那麼只要選擇組合框中的最後一個Item(More Colors)即可,這個Item是爲用戶自定義顏色而專門設置的。
二、編程步驟
1、啓動Visual C++6.0,生成一個基於對話框的項目,將該項目命名爲"WW";
2、使用Class Wizard新建一個類CBSColorComboBox,其基類選擇爲CComboBox類;
3、在程序的對話框中放置一個ComboBox控件,使用CLASSWIZARD添加相應的CComboBox類成員變量,然後將該成員變量的類型修改爲CBSColorComboBox;
4、添加代碼,編譯運行程序。
三、程序代碼
//////////////////////////////////////////////////////////////CBSColorComboBox類的頭文件;
#if !defined(_BS_BSCOLORCB)
#define _BS_BSCOLORCB
#include <afxtempl.h>
//系統常用顏色的自定義名稱
const static char* strColorName[] =
{
"crSCROLLBAR","crBACKGROUND","crACTIVECAPTION", "crINACTIVECAPTION", "crMENU", "crWINDOW", "crWINDOWFRAME", "crMENUTEXT", "crWINDOWTEXT", "crCAPTIONTEXT", "crACTIVEBORDER","crINACTIVEBORDER", "crAPPWORKSPACE", "crHIGHLIGHT", "crHIGHLIGHTTEXT", "crBTNFACE", "crBTNSHADOW", "crGRAYTEXT", "crBTNTEXT", "crINACTIVECAPTIONTEXT",
"crBTNHIGHLIGHT","cr3DDKSHADOW", "cr3DLIGHT", "crINFOTEXT", "crINFOBK",
"crHOTLIGHT","crGRADIENTACTIVECAPTION", crGRADIENTINACTIVECAPTION"
};
typedef struct BSCBITEM
{
int iIndex;
COLORREF crColor;
LPCTSTR lpCaption;
}BSCBITEM, *LPBSCBITEM;
class CBSColorComboBox : public CComboBox
{
DECLARE_DYNCREATE(CBSColorComboBox)
public:
CBSColorComboBox();
virtual ~CBSColorComboBox();
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
//初始化組合框(第一個被調用的函數)
void InitBSColorCB(void);
//得到當前的顏色值或R/G/B值
COLORREF GetColor();
void GetRGBValue(int* R, int* G, int* B);
public:
//{{AFX_VIRTUAL(CBSColorComboBox)
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
//}}AFX_VIRTUAL
protected:
bool m_bOverControl; //鼠標的狀態(是否處於按鈕上)
int iIconX, iIconY; //SMALL ICON的大小尺寸
COLORREF m_crColor; //當前選中的顏色
CList<LPBSCBITEM, LPBSCBITEM> m_crItem;
void OnCBPaint(CDC* pDC);
LPBSCBITEM GetItem(int iIndex = 0);
protected:
//{{AFX_MSG(CBSColorComboBox)
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnPaint();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnSelchange();
afx_msg void OnSelendok();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#endif // !defined(_BS_BSCOLORCB)
///////////////////////////////////////////////////////////////CBSColorComboBox的實現文件;
#include "stdafx.h"
#include "BSColorComboBox.h"
CBSColorComboBox::CBSColorComboBox()
{
//當前鼠標是否在對象上
m_bOverControl = false;
//小圖標尺寸
iIconX = ::GetSystemMetrics(SM_CXSMICON);
iIconY = ::GetSystemMetrics(SM_CYSMICON);
}
CBSColorComboBox::~CBSColorComboBox()
{
while(!m_crItem.IsEmpty())
{
LPBSCBITEM lpItem = m_crItem.RemoveHead();
delete lpItem;
}
}
BOOL CBSColorComboBox::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
DWORD dw = dwStyle;
if( !CComboBox::Create(dw, rect, pParentWnd, nID) )
return false;
CFont * font = CFont::FromHandle((HFONT)::GetStockObject(DEFAULT_GUI_FONT));
SetFont(font);
return true;
}
IMPLEMENT_DYNCREATE(CBSColorComboBox, CComboBox)
BEGIN_MESSAGE_MAP(CBSColorComboBox, CComboBox)
//{{AFX_MSG_MAP(CBSColorComboBox)
ON_WM_ERASEBKGND()
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_MOUSEMOVE()
ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)
ON_CONTROL_REFLECT(CBN_SELENDOK, OnSelendok)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CBSColorComboBox::InitBSColorCB(void)
{
int iMinColor = COLOR_SCROLLBAR,
iMaxColor = COLOR_BTNHIGHLIGHT;
if(WINVER >= 0x0400)
iMaxColor = COLOR_INFOBK;
if(WINVER >= 0x0500)
iMaxColor = 28;
//初始化CB顏色列表框的Item(常見的SysColor值)
for(int iLoop = iMinColor; iLoop <= iMaxColor; ++iLoop)
{
LPBSCBITEM lpItem = new BSCBITEM;
lpItem->iIndex = AddString(strColorName[iLoop]);
lpItem->crColor = ::GetSysColor(iLoop);
lpItem->lpCaption = strColorName[iLoop];
//
if(m_crItem.IsEmpty())
m_crItem.AddHead(lpItem);
else
m_crItem.AddTail(lpItem);
}
//該Item是爲了用戶自定義顏色而設置
LPBSCBITEM lpItem = new BSCBITEM;
lpItem->iIndex = AddString("More Colors");
lpItem->crColor = RGB(213, 233, 249);
lpItem->lpCaption = "More Colors";
if(m_crItem.IsEmpty())
m_crItem.AddHead(lpItem);
else
m_crItem.AddTail(lpItem);
//初始化當前顏色
m_crColor = m_crItem.GetHead()->crColor;
}
BOOL CBSColorComboBox::OnEraseBkgnd(CDC* pDC)
{
ASSERT(pDC->GetSafeHdc());
return false;
}
void CBSColorComboBox::OnPaint()
{
CPaintDC dc(this);
OnCBPaint(&dc);
}
void CBSColorComboBox::OnCBPaint(CDC* pDC)
{
ASSERT(pDC->GetSafeHdc());
//繪製客戶區
CDC dMemDC;
dMemDC.CreateCompatibleDC(pDC);
dMemDC.SetMapMode(pDC->GetMapMode());
//畫動作
CBitmap mNewBmp;
RECT rc;
GetClientRect(&rc);
mNewBmp.CreateCompatibleBitmap(pDC, rc.right - rc.left, rc.bottom - rc.top);
CBitmap* pOldBmp = dMemDC.SelectObject(&mNewBmp);
//子類可以以friend方式來訪問父類的protected成員變量和函數
CWnd::DefWindowProc(WM_PAINT, (WPARAM)dMemDC.m_hDC, 0);
pDC->BitBlt(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, &dMemDC,rc.left ,rc.top, SRCCOPY);
//恢復
dMemDC.SelectObject(pOldBmp);
pOldBmp->DeleteObject();
dMemDC.DeleteDC();
GetWindowRect(&rc);
ScreenToClient(&rc);
pDC->DrawEdge(&rc, (m_bOverControl ? BDR_RAISEDINNER: BDR_SUNKENINNER), BF_RECT);
}
void CBSColorComboBox::OnTimer(UINT nIDEvent)
{
if(nIDEvent == 888 && IsWindowEnabled())
{
CPoint point;
::GetCursorPos(&point);
CRect rect;
GetWindowRect(&rect);
if(rect.PtInRect(point))
{
m_bOverControl = true;
}
else
{
m_bOverControl = false;
KillTimer(nIDEvent);
}
}
CComboBox::OnTimer(nIDEvent);
}
void CBSColorComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
lpMeasureItemStruct->itemHeight = iIconY + 5;
}
void CBSColorComboBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS->CtlType == ODT_COMBOBOX);
//畫筆
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
ASSERT(pDC->GetSafeHdc());
//繪製區
RECT rc = lpDIS->rcItem;
RECT rcIcon(rc), rcTxt(rc);
//當前的Item索引號
LPBSCBITEM lpItem = GetItem(lpDIS->itemID);
if(lpItem != NULL)
{
//畫顏色Icon
rcIcon.right = rcIcon.left + iIconX;
rcIcon.top += (rc.bottom - rc.top - iIconY) / 2;
rcIcon.bottom = rcIcon.top + iIconY;
pDC->FillSolidRect(rcIcon.left, rcIcon.top,
rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, lpItem->crColor);
pDC->DrawEdge(&rcIcon, BDR_RAISEDINNER, BF_RECT);
//開始畫文字
int nOldBkMode = pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(0, 0, 0));
rcTxt.left = rcIcon.right + 5;
rcTxt.top = rcIcon.top;
pDC->DrawText(lpItem->lpCaption, &rcTxt,
DT_VCENTER | DT_END_ELLIPSIS | DT_NOCLIP | DT_SINGLELINE);
pDC->SetBkMode(nOldBkMode);
}
}
void CBSColorComboBox::OnMouseMove(UINT nFlags, CPoint point)
{
m_bOverControl = true;
SetTimer(888, 100, NULL);
CComboBox::OnMouseMove(nFlags, point);
}
LPBSCBITEM CBSColorComboBox::GetItem(int iIndex)
{
//當前的Item索引號
POSITION pos = m_crItem.FindIndex(iIndex);
if(pos)
{
LPBSCBITEM lpItem = m_crItem.GetAt(pos);
ASSERT(lpItem);
return lpItem;
}
else
return (LPBSCBITEM)NULL;
}
COLORREF CBSColorComboBox::GetColor()
{
if(IsWindowEnabled())
return m_crColor;
else
{
return (m_crColor = GetItem(this->GetCurSel())->crColor);
}
}
void CBSColorComboBox::GetRGBValue(int* R, int* G, int* B)
{
*R = GetRValue((DWORD)m_crColor);
*G = GetGValue((DWORD)m_crColor);
*B = GetBValue((DWORD)m_crColor);
}
void CBSColorComboBox::OnSelchange()
{
int iIndex = GetCurSel();
if(iIndex != CB_ERR && iIndex >= 0)
{
CDC* pDC = this->GetDC();
//繪製區
RECT rc;
int iScrollX = ::GetSystemMetrics(SM_CXVSCROLL);
GetClientRect(&rc);
pDC->FillSolidRect(rc.left + 2, rc.top + 2, rc.right - rc.left - iScrollX - 4, rc.bottom - rc.top - 2,
::GetSysColor(COLOR_WINDOW));
RECT rcIcon(rc), rcTxt(rc);
//當前的Item索引號
LPBSCBITEM lpItem = GetItem(iIndex);
if(lpItem != NULL)
{
m_crColor = lpItem->crColor;
//畫顏色Icon
rcIcon.left += 2;
rcIcon.right = rcIcon.left + iIconX;
rcIcon.top += (rc.bottom - rc.top - iIconY) / 2;
rcIcon.bottom = rcIcon.top + iIconY;
pDC->FillSolidRect(rcIcon.left, rcIcon.top,
rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, lpItem->crColor);
pDC->DrawEdge(&rcIcon, BDR_RAISEDINNER, BF_RECT);
//開始畫文字
int nOldBkMode = pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(RGB(0, 0, 0));
rcTxt.left = rcIcon.right + 5;
rcTxt.top = rcIcon.top;
CFont* font = CFont::FromHandle((HFONT)::GetStockObject(DEFAULT_GUI_FONT));
pDC->SelectObject(font);
pDC->DrawText(lpItem->lpCaption, &rcTxt,
DT_VCENTER | DT_END_ELLIPSIS | DT_NOCLIP | DT_SINGLELINE);
pDC->SetBkMode(nOldBkMode);
}
pDC->DeleteDC();
}
}
void CBSColorComboBox::OnSelendok()
{
int iIndex = this->GetCurSel();
LPBSCBITEM lpTmpItem = GetItem(iIndex);
if(lpTmpItem != NULL)
{
if(lpTmpItem->lpCaption == "More Colors")
{
CColorDialog crDlg(RGB(255, 0, 0), CC_FULLOPEN);
int iRet = crDlg.DoModal();
if(iRet == IDOK)
{
m_crColor = crDlg.GetColor();
LPBSCBITEM lpItem = m_crItem.GetTail();
ASSERT(lpItem);
lpItem->crColor = m_crColor;
Invalidate();
}
}
}
}
四、小結
上面的代碼也適用於菜單等大多數控件的自畫過程,其實本書在前面一些實例中也已經講述了控件自畫的內容,讀者朋友們可以結合起來一起學習,相信一定能夠把控件的自畫這一內容掌握的一清二楚的。