MFC 自绘ComboBox

效果图:html


.h文件函数

#pragma once
#include"stdafx.h"

class CComboBoxXI : public CComboBox
{
	DECLARE_DYNAMIC(CComboBoxXI)

public:
	CComboBoxXI();
	virtual ~CComboBoxXI();

	void SetImageList(CImageList* pImageList);
	CImageList* GetImageList() const;
	int GetItemImage(int nIndex);
	int SetItemImage(int nIndex, int nImageIndex, BOOL bRepaint = TRUE);

	int AddString(LPCTSTR lpszString, int nImageIndex = -1);
	int DeleteString(UINT nIndex);
	int InsertString(int nIndex, LPCTSTR lpszString, int nImageIndex);
	void ResetContent();

	DWORD_PTR GetItemData(int nIndex) const;
	int SetItemData(int nIndex, DWORD_PTR dwItemData);
	void* GetItemDataPtr(int nIndex) const;
	int SetItemDataPtr(int nIndex, void* pData);

protected:
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);

	DECLARE_MESSAGE_MAP()

private:
	// 关联的CImageList
	CImageList *m_pImageList;
	CSize      m_imageSize;

	// EditCtrl的高度
	unsigned int  m_nEditHeight;

	// 是不是DropList:
	// -1(待定,未检测), 1(Yes), 0(No)
	// 只对DropList设置EditCtrl的高度,对DropDown和Simple则采用默认高度
	int m_iIsDropList;

	struct CBDataXI
	{
		// 图像序号,若是为-1,则表示无图像
		int iImageIndex;

		// 关联数据的指针
		LPVOID pData;

		CBDataXI()
		{
			iImageIndex = -1;
			pData = NULL;
		}
	};

	void DeleteItemData(int nIndex);
};

.cpp 文件

/**********************************************************
 ComboBoxXI.cpp : implementation file
 Written by WangYao (wangyao1052@163.com)
 Version: V1.0 2014-06-07
**********************************************************/

#include "stdafx.h"
#include "ComboBoxXI.h"

#define	CBXI_CX_BORDER   2
#define	CBXI_CY_BORDER   2

// CComboBoxXI

IMPLEMENT_DYNAMIC(CComboBoxXI, CComboBox)

CComboBoxXI::CComboBoxXI()
{
	m_pImageList = NULL;
	m_imageSize.cx = 0;
	m_imageSize.cy = 0;
	m_nEditHeight = 0;
	m_iIsDropList = -1;
}

CComboBoxXI::~CComboBoxXI()
{
}

//---------------------------------------------------------
// 函数: SetImageList
// 功能: 设置关联的ImageList
// 参数: 
//       pImageList --- ImageList的指针
// 返回: 无
//---------------------------------------------------------
void CComboBoxXI::SetImageList(CImageList* pImageList)
{
	m_pImageList = pImageList;
	if (m_pImageList)
	{
		int cx;
		int cy;
		ImageList_GetIconSize(*m_pImageList, &cx, &cy);
		m_imageSize.cx = cx;
		m_imageSize.cy = cy;
	}
	else
	{
		m_imageSize.cx = 0;
		m_imageSize.cy = 0;
	}

	Invalidate();
}

//---------------------------------------------------------
// 函数: GetImageList
// 功能: 获取关联ImageList的指针
// 参数: 无
// 返回: 关联ImageList的指针,若没有关联ImageList返回NULL
//---------------------------------------------------------
CImageList* CComboBoxXI::GetImageList() const
{
	return m_pImageList;
}

//---------------------------------------------------------
// 函数: GetItemImage
// 功能: 获取项的图片索引
// 参数: 
//       nIndex  --- 项索引(以0为起点)
// 返回:
//       以0为起始的图片序号
//       若存在错误,如nIndex超出范围,返回-1
//       若组合框项没有关联图片则返回-1
//---------------------------------------------------------
int CComboBoxXI::GetItemImage(int nIndex)
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemData(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return -1;
	}

	return pCBData->iImageIndex;
}

//---------------------------------------------------------
// 函数: SetItemImage
// 功能: 设置项的图片索引
// 参数: 
//       nIndex      --- 项索引(以0为起点)
//       nImageIndex --- 图片索引(以0为起点)
//       bRepaint    --- 是否重绘
// 返回:
//       成功返回0,失败返回-1
//---------------------------------------------------------
int CComboBoxXI::SetItemImage(int nIndex, int nImageIndex, BOOL bRepaint)
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemData(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return -1;
	}
	pCBData->iImageIndex = nImageIndex;

	if (bRepaint)
	{
		Invalidate();
	}

	return 0;
}

