要點
1. ComboBox控件由三部分組成:ComboBox本身,CEidt或者CStaitc,ClistBox。當類型是Dropdown時,內部是CEdit;是類型是Drop List時,內部是Static。
2. ComboBox支持自繪需要修改Owner Draw屬性為Fixed(固定的列表寬度和高度) or Variable(可變的列表寬度和高度)。響應WM_DRAWITEM(反射OCM_DRAWITEM)消息,重載DrawItem函數對下拉列表元素進行重繪。在DrawItem中需要用到GetLBText函數來獲取要繪制的文本信息,這就需要增加Has Strings屬性。
3. 改變ComboBox列表寬度高度,是通過響應WM_MEASUREITEM(發射OCM_MEASUREITEM)消息,重載MeasureItem函數,修改LPMEASUREITEMSTRUCT成員來達到效果。有這么短英文原話(出處忘了是哪了)
For “fixed” type owner draw list box or combo box, WM_MEASUREITEM is sent when it is first created and the returned size will be used for all items. For “variable” type owner-draw list box or combo box, this message is sent for each item separately.
意思就是如果屬性是“Fixed”,WM_MESUREITEM消息在控件第一次創建的時候觸發,重載函數MesureItem返回的LPMEASUREITEMSTRUCT成員中的寬度和高度將被用作列表中的所有條目。如果屬性是“Variable”,列表中每一項分別響應。
這句話容易被忽略掉一種情況,如果是通過子類化(DDX_Control,SubclassDlgItem或SubclassWindow)來改變已有的控件,該控件又是“Fixed”。則重載的MeasureItem將不起作用。因為在子類化之前,創建的時候已經觸發過了。所以要想改變列表寬度和高度,要這么設置:
1) 通過子類化關聯控件:屬性要設置為“Variable”,下拉列表包含幾個條目,MeasureItem就會被調用幾次,改變每一項的的寬度高度,但是不能文本框(LPMEASUREITEMSTRUCT成員itemID = -1)的寬度高度
2) 調用Create動態創建:屬性設置為“Fixed”,MeasureItem會被調用兩次,LPMEASUREITEMSTRUCT成員itemID為-1和1。等於-1的時候可以改變文本框寬度高度。等於1的時候,被設置的寬度高度將應用於下拉列表所有項;屬性設置為“Variable”,MeasureItem會被調用>2次(列表條目個數+1)。同樣的,等於itemID=-1時候改變文本框,itemID=0,1,2...改變列表每一項各自的寬度高度。(如果發現點擊下拉按鈕出現不了列表框,請檢查調用Create函數時候穿進去的Rect的高度是不是太小了)
4. 改變顏色:
● 改變CEdit顏色(Dropdown類型):只要響應WM_CTLCOLOREDIT消息,在響應函數中返回新的畫刷;
● 改變下拉列表顏色:在DrawItem重載函數中,根據Item狀態,繪制顏色。
實踐
新建一個ComboBox派生類CColorComboBox,並且繼承CownerDraw,MSG_MAP中添加自繪的消息鏈,父窗口中MSG_MAP中添加消息反射宏 REFLECT_NOTIFICATIONS()
1: class CColorComboBox
2: : public CWindowImpl<CColorComboBox, CComboBox>
3: , public COwnerDraw<CColorComboBox>
4: {5: public:
6: CColorComboBox(){}7: ~CColorComboBox(){}8:9: protected:
10: BEGIN_MSG_MAP(CColorComboBox)11: CHAIN_MSG_MAP_ALT(COwnerDraw<CColorComboBox> ,1)12: DEFAULT_REFLECTION_HANDLER()13: END_MSG_MAP()14: }
如果是子類化,在界面設計器中添加一個ComboBox控件,修改其Owner Draw屬性為“Variable”,Has Strings屬性設置為“true”。在CColorComboBox類中,重載SubclassWindow函數:
1: BOOL SubclassWindow(HWND hWnd)2: {3: ATLASSERT(hWnd);4: ATLASSERT(::IsWindow(hWnd));5:6: BOOL bRet = __super::SubclassWindow(hWnd);7: if ( bRet )
8: _Init();9:10: ATLASSERT(GetStyle() & CBS_OWNERDRAWVARIABLE);11: ATLASSERT(GetStyle() & CBS_HASSTRINGS);12:13: return bRet;
14: }
如果是動態創建,重載Create函數:
1: HWND Create(HWND hWndParent, _U_RECT rect, _U_MENUorID MenuOrID = 0U)2: {3: HWND hWnd = __super::Create(hWndParent, rect, NULL,4: WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWN /*or CBS_DROPDOWNLIST*/
5: | CBS_HASSTRINGS | CBS_OWNERDRAWFIXED/* or CBS_OWNERDRAWVARIABLE*/,
6: 0, MenuOrID, NULL);7:8: if ( hWnd )
9: _Init();10:11: return hWnd;
12: }
自繪下拉類表,重載DrawItem函數:
1: void DrawItem(LPDRAWITEMSTRUCT lpdis)
2: {3: if ( (long)lpdis->itemID == -1 )4: return;
5:6: CDCHandle dc(lpdis->hDC);7: CRect rcItem(lpdis->rcItem);8: CString strItem;9: GetLBText(lpdis->itemID, strItem);10:11: dc.SetBkMode(TRANSPARENT);12:13: BOOL bSelected = (lpdis->itemState & ODS_SELECTED) ? TRUE : FALSE;14: if ( bSelected )
15: {16: dc.FillSolidRect(rcItem, RGB(238, 238, 238));17: dc.SetTextColor(RGB(0, 0, 0));18: }19: else
20: {21: dc.FillSolidRect(rcItem, RGB(218, 218, 218));22: dc.SetTextColor(RGB(255, 255, 255));23: }24:25: dc.DrawText(strItem, strItem.GetLength(), rcItem, DT_LEFT | DT_SINGLELINE);26: }
改變寬度高度,重載MeasureItem函數:
1: void MeasureItem(LPMEASUREITEMSTRUCT lpmis)
2: {3: if ( (long)lpmis->itemID == -1 )4: lpmis->itemHeight = 15; // 文本框(對子類化無效)
5: else
6: lpmis->itemHeight = 40; // 列表
7: }
改變CEdit顏色,響應WM_CTLCOLOREDIT消息,返回新的畫刷:
1: HBRUSH OnCtlColorEdit(CDCHandle dc, CEdit edit)2: {3: dc.SetBkMode(TRANSPARENT);4: dc.SetTextColor(RGB(255, 0, 0));5:6: HBRUSH hBrush = ::CreateSolidBrush(RGB(216, 216, 216));7: return hBrush;
8: }
最終效果:
完整代碼:ColorComboBox vs2008