- /*
- 关于消息和消息队列
- 1:概述
- 与基于DOS的应用程序不同,Windows应用程序是消息驱动的。它不会直接调用函数以获得输入,
- 而是等等 操作系统给它们提供输入。
- 在应用程序中,系统会为程序的各种窗口传递所有的输入。每一个窗口都有一个处理函数,
- 它可以被称之为 窗口过程,只要有针对该窗口的输入,操作系统就会调用该窗口的窗口过程。
- 窗口过程处理完输入之后就会将控制权还给操作系统。
- 《插曲:窗口过程》
- 每一个窗口都有一个与其相关的窗口过程,它会处理发送或投掷到同一个类的所有窗口的消息,
- 窗口的各个方面的表现和行为都依赖于窗口过程对这些消息的处理结果。
- 每一个窗口都是一个窗口类的成员,窗口类决定着每一个窗口的默认的窗口过程。
- CallWindowProc会将消息信息传递给指定的窗口过程。
- DefWindowProc会调用默认的窗口过程来处理应用程序不处理的消息,这一点就保证了
- 很一个接收到的消息都会被处理。
- 在Windows XP中,如果一个顶级窗口持续几秒不对其接收到的消息作出反应,系统就会
- 认为这个窗口就此失去了响应的能力,这种情况下,系统就会隐藏起来这个无响应的窗口,
- 取而代之的会创建一个与其有着相同大小,位置,层次及视觉效果的镜像窗口,并允许
- 用户对其进行拖动,改变大小,甚至关闭等操作。这些操作只是几个 为数不多的被支持
- 的操作,因为实际情况下,真实的窗口是没有反应能力的。注意在调试状态下,
- 系统不会产生这样的镜像窗口。
- 2:Windows 消息
- 系统以消息的形式向窗口过程传递输入,消息由操作系统和应用程序共同产生。系统会在
- 接收到每一个输入时产生一个消息,同时如果应用程序改变的当前的系统设置,系统也会
- 产生消息。当应用程序想引导自己的窗口完成一些操作完想与其它应用程序的窗口通信时,
- 其自身也会产生消息。
- 消息由四个参数组成,其意义在其它的函数里已经说明。
- 消息通过一个指定的名称来指示这个消息的意义,当窗口过程接收到一个消息之后,它会
- 通过消息的标识来确定如何处理接收到的消息。
- 消息的参数指定的要传递的数据或其地址,消息参数的意义视消息的类型而定,如果消息
- 不打算使用参数的时候,一般 将附加信息设置为NULL。窗口过程会根据消息的标识
- 来正确的翻译消息的参数。
- 3: 息的类型
- (1)系统定义的消息
- 系统通过发送或投掷系统定义的消息与应用程序进行通信,控制应用程序的操作及为其
- 提供必要的输入或其它信息用于处理。 应用程序同样也可以发送或者投掷系统定义的消息,
- 程序常通过这种方式控制其窗口类里控件的行为。
- 每一个系统定义的消息都有一个唯一的消息标识和一个相关的用于表示其意义的
- 常量标识符,比如WM_PAINT。
- 常量标识符指定了系统定义的消息所属于的类型,标识符的前缀标明了可以处理
- 这种消息的窗口类型,如下所示:
- Prefix Message category
- ABM Application desktop toolbar
- BM Button control
- CB Combo box control
- CBEM Extended combo box control
- CDM Common dialog box
- DBT Device
- DL Drag list box
- DM Default push button control
- DTM Date and time picker control
- EM Edit control
- HDM Header control
- HKM Hot key control
- IPM IP address control
- LB List box control
- LVM List view control
- MCM Month calendar control
- PBM Progress bar
- PGM Pager control
- PSM Property sheet
- RB Rebar control
- SB Status bar window
- SBM Scroll bar control
- STM Static control
- TB Toolbar
- TBM Trackbar
- TCM Tab control
- TTM Tooltip control
- TVM Tree-view control
- UDM Up-down control
- WM General window
- 普通窗口消息WM包括很大范围的信息与需求,包括硬件的如mouse和keyboard,
- 菜单和对话框输入,窗口的创建与管理及动态数据交换DDE等。
- (2)程序定义的消息
- 应用程序可以自己创建一个消息自用或者用于与其它进程进行通信。如果用于自用的话,
- 接收他们的窗口过程必须对它们进行翻译和处理。
- 消息标识:
- 系统留用了0X0000到OXO3FF(WM_USER - 1)用于系统定义的消息,
- 应用程序不能使用这些标识定义消息进行自用。
- OXO400(WM_USER)到OX07FF范围内的标识是为应用程序提供的区间,
- 在这个范围内应用程序可以定义自己的消息。
- 如果你的程序标记为4.0版本,那么范围0X8000到OXBFFF范围内的数值
- 也允许定义为程序的自定义消息。
- 当应用程序调用函数RegisterWindowMessage想获取一个系统内唯一的消息标识时,
- 其返回的标识范围为OXC000到OXFFFF。
- 4: 消息映射
- 系统通过两种方式将消息发送到接收其的窗口过程:将消息投掷到一个先进先出的队列中,
- 消息队列是一个系统定义的内存对象,用于暂时性的存储和发送消息。
- 被发送到消息队列的消息叫作队列消息。比如鼠标键盘之类的硬件输入,定时器,
- 绘制及退出等。其它的不被投掷到消息队列而是被直接发送给窗口过程的消息称之为
- 非队列消息。
- 队列消息和非队列消息也表示了消息映射的两种方式。
- 队列消息:
- 系统可以在一个时刻显示任意多个的窗口,为了将硬件输入比如键盘消息发送到
- 合适的窗口,就需要消息队列的帮助。
- 消息队列分为两种,一种是系统消息队列,它在系统中唯一,由系统进行维护;
- 另一种就是GDI线程自己内部的程序消息队列。
- 为了避免为非GDI线程创建消息队列的负担,每个线程在创建之初都不会同时
- 创建消息队列,只有当线程第一次调用用户或GDI函数时才会创建一个应用程序消息队列。
- 当用户点击鼠标或按下键盘的时候,相关的驱动程序会将这个事件转换下系统内部的消息,
- 然后将它们放到系统消息队列中,系统会从系统消息队列中一次移走一个消息,
- 检查它们的目标窗口,然后将它们放到目标窗口所在线程的消息队列中,线程的
- 消息队列会接收其创建的所有窗口的消息。收到消息之后,线程会将消息移走并引
- 导系统将消息发送到相关的窗口过程。
- WM_PAINT,WM_TIMER,WM_QUIT消息是个例外,它们会被系统放到消息队列的最后,
- 这样可以保证窗口的输入能够在队列里先进先出。
- 只有当消息队列里没有这三种消息以外的其它消息时,这三种消息才会被取出处理。
- 另外,针对同一窗口的多个WM_PAINT消息将会被 合并为一个,窗口的多个需要重绘区域
- 也将被合并成一个较大的区域,这样的处理可以有效的减少窗口重绘的次数。
- 系统投掷消息的方式是先填充一个MSG结构体再将其复制到目标消息队列中。
- 当线程需要投掷消息时,可以使用PostMessage或PostThreadMessage。
- 应用程序可以通过GetMessage从自己的消息队列中取出一个消息,如果只想检查队列中
- 有无消息,可以使用PeekMessage加上合适的参数。
- PeekMessage会将队列中消息的信息填充到一个MSG结构中。
- 从消息队列中取出一个消息之后,程序可以通过函数DispatchMessage引导系统将
- 取出的消息发送到合适的窗口过程。DispatchMessage只会将窗口的句柄,消息标识符,
- 两个附加的参数发送给窗口过程,消息发出时间及光标位置不会被发送到窗口过程。
- 如果在处理消息的过程中程序想获得这两方面的信息,可以使用函数GetMessageTime
- 和GetMessagePos。
- 当一个线程的消息队列里没有消息的时候,它可以使用函数WaitMessage将控制权
- 交给其它线程,此函数会挂起调用其的线程,并且直到队列中有消息之后才会返回。
- 另外可以通过函数SetMessageExtraInfo将一个值关联到当前线程的消息队列,
- 然后再调用函数GetMessageExtraInfo获取与最后一个用GetMessage或PeekMessage
- 获取的消息相关联的附加值。
- 非队列消息:
- 非队列消息会避开系统消息队列和程序消息队列直接被发送到相关的窗口过程。
- 当系统想通知窗口会有一个事件影响它时,通常以非队列消息的形式将相关的事件发送过去。
- 比如,当用户激活了一个新的窗口时,系统会发给新激活的窗口一系列的消息,有WM_ACTIVE,
- WM_SETFOCUS,WM_SETCUROSR,这些消息会通知窗口它已经被激活了,键盘的输入是针对它的,
- 光标已经移动了它的边框范围内。
- 当应用程序调用特定的系统函数时,非队列消息也会有作用,比如当调用SetWindowPos
- 去移动一个窗口时,WM_WINDOWPOSCHANGED 这个消息将会被发送。
- 类似的函数还有BroadcastSystemMessage, BroadcastSystemMessageEx, SendMessage,
- SendMessageTimeout, and SendNotifyMessage.
- 5: 消息处理
- 应用程序必须要取出并处理其线程内消息队列里的消息。单线程的程序通常在WinMain函数
- 里有一个消息循环用于取出,发送消息。
- 对于多线程的程序而言,每一个已经创建了窗口的线程都会有一个消息循环。
- 消息循环:
- 简单的消息循环通常由三个函数组成:GetMessage,TranslateMessage和DispatchMessage。
- 记住当GetMessage出现错误时,它会返回-1,所以消息循环的写法要注意,
- 可以参考MSDN或上下相关小短文。
- GetMessage函数从消息队列中取出一个消息并将其复制到一个MSG结构中,
- 如果不遇到WM_QUIT消息,它将返回一个非0值,当遇到WM_QUIT消息时,
- 它会返回FALSE以结束消息循环。在单线程程序中,结束消息循环通常是关闭程序的第一步。
- 程序可以通过函数PostQuitMessage结束自己的消息循环,这种情况通常出现在程序的
- 主窗口过程响应WM_DESTROY消息的时候。
- 当指定一个窗口句柄作为GetMessage的第二个参数的时候,GetMessage只会取出与
- 指定窗口相关的消息。当然,GetMessage也可以进行消息的过滤。
- 如果一个线程要接收来自键盘的输入,那么其消息循环中必须要有函数TranslateMessage的支持。
- 当用户每按下键盘上的一个键时, 系统都会产生一个虚拟的键盘消息,
- 虚拟的键盘消息只包括了用于标识哪个键被按下的按键码,并不包括按键所代表的字符值,
- 而TranslateMessage的作用就是将虚拟按键码转换为字符消息并将其放到消息队列中,
- 在随后的反复操作中,字符消息会被取出并发送到相关的窗口过程。
- DispatchMessage会将消息发送到MSG结构中窗口句柄指定的窗口,
- 如果窗口句柄被指定为HWND_TOPMOST,那么DispatchMessage会将消息发送到系统中
- 所有顶级的窗口;如果句柄为空的话,则此函数什么也不做。
- 当程序初始化之后并至少创建了一个窗口之后,其消息循环也便开始了。一旦开始之后,
- 消息循环会持续从队列中提取消息并发送直到WM_QUIT消息被取到。
- 一个消息队列只需要一个消息循环即可,即使程序已经包括了多个窗口。
- 另外,可以通过多个方式改造消息循环,比如你可以从消息队列中取出消息但并不发送它们。
- 当投掷的消息没有指明接收窗口时,这一点就比较有用。另外,也可以让GetMessage
- 只取出指定的消息,其它的消息还留在消息队列中。当我们确实不想按照通常的办法
- 从先进先出队列中取消息时就可以这样子做。
- 当程序想使用快捷键功能时,必须能够将键盘消息转换为命令消息,为了实现这一点,
- 消息循环中必须要包括TranslateAccelerator。
- 如果一个线程包括了模态对话框,为了使对话框能够接收键盘消息,
- 消息循环中必须要包括IsDialogMessage函数。
- */
消息与消息队列一(译自MSDN的About Message and Message Queue)
最新推荐文章于 2024-09-15 23:13:57 发布