自繪按鈕實現顏色選擇器

鄧學彬發表於2012-02-25

一.前言

很多時候,我們需要讓使用者在軟體上選擇顏色,那麼一個“顏色選擇器”就肯定需要了,本例程就是使用普通的按鈕(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兩種工程原始碼)

相關文章