一.前言
很多时候,我们需要让用户在软件上选择颜色,那么一个“颜色选择器”就肯定需要了,本例程就是使用普通的按钮(Button)控件来实现颜色选择器。
首先来看一下最后的效果图:
从上图可以看出,这个“颜色选择器”分3个部分,1是可以显示当前选中颜色的按钮;2是点击按钮时在下方弹出的颜色选择部分;3是点击“更多颜色”时弹出的一个选择颜色的对话框。下面分别说说各个部分的实现。
二.自绘按钮
在这里我首先创建了一个CColorButton类,继承自CButton,要实现自绘,首先是要加入BS_OWNERDRAW样式
BOOL CColorButton::PreCreateWindow(CREATESTRUCT& cs)
{
BOOL bRet=CButton::PreCreateWindow(cs);
ColorButtonInit();
return bRet;
}
void CColorButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
ColorButtonInit();
}
void CColorButton::ColorButtonInit()
{
m_bTracking=false;
m_bOver=m_bDown=m_bDisable=false;
m_bDisable=IsWindowEnabled()?FALSE:TRUE;
ModifyStyle(NULL,BS_OWNERDRAW);
}
然后重载DrawItem函数:
void CColorButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
DrawButton(lpDrawItemStruct->hDC);
}
这里的DrawButton是一个单独的函数,用来画出按钮,这里我使用了DrawThemeBackground来画按钮背景,这个API函数的优点是可以根据当前系统主题来画出控件:
void CColorButton::DrawButton(HDC hDestDC)
{
CRect rc;
GetClientRect(rc);
int nWindth=rc.Width();
int nHeight=rc.Height();
HDC hDC=CreateCompatibleDC(hDestDC);//创建兼容DC,采用双缓冲画出
HBITMAP hBitmap=CreateCompatibleBitmap(hDestDC,nWindth,nHeight);
HBITMAP hOldBitmap=(HBITMAP)SelectObject(hDC,hBitmap);
//画出整个控件背景
HBRUSH hbr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
FillRect(hDC,&rc,hbr);
DeleteObject(hbr);
HTHEME hTheme=OpenThemeData(m_hWnd,L"Button");
if(hTheme){
if(m_bDisable){//禁止状态
DrawThemeBackground (hTheme,hDC, BP_PUSHBUTTON, PBS_DISABLED,&rc,NULL);
}else if(m_bDown){//按下状态
DrawThemeBackground (hTheme,hDC, BP_PUSHBUTTON, PBS_PRESSED,&rc,NULL);
}else if(m_bOver){//热点状态
DrawThemeBackground (hTheme,hDC, BP_PUSHBUTTON, PBS_HOT,&rc,NULL);
}else{//普通状态
DrawThemeBackground (hTheme,hDC, BP_PUSHBUTTON, PBS_NORMAL,&rc,NULL);
}
CloseThemeData (hTheme);
}else{
if(m_bDisable){
DrawFrameControl (hDC,rc,DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_INACTIVE);
}else if(m_bDown){
DrawFrameControl (hDC,rc,DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_PUSHED);
}else if(m_bOver){
DrawFrameControl (hDC,rc,DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_HOT);
}else{
DrawFrameControl (hDC,rc,DFC_BUTTON, DFCS_BUTTONPUSH);
}
}
//画出颜色显示区
rc=CRect(4,4,nWindth-17,nHeight-4);
hbr=CreateSolidBrush(0xFFFFFF);//颜色显示区的背景
FillRect(hDC,&rc,hbr);
DeleteObject(hbr);
HPEN hPen=CreatePen(PS_SOLID,1,GetSysColor(COLOR_WINDOWFRAME));//颜色显示区的边框
FrameRect(hDC,&rc,(HBRUSH)hPen);
DeleteObject(hPen);
rc.InflateRect(-2,-2);
hbr=CreateSolidBrush(m_CurColor);//当前的颜色
FillRect(hDC,&rc,hbr);
DeleteObject(hbr);
//画出下拉的小三角
int w=7;
int h=4;
int x=nWindth-w-7;
int y=(nHeight-h)/2;
for(int i=0;i<h;i++){
MoveToEx(hDC,x,y,NULL);
LineTo(hDC,x+w,y);
x++;
y++;
w=w-2;
}
//复制到控件的DC上
BitBlt(hDestDC,0,0,nWindth,nHeight,hDC,0,0,SRCCOPY);
//删除资源,释放内存
SelectObject(hDC,hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hDC);
}
当然还需要在按钮的鼠标消息里记录按钮的状态,但这些不是本例程的主要部分,在文章里就不全部写出了,大家可以下载完整源码查看。
最后响应BN_CLICKED消息,弹出位于下方的颜色选择控件。
三.绘制位于下方的颜色选择控件
这个颜色选择控件稍微复杂一些,也可以算作本例程里一些有用的东西,我是继承自CWnd创建了一个新的窗口类,采用DirectUI的方式画出各个颜色小方块,即各个“子控件”事实上是不存在的,只是一些逻辑区域,直接画在父窗口上。
首先我定义了一个数据结构来保存各个“子控件”的信息,并且加入到数组
typedef struct tagCOLORITEM
{
COLORREF color;
RECT rect;
BOOL bCheck;
}COLORITEM,*LPCOLORITEM;
CArray <COLORITEM,COLORITEM&> m_ItemArray;
创建完窗口后创建各个“子控件”:
CreateItem(0x000000,_T("黑色"));
CreateItem(0x800000,_T("藏青"));
int CColorWnd::CreateItem(COLORREF color,CString strName)
{
int nIndex=m_nCount;
COLORITEM item;
item.bCheck=color==m_CurColor;
item.color=color;
item.rect=CRect(m_nItemX,m_nItemY,m_nItemX+18,m_nItemY+18);
m_ItemArray.Add(item);
m_nCount++;
m_nItemX+=18;
if(m_nCount%7==0){
m_nItemY+=18;
m_nItemX=0;
}
m_ToolTip.AddTool(this,strName,&item.rect,100+nIndex);
return nIndex;
}
最后画出各个“子控件”:
void CColorWnd::UpdateCache()
{
CRect rc;
GetClientRect(rc);
int nWindth=rc.Width();
int nHeight=rc.Height();
HDC hDC=::GetDC(m_hWnd);
m_hCacheDC=CreateCompatibleDC(hDC);//创建兼容DC,采用双缓冲画出
m_hCacheBitmap=CreateCompatibleBitmap(hDC,nWindth,nHeight);
m_hOldBitmap=(HBITMAP)SelectObject(m_hCacheDC,m_hCacheBitmap);
m_hOldFont=(HFONT)SelectObject(m_hCacheDC,(HFONT)GetStockObject(DEFAULT_GUI_FONT));
SetBkMode(m_hCacheDC,TRANSPARENT);
m_hOrderPen1=CreatePen(PS_SOLID,1,0xA0A0A0);
m_hOrderPen2=CreatePen(PS_SOLID,1,0xFFFFFF);
m_hBackBrush1=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
m_hBackBrush2=CreateSolidBrush(0xFFFFFF);
FillRect(m_hCacheDC,&rc,m_hBackBrush1);
int x=4;
int y=nHeight-29;
HPEN hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen1);
MoveToEx(m_hCacheDC,x,y,NULL);
LineTo(m_hCacheDC,nWindth-x,y);
SelectObject(m_hCacheDC,hOldPen);
y+=1;
hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen2);
MoveToEx(m_hCacheDC,x,y,NULL);
LineTo(m_hCacheDC,nWindth-x,y);
SelectObject(m_hCacheDC,hOldPen);
for(int i=0;i<m_nCount;i++){
DrawItem(m_hCacheDC,i);
}
BitBlt(hDC,0,0,nWindth,nHeight,m_hCacheDC,0,0,SRCCOPY);
::ReleaseDC(m_hWnd,hDC);
}
void CColorWnd::DrawItem(HDC hDC,int nIndex)
{
COLORITEM item=m_ItemArray.GetAt(nIndex);
HBRUSH hbr=m_hBackBrush1;
if(item.color!=-1 && item.bCheck){
hbr=m_hBackBrush2;
}
FillRect(m_hCacheDC,&item.rect,hbr);
if(m_nDownItem==nIndex || item.bCheck){
HPEN hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen1);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.top);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.left,item.rect.bottom-1);
SelectObject(m_hCacheDC,hOldPen);
hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen2);
MoveToEx(m_hCacheDC,item.rect.right-1,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.bottom-1);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.bottom-1,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.bottom-1);
SelectObject(m_hCacheDC,hOldPen);
}else if(m_nHotItem==nIndex){
HPEN hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen2);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.top);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.left,item.rect.bottom-1);
SelectObject(m_hCacheDC,hOldPen);
hOldPen=(HPEN)SelectObject(m_hCacheDC,m_hOrderPen1);
MoveToEx(m_hCacheDC,item.rect.right-1,item.rect.top,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.bottom-1);
MoveToEx(m_hCacheDC,item.rect.left,item.rect.bottom-1,NULL);
LineTo(m_hCacheDC,item.rect.right-1,item.rect.bottom-1);
SelectObject(m_hCacheDC,hOldPen);
}
if(item.color!=-1){
CRect rc(item.rect);
rc.InflateRect(-3,-3);
hbr=CreateSolidBrush(item.color);
FillRect(hDC,&rc,hbr);
DeleteObject(hbr);
FrameRect(hDC,&rc,(HBRUSH)m_hOrderPen1);
}else{
DrawText(hDC,_T("其他颜色..."),-1,&item.rect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}
}
四.点击“更多颜色”时弹出的颜色选择对话框
这部分,我就没有自己去画了,而是直接采用了MFC的CColorDialog:
CColorDialog dlg(m_CurColor,0,this);
if(dlg.DoModal()==IDOK){
SetColor(dlg.GetColor());
}
五.使用CColorButton
1.把ColorButton.h、ColorButton.cpp加入到你的工程
2.在对话框的头文件里引用ColorButton.h
#include "ColorButton.h"
3.在对话框的头文件里声明变量
CColorButton m_ColorButton1;
4.可以使用一下3种方式来关联按钮控件
DDX_Control(pDX,IDC_BUTTON1, m_ColorButton1);
m_ColorButton1.SubclassWindow(...)子类化关联现有控件;
m_ColorButton1.Create(...)创建控件
5.在BEGIN_MESSAGE_MAP里添加消息映射
ON_BN_COLORCHANGE(IDC_BUTTON1,& CColorBtnTestDlg::OnBnColorChangeButton1)
6.设置“颜色选择器”的颜色 m_ColorButton1.SetColor()
7.获取“颜色选择器”的当前选择的颜色 m_ColorButton1.GetColor()
六.总结
很多时候我们需要一些非系统自带的标准控件,完全可以通过自绘来实现,Windows整个都是画出来的,只要掌握了方法,我们也可以画出各种的窗口、控件,希望本文能给你带来一些帮助。
源代码下载(包含VS2005以及VC6两种工程源码)