效果图:
原帖: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 once
19:
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: #endif
255:
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: };