窗口与消息

在用户眼中 这些窗口是屏幕商的对象 并可借助键盘或者鼠标直接与之进行交互 程序员的视角和用户非常一致 用户对窗口的输入以”消息”的形式 传递给窗口 而窗口也借助消息与其他窗口进行通信 深入理解windows消息这个概念是学习widnows非常重要的一个步骤
当我们修改程序窗口尺寸时,windows便向应用程序发送一条携带新窗口尺寸相关信息的消息 接着应用程序对消息作出反应改变窗口的尺寸
windwos向应用程序发送了一条消息 其实在说windows调用了程序内部的一个函数 且是该程序的核心 该函数的参数是windows所发送给你程序接受的消息 这个函数被称为窗口过程
窗口是窗口类创建的 窗口类表示了用于处理传递给窗口的消息的窗口过程 窗口类的使用允许多个窗口共享同一窗口类 例如 windows程序中的所有按钮都是基于相同的窗口类 与窗口类关联的窗口过程位于一个windows动态链接库中 他可对所有传递到按钮窗口的消息进行处理
面向对象编程中 对象是数据和代码的结合 一个窗口也是一个对象 其代码对应床后过程 数据对应窗口过程保留的信息以及windows为每个窗口存在于系统中所保留的信息
窗口过程就是用于处理传递给窗口的消息 通过这些消息将用户的操作告诉窗口等等 消息是如何存放和发出的呢? 当windows程序开始被执行时 windows先创建一个消息队列 该消息队列中存放着应用程序可能创建的所有窗口的消息 windows应用程序一般都包含一小段称为消息循环(message loop )的代码 改代码用于从消息队列中检索消息 并将其分发给相应的窗口过程 其他消息则不经过消息队列直接发送给窗口过程
可以通过一个例子看一下窗口的实际应用

HELLOWIN程序

要创建窗口首先要注册一个窗口类 而窗口类又需要窗口过程来处理窗口消息

#include<windows.h>
LRESULT CALLBACK Wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreinstance, PSTR szCmdLine, int iCmdShow0
)
{
	static TCHAR szAppname[] = TEXT("HELLOwin");
	HWND hwnd;
	MSG msg;
	WNDCLASS wndclass;
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = Wndproc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppname;
	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows Nt"), szAppname
			, MB_ICONERROR);
		return 0;	
	}
	hwnd = CreateWindow(szAppname, 
		TEXT("hello win"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
	ShowWindow(hwnd, iCmdShow0);
	UpdateWindow(hwnd);
	while (GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}
LRESULT CALLBACK Wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	switch (message)
	{
	case WM_CREATE:
		return 0;
	case WM_PRINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		DrawText(hdc, TEXT("hello windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	}
	return	DefWindowProc(hwnd, message, wParam, lParam);
}

这么框架代码我想没有人会记住这种框架的所有细节 通常 都是cv代码复制到新的程序中去 然后加以修改 接下来我们会以上面的程序为例分析一些细节
Wndproc是我们的窗口过程函数 在本程序中并未有任何调用窗口过程函数出现 但是winmain中有一个引用

函数调用

  • LoadIcon 加载图标 供程序使用
  • LoadCursor 加载鼠标光标 供程序使用
  • Getstockobject 获取一个图像对象 在本例中是一个用来对窗口背景进行重绘的画刷
  • Registerclass 为应用程序的窗口注册一个类
  • Messagebox 显式消息框
  • createwindow 基于窗口类创建一个窗口
  • showwindow在屏幕中显式窗口
  • updatewindow 指示窗口对其自身进行重绘
  • getmessage 从消息队列获取消息
  • translatemessage 翻译一些键盘消息
  • dispatchmessage 将消息发送给窗口过程
  • beginpaint 标明窗口绘制开始
  • drawtext 显示一个文本字符串
  • endpaint 结束窗口绘制
  • postquitmessage 将退出消息插入消息队列
  • Defwindowproc执行默认的消息处理
    窗口中常见的常量
    CS 类风格选项
    CW 创建窗口选项
    DT文本绘制选项
    IDI图标的ID号
    IDC光标的ID号
    MB 消息框选项
    SND声音选项
    WM 窗口消息
    WS窗口风格
    UNIT 就是无符号整型 PSTR 表示char*
    wParam 和 lParam在32位系统中分别表示UNIT和Long
    LRESULT表示long类型
    CALLBACK和WINAPI都是_stdcall
    四种数据结构
    MSG 消息结构
    WNDCLASS窗口类结构
    PAINTSTRUCT绘制结构
    RECT矩形结构
    理解句柄
    对于各种类型的句柄 有下表所示的三种大写标识符
    HSITANCE 程序实例句柄
    HWND窗口句柄
    HDC设备环境句柄
    句柄本质上是引用某个对象的数值。句柄的实际取值并不重要,将句柄传递给你的程序的windows模块则知道如何通过句柄来引用对象

窗口类的注册

窗口是基于窗口类来创建的 窗口类确定了处理窗口消息的窗口过程
多个窗口可以同时基于某一窗口类创建 窗口类为所有这些窗口定义了窗口过程和一些其他特性 当创建一个窗口时 我们往往还需要定义一些窗口特有的附加特性
创建应用程序窗口之前 必须调用registerclass来注册窗口类 该函数只需要一个参数 即一个wndclass的指针 对于窗口类有两种版本 一种是ASCII一种是UNICODE 区别是最后两个字段字符串的编码格式 我们一般定义的是wndclass 他的最后两个字段为LPCTSTR

typedef struct
{
UNIT stytle
WNDPROC lpfnWndproc
int cbClsExtra
int cbWndExtra
HISTANCE hINstance
HICON hIcon
HCURSOR hCursor
HBRUSH hbrBackground
LPCTSTR lpszMenuName
LPCTSTR lpszClassName
}
WNDCLASS,*PWNDCLASS;

其中最重要是第二个字段和最后一个字段 第二个字段是基于该窗口类的所有窗口的窗口过程的地址 即Wndproc 最后一个字段是窗口类的名称 允许用户任意命名 当程序只创建一个窗口时 窗口类的名称和程序名相同 窗口过程函数将处理所有基于该窗口
对于wndclass结构的第二个字段 初始化语句如下
wndclass.lpfnWndproc = Wndproc;
该语句将该窗口类的窗口过程设为Wmdproc函数 即HELLOWIN.C中的第二个函数 这个函数将处理传递给所有基于该窗口类创建的窗口的消息
cbxxExtra字段是用在类结构和windows内部维护的窗口结构中预留一些额外的空间
应用程序的实例句柄字段(WinMain的一个参数)
hinstance
hicon字段 为所有基于该窗口类创建的窗口设定一个图标
hbrbackground 为这类窗口的客户区指定了背景色 hbr表示画刷的句柄GetStockobject(WHITE_BRUSH);这意味着窗口客户区的颜色将被填充为白色
lpszmenuname 没有菜单填NULL
最后一个是窗口类的名称
当WNDCLASS结构的十个字段完成初始化之后 程序通过RegisterClass完成对窗口类的注册 注册窗口类的函数也有两个 一个是ASCII一个是UNICODE 参数也会随之改变
怎么知道函数调用哪里出错 可以使用getlasterror函数是windows中的一个通用函数 用于获取当函数调用失败时的扩展错误信息 各函数的说明文档中都指示了是否可以利用getlasterror获取这个信息

窗口的创建

由于窗口类只是定义了窗口的一般特征 因此基于同一窗口可以创建许多不同的窗口 在调用CreateWindow函数创建窗口时 可以指定许多与窗口有关的细节信息
所有的下压按钮窗口都基于相同的窗口类 与该窗口类关联的窗口过程位于windows内部 并负责处理鼠标和键盘对按钮的输入 以及定义按钮在屏幕上的视觉外观 这些都属于窗口定义而非窗口类定义

hwnd = CreateWindow(szAppName      //窗口类名称
					TEXT("the hello program")//窗口标题
					WS_OVERLAPPEDWINDOW   //窗口风格
					CW_USEDEFAULT//初始x坐标
					CW_USEDEFAULT//初始y坐标
					CW_USEDEFAULT//初始x方向尺寸
					CW_USEDEFAULT//初始y方向尺寸
					NULL//父窗口句柄
					NULL//窗口菜单句柄
					hInstance//程序实例句柄
					NULL//创建参数
				   );

CreateWindow也有两个版本的函数
窗口类名称的参数是szAppName 该参数中包含”Hello Win”字符串 即程序刚注册窗口类的名称 创建窗口通过这种方式与窗口类建立了关联
该程序创建的窗口是一个普通的层叠窗口(overlapped) 该窗口有一个标题栏 一个位于标题栏左边的系统菜单按钮 一个窗口尺寸调整边框以及位于标题栏右方的三个按钮 这是窗口的标准风格 对应createwindow的第三个参数

#define WS_OVERLAPPEDWINDOW
{
WS_OVERLAPPED|
WS_CAPTION|
WS_SYSMENU|
WS_THICKFRAME|
WS_MINIMIZEBOX|
WS_MAXIMIZEBOX|
}

CW_USEDEFAULT意味着我们将其设置为默认值
如果新建窗口为顶级窗口 注释为父窗口句柄的参数就应设置为NULL 通常当两个窗口存在父子关系时 子窗口总是位于父窗口的前方 应用程序窗口总是位于桌面窗口的前方
窗口菜单句柄 如果不需要菜单的话也应该设为NULL
程序实例句柄则被设为作为WinMain函数参数传入的程序实例句柄
Createwindow的返回值为一个指向所创建窗口的句柄 该句柄保存在变量hwnd中 该变量被认为是HWND类型 windows系统中每一个窗口都有自己的句柄 windows许多函数都以句柄为参数传递

窗口的显示

当createwindow调用返回时 窗口已在windows内部创建(已经有一块存放窗口信息和其他信息的内存) 但如果要将窗口显示在屏幕上 需调用showwindow(hwnd,iCmdShow);第一个参数是创建窗口返回的句柄 第二个参数是WinMain函数所接收的iCmdShow值 该参数决定窗口在屏幕中的初始显式形式 即最大化显式 最小化或者最大化窗口
函数showwindow将窗口显示在屏幕中 如果该函数的第二个参数是SW_SHOWNORMAL 则该窗口的客户区将被在窗口类中所指示的背景画刷擦除 然后下面的调用将使的窗口客户区重绘Updatewindow(hwnd);这是通过窗口过程发送一条WM_PAINT消息而完成的

消息循环

Updatewindow被调用之后 新建窗口在屏幕中完全可见了 此时 该程序必须能够接受来自用户的键盘输入和鼠标输入 windows为当前每一个运行的windows程序都维护了一个消息队列 当输入事件发生后 windows会自动将这些事件转换为消息 并将其放置在应用程序的消息循环队列中
应用程序通过执行一段名为消息循环的代码来从该消息队列中获取消息

while(getmessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

其中msg是一个结构变量 其类型为MSG 定义如下
typedef struct tagMSG
{
HWND hwnd;
UINT message
WPARAM wParam;
LPARAM laram;
DWORD time;
POINT pt;
}
MSG,*PMASG
POINT是另一种结构

typedef struct tagPOINT
{
long x;
long y;
}POINT,\*POINT

开启消息循环的Getmessage函数用于从消息队列中对消息进行检索
Getmessage(&msg,NULL,0,0)
2 3 4个参数分别被设为NULL或者0 表明该程序希望获取由该程序所创建的所有窗口消息 windows用从消息队列中得到的下一条消息来填充消息结构的各个字段
hwnd 消息所指向的窗口的句柄 它与createwindow返回的窗口句柄相同
message 消息标识符 用于标识消息的数字 例如我们左击鼠标左键windows会将一message字段为WM_LBUTTONDOWN的消息放入队列中
Wparam 一个32位的消息参数 该参数的具体含义取决于具体的消息
Lparam 一个32位的消息参数 该参数的具体含义取决于具体的消息
time 消息进入消息队列的时间
pt 消息进入消息队列中时鼠标指针的坐标位置
如果从消息队列中检索到的消息的message字段不等于WM_QUIT 则Getmessage将返回一个非0值 否则返回0
Translatemessage 将msg结构返还给windows以进行某些键盘消息的转换
Dispatchmessage 将msg结构再次返回给windows
接下来 windows会将这条消息发送给合适的窗口过程来处理 也就是说windows调用了窗口过程(Wndproc) 当wndproc处理完消息后 将控制权转回给windows 后者还将Dispatchmessage调用服务 当windows从dispatchmessage返回程序后 消息循环继续下次getmessage调用

窗口过程

前面我们已经说到了包括注册窗口 创建窗口 显示窗口 消息循环 获取检索消息
窗口过程决定了窗口客户区的显示内容以及窗口如何对用户的输入做出响应 在HELLOWIN程序中 窗口过程是一个名为Wndproc的函数 窗口过程的名称可以任意命名 一个windows程序可包含多个窗口过程 但一个窗口过程总是与一个通过调用RegisterClass注册的特定窗口类相关联。
窗口过程如下定义

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)

窗口过程的四个参数和MSG结构的前四个字段是一一对应的。有时一个消息参数是由两个为值组合而成,有时一个消息参数是一个指向文本字符串或一个数据结构的指针
应用程序并不直接对窗口进行调用 窗口过程几乎总还是由windows自身调用的 应用程序如何希望调用自身的窗口过程 则可通过调用函数Sendmessage来实现

消息的处理

窗口过程接受的每条消息都是由一个数字来标识 即窗口过程的message参数
Windows头文件以WM为前缀的标识符
通常windos程序员会用switch结构来确定窗口过程所收到的消息类型以及对应的处理方法 当窗口过程对消息处理后 应返回0 所有窗口过程不进行处理的消息都必须传给名称为Defwindowproc的windows函数
在我们写的窗口过程函数中 我们只对3个消息进行了处理 即WM_CREATE
WM_PAINT WM_DESTROY

case WM_CREATE
处理消息
return 0
case WM_PAINT
处理消息
return 0
case WM_DESTROY
处理消息
return 0

用DefWindowProc来对所有窗口过程没有处理的消息进行默认处理非常重要,否则其他的正常行为(如结束程序)将无法进行。
即wParam中存在的是应用程序的返回代码,所以此处的return msg.wParam的功用是将返回代码依次返回调用层(退出)。 也可以说是WM_QUIT的消息的附加值 一般为0
image.png

创建窗口控件

  1. 按钮
    按钮的创建我们一般选择放在主程序窗口之后或者在主程序窗口创建的时候 会投递一条WM_CREATE的消息 可以在里面创建窗口控件
		CreateWindow(WC_BUTTON, TEXT("按钮1"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 100, 100, 50, hwnd, (HMENU)IDC_BUTTON_1, NULL, NULL);

样式为可点击|可见|子窗口
创建完控件如何响应他的消息呢
WM_COMMAND 这个消息显示了窗口的按钮和菜单被操作的事件 同时我们根据wParam附加消息的高低位判断他的ID号可以消息通知码

	case WM_COMMAND:
	{
		UINT nCtrlId = LOWORD(wParam);
		UINT nCode = HIWORD(wParam);
		if (nCtrlId == IDC_BUTTON_1 && nCode == BN_CLICKED)
		{
			MessageBox(hwnd, TEXT("按钮1被单击"), TEXT("tip"), MB_OK);
		}
	}
image.png
  1. 高级列表框的创建

[VS不能将“const wchar_t*”类型的值分配到“LPWSTR“类型的实体不能将 “const wchar_t *” 类型的值分配到 “lpwstr” 类型的实体清茶染烟月的博客-CSDN博客](https://blog.csdn.net/weixin_45442587/article/details/117148480)

		HWND hListCtrl = CreateWindow(WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | WS_BORDER |LVS_REPORT| LVS_SHOWSELALWAYS, 220, 50, 500, 200, hwnd, (HMENU)IDC_LIST_1, NULL, NULL);

这样就可以创建一个有边框的报表样式的列表框
插入行

LVCOLUMN lvo = {0};
		lvo.mask |= LVCF_TEXT|LVCF_WIDTH;
		lvo.cx = 100;
		lvo.pszText = TEXT("姓名");
		SendMessage(hListCtrl, LVM_INSERTCOLUMN, 0, LPARAM(&lvo));
		LVCOLUMN lvo1 = { 0 };
		lvo1.mask |= LVCF_TEXT | LVCF_WIDTH;
		lvo1.cx = 100;
		lvo1.pszText = TEXT("性别");
		SendMessage(hListCtrl, LVM_INSERTCOLUMN, 1, LPARAM(&lvo1));
		LVCOLUMN lvo2 = { 0 };
		lvo2.mask |= LVCF_TEXT | LVCF_WIDTH;
		lvo2.cx = 100;
		lvo2.pszText = TEXT("年龄");
		//SendMessage(hListCtrl, LVM_INSERTCOLUMN, 1, LPARAM(&lvo2));
		ListView_InsertColumn(hListCtrl, 2, &lvo2);

若要向列表视图控件添加列,请发送 LVM_INSERTCOLUMN 消息或使用 ListView_InsertColumn 宏。 若要删除列,请使用 LVM_DELETECOLUMN 消息。
如果要插入行值 则可以使用ListView_InsertItem宏或者发送LVM_INSERTITEM消息 和插入行是的使用方法是一样的

LV_ITEM lvi = { 0 };
		lvi.mask = LVIF_TEXT;
		lvi.pszText = TEXT("张玥");
		ListView_InsertItem(hListCtrl, &lvi);
		ZeroMemory(&lvi, sizeof(lvi));
		lvi.mask = LVIF_TEXT;
		lvi.iItem = 0;
		lvi.iSubItem = 1;
		lvi.pszText = TEXT("男");
		ListView_SetItem(hListCtrl, &lvi);
		ZeroMemory(&lvi, sizeof(lvi));
		lvi.mask = LVIF_TEXT;
		lvi.iItem = 0;
		lvi.iSubItem = 2;
		lvi.pszText = TEXT("24");
		//ListView_SetItem(hListCtrl, &lvi);
		ListView_SetItemText(GetDlgItem(hwnd, IDC_LIST_1), 0, 2, TEXT("24"));

课后作业

  1. 新建一个按钮 实现点击按钮增加列表框的新行
  2. 实现删除固定4 5行的按钮
  3. 利用信息框显示目前列表的行数
  4. 整行选择
    代码如下
#include<Windows.h>
#include<commctrl.h>
#include <windowsx.h>
#include <tchar.h>
#pragma warning (disable:4996)
UINT IDC_BUTTON_1 = 200;
UINT IDC_BUTTON_2 = 201;
UINT IDC_BUTTON_3 = 202;
UINT IDC_LIST_1 = 300;
UINT IDC_EDIT_1 = 400;
LRESULT CALLBACK Wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);//窗口过程
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreinstance, PSTR szCmdLine, int nCmdShow)
{
	//绑定窗口类和窗口过程函数
	WNDCLASS wndclass = { 0 };
	 static TCHAR szAppname[] = TEXT("神人win32"); 
	 wndclass.lpszClassName = szAppname;
	 wndclass.style = CS_HREDRAW | CS_VREDRAW;
	 wndclass.lpfnWndProc = Wndproc;
	 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	 if (!RegisterClass(&wndclass))
	 {
		 MessageBox(NULL, TEXT("注册窗口类失败"), TEXT("tip"), MB_OK);
		 return 0;
	 }
	 //创建窗口
	 HWND hwnd = CreateWindow(szAppname, szAppname, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);
	 if (!hwnd)
	 {
		 MessageBox(NULL, TEXT("窗口创建失败"), TEXT("tip"), MB_OK);
		 return 0;
	 }
	 ShowWindow(hwnd, nCmdShow);
	 //消息循环 获取消息队列中的消息 转换并再次返回给系统
	 MSG msg;
	 while (GetMessage(&msg,NULL,0,0))
	 {
		 TranslateMessage(&msg);
		 DispatchMessage(&msg);
	 }
	 return msg.wParam;
	 
}
//消息处理
LRESULT CALLBACK Wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT PS;
	HDC hdc;
	RECT rect;
	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &PS);
		GetClientRect(hwnd, &rect);
		DrawText(hdc, TEXT("神人博客:https://deitylei.cn"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
		EndPaint(hwnd, &PS);
		break;
	case WM_CREATE:
	{
	
		HWND hButtonWnd = CreateWindow(WC_BUTTON, TEXT("插入列表行"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 100, 100, 50, hwnd, (HMENU)IDC_BUTTON_1, NULL, NULL);
		HWND hListCtrl = CreateWindow(WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | WS_BORDER |LVS_REPORT| LVS_SHOWSELALWAYS, 220, 50, 500, 200, hwnd, (HMENU)IDC_LIST_1, NULL, NULL);
		DWORD style = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_CHECKBOXES;
		//扩展格式  整行选择
		ListView_SetExtendedListViewStyle(GetDlgItem(hwnd, IDC_LIST_1), style);
		HWND hEdit = CreateWindow(WC_EDIT, TEXT("输入行值"), WS_CHILD | WS_VISIBLE | WS_BORDER, 50, 200, 100, 100, hwnd, (HMENU)IDC_EDIT_1, NULL, NULL);
		HWND hButtonWnd2 = CreateWindow(WC_BUTTON, TEXT("删除4 5行"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 300, 300, 100, 50, hwnd, (HMENU)IDC_BUTTON_2, NULL, NULL);
		HWND hButtonWnd3 = CreateWindow(WC_BUTTON, TEXT("显示行数"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 420, 300, 100, 50, hwnd, (HMENU)IDC_BUTTON_3, NULL, NULL);
		LVCOLUMN lvo = {0};
		lvo.mask |= LVCF_TEXT|LVCF_WIDTH;
		lvo.cx = 100;
		lvo.pszText = TEXT("姓名");
		SendMessage(hListCtrl, LVM_INSERTCOLUMN, 0, LPARAM(&lvo));
		LVCOLUMN lvo1 = { 0 };
		lvo1.mask |= LVCF_TEXT | LVCF_WIDTH;
		lvo1.cx = 100;
		lvo1.pszText = TEXT("性别");
		SendMessage(hListCtrl, LVM_INSERTCOLUMN, 1, LPARAM(&lvo1));
		LVCOLUMN lvo2 = { 0 };
		lvo2.mask |= LVCF_TEXT | LVCF_WIDTH;
		lvo2.cx = 100;
		lvo2.pszText = TEXT("年龄");
		//SendMessage(hListCtrl, LVM_INSERTCOLUMN, 1, LPARAM(&lvo2));
		ListView_InsertColumn(hListCtrl, 2, &lvo2);
		LV_ITEM lvi = { 0 };
		lvi.mask = LVIF_TEXT;
		lvi.pszText = TEXT("JHON");
		ListView_InsertItem(hListCtrl, &lvi);
		ZeroMemory(&lvi, sizeof(lvi));
		lvi.mask = LVIF_TEXT;
		lvi.iItem = 0;
		lvi.iSubItem = 1;
		lvi.pszText = TEXT("男");
		ListView_SetItem(hListCtrl, &lvi);
		ZeroMemory(&lvi, sizeof(lvi));
		lvi.mask = LVIF_TEXT;
		lvi.iItem = 0;
		lvi.iSubItem = 2;
		lvi.pszText = TEXT("18");
		//ListView_SetItem(hListCtrl, &lvi);
		ListView_SetItemText(GetDlgItem(hwnd, IDC_LIST_1), 0, 2, TEXT("24"));
	}
	break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_COMMAND:
	{
		UINT nCtrlId = LOWORD(wParam);
		UINT nCode = HIWORD(wParam);
		if (nCode == BN_CLICKED)
		{
			if (nCtrlId == IDC_BUTTON_1)
			{//新增行
				TCHAR szEdit[MAX_PATH];
				int Rowitotal = ListView_GetItemCount(GetDlgItem(hwnd, IDC_LIST_1));
				Edit_GetText(GetDlgItem(hwnd, IDC_EDIT_1), szEdit, MAX_PATH);
				LV_ITEM lvitemp = { 0 };
				lvitemp.mask = LVIF_TEXT;
				lvitemp.pszText = TEXT("1");
				lvitemp.iItem = Rowitotal;
				int Row = ListView_InsertItem(GetDlgItem(hwnd, IDC_LIST_1), &lvitemp);
				ListView_SetItemText(GetDlgItem(hwnd, IDC_LIST_1), Row, 1, szEdit);
			}
			else if (nCtrlId == IDC_BUTTON_2)
			{
				//删除4 5 行
				ListView_DeleteItem(GetDlgItem(hwnd, IDC_LIST_1), 4);
				ListView_DeleteItem(GetDlgItem(hwnd, IDC_LIST_1), 5);
			}
			else if (nCtrlId == IDC_BUTTON_3)
			{
				//显示行数
				int ItemCount =  ListView_GetItemCount(GetDlgItem(hwnd, IDC_LIST_1));
				TCHAR szCount[100];
				_stprintf(szCount,TEXT("共有 %d 行!"), ItemCount);//将设置格式的数据写入字符串
				MessageBox(NULL, szCount, TEXT("tip"), MB_OK);
				ZeroMemory(szCount, sizeof(szCount));
			}
			
		}

	}
		break;
	default:
		return DefWindowProc(hwnd, message, wParam, lParam);
		break;
	}
}
image.png

列表框被动消息响应

  1. 鼠标点击列表框项目 将点击的信息插入道编辑框
    那么列表框被操作的事件的消息是什么?
     WM_NOTIFY;这个消息通知控件的父窗口控件中已发生事件或控件需要某种信息。同时_NM_LISTVIEW结构包含有关的列表视图的通知消息的信息
typedef struct tagNM_LISTVIEW {

NMHDR hdr;

int iItem;

int iSubItem;

UINT uNewState;

UINT uOldState;

UINT uChanged;

POINT ptAction;

LPARAM lParam;

} NM_LISTVIEW;

这里面猜测一下有列表的行和列等等
通过强转# WM_NOTIFY投递的附加参数指向包含通知代码和其他信息的 NMHDR 结构的指针。 对于某些通知消息,此参数指向将 NMHDR 结构作为其第一个成员的较大结构。

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_NOTIFY:
        switch (((LPNMHDR)lParam)->code)
        {
        case CUSTOM_SELCHANGE:
            if (((LPNMHDR)lParam)->idFrom == IDC_CUSTOMLISTBOX1)
            {
                ...   // Respond to message.
                return TRUE;
            }
            break; 
        ... // More cases on WM_NOTIFY switch.
        break;
        }
    ...  // More cases on message switch.
    }
    return FALSE;
}

可以判断是哪个控件发出的消息 进而对他进行处理
代码如下

case WM_NOTIFY:
	{
		NMHDR* Pnmhdr = (NMHDR*)lParam;
		if (Pnmhdr->idFrom == IDC_LIST_1)
		{
			if (Pnmhdr->code == NM_CLICK)
			{
				NMLISTVIEW* lpnmitem = (NMLISTVIEW*)lParam;
				TCHAR Buff[128] = { 0 };
				_stprintf(Buff, TEXT("您点击了%d行!"), lpnmitem->iItem);
				SetWindowText(GetDlgItem(hwnd, IDC_EDIT_1), Buff);
				//MessageBox(NULL, Buff, TEXT("tip"), MB_OK);
			}
			else if (Pnmhdr->code == NM_RCLICK)
			{
				NMLISTVIEW* lpnmitem = (NMLISTVIEW*)lParam;
				TCHAR Buff[128] = { 0 };
				_stprintf(Buff, TEXT("您点击了%d行%d列!"), lpnmitem->iItem,lpnmitem->iSubItem);
				MessageBox(NULL, Buff, TEXT("tip"), MB_OK);
			}
		}

网站标题:CV鼻祖洋芋

原创文章,作者:locus,如若转载,请注明出处:https://blog.cvpotato.cn/forward-code/285/

本博客所发布的内容,部分为原创文章,转载注明来源,网络转载文章如有侵权请联系站长!

(0)
上一篇 6天前
下一篇 6天前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注