找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 7177|回复: 3
打印 上一主题 下一主题
收起左侧

windows创建窗口与消息处理的全过程解析 超详细

[复制链接]
跳转到指定楼层
楼主
ID:102668 发表于 2016-1-16 07:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本文中51黑将向你详细介绍,在我们天天都要用的windows操作系统里面,一个窗口究竟是怎么产生和工作的.

1. 窗口类参数的意义
要WinMain登记窗口类,首先要填写一个WNDCLASS结构,其定义如下所示:

typedef struct_WNDCLASSA
   {
     UINT style ;         //窗口类风格
     WNDPROC lpfnWndProc ;    //指向窗口过程函数的指针
     int cbClsExtra ;       //窗口类附加数据
     int cbWndExtra;       //窗口附加数据
     HINSTANCE hInstance ;    //拥有窗口类的实例句柄
     HICON hIcon ;        //最小窗口图标
     HCURSOR hCursor ;      //窗口内使用的光标
     HBRUSH hbrBackground ;   //用来着色窗口背景的刷子
     LPCSTR lpszMenuName;    //指向菜单资源名的指针
     LPCSTR lpszClassName ;   // 指向窗口类名的指针
   }
其中参数的意义如下:
(1)第一个参数:成员style控制窗口的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.也可把sytle设为0.本程序中为wc.style=CS_HREDRAW | CS_VREDRAW,它表示当窗口的纵横坐标发生变化时要重画整个窗口。你看:无论你怎样拉动窗口的大小,那行字都会停留在窗口的正中部,而假如把这个参数设为0的话,当改动窗口的大小时,那行字则不一定处于中部了。
(2)第二个参数:lpfnWndProc包括一个指向该窗口类的消息处理函数的指针,此函数称为窗口过程函数。它将接收Windows发送给窗口的消息,并执行相应的任务。其原型为:
long FAR PASCAL WndProc(HWND,unsigned,WORD,LONG);并且必须在模快定义中回调它。WndProc是一个回调函数(见第五节),如果暂时无法理解这个模糊的概念意味着什么,可先放过,等到讲消息循环时再详谈。
(3)第三,四个参数:cbWndExtra域指定用本窗口类建立的所有窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。
(4)第五个参数:hInstance域标识应用程序的实例hInstance,当然,实例名是可以改变的。wc.hInstance= hInstance ;这一成员可使Windows连接到正确的程序。
(5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。
(6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.
(7)第八个参数:wc.hbrBackground域决定Windows用于着色窗口背景的刷子颜色,函数GetStockObject返回窗口的颜色,本程序中返回的是白色,你也可以把它改变为红色等其他颜色.试试看
(8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,所以为NULL。
(9)第十个参数:lpszClassName指定了本窗口的类名。

注册窗口类如下:
if (!RegisterClass (&wc)) {
       MessageBox (NULL, TEXT ("This program requiresWindows NT!"),
               szAppName,MB_ICONERROR) ;
       return 0 ;
}


-------------------2.创建显示更新窗口-------------------------


                         注册窗口类后,就可以创建窗口了,本程序中创建窗口的有关语句如下:
hwnd =CreateWindow (szAppName,                    //window class name
                          TEXT("欢迎你的到来!"),    //window caption
                          WS_OVERLAPPEDWINDOW,        //window style
                          CW_USEDEFAULT,              //initial x position
                          CW_USEDEFAULT,              //initial y position
                          CW_USEDEFAULT,              //initial x size
                          CW_USEDEFAULT,              //initial y size
                          NULL,                       //parent window handle
                          NULL,                       //window menu handle
                          hInstance,                  //program instance handle
                          NULL);                     //creation parameters
参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。
参数2:用来表明窗口的标题。
参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。
参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。
参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
最后一个参数是附加数据,一般都是0。
CreateWindow()的返回值是已经创建的窗口的句柄,应用程序使用这个句柄来引用该窗口。如果返回值为0,就应该终止该程序,因为可能某个地方出错了。如果一个程序创建了多个窗口,则每个窗口都有各自不同的句柄.

API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API函数ShowWindows.形式为:ShowWindow(hwnd, iCmdShow);
其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化(SW_SHOWMAXIMIZED)。WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。你可把iCmdShow改变为这些参数试试。
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow(hwnd),最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.


-----------------3.消息循环------------------------


                        主窗口显示出来了,WinMain就开始处理消息了,怎么做的呢?
Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。应用程序又是怎么来接收这个消息呢?这就讲讲消息循环了。
应用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为"消息循环"。这段循环代码是什么呢?好,往下看:
在咱们的第二只小板凳中,这段代码就是:
......
MSG msg; //定义消息名
while(GetMessage (&msg, NULL, 0,0))
     {
          TranslateMessage(&msg) ; //翻译消息
          DispatchMessage(&msg) ; //撤去消息
     }
     returnmsg.wParam ;
MSG结构在头文件中定义如下:
typedef structtagMSG
{
HWND hwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORD time;
POINT pt;
} MSG,*PMSG;
MSG数据成员意义如下:
参数1:hwnd是消息要发送到的那个窗口的句柄,这个窗口就是咱们用CreateWindows函数创建的那一个。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。
参数2:message是一个数字,它唯一标识了一种消息类型。每种消息类型都在Windows文件中定义了,这些常量都以WM_开始后面带一些描述了消息特性的名称。比如说当应用程序退出时,Windows就向应用程序发送一条WM_QUIT消息。
参数3:一个32位的消息参数,这个值的确切意义取决于消息本身。
参数4:同上。

参数5:消息放入消息队列中的时间,在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。
参数6:消息放入消息队列时的鼠标坐标.
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
GetMessage(&msg,NULL,0,0),第一个参数是要接收消息的MSG结构的地址,第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;第三,四参数指定消息范围。后面三个参数被设置为默认值,这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。均为NULL时就表示获取所有消息。
消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来说它并不起什么作用,所以现在没有必要考虑它。
下一个函数调用DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。我们在讲到登记窗口类时曾提到过,登记窗口类时,我们曾指定Windows把函数WindosProc作为咱们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。

Windows是一种非剥夺式多任务操作系统。只有的应用程序交出CPU控制权后,Windows才能把控制权交给其他应用程序。当GetMessage函数找不到等待应用程序处理的消息时,自动交出控制权,Windows把CPU的控制权交给其他等待控制权的应用程序。由于每个应用程序都有一个消息循环,这种隐式交出控制权的方式保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。
当WinMain函数把控制返回到Windows时,应用程序就终止了。应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册,每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息。
但是,一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。
虽然WinMain指定了返回值的数据类型,但Windows并不使用返回值。不过,在调试一应用程序时,返回值地有用的。通常,可使用与标准C程序相同的返回值约定:0表示成功,非0表示出错。PostQuitMessage函数允许窗口函数指定返回值,这个值复制到WM_QUIT消息的wParam参数中。为了的结束消息循环之后返回这个值,我们的第二只小板凳中使用了以下语句:
return msg.wParam ; //表示从PostQuitMessage返回的值
例如:当Windows自身终止时,它会撤消每个窗口,但不把控制返回给应用程序的消息循环,这意味着消息循环将永远不会检索到WM_QUIT消息,并且的循环之后的语句也不能再执行。Windows的终止前的确发送一消息给每个应用程序,因而标准C程序通常会的结束前清理现场并释放资源,但Windows应用程序必须随每个窗口的撤消而被清除,否则会丢失一些数据。




---------------------4.窗口过程,窗口过程函数-------------------------------------


       如前所述,函数GetMessage负责从应用程序的消息队列中取出消息,而函数DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。然后出台的就是这个窗口过程了,这个窗口过程的任务是干什么呢?就是最终用来处理消息的,就是消息的处理器而已,那么这个函数就是WindowProc,在VisualC++6.0中按F1启动MSDN,按下面这个路径走下来:

PlatFormSDK-->UserInterface services-->Windowsuser Interface-->Windowing-->WindowProcedures-->WindowProcedureReference-->WindowsProcedureFunctions-->WindowProc
啊,太累了,不过我们终于的MSDN中找到了这个函数,前几次我讲解这些API函数的时候,都是的知道的情况下搜索出来的,所以没有详细给出每个函数的具体位置,而这次我却是一点点去找的,还好,没被累死,体会到MSDN的庞大了吧,不过我用的是MSDN2000,是D版的,三张光盘装。你用的MSDN如果按这个路径走下去的话,可能会找不到,不过我想大致也是在这个位置了,找找看!!!
LRESULT CALLBACKWindowProc
(
HWNDhwnd, // handle towindow
UINTuMsg, // messageidentifier
WPARAMwParam, // first messageparameter
LPARAMlParam // second messageparameter
);
这个函数这里被我们称为WndProc.
下面讲解:
不知你注意到了没有,这个函数的参数与刚刚提到的GetMessage调用把返回的MSG结构的前四个成员相同。如果消息处理成功,WindowProc的返回值为0.
Windows的启动应用程序时,先调用WinMain函数,然后调用窗口过程,注意:在我们的这个程序中,只有一个窗口过程,实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都有一个与之相对应的窗口过程。无论Windows何时想传递一个消息到一窗口,都将调用相应的窗口过程。当Windows从环境,或从另一个应用程序,或从用户的应用程序中得到消息时,它将调用窗口过程并将信息传给此函数。总之,窗口过程函数处理所有传送到由此窗口类创建的窗口所得到的消息。并且窗口过程有义务处理Windows扔给它的任何消息。我们在学习Windows程序设计的时候,最主要的就是学习这些消息是什么以及是什么意思,它们是怎么工作的。
令我们不解的是,在程序中我们看不出来是哪一个函数在调用窗口过程。它其实是一个回调函数.前面已经提到,Windows把发生的输入事件转换成输入消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通信。
回调函数是输出函数中特殊的一种,它是指那些在Windows环境下直接调用的函数。一个应用程序至少有一个回调函数,因为在应用程序处理消息时,Windows调用回调函数。这种回调函数就是我们前面提到的窗口过程,它对对应于一个活动的窗口,回调函数必须向Windows注册,Windows实施相应操作即行回调。
每个窗口必须有一个窗口过程与之对应,且Windows直接调用本函数,因此,窗口函数必须采用FARPASCAL调用约定。在我们的第二只小板凳中,我们的窗口函数为WndProc,必须注意这里的函数名必须是前面注册的窗口类时,向域wc.lpfnWndProc所赋的WndProc。函数WndProc就是前面定义的窗口类所生成的所有窗口的窗口函数。
在我们的这个窗口函数中,WndProc处理了共有两条消息:WM_PAINTWM_DESTROY.
窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。窗口过程检查一条消息,然后根据这些消息执行特定的动作未被处理的消息通过DefWindowProc函数传回给Windows作缺海上处理。
可以发送窗口函数的消息约有220种,所有窗口消息都以WM_开头,这些消息在头文件中被定义为常量。引起Windows调用窗口函数的原因有很多,,如改变窗口大小啊,改变窗口在屏幕上的位置啊什么的。



----------------5.处理消息----------------------------------


              窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句。大多数windowsproc都有具有下面形式的内部结构

switch(uMsgId)
{
caseWM_(something):
//这里此消息的处理过程
return0;
caseWM_(something else):
//这里是此消息的处理过程
ruturn0;
default:
//其他消息由这个默认处理函数来处理
returnDefWindowProc(hwnd,uMsgId,wParam,lParam);
}
在处理完消息后,要返回0,这很重要-----它会告诉Windows不必再重试了。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。这个函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,例如,画窗口的非用户区,更新窗口的正文标题等等等等。
再提示一下,以WM_的消息在Windows头文件中都被定义成了常量,如WM_QUIT=XXXXXXXXXXX,但我们没有必要记住这个数值,也不可能记得住,我们只要知道WM_QUIT就OK了。
在第二只小板凳中我们只让窗口过程处理了两个消息:一个是WM_PAINT,另一个是WM_DESTROY,先说说第一个消息---WM_PAINT.
关于WM_PAINT:
无论何时Windows要求重画当前窗口时,都会发该消息。也可以这样说:无论何时窗口非法,都必须进行重画。哎呀,什么又是"非法窗口"?什么又是重画啊?
在MSDN里面找有关这个方面的内容
Platform SDK-->Graphics and Multimedia Services-->Windows GDI-->Painting and Drawing-->Using the WM_PAINTMessage-----终于找到了。
下面是一大套理论:
让我们把Windows的屏幕想像成一个桌面,把一个窗口想像成一张纸。当我们把一张纸放到桌面上时,它会盖住其他的纸,这样被盖住的其他纸上的内容都看不到了。但我们只要把这张纸移开,被盖住的其他纸上的内容就会显示出来了---这是一个很简单的道理,谁都明白。
对于我们的屏幕来说,当一个窗口被另一窗口盖住时,被盖住的窗口的某些部分就看不到了,我们要想看到被盖住的窗口的全部面貌,就要把另一个窗口移开,但是当我们移开后,事情却起了变化-----很可能这个被盖住的窗口上的信息被擦除了或是丢失了。当窗口中的数据丢失或过期时,窗口就变成非法的了---或者称为"无效"。于是我们的任务就来了,我们必须考虑怎样在窗口的信息丢失时"重画窗口"--使窗口恢复成以前的那个样子。这也就是我们在这第二只小板凳中调用UpdateWindow的原因。
你忘记了吗?刚才我们在(三)显示和更新窗口 中有下面的一些文字:
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.---这是程序第一次调用了这条消息。
为重新显示非法区域,Windows就发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有改变窗口大小,对话框关闭,使用了UpdateWindows和ScrollWindow函数等。这里注意,Windows并非是消息WM_PAINT的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息......
通常情况下用BeginPaint()来响应WM_PAINT消息。如果要在没有WM_PAINT的情况下重画窗口,必须使用GetDC函数得到显示缓冲区的句柄。这里面不再扩展。详细见MDSN。
这个BeginPaint函数会执行准备绘画所需的所有步骤,包括返回你用于输入的句柄。结束则是以EndPaint();
在调用完BeginPaint之后,WndProc接着调用GetClientRect:
GetClientRect(hwnd,&rect);
第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT类型的结构。查MSDN,可看到这个结构有四个成员。
WndProc做了一件事,他把这个RECT结构的指针传送给了DrawText的第四个参数。函数DrawText的目的就是在窗口上显示一行字----"你好,欢迎你来到VC之路!",有关这个函数的具体用法这里也没必要说了吧。
关于WM_DESTROY
这个消息要比WM_PAINT消息容易处理得多:只要用户关闭窗口,就会发送WM_DESTROY消息(在窗口从屏幕上移去后)。
程序通过调用PostQuitMessage以标准方式响应WM_DESTROY消息:
PostQuitMessage (0) ;
这个函数在程序的消息队列中插入一个WM_QUIT消息。(四)创建消息循环中我们曾有这么一段话:
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
.......
在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。




---------------------还有一个对本文的总结请看51黑2楼----------------------------------------



分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:102668 发表于 2016-1-16 07:09 | 只看该作者
                        用SDK创建窗口的过程,是程序和Windows交互的过程。创建过程的每一步都要用到Windows API。
第一步创建窗口类:即定义一个WNDCLASS对象,然后填充其内的变量。如:
WNDCLASS wc;
wc.style=CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   =WndProc;//指向窗口过程函数的指针
......
定义好后,进入第二步注册窗口类:即调用Windows APIRegisterClass(&wc)即可注册窗口类wc。
第三步创建窗口:这一步要用到CreateWindow(......)它返回一个窗口ID句柄。我们用HWND类型变量获得该值。以后要用到该窗口,将该变量导入即可。
第四步显示和更新窗口:即调用 ShowWindow (hwnd, iCmdShow) 和UpdateWindow(hwnd)显示和更新和hwnd对应的窗口。iCmdShow告诉它如何显示这个窗口。
第五步创建循环消息:
while (GetMessage (&msg, NULL,0, 0))//从消息队列中取出一条消息
{
  TranslateMessage (&msg);//进行消息(如键盘消息)转换
  DispatchMessage (&msg);//分派消息到窗口的回调函数处理,(OS调用窗口回调函数进行处理)。
}
这里调用了三个API,注释已经写得很清楚了。其中DispatchMessage调用的回调函数即是我们上面在创建窗口类时定义的wc.lpfnWndProc  = WndProc,WndProc(......)的这个语句中被定义为回调函数。我们一会还得定义WndProc(......)。循环体退出的条件是当GetMessage()取出的消息是WM_QUIT时。其它消息则会进行处理。
第六步终止应用程序:
return msg.wParam ; //表示从PostQuitMessage返回的值
第七步完成回调函数(WndProc):
回调函数定义如下:
LRESULT CALLBACK WndProc (HWND hwnd,
                         UINT message,
                         WPARAM wParam,
                         LPARAM lParam)
{     switch (message)
      {case WM_XXXXX:
          //相应的处理方法
          return x;
        ......
      }   
      return DefWindowProc (hwnd, message, wParam, lParam) ;
}
完成...
                                                                       
回复

使用道具 举报

板凳
ID:106732 发表于 2017-4-5 10:42 | 只看该作者
写得太好了,谢谢楼主分享,谢谢
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表