效果图:
原帖:http://www.cnblogs.com/liangbin/articles/2064932.html
通过SetWindowLong自定义ListBox的窗口过程“ComboBoxListBoxProc”,来改变在下拉列表中的一些行为。
但是这么做需要在CheckComboBox类中添加一些静态变量,可以在自定义的窗口过程中调用。
其实可以封装的更简洁些,通过容器窗口,也可以通过子类/超类化,下面代码通过容器窗口实现:
1: //*************************************************************************
2: //
3: // Copyright (C), 2009-2010, Handy Information Co.,Ltd,All Rights Reserved
4: //
5: // FileName: CheckComboBox.h
6: // Author: yinxf
7: // Date: 2012/2012/4
8: // Description: 带checkbox的ComboBox,自绘
9: // 要将ComboBox的Tyep设置为Drop List
10: // OwnerDraw设置为Variable
11: // Has Strings设置为True
12: // Function List:
13: // History:
14: // <author> <time> <desc>
15: // yinxf 2010/07/13 Build
16: //
17: //*************************************************************************
18: #pragma once19:20: class CCheckComboBox
21: : public CWindowImpl<CCheckComboBox, CComboBox>
22: , public COwnerDraw<CCheckComboBox>
23: {24: public:
25: CCheckComboBox(void)
26: : m_listBox(this, 1)
27: , m_bTextUpdated(FALSE)28: {}29: ~CCheckComboBox(void){}
30:31: // 设置选中
32: int SetCheck(int nIndex, BOOL bCheck)33: {34: int nRet = SetItemData(nIndex, bCheck);
35: if ( nRet == CB_ERR )
36: return nRet;
37:38: // Signal that the text need updating
39: m_bTextUpdated = FALSE;40:41: // Redraw the window
42: Invalidate(FALSE);43:44: return nRet;
45: }46:47: // 获取选中状态
48: BOOL GetCheck(int nIndex)
49: {50: return GetItemData(nIndex);
51: }52:53: // Selects all/unselects all
54: void SelectAll(BOOL bCheck = TRUE)
55: {56: for ( int i = 0; i < GetCount(); i++ )57: SetCheck(i, bCheck);58: }59:60: protected:
61: BEGIN_MSG_MAP(CCheckComboBox)62: MSG_WM_CTLCOLORLISTBOX(OnCtlColorListBox)63: MSG_WM_GETTEXT(OnGetText)64: MSG_WM_GETTEXTLENGTH(OnGetTextLength)65: CHAIN_MSG_MAP_ALT(COwnerDraw<CCheckComboBox>, 1)66: DEFAULT_REFLECTION_HANDLER()67: /////////////////处理ListBox消息/////////////////////////////
68: ALT_MSG_MAP(1)69: MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)70: MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)71: MESSAGE_HANDLER(LB_GETCURSEL, OnGetCurSel)72: MESSAGE_HANDLER(WM_RBUTTONDOWN, OnRButtonDown)73: //////////////////////////////////////////////////////////////
74: END_MSG_MAP()75:76: BOOL SubclassWindow(HWND hWnd)77: {78: BOOL bRet = __super::SubclassWindow(hWnd);79: if ( bRet )
80: {81: // 一旦创建无法修改,要么通过create创建
82: // CWindow::ModifyStyle(0, CBS_OWNERDRAWVARIABLE); // 添加自绘属性
83: // CWindow::ModifyStyle(CBS_DROPDOWN, CBS_DROPDOWNLIST);
84:85: DWORD style = GetStyle();86: // Make sure to use the CBS_DROPDOWNLIST style
87: ATLASSERT(style & CBS_DROPDOWNLIST);88: // Make sure to use the CBS_OWNERDRAWVARIABLE style
89: ATLASSERT(style & CBS_OWNERDRAWVARIABLE);90: // Use default strings. We need the itemdata to store checkmarks
91: ATLASSERT(style & CBS_HASSTRINGS);92: }93: return bRet;
94: }95:96: // 自绘
97: void DrawItem(LPDRAWITEMSTRUCT lpdis)
98: {99: CRect rcCheck = lpdis->rcItem;100: CRect rcText = lpdis->rcItem;101: CString strText;102:103: // 检查是否绘制的静态文本框
104: if ( (LONG)lpdis->itemID < 0 ) // *一定要强制转换为有符号性*105: {106: RecalcText();107: strText = m_strTextAll;108: }109: // 下拉列表
110: else
111: {112: BOOL bCheck = GetCheck(lpdis->itemID);113: GetLBText(lpdis->itemID, strText);114:115: // 绘制checkbox
116: TEXTMETRIC txtMetric;117: GetTextMetrics(lpdis->hDC, &txtMetric);118: rcCheck.top += 1;119: rcCheck.left = 0;120: rcCheck.right = rcCheck.left + txtMetric.tmHeight + txtMetric.tmExternalLeading + 6;121: rcCheck.bottom -= 1;122: rcText.left = rcCheck.right;123:124: UINT nState = bCheck ? DFCS_CHECKED : DFCS_BUTTONCHECK;125: DrawFrameControl(lpdis->hDC, rcCheck, DFC_BUTTON, nState);126: }127:128: // 绘制文字
129: if ( lpdis->itemState & ODS_SELECTED ) // 设置背景色130: {131: SetBkColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHT));132: SetTextColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));133: }134: else
135: {136: SetBkColor(lpdis->hDC, GetSysColor(COLOR_WINDOW));137: SetTextColor(lpdis->hDC, GetSysColor(COLOR_WINDOWTEXT));138: }139:140: // Erase and draw
141: ExtTextOut(lpdis->hDC, 0, 0, ETO_OPAQUE, rcText, 0, 0, 0); // ETO_OPAQUE表示当前背景颜色会充满整个矩形框
142: DrawText(lpdis->hDC, strText, strText.GetLength(), rcText, DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);143:144: // 绘制焦点(虚线)
145: if ((lpdis->itemState & (ODS_FOCUS|ODS_SELECTED)) == (ODS_FOCUS|ODS_SELECTED))
146: DrawFocusRect(lpdis->hDC, &rcText);147: }148:149: LRESULT OnCtlColorListBox(HDC hdc, HWND hwnd)150: {151: ATLTRACE(_T("[%s]0x%x"), _T(__FUNCTION__), hwnd);
152:153: if ( NULL == m_listBox.m_hWnd )
154: m_listBox.SubclassWindow(hwnd);155:156: return DefWindowProc();
157: }158:159: //
160: // By adding this message handler, we may use CWindow::GetText()
161: //
162: int OnGetText(int cchTextMax, LPTSTR lpszText)163: {164: // Make sure the text is updated
165: RecalcText();166:167: if (NULL == lpszText)
168: return 0;
169:170: // Copy the 'fake' window text
171: _tcscpy_s(lpszText, cchTextMax, m_strTextAll);172: return m_strTextAll.GetLength();
173: }174:175: //
176: // By adding this message handler, we may use CWindow::GetTextLength()
177: //
178: int OnGetTextLength()
179: {180: // Make sure the text is updated
181: RecalcText();182: return m_strTextAll.GetLength();
183: }184:185: ///////////////////////////////////////////////////////////////////////////////////
186: // 处理ListBox消息
187:188: LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
189: {190: CPoint pt;191: pt.x = LOWORD(lParam);192: pt.y = HIWORD(lParam);193:194: // Compute which index to check/uncheck
195: int nItemHeight = GetItemHeight(0);
196: int nTopIndex = GetTopIndex();
197: int nIndexClick = nTopIndex + pt.y / nItemHeight;
198:199: // Get clicked item rect
200: CRect rcItem;201: m_listBox.GetItemRect(nIndexClick, rcItem);202:203: if ( PtInRect(rcItem, pt) )
204: {205: // Invert the check mark
206: SetCheck(nIndexClick, !GetCheck(nIndexClick));207: ::InvalidateRect(m_listBox.m_hWnd, rcItem, FALSE);208:209: // Notify that selection has changed
210: /*::SendMessage(this->GetParent().m_hWnd, WM_COMMAND,
211: MAKELONG(::GetWindowLong(this->m_hWnd, GWL_ID), CBN_SELCHANGE),212: (LPARAM)this->m_hWnd);*/213:214: // 按照上面发送消息没有效果,直接刷新
215: Invalidate(FALSE);216: }217:218: bHandled = FALSE;219: return 0;
220: }221:222: LRESULT OnLButtonUp(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)223: {224: // 什么都不做,让列表窗口在被选中了一个条目后仍然保持显示(不自动关闭)
225: return 0;
226: }227:228: LRESULT OnGetCurSel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)229: {230: // Make the combobox always return -1 as the current selection. This
231: // causes the lpDrawItemStruct->itemID in DrawItem() to be -1
232: // when the always-visible-portion of the combo is drawn
233: return -1;
234: }235:236: LRESULT OnRButtonDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)237: {238: // If you want to select all/unselect all using the
239: // right button, remove this ifdef. Personally, I don't really like it
240: #if TRUE
241: INT nCount = GetCount();242: INT nSelCount = 0;243:244: for (INT i = 0; i < nCount; i++)
245: {246: if ( GetCheck(i) )
247: nSelCount++;248: }249:250: SelectAll(nSelCount != nCount);251:252: // invalidate list box
253: ::InvalidateRect(m_listBox.m_hWnd, 0, FALSE);254: #endif255:256: bHandled = FALSE;257: return 0;
258: }259:260:261: //////////////////////////////////////////////////////////////////////////
262: // Help
263:264: //
265: // This routine steps through all the items and builds
266: // a string containing the checked items
267: //
268: void RecalcText()
269: {270: if ( m_bTextUpdated )
271: return;
272:273: CString strText;274:275: int nCount = GetCount();
276: for ( int i = 0; i < nCount; i++ )277: {278: if ( TRUE == GetCheck(i) )
279: {280: CString strItem;281: GetLBText(i, strItem);282:283: if ( !strText.IsEmpty() )
284: strText += _T(", ");
285:286: strText += strItem;287: }288: }289:290: m_strTextAll = strText;291:292: m_bTextUpdated = TRUE;293: return;
294: }295:296: protected:
297: CContainedWindowT<CListBox> m_listBox;298: CString m_strTextAll; // 静态文本框显示的内容
299: BOOL m_bTextUpdated;300: };