自绘按钮实现颜色选择器

一.前言

很多时候,我们需要让用户在软件上选择颜色,那么一个“颜色选择器”就肯定需要了,本例程就是使用普通的按钮(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两种工程源码)

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值