窗口消息Word格式.docx

上传人:b****4 文档编号:6920266 上传时间:2023-05-07 格式:DOCX 页数:22 大小:433.61KB
下载 相关 举报
窗口消息Word格式.docx_第1页
第1页 / 共22页
窗口消息Word格式.docx_第2页
第2页 / 共22页
窗口消息Word格式.docx_第3页
第3页 / 共22页
窗口消息Word格式.docx_第4页
第4页 / 共22页
窗口消息Word格式.docx_第5页
第5页 / 共22页
窗口消息Word格式.docx_第6页
第6页 / 共22页
窗口消息Word格式.docx_第7页
第7页 / 共22页
窗口消息Word格式.docx_第8页
第8页 / 共22页
窗口消息Word格式.docx_第9页
第9页 / 共22页
窗口消息Word格式.docx_第10页
第10页 / 共22页
窗口消息Word格式.docx_第11页
第11页 / 共22页
窗口消息Word格式.docx_第12页
第12页 / 共22页
窗口消息Word格式.docx_第13页
第13页 / 共22页
窗口消息Word格式.docx_第14页
第14页 / 共22页
窗口消息Word格式.docx_第15页
第15页 / 共22页
窗口消息Word格式.docx_第16页
第16页 / 共22页
窗口消息Word格式.docx_第17页
第17页 / 共22页
窗口消息Word格式.docx_第18页
第18页 / 共22页
窗口消息Word格式.docx_第19页
第19页 / 共22页
窗口消息Word格式.docx_第20页
第20页 / 共22页
亲,该文档总共22页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

窗口消息Word格式.docx

《窗口消息Word格式.docx》由会员分享,可在线阅读,更多相关《窗口消息Word格式.docx(22页珍藏版)》请在冰点文库上搜索。

窗口消息Word格式.docx

为了使这个概念更加明确具体,可以想像一个线程建立了一个窗口,然后就结束了。

在这种情况下,窗口不会收到一个WM_DESTROY或WM_NCDESTROY消息,因为线程已经结束,不可能被用来使窗口接收和处理这些消息。

这也意味着每个线程,如果它至少建立了一个窗口,都由系统对它分配一个消息队列。

这个队列用于窗口消息的派送(dispatch)。

为了使窗口接收这些消息,线程必须有它自己的消息循环。

1.1线程消息队列

Windows的一个主要目标是为程序的运行提供一个强壮的环境。

每个线程必须有完全不受其他线程影响的消息队列。

使线程可以维持它自己的键盘焦点(keyboardfocus)、窗口激活、鼠标捕获等。

系统分配一个THREADINFO结构,并将这个数据结构与线程联系起来。

线程的虚拟输入队列和一组变量组成,用来跟踪输入状态管理信息:

哪个窗口有键盘焦点,激活,按键按下,符号状态。

鼠标捕捉,形状,可见性。

三个线程及相应的THREADINFO结构

1.2将消息投递到一个线程消息队列中去

当线程有了与之相联系的THREADINFO结构时,线程就有了自己的消息队列集合。

如果一个进程建立了三个线程,并且所有这些线程都调用CreateWindow,则将有三个消息队列集合。

消息被放置在线程的登记消息队列中,这要通过调用PostMessage函数来完成:

boolpostmmessage(HWNDhwnd,UINTuMsg,WPARAMwParam,LPARAMlParam);

当一个线程调用这个函数时,系统要确定是哪一个线程建立了用hwnd参数标识的窗口。

然后系统分配一块内存,将这个消息参数存储在这块内存中,并将这块内存追加到相应线程的投送消息队列中。

并且,这个函数还设置QS_POSTMESSAGE唤醒位。

函PostMessage在投送了消息之后立即返回,调用该函数的线程不知道登记投送的消息是否被指定窗口的窗口过程所处理。

实际上,有可能这个指定的窗口永远不会收到投送的消息。