//---------------------------------------------------------
// 函数: AddString
// 功能: 添加项
// 参数: lpszString  --- 字符串指针
//        nImageIndex --- 图片索引(以0为起点)
// 返回: 新增项的索引值
//        失败则返回CB_ERR或CB_ERRSPACE
//---------------------------------------------------------
int CComboBoxXI::AddString(LPCTSTR lpszString, int nImageIndex)
{
	int nIndex = CComboBox::AddString(lpszString);
	if (nIndex != CB_ERR && nIndex != CB_ERRSPACE)
	{
		CBDataXI *pData = new CBDataXI();
		pData->iImageIndex = nImageIndex;
		pData->pData = NULL;
		CComboBox::SetItemData(nIndex, (DWORD_PTR)pData);
	}
	
	return nIndex;
}

//---------------------------------------------------------
// 函数: InsertString
// 功能: 在指定位置插入项
// 参数: 
//       nIndex      --- 指定位置
//       lpszString  --- 字符串指针
//       nImageIndex --- 图片索引(以0为起点)
// 返回: 
//       插入项索引值
//       失败则返回CB_ERR或CB_ERRSPACE
//---------------------------------------------------------
int CComboBoxXI::InsertString(int nIndex, LPCTSTR lpszString, int nImageIndex)
{
	int nRet = CComboBox::InsertString(nIndex, lpszString);
	if (nRet != CB_ERR && nRet != CB_ERRSPACE)
	{
		CBDataXI *pData = new CBDataXI();
		pData->iImageIndex = nImageIndex;
		pData->pData = NULL;
		CComboBox::SetItemData(nIndex, (DWORD_PTR)pData);
	}

	return nRet;
}

//---------------------------------------------------------
// 函数: DeleteString
// 功能: 删除项
// 参数: 
//       nIndex  --- 索引
// 返回: 
//       成功则返回当前还剩的项数
//       失败则返回CB_ERR
//---------------------------------------------------------
int CComboBoxXI::DeleteString(UINT nIndex)
{
	DeleteItemData(nIndex);

	return CComboBox::DeleteString(nIndex);
}

//---------------------------------------------------------
// 函数: ResetContent
// 功能: 清空内容
//---------------------------------------------------------
void CComboBoxXI::ResetContent()
{
	int nCnt = CComboBox::GetCount();
	for (int i = 0; i < nCnt; ++i)
	{
		DeleteItemData(i);
	}

	CComboBox::ResetContent();
}

//---------------------------------------------------------
// 函数: GetItemData
// 功能: 获取项关联的数据
// 参数:
//       nIndex --- 索引
// 返回:
//       关联的数据,失败则返回CB_ERR
//---------------------------------------------------------
DWORD_PTR CComboBoxXI::GetItemData(int nIndex) const
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemData(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return CB_ERR;
	}

	return (DWORD_PTR)pCBData->pData;
}

//---------------------------------------------------------
// 函数: SetItemData
// 功能: 设置项关联的数据
// 参数:
//       nIndex     --- 索引
//       dwItemData --- 关联的数据 
// 返回:
//       成功返回CB_OKAY, 失败返回CB_ERR
//---------------------------------------------------------
int CComboBoxXI::SetItemData(int nIndex, DWORD_PTR dwItemData)
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemData(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return CB_ERR;
	}

	pCBData->pData = (LPVOID)dwItemData;
	return CB_OKAY;
}

//---------------------------------------------------------
// 函数: GetItemDataPtr
// 功能: 获取项关联的数据
// 参数:
//       nIndex --- 索引
// 返回:
//       关联的数据,失败则返回CB_ERR
//---------------------------------------------------------
void* CComboBoxXI::GetItemDataPtr(int nIndex) const
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemDataPtr(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return (void*)CB_ERR;
	}

	return pCBData->pData;
}

//---------------------------------------------------------
// 函数: SetItemDataPtr
// 功能: 设置项关联的数据
// 参数:
//       nIndex --- 索引
//       pData  --- 关联的数据
// 返回:
//       成功返回CB_OKAY, 失败则返回CB_ERR
//---------------------------------------------------------
int CComboBoxXI::SetItemDataPtr(int nIndex, void* pData)
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemDataPtr(nIndex);
	if (pCBData == (CBDataXI*)CB_ERR || pCBData == NULL)
	{
		return CB_ERR;
	}

	pCBData->pData = pData;
	return CB_OKAY;
}

void CComboBoxXI::DeleteItemData(int nIndex)
{
	CBDataXI* pCBData = (CBDataXI*)CComboBox::GetItemData(nIndex);
	if (pCBData != (CBDataXI*)CB_ERR)
	{
		delete pCBData;
		CComboBox::SetItemData(nIndex, (DWORD_PTR)NULL);
	}
}

