首页 > 代码库 > Windows 消息机制详解
Windows 消息机制详解
总的来说:
MSG包括:
窗口句柄,指示MSG发送的目的窗口
消息标识
lPARAM、wParam
发送时间
发送时的鼠标位置
关于消息队列:
Windows系统有一个系统消息队列
每个线程都有一个自己的消 息队列(由于发送消息MSG需 要提供一个窗口HWnd,而基 本有窗口的线程,都是UI线 程),因此基本上如果线程使用了GDI函数,则windows给该线程分配一个线程消息队列,这个消息队列负责该线程的所有窗口的消息。
所有的窗口都有自己的句柄(HWND),消息被发送时,这个句柄就已经被指定了。所以 当子窗口收到一个消息时,其父窗口不会也收到这个消息,除非子窗口手 动的转发。
消息分为:
实际上MSDN把消息分为队列型(Queued Message)和非队列型(Non-queued Message),这只是不同的路由方式, 但最终都会由消息处理函数来处理。
队 列型消息包括硬件的输入(WM_KEY*等)、WM_TIMER消 息、WM_PAINT消息等;非队列型的一些例子有WM_SETFOCUS, WM_ACTIVE, WM_SETCURSOR等,它们被直接发送给处理函数。
其实,按照MSDN的说法和消息的路由过程可以理 解为,Posted Message Queue里的消息是真正的队列型消 息,而通过SendMessage()发送到消息,即使它进入了Sent Message Queue,由于SendMessage要求的同步处理,这些消息也 应该算非队列型消息。也许,Windows系统会特殊处理,使消息强行绕 过队列。
=====================
一节详细描述消息和消息队列以及如何在 你程序中使用他们。
关于消息和消息 队列
与传统的应用程序不 同,Microsoft Windows应用程序并不显式地用一个函数的调用(如c运行库)来获取输入,而是,等待windows系统把输入传给它们。
windows系统把应用程序 的所有输入传给应用程序的窗口,每个窗口都有一个称之为窗口过程的函数.当窗口有输入时windows系统要调用它,窗口过程处理输入并把控制返回 windows系统。有关窗口过程,参见 “窗口过程”。 这一章讲述消息及消息队列,并说明在应用程序中如何使用它们。
消息
windows系统以消息的形式把输入传给窗口过程,消息是由windows系 统或应用程序产生的.windows系统对每一个输入事件都要产生消息,例如,用户按键盘、移动鼠标或单击一个滚动条控制框。windows系统为了响应 应用程序给系统带来的变化也会产生消息,比如应用程序改变了系统字体资源池或是改变了一个窗门的大小。应用程序可通过产生消息指导它自己的窗口来完成某个 任务,或是与其它应用程序的窗口进行通信。
windows 系统把消息发送给窗口过程.窗口过程有四个参数:窗口句柄、消息标识以及两个叫做消息参数的32位值。窗口句柄决定消息将发送到哪—个窗 口,windows系统则用它来确定向哪一个窗口过程发送消息。
消息标识是一个命名的常量,由它来标明消息的目的。如果窗口过程接收到一条消息,它就通过消息标识来决定如何处理这条 消息。例如,消息标识WM_PAINT 通知窗口过程,窗口的客户区被改变了,需要重画。
消息参数指定窗口过程在处理消息时所用的数据或数据的位 置,消息的意图及数值取决了消息本身。消息参数可以是一个整数、紧缩的位标志、一个含有附加数据结构的指针等等。如果消息不使用消息参数,一般就都设置成NULL 、 窗口过程必须检查消息标识以确定如何解释消息参数。
消息路由
windows系统用两种方式向窗口过程发送消息:把消息投递到一个先进先出的消息队列中,它是一个系统定义的内存块用于临时存储消息;或是把消 息直接发给窗口过程。
投递到 消息队列中的消息叫排队消息,它们主要是用户通过鼠标或键盘的输入结果.如WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR 消息。其它的排队消息包括定时器、绘制和退出消息:WM_TIMER, WM_PAINT, and WM_QUIT 。所有直接发送到窗口过程的其它消息称之为非排队消息。
排队消息
windows系统在同一时间可显示多个窗口,要发送鼠标和键盘输入到相应 的窗口,windows系统要用到消息队列,它要管理一个系统消息队列和任意数目线程消息队列,每一个队列对应于一个线程。
不管什么时候,只要用户移动鼠标或是敲键盘.鼠标或键盘的 设备驱动器都要把输入转换成消息,并把它们放到系统消息队列中去。windows从系统队列中每次移走一条消息,确定目的窗口,再把它们投递到创建目的窗 口的线程的消息队列中,线程消息队列接收所有由该线程创建的窗口的鼠标和键盘消息。线程从它的队列中移走消息并指导windows系统将它们发送到相应的 窗口过程进行处理。有关线程,参见 “进程和线程”。
WM_PAINT 消息有点特别,windows系统总是把这条消息放在消息队列的最后,这样 可保证窗口按先进先出次序接收它的输入消息,WM_PAINT 消息被保持在队列中,只有在队列中没有其它消息时才发送 到窗口过程。同一个窗口的多个WM_PAINT 消息被合并成一个WM_PAINT 消 息,把客户区所有无效部分合并成一个区域.合并WM_PAINT 消息节约了窗口必须重画客户区内容的时间。
系统向线程消息队列投递消息是通过填充 一个MSG 结构,再把它复制到消息队列中,MSG 结构中的信息包括接收消息的窗口 句柄、消息标识、两个消息参数、消息时间以及鼠标的位置,线程可把消息投递到它自己的消息队列中或是通过函数 PostMessage 和PostThreadMessage 把 消息投递到其它线程的队列中去。
应 用程序可通过函数GetMessage 从它的队列中移走一条消息,应用程序还可用函数PeekMessage 来 检查队列中的某个消息但并不移走它,这个函数用有关这条消息的信息填充MSG 结构。
把一条消息从它的队列中移走后.应用程序可用函数DispatchMessage 指 导windows系统把这条消息发送到窗口过程进行处理。DispatchMessage 利用前面调用函数GetMessage 或PeekMessage 时 填充的MSG 结构的指针,把窗口句柄、消息标识及两个消息参数传给窗口过程,但它并不传送时间或鼠标光标的位置.应用 程序可以在处理一条消息时,通过调用函数GetMessageTime 和GetMessagePos 来 获取这些信息。
一个线程可以 用函数WaitMessage 当他没有其他消息在其队列里时,产生对其他线程的控制。此函数将终止线程,直到一个新消 息被放入该线程的消息队列里,然后返回。
你 可以调用函数SetMessageExtraInfo 来设置当前线程消息队列的附加信息。是和当前线程的消息队列联系 的32位值。用户可以用函数GetMessageExtraInfo 来获得附加信息,该信息将会保留到下次调用函数GetMessage 或PeekMessage 之 前。
非排队消息
非 排队消息是直接发送到目标窗口过程的,而不通过系统消息队列和线程消息队列。windows系统一般通过发送非排队消息把影响某窗口的事件通知窗口。例 如,如果用户激活一个新的应用程序窗口.windows系统就会向该窗口发送一系列的消息,包括:WM_ACTIVATE ,WM_SETFOCUS 和WM_SETCURSOR ,这些消息分别通知窗口: 它被激活了;将通过这个窗口进行键盘输入;鼠标已移到这个窗口边 框的里面了 。非排队消息也有可能发生在 应用程序调用一个windows系统函数时,例如,在应用程序用函数SetWindowPos 来移动一个窗口之 后,windows系统发送一条WM_WINDOWPOSCHANGED 消息。
应用程序是调用函数SendMessage 、SendNotifyMessage 或SendDlgItemMessage 发 送消息的。
消息处理
应用程序必须删除和处理投递到它的线 程消息队列中的消息,单一线程的应用程序一般是在它的WinMain函数中使用一个消息环来删除消息,并把消息发送到相应的窗口过程进行处理。具有多重线 程的应用程序在创建窗口的每一个线程中使用一个消息环,下一节将讲述消息环是如何工作的,另外还解释了窗口过程的一般规则。
消息环
一个简单的消息环含有一个对下列函数的调 用:GetMessage, TranslateMessage和DispatchMessage。函数GetMessage从队列中检取一条消息并把它复制到一个MSG结构 中.GetMessage应返回TRUE,但如果它得到的是WM_QUIT消息,它就返回FALSE并结束循环。在单一线程的应用程序中,结束消息循环通 常是关闭应用程序的第一步。一般在应用程序主窗口的窗口过程中响应WM_DESTROY消息时,应用程序通过函数PostQuitMessage关闭它自 己的消息环。
如果在 GetMessage中指定窗口句柄,那么从队列中检取的只是指定窗口的消息。GetMessage 也能过滤队列中的消息,这种情况下检取的只是指定范围内的消息。有关过滤消息,参见 “消息过滤”。
如果某个线程想接收键盘的字符输入,那么线程消息环中必须含有 TranslateMessage。Windows系统在用户每按一次键时会产生一个虚键消息(WM_KEYDOWN和WM_KEYUP),虚键消息含有 一个标识哪一个键被按过的虚键码,但不是它的字符值,要得到这个值,消息环中必须含有TranslateMessage,由它来把虚键消息翻译成字符消息 (WM_CHAR),再把它放回到应用程序的消息队列中去.这样字符消息才能在消息环的下一轮循环中被发送到窗口过程。
函数DispatchMessage把消息发送到与MSG结构 中指定的窗口句柄相应的窗口过程,如果窗口句柄是HWND_TOPMOST ,DispatchMessage就把消息发送到系统中所有顶层窗口的窗口过程。如果窗口句柄是NULL,对于这条消息DispatchMessage则 什么也不做。
应用程序的主线 程在初始化应用程序并且至少创建了一个窗口之后就开始了消息循环,一旦开始,消息环就连续不断地从线程的消息队列中校取消息并把它们分发到相应的窗口,函 数GetMessage从消息队列中检取到WM_QUIT消息时,消息环就结束了。
一个消息队列只需要有一个消息环,而不管应用程序有多少个窗口,因为队列中的每一条消息是一个 MSG结构,其中含有接收消息的窗口句柄,DispatchMessage总能把消息发送到相应的窗口。
应用程序可以有多种方法修改它的消息环,例如,它可以从队列中检取消息但并不 发送到任何窗口,这对那些投递不指定窗口的消息的应用程序是很有用的,(这些消息是提供给应用程序的,而不是某个窗口,因为它们含有NULL窗口句柄)。 应用程序也能指导GetMessage来搜索队列中一个特定的消息,而不管其它消息,这对那些有时不按消息队列先进先出次序检取消息的应用程序来说是很有 用的。
使用键盘加速键的应用 程序必须能够把键盘消息转换成命令消息,要这样做,应用程序的消息环必须调用函数TranslateAccelerator有关加速键,参见 “键盘加速键”。
窗口过程
窗口过程是一个函数,用来接收和处理 所有发送到该窗口的消息,每个窗口类都有一个窗口过程,同一窗口类所创建的窗口共用同一个窗口过程来响应消息。
系统通过把消息数据作为过程的参数来向窗口过程发送消息,再由窗口过程 完成与消息相应的活动。它需要检查消息的标识,在处理消息时要使用由消息参数指定的这个信息。
窗口过程一般不会忽略—条消息,如果它不处理某条消息,它就必须把这条消息传回系统进行 默认处理,窗口过程是调用函数DefWindowProc 来完成的,由它完成一个默认的操作并返回消息结果。绝大多数 窗口过程只处理几种类型的消息,其它的则通过调用DefWindowProc 传给了系统。
因为窗口过程是由所有属于同类窗口共享的,所以它能处理 几个不同窗口的消息,要识别受消息影响的某个窗口,窗口过程可以检查消息所带的窗口句柄。有关窗口过程,参见 “窗口过程”。
传递和发送消息
任何应用程序都能投递和发送消息,就跟系统一样,应用程序投递一 条消息是通过把它复制到消息队列,发送消息则是通过把消息数据作为窗门过程的参数。要投递消息,应用程 序需 要用到函数PostMessage ,要发送消息,程序使用函数SendMessage, BroadcastSystemMessage, SendMessageCallback, SendMessageTimeout, SendNotifyMessage 或SendDlgItemMessage 。
应用程序通常投递—条消息来通知某个窗口去完成 一个任务。PostMessage 为消息创建一个MSG 结构并把消息拷到消息队列 中,最后由应用程序的消息环检取这条消息再把它发送到相应的窗口过程。
应用程序一般是通过发送一条消息通知窗口过程立即完成某项任务,函数SendMessage 把 消息发送到与给定窗口相应的窗口过程,这个函数要等待窗口过程完成处理井返回消息的结果。父窗口与子窗口之间也是通过发送消息来进行相互间的通信,例如, 某个父窗口有一个编辑控制框作为它的子窗口,就可通过向它发送消息设置控制框的正文,这个控制框则通过向父窗口发送消息来把用户对正文的改变通知其父窗 口。
函数SendMessageCallback 也 能发送消息到指定窗口的窗口过程里,但是,这函数是立即返回,当窗口过程函数处理完消息后,系统调用指定的回调函数,有关更多回调函数信息,参考函数SendAsyncProc 。
有时,应用程序也有可能要求 向系统中的所有顶层窗口发送或投递一条消息,例如,如果应用程序改变了系统的时间,它必须通过发送WM_TIMECHANGE 消 息来通知所有顶层窗口,应用程序向所有顶层窗口发送或投递一条消息是调用函数SendMessage 或PostMessage , 并在hwnd 参数中指定HWND_TOPMOST 。你同样通过函数BroadcastSystemMessage 指 定参数lpdwRecipients 的值为BSM_APPLICATIONS 来 广 播消息给所有程序
应用程序能 够投递一条消息而不指定窗口,在调用PostMessage 时应用程序提供NULL 窗 口句柄,这条消息就被投递到与当前线程相应的队列中。因为没有指定窗口句柄,应用程序就必须在处理消息环中的这条消息,这也是一种创建消息的方法.此类消 息适用于整个应用程序,而不只是指某个窗口。
使用函数InSendMessage 窗口过程能够确定它所处理的消息是从另一个线程发来的,这种能力 在需要根据消息源进行消息处理时是很有用的。
经常出现的一个编程错误是假设函数PostMessage 总能成功地投递一条消息,这在消息队列是满 的时候是不对的,应用程序应该检查函数PostMessage 的返回值以确认消息是否已经被投递,否则要重新投递这条 消息。
消息种类
这部分将两种类型windows消 息;系统定义的消息,程序定义的消息
系 统消息
系统使用系统定义的 消息来控制应用程序的操作,并给应用程序提供输入或其他信息进行处理。系统在与应用程序进行通信是时是发送系统消息的。应用程序也能发送或投递系统消息, 应用程序通常用这些消息来控制预注册类创建的控制窗口的操作。
每条系统消息都有一个唯一的消息标识,对应于一个符号常量(在Windows系统头文件中定义),它表明了消息的目的, 例如,常量WM_PAINT 要求窗口绘制它的内容。
符号常量指定了系统消息所属的类别,常量的前缀标识能够解释和处理消息的亩口的类型。下表列出 了前缀及相应的消息类别:
前 缀 消息类
ABM Application desktop toolbar
BM Button control
CB Combo box control
CDM Common dialog box
DBT Device
DL Drag list box
DM Default push button control
EM Edit control
HDM Header control
LB List box control
LVM List view control
PBM Progress bar
PSM Property sheet
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
通 用窗口消息覆盖了—个较大范围的信息和请求,包括鼠标和键盘输入消息、菜单和对话框输入消息、窗口创建和管理消息及动态数据交换消息(DDL)。
应用程序定义消息
应用程 序可创建用在它自己的窗口中的消息,或是与其它进程中的窗口进行通信的消息。如果应用程序创建了它自己的消息,接收它们的窗口过程必须能够对消息进行翻 译,并提供相应的处理。 ’
windows 系统保留用于系统定义的消息的标识值的范围从0x0000到0x03FF(等于WM_USER —1)和0x8000到 0xBFFF应用程序不能把这些值用于私有消息。
从0x0400(WM_USER 的值)到0x7FFF之间的值是可用于应用程序定义的用于它自己 的消息标识,而从0xC000到0xFFFF之间的值是应用程序为了与其它应用程序中的窗口进行通信所定义的消息标识。
应用程序用函数RegisterWindowMessage 注 册一条消息时,windows系统返回的消息标识在0xC000到0xFFFF之间,这个函数所返回的消息标识应保证在整个系统中是唯一的。如果应用程序 要创建与其它应用程序中的窗口进行通信的消息,则使用RegisterWindowMessage 来对它进行注册,这 个函数可防止由于其它的应用程序基于不同的目的使用了相同的消息标识所产生的冲突。
消息过滤
应用程序可使用函数GetMessage 或PeekMessage 来 指定一个消息过滤器,从消息队列中检取指定的消息[忽略其它的消息),这是一个消息标识的范围(由第一个和最后一个标识指定)、一个窗口句柄或者两者都是GetMessage 和PeekMessage 利 用消息过滤器有选择地检取队列中的某条消息。如果某个应用程序必须检索消息队列中的排在后面的消息,消息过滤则是很有用的。
过滤消息的应用程序必须保证满足消息过滤器的消息是能被投 递的,例如,如果某个应用程序的过滤器用于一个并不接收键盘输入的窗口中的WM_CHAR 消息,函数GetMessage 就 不能返回,这样就会“挂起”这个应用程序。
要 过滤键盘、鼠标和DDE消息,应用程序可以便用下列常量WM_KEYFIRST 和 WM_KEYLAST, WM_MOUSEFIRST 和 WM_MOUSELAST messages, 和 WM_DDE_FIRST 和 WM_DDE_LAST
消息死锁
调 用函数SendMessage 的线程向另一个线程发送一条消息,要等待接收消息的窗口过程返回,如果接收消息的线程在 处理消息时放弃了控制,发送消息的线程就不能继续执行下去,因为它正等待SendMessage 返回,这种情况就叫做 死锁。接收消息的线程无须直接地放弃控制,调用下列函数其个的一个就能让线程放弃控制。
DialogBox
DialogBoxIndirect
DialogBoxIndirectParam
DialogBoxParam
GetMessage
MessageBox
PeekMessage
窗口过程可以确定它所接收的 消息是不是另一个线程通过调用函数InSendMessage 发来的。在处理一条消息时调用前面所列出的任一个函数之 前,窗口过程应首先调用InSendMessage ,如果函数返回TRUE ,窗口 过程就必须在调用任何能使线程放弃控制的函数之前调用函数ReplyMessage 。
使用消息和消息队列
这节描述如何完成下面的工作
创建消息环
检查消息队列
投递消息
发送消息
创建消息环
windows 系统为每一个线程自动创建消息队列,如果线程创建了一个或多个窗口,就必须提供从线程消息队列中检取消息,并把它们发送至相应窗口过程的消息环。
因为windows系统指导向应用程 序中的某个窗口发送消息,线程就必须在启动它的消息环之前至少要创建—个窗口,绝大多数windows应用程序含有一个创建窗口的线程。一个典型的应用程 序是在函数WinMain 中注册它主窗口的窗口类。创建和显示主窗口.然后启动消息环。
函数GetMessage 和 DispatchMessage 用来创建消息环,如果应用程序必须从用户得到字符输入,那么在消息环中应包含函数TranslateMessage , TranslateMessage 把 虚键消转换成字符消息。下面的范例说明了一个简单的windows应用程序的WinMain 函数中的消息环
HINSTANCE hinst;
HWND hwndMain;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the main window.
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
hinst = hInstance; // save instance handle
// Create the main window.
hwndMain = CreateWindow("MainWndClass", "Sample", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL, hinst, (LPVOID) NULL);
// If the main window cannot be created, terminate
// the application.
if (!hwndMain)
return FALSE;
// Show the window and paint its contents.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Start the message loop.
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Return the exit code to Windows.
return msg.wParam;
}
函数GetMessage,TranslateMessage 以 及DispatchMessage 把MSG 结构的指针当作一个参数。如果有消息,GetMessage 把 它复制到MSG 结构中,如果这个消息是一条虚键消息(如WM_KEYDOWN 或WM_SYSKEYDOWN ),TranslateMessage 产 生一个字符消息(WM_CHAR 或WM_SYSCHAR ),并把它放到消息队列中 去。DispatchMessage 也使用MSG 结构的成员用作窗口过程的参数. 但要等到窗口过程完成处理后才返回。
如 果某个线程支持加速键,那么它的消息环必须含有函数TranslateAccelerator 。这个函数检查与线程加 速键表中的一个入口相匹配的组合键,如果它找到一个匹配值.TranslateAccelerator 就把组合键翻译 成一条WM_COMMAND 消息,并把它发送到窗口过程。
如果某个线程使用模式对话框,消息环中必须含有函数IsDialogMessage 以 便于对话框能够接收键盘输入。
有 关对话框,参见“对话框”。
下 面的范例说明了一个使用加速键的线程的消息环,其中显示了一个模式对话框。
如果TranslateAccelerator 或IsDialogMessage 返 回TRUE (指示消息已被处理),就不再调用TranslateMessage 和 DispatchMessage 原因是TranslateAccelerator 或IsDialogMessage 完 成所有对消息的翻译和发送工作。
HWND hwndMain;
HWND hwndDlgModeless = NULL;
MSG msg;
HACCEL haccel;
// Perform initialization and create a main window.
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
检消息队列
有时,应用程序需要在线程 消息环的外面检查线程消息队列的内容,例如,如果某个应用程序的窗口过程进行一个较长的绘画 操作,就可能允许用户中断这个操作。除非应用程序在处理鼠标和键盘消息的操作过程中不停地检查消息队列,否则在操作结束之前就不再会响应用户的输入.原因 是线程消息环中的函数DispatchMessage在窗口过程处理完消息之前是不会返回的。
对于一个较长时间的操作,可使用函数PeekMessage来 检查消息队列,PeekMessage与函数GetMessage是很相似的,都 可用来检查消息队列中与过滤器标准相匹配的消息,再把这个消息复制到一个MSG结构中。它们之间主要的不同就是GetMessage要 等待队列中出现一条与过滤器标准相匹配的消息,而PeekMessage 会立即返回,不管队列中是否有某条消息。 下面的范例说明了如何使用PeekMessage在 一个长操作期间,检查消息队列中的一条单击鼠标或键盘输入消息。 HWND hwnd; BOOL fDone; MSG msg; // Begin the operation and continue until it is complete // or until the user clicks the mouse or presses a key. fDone = FALSE; while (!fDone) { fDone = DoLengthyOperation(); // application-defined function // Remove any messages that may be in the queue. If the // queue contains any mouse or keyboard // messages, end the operation. while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) { switch(msg.message) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: // Perform any required cleanup. fDone = TRUE; } } } 其它的函数如GetQueueStatus和GetInputState也 能用来检查线程消息队列中的内容,GetQueueStatus返回一组标志,用来指明队列中消息的类型,这是一个最 快的办法来确定队列中是否有消息,如果队列中台有鼠标或键盘消息,GetInputState就返回TRUE, 这两个函数都能用来确定队列中是否有需要处理的消息。 投递消息使用函数PostMessage把一条消息投递到消息队列中,PostMessage在 线程消息队列的最后放置消息并立即返回,它不等待线程处理这条消息。函数的参数包括窗口句柄、消息标识相两个消息参数,windows系统把这些参数复制 到一个MSG结构中,填充结构的time和pt成 员,再把这个结构放到消息队列中。 windows 系统用函数PostMessage所带的窗口句柄来决定哪一个线程消息队列接收消息.如果句柄是HWND_TOPMOST,windows 系统就把这条消息投递到所有顶层窗口的线程消息队列中。 函数PostThreadMessage可用来向一个指定的线程消息队列投递消息,PostThreadMessage与PostMessage也 很相似.只是它的第一个参数是线程标识而不是窗口句柄,可通过调用函数GetCurrentThreadId检取这个 线程标识。 函数PostQuitMessage用 来退出消息环,PostQuitMessage向当前正在执行的线程发送WM_QUIT消 息,如果线程消息环接收到WM_QUIT消息,就结束消息环并把控制返回给windows系统。应用程序通常调用PostQuitMessage响 应WM_DESTROY消息, 用法如下 case WM_DESTROY: // Perform cleanup tasks. PostQuitMessage(0); break; 发送消息函数SendMessage是用来直接向 一个窗口过程发送消息,SendMessage调用一个窗口过程,并等待过程对消息的处理和返回结果。 一条消息可以被发往系统中的任何一个窗口,而仅要求有一个窗口句柄,windows系统用这个句柄决定哪一个窗口过程应该接收这条消息。 如果窗口过程在处理由另一个线程发来的消息时放弃控制,就会出现消息死锁(有关消息死锁,参见 “消息死锁”)。在处理一个可能是发自另一个线程的消息之前,窗口过程应首先调用函数InSendMessage,如 果这个函数返回TRUE,那么窗口过程就应在调用任何可以使线程放弃控制的函数之前调用ReplyMessage, 做法如下: case WM_USER + 5: if (InSendMessage()) ReplyMessage(TRUE); DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc); break; 有一些消息可以发给对话框中的控制框,这些控制框消息设置控制框的外观、特性和内容或是检取有关 控制框的信息。例如,CB_ADDSTRING消息可以向组合框添加字串,BM_SETCHECK消 息能够设置复选框或单选按钮的选择状态。 使 用函数SendDlgItemMessage向一个控制框发送一条消息,要指定控制框的标识,含有这个控制框的对话框 窗口的句柄。下面的范例用在对话框过程中的,把一个字串从组合框的编辑控制框拷到它的列表框,这个例子用SendDlgItemMessage向 组合框发送一条CB_ADDSTRING消息. HWND hwndCombo; int cTxtLen; PSTR pszMem; switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDD_ADDCBITEM: // Get the handle of the combo box and the // length of the string in the edit control // of the combo box. hwndCombo = GetDlgItem(hwndDlg, IDD_COMBO); cTxtLen = GetWindowTextLength(hwndCombo); // Allocate memory for the string and copy // the string into the memory. pszMem=(PSTR)VirtualAlloc((LPVOID)NULL,(DWORD)(cTxtLen+1),MEM_COMMIT, PAGE_READWRITE); GetWindowText(hwndCombo, pszMem, cTxtLen + 1); // Add the string to the list box of the // combo box and remove the string from the // edit control of the combo box. if (*pszMem != NULL) { SendDlgItemMessage(hwndDlg, IDD_COMBO, CB_ADDSTRING, 0, (DWORD) ((LPSTR) pszMem)); SetWindowText(hwndCombo, (LPSTR) NULL); } // Free the memory and return. VirtualFree(pszMem, 0, MEM_RELEASE); return TRUE; // Process other dialog box commands. } // Process other dialog box messages. } 消息及消息队列参考下列函数函数用于消息和消息队列 BroadcastSystemMessage DefWindowProc DispatchMessage GetInputState GetMessage GetMessageExtraInfo GetMessagePos GetMessageTime GetQueueStatus InSendMessage PeekMessage PostMessage PostQuitMessage PostThreadMessage RegisterWindowMessage ReplyMessage SendAsyncProc SendMessage SendMessageCallback SendMessageTimeout SendNotifyMessage SetMessageExtraInfo TranslateMessage WaitMessage Obsolete Functions PostAppMessage SetMessageQueue WM_USER
MSG包括:
窗口句柄,指示MSG发送的目的窗口
消息标识
lPARAM、wParam
发送时间
发送时的鼠标位置
关于消息队列:
Windows系统有一个系统消息队列
每个线程都有一个自己的消 息队列(由于发送消息MSG需 要提供一个窗口HWnd,而基 本有窗口的线程,都是UI线 程),因此基本上如果线程使用了GDI函数,则windows给该线程分配一个线程消息队列,这个消息队列负责该线程的所有窗口的消息。
所有的窗口都有自己的句柄(HWND),消息被发送时,这个句柄就已经被指定了。所以 当子窗口收到一个消息时,其父窗口不会也收到这个消息,除非子窗口手 动的转发。
消息分为:
实际上MSDN把消息分为队列型(Queued Message)和非队列型(Non-queued Message),这只是不同的路由方式, 但最终都会由消息处理函数来处理。
队 列型消息包括硬件的输入(WM_KEY*等)、WM_TIMER消 息、WM_PAINT消息等;非队列型的一些例子有WM_SETFOCUS, WM_ACTIVE, WM_SETCURSOR等,它们被直接发送给处理函数。
其实,按照MSDN的说法和消息的路由过程可以理 解为,Posted Message Queue里的消息是真正的队列型消 息,而通过SendMessage()发送到消息,即使它进入了Sent Message Queue,由于SendMessage要求的同步处理,这些消息也 应该算非队列型消息。也许,Windows系统会特殊处理,使消息强行绕 过队列。
=====================
一节详细描述消息和消息队列以及如何在 你程序中使用他们。
关于消息和消息 队列
与传统的应用程序不 同,Microsoft Windows应用程序并不显式地用一个函数的调用(如c运行库)来获取输入,而是,等待windows系统把输入传给它们。
windows系统把应用程序 的所有输入传给应用程序的窗口,每个窗口都有一个称之为窗口过程的函数.当窗口有输入时windows系统要调用它,窗口过程处理输入并把控制返回 windows系统。有关窗口过程,参见 “窗口过程”。 这一章讲述消息及消息队列,并说明在应用程序中如何使用它们。
消息
windows系统以消息的形式把输入传给窗口过程,消息是由windows系 统或应用程序产生的.windows系统对每一个输入事件都要产生消息,例如,用户按键盘、移动鼠标或单击一个滚动条控制框。windows系统为了响应 应用程序给系统带来的变化也会产生消息,比如应用程序改变了系统字体资源池或是改变了一个窗门的大小。应用程序可通过产生消息指导它自己的窗口来完成某个 任务,或是与其它应用程序的窗口进行通信。
windows 系统把消息发送给窗口过程.窗口过程有四个参数:窗口句柄、消息标识以及两个叫做消息参数的32位值。窗口句柄决定消息将发送到哪—个窗 口,windows系统则用它来确定向哪一个窗口过程发送消息。
消息标识是一个命名的常量,由它来标明消息的目的。如果窗口过程接收到一条消息,它就通过消息标识来决定如何处理这条 消息。例如,消息标识WM_PAINT 通知窗口过程,窗口的客户区被改变了,需要重画。
消息参数指定窗口过程在处理消息时所用的数据或数据的位 置,消息的意图及数值取决了消息本身。消息参数可以是一个整数、紧缩的位标志、一个含有附加数据结构的指针等等。如果消息不使用消息参数,一般就都设置成NULL 、 窗口过程必须检查消息标识以确定如何解释消息参数。
消息路由
windows系统用两种方式向窗口过程发送消息:把消息投递到一个先进先出的消息队列中,它是一个系统定义的内存块用于临时存储消息;或是把消 息直接发给窗口过程。
投递到 消息队列中的消息叫排队消息,它们主要是用户通过鼠标或键盘的输入结果.如WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, and WM_CHAR 消息。其它的排队消息包括定时器、绘制和退出消息:WM_TIMER, WM_PAINT, and WM_QUIT 。所有直接发送到窗口过程的其它消息称之为非排队消息。
排队消息
windows系统在同一时间可显示多个窗口,要发送鼠标和键盘输入到相应 的窗口,windows系统要用到消息队列,它要管理一个系统消息队列和任意数目线程消息队列,每一个队列对应于一个线程。
不管什么时候,只要用户移动鼠标或是敲键盘.鼠标或键盘的 设备驱动器都要把输入转换成消息,并把它们放到系统消息队列中去。windows从系统队列中每次移走一条消息,确定目的窗口,再把它们投递到创建目的窗 口的线程的消息队列中,线程消息队列接收所有由该线程创建的窗口的鼠标和键盘消息。线程从它的队列中移走消息并指导windows系统将它们发送到相应的 窗口过程进行处理。有关线程,参见 “进程和线程”。
WM_PAINT 消息有点特别,windows系统总是把这条消息放在消息队列的最后,这样 可保证窗口按先进先出次序接收它的输入消息,WM_PAINT 消息被保持在队列中,只有在队列中没有其它消息时才发送 到窗口过程。同一个窗口的多个WM_PAINT 消息被合并成一个WM_PAINT 消 息,把客户区所有无效部分合并成一个区域.合并WM_PAINT 消息节约了窗口必须重画客户区内容的时间。
系统向线程消息队列投递消息是通过填充 一个MSG 结构,再把它复制到消息队列中,MSG 结构中的信息包括接收消息的窗口 句柄、消息标识、两个消息参数、消息时间以及鼠标的位置,线程可把消息投递到它自己的消息队列中或是通过函数 PostMessage 和PostThreadMessage 把 消息投递到其它线程的队列中去。
应 用程序可通过函数GetMessage 从它的队列中移走一条消息,应用程序还可用函数PeekMessage 来 检查队列中的某个消息但并不移走它,这个函数用有关这条消息的信息填充MSG 结构。
把一条消息从它的队列中移走后.应用程序可用函数DispatchMessage 指 导windows系统把这条消息发送到窗口过程进行处理。DispatchMessage 利用前面调用函数GetMessage 或PeekMessage 时 填充的MSG 结构的指针,把窗口句柄、消息标识及两个消息参数传给窗口过程,但它并不传送时间或鼠标光标的位置.应用 程序可以在处理一条消息时,通过调用函数GetMessageTime 和GetMessagePos 来 获取这些信息。
一个线程可以 用函数WaitMessage 当他没有其他消息在其队列里时,产生对其他线程的控制。此函数将终止线程,直到一个新消 息被放入该线程的消息队列里,然后返回。
你 可以调用函数SetMessageExtraInfo 来设置当前线程消息队列的附加信息。是和当前线程的消息队列联系 的32位值。用户可以用函数GetMessageExtraInfo 来获得附加信息,该信息将会保留到下次调用函数GetMessage 或PeekMessage 之 前。
非排队消息
非 排队消息是直接发送到目标窗口过程的,而不通过系统消息队列和线程消息队列。windows系统一般通过发送非排队消息把影响某窗口的事件通知窗口。例 如,如果用户激活一个新的应用程序窗口.windows系统就会向该窗口发送一系列的消息,包括:WM_ACTIVATE ,WM_SETFOCUS 和WM_SETCURSOR ,这些消息分别通知窗口: 它被激活了;将通过这个窗口进行键盘输入;鼠标已移到这个窗口边 框的里面了 。非排队消息也有可能发生在 应用程序调用一个windows系统函数时,例如,在应用程序用函数SetWindowPos 来移动一个窗口之 后,windows系统发送一条WM_WINDOWPOSCHANGED 消息。
应用程序是调用函数SendMessage 、SendNotifyMessage 或SendDlgItemMessage 发 送消息的。
消息处理
应用程序必须删除和处理投递到它的线 程消息队列中的消息,单一线程的应用程序一般是在它的WinMain函数中使用一个消息环来删除消息,并把消息发送到相应的窗口过程进行处理。具有多重线 程的应用程序在创建窗口的每一个线程中使用一个消息环,下一节将讲述消息环是如何工作的,另外还解释了窗口过程的一般规则。
消息环
一个简单的消息环含有一个对下列函数的调 用:GetMessage, TranslateMessage和DispatchMessage。函数GetMessage从队列中检取一条消息并把它复制到一个MSG结构 中.GetMessage应返回TRUE,但如果它得到的是WM_QUIT消息,它就返回FALSE并结束循环。在单一线程的应用程序中,结束消息循环通 常是关闭应用程序的第一步。一般在应用程序主窗口的窗口过程中响应WM_DESTROY消息时,应用程序通过函数PostQuitMessage关闭它自 己的消息环。
如果在 GetMessage中指定窗口句柄,那么从队列中检取的只是指定窗口的消息。GetMessage 也能过滤队列中的消息,这种情况下检取的只是指定范围内的消息。有关过滤消息,参见 “消息过滤”。
如果某个线程想接收键盘的字符输入,那么线程消息环中必须含有 TranslateMessage。Windows系统在用户每按一次键时会产生一个虚键消息(WM_KEYDOWN和WM_KEYUP),虚键消息含有 一个标识哪一个键被按过的虚键码,但不是它的字符值,要得到这个值,消息环中必须含有TranslateMessage,由它来把虚键消息翻译成字符消息 (WM_CHAR),再把它放回到应用程序的消息队列中去.这样字符消息才能在消息环的下一轮循环中被发送到窗口过程。
函数DispatchMessage把消息发送到与MSG结构 中指定的窗口句柄相应的窗口过程,如果窗口句柄是HWND_TOPMOST ,DispatchMessage就把消息发送到系统中所有顶层窗口的窗口过程。如果窗口句柄是NULL,对于这条消息DispatchMessage则 什么也不做。
应用程序的主线 程在初始化应用程序并且至少创建了一个窗口之后就开始了消息循环,一旦开始,消息环就连续不断地从线程的消息队列中校取消息并把它们分发到相应的窗口,函 数GetMessage从消息队列中检取到WM_QUIT消息时,消息环就结束了。
一个消息队列只需要有一个消息环,而不管应用程序有多少个窗口,因为队列中的每一条消息是一个 MSG结构,其中含有接收消息的窗口句柄,DispatchMessage总能把消息发送到相应的窗口。
应用程序可以有多种方法修改它的消息环,例如,它可以从队列中检取消息但并不 发送到任何窗口,这对那些投递不指定窗口的消息的应用程序是很有用的,(这些消息是提供给应用程序的,而不是某个窗口,因为它们含有NULL窗口句柄)。 应用程序也能指导GetMessage来搜索队列中一个特定的消息,而不管其它消息,这对那些有时不按消息队列先进先出次序检取消息的应用程序来说是很有 用的。
使用键盘加速键的应用 程序必须能够把键盘消息转换成命令消息,要这样做,应用程序的消息环必须调用函数TranslateAccelerator有关加速键,参见 “键盘加速键”。
窗口过程
窗口过程是一个函数,用来接收和处理 所有发送到该窗口的消息,每个窗口类都有一个窗口过程,同一窗口类所创建的窗口共用同一个窗口过程来响应消息。
系统通过把消息数据作为过程的参数来向窗口过程发送消息,再由窗口过程 完成与消息相应的活动。它需要检查消息的标识,在处理消息时要使用由消息参数指定的这个信息。
窗口过程一般不会忽略—条消息,如果它不处理某条消息,它就必须把这条消息传回系统进行 默认处理,窗口过程是调用函数DefWindowProc 来完成的,由它完成一个默认的操作并返回消息结果。绝大多数 窗口过程只处理几种类型的消息,其它的则通过调用DefWindowProc 传给了系统。
因为窗口过程是由所有属于同类窗口共享的,所以它能处理 几个不同窗口的消息,要识别受消息影响的某个窗口,窗口过程可以检查消息所带的窗口句柄。有关窗口过程,参见 “窗口过程”。
传递和发送消息
任何应用程序都能投递和发送消息,就跟系统一样,应用程序投递一 条消息是通过把它复制到消息队列,发送消息则是通过把消息数据作为窗门过程的参数。要投递消息,应用程 序需 要用到函数PostMessage ,要发送消息,程序使用函数SendMessage, BroadcastSystemMessage, SendMessageCallback, SendMessageTimeout, SendNotifyMessage 或SendDlgItemMessage 。
应用程序通常投递—条消息来通知某个窗口去完成 一个任务。PostMessage 为消息创建一个MSG 结构并把消息拷到消息队列 中,最后由应用程序的消息环检取这条消息再把它发送到相应的窗口过程。
应用程序一般是通过发送一条消息通知窗口过程立即完成某项任务,函数SendMessage 把 消息发送到与给定窗口相应的窗口过程,这个函数要等待窗口过程完成处理井返回消息的结果。父窗口与子窗口之间也是通过发送消息来进行相互间的通信,例如, 某个父窗口有一个编辑控制框作为它的子窗口,就可通过向它发送消息设置控制框的正文,这个控制框则通过向父窗口发送消息来把用户对正文的改变通知其父窗 口。
函数SendMessageCallback 也 能发送消息到指定窗口的窗口过程里,但是,这函数是立即返回,当窗口过程函数处理完消息后,系统调用指定的回调函数,有关更多回调函数信息,参考函数SendAsyncProc 。
有时,应用程序也有可能要求 向系统中的所有顶层窗口发送或投递一条消息,例如,如果应用程序改变了系统的时间,它必须通过发送WM_TIMECHANGE 消 息来通知所有顶层窗口,应用程序向所有顶层窗口发送或投递一条消息是调用函数SendMessage 或PostMessage , 并在hwnd 参数中指定HWND_TOPMOST 。你同样通过函数BroadcastSystemMessage 指 定参数lpdwRecipients 的值为BSM_APPLICATIONS 来 广 播消息给所有程序
应用程序能 够投递一条消息而不指定窗口,在调用PostMessage 时应用程序提供NULL 窗 口句柄,这条消息就被投递到与当前线程相应的队列中。因为没有指定窗口句柄,应用程序就必须在处理消息环中的这条消息,这也是一种创建消息的方法.此类消 息适用于整个应用程序,而不只是指某个窗口。
使用函数InSendMessage 窗口过程能够确定它所处理的消息是从另一个线程发来的,这种能力 在需要根据消息源进行消息处理时是很有用的。
经常出现的一个编程错误是假设函数PostMessage 总能成功地投递一条消息,这在消息队列是满 的时候是不对的,应用程序应该检查函数PostMessage 的返回值以确认消息是否已经被投递,否则要重新投递这条 消息。
消息种类
这部分将两种类型windows消 息;系统定义的消息,程序定义的消息
系 统消息
系统使用系统定义的 消息来控制应用程序的操作,并给应用程序提供输入或其他信息进行处理。系统在与应用程序进行通信是时是发送系统消息的。应用程序也能发送或投递系统消息, 应用程序通常用这些消息来控制预注册类创建的控制窗口的操作。
每条系统消息都有一个唯一的消息标识,对应于一个符号常量(在Windows系统头文件中定义),它表明了消息的目的, 例如,常量WM_PAINT 要求窗口绘制它的内容。
符号常量指定了系统消息所属的类别,常量的前缀标识能够解释和处理消息的亩口的类型。下表列出 了前缀及相应的消息类别:
前 缀 消息类
ABM Application desktop toolbar
BM Button control
CB Combo box control
CDM Common dialog box
DBT Device
DL Drag list box
DM Default push button control
EM Edit control
HDM Header control
LB List box control
LVM List view control
PBM Progress bar
PSM Property sheet
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
通 用窗口消息覆盖了—个较大范围的信息和请求,包括鼠标和键盘输入消息、菜单和对话框输入消息、窗口创建和管理消息及动态数据交换消息(DDL)。
应用程序定义消息
应用程 序可创建用在它自己的窗口中的消息,或是与其它进程中的窗口进行通信的消息。如果应用程序创建了它自己的消息,接收它们的窗口过程必须能够对消息进行翻 译,并提供相应的处理。 ’
windows 系统保留用于系统定义的消息的标识值的范围从0x0000到0x03FF(等于WM_USER —1)和0x8000到 0xBFFF应用程序不能把这些值用于私有消息。
从0x0400(WM_USER 的值)到0x7FFF之间的值是可用于应用程序定义的用于它自己 的消息标识,而从0xC000到0xFFFF之间的值是应用程序为了与其它应用程序中的窗口进行通信所定义的消息标识。
应用程序用函数RegisterWindowMessage 注 册一条消息时,windows系统返回的消息标识在0xC000到0xFFFF之间,这个函数所返回的消息标识应保证在整个系统中是唯一的。如果应用程序 要创建与其它应用程序中的窗口进行通信的消息,则使用RegisterWindowMessage 来对它进行注册,这 个函数可防止由于其它的应用程序基于不同的目的使用了相同的消息标识所产生的冲突。
消息过滤
应用程序可使用函数GetMessage 或PeekMessage 来 指定一个消息过滤器,从消息队列中检取指定的消息[忽略其它的消息),这是一个消息标识的范围(由第一个和最后一个标识指定)、一个窗口句柄或者两者都是GetMessage 和PeekMessage 利 用消息过滤器有选择地检取队列中的某条消息。如果某个应用程序必须检索消息队列中的排在后面的消息,消息过滤则是很有用的。
过滤消息的应用程序必须保证满足消息过滤器的消息是能被投 递的,例如,如果某个应用程序的过滤器用于一个并不接收键盘输入的窗口中的WM_CHAR 消息,函数GetMessage 就 不能返回,这样就会“挂起”这个应用程序。
要 过滤键盘、鼠标和DDE消息,应用程序可以便用下列常量WM_KEYFIRST 和 WM_KEYLAST, WM_MOUSEFIRST 和 WM_MOUSELAST messages, 和 WM_DDE_FIRST 和 WM_DDE_LAST
消息死锁
调 用函数SendMessage 的线程向另一个线程发送一条消息,要等待接收消息的窗口过程返回,如果接收消息的线程在 处理消息时放弃了控制,发送消息的线程就不能继续执行下去,因为它正等待SendMessage 返回,这种情况就叫做 死锁。接收消息的线程无须直接地放弃控制,调用下列函数其个的一个就能让线程放弃控制。
DialogBox
DialogBoxIndirect
DialogBoxIndirectParam
DialogBoxParam
GetMessage
MessageBox
PeekMessage
窗口过程可以确定它所接收的 消息是不是另一个线程通过调用函数InSendMessage 发来的。在处理一条消息时调用前面所列出的任一个函数之 前,窗口过程应首先调用InSendMessage ,如果函数返回TRUE ,窗口 过程就必须在调用任何能使线程放弃控制的函数之前调用函数ReplyMessage 。
使用消息和消息队列
这节描述如何完成下面的工作
创建消息环
检查消息队列
投递消息
发送消息
创建消息环
windows 系统为每一个线程自动创建消息队列,如果线程创建了一个或多个窗口,就必须提供从线程消息队列中检取消息,并把它们发送至相应窗口过程的消息环。
因为windows系统指导向应用程 序中的某个窗口发送消息,线程就必须在启动它的消息环之前至少要创建—个窗口,绝大多数windows应用程序含有一个创建窗口的线程。一个典型的应用程 序是在函数WinMain 中注册它主窗口的窗口类。创建和显示主窗口.然后启动消息环。
函数GetMessage 和 DispatchMessage 用来创建消息环,如果应用程序必须从用户得到字符输入,那么在消息环中应包含函数TranslateMessage , TranslateMessage 把 虚键消转换成字符消息。下面的范例说明了一个简单的windows应用程序的WinMain 函数中的消息环
HINSTANCE hinst;
HWND hwndMain;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the main window.
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
hinst = hInstance; // save instance handle
// Create the main window.
hwndMain = CreateWindow("MainWndClass", "Sample", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, (HMENU) NULL, hinst, (LPVOID) NULL);
// If the main window cannot be created, terminate
// the application.
if (!hwndMain)
return FALSE;
// Show the window and paint its contents.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Start the message loop.
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Return the exit code to Windows.
return msg.wParam;
}
函数GetMessage,TranslateMessage 以 及DispatchMessage 把MSG 结构的指针当作一个参数。如果有消息,GetMessage 把 它复制到MSG 结构中,如果这个消息是一条虚键消息(如WM_KEYDOWN 或WM_SYSKEYDOWN ),TranslateMessage 产 生一个字符消息(WM_CHAR 或WM_SYSCHAR ),并把它放到消息队列中 去。DispatchMessage 也使用MSG 结构的成员用作窗口过程的参数. 但要等到窗口过程完成处理后才返回。
如 果某个线程支持加速键,那么它的消息环必须含有函数TranslateAccelerator 。这个函数检查与线程加 速键表中的一个入口相匹配的组合键,如果它找到一个匹配值.TranslateAccelerator 就把组合键翻译 成一条WM_COMMAND 消息,并把它发送到窗口过程。
如果某个线程使用模式对话框,消息环中必须含有函数IsDialogMessage 以 便于对话框能够接收键盘输入。
有 关对话框,参见“对话框”。
下 面的范例说明了一个使用加速键的线程的消息环,其中显示了一个模式对话框。
如果TranslateAccelerator 或IsDialogMessage 返 回TRUE (指示消息已被处理),就不再调用TranslateMessage 和 DispatchMessage 原因是TranslateAccelerator 或IsDialogMessage 完 成所有对消息的翻译和发送工作。
HWND hwndMain;
HWND hwndDlgModeless = NULL;
MSG msg;
HACCEL haccel;
// Perform initialization and create a main window.
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
检消息队列
有时,应用程序需要在线程 消息环的外面检查线程消息队列的内容,例如,如果某个应用程序的窗口过程进行一个较长的绘画 操作,就可能允许用户中断这个操作。除非应用程序在处理鼠标和键盘消息的操作过程中不停地检查消息队列,否则在操作结束之前就不再会响应用户的输入.原因 是线程消息环中的函数DispatchMessage在窗口过程处理完消息之前是不会返回的。
对于一个较长时间的操作,可使用函数PeekMessage来 检查消息队列,PeekMessage与函数GetMessage是很相似的,都 可用来检查消息队列中与过滤器标准相匹配的消息,再把这个消息复制到一个MSG结构中。它们之间主要的不同就是GetMessage要 等待队列中出现一条与过滤器标准相匹配的消息,而PeekMessage 会立即返回,不管队列中是否有某条消息。 下面的范例说明了如何使用PeekMessage在 一个长操作期间,检查消息队列中的一条单击鼠标或键盘输入消息。 HWND hwnd; BOOL fDone; MSG msg; // Begin the operation and continue until it is complete // or until the user clicks the mouse or presses a key. fDone = FALSE; while (!fDone) { fDone = DoLengthyOperation(); // application-defined function // Remove any messages that may be in the queue. If the // queue contains any mouse or keyboard // messages, end the operation. while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) { switch(msg.message) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: // Perform any required cleanup. fDone = TRUE; } } } 其它的函数如GetQueueStatus和GetInputState也 能用来检查线程消息队列中的内容,GetQueueStatus返回一组标志,用来指明队列中消息的类型,这是一个最 快的办法来确定队列中是否有消息,如果队列中台有鼠标或键盘消息,GetInputState就返回TRUE, 这两个函数都能用来确定队列中是否有需要处理的消息。 投递消息使用函数PostMessage把一条消息投递到消息队列中,PostMessage在 线程消息队列的最后放置消息并立即返回,它不等待线程处理这条消息。函数的参数包括窗口句柄、消息标识相两个消息参数,windows系统把这些参数复制 到一个MSG结构中,填充结构的time和pt成 员,再把这个结构放到消息队列中。 windows 系统用函数PostMessage所带的窗口句柄来决定哪一个线程消息队列接收消息.如果句柄是HWND_TOPMOST,windows 系统就把这条消息投递到所有顶层窗口的线程消息队列中。 函数PostThreadMessage可用来向一个指定的线程消息队列投递消息,PostThreadMessage与PostMessage也 很相似.只是它的第一个参数是线程标识而不是窗口句柄,可通过调用函数GetCurrentThreadId检取这个 线程标识。 函数PostQuitMessage用 来退出消息环,PostQuitMessage向当前正在执行的线程发送WM_QUIT消 息,如果线程消息环接收到WM_QUIT消息,就结束消息环并把控制返回给windows系统。应用程序通常调用PostQuitMessage响 应WM_DESTROY消息, 用法如下 case WM_DESTROY: // Perform cleanup tasks. PostQuitMessage(0); break; 发送消息函数SendMessage是用来直接向 一个窗口过程发送消息,SendMessage调用一个窗口过程,并等待过程对消息的处理和返回结果。 一条消息可以被发往系统中的任何一个窗口,而仅要求有一个窗口句柄,windows系统用这个句柄决定哪一个窗口过程应该接收这条消息。 如果窗口过程在处理由另一个线程发来的消息时放弃控制,就会出现消息死锁(有关消息死锁,参见 “消息死锁”)。在处理一个可能是发自另一个线程的消息之前,窗口过程应首先调用函数InSendMessage,如 果这个函数返回TRUE,那么窗口过程就应在调用任何可以使线程放弃控制的函数之前调用ReplyMessage, 做法如下: case WM_USER + 5: if (InSendMessage()) ReplyMessage(TRUE); DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc); break; 有一些消息可以发给对话框中的控制框,这些控制框消息设置控制框的外观、特性和内容或是检取有关 控制框的信息。例如,CB_ADDSTRING消息可以向组合框添加字串,BM_SETCHECK消 息能够设置复选框或单选按钮的选择状态。 使 用函数SendDlgItemMessage向一个控制框发送一条消息,要指定控制框的标识,含有这个控制框的对话框 窗口的句柄。下面的范例用在对话框过程中的,把一个字串从组合框的编辑控制框拷到它的列表框,这个例子用SendDlgItemMessage向 组合框发送一条CB_ADDSTRING消息. HWND hwndCombo; int cTxtLen; PSTR pszMem; switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDD_ADDCBITEM: // Get the handle of the combo box and the // length of the string in the edit control // of the combo box. hwndCombo = GetDlgItem(hwndDlg, IDD_COMBO); cTxtLen = GetWindowTextLength(hwndCombo); // Allocate memory for the string and copy // the string into the memory. pszMem=(PSTR)VirtualAlloc((LPVOID)NULL,(DWORD)(cTxtLen+1),MEM_COMMIT, PAGE_READWRITE); GetWindowText(hwndCombo, pszMem, cTxtLen + 1); // Add the string to the list box of the // combo box and remove the string from the // edit control of the combo box. if (*pszMem != NULL) { SendDlgItemMessage(hwndDlg, IDD_COMBO, CB_ADDSTRING, 0, (DWORD) ((LPSTR) pszMem)); SetWindowText(hwndCombo, (LPSTR) NULL); } // Free the memory and return. VirtualFree(pszMem, 0, MEM_RELEASE); return TRUE; // Process other dialog box commands. } // Process other dialog box messages. } 消息及消息队列参考下列函数函数用于消息和消息队列 BroadcastSystemMessage DefWindowProc DispatchMessage GetInputState GetMessage GetMessageExtraInfo GetMessagePos GetMessageTime GetQueueStatus InSendMessage PeekMessage PostMessage PostQuitMessage PostThreadMessage RegisterWindowMessage ReplyMessage SendAsyncProc SendMessage SendMessageCallback SendMessageTimeout SendNotifyMessage SetMessageExtraInfo TranslateMessage WaitMessage Obsolete Functions PostAppMessage SetMessageQueue WM_USER
Windows 消息机制详解
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。