队列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。队列化消息还包含时钟消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。
非队列化消息则是其它消息。在许多情况下,非队列化消息来自呼叫特定的Windows函数。例如,当WinMain呼叫CreateWindow时,Windows将建立窗口并在处理中给窗口消息处理程序发送一个WM_CREATE消息。当WinMain呼叫ShowWindow时,Windows将给窗口消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain呼叫UpdateWindow时,Windows将给窗口消息处理程序发送WM_PAINT消息。键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。例如,用键盘或鼠标选择了一个菜单项时,键盘或鼠标消息就是队列化的,而说明菜单项已选中的WM_COMMAND消息则可能就是非队列化的。
Windows消息分类
Windows应用程序都是基于消息驱动的,消息一般分为标准Windows消息、控件通知消息和命令消息三大类。
1. 标准Windows消息
标准Windows消息,除WM_COMMAND消息外,所有以WM为前缀的消息都是标准Windows消息。标准Windows消息只能由窗口类和视图类进行处理。标准Windows消息都有黙认的处理函数,这些函数在CWnd类中过行了预定义,处理函数均以前缀On开头。
标准Windows消息主要分为三类:
(1)键盘消息
当用户按下键盘上的某一个键时,会产生WM_CHAR消息。该消息的处理函数为OnChar.
(2) 鼠标消息
WM_MOUSEMOVE WM_LBUTTONDOWN WM_RBUTTONDOWN
(3)窗口消息
所有窗口的变化,包括内容重绘、窗口最大化、窗口重新定义大小、窗口滚动条滚动等产生的消息 均属于窗口消息。当调用成员函数UpdateWindow 或RedrawWindow要求重新绘制窗口内容时,将会发送WM_PAINT消息,当窗口最小化后再还原或被其它窗口遮盖后又移开时,也会发送WM_PAINT消息。WM_PAINT消息的处理函数为OnPaint.
2. 控件消息(WM_COMMAND)
由控件产生的消息,例如按钮,列表框的选择等都会产生通告消息。控件消息是从控件传送给父窗口的消息。发送控件消息的控件在Visual C++中使用唯一ID号来进行标识,使用控件类来操纵相应的控件。与标准Windows消息一样,控件消息也在视图类、窗口类进行处理。但是,如果用户单击按钮控件,所发出的控件通知消息BN_CLICKED将作为命令消息来处理。
3.命令消息
命令消息是菜单项、工具栏按钮、加速键等用户界面对象发送的WM_COMMAND消息。命令消息可以被文档、视图、窗口、应用程序等对象处理。发送命令消息的用户界面对象在Visual C++中也使用唯一的ID号来标识。通过给界面和命令消息分配相同的ID号,可以把用户界面对象与命令联系起来。
Windows把非命令消息直接发送给窗口类对象,该窗口类中用于处理该消息的处理函数 将被调用。但是,对于命令消息,将把命令消息发送给多个候选对象(称为命令目标),目标中总有一个将调用该命令的处理函数。
注意:由于CWnd类派生于CCmdTarget类,所以凡是从CWnd派生的类,他们既可以接收标准消息,也可以接收命令消息和通告消息。而对于从CCmdTarget类派生的类只能接收命令消息和通告消息,不能接受标准消息。
*******************************************************************************************************************
Windows消息控制中心一般是三层结构,其顶端就是Windows内核。Windows内核维护着一个消息队列,第二级控制中心从这个消息队列中获取属于自己管辖的消息,后做出处理,有些消息直接处理掉,有些还要发送给下一级窗体(Window)或控件(Control)。第二级控制中心一般是各Windows应用程序的Application对象。第三级控制中心就是Windows窗体对象,每一个窗体都有一个默认的窗体过程,这个过程负责处理各种接收到的消息。如下图所示:
通知消息是针对标准Windows控件的消息。这些控个包括:按钮(Button)、组合框(ComboBox)、编辑框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具条(Toolbar)、菜单(Menu)等。每种消息以不同的字符串打头。
不是每个控件都能接收消息,转发消息和绘制自身,只有具有句柄(handle)的控件才能做到。有句柄的控件本质上都是一个窗体(window),它们可以独立存在,可以作为其它控件的容器,而没有句柄的控件,如Label,是不能独立存在的,只能作为窗口控件的子控件,它不能绘制自身,只能依靠父窗体将它绘制来。
句柄的本质是一个系统自动维护的32位的数值,在整个操作系统的任一时刻,这个数值是唯一的。但该句柄代表的窗体释放后,句柄也会被释放,这个数值又可能被其它窗体使用。也就是说,句柄的数值是动态的,它本身只是一个唯一性标识,操作系统通过句柄来识别和查找它所代表的对象。
然而,并非所有的句柄都是窗体的句柄,Windows系统中还中很多其它类型的句柄,如画布(hdc)句柄,画笔句柄,画刷句柄,应用程序句柄(hInstance)等。这种句柄是不能接收消息的。但不管是哪种句柄,都是系统中对象的唯一标识。本文只讨论窗体句柄。
那为什么句柄使窗口具有了如此独特的特性呢?实际是都是由于消息的原因。由于有了句柄,窗体能够接收消息,也就知道了该什么时候绘制自己,绘制子控件,知道了鼠标在什么时候点击了窗口的哪个部分,从而作出相应的处理。句柄就好像是一个人的身份证,有了它,你就可以从事各种社会活动;否则的话,你要么是一个社会看不到的黑户,要么跟在别人后面,通过别人来证明你的存在。
1、从消息队列获取消息:
可以通过PeekMessage或GetMessage函数从Windows消息队列中获取消息。Windows保存的消息队列是以线程(Thread)来分组的,也就是说每个线程都有自己的消息队列。
2、发送消息
发送消息到指定窗体一般通过以下两个函数完成:SendMessage和PostMessage。两个函数的区别在于:PostMessage函数只是向线程消息队列中添加消息,如果添加成功,则返回True,否则返回False,消息是否被处理,或处理的结果,就不知道了。而SendMessage则有些不同,它并不是把消息加入到队列里,而是直接翻译消息和调用消息处理,直到消息处理完成后才返回。所以,如果我们希望发送的消息立即被执行,就应该调用SendMessage。
还有一点,就是SendMessage发送的消息由于不会被加入到消息队列中,所以通过PeekMessage或GetMessage是不能获取到由SendMessage发送的消息。
另外,有些消息用PostMessage不会成功,比如wm_settext。所以不是所有的消息都能够用PostMessage的。
还有一些其它的发送消息API函数,如PostThreadMessage,SendMessageCallback,SendMessageTimeout,SendNotifyMessage等。
系统消息队列/线程消息队列
windows维护着一个系统消息队列,以及分别为每个GUI线程维护一个各自的线程消息队列。为了避免非GUI线程的创建线程消息队列的开销,所有线程创建初始化时,均不创建消息队列。只有当线程第一次调用GDI函数时,系统才会为线程创建消息队列。所以那些非GUI线程是没有消息队列的。
每当用户移动鼠标,点击按钮或键盘时,鼠标或键盘的设备驱动程序会将输入转换成消息,并将消息放在系统消息队列里。删windows会检查自己的消息队列,如果消息队列不为空,则每次取出并删除一个消息,然后确定消息的目标窗口,然后把消息放到创建这个窗口的线程的线程消息队列里。线程的消息队列接收由线程创建的窗口的所有的鼠标和键盘消息。然后线程会从队列中删除信息,并告诉系统把它们派发到对应的窗口消息处理函数。
在Win32中,每一个线程有它自己专属的消息队列。这并不意味着每一个窗口有它自己的消息队列,因为一个线程可以产生许多窗口
DispatchMessage要等到WndProc处理完后才会返回