void CComboBoxXI::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	if (lpDrawItemStruct->itemID == -1)
	{
		return;
	}

	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
	pDC->SetBkMode(TRANSPARENT);
	RECT& rcItem = lpDrawItemStruct->rcItem;

	CString cstrItemText;
	CComboBox::GetLBText(lpDrawItemStruct->itemID, cstrItemText);
	
	CBDataXI* pData = (CBDataXI*)CComboBox::GetItemDataPtr(lpDrawItemStruct->itemID);
	if (NULL == pData)
	{
		return;
	}

	// 是否处于选中状态
	BOOL bIsSelected = (lpDrawItemStruct->itemAction | ODA_SELECT) 
		&& (lpDrawItemStruct->itemState & ODS_SELECTED);

	// 是否处于焦点状态
	BOOL bIsFocused = (lpDrawItemStruct->itemAction | ODA_FOCUS) 
		&& (lpDrawItemStruct->itemState & ODS_FOCUS);

	// 绘制背景
	if (bIsSelected)
	{
		COLORREF oldColor = pDC->GetBkColor();
		pDC->FillSolidRect(&rcItem, ::GetSysColor(COLOR_HIGHLIGHT));
		pDC->SetBkColor(oldColor);
	}
	else
	{
		pDC->FillSolidRect(&rcItem, pDC->GetBkColor());
	}

	// 绘制ICON
	CRect rcIcon(rcItem.left, rcItem.top, rcItem.left, rcItem.top);
	if (m_pImageList)
	{
		HICON hIcon = m_pImageList->ExtractIcon(pData->iImageIndex);
		if (hIcon)
		{
			rcIcon.right = rcItem.left + m_imageSize.cx + 2 * CBXI_CX_BORDER;
			rcIcon.bottom = rcItem.top + m_imageSize.cy + 2 * CBXI_CY_BORDER;

			CPoint pos(rcIcon.left + CBXI_CX_BORDER, rcIcon.top + CBXI_CY_BORDER);
			pDC->DrawState(pos, m_imageSize, hIcon, DSS_NORMAL, (CBrush*)NULL);
			::DestroyIcon(hIcon);
		}
	}

	// 绘制Text
	if (bIsSelected)
	{
		pDC->SetTextColor(::GetSysColor(COLOR_HIGHLIGHTTEXT));
	}
	else
	{
		pDC->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
	}
	CRect rcText(rcItem);
	rcText.left = rcIcon.right;
	rcText.top = rcIcon.top;
	pDC->DrawText(cstrItemText, rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_WORDBREAK);
}

void CComboBoxXI::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	CDC *pDC = GetDC();
	LPCTSTR lpszText = (LPCTSTR)lpMeasureItemStruct->itemData;
	CSize szText = pDC->GetTextExtent(lpszText);
	ReleaseDC(pDC);

	lpMeasureItemStruct->itemHeight = 
		szText.cy > m_imageSize.cy + 2 * CBXI_CY_BORDER ? szText.cy :  m_imageSize.cy + 2 * CBXI_CY_BORDER;

	// 检测是不是DropList
	if (-1 == m_iIsDropList)
	{
		DWORD dwStyle = GetStyle();
		if (DWORD(CBS_DROPDOWNLIST & dwStyle) == (DWORD)CBS_DROPDOWNLIST)
		{
			m_iIsDropList = 1;
		}
		else
		{
			m_iIsDropList = 0;
		}
	}

	// 设置EditCtrl的高度
	if (m_iIsDropList == 1 && lpMeasureItemStruct->itemHeight > m_nEditHeight)
	{
		m_nEditHeight = lpMeasureItemStruct->itemHeight;
		SetItemHeight(-1, m_nEditHeight);
	}
}

BEGIN_MESSAGE_MAP(CComboBoxXI, CComboBox)
END_MESSAGE_MAP()

// CComboBoxXI message handlers

说明:

=============== 提供了以下接口 ===============================
void SetImageList(CImageList* pImageList);
CImageList* GetImageList() const;
int GetItemImage(int nIndex);
int SetItemImage(int nIndex, int nImageIndex, BOOL bRepaint = TRUE);


int AddString(LPCTSTR lpszString, int nImageIndex = -1);
int DeleteString(UINT nIndex);
int InsertString(int nIndex, LPCTSTR lpszString, int nImageIndex);
void ResetContent();


DWORD_PTR GetItemData(int nIndex) const;
int SetItemData(int nIndex, DWORD_PTR dwItemData);
void* GetItemDataPtr(int nIndex) const;
int SetItemDataPtr(int nIndex, void* pData);




================= 示例 =================================
OnInitialDialog()
{
...........

m_imglist1.Create(32,32, ILC_COLOR32 | ILC_MASK, 2, 1);
m_imglist1.Add(AfxGetApp()->LoadIcon(IDI_ICON1));
m_imglist1.Add(AfxGetApp()->LoadIcon(IDI_ICON2));


m_combo.SetImageList(&m_imglist1);
m_combo.AddString(_T("123"), 0);
m_combo.AddString(_T("456"), 1);
m_combo.SetCurSel(0);
}


注:ComboBox要设置两个属性 
HasStrings: True 
Owner Draw: Variable
spa