Skip to content

各种消息

在Windows中,消息分为以下三类:

image-20240827172155227

这些消息可以在窗口处理函数中接受

c++
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
        case WM_CREATE:
        case WM_DESTROY:
        case WM_PAINT:
        // ...
        return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
	WNDCLASS wc = { };
	wc.lpfnWndProc = WindowProc;

	return 0;
}

1.标准消息

类型含义
WM_NULL空消息,此消息将被接收窗口忽略
WM_CREATE应用程序创建一个窗口
WM_DESTROY一个窗口被销毁
WM_MOVE移动—个窗口
WM_SIZE改变一个窗口的大小
WM_ACTIVATE一个窗口被激活或失去激活状态
WM_SETFOCUS获得焦点后
WM_PAINT要求一个窗口重绘自己
WM_CLOSE当一个窗口或应用程序要关闭时发送一个信号
WM_QUIT用来结束消息循环,可调用PostQuitMessage()函数来产生此消息
WM_ERASEBKGND当窗口背景必须被擦除时 (例如在窗口改变大小时)
WM_MOUSEMOVE鼠标移动
WM_LBUTTONDOWN鼠标左键按下
WM_LBUTTONUP鼠标左键抬起
WM_RBUTTONDOWN鼠标右键按下
WM_RBUTTONUP鼠标右键抬起
WM_LBUTTONDBLCLK双击鼠标左键
WM_RBUTTONDBLCLK双击鼠标右键
WM_MOUSEWHEEL鼠标滚轮信息
WM_SIZING调整窗口大小时发送此消息给窗口,通过此消息应用程序可以监视或修
改窗口大小和位置
WM_MOVING移动窗口时发送此消息给窗口,通过此消息应用程序可以监视或修改窗
口大小和位置
WM_MDICREATE发送此消息给多文档的客户窗口来创建一个MDI子窗口
WM_MDIDESTROY发送此消息给多文档的客户窗口来关闭一个MDI子窗口
WM_MDIACTIVATE发送此消息给多文档的客户窗口通知客户窗口激活另一个MDI子窗口
WM_MDINEXTMDI客户窗口激活下一个或前一个窗口
WM_HOTKEY当用户按下由RegisterHotKey()注册的热键时产生此消息

为了防止用户定义的消息ID与系统的消息ID冲突,Microsoft定义了一个宏WM_USER,小于WM_USER的ID被系统使用,大于WM_USER的ID被用户使用。

c++
#define WM_USER   0x0400   // (十进制1024)

以下是消息编号的范围。

范围含义
0WM_USER-1消息保留供系统使用。
WM_USER0x7FFF用于私有窗口类的整型消息
WM_APP(0x8000)到0xBFFF消息可供应用程序使用。
0xC0000xFFFF字符串消息供应用程序使用。
大于0xFFFF由系统保留。

1.1 窗口的创建

在调用Windows的CreateWindow函数创建窗口时,一般会先发出WM_NCCREATE消息然后发出WM_CREATE消息。

image-20240828102639168

1.1.1 WM_NCCREATE

使用CreateWindow函数创建窗口时,CreateWindow先直接发送WM_NCCREATE消息给窗口过程函数

NC,即non-client区域,包括标题栏、窗口边框、最大、最小按钮、滚动条

  • 如果应用程序处理此消息,则应该返回TRUE以继续创建窗口
  • 如果应用程序返回FALSE,则CreateWindowCreateWindowEx函数将返回一个NULL句柄。不会发送WM_CREATE消息
c++
#define WM_NCCREATE 0x0081

1.1.2 WM_CREATE

发送完WM_NCCREATE后,CreateWindow函数在窗口创建成功并具在返回之前直接发送WM_CREATE消息给窗口过程函数,窗口过程函数会在创建窗口后但在窗口变为可见之前接收到此消息(可以在此时机初始化滚动条,创建子窗口、控件等等)。

c++
#define WM_CREATE 0x0001

1.1.3 CREATESTRUCT

WM_NCCREATEWM_CREATE中,wParam未使用,IParam为指向CREATESTRUCT结构的指针,其中包含有关正在创建的窗口的信息。

