windows程序是使用事件驱动的程序设计模式,主要就是基于消息的
和DOS系统直接操纵硬盘不同,消息系统对于window程序来说十分重要,它是程序运行的源泉
什么是消息?
用户移动鼠标、点击鼠标、敲击键盘等等所有的操作都称为事件
当有事件发生时,windows会生成一条消息,告诉程序发生了什么事情,这个消息就是表示一种传递信息的载体
typedef struct tagMsg
{
HWND hwnd; //接受该消息的窗口句柄
UINT message; //消息常量标识符,也就是我们通常所说的消息号
WPARAM wParam; //32位消息的特定附加信息,确切含义依赖于消息值
LPARAM lParam; //32位消息的特定附加信息,确切含义依赖于消息值
DWORD time; //消息创建时的时间
POINT pt; //消息创建时的鼠标/光标在屏幕坐标系中的位置
}MSG;
MSG 结构体中各成员变量的含义如下:
- hwnd表示消息所属的窗口。用户一般是在程序的窗口下进行操作,所以一个消息一般都是与某个窗口相关联的。例如在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。
- message表示消息类型,是一个数值。在Windows中,消息是由一个数值来表示的,不同类型的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX 对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。
- 第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。
- time表示消息投递到消息队列中的时间
- pt表示消息创建时的鼠标/光标在屏幕坐标系中的位置
消息的类型
windows 消息类型可以分为以下两大类:
系统定义的消息
非用户定义的消息,基范围在【0x0000,0x03ff】之间,又可以分为三小类:
- 窗口消息:与窗口的内部运作有关,创建窗口,绘制窗口,销毁窗口
- 命令消息:一般特指WM_COMMAND消息,与处理用户请求有关,通常由控件或者菜单产生。
- 控件通知消息:特指WM_NOTIFY消息。通常指一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件(按钮、列表框、组合框、编辑框,以及化公共控件树状视图、列表视图)。
一般标志性的消息标识符:
WM_NULL---0x0000 空消息。
0x0001----0x0087 主要是窗口消息。
0x00A0----0x00A9 非客户区消息
0x0100----0x0108 键盘消息
0x0111----0x0126 菜单消息
0x0132----0x0138 颜色控制消息
0x0200----0x020A 鼠标消息
0x0211----0x0213 菜单循环消息
0x0220----0x0230 多文档消息
0x03E0----0x03E8 DDE消息
0x0400 WM_USER
0x8000 WM_APP
0x0400----0x7FFF 应用程序自定义私有消息
由于控件通知消息很重要的,编程者用的也比较多,但是具体的含义往往令初学者晕头转向,这里我CV了一些常用是消息宏定义名:
- 按扭控件
BN_CLICKED 用户单击了按钮
BN_DISABLE 按钮被禁止
BN_DOUBLECLICKED 用户双击了按钮
BN_HILITE 用/户加亮了按钮
BN_PAINT 按钮应当重画
BN_UNHILITE 加亮应当去掉
- 组合框控件
CBN_CLOSEUP 组合框的列表框被关闭
CBN_DBLCLK 用户双击了一个字符串
CBN_DROPDOWN 组合框的列表框被拉出
CBN_EDITCHANGE 用户修改了编辑框中的文本
CBN_EDITUPDATE 编辑框内的文本即将更新
CBN_ERRSPACE 组合框内存不足
CBN_KILLFOCUS 组合框失去输入焦点
CBN_SELCHANGE 在组合框中选择了一项
CBN_SELENDCANCEL 用户的选择应当被取消
CBN_SELENDOK 用户的选择是合法的
CBN_SETFOCUS 组合框获得输入焦点
- 编辑框控件
EN_CHANGE 编辑框中的文本己更新
EN_ERRSPACE 编辑框内存不足
EN_HSCROLL 用户点击了水平滚动条
EN_KILLFOCUS 编辑框正在失去输入焦点
EN_MAXTEXT 插入的内容被截断
EN_SETFOCUS 编辑框获得输入焦点
EN_UPDATE 编辑框中的文本将要更新
EN_VSCROLL 用户点击了垂直滚动条消息含义
- 列表框控件
LBN_DBLCLK 用户双击了一项
LBN_ERRSPACE 列表框内存不够
LBN_KILLFOCUS 列表框正在失去输入焦点
LBN_SELCANCEL 选择被取消
LBN_SELCHANGE 选择了另一项
LBN_SETFOCUS 列表框获得输入焦点
应用定义的消息
- WM_USER : 【0X0400-0X7FFF】, 用户自定义的消息范围。
- WM_APP : 【0X8000-0XBFFF】,用于程序之间的消息通信。
- RegisterWindoMessage :【0XC000-0XFFFF】
什么是消息队列?
在windows编程中,每一个windows应用程序开始执行后,系统都会为该程序创建一个队列,这个队列用来存放该应用程序所创建的信息。
例如,当我们按下鼠标右键的时候,这时会产生一个WM_RBUTTONDOWN消息,系统会自动将这个消息放进当前窗口所属的应用程序的消息队列中,等待应用程序的结束。Windows将产生的消息以此放进消息队列中,应用程序则通过一个消息循环不断的从该消息队列中读取消息,并做出响应(后面会详细讲述消息处理过程…)
从windows维护角度上看有两种消息队列:
- 系统消息队列,设备驱动会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程消息队列中等待处理
- 线程消息队列,每个GUI线程都会维护一个线程消息队列(这个队列只有在线程调用GDI函数时才会创建,默认不创建,这是为了避免给Non-GUI线程创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列)
从消息的发送途径上来看可以分为队列消息和非队列消息
- 队列消息:消息会先保存在消息队列中,消息循环会从此队列中取出消息并分发到各窗口处理
- 非队列消息:消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理,非队列消息也可以由应用程序调用系统调用系统函数产生
一些常用的API函数
发送消息
把一个消息发送到窗口有3种方式:发送、寄送和广播。
- 发送消息的函数:
- SendMessage
- SendMessageCallback
- SendNotifyMessage
- SendMessageTimeout- 寄送消息的函数:
- PostMessage
- PostThreadMessage
- PostQuitMessage- 广播消息的函数:
- BroadcastSystemMessage
- BroadcastSystemMessageEx
SendMessage 发送消息函数
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
函数直接把消息送到窗口过程处理,处理后才返回,如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息返回
PostMessage 寄送消息函数
bool PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam),
函数把消息放到指定线程的消息队列中后立即返回。
BroadcastSystemMessage 广播消息函数
long BroadcastSystemMessage(DWORDdwFlags,LPDWORD lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM lParam);
函数向指定的接收者发送一条消息,这些接收者可以是应用程序、可安装的驱动程序、网络驱动程序、系统级别的设备驱动消息和他们的任意组合。
接收消息
消息的接收主要有3个函数:
GetMessage PeekMessage WaitMessage。
GetMessage 获取消息函数
BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
GetMessage主要是从指定的窗口hWnd中取出wMsgFilterMin和wMsgFilterMax参数给出的消息范围内的消息,消息取出后就从消息队列中删除该消息。GetMessage从消息队列中取不到消息,则线程就会被挂起,直到取出消息来返回。
PeekMessage 查询消息函数
BOOL PeekMessage(LPMSG lpMsg,HWNDhWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);
PeekMessage只是从消息队列中查询消息,如果有消息,则立刻返回true;没有则返回false。如果有消息,PeekMessage中wRemoveMsg参数中设置的是PM_REMOVE则在取出消息并将消息从队列中删除,若设置是PM_NOREMOVE消息就不会从消息队列中取出。
WaitMessage
BOOL WaitMessage();
当一个线程的消息队列中没有消息存在时,waitMessage函数会使该线程中断并处于等待状态,同时把控制权交给其它线程,直到被中断那个线程的消息队列中有了新的消息为止。
转换消息
TranslateMessage:把一个virtual-key消息转化成字符消息,并放到当前线程的消息队列中,消息循环下一次取出处理。
TranslateAccelerator:将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN或者WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND或者WM_SYSCOMMAND消息,然后把转化后的WM_COMMAND或者WM_SYSCOMMAND消息直接发送到窗口过程处理,处理完后才会返回。返回值:若函数调用成功,则返回非零值,若函数调用失败则返回零
消息循环
GetMessage 函数用来从消息队列中获取一条消息,并保存到 MSG 结构体变量中。
注意:GetMessage 的返回值永远为非零值,while 循环会一直进行下去。如果队列中没有消息,GetMessage 函数会等待,直到有消息进入。
获取到消息后,需要调用 TranslateMessage 函数对消息进行转换(翻译),然后再调用 DispatchMessage 函数将消息传给窗口过程去处理(调用窗口过程)。
最后附上消息处理的整个步骤: