目录
引言
一、通用控件简介
二、 WM_NOTIFY 消息
三、通用控件的使用
3.1 进度条
3.2 滑块
3.3 `ListControl`
引言
通用控件是Windows操作系统扩展的一组功能丰富的界面元素,广泛应用于各类应用程序中。它们不仅简化了用户界面的开发,还提供了强大的交互功能。从简单的进度条到复杂的列表视图,通用控件通过动态创建或拖拽方式轻松集成到应用程序中。对于复杂的控件,如列表控件和树控件,Windows采用`WM_NOTIFY`消息机制来通知父窗口用户的操作,取代了传统的`WM_COMMAND`消息。
一、通用控件简介
通用控件是Windows操作系统提供的一组扩展控件,其中一些控件的使用相对简单,而另一些则较为复杂。对于复杂的通用控件,例如列表控件(List Control)和树控件(Tree Control),它们与父窗口的通信不再通过`WM_COMMAND`消息,而是通过`WM_NOTIFY`消息来实现。
我们可以通过两种主要方式来创建通用控件:动态创建和对话框拖拽。根据具体需求,开发者可以灵活选择适合的方式。以下是动态创建通用控件的步骤:
1. 包含头文件:在代码中包含`<CommCtrl.h>`头文件,以便使用通用控件的相关定义和函数。
2. 链接库文件:确保项目链接了`ComCtrl32.lib`库文件,以便在编译时正确引用通用控件的函数。
3. 初始化通用控件:调用`InitCommonControls`函数来初始化通用控件库。
4. 创建控件:使用`CreateWindowEx`函数动态创建所需的通用控件。
相比之下,对话框拖拽方式更为简便。开发者只需在开发环境的工具箱中找到所需的控件,然后直接将其拖拽到对话框模板上即可完成控件的创建和布局。这种方式特别适合快速构建用户界面。
常见的通用控件如下:
需要注意的是,`Property sheets`(属性表)、`property pages`(属性页)和`image list`(图像列表)控件具有专门的创建函数,开发者应使用这些特定函数来创建它们,而不是通过通用的`CreateWindowEx`函数。此外,`Drag list`(可拖拽列表)本质上是一个支持拖拽功能的`listbox`(列表框)控件,因此它并没有独立的类名,而是基于标准列表框的功能扩展而来。
此外,有些常用的通用控件,Windows对类名还提供了宏,定义在`commctrl.h`中。部分代码如下:
#ifdef _WIN32
#define WC_LISTVIEWA "SysListView32"
#define WC_LISTVIEWW L"SysListView32"
#ifdef UNICODE
#define WC_LISTVIEW WC_LISTVIEWW
#else
#define WC_LISTVIEW WC_LISTVIEWA
#endif
#endif
现列举出一些常见类名的宏:
这些通用控件可以有通用的窗口类风格,如`WS_CHILD`、`WS_VISIBLE`等。它们当然还有其他的特殊风格,例如按钮控件有`BS_PUSHBUTTON`风格,树型视图控件有`TVS_XXXXX`风格,列表控件有`LVS_XXXX`风格。如下表:
二、 WM_NOTIFY 消息
我们知道,对于`WM_COMMAND`消息,其附加消息是这样的:
一切运行得非常顺利。通过`WPARAM`的高位设置为1或0,可以区分菜单、快捷键或控件事件;通过`WPARAM`的低位,可以获取发出`WM_COMMAND`消息的菜单项或控件的ID;而通过`LPARAM`,则可以获取控件的句柄。
然而,有一天,当用户选中`ListControl`控件中的某一行时,问题出现了:父窗口需要知道被选中行的索引。这时,开发者发现`WM_COMMAND`消息的`WPARAM`和`LPARAM`已经被完全占用,无法再传递额外的信息。这该怎么办呢?一种解决方案是引入一个新的消息,命名为`WM_LIST_CONTROL_CLICKED`,专门用于传递列表控件中被选中行的索引信息。这样,父窗口就可以通过处理这个消息来获取所需的数据,从而解决信息传递的难题。如下:
这确实可以解决问题,但是问题出现了,这么多的控件,每一个都有多种复杂操作,给每一个复杂操作加一个消息,这样既不绿色也不环保。于是乎,`WM_NOTIFY`消息出现了。
现在,我们将所有附加信息都存放在`NMHDR`(Notify Message Handler)的一个结构体中,该结构体指针通过`LPARAM`通知到父窗口。`NMHDR`如下:
typedef struct tagNMHDR {HWND hwndFrom; // 控件句柄UINT_PTR idFrom; // 控件IDUINT code; // NM_code
} NMHDR;
这只是一个一般的结构,如果我们需要知道`ListView`选中的行和列,那么需要:
typedef struct tagNMLISTVIEW {NMHDR hdr; // NMHDRint iItem; // 行号int iSubItem; // 列号UINT uNewState;UINT uOldState;UINT uChanged;POINT ptAction;LPARAM lParam;
} NMLISTVIEW, *LPNMLISTVIEW;
像其他的控件,都会对应这样一个结构体,它们的第一个字段一定是`NMHDR`。
微软标准控件并不会发送`WM_NOTIFY`消息,这些控件有:`Edit`、`ComboBox`、`ListBox`、`Button`、`ScrollBar`、`Static`等。
三、通用控件的使用
3.1 进度条
常用操作:
- 设置进度:`SendMessage(hPosControl, PBM_SETPOS, 数值, 0);`
- 获取进度:`int nPos = SendMessage(hPosControl, PBM_GETPOS, 0, 0);`
注意:进度条控件不需要通知父窗口什么消息,一般只需要设置进度条反馈给用户当前进度。
3.2 滑块
常用操作:
- 设置范围:`SendMessage(hSliderControl, TBM_SETRANGE, true, MAKELONG(最小值, 最大值));`
- 设置滑块值:`SendMessage(hSliderControl, TBM_SETPOS, true, 值);`
- 获取滑块值:`int nPos = SendMessage(hSliderControl, TBM_GETPOS, 0, 0);`
滑块类似于视频播放软件、音乐播放软件上调整播放位置的控件。在你移动滑块的时候,会给父窗口发送消息,告知用户移动了滑块。
滑块控件在被拖动的时候会给父窗口发送消息:`WM_HSCROLL`(横着)或`WM_VSCROLL`(竖着)。通过这两个消息能够得知滑块被拖动到了哪里,并做出相应的处理。具体消息携带内容,请查阅MSDN。
3.3 `ListControl`
`ListControl`与`TreeControl`相对来说是比较复杂的控件,也是我们讲的最后两个控件。但两个控件的使用方式都极为相似,可以互相印证着学习。我们先来看看`ListControl`控件。
首先,有这么几点概念需要掌握:
- 列表框由行和列组成,故而添加行与添加列是最需要掌握的基本操作。
- 要掌握添加行与添加列就需要掌握两个结构体`LVITEM`与`LVCOLUMN`,和两个宏:
- `ListView_InsertColumn(hWnd, nIndex, LPVCOLUMN)`:用于插入列
- `ListView_InsertItem(hWnd, LVITEM);`:用于插入行
- 还需要掌握另外一个给列表框设置文本的宏:`ListView_SetItemText(hWnd, 行号, 列号, 文本);`
- 说它们是宏,大家可能也注意到了,本质上它们都是`SendMessage()`。
以下是`LVCOLUMN`的结构体,用于插入一列时提供列的信息,比如宽度、列名、插入的是第几列等等。
typedef struct tagLVCOLUMNW {UINT mask;int fmt;int cx;LPWSTR pszText;int cchTextMax;int iSubItem;int iImage;int iOrder;
#if (NTDDI_VERSION >= NTDDI_VISTA)int cxMin; // min snap pointint cxDefault; // default snap pointint cxIdeal; // read only.
#endif
} LVCOLUMN, *LPLVCOLUMN;
仅介绍几个重要的字段:
有的人会问`iSubItem`有什么用,好像什么用都没有,随便填不会影响程序的结果。
以下是`LVITEM`的结构体:
typedef struct tagLVITEMW {UINT mask;int iItem;int iSubItem;UINT state;UINT stateMask;LPWSTR pszText;int cchTextMax;int iImage;LPARAM lParam;int iIndent;
#if (NTDDI_VERSION >= NTDDI_WINXP)int iGroupId;UINT cColumns; // tile view columnsPUINT puColumns;
#endif
#if (NTDDI_VERSION >= NTDDI_VISTA)int *piColFmt;int iGroup; // readonly.only valid for owner data.
#endif
} LVITEMW, *LPLVITEMW;
常用字段如下:
先熟悉这些就行,更详细的可以查阅MSDN。
以下是具体示例代码:
resource.h
#ifndef RESOURCE_H
#define RESOURCE_H#define IDC_LIST1 106
#define IDD_DIALOG1 107
#endif // RESOURCE_H
rc代码
#include <windows.h>
#include <winres.h>
#include <commctrl.h>
#include "resource.h"// 对话框资源定义
IDD_DIALOG1 DIALOGEX 0, 0, 400, 300
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "列表视图示例"
FONT 9, "MS Shell Dlg", 0, 0, 0x1
BEGINCONTROL "", IDC_LIST1, "SysListView32", LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP, 10, 10, 380, 280
END
cpp代码
#include <windows.h>
#include <CommCtrl.h>
#include "resource.h" // 包含资源头文件
#pragma comment(lib, "comctl32.lib")// 对话框过程函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd
) {// 显示对话框DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);return 0;
}// 对话框过程函数实现
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_INITDIALOG: {// 初始化列表框控件HWND hList = GetDlgItem(hwndDlg, IDC_LIST1);ListView_SetExtendedListViewStyle(hList, LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);// 定义列标题const TCHAR* columns[] = { L"Column 1", L"Column 2", L"Column 3" };const int columnWidth = 100;// 插入列LVCOLUMN lvc = {};lvc.mask = LVCF_TEXT | LVCF_WIDTH;lvc.cx = columnWidth;for (int i = 0; i < sizeof(columns) / sizeof(columns[0]); ++i) {lvc.pszText = (WCHAR*)columns[i];ListView_InsertColumn(hList, i, &lvc);}// 插入行数据for (int i = 0; i < 20; ++i) {LVITEM lvi = {};lvi.mask = LVIF_TEXT;lvi.iItem = i; // 行号lvi.iSubItem = 0; // 列号lvi.pszText = (WCHAR*)L"Row Data";ListView_InsertItem(hList, &lvi);// 设置子项文本ListView_SetItemText(hList, i, 1, (WCHAR*)L"SubItem 1");ListView_SetItemText(hList, i, 2, (WCHAR*)L"SubItem 2");}}break;case WM_COMMAND:// 处理命令消息break;case WM_NOTIFY: {// 处理通知消息NMHDR* pNmHdr = (NMHDR*)lParam;if (pNmHdr->code == NM_CLICK && pNmHdr->idFrom == IDC_LIST1) {NMITEMACTIVATE* pNmItem = (NMITEMACTIVATE*)lParam;TCHAR szText[1024] = {};ListView_GetItemText(pNmHdr->hwndFrom, pNmItem->iItem, pNmItem->iSubItem, szText, 1024);MessageBox(hwndDlg, szText, L"Item Clicked", MB_OK);}}break;case WM_CLOSE:// 关闭对话框EndDialog(hwndDlg, 0);break;default:// 默认处理break;}return 0;
}
在以上的示例中,我们演示了如何操作对话框上的列表框控件,并且响应了它的鼠标点击消息。