c++
typedef struct tagCREATESTRUCT{
    LPVOID IpCreateParams;			//创建窗口的基本参数
    HANDLE hlnstance;				//拥有将创建的窗口的模块实例句柄
    HMENU hMenu;					//新窗口的菜单句柄
    HWND hwndParent;				//新窗口的父窗口句柄
    int cy; 						//新窗口的高度
    int cx;							//新窗口的宽度
    int y;							//新窗口的左上角Y坐标
    int x;							//新窗口的左上角X坐标
    LONG style;						//新窗口的风格
    LPCSTR IpszName;				//新窗口的名称
    LPCSTR IpszClass;				//新窗口的窗口类名
    DWORD dwExStyle;				//新窗口的扩展参数
} CREATESTRUCT;

使用CREATESTRUCT指针,注意在case中初始化指针需要使用{}

c++
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_NCCREATE:{
            CREATESTRUCT *p = (CREATESTRUCT*)lParam;
            return true;
        }
        case WM_CREATE:{
            CREATESTRUCT *p = (CREATESTRUCT*)lParam;
            SetWindowText(hWnd,"黑神话悟空");   // 修改窗口标题
            return 0;
        }
        default:
            return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

1.2 窗口的关闭

当我们点击窗口右上角的关闭按钮,会发送WM_CLOSE消息,但此时程序并不会结束

需要我们使用DestoryWindos()函数销毁窗口

在调用DestoryWindos()把窗口从屏幕移除之后,会发送WM_DESTORY消息用来销毁窗口,此时此时可用于释放与窗口关联的已分配内存对象。

在处理WM_DESTORY消息时,需要调用PostQuitMessage()函数,发送WM_QUIT消息,让GetMessage()结束消息循环

发送WM_DESTORY之后,会发送WM_NCDESTORY通知窗口其非客户区域正在销毁

image-20240828111013921

c++
#define WM_CLOSE 0x0010
#define WM_DESTORY 0x0002
#define WM_NCDESTORY 0x0082

注意

1.应用程序处理WM_CLOSEWM_DESTORYWM_NCDESTORY消息都应该返回0

2.在这三个消息中,wParamlParam都未使用

在系统的默认窗口处理函数中,这三个消息逻辑大致如下

c++
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CLOSE:
            DestroyWindow(hWnd);
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        case WM_NCDESTROY:
            return 0;
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

1.3 窗口的刷新

1.3.1 WM_PAINT

在Winodws系统中,一切东西几乎都是绘制的,包括窗口、按钮、对话框、图片、以及一切文字,而WM_PAINT消息就是在绘制这些对象时系统发送的消息,它也是WIndows中最重要的消息之一。如果处理此消息,则应用程序返回零。

c++
#define WM_PAINT 0x000F

注意:在此消息中wParamIParam未使用

以下操作会触发WM_PAINT消息

  • 程序启动时,UpdateWindow()函数发送一个WM_PAINT消息来更新指定窗口的客户区。
  • 用鼠标调整窗口的大小时会连续触发
  • 最小化时不会触发WM_PAINT消息,但是从最小化还原时会进行触发
  • 最大化时会触发WM_PAINT消息
  • 当向屏幕外拖动窗口时,不会触发WM_PAINT消息,但是拉回到屏幕内时会不断的触发WM_PAINT消息
  • 使用InvalidateRect()函数触发WM_PAINT消息

备注:仅仅移动或者覆盖窗口,不会引I起WM_PAINT,因为窗口的客户区大小并未改变!

c++
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
        case WM_PAINT: {
            OutputDebugString("窗口变化\n");
            break;
        }
		return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

OutputDebugString()函数可在vs控制台打印消息

注意

上述代码WM_PAINTcase中,若吧break换成了return 0,那么这段代码就会一直执行

原因是如果当前窗口用户区的某一部分变为无效,则Windows会将一个WM_PAINT消息放到消息队列中,如果不调用BeginPaintEndPaint函数处理,(同时,也不调用ValidateRect函数),该无效区域不会变为有效,于是Windows将会发送另一个WMPAINT消息,并且会一直发送下去。默认消息处理函数DefWindowProc处理WM_PAINT消息的代码如下

c++
case WM_PAINT: {
    PAINTSTRUCT ps;
    HDC hdc=BeginPaint(hwnd, &ps);
    EndPaint(hwnd,&ps);
    return 0;
}

1.3.2 BeginPaint()

