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

目录

引言

一、通用控件简介

[二、 WM_NOTIFY 消息](#二、 WM_NOTIFY 消息)

三、通用控件的使用

[3.1 进度条](#3.1 进度条)

[3.2 滑块](#3.2 滑块)

[3.3 `ListControl`](#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`中。部分代码如下:

cpp 复制代码
#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`如下:

cpp 复制代码
typedef struct tagNMHDR {
    HWND hwndFrom;  // 控件句柄
    UINT_PTR idFrom;  // 控件ID
    UINT code;  // NM_code
} NMHDR;

这只是一个一般的结构,如果我们需要知道`ListView`选中的行和列,那么需要:

cpp 复制代码
typedef struct tagNMLISTVIEW {
    NMHDR hdr;  // NMHDR
    int 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`的结构体,用于插入一列时提供列的信息,比如宽度、列名、插入的是第几列等等。

cpp 复制代码
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 point
    int cxDefault; // default snap point
    int cxIdeal;   // read only.
#endif
} LVCOLUMN, *LPLVCOLUMN;

仅介绍几个重要的字段:

有的人会问`iSubItem`有什么用,好像什么用都没有,随便填不会影响程序的结果。

以下是`LVITEM`的结构体:

cpp 复制代码
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 columns
    PUINT puColumns;
#endif
#if (NTDDI_VERSION >= NTDDI_VISTA)
    int *piColFmt;
    int iGroup; // readonly.only valid for owner data.
#endif
} LVITEMW, *LPLVITEMW;

常用字段如下:

先熟悉这些就行,更详细的可以查阅MSDN。

以下是具体示例代码:

resource.h

cpp 复制代码
#ifndef RESOURCE_H
#define RESOURCE_H

#define IDC_LIST1     106
#define IDD_DIALOG1   107
#endif // RESOURCE_H

rc代码

cpp 复制代码
#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
BEGIN
    CONTROL         "", IDC_LIST1, "SysListView32", LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP, 10, 10, 380, 280
END

cpp代码

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

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

相关推荐
酷酷的崽7983 分钟前
如何在AVL树中高效插入并保持平衡:一步步掌握旋转与平衡因子 —— 平衡因子以及AVL结构篇
c语言·数据结构·c++
阿巴~阿巴~17 分钟前
蓝桥杯刷题——第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
c语言·c++·蓝桥杯
敲上瘾28 分钟前
共享内存通信效率碾压管道?System V IPC原理与性能实测
linux·运维·服务器·c++·算法·信息与通信
夜泉_ly1 小时前
项目日记 -云备份 -项目认识与环境搭建
linux·网络·c++
.m1 小时前
Adobe Photoshop CC 2025配置要求
windows
jyan_敬言1 小时前
【C++】入门基础(二)引用、const引用、内联函数inline、nullptr
c语言·开发语言·数据结构·c++·青少年编程·编辑器
UpUpUp……1 小时前
模拟String基本函数/深浅拷贝/柔性数组
开发语言·c++·算法
yerennuo2 小时前
windows第十三章 GDI绘图技术
windows
曦月逸霜3 小时前
第十次CCF-CSP认证(含C++源码)
数据结构·c++·算法·ccf-csp