VC++ CComboBox自绘

头文件定义(CSWComboBox.h):

#pragma once

class CSWComboBox : 
	public CComboBox
{
	DECLARE_DYNAMIC(CSWComboBox)

public:
	CSWComboBox();
	~CSWComboBox();

private:
	// 初始化
	void CommonConstruct();

	// 销毁申请的内存空间
	void ResetContent();

	// 自适应列表框宽度
	void AutoFitDroppedWidth();

	// 获取子控件CEdit
	CEdit* FindChildEdit();

	// 偏移CEdit子窗体位置
	void SetChildEditOffset(int nOffset = 20);

private:
	struct ComboData {
		DWORD_PTR dwItemData;
		UINT nImageIndex;
		std::string sItemText;
	};

	std::map m_mapItemImage;
	CBrush m_brEdge;		// 边框画刷
	BOOL m_bIsMouseHover;	// 鼠标停留
	BOOL m_bIsMouseDown;	// 鼠标按下
	BOOL m_bShow3DEdge;		// 3D边框
	CSize m_szImage;		// 图标尺寸

public:
	void Show3DEdge(BOOL bShow = TRUE);
	UINT AddImageList(Gdiplus::Bitmap* pBitmap);
	
protected:
	void DrawEdge(COLORREF clrTopLeft, COLORREF clrBottomRight);

public:
	virtual BOOL PreTranslateMessage(MSG* pMsg);
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
	virtual void PreSubclassWindow();

	virtual int AddString(LPCTSTR lpszString, int nImageIndex = -1);//加载每一项的文字和对应的图标索引
	virtual int InsertString(int nIndex, LPCTSTR lpszString, int nImageIndex = -1);
	virtual int GetLBText(int nIndex, CString& strText);//根据项索引获取对应的文字
	virtual int GetLBImage(int nIndex, int& nImageIndex);//根据项索引获取对应的图标索引
	virtual int SetItemData(int nItem, DWORD_PTR dwItemData);
	virtual DWORD_PTR GetItemData(int nIndex) const;
	virtual int SetCurSel(int nSelect);
	virtual BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);

protected:
	afx_msg void OnPaint();
	afx_msg void OnDestroy();
	afx_msg void OnCbnSelchange();
	afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct);
	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
	afx_msg void OnCbnEditchange();
	afx_msg HRESULT OnResetEditPositionMsg(WPARAM, LPARAM);
	DECLARE_MESSAGE_MAP()
};

源码实现(CSWComboBox.cpp):

#include "stdafx.h"
#include "CSWComboBox.h"

// CSWComboBox
CSWComboBox::CSWComboBox()
{
	CommonConstruct();
}

CSWComboBox::~CSWComboBox()
{
}

IMPLEMENT_DYNAMIC(CSWComboBox, CComboBox)
BEGIN_MESSAGE_MAP(CSWComboBox, CComboBox)
	//{{AFX_MSG_MAP(CSWComboBox)
	ON_WM_PAINT()
	ON_WM_DESTROY()
	ON_CONTROL_REFLECT(CBN_SELCHANGE, &CSWComboBox::OnCbnSelchange)
	ON_WM_MEASUREITEM()
	ON_WM_CTLCOLOR()
	ON_CONTROL_REFLECT(CBN_EDITCHANGE, &CSWComboBox::OnCbnEditchange)
	ON_WM_SIZE()
	ON_MESSAGE(WM_USER + 1, OnResetEditPositionMsg)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CSWComboBox::CommonConstruct()
{
	m_brEdge.CreateSolidBrush(RGB(151, 151, 151));
	m_bIsMouseHover = FALSE;
	m_bIsMouseDown = FALSE;
	m_bShow3DEdge = FALSE;
	m_szImage = CSize(0, 0);
}