BeginPaint函数使窗口无效区域变得有效,从而使windows不再发送WM_PAINT消息(直到窗口大小改变等,是窗口再次变得无效,如果窗口一直无效,则windows会不停的发送WM_PAINT消息)。同时填充PAINTSTRUCT结构,让程序员可以根据ps变量中的标志值进行某些操作。

c++
HDC BeginPaint(
	HWND hwnd,
    LPPAINTSTRUCT lpPaint;
)

参数

  • hwnd:标识要重画的窗口句柄
  • lpPaint:指向绘图信息结构的指针

返回值

  • 如果函数成功,返回值是指定窗口的“显示设备描述表”句柄。
  • 如果函数失败,返回值是NULL,表明没有得到显示设备的内容。

1.3.3 EndPaint()

EndPaint功能在指定的窗口中标记绘画结束。每个调用BeginPaint函数都需要此函数,但只有在绘制完成之后。

c++
HDC EndPaint(
	HWND hwnd,
    const PAINTSTRUCT* Lppant;
)

参数

  • hwnd:标识已重新绘制的窗口。
  • Lppant:指向PAINTSTRUCT结构,其中包含BeginPaint检索到的绘画信息。

返回值:返回值始终为零。

1.3.4 PAINTSTRUCT

c++
typedef struct tagPAINTSTRUCT {
    HDC hdc;
    BOOL fErase;
    RECT rcPaint;
    BOOL fRestore;
    BOOL fIncUpdate;
    BYTE rgbReserved[32];
} PAINTSTRUCT, PPAINTSTRUCT*;
  • hdc:用于绘画的显示器DC的句柄
  • fErase:指定背景是否必须被清除。
    • 如果应用程序应该擦除背景,则此值不为零。
    • 如果没有背景画笔创建一个窗口类,则该应用程序负责擦除背景。请参阅WNDCLASS结构看一看hbrBackgroundlb成员的说明。
  • rcPaint:指定客户区的矩形
  • fRestore:保留,由系统内部使用
  • fIncUpdate:保留,由系统内部使用
  • rgbReserved:保留,由系统内部使用

1.3.5 WM_ERASEBKGND

当窗口的移动,窗口的大小的改变时,必须擦除窗口背景时发送WM_ERASEBKGND消息被发送以准备用于绘画的无效部分的窗口。

Windows会给窗口发出先发出一次或者几次WM_ERASEBKGND消息、紧接着是WM_PAINT消息。

当窗口无效时,DefWindowProc函数默认通过使用WNDCLASS结构中指定的hbrBackground背景画笔擦除背景。

如果hbrBackgroundNULL,则应用程序应处理WM_ERASEBKGND消息并清除背景。如果应用程序处理消息并擦除背景,应用程序应返回非零值以响应WM_ERASEBKGND;这表明不需要进一步擦除。如果应用程序返回零,窗口将保持标记为需要擦除(通常,这表示BeginPaint中获取的PAINTSTRUCT结构的fErase成员将为TRUE)。

备注

  • wParam中传送设备上下文的句柄。
  • IParam中此参数未使用。
