相信读者朋友们对OFFICE2000一定非常熟悉吧,它里面的东东可真不少,不管是活泼可爱的"大眼夹",还是各种平面造型的Windows控件都很受广大用户喜欢。那么这次就让我们来模仿它做个十分常用的控件:"颜色组合框"。如果你现在正在做关于字处理类的软件时这个"东东"一定对你有用。程序编译运行后的界面效果如图一所示:
图一、颜色选择组合框 | 一、实现方法
首先让我们先来了解一下画控件的基本原理和过程,也许这个才是本文的原意。大家都知道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 //系统常用颜色的自定义名称 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 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(); } } } } 四、小结
上面的代码也适用于菜单等大多数控件的自画过程,其实本书在前面一些实例中也已经讲述了控件自画的内容,读者朋友们可以结合起来一起学习,相信一定能够把控件的自画这一内容掌握的一清二楚的。
|