void CSWComboBox::OnPaint() 
{
	ModifyStyleEx (WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE, 0, SWP_FRAMECHANGED);
	Default(); // 如果需要绘制下拉三角形,屏蔽此行代码

	// 绘制下拉三角形
	CRect rcClient;
	GetClientRect(&rcClient);
// 	CDC* pDC = GetDC();
// 	CRect rcButton = rcClient;
// 	rcButton.left = rcButton.right - 16;
// 	if (rcButton.left < rcClient.left) rcButton.left = rcClient.left;
// 	static CPen penBlack(PS_SOLID, 1, RGB(0, 0, 0));
// 	static CPen penBtnShadow(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
// 	CPen* ppenOld;
// 	if (IsWindowEnabled())
// 		ppenOld = pDC->SelectObject(&penBlack);
// 	else	ppenOld = pDC->SelectObject(&penBtnShadow);
// 	for (long i = 0; i < 4; i++) {
// 		pDC->MoveTo(rcButton.left + 3 + i, rcButton.top + rcButton.Height() / 2 - 2 + i);
// 		pDC->LineTo(rcButton.left + 3 + 7 - i, rcButton.top + rcButton.Height() / 2 - 2 + i);
// 		if (!IsWindowEnabled())
// 			pDC->SetPixel(rcButton.left + 3 + 7 - i, rcButton.top + rcButton.Height() / 2 - 2 + i + 1, 0xFFFFFF);
// 	}
// 	pDC->SelectObject(ppenOld);
// 	ReleaseDC(pDC);

	if (m_bShow3DEdge)
	{		
		COMBOBOXINFO comboBoxInfo;
		comboBoxInfo.cbSize = sizeof(COMBOBOXINFO);
		GetComboBoxInfo(&comboBoxInfo);
		CRect rcComboBoxBtn = comboBoxInfo.rcButton;
		rcClient.right -= rcComboBoxBtn.Width();
		CDC* pDC = GetDC();
		rcClient.DeflateRect(1, 1, 2, 2);
		if (!IsWindowEnabled()) 
		{
			pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNHIGHLIGHT), ::GetSysColor(COLOR_BTNHIGHLIGHT));
			rcClient.OffsetRect(1, 1);
			pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNHIGHLIGHT), ::GetSysColor(COLOR_BTNHIGHLIGHT));
		}
		else
		{
			pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNFACE), ::GetSysColor(COLOR_BTNSHADOW));
			rcClient.OffsetRect(1, 1);
			pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNSHADOW), ::GetSysColor(COLOR_BTNFACE));
		}

		ReleaseDC(pDC);
		return;
	}

	DrawEdge(::GetSysColor(COLOR_BTNFACE), ::GetSysColor(COLOR_BTNFACE));
}

void CSWComboBox::DrawEdge(COLORREF clrTopLeft, COLORREF clrBottomRight)
{
	CRect rcClient;
	GetClientRect(&rcClient);
	CDC* pDC = GetDC();
	rcClient.DeflateRect(1, 1);
	if (!IsWindowEnabled())
		pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNHIGHLIGHT), ::GetSysColor(COLOR_BTNHIGHLIGHT));
	else
		pDC->Draw3dRect(rcClient, ::GetSysColor(COLOR_BTNFACE), ::GetSysColor(COLOR_BTNFACE));

	if (!IsWindowEnabled()) 
	{
		ReleaseDC(pDC);
		return;
	}

	// 画边框颜色
	GetClientRect(&rcClient);
	CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNSHADOW));
	HGDIOBJ hOldPen = (HGDIOBJ)::SelectObject(pDC->GetSafeHdc(), (HGDIOBJ)pen.GetSafeHandle()); //选择画笔
 	HBRUSH hOldBrush = (HBRUSH)::SelectObject(pDC->GetSafeHdc(), (HBRUSH)::GetStockObject(HOLLOW_BRUSH)); // 选择一个空的画刷
 	pDC->Rectangle(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height());
	::SelectObject(pDC->GetSafeHdc(), hOldBrush);
	::SelectObject(pDC->GetSafeHdc(), hOldPen);

	// 绘制右侧下拉按钮的外框,使其看起来平面化
	{
		COMBOBOXINFO comboBoxInfo;
		comboBoxInfo.cbSize = sizeof(COMBOBOXINFO);
		GetComboBoxInfo(&comboBoxInfo);
		CRect rcComboBoxBtn = comboBoxInfo.rcButton;
		CRect rcComboBoxItem = comboBoxInfo.rcItem;
		CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_BTNFACE));
		HGDIOBJ hOldPen = (HGDIOBJ)::SelectObject(pDC->GetSafeHdc(), (HGDIOBJ)pen.GetSafeHandle());
		HBRUSH hOldBrush = (HBRUSH)::SelectObject(pDC->GetSafeHdc(), (HBRUSH)::GetStockObject(HOLLOW_BRUSH));
		pDC->Rectangle(rcComboBoxItem.right - 1, rcComboBoxItem.top - 1, rcComboBoxItem.right + rcComboBoxBtn.Width() + 1, rcComboBoxItem.bottom + 1);
		::SelectObject(pDC->GetSafeHdc(), hOldBrush);
		::SelectObject(pDC->GetSafeHdc(), hOldPen);
	}

	int nIndex = GetCurSel();
	if (nIndex != CB_ERR)
	{
		int nImageIndex;
		GetLBImage(nIndex, nImageIndex);
		if (nImageIndex >= 0)
		{
			Gdiplus::Bitmap* pBitmap = m_mapItemImage[nImageIndex];
			if (pBitmap && pBitmap->GetLastStatus() == Gdiplus::Ok)
			{
				Gdiplus::Graphics g(pDC->GetSafeHdc());
				g.DrawImage(pBitmap, RectF(6, 6, 15, 15),
					0,
					0,
					pBitmap->GetWidth(),
					pBitmap->GetHeight(),
					UnitPixel,
					NULL,
					NULL,
					NULL);
			}
		}
	}

	ReleaseDC(pDC);
}