c++
LRESULT CALLBACKMyWindowProc(HWNDhWnd,UINTMsg,WPARAMwParam,LPARAM IParam){
    switch (Msg){
            case WM_ERASEBKGND:{
                static int i=0;
                char str[64];
                sprintf(str,“第%d次接收WM_ERASEBKGND消息!\n",i++);
                OutputDebugStringA(str);
                return 1;
            }
            case WM_PAINT:{
                static int j=0;
                char str[64];
                sprint(str,“第%d次接收WM_PAINT消息!\n",j++);
                OutputDebugStringA(str);
                break;
            }
            default:
                returnDefWindowProc(hWnd,Msg,wParam,IParam);
    }
    returnDefWindowProc(hWnd,Msg,wParam,IParam);

}

1.3.6 UpdateWindow()

UpdateWindow用来更新窗口客户区

c++
BOOL WINAPI UpdateWindow(
	HWND hWnd//窗口的句柄
);

返回值:如果函数成功,返回值为非零。失败返回零。

TIP

如果窗口的更新区域不是空的,则UpdateWindow函数通过向窗口发送WMPAINT消息来更新指定窗口的客户区。该函数直接将WM_PAINT消息发送到指定窗口的窗口过程,绕过应用程序队列。如果更新区域为空,则不发送WM_PAINT消息。

1.3.7 InvalidateRect()

InvalidateRect函数添加一个矩形到指定窗口的更新区域(必须重绘的窗口客户区域部分)。

c++
BOOL InvalidateRect(
    HWND hWnd,
    const RECT* IpRect,
    BOOL bErase,
);

参数:

  • hWnd:窗口的句柄
  • IpRect:矩形区域,NULL则将整个客户区添加到更新区域。
  • bErase:指定更新区域内的背景在处理更新区域时是否要擦除。
    • 如果此参数为TRUE,则在调用BeginPaint函数时将清除背景。
    • 如果此参数为FALSE,则背景保持不变。

返回值:如果函数成功,返回值为非零。失败返回零。

TIP

无效区域在更新区域中累积,直到发生下一个WM_PAINT消息时处理该区域,或者直到使用ValidateRectValidateRgn函数验证该区域为止。只要更新区域不是空的,并且该窗口的应用程序队列中没有其他消息,系统 就会向窗口发送WM_PAINT消息。当参数bErase=TRUE时,WM_ERASEBKGND消息产生,当bErasefalseWM_ERASEBKGND消息不产生。如果更新区域的任何部分的bErase参数为TRUE,则背景将在整个区域中擦 除,而不仅仅在指定部分中擦除。

1.3.8 RedrawWindow()

RedrawWindow函数更新窗口的客户区指定的矩形或区域。

c++
BOOL RedrawWindow(
    HWND hWnd,
    const RECT* IprcUpdate,
    HRGN hrgnUpdate,
    UINT flags
);

参数

  • hWnd:窗口的句柄,如果此参数为NULL,则更新桌面窗口。
  • IprcUpdate:矩形区域
  • hrgnUpdate:更新区域的句柄。如果hrgnUpdate和lprcUpdate参数都为NULL,则将整个客户区添加到更新区域。
  • flags:一个或多个重绘标志。
    • RDW_ERASE:窗口重新绘制时导致窗口收到WM_ERASEBKGND消息。RDW_INVALIDATE标志也必须指定,否则RDW_ERASE不起作用。
    • RDW_FRAME:导致与更新区域相交的窗口的非客户区域的任何部分都收到WM_NCPAINT消息。RDW_INVALIDATE标志也必须指定;否则,RDW_FRAME不起作用。
    • RDW_INTERNALPAINT:管窗口的任何部分是否无效,都会导致WM_PAINT消息被发布到窗口。
    • RDW_INVALIDATE:使lprcUpdate或hrgnUpdate无效(只有一个可能为非NULL)。如果两者均为NULL,则整个窗口将失效。

UpdateWindow、InvalidateRect、RedrawWindow区别

类型区别
UpdateWindow如果窗口的更新区域不是空的,则UpdateWindow函数立即向窗口发送WMPAINT消息来更新指定窗口的客户区。
InvalidateRect只增加一个无效区域在更新区域中,并不立刻进行重绘,直到发生下一个WM_PAINT消息时处理该区域是才重绘。如果希望立即刷新无效区域,可以在调用InvalidateRect之后调用UpdateWindow。
RedrawWindowRedrawWindowO则是具有lnvalidateO和UpdateWindowO的双特性。声明窗口的状态为无效,并立即更新窗口,立即调用WMPAINT消息处理。

1.3.9 WM_NCPAINT

当窗口客户区以外的部分(如窗口标题栏、菜单栏等)需要需要重画时,系统向程序发出WM_NCPAINT消息。因标准窗口的客户区以外部分为窗口必需部分,因而该消息将默认被发送到DefWindowProc函数进行默认处理。我们可通过截获该消息来实现窗口框架自定义绘制。

处理WM_NCPAINT消息时:wParam为窗口更新区域的句柄。更新区域被剪裁到窗口框架。IParam参数未使用

1.4 鼠标消息

消息类型说明
WM_MOUSEMOVE鼠标移动
WM_LBUTTONDOWN鼠标左键按下
WM_LBUTTONUP鼠标左键抬起
WM_RBUTTONDOWN鼠标右键按下
WM_RBUTTONUP鼠标右键抬起
WM_LBUTTONDBLCLK双击鼠标左键
WM_RBUTTONDBLCLK双击鼠标右键
WM_MOUSEWHEEL鼠标滚轮信息

鼠标消息携带的参数:

  • WMPARAM—-其他按键的状态,例如Ctrl/Shift等

  • LPARAM鼠标的位置,窗口客户区的坐标系。

    • LOWORD(LPARAM):X坐标位置

    • HIWORD(LPARAM):Y坐标位置

注意:HIWORD宏一般用来取得LPARAM的高位(高16位),LOWORD返回值是指定值的低16位

c++
#define HIWORD(I)    ((WORD)(((DWORD)(I)>>16)&0xFFFF))
#define LOWORD(I)    ((WORD)(((DWORD_PTR)(I))&OxFFFF))
c++
#include <windows.h>
#include <iostream>
#include <string>
using namespace std;
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_LBUTTONDOWN:
	{
		int x = LOWORD(lParam);
		int y = HIWORD(lParam);
		string str="鼠标左键按下"+to_string(x) +","+ to_string(y)+"\n";
		OutputDebugString(str.c_str());
		break;
	}
	case WM_RBUTTONDOWN:
	{
		int x = LOWORD(lParam);
		int y = HIWORD(lParam);
		string str="鼠标右键按下"+to_string(x) +","+ to_string(y)+"\n";
		OutputDebugString(str.c_str());
		break;
	}	
	case WM_LBUTTONDBLCLK:
	{
		int x = LOWORD(lParam);
		int y = HIWORD(lParam);
		string str="鼠标左键双击"+to_string(x) +","+ to_string(y)+"\n";
		OutputDebugString(str.c_str());
		break;
	}
	case WM_MOUSEMOVE:
	{
		int x = LOWORD(lParam);
		int y = HIWORD(lParam);
		string str="鼠标移动:"+to_string(x) +","+ to_string(y)+"\n";
		OutputDebugString(str.c_str());
		break;
	}
	return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

注意

想要窗口响应左键双击消息,需要在注册窗口时给style添加CS_DBLCLKS

c++
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
	WNDCLASS wc = { };
	wc.style = CS_DBLCLKS;
    // ...
	RegisterClass(&wc);
}

1.5 键盘信息

系统按键消息,与ALT键组合的组合键

消息类型说明
WM_SYSKEYDOWN按键按下
WM_SYSKEYUP按键弹起

非系统按键消息

消息类型说明
WM_KEYDOWN按键按下
WM_KEYUP按键弹起

注意

  • 除Print键之外都有“按下”消息。
  • 所有键都存在“弹起”消息。
  • 根据MSDN说明,只有下面这些键才会产生字符消息:
    • 任何字符键
    • 回退键(BACKSPACE)
    • 回车键建(carriage return)
    • ESC
    • SHIFT + ENTER(linefeed换行)
    • TAB

键盘驱动程序为每个实体键对应都有一个统一的虚拟键,Windows定义的虚拟键都定义在WinUser.h这个头文件里面,都是以VK_作为前缀,比如回车键的虚拟键是VK_RETURN

键位名称描述
VK_0 到 VK_9表示键盘上数字“0”到“9”键(ASCII码为0x30 - 0x39)
VK_A 到 VK_Z表示键盘上字母“A”到“Z”键(ASCII码为0x41 - 0x5a)
VK_F1 到 VK_F12表示键盘上“F1”到“F12”键
VK_ENTER表示键盘上“ENTER”键
VK_SHIFT表示键盘上“SHIFT”键
VK_SPACE表示键盘上“SPACEBAR”键
VK_CONTROL表示键盘上“CTRL”键
VK_SUBTRACT表示键盘上“-”键
VK_TAB表示键盘上“TAB”键
VK_ALT表示键盘上“ALT”键
VK_BACK_SPACE表示键盘上“BACKSPACE”键
VK_CAPS_LOCK表示键盘上“CAPS LOCK”键
VK_CLOSE_BRACKET表示“]”键
VK_COMMA表示“,”键
VK_DECIMAL表示数字键盘上的“.”键
VK_DELETE表示键盘上“DELETE”键
VK_DIVIDE表示“/”键
VK_DOWN表示键盘上“向下箭头”键
VK_END表示键盘上“END”键
VK_ADD表示数字键盘的“+”键
VK_EQUALS表示键盘上“=”键
VK_ESCAPE表示键盘上“ESC”键
VK_UP上键
VK_DOWN下键
VK_LEFT左键
VK_RIGHT右键

根据不同按键处理不同逻辑

c++
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_KEYDOWN:
  		UINT vkCode = wParam;
		switch (vkCode) {
            case VK_UP:
                OutputDebugString("按下了上箭头");
                break;
            case 'A':
                OutputDebugString("按下了A");
                break;
		}
	return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

2.命令消息

以WM_COMMAND表示,来自菜单、加速键或工具栏按钮的消息。

c++
#define WM_COMMAND   0x0111    // (十进制273)

备注: LOWORD(wParam)是菜单或加速键的ID,菜单的分隔条的ID为0。对于系统菜单的菜单项和加速键,点击后,Windows会向所属的窗体发送WM_SYSCOMMAND,而不是WM_COMMAND消息。

3.通告消息

WM_COMMANDWM_NOTIFY形式呈现的。表示,由各种控件产生的消息,例如,按钮的单击等,为的是向其父窗口(通常是对话框)通知事件的发生。

备注:

LOWORD(wParam)是控件ID,HIWORD(wParam)Notification Code,比如BN_CLICKED,BN_DBLCLK等,标志用户对控件的操作,双击,单击之类。

Notification Code根据不同控件的命名规律:

  • Button按钮:BN_XXX
  • ListBox列表框:LBN_XXX
  • ComboBox组合框:CBN_XXX
  • Tab框:TBN_XXX
  • Edit编辑框:EN_XXX
  • Static静态文本:STN_XXX

