65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (27投票s)

2010年9月29日

CPOL

5分钟阅读

viewsIcon

76082

downloadIcon

8725

为标准组合框和列表框控件添加复选框

目录

引言

在我研究 Win32 SDK 属性网格 [^] 项目时,我遇到了 Magnus Egelberg 一个很棒的 CheckComboBox 控件 [^],并决定为我的 ANSI C99 项目编写类似的东西。完成后,我决定再添加一个自定义的勾选列表框,做成一个“一石二鸟”的方案。

更新:请查看我最新关于此主题的处理方式 这里 [^],我将在其中描述一种不同的自定义这些控件的方法,包括对项目用户数据的支持。

勾选组合框

Demo screenshot

使用此控件相当直接。首先,由于它是一个自定义控件,因此在使用前必须对其进行初始化。

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_FLATCHECKSCBCM_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 

勾选列表框

Demo screenshot

与勾选组合框一样,使用此控件相当直接。同样,由于它是一个自定义控件,因此在使用前必须对其进行初始化。

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_FLATCHECKSLBCM_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_DRAWITEMWM_MEASUREITEM 发送回我控件的子类过程。

最终,我放弃了这种方法,转而将 combobox 封装在一个不可见窗口中,这使得自定义控件更容易实现,但代价是每个实例需要一个额外的窗口句柄。为了实现这种不可见窗口的方法,我需要定义一个自定义窗口类,处理 WM_CREATEWM_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_DRAWITEMWM_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 - 添加了对禁用复选框项目的支持
© . All rights reserved.