查看:一个简单的 Win32 SDK 勾选列表框和组合框






4.90/5 (27投票s)
为标准组合框和列表框控件添加复选框
目录
引言
在我研究 Win32 SDK 属性网格 [^] 项目时,我遇到了 Magnus Egelberg 一个很棒的 CheckComboBox 控件 [^],并决定为我的 ANSI C99 项目编写类似的东西。完成后,我决定再添加一个自定义的勾选列表框,做成一个“一石二鸟”的方案。
更新:请查看我最新关于此主题的处理方式 这里 [^],我将在其中描述一种不同的自定义这些控件的方法,包括对项目用户数据的支持。
勾选组合框
使用此控件相当直接。首先,由于它是一个自定义控件,因此在使用前必须对其进行初始化。
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
INITCOMMONCONTROLSEX icc;
WNDCLASSEX wcx;
ghInstance = hInstance;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);
InitCheckedComboBox(hInstance);
//
// Do other stuff
//
勾选组合框的消息和宏
使用 Windows 消息配置控件以实现所需功能。此控件采用标准的组合框消息/宏,但有一些例外。
使用 ComboBox_SetText()
或显式发送 WM_SETTEXT
来设置控件的文本将不起作用,因为控件显示的文本由下拉列表中的选择决定。此外,我还添加了两条消息 CBCM_FLATCHECKS
和 CBCM_CHECKALL
来启用一些显示/行为定制。
我创建了以下宏,用于在配置此控件时与标准的组合框宏一起使用。如果您更喜欢显式调用 SendMessage()
或 PostMessage()
,请参考头文件中的宏定义以了解用法。
CheckedComboBox_SetCheckState
检查或取消勾选勾选 combobox
控件中的项目。
INT CheckedComboBox_SetCheckState(
HWND hwndCtl
INT iIndex
BOOL fCheck
);
/* Parameters
hwndCtl
Handle of a checked combobox.
iIndex
The zero-based index of the item for which to set the check state.
fCheck
A value that is set to TRUE to select the item, or FALSE to deselect it.
Return Values
The zero-based index of the item in the combobox. If an error occurs,
the return value is CB_ERR.*/
CheckedComboBox_GetCheckState
获取勾选 combobox
控件中项目的勾选状态。
BOOL CheckedComboBox_GetCheckState(
HWND hwndCtl
INT iIndex
);
/* Parameters
hwndCtl
Handle of a checked combobox.
iIndex
The zero-based index of the item for which to get the check state.
Return Values
Nonzero if the given item is checked, or zero otherwise.*/
CheckedComboBox_SetFlatStyleChecks
设置复选框的外观。
BOOL CheckedComboBox_SetFlatStyleChecks(
HWND hwndCtl
BOOL fFlat
);
/* Parameters
hwndCtl
Handle of a checked combobox.
fFlat
TRUE for flat checkboxes, or FALSE for standard checkboxes.
Return Values
No return value.*/
CheckedComboBox_EnableCheckAll
设置全选/取消全选功能。
BOOL CheckedComboBox_SetFlatStyleChecks(
HWND hwndCtl
BOOL fEnable
);
/* Parameters
hwndCtl
Handle of a checked combobox.
fEnable
TRUE enables right mouse button select/deselect all feature, or FALSE disables feature.
Return Values
No return value.*/
勾选组合框通知
勾选的 combobox
通知消息与标准 combobox
发送的消息相同。勾选 combobox
的父窗口通过 WM_COMMAND
消息接收通知消息。下面是一个近距离通知的示例。
CBN_CLOSEUP
idComboBox = (int) LOWORD(wParam); // identifier of checked combobox
hwndComboBox = (HWND) lParam; // handle of checked combobox
勾选列表框
与勾选组合框一样,使用此控件相当直接。同样,由于它是一个自定义控件,因此在使用前必须对其进行初始化。
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
INITCOMMONCONTROLSEX icc;
WNDCLASSEX wcx;
ghInstance = hInstance;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icc);
InitCheckedListBox(hInstance);
//
// Do other stuff
//
勾选列表框的消息和宏
使用 Windows 消息配置控件以实现所需功能。此控件采用标准的列表框消息/宏,但有一些例外。
我添加了两条消息 LBCM_FLATCHECKS
和 LBCM_CHECKALL
来启用一些显示/行为定制。
我创建了以下宏,用于在配置此控件时与标准的列表框宏一起使用。如果您更喜欢显式调用 SendMessage()
或 PostMessage()
,请参考头文件中的宏定义以了解用法。
CheckedListBox_SetCheckState
检查或取消勾选勾选列表框控件中的项目。
INT CheckedListBox_SetCheckState(
HWND hwndCtl
INT iIndex
BOOL fCheck
);
/* Parameters
hwndCtl
Handle of a checked listbox.
iIndex
The zero-based index of the item for which to set the check state.
fCheck
A value that is set to TRUE to select the item, or FALSE to deselect it.
Return Values
The zero-based index of the item in the combobox. If an error occurs,
the return value is LB_ERR.*/
CheckedListBox_GetCheckState
获取勾选列表框控件中项目的勾选状态。
BOOL CheckedListBox_GetCheckState(
HWND hwndCtl
INT iIndex
);
/* Parameters
hwndCtl
Handle of a checked listbox.
iIndex
The zero-based index of the item for which to get the check state.
Return Values
Nonzero if the given item is checked, or zero otherwise.*/
CheckedListBox_SetFlatStyleChecks
设置复选框的外观。
BOOL CheckedListBox_SetFlatStyleChecks(
HWND hwndCtl
BOOL fFlat
);
/* Parameters
hwndCtl
Handle of a checked listbox.
fFlat
TRUE for flat checkboxes, or FALSE for standard checkboxes.
Return Values
No return value.*/
CheckedListBox_EnableCheckAll
设置全选/取消全选功能。
BOOL CheckedListBox_EnableCheckAll(
HWND hwndCtl
BOOL fEnable
);
/* Parameters
hwndCtl
Handle of a checked listbox.
fEnable
TRUE enables right mouse button select/deselect all feature, or FALSE disables feature.
Return Values
No return value.*/
勾选列表框通知
勾选列表框通知消息与标准列表框发送的消息相同,但有一个例外。我添加了 LBCN_ITEMCHECK
通知,以指示列表中项目的勾选状态已更改。勾选列表框的父窗口通过 WM_COMMAND
消息接收通知消息。这是 itemcheck
通知。
LBCN_ITEMCHECK
idListBox = (int) LOWORD(wParam); // identifier of checked listbox
hwndListBox = (HWND) lParam; // handle of checked listbox
设计考量(来自勾选组合框的示例)
MFC 包含用于将所有者绘制消息反射回子控件的机制,有效地使它们成为自绘消息。Joseph M. Newcomer 在 此处 [^](参见“反射消息”部分)提供了关于这一切如何工作的精彩解释。由于我没有使用 MFC,也没有这种方法,我曾考虑简单地子类化一个 combobox
并调用 FORWARD_
消息解包宏将 WM_DRAWITEM
和 WM_MEASUREITEM
发送回我控件的子类过程。
最终,我放弃了这种方法,转而将 combobox
封装在一个不可见窗口中,这使得自定义控件更容易实现,但代价是每个实例需要一个额外的窗口句柄。为了实现这种不可见窗口的方法,我需要定义一个自定义窗口类,处理 WM_CREATE
和 WM_SIZE
以创建子 combobox
并将其大小调整为填充父窗口,最后,透明地将消息路由到和来自我包装的子窗口。
在此,我定义了自定义控件窗口类并进行注册。
ATOM InitCheckedComboBox(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
// Get standard combobox information
wcex.cbSize = sizeof(wcex);
if (!GetClassInfoEx(NULL, WC_COMBOBOX, &wcex))
return 0;
// Add our own stuff
wcex.lpfnWndProc = (WNDPROC)Control_Proc;
wcex.hInstance = hInstance;
wcex.lpszClassName = g_szClassName;
// Register our new class
return RegisterClassEx(&wcex);
}
这是控件过程,它具有对 WM_DRAWITEM
和 WM_MEASUREITEM
的自访问,请注意 default:
调用 DefaultHandler()
方法,我稍后会讲到它。
static LRESULT CALLBACK Control_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
HANDLE_MSG(hwnd, WM_DRAWITEM, Control_OnDrawItem);
HANDLE_MSG(hwnd, WM_MEASUREITEM, Control_OnMeasureItem);
HANDLE_MSG(hwnd, WM_CREATE, Control_OnCreate);
HANDLE_MSG(hwnd, WM_DESTROY, Control_OnDestroy);
HANDLE_MSG(hwnd, WM_SIZE, Control_OnSize);
HANDLE_MSG(hwnd, WM_GETTEXT, Control_OnGetText);
HANDLE_MSG(hwnd, WM_GETTEXTLENGTH, Control_OnGetTextLength);
case WM_SETTEXT:
return 0; // Text to be set by drop down selection only.
case CBCM_FLATCHECKS:
{
DWORD dwUserData = (DWORD)GetWindowLongPtr(GetDlgItem(hwnd, ID_COMBOBOX),
GWLP_USERDATA);
if (FALSE != (BOOL)wParam)
dwUserData |= FLATCHECKS;
else
dwUserData &= ~FLATCHECKS;
return SetWindowLongPtr(GetDlgItem(hwnd, ID_COMBOBOX),
GWLP_USERDATA, (LONG_PTR)dwUserData);
}
case CBCM_CHECKALL:
{
DWORD dwUserData = (DWORD)GetWindowLongPtr(GetDlgItem(hwnd, ID_COMBOBOX),
GWLP_USERDATA);
if (FALSE != (BOOL)wParam)
dwUserData |= CHECKALL;
else
dwUserData &= ~CHECKALL;
return SetWindowLongPtr(GetDlgItem(hwnd, ID_COMBOBOX),
GWLP_USERDATA, (LONG_PTR)dwUserData);
}
default:
return DefaultHandler(hwnd, GetDlgItem(hwnd, ID_COMBOBOX), msg, wParam, lParam);
}
}
这是 WM_CREATE
和处理程序,我在其中通过去除边框来(模拟)实现自定义控件窗口的不可见性,并将其大小调整为子 combobox
的静态部分。子窗口现在决定了控件的外观和感觉。
BOOL Control_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
HWND hCombo;
// Remove CBS_OWNERDRAWVARIABLE if defined and add the bits we need
lpCreateStruct->style &= ~((DWORD)CBS_OWNERDRAWVARIABLE);
// Use default strings. We need the itemdata to store checkmarks
lpCreateStruct->style |= (CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS);
hCombo = CreateWindowEx(lpCreateStruct->dwExStyle, WC_COMBOBOX, NULL,
lpCreateStruct->style, 0, 0,
lpCreateStruct->cx, lpCreateStruct->cy, hwnd,
(HMENU)ID_COMBOBOX, lpCreateStruct->hInstance, NULL);
if (!hCombo)
return FALSE;
SendMessage(hCombo, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0);
// Subclass combobox and save the old proc
SetProp(hCombo, WPROC, (HANDLE)GetWindowLongPtr(hCombo, GWLP_WNDPROC));
SubclassWindow(hCombo, Combo_Proc);
// Configure the parent window to be invisible,
// combobox child to determine appearance.
SetWindowLongPtr(hwnd, GWL_STYLE, WS_CHILD |
(WS_TABSTOP & GetWindowLongPtr(hwnd, GWL_STYLE) ? WS_TABSTOP : 0));
SetWindowLongPtr(hwnd, GWL_EXSTYLE, 0l);
// Certain window data is cached, so changes you make using SetWindowLongPtr
// will not take effect until you call the SetWindowPos() function. SWP_FRAMECHANGED
// causes the window to recalculate the non client area and, in our case,
// remove scroll bar and border.
RECT rc = {0};
GetClientRect(hCombo, &rc); // Client = just the static field of the combo
SetWindowPos(hwnd, NULL, lpCreateStruct->x, lpCreateStruct->y,
lpCreateStruct->cx, rc.bottom - rc.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
// Create and store a circular buffer (for Join())
SetProp(hwnd, PROPSTORAGE, calloc(2, sizeof(LPTSTR)));
return TRUE;
}
以及 WM_SIZE
处理程序,其中控件的高度固定为子 combobox
静态部分的高度。
VOID Control_OnSize(HWND hwnd, UINT state, INT cx, INT cy)
{
HWND hCombo = GetDlgItem(hwnd, ID_COMBOBOX);
RECT rc = {0};
GetClientRect(hCombo, &rc); // Client = just the field of the combo
//Size comboBox component to fill parent
SetWindowPos(hCombo, NULL, 0, 0, cx, rc.bottom - rc.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
最后,我将大多数消息透明地路由到子控件,但不是所有消息。有些消息源自子控件,因此将它们路由回来会导致循环,直到堆栈溢出。其他消息或通知旨在由隐藏窗口的父窗口处理。这些例外情况通过以下方法得到很好的处理
static LRESULT DefaultHandler
(HWND hwnd, HWND hChild, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
// These messages to be handled by this window
case WM_DRAWITEM:
case WM_MEASUREITEM:
case WM_CREATE:
case WM_DESTROY:
case WM_SIZE:
// Sending these to child will cause stack overflow
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
case WM_MOUSEACTIVATE:
// Sending these to child will cause improper sizing / positioning
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED:
case WM_NCCALCSIZE:
// Sending this to child will mess up child paint
case WM_PAINT:
break; //<- End Fallthrough
// Pass child notifications to parent
case WM_COMMAND:
FORWARD_WM_COMMAND(GetParent(hwnd), GetDlgCtrlID(hwnd), hwnd,
HIWORD(wParam), SNDMSG);
return 0;
case WM_NOTIFY:
((LPNMHDR)lParam)->hwndFrom = hwnd;
((LPNMHDR)lParam)-&idFrom = GetDlgCtrlID(hwnd);
return FORWARD_WM_NOTIFY(GetParent(hwnd), ((LPNMHDR)lParam)->idFrom,
(LPNMHDR)lParam, SNDMSG);
default: // The rest of the messages passed to child (if it exists)
{
if(NULL != hChild)
return SNDMSG(hChild, msg, wParam, lParam);
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
勾选列表框共享此基本架构,但实现起来当然更简单。
最终评论
我用 Doxygen [^] 注释记录了此源代码,以帮助可能发现它有用的人。您的反馈将不胜感激。
历史
- 2010年9月24日:版本 1.0.0.0
- 2017年6月6日:版本 1.1.0.0 - 添加了对禁用复选框项目的支持