3.1 WM_COMMAND

当我们按下一个菜单选项,或者一个控件需要通知父窗口一个事件发生(如鼠标单击、双击等),或者快捷键被按下时,Windows将会发送一个WM_COMMAND消息给父窗口。

  • 并通过wParam高位置1或0区分菜单、快捷键、或者控件事件Code,
  • 通过wParam低位可知道菜单ID或控件ID,通过IParam知道控件句柄。
WM_COMMAND消息来源WPARAM高位WPARAM低位LPARAM
菜单0菜单ID0
快捷键1快捷键对应菜单ID0
控件响应Code(如BN_CLICKED)控件ID控件句柄

3.2 WM_NOTIFY

WM_COMMAND可携带的信息较少,WM_NOTIFYWM_COMMAND功能更强大,可以存储一些额外的信息

  • Edit,Button,ListBox等发送WM_COMMAND消息
  • 而ListView,Toolbar,Tree等控件发送WM_NOTIFY消息,因为需要提供的信息更多。
消息类型WPARAMLPARAM
WM_NOTIFY发生WM_NOTIFY消息的控件IDNMHDR指针

我们将所有附加信息都存放在NMHDR(Notify Message Handler)的一个公共结构体中,该结构体指针通过LPARAM通知到父窗口。NMHDR如下:

c++
typedef struct tagNMHDR
{
    HWND hwndFrom;			// 控件句柄
    UINT_PTR idFrom;		// 控件ID
    UINT code;				// NM_code
} NMHDR;

每个控件根据其自身特点来定义结构体,但要求第一个字段一定是NMHDR。如果我们需要知道ListView选中的行和列,那么可以这么定义:

c++
typedef struct tagNMLISTVIEW
{
    NMHDR hdr;			// NMHDR.
    int iltem;			// 行号.
    int iSubltem;		// 列号.
    UINT uNewState;		
    UINT uOldState;		
    UINT uChanged;
    POINT ptAction;
    LPARAM IParam;
}NMLISTVIEW,*LPNMLISTVIEW;

一些Notification Code对所有新的Windows控件来说是通用的:

Notification codeSentbecause
NM_CLICK用户在控件内单击鼠标左键
NM_DBLCLK用户在控件内双击鼠标左键
NM_RCLICK用户在控件内单击鼠标右键
NM_RDBLCLK用户在控件内双击鼠标右键
NM_RETURN用户在控件具有输入焦点的时候按下回车
NM_SETFOCUS控件获得输入焦点
NM_KILLFOCUS控件失去输入焦点
NM_OUTOFMEMORY控件因为没有足够的可用内存而不能完成一项操作

MIT Licensed