BOOL CSWComboBox::PreTranslateMessage(MSG* pMsg) 
{
	if (pMsg->message == WM_KEYDOWN || pMsg->message==WM_KEYUP)
	{
		::TranslateMessage(pMsg);
		::DispatchMessage(pMsg);
		return TRUE;
	}
	
	if (pMsg->message == WM_SYSCHAR)
		return TRUE;
		
	return CComboBox::PreTranslateMessage(pMsg);
}

void CSWComboBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	TRACE("%s::DrawItem(%d)\n", this->GetRuntimeClass()->m_lpszClassName, GetTickCount());
	CDC dc;
	dc.Attach(lpDIS->hDC);
	CRect rcItem = lpDIS->rcItem;
	int nItem = lpDIS->itemID;
	if (nItem == -1)
		return;

	dc.SetBkMode(TRANSPARENT);
	if (lpDIS->itemState & ODS_SELECTED)
	{
		dc.SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
		dc.FillSolidRect(&rcItem, GetSysColor(COLOR_HIGHLIGHT));
		if (lpDIS->itemAction & ODA_FOCUS)
			dc.DrawFocusRect(&rcItem);
	}
	else
	{
		dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
		dc.FillSolidRect(&rcItem, GetSysColor(COLOR_WINDOW));		
	}

	// 画下拉项左侧图标
	CRect rcIcon = rcItem;
	rcIcon.DeflateRect(2, 2);
	rcIcon.right = rcIcon.left + rcIcon.Height();
	int nImageIndex;
	GetLBImage(nItem, nImageIndex);
	if (nImageIndex >= 0)
	{
		Gdiplus::Bitmap* pBitmap = m_mapItemImage[nImageIndex];
		if (pBitmap && pBitmap->GetLastStatus() == Gdiplus::Ok)
		{
			Gdiplus::Graphics g(dc.GetSafeHdc());
			g.DrawImage(pBitmap, RectF(rcIcon.left, rcIcon.top, rcIcon.Width(), rcIcon.Height()),
				0, 
				0, 
				pBitmap->GetWidth(), 
				pBitmap->GetHeight(), 
				UnitPixel, 
				NULL, 
				NULL, 
				NULL);
			rcItem.left += m_szImage.cx; 
		}
	}

	CRect rcText = rcItem;
	CString strText;
	GetLBText(nItem, strText);
	dc.DrawText(strText, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
	if (lpDIS->itemState & ODS_SELECTED && GetDroppedState())
	{
		HICON hIcon = LoadIcon(NULL, IDI_ERROR);
		ICONINFO icoObj;
		GetIconInfo(hIcon, &icoObj);
		CRect rcRIcon(rcItem);
		rcRIcon.left = rcRIcon.right - icoObj.xHotspot - 2;
		rcRIcon.top += (rcRIcon.Height() - icoObj.yHotspot) / 2;
		DrawIconEx(dc.m_hDC, rcRIcon.left, rcRIcon.top, hIcon, icoObj.xHotspot, icoObj.yHotspot, NULL, NULL, DI_NORMAL);
	}

 	dc.Detach();
}

void CSWComboBox::AutoFitDroppedWidth()
{
	// 计算下拉列表最长宽度
	CString csItem;
	int nWidth = GetDroppedWidth();
	CDC* pDC = this->GetDC();
	for (int i = 0; i < GetCount(); i++)
	{
		GetLBText(i, csItem);
		CSize sz = pDC->GetTextExtent(csItem);
		if (sz.cx > nWidth)
		{
			// 如果非只读,则预留出删除叉叉的宽度
// 			if (m_bReadOnly)
// 				nWidth = sz.cx;
// 			else
				nWidth = sz.cx + m_szImage.cx;
		}
	}

	SetDroppedWidth(nWidth);
	ReleaseDC(pDC);
}