如果建立这个特定窗口的线程在处理完它的消息队列中的所有消息之前就结束了,就会发生这种事。

还可以通过调用PostThreadMessage将消息放置在线程的登记消息队列中。

Postthreadmessage(DWORDdwThreadId,UINTuMsg,WPARAMwParam,LPARAMlParam);

PostThreadMessage函数所期望的线程由第一个参数dwThreadId所标记。

当消息被设置到队列中时,MSG结构的hwnd成员将设置成NULL。

当程序要在主消息循环中执行一些特殊处理时要调用这个函数。

线程主消息循环是这样:

在GetMessage或PeekMessage取出一个消息时,主消息循环代码检查hwnd是否为NULL,并检查MSG结构的msg成员来执行特殊的处理。

如果线程确定了该消息不被指派给一个窗口,则不调用DispatchMessage,消息循环继续取下一个消息。

像PostMessage函数一样,PostThreadMessage在向线程的队列投送了消息之后就立即返回。

该函数的线程不知道消息是否以及何时被处理。

VoidpostQuitMesage(intnExitCode);

//退出线程消息循环

PostQuitMessage并不实际投送一个消息到任何一个THREADINFO结构的队列。

只是在内部,PostQuitMessage设定QS_QUIT唤醒标志(后面将要讨论),并设置THREADINFO结构的nExitCode成员。

因为这些操作永远不会失败,所以PostQuitMessage的原型被定义成返回VOID。

1.3向窗口发送消息

LRESULTSendmessage(HWNDhwnd,UINTuMsg,WPARAMwParam,LPARAMlParam);

窗口过程将处理这个消息。

只有当消息被处理之后,SendMessage才能返回到调用程序。

由于具有这种同步特性,比之PostMessage或PostThreadMessage,SendMessage用得更频繁。

调用这个函数的线程在下一行代码执行之前就知道窗口消息已经被完全处理。

同一线程窗口:

它只是将指定窗口的窗口过程,将其作为一个子程序来调用。

当窗口过程完成对消息的处理时,它向SendMessage返回一个值。

SendMessage再将这个值返回给调用线程。

其他线程窗口:

SendMessage的内部工作就复杂得多(即使两个线程在同一进程中也是如此)。

Windows要求建立窗口的线程处理窗口的消息。

其他进程所建立的窗口:

当一个线程调用SendMessage向一个由其他进程所建立的窗口发送一个消息,也就是向其他的线程发送消息,发送线程不可能处理窗口消息,因为发送线程不是运行在接收进程的地址空间中,因此不能访问相应窗口过程的代码和数据。

实际上,发送线程要挂起,而由另外的线程处理消息。

所以为了向其他线程建立的窗口发送一个窗口消息,系统必须执行下面将讨论的动作。

首先,发送的消息要追加到接收线程的发送消息队列,同时还为这个线程设定QS_SENDMESSAGE标志。

其次,如果接收线程已经在执行代码并且没有等待消息(如调用GetMessage、PeekMessage或WaitMessage),发送的消息不会被处理,系统不能中断线程来立即处理消息。

当接收进程在等待消息时,系统首先检查QS_SENDMESSAGE唤醒标志是否被设定,如果是,系统扫描发送消息队列中消息的列表,并找到第一个发送的消息。

有可能在这个队列中有几个发送的消息。

例如,几个线程可以同时向一个窗口分别发送消息。

当发生这样的事时,系统只是将这些消息追加到接收线程的发送消息队列中。

当接收线程等待消息时,系统从发送消息队列中取出第一个消息并调用适当的窗口过程来处理消息。

如果在发送消息队列中再没有消息了,则QS_SENDMESSAGE唤醒标志被关闭。

当接收线程处理消息的时候,调用SendMessage的线程被设置成空闲状态(idle),等待一个消息出现在它的应答消息队列中。

在发送的消息处理之后,窗口过程的返回值被登记到发送线程的应答消息队列中。

