欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > 【第九节】windows sdk编程:通用控件的使用

【第九节】windows sdk编程:通用控件的使用

2025/3/16 11:15:24 来源:https://blog.csdn.net/linshantang/article/details/146280278  浏览:    关键词:【第九节】windows sdk编程:通用控件的使用

目录

引言

一、通用控件简介

二、 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;
}

        在以上的示例中,我们演示了如何操作对话框上的列表框控件,并且响应了它的鼠标点击消息。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词