int CSWComboBox::AddString(LPCTSTR lpszString, int nImageIndex)
{
	int nItem = CComboBox::AddString(lpszString);//插入一条文本
	ComboData* pData = new ComboData;
	if (pData == NULL)
		return -1; 

	pData->sItemText = lpszString;
	pData->nImageIndex = nImageIndex;
	CComboBox::SetItemData(nItem, (DWORD)pData);
	AutoFitDroppedWidth();
	return nItem;
}

int CSWComboBox::SetItemData(int nIndex, DWORD_PTR dwItemData)
{
	ComboData* pData = (ComboData*)CComboBox::GetItemData(nIndex);
	if (pData == NULL)
		return 0;

	pData->dwItemData = dwItemData;
	return CComboBox::SetItemData(nIndex, (DWORD)pData);
}

DWORD_PTR CSWComboBox::GetItemData(int nIndex) const
{
	ComboData* pData = (ComboData*)CComboBox::GetItemData(nIndex);
	if (pData == NULL)
		return 0;

	return pData->dwItemData;
}

int CSWComboBox::InsertString(int nIndex, LPCTSTR lpszString, int nImageIndex)
{
	int nItem = CComboBox::InsertString(nIndex, lpszString);//插入一条文本
	ComboData* pData = new ComboData;
	if (pData == NULL)
		return -1;

	pData->sItemText = lpszString;
	pData->nImageIndex = nImageIndex;
	CComboBox::SetItemData(nItem, (DWORD)pData); 
	AutoFitDroppedWidth();
	return nItem;
}

int CSWComboBox::GetLBText(int nIndex, CString& strText) //获取当前绘制项的文本
{
	ComboData* pData = (ComboData*)CComboBox::GetItemData(nIndex);
	if (pData == NULL)
		return 0;

	strText = pData->sItemText.c_str();
	return 1;
}

int CSWComboBox::GetLBImage(int nIndex, int& nImageIndex) //获取当前绘制项的图像
{
	ComboData* pData = (ComboData*)CComboBox::GetItemData(nIndex);
	if (pData == NULL) 
		return 0;

	nImageIndex = pData->nImageIndex;
	return 1;
}

void CSWComboBox::ResetContent()//释放所申请的内存
{
	int nItemIndex = GetCount();
	while (nItemIndex--)
	{
		ComboData* pData = (ComboData*)CComboBox::GetItemData(nItemIndex);
		if (pData)
		{
			delete pData;
			pData = NULL;
		}
	}

	CComboBox::ResetContent();
}

void CSWComboBox::OnDestroy()
{
	CComboBox::OnDestroy();
	ResetContent();
}

void CSWComboBox::OnCbnSelchange()
{
	int nSelIndex = GetCurSel();
	if (nSelIndex == CB_ERR)
		return;

	ComboData* pData = (ComboData*)CComboBox::GetItemData(nSelIndex);
	if (pData->nImageIndex!=-1)
	{
		CListBox* pListBox = NULL;
		COMBOBOXINFO combInfo;
		combInfo.cbSize = sizeof(COMBOBOXINFO);
		GetComboBoxInfo(&combInfo);
		if (combInfo.hwndList)
			pListBox = (CListBox*)CWnd::FromHandle(combInfo.hwndList);

		if (pListBox == NULL)
			return;

		CRect rcItem;
		pListBox->GetItemRect(nSelIndex, rcItem);
		CPoint point;
		GetCursorPos(&point);
		ScreenToClient(&point);
		CEdit *pEdit = FindChildEdit();
		if (pEdit)
		{
			CRect rcEdit;
			pEdit->GetClientRect(&rcEdit);
			point.y -= rcEdit.Height();
		}
		
		rcItem.left = rcItem.right - 18;
		if (rcItem.PtInRect(point))
		{
			if (pData)
			{
				delete pData;
				pData = NULL;
			}
			this->DeleteString(nSelIndex);
			//pListBox->DeleteString(nSelIndex);
			SetWindowText(_T(""));
		}
		else
		{
			SetChildEditOffset(20);
		}

		Invalidate();
	}
}

void CSWComboBox::Show3DEdge(BOOL bShow)
{
	m_bShow3DEdge = bShow;
	RedrawWindow();
}

