本帖最后由 Xor0ne 于 2021-1-6 20:25 编辑
SDK之路第一天_0106 - 1.预备知识
- 2.Winmain
- 3.Windows消息机制
- 3.1.Dos的过程驱动与Windows的事件驱动
- 消息(Message)的概念和表示:
- 3.2.Windows编程的特点:
- 3.2.1.注册窗口类
- 3.2.2.创建窗口
- 3.2.3.显示和更新窗口
- 3.2.4.创建消息循环
- Msg消息结构
- 3.2.5.终止应用程序
- 3.2.6.窗口过程,窗口过程函数
- 3.2.7.处理消息
- (1).关于WM_PAINT
- (2).关于WM_DESTROY
-
- 4.附件:关于第二个小板凳的注释
- 总结
(论坛不能自动生成目录,所以只能把我本地的目录复制过来了,有点丑,还请读者见谅!本文大部分内容转载于SDK之路文集,其中一些个人不是很理解的地方稍加了注释!)
1.预备知识1.1.API:全称application program interface,意思是应用程序编程接口(说起API并不仅仅指windows而言,windows支持的API叫winapi)。winapi就是应用程序和windows之间通讯的一个编程界面。windows提供了上千个API函数,以方便程序员来编写应用程序。
1.2.WinSDK编程:WinSDK程序设计就是API方式的windows程序设计。SDK,全称Software Developers Kit,意思是软件开发工具箱。它是在windows程序设计早期,程序员进行windows程序设计所必须购买的一个软件包(不知道那时候有没有D版的)。
1.3.MFC:MFC,全称Microsoft Foundation Classes,微软把WinAPI进行封装的类库。它是一个类的集合,通过覆盖WinAPI,为编程提供了一个面向对象的界面。它使windows程序员能够利用C++面象对象的特性进行编程,类似BCB的OWL,Delphi的VCL组件。它把那些进行SDK编程时最繁琐的部分提供给程序员,使之专注于功能的实现。
1.4.句柄(handle):在标准C库中句柄用来对文件输入输出。代码示例如下: - int handle;
- handle=open("filename","r")
- if(handle)
- {
- read(handle,block,bytesToread);
- }
- close(handle);
复制代码
在文件被成功打开后,open()返回一个句柄,在read()中使用这个句柄来阅读这个文件。句柄不是指针。程序不能直接使用句柄来阅读文件中的信息。如果不能把它传送给输入输出函数调用的话,句柄就没有用了。句柄不返回零。句柄命名以h开始。这是匈牙利表示法的规定。
1.5.实例:在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。
1.6.消息MSG: 在传统的C程序中,我们调用fopen函数打开文件。这个库函数最终调用操作系统(提供的函数)来打开文件。而在Windows中,不仅用户程序可以调用系统的API函数,反过来,系统也会调用用户程序,这个调用是通过消息来进行的。
2.Winmain
【示例程序1】第一只小板凳:打开"an empty project"后,在VC的IDE中,打开文本编辑器。输入以下内容: - #include "stdafx.h"
- int APIENTRY WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR lpCmdLine,
- int nCmdShow)
- {
- // TODO: Place code here.
- MessageBox (NULL, TEXT ("你好,欢迎来到VC之路"), TEXT ("欢迎"), 0) ;
- return 0;
- }
复制代码
WinMain() 第一个参数:应用程序的当前实例句柄。 第二个参数:应用程序的前一个实例句柄,别管它,对于Win32位而言,它一般是NULL. 第三个参数:指向任何传给程序的命令行参数。PSTR代表"指向字符串的指针"。 第四个参数:它告诉应用程序如何初始化窗口,如最大化,最小化等状态。
WinMain()所起的作用:初始化,展示,销毁应用程序等。
【示例程序2】第二只小板凳 打开VC,file-->new-->progects-->win32 application,并在project name 中输入hello.顺路走下来,选择第二项。在编辑器中输入以下程序覆盖向导产生的代码: - // testsdk02.cpp : Defines the entry point for the application.
- //
- #include "stdafx.h" //注意,这个向导产生的头文件不能去掉
- #include <windows.h>
- LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
-
- int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
- PSTR szCmdLine, int iCmdShow)
- {
- static TCHAR szAppName[] = TEXT ("HelloWin") ;
- HWND hwnd ;
- MSG msg ;
- WNDCLASS wc ;
-
- wc.style = CS_HREDRAW | CS_VREDRAW ;
- wc.lpfnWndProc = WndProc ;
- wc.cbClsExtra = 0 ;
- wc.cbWndExtra = 0 ;
- wc.hInstance = hInstance ;
- wc.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
- wc.hCursor = LoadCursor (NULL, IDC_ARROW) ;
- wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
- wc.lpszMenuName = NULL ;
- wc.lpszClassName = szAppName ;
-
- if (!RegisterClass (&wc))
- {
- MessageBox (NULL, TEXT ("This program requires Windows NT!"),
- szAppName, MB_ICONERROR) ;
- return 0 ;
- }
- 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
-
- ShowWindow (hwnd, iCmdShow) ;
- UpdateWindow (hwnd) ;
-
- while (GetMessage (&msg, NULL, 0, 0))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- return msg.wParam ;
- }
-
-
- LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- HDC hdc ;
- PAINTSTRUCT ps ;
- RECT rect ;
- switch (message)
- {
- case WM_PAINT:
- hdc = BeginPaint (hwnd, &ps) ;
- GetClientRect (hwnd, &rect) ;
- DrawText (hdc, TEXT ("你好,欢迎你来到VC之路!"), -1, &rect,
- DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
- EndPaint (hwnd, &ps) ;
- return 0 ;
-
- case WM_DESTROY:
- PostQuitMessage (0) ;
- return 0 ;
- }
- return DefWindowProc (hwnd, message, wParam, lParam) ;
- }
复制代码
3.Windows消息机制
3.1.Dos的过程驱动与Windows的事件驱动
DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。 Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
拓展:
Windows程序设计是一种事件驱动方式的程序设讨模式,主要是基于消息的。例如,当用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到这个事件。于是将这个事件包装成一个消息(这就是事件和消息的关系),投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”实际上是操作系统调用程序中-个专门负责处理消息的函数,这个函数称为窗口过程。也就是说,窗口过程函数是由操作系统发给应用程序的。具体过程图如下:
消息(Message)的概念和表示:消息(Message)指的就是Windows 操作系统发给应用程序的一个通告,它告诉应用程序某个特定的事件发生了。比如,用户单击鼠标或按键都会引发Windows 系统发送相应的消息。最终处理消息的是应用程序的窗口函数,如果程序不负责处理的话系统将会作出默认处理。从数据结构的角度来说,消息是一个结构体,它包含了消息的类型标识符以及其他的一些附加信息。关于msg的结构体形式请参见3.2.4的讲解。
3.2.Windows编程的特点:
C语言编程至少有一个主程序,其名字是main()。Windows程序则至少两个主程序,一个是WinMain(), - int WINAPI WinMain(HINSTANCE hInstance, // handle to current instance
- HINSTANCE hPrevInstance, // handle to previous instance
- LPSTR lpCmdLine, // command line
- int nCmdShow // show state
- );
复制代码
另一个是窗口过程函数WndProc,它的函数原型为: - long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
复制代码
Windows应用程序的编程就围绕这两个部份进行的。其中WinMain函数为应用程序的入口点,它的名字一定要是WinMain。
在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WndProc。在dos里,程序能直接控制事件的发生顺序,结果等。而在Windows里,应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。WndProc是一个主回调函数,Windows至少有一个回调函数。
实例:在Windows中,能多次同时运行同一个应用程序,即运行多个副本,每个副本叫做一个“实例”。
下面把【示例程序2】支解为四块:(一)建立,注册窗口类.(二)创建窗口.(三)显示和更新窗口.(四)创建消息循环.(五)终止应用程序.(六)窗口过程.(七)处理消息. 3.2.1.注册窗口类
(1)建立窗口类 WinMain()是程序的入口,它相当于一个中介人的角色,把应用程序(指小窗口)介绍给windows.首要的一步是登记应用程序的窗口类.
窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中所有窗口消息的窗口函数.只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口.创建窗口时,还可以指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在建立窗口类后,必须向Windows登记.
建立窗口类就是用WNDCLASS结构定义一个结构变量,在这个程序中就是指 WNDCLASS wc ;然后用自己设计的窗口属性的信息填充结构变量wc的域. 要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 ; // 指向窗口类名的指针
- }
复制代码
在VC6.0里面,把光标定位在WNDCLASS上,按F1,即可启动MSDN,在MSDN里你可看到这个结构原形.在下节讲解这些参数在本程序中的具体用法.
(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指定了本窗口的类名。
(2)注册窗口类 当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在创建窗口之前,是必须要注册窗口类的,注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数RegisterClass返回0值,也只能返回0值,因为注册不成功,程序已经不能再进行下去了 示例程序2中的注册窗口类如下: - //当对WNDCLASS结构域一一赋值后,注册窗口类,调用的API函数为RegisterClass
- if (!RegisterClass (&wc))
- {
- MessageBox (NULL, TEXT ("This program requires Windows NT!"),
- szAppName, MB_ICONERROR) ;
- return 0 ;
- }
复制代码
3.2.2.创建窗口注册窗口类后,就可以创建窗口了,示例程序2中创建窗口的有关语句如下:
- hwnd = CreateWindow (szAppName, // 1.window class name
- TEXT ("欢迎你的到来!"), // 2.window caption
- WS_OVERLAPPEDWINDOW, // 3.window style
- CW_USEDEFAULT, // 4.initial x position
- CW_USEDEFAULT, // 5.initial y position
- CW_USEDEFAULT, // 6.initial x size
- CW_USEDEFAULT, // 7.initial y size
- NULL, // 8.parent window handle
- NULL, // 9.window menu handle
- hInstance, // 10.program instance handle
- NULL) ; // 11.creation parameters
复制代码
CreateWindow()的返回值是已经创建的窗口的句柄,应用程序使用这个句柄来引用该窗口。如果返回值为0,就应该终止该程序,因为可能某个地方出错了。如果一个程序创建了多个窗口,则每个窗口都有各自不同的句柄.
参数1:登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。 参数2:用来表明窗口的标题。 参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。 参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。 参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。 参数8: 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。 参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。 最后一个参数是附加数据,一般都是0。
3.2.3.显示和更新窗口
API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API函数ShowWindows.形式为: - ShowWindow (hwnd, iCmdShow);
复制代码
其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化(SW_SHOWMAXIMIZED)。WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。你可把iCmdShow改变为这些参数试试。
WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.
3.2.4.创建消息循环
主窗口显示出来了,WinMain就开始处理消息了,怎么做的呢?
Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。应用程序又是怎么来接收这个消息呢?这就讲讲消息循环了。
应用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为"消息循环"。这段循环代码是什么呢?好,往下看:
在程序示例二中的代码如下所示:
- <blockquote><span style="font-size: 14px;">MSG msg; //定义消息名</span>
复制代码
拓展:
TranslateMessage函数:如果线程需要接受键盘字符的输入。每次用户按下一个键,该系统产生相应的虚拟键消息(WM_KEYDOWN和WM_KEYUP)。虚拟键消息包含一个虚拟键码,标识的是被按下的键,而不是它相关的字符值。要获得此值,消息循环必须包含TranslateMessage,用来将虚拟键消息翻译成字符消息(WM_CHAR)的并放到应用程序的消息队列里。经过若干次循环后,WM_CHAR消息会被并派发到一个窗口。
DispatchMessage函数将消息发送到到与MSG结构中的窗口句柄关联的窗口。如果窗口句柄是HWND_TOPMOST,DispatchMessage则将消息发送到操作系统所有的顶层窗口。如果窗口句柄是NULL,DispatchMessage不做任何事。一个应用程序的主线程初始化后,系统就启动应用程序的消息循环,并创造至少一个窗口。一旦启动,消息循环持续从该线程的消息队列中删除消息,并派发他们到相应的窗口。GetMessage函数从消息列表中获取到WM_QUIT消息时,消息循环结束。
一个消息队列只需要一个消息循环,即使一个应用程序包含有多个窗口。 DispatchMessage总是调度消息到正确的窗口,这是因为每个队列中的消息是MSG结构,它包含着消息所属的窗口的句柄。
Msg消息结构
MSG结构在头文件中的定义如下: - typedef struct tagMSG
- {
- HWND hwnd; //消息要发送到的那个窗口的句柄
- UINT message; //唯一标识了一种消息类型
- WPARAM wParam; //32位的消息参数,这个值的确切意义取决于消息本身。
- LPARAM lParam; //同上
- 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()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。
3.2.5.终止应用程序
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应用程序必须随每个窗口的撤消而被清除,否则会丢失一些数据。
3.2.6.窗口过程,窗口过程函数
如前所述,函数GetMessage负责从应用程序的消息队列中取出消息,而函数DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。然后出台的就是这个窗口过程了,这个窗口过程的任务是干什么呢?就是最终用来处理消息的,就是消息的处理器而已,那么这个函数就是WindowProc,在Visual C++6.0中按F1启动MSDN,按下面这个路径走下来:
PlatForm SDK****-->User Interface services-->Windows user Interface-->Windowing-->Window Procedures-->Window Procedure Reference-->Windows Procedure Functions-->WindowProc
- LRESULT CALLBACK WindowProc
- (
- HWND hwnd, // handle to window
- UINT uMsg, // message identifier
- WPARAM wParam, // first message parameter
- LPARAM lParam // second message parameter
- );
复制代码
下面讲解:
不知你注意到了没有,这个函数的参数与刚刚提到的GetMessage调用把返回的MSG结构的前四个成员相同。如果消息处理成功,WindowProc的返回值为0. Windows的启动应用程序时,先调用WinMain函数,然后调用窗口过程,注意:在我们的这个程序中,只有一个窗口过程,实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都 有一个与之相对应的窗口过程。无论Windows何时想传递一个消息到一窗口,都将调用相应的窗口过程。当Windows从环境,或从另一个应用程序,或从用户的应用程序中得到消息时,它将调用窗口过程并将信息传给此函数。总之,窗口过程函数处理所有传送到由此窗口类创建的窗口所得到的消息。并且窗口过程有义务处理Windows扔给它的任何消息。我们在学习Windows程序设计的时候,最主要的就是学习这些消息是什么以及是什么意思,它们是怎么工作的。
令我们不解的是,在程序中我们看不出来是哪一个函数在调用窗口过程。它其实是一个回调函数.前面已经提到,Windows把发生的输入事件转换成输入消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通信。
回调函数是输出函数中特殊的一种,它是指那些在Windows环境下直接调用的函数。一个应用程序至少有一个回调函数,因为在应用程序处理消息时,Windows调用回调函数。这种回调函数就是我们前面提到的窗口过程,它对对应于一个活动的窗口,回调函数必须向Windows注册,Windows实施相应操作即行回调。
每个窗口必须有一个窗口过程与之对应,且Windows直接调用本函数,因此,窗口函数必须采用FAR PASCAL调用约定。在我们的第二只小板凳中,我们的窗口函数为WndProc,必须注意这里的函数名必须是前面注册的窗口类时,向域wc.lpfnWndProc所赋的WndProc。函数WndProc就是前面定义的窗口类所生成的所有窗口的窗口函数。
在我们的这个窗口函数中,WndProc处理了共有两条消息:WM_PAINT和WM_DESTROY. 窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。窗口过程检查一条消息,然后根据这些消息执行特定的动作未被处理的消息通过DefWindowProc函数传回给Windows的DefWindowProc作缺省处理。 可以发送窗口函数的消息约有220种,所有窗口消息都以WM_开头,这些消息在头文件中被定义为常量。引起Windows调用窗口函数的原因有很多,,如改变窗口大小啊,改变窗口在屏幕上的位置啊什么的。
Windows已经把任务扔给窗口过程了,窗口过程是怎么处理消息的呢? 3.2.7.处理消息
窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句。大多数windows proc都有具有下面形式的内部结构:
- switch(uMsgId)
- {
- case WM_(something):
- //这里此消息的处理过程
- return 0;
- case WM_(something else):
- //这里是此消息的处理过程
- return 0;
- default:
- //其他消息由这个默认处理函数来处理
- return DefWindowProc(hwnd,uMsgId,wParam,lParam);
- }
复制代码
在处理完消息后,要返回0,这很重要-----它会告诉Windows不必再重试了。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。这个函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,例如,画窗口的非用户区,更新窗口的正文标题等等等等。
再提示一下,以WM_的消息在Windows头文件中都被定义成了常量,如WM_QUIT=XXXXXXXXXXX,但我们没有必要记住这个数值,也不可能记得住,我们只要知道WM_QUIT就OK了。
在第二只小板凳(即示例程序2)中我们只让窗口过程处理了两个消息:一个是WM_PAINT,另一个是WM_DESTROY,先说说第一个消息---WM_PAINT.
(1).关于WM_PAINT
无论何时Windows要求重画当前窗口时,都会发该消息。也可以这样说:无论何时窗口非法,都必须进行重画。 哎呀,什么又是"非法窗口"?什么又是重画啊?你这人有没有完,嗯? 稍安勿燥,我比你还烦呢?我午饭到现在还没吃呢!你有点耐心,来点专业精神好不好???我开始在MSDN里面找有关这个方面的内容了,别急,我找找看: Platform SDK-->Graphics and Multimedia Services-->Windows GDI-->ainting and Drawing-->Using the WM_PAINT Message
-----终于找到了。 下面是一大套理论: 让我们把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。
(2).关于WM_DESTROY
这个消息要比WM_PAINT消息容易处理得多:只要用户关闭窗口,就会发送WM_DESTROY消息(在窗口从屏幕上移去后)。 程序通过调用PostQuitMessage以标准方式响应WM_DESTROY消息:
这个函数在程序的消息队列中插入一个WM_QUIT消息。在(四)创建消息循环中我们曾有这么一段话: 消息循环以GetMessage调用开始,它从消息队列中取出一个消息: ....... 在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。
至此,第二支小板凳终于支解完毕!!
个人对于第二个小板凳做的注释:
// #include "stdafx.h" //注意,这个向导产生的头文件不能去掉 #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HelloWin") ; //每一个窗口类在注册时需要指定一个窗口处理函数(Window Procedure),这个函数是一个回调函数,就是用来处理消息的。 HWND hwnd ; //窗口类句柄->要发送到的窗口句柄 MSG msg ; //定义消息名字 WNDCLASS wc ; //【1】建立窗口类-->设计窗口
//设计自己的窗口属性 wc.style = CS_HREDRAW | CS_VREDRAW ; //参数1:窗口类风格 wc.lpfnWndProc = WndProc ; //参数2:指向该窗口类的消息处理函数的指针。窗口处理函数,处理消息 wc.cbClsExtra = 0 ; //参数3.4:窗口类附加数据,指定用本窗口类建立的所有窗口结构分配的额外字节数。 wc.cbWndExtra = 0 ; wc.hInstance = hInstance ; //拥有窗口类的实例句柄hInstance,本成员可使Windows连接到正确的程序。 wc.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; //最小窗口图标 wc.hCursor = LoadCursor (NULL, IDC_ARROW) ; //定义该窗口产生的光标形状 wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; //用来着色窗口背景的刷子 wc.lpszMenuName = NULL ; //指向菜单资源名的指针 wc.lpszClassName = szAppName ; // 指向窗口类名的指针
//当对WNDCLASS结构域一一赋值后,注册窗口类,调用的API函数为RegisterClass if (!RegisterClass (&wc)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; }
//【2】创建窗口 //createwindow函数创建窗口,前提是需要提供一个已注册的窗口类(Window Class) 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->在创建窗口时指定其父窗口,这里没有所以为0 NULL, // window menu handle->指明窗口的菜单 hInstance, // program instance handle->拥有窗口类的实例句柄hInstance NULL) ; // creation parameters->附加数据,一般都是0。
//【3】显示和更新窗口-->产生WM_PAINT消息 ShowWindow (hwnd, iCmdShow) ; /*显示窗口: 调用API函数ShowWindow显示API函数CreateWindow创建完的窗口 参数hwnd是要发送到的窗口句柄,即告诉ShowWindow()显示哪一个窗口 第二个参数则告诉它如何显示这个窗口,即窗口显示的大小控制 */ UpdateWindow (hwnd) ; /*重画窗口: WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。 调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新. */ //【4】创建消息循环-->WinMain不断地取出消息,分配给对应的窗口函数 while (GetMessage (&msg, NULL, 0, 0)) /*从消息队列中取出一个消息 参数msg:要接收消息的MSG结构的地址; 第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息; 第三,四参数指定消息范围。 只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 均为NULL时就表示获取所有消息。 */ { TranslateMessage (&msg) ; /*翻译消息-->将虚拟键消息(输入事件)转换为字符消息.*/ DispatchMessage (&msg) ; /*要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。 将消息发送到到与MSG结构中的窗口句柄关联的窗口。如果窗口句柄是HWND_TOPMOST. 如果窗口句柄是NULL,DispatchMessage不做任何事。 */ }
//【5】终止应用程序 /*一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。 当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。 通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。*/ return msg.wParam ;/*表示从PostQuitMessage返回的值 当WinMain函数把控制返回到Windows时,应用程序就终止了。 应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册, 每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息*/ }
//【6】窗口过程,窗口过程函数 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, TEXT ("你好,欢迎你来到VC之路!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ;
case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
总结:
(至此,我的SDK第一天也结束了,怎么说呢,煎熬,但是也挺有趣的!感谢大佬带来的文章!知识点比较偏底层,有一种“操作系统程序化”的既视感,但与大佬的“对话”深刻的加深了我对知识的理解啦!下面是我在这一天中的学到的图吧,有些地方还不知道该怎么“圆谎”,慢慢学,慢慢画,加油!)
(论坛转载文章实在是太麻烦啦,,我下次还是直接把自己的成果搬过来即可吧!不清楚的地方欢迎读者私信哦!>_<)
|