Windows编程系列:Windows中的消息

win32控制台程序

控制台程序整个执行过程是按照代码的顺序依次执行,到main函数的结束,标志着整个程序的退出。

1 int main()
2 {
3     
4     return 0;
5 }

整个过程可以描述为以下:

Windows应用程序

Windows应用程序会响应来自用户和操作系统的事件。

来自用户的事件包括:鼠标单击,按键,触摸屏手势等。
操作系统中的事件:用户可能插入了新的硬件设备,或者Windows可能进入了低功耗状态(睡眠或休眠)等。

这些事件可以在程序运行时随时以几乎任何顺序发生,为了解决此问题,Windows使用了消息传递模型。 操作系统通过向其传递消息(https://www.cnblogs.com/zhaotianff/p/11285312.html)来与您的应用程序窗口通信。 消息只是指定特定事件的数字代码。

例如,如果用户按下鼠标左键,则窗口会收到一条包含以下消息代码的消息。

1 #define WM_LBUTTONDOWN    0x0201

消息循环

https://www.cnblogs.com/zhaotianff/p/11297319.html这篇文章中,介绍了如何创建一个Windows窗体应用程序。在步骤5中,创建了一个消息循环。

Windows窗体程序在运行后,将会收到无数的消息(鼠标移动和按下,键盘按下都会产生消息)。

一个窗体程序可能包含多个窗口,每个窗口都有自己的窗口过程(WNDPROC)。 应用程序需要一个循环来检索消息并将它们发送到对应的窗口。

对于每个创建窗口的线程,操作系统都会为窗口消息创建一个队列。 此队列保存在该线程上创建的所有窗口的消息。通过调用GetMessage函数从队列中提取消息。

1 BOOL GetMessage(
2   LPMSG lpMsg,
3   HWND  hWnd,
4   UINT  wMsgFilterMin,
5   UINT  wMsgFilterMax
6 );

取出消息后,需要对消息进行转换。这个时候就需要调用TranslateMessage()函数,该函数将虚拟消息转换为字符消息。字符消息被送到调用线程的消息队列里,当下一次线程调用函数GetMessage()时被读出。

然后再调用DispatchMessage函数将消息分发到各个窗口,DispatchMessage函数告诉操作系统调用消息目标窗口的窗口过程。 也就是说,操作系统在其窗口列表里查找窗口句柄,找到与该窗口关联的函数指针,然后调用该函数。

假设鼠标按下,将会发生如下事件:

1、操作系统将WM_LBUTTONDOWN消息放入消息队列

2、消息循环时,调用GetMessage函数取出,并将从消息队列中取出的消息将保存在MSG结构体对象中

3、程序调用TranslateMessage函数和DispatchMesage函数

4、在DispatchMessage函数内部,调用目标窗口的窗口过程

5、窗口对消息进行处理或忽略

当目标窗口的窗口过程执行完成后,将会返回DispatchMessage函数,此时,再会循环读取下一次消息进行处理。

只要程序没有退出GetMessage函数返回非0值),这个循环会一直执行下去。

 while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

结束消息循环

当需要结束消息循环时,可以执行以下函数

1 PostQuitMessage(0);

PostQuitMessage函数实际上是发送了一个WM_QUIT消息到消息队列中,这会使GetMessage()返回返回0。当GetMessage函数返回0时,整个消息循环结束。WM_QUIT消息不会被DispatchMessage函数传递到窗口过程,所以不需要对WM_QUIT消息进行处理。

PostMessage和SendMessage

PostMessage

将消息复制到指定窗口的线程关联的消息队列中,并直接返回。无需等待线程处理消息。如果未指定窗口句柄,则将消息复制到当前线程关联的消息队列中。

使用PostMessage发送消息时,需要判断函数返回值,以便知道消息是否正确发送。如果消息队列满了,PostMessage会返回失败,此时需要对消息进行重发。

SendMessage

将指定的消息发送到一个或多个窗口,直到消息被窗口的窗口过程处理完成后才返回。这种方式会要求窗口过程立即处理该消息,而不是从消息队列中去取出消息再进行处理。在进行窗口通信时,常会使用这种方式

消息和事件

消息(Message)就是用于描述某个事件所发生的信息,而事件(Event)则是用户操作而产生的动作(如鼠标按下)。事件产生消息,而消息可以来自事件,也可以来自其它,如SendMessage

事件:

消息:

系统预定义消息

系统预定义消息可以分为三类:

窗口消息(Windows Messages)

窗口消息用于窗口的内部动作,如创建窗口消息(WM_CREATE)、绘制窗口消息(WM_PAINT)、销毁窗口(WM_DESTROY)等。窗口消息可以用于一般窗口,也可以用于对话框、控件等。

常用的窗口消息如下:

消息   说明
WM_CREATE 窗口创建消息,其中wParam不用,lParam指向CREATESTRUCT结构体的指针,该结构体包含将要创建的窗口的消息
WM_PAINT 窗口绘制消息,调用函数UpdateWindow或RedrawWindow,会发送该消息。wParam和lParam参数无用
 WM_DESTROY 销毁窗口消息,wParam和lParam无用 
 WM_SETFOCUS 对于即将获取焦点的窗口,会收到WM_SETFOCUS消息,wParam参数是正在失去焦点的窗口的句柄 
 WM_KILLFOCUS 焦点消息,对于正在失去焦点的窗口,会收到WM_KILLFOCUS消息,wParam参数是即将接收输入焦点的窗口的句柄
 WM_MEASUREITEM  当具有自画风格的COMBOBOX、LISTBOX、LISTVIEW控件或MENUITEM被创建的时候,该消息会发送给这些控件或菜单的拥有者窗口,用来设置这些控件或菜单的每个项的大小。比如LISTBOX框如果具有LBS_OWNERDRAWFIXED风格,那么它被创建的时候,系统会发送WM_MEASUREITEM消息来设置每个项(每行)的高度,如果没有LBS_OWNERDRAWFIXED风格,则不会发送WM_MEASUREITEM消息。消息参数wParam的值为控件ID;lParam指向MEASUREITEMSTRUCT结构体的指针,该结构体包含自画控件或菜单项的尺寸信息
 WM_DRAWITEM  当具有自画风格的COMBOBOX、LISTBOX、LISTVIEW控件或MENUITEM的外观发生改变时(即需要重画)的时候,该消息会发送给这些控件或菜单的拥有者窗口,收到此消息之后控件才会执行重画。比如LISTBOX如果具有LBS_OWNERDRAWFIXED风格,那么它需要重画的时候,系统会发送WM_DRAWITEM消息来重画每项(每行),如果没有LBS_OWNERDRAWFIXED风格,则不会发送WM_DRAWITEM消息。消息参数wParam的值为控件ID;lParam指向DRAWITEMSTRUCT结构体的指针,该结构体为需要自绘的控件或菜单项提供了必须的信息

命令消息(Command Messages)

命令消息用于处理用户请求,如用户单击菜单项或工具栏按钮时,就会产生命令消息。命令消息的形式是WM_COMMAND,消息参数wParam的低字节LOWORD(wParam),表示菜单项或工具栏按钮。

WM_COMMAND也可以作为标准控件的控件通知消息。如果消息参数lParam为NULL,则WM_COMMAND是一个命令消息,否则WM_COMMAND是一个标准控件通知消息。lParam值就是控件句柄值。标准控件可以参考推荐阅读中的链接。

控件通知消息

控件通知就是控件消息通知其父窗口,控件通知消息分为三种

1.作为窗口消息的子集

它主要发生在:

控件窗口在创建或销毁之前,会发送WM_PARENTNOTIFY消息给它的父窗口;控件窗口绘制自身窗口的消息,比如WM_CTLCOLOR、WM_DRAWITEM、WM_MEASUREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKTOITEM、WM_COMMAND和COMPAREIITEM;由滚动条控件发送,通知其父窗口滚动的消息,如WM_VSCROLL和WM_HSCROLL。

2.WM_COMMAND形式

这种控件通知的方式是利用命令消息,向父窗口发送WM_COMMAND消息,但只有标准控件才采用这种方式,如Button、Edit和ListBox等

在WM_COMMAND中,lParam用来区分是命令消息还是控件通知消息。如果消息参数为NULL,则是命令消息,否则为控件通知消息。为消息为控件通知消息是,lParam是控件的句柄,LOWORD(wParam)表示控件的ID,HIWORD(wParam)表示控件通知码,标记控件所发生的各种事件,如Button的通知码如下:

控件 通知码 说明
Button BN_CLICKED 用户单击了按钮
BN_DISABLE 按钮被禁止
BN_DOUBLECLICKED 用户双击了按钮
BN_HILITE 用户选择了按钮
BN_UNHILITE 按钮高亮应该被移除
BN_PAINT 按钮应当重画

可以访问推荐阅读中的标准控件链接,然后找到每个控件的通知(Notifications)部分文档来查看完整的通知码。

3.WM_NOTIFY形式

上面说到标准控件发送信息给父窗口是通过WM_COMMAND,除此之外的通用控件(如TreeView、ListView)发送给父窗口的消息是WM_NOTIFY。单击或双击一个通用控件或在通用控件中选择部分文本、操作通用控件的滚动条,都会产生一个WM_NOTIFY消息。WM_NOTIFY消息参数wParam表示控件ID,lParam表示 指向结构体NMHDR的指针。NMHDR包含控件通知的内容。

说明:

WM_NOTIFY消息的出现,是为了解决WM_COMMAND消息无法承载更多信息的问题。

在以前控件不多的时候,这些控件发送信息给父窗口通过WM_COMMAND,然后两个消息参数里分别表示控件ID和通知码。对于简单的事件,这样的方式没问题,但是如果对于复杂的事件,比如控件绘制事件,它需要很多信息传给父窗口,而WM_COMMAND两个消息已经被占用了,应该怎么办?为了解决这个问题,就专门为控件绘制增加了一个新的消息WM_DRAWITEM,wParam表示控件ID,lParam为指向结构体DRAWITEMSTRUCT的指针。随着控件的再次增加,再次增加WM_XXX的消息变得不太现实,所以就出现了WM_NOTIFY消息,wParam表示控件ID,lParam分两种情况,对于一些通知码,它的值为指向结构体NMHDR的指针,对于另外 一些通知码,它的值为指向包含更多信息结构体的指针,并且这个结构体的第一个字段必须是NMHDR类型的变量。

比如:

在ListView中按下键盘,控件会发送WM_NOTIFY消息给父窗口,lParam表示指向NMLVKEYDOWN结构体的指针,而事件通知码LVN_KEYDOWN放到该结构体第一个字段NMHDR类型变量的code字段中。

自定义消息

系统保留的消息标识符的范围是:0x0000 到 0x03FF(WM_USER-1)

这些值被系统定义的消息使用,用户不能使用这些值定义自己的消息。

用户可以定义的消息范围是:0x0400 (WM_USER) 到 0x7FFF。

如下:

1 #define MSG_USERDEFINE WM_USER+1

推荐阅读:

Windows消息

https://docs.microsoft.com/en-us/windows/win32/learnwin32/window-messages

消息和消息队列 

https://docs.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues

标准控件

https://docs.microsoft.com/en-us/windows/win32/controls/window-controls

原文地址:https://www.cnblogs.com/zhaotianff/p/13852218.html