UINT CSWComboBox::AddImageList(Gdiplus::Bitmap* pBitmap)
{
	if (pBitmap == NULL)
		return UINT(-1);

	UINT nCount = m_mapItemImage.size();
	m_mapItemImage[nCount] = pBitmap;
	m_szImage = CSize(max(m_szImage.cx, pBitmap->GetWidth()), max(m_szImage.cy, pBitmap->GetHeight()));
	return nCount;
}

void CSWComboBox::PreSubclassWindow()
{
	// TODO: 在此添加专用代码和/或调用基类
	SetWindowLong(m_hWnd, GWL_EXSTYLE, GetWindowLong(m_hWnd, GWL_STYLE) | BS_OWNERDRAW);
	CComboBox::PreSubclassWindow();
}

void CSWComboBox::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CComboBox::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}

HBRUSH CSWComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性
	if (nCtlColor == CTLCOLOR_STATIC)
	{
// 		CFont* font = new CFont;
// 		font->CreatePointFont(72, _T("宋体"));
//		pDC->SelectObject(font);
		pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
		pDC->SetBkMode(TRANSPARENT);
		HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
// 		font->DeleteObject();
// 		delete font;
		return (HBRUSH)hBrush;
	}

	if (nCtlColor == CTLCOLOR_EDIT)
	{
		CEdit* pEdit = reinterpret_cast(pWnd);
		//pEdit->SetLimitText(256);
		pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
// 		CFont* font = new CFont;
// 		font->CreatePointFont(72, _T("宋体"));
// 		pDC->SelectObject(font);
		pDC->SetBkMode(TRANSPARENT);
		HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
//		font->DeleteObject();
//		delete font;
		return (HBRUSH)hBrush;
	}

	if (nCtlColor == CTLCOLOR_LISTBOX)
	{
// 		CFont* font = new CFont;
// 		font->CreatePointFont(72, _T("宋体"));
// 		pDC->SelectObject(font);
		pDC->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
		pDC->SetBkMode(TRANSPARENT);
		HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
// 		font->DeleteObject();
// 		delete font;
		return (HBRUSH)hBrush;
	}

	// TODO:  如果默认的不是所需画笔,则返回另一个画笔
	return hbr;
}


void CSWComboBox::OnCbnEditchange()
{
	// TODO: 在此添加控件通知处理程序代码
	SetChildEditOffset(0);
}

int CSWComboBox::SetCurSel(int nSelect)
{
	int nIndex = CComboBox::SetCurSel(nSelect);
	if (nIndex != CB_ERR)
		PostMessage(WM_USER+1);

	return nIndex;
}

// 获取子控件CEdit
CEdit* CSWComboBox::FindChildEdit()
{
	::CWnd *pWnd = GetWindow(GW_CHILD);
	while (pWnd)
	{
		TCHAR classname[256];
		::GetClassName(pWnd->m_hWnd, classname, 256);
		if (lstrcmpi(classname, _T("Edit")) == 0)
			break;
		if (pWnd)
			pWnd = pWnd->GetNextWindow();
	}

	return (CEdit*)pWnd;
}

// 偏移CEdit子窗体位置
void CSWComboBox::SetChildEditOffset(int nOffset/* = 20*/)
{
	CEdit *pEdit = FindChildEdit();
	if (pEdit)
	{
		CRect rcEditWindow; pEdit->GetWindowRect(rcEditWindow);
		CRect rcWindow; GetWindowRect(rcWindow);
		rcEditWindow.left = nOffset + 3;
		rcEditWindow.top = 6; // 相对位置本来是3,考虑到文本居中显示,6比较合适
		rcEditWindow.right = rcWindow.Width() - 20;
		rcEditWindow.bottom = rcWindow.Height() - 3;
		pEdit->MoveWindow(rcEditWindow);
	}
}

HRESULT CSWComboBox::OnResetEditPositionMsg(WPARAM wParam, LPARAM lParam)
{
	SetChildEditOffset();
	return S_OK;
}

BOOL CSWComboBox::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
	// TODO: 在此添加专用代码和/或调用基类
	dwStyle &= ~0x0F;
	dwStyle |= CBS_DROPDOWNLIST;
	dwStyle |= CBS_OWNERDRAWFIXED;
	dwStyle |= CBS_HASSTRINGS;
	return CComboBox::Create(dwStyle, rect, pParentWnd, nID);
}

效果图如下:

VC++ CComboBox自绘_第1张图片

你可能感兴趣的:(VC++(基础篇),VC++(日积月累篇),VC++(控件篇))