发送线程现在被唤醒,取出包含在应答消息队列中的返回值。

这个返回值就是调用SendMessage的返回值。

这时,发送线程继续正常执行。

当一个线程等待SendMessage返回时,它基本上是处于空闲状态。

但它可以执行一个任务:

如果系统中另外一个线程向一个窗口发送消息,这个窗口是由这个等待SendMessage返回的线程所建立的,则系统要立即处理发送的消息。

在这种情况下,系统不必等待线程去调用GetMessage、PeekMessage或WaitMessage。

由于Windows使用上述方法处理线程之间发送的消息,所以有可能造成线程挂起(hang)。

例如,当处理发送消息的线程含有错误时,会导致进入死循环

利用4个函数——SendMessageTimeout、SendMessageCallback、SendNotifyMessage和ReplyMessage,可以编写保护性代码防止出现这种情况

LRESULTSendMessageTimeout(

__inHWNDhWnd,

__inUINTMsg,

__inWPARAMwParam,

__inLPARAMlParam,

__inUINTfuFlags,

__inUINTuTimeout,

__out_optPDWORD_PTRlpdwResult);

hWnd:

其窗口程序将接收消息的窗口的句柄。

如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口。

Msg:

指定被发送的消息。

wParam:

指定附加的消息指定信息。

IParam:

fuFlags;

指定如何发送消息。

此参数可为下列值的组合:

SMTO_ABORTIFHUNG:

如果接收进程处于“hung”状态,不等待超时周期结束就返回。

SMTO_BLOCK:

阻止调用线程处理其他任何请求,直到函数返回。

SMTO_NORMAL:

调用线程等待函数返回时,不被阻止处理其他请求。

SMTO_NOTIMEOUTIFNOTHUNG:

Windows95及更高版本:

如果接收线程没被挂起,当超时周期结束时不返回。

uTimeout:

为超时周期指定以毫秒为单位的持续时间。

如果该消息是一个广播消息,每个窗口可使用全超时周期。

例如,如果指定5秒的超时周期,有3个顶层窗回未能处理消息,可以有最多15秒的延迟。

IpdwResult:

指定消息处理的结果,依赖于所发送的消息。

SMTO_ABORTIFHUNG标志是告诉SendMessageTimeout去查看接收消息的线程是否处于挂起状态,如果是,就立即返回。

SMTO_NOTIMEOUTIFNOTHUNG标志使函数在接收消息的线程没有挂起时不考虑等待时间限定值。

SMTO_BLOCK标志使调用线程在SendMessageTimeout返回之前,不再处理任何其他发送来的消息。

SMTO_NORMAL标志在Winuser.h中定义成0,如果不想指定任何其他标志及组合,就使用这个标志。

前面说过,一个线程在等待发送的消息返回时可以被中断,以便处理另一个发送来的消息。

使用SMTO_BLOCK标志阻止系统允许这种中断。

仅当线程在等待处理发送的消息的时候(不能处理别的发送消息),才使用这个标志。

使用SMTO_BLOCK可能会产生死锁情况,直到等待时间期满。

例如,如果你的线程向另外一个线程发送一个消息,而这个线程又需要向你的线程发送消息。

在这种情况下,两个线程都不能继续执行,并且都将永远挂起。

SendMessageTimeout(hWnd,MSG_LOG_MESSAGE,nMsgType,(LPARAM)(LPCTSTR)m_cstrMessage,SMTO_BLOCK,15000,0);

顺便提一下,这个函数在WinUser.h头文件中的原型是不正确的。

这个函数只被原型化返回一个BOOL型值,因为LRESULT实际是通过函数的一个参数返回的。

这会引起一些问题,因为如果对函数传递一个无效的窗口句柄或者等待超时,SendMessageTimeout都会返回FALSE。

要知道函数失败详细情况的唯一办法是调用GetLastError。

如果函数是由于等待超时而失败,则GetLastError为0(ERROR_SUCCESS)。

如果对参数传递了一个无效句柄,GetLastError为1400(ERROR_INVALID_WINDOW_HANDLE)。

1.4唤醒一个线程

当一个线程调用GetMessage或WaitMessage,但没有对这个线程或这个线程所建立窗口的消息时,系统可以挂起这个线程,这样就不再给它分配CPU时间。

当有一个消息登记(投送)或发送到这个线程,系统要设置一个唤醒标志,指出现在要给这个线程分配CPU时间,以便处理消息。

正常情况下,如果用户不按键或移动鼠标,就没有消息发送给任何窗口。

这意味着系统中大多数线程没有被分配给CPU时间。

1.4.1队列状态标志

DWORDGetQueueStatus(UINTfuFlags);

查询其队列标志。

从线程队列提取消息的算法

当一个线程调用GetMessage或PeekMessage时,系统必须检查线程的队列状态标志的情况,并确定应该处理哪个消息。

1)如果QS_SENDMESSAGE标志被设置,系统向相应的窗口过程发送消息。

GetMessage或PeekMessage函数在内部进行这种处理,并且在窗口过程处理完消息之后不返回到线程,这些函数要等待其他要处理的消息。

2)如果消息在线程的登记消息队列中,函数GetMessage或PeekMessage填充传递给它们的MSG结构,然后函数返回。

这时,线程的消息循环通常调用DispatchMessage,让相应的窗口过程来处理消息。

3)如果QS_QUIT标志被设置。

GetMessage或PeekMessage返回一个WM_QUIT消息(其中wParam参数是规定的退出代码)并复位QS_QUIT标志。

4)如果消息在线程的虚拟输入队列,函数GetMessage或PeekMessage返回硬件输入消息。

5)如果QS_PAINT标志被设置,GetMessage或PeekMessage为相应的窗口返回一个WM-PAINT消息。

6)如果QS_TIMER标志被设置,GetMessage或PeekMessage返回一个WM_TIMER消息。

设计这样顺序的想法:

所以如果按鼠标按钮,处理WM_LBUTTONDOWN消息的窗口可能向不同的窗口投送三个消息。

由于是硬件事件引发三个软件事件,所以系统要在读取用户的下一个硬件事件之前,先处理这些软件事件。

这也说明了为什么登记消息队列要在虚拟输入队列之前检查。

这种事件序列的一个很好的例子是调用TranslateMessage函数。

这个函数检查是否有一个WM_KEYDOWN或一个WM_SYSKEYDOWN消息从输入队列中取出。

如果有一个这样的消息被取出,系统检查虚键(virtualkey)信息是否能转换成等价的字符。

如果虚键信息能够转换,TranslateMessage调用PostMessage将一个WM_CHAR消息或一个WM_SYSCHAR消息放置在登记消息队列中。

下次调用GetMessage时,系统首先检查登记消息队列中的内容,如果其中有消息存在,从队列中取出消息并将其返回。

返回的消息将是WM_CHAR消息或WM_SYSCHAR消息。

再下一次调用GetMessage时,系统检查登记消息队列,发现队列已空。

系统再检查输入队列,在其中找到WM_(SYS)KEYUP消息。

GetMessage返回这个消息。

由于系统是按这种方式工作,下面的硬件事件序列WM_KEYDOWN、WM_KEYUP向窗口过程生成如下的消息序列(假定虚键信息可以转换成等价的字符):

wm_keydown----wm_char------wm_keyup

postQuitmessage(0);

设置QS_QUIT;

在低内存(lowmemory)情况下,投送一个消息有可能失败。

如果一个程序想退出,它应该被允许退出,即使是在低内存的情况下。

第二个理由是使用标志可使线程在线程的消息循环结束前完成对所有其他登记消息的处理。

例如对下面的代码段,WM_USER消息将先于WM_QUIT消息从队列中取出,尽管WM_USER消息是在调用PostQuitMessage之后登记到队列中的。

CaseWM_CLOSE:

Postquitmessage(0);

Postmessage(hwnd,wm_user,0,0);

最后两个消息是WM_PAINT和WM_TIMER。

因为画屏幕是一个慢过程,所以WM_PAINT消息的优先级低。

如果每当窗口变得无效时就发送一个WM_PAINT消息,系统运行就会太慢。

在键盘输入之后放置WM_PAINT消息,系统会运行得很快。

例如,选择一个调用对话框的菜单项,从框中选定一个项,在对话框出现在屏幕上之前一直按Enter。

如果你的按键速度足够快,按键消息总是先于任何WM_PAINT消息从队列中取出。

当按Enter接受对话框的选项,对话框窗口被清除,系统复位QS_PAINT标志。

最后一个消息WM_TIMER,比WM_PAINT的优先级还低。

为理解这一点,想一想有一个程序用每个WM_TIMER消息来更新它的显示画屏。

如果定时器消息来的太快,则显示画屏就没有机会重画自己。

在WM_TIMER消息之前先处理WM_PAINT消息,就可以避免这个问题,程序总能更新它的显示画屏。

1.4.2使用内核对象或者队列状态标志来唤醒一个线程

GetMessage或PeekMessage函数导致一个线程睡眠,直到该线程需要处理一个与用户界面(UI)相关的任务。

有时候,若能让线程被唤醒去处理一个与UI有关的任务或其他任务,就会许多方便。

例如,一个线程可能启动一个长时间运行的操作,并可以让用户取消这个操作。

这个线程需要知道何时操作结束(与UI无关的任务),或用户是否按了Cancel按钮(与UI相关的任务)来结束操作。

一个线程可以调用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx函数,使线程等待它自已的消息。

该函数的特点是它不但可以等待内核对象,还可以等消息。

也就是当有消息到来时,该函数也一样可以返回,并处理消息,这样就给了工作线程退出的机会。

DWORDMsgWaitForMultipleObjects(

DWORDnCount,//要等待的内核对象数目

LPHANDLEpHandles,//要等待的内核对象句柄数组指针

BOOLfWaitAll,//是等待全部对象还是单个对象

DWORDdwMilliseconds,//等待时间 

DWORDdwWakeMask);

//等待的消息类型

下面就详解一下该函数的参数使用方法:

DWORDnCount:

要等待的内核对象的数目。

如果等待两个线程退出,则nCount=2;

LPHANDLEpHandles:

要等待的内核对象句柄数组指针。

如果只要等待一个线程退出,则直接设置该线程句柄的指针即可:

MsgWaitForMultipleObjects(1,&

m_pThread->

m_hThread,…)

如果要等待两个线程退出,则使用方法为:

HANDLEhArray[2]={m_pThread1->

m_hThread,m_pThread2->

m_hThread};

MsgWaitForMultipleObjects(2,hArray,…)

BOOLfWaitAll:

TRUE-表示只有要等待的线程全部退出后,此函数才返回,FALSE-表示要等待的线程中任意一个退出了,或是有消息到达了,此函数均会返回。

我要等待一个线程退出,将fWaitAll设置为

FALSE,目的是无论是线程真的退出了,还是有消息到达了,该函数都能返回。

如果将该fWaitAll设置为TRUE,那么函数返回的唯一条件是线程退出了,即便是有消息到来了,该函数也一样不会返回。

DWORDdwMilliseconds:

等待的事件,单位是毫秒。

可以设置为INFINITE,无穷等待

DWORDdwWakeMask:

等待的消息类型,通常可以设置为QS_ALLINPUT。

此宏表示的是可以等待任意类型的消息。

当然,也可以指定等待的消息类型。

#defineQS_ALLINPUT 

(QS_INPUT 

|\

QS_POSTMESSAGE 

QS_TIMER 

QS_PAINT 

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 高等教育 > 理学

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2