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