探索Windows:创建自己的窗口和探索消息机制
让我们与星光探索者一起,探索Windows吧!
创建自己的窗口
前两期的探索内容并没有创建属于自己的窗口,那么我们应该如何创建属于自己的窗口呢?
一提到窗口,你很可能会想到下面的画面
没错,这就是一个窗口!实际上,这个应用程序的所有按钮,文本编辑框,菜单都是窗口。有的时候,我们在使用办公软件(如WPS,微软办公套件等),或浏览器时,我们通过点击一下鼠标右键,就弹出了一个菜单,俗称右键菜单。这个右键菜单也可以是窗口,当右键菜单没显示的时候,我们按了一下右键,他就显示了。这样看来,窗口既可以是显示的,也可以是不显示的。
因而,我们可以认为窗口是:
- 占据屏幕的某些部分。
- 在给定时刻可能看不到或可能不可见。
- 了解如何绘制自身。
- 响应用户或操作系统的事件。
说了这么多,让我们一起探索如何创造自己的窗口吧!
一个完整的窗口程序需要这些步骤:注册窗口类、创建窗口、处理消息。话不多说,看代码:
1 |
|
编译此段代码,可以看到下面的结果:
成功创建出窗口了!
过程解析
首先是WNDCLASS结构体,示例代码已经大部分标的很清楚了。官网上介绍的比星光探索者探索到的还要详细,点击查看官网介绍WNDCLASS窗口类结构体!
在示例代码中,调用了LoadIcon,LoadCursor来加载图标资源,原型如下:
1 | HICON LoadIcon( |
Windows的数据类型,如果以H
开头,大多都是…的句柄的意思。
接下来是RegisterWindow函数,CreateWindow函数,这些都是宏定义,都是根据程序使用的字符集来选择调用哪个版本的。例如CreateWindow如果是多字节字符集,将调用CreateWindowA版本,如果是Unicode字符集,将调用CreateWindowW版本,很多皆是如此。
ShowWindow 函数和UpdateWindow 函数参数都很简单。
(重点内容) 窗口过程函数,是处理窗口消息的函数。Windows是基于消息系统的,当鼠标点击窗口或键盘输入时,将发送相应消息给窗口。因而窗口过程函数即为重要。窗口过程函数写法如下:
1 | // WinProc是窗口处理函数的名称,可以随便改 |
此函数的返回值为窗口消息的处理结果,int类型可以转换成HRESULT类型,大多数情况下返回0表示正常处理。
如果不处理消息,可交给默认的过程函数DefWinProc处理。但是如果不正确处理相关消息,可能会导致窗口关不了,窗口没了进程还在的情况。
Windows消息机制
Windows是以消息为基础的操作系统。消息(MSG类型)组成如下:
窗口句柄
消息ID
消息的两个附带信息
消息产生时间
消息产生时的鼠标位置
当系统通知窗口工作时,采用消息的方式派发给窗口。(本段落以下内容引用自微软官网)如果顶级窗口停止响应消息超过几秒钟,系统将认为窗口不会响应。 在这种情况下,系统会隐藏窗口,并将其替换为具有相同 Z 顺序、位置、大小和视觉属性的幽灵窗口。 这样,用户就可以移动它、调整大小,甚至关闭应用程序。 但是,这些操作是唯一可用的操作,因为应用程序实际上没有响应。 在调试器模式下,系统不会生成虚影窗口。
消息队列,顾名思义就是储存消息的队列,队列具有先进先出(First in first out, FIFO) 的特性。操作系统本身就维护有消息队列,每个应用程序也会有他自己的消息队列。
但是,并不是所有的消息都会进消息队列,有的消息是直接让窗口处理不进消息队列的。因此,根据消息进不进消息队列,我们可将消息分为队列消息和非队列消息。
队列消息:消息发送时,首先进入队列,然后通过消息循环,从队列当中获取。常见的队列消息有WM_PAINT,键盘消息,鼠标消息,定时器消息等
非队列消息:消息发送时,直接找到消息接收窗口的窗口过程函数,直接调用此函数完成消息。常见的非队列消息有WM_CREATE,WM_SIZE等
上面说的都是常见的队列消息和非队列消息,只要你想让他进队列,他就是队列消息,否则是非队列消息。
抓取消息的API
GetMessage函数。 上面的消息循环就是用的这个抓取队列中的消息。如果这个函数的最后两个参数都为0,什么消息都抓取。一般不返回非0值。当遇到WM_QUIT消息(使应用程序退出的消息)时,GetMessage将返回FALSE。GetMessage函数是阻塞函数,如果没有消息会一直等待。
GetMessage函数在工作时,会按顺序执行下列步骤:
在程序(线程)消息队列中查找消息,如果队列有消息,检查是否满足抓取消息的范围,满足就抓取
如果程序(线程)消息队列没有消息,向系统队列获取属于本程序的消息,如果有就转发到程序的消息队列。
如果还是没有消息,检查当前进程的所有窗口有没有需要重绘的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,然后处理
如果没有重绘区域,检查计时器有没有到时的计时器,如果有将产生WM_TIMER消息,然后处理
如果没有计时器,就整理程序资源,内存等
如果执行到了这一步,GetMessage就会一直等候,直到消息有为止
1 | BOOL GetMessage( |
PeekMessage函数用于以查看的方式查看队列有没有消息,如果没有消息返回0,否则返回非0值。这个函数的前四个参数与GetMessage函数意义完全相同,第五个参数的意义为如果有消息,如果值为PM_NOREMOVE,将不从消息队列中移除这个消息,如果值为PM_REMOVE,将从消息队列中移除这个消息。
1 | BOOL PeekMessage( |
发送消息的API
SendMessage 函数 用于向指定窗口句柄的窗口发送消息,这种方式发送的消息是不进消息队列的。此函数会一直等待消息处理结果,其返回值即为目标窗口的处理结果
1 | LRESULT SendMessage( |
PostMessage函数用于向指定窗口句柄的窗口发送消息,这种方式发送的消息进消息队列。此函数发送完马上返回,不等待消息处理结束。
1 | BOOL PostMessage( |
本期到此结束,下期看星光探索者处理常用的消息及自定义消息