WINDOWS消息和窗口简介
一、WINDOWS的消息和窗口简介:
1、什么是windows在这里我就不介绍了,但是作为一个程序员我们要知道WINDOWS最重要的一个也是我们程序员常用的一个东西就是消息。窗口是以消息的形式输入的,窗口也用消息与其它窗口通讯。
2、我们常会说windows给程序发送了一个消息,其实这是指windows调用程序中的一个函数,该函数的参数描述了这个特定消息。这种位于windows程序中的函数称为“窗体消息处理程序”。程序建立的每一个窗体都有相关的窗口消息处理程序。这个窗口消息处理程序是一个函数,既可以在程序中,也可以在动态链接库中。Windows通过调用窗口消息处理程序来给窗体发送消息。窗口消息处理程序根据此消息进行处理,然后将控制传回给windows。
3、在对象导向的程序设计中,对象是程序与数据的组合。窗口是一种对象,其程序是窗口消息处理程序。数据是窗口消息处理程序保存的信息和windows为每个窗口以及系统中那个窗口类别保存的信息。
4、windows程序开始执行后,windows为该程序建立一个“消息队列”。这个消息队列用来存放该程序可能建立的各种不同窗口的消息。程序中有一小段程序代码,叫做“消息循环”,用来从队列中取出消息,并且将它们发送给相应的窗口消息处理程序。有些消息直接发送给窗口消息处理程序,不用放入消息队列中。
二、一个真正的windows程序:
// HELLOWIN.C /*------------------------------------------------------------------------ HELLOWIN.C -- Displays "Hello, Windows 98!" in client area (c) Charles Petzold, 1998 -----------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { // TEXT 宏——UNICODE标识 static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; MSG msg ; WNDCLAS wndclass ; // 窗口类 wndclass.style = CS_HREDRAW | CS_VREDRAW ; // 窗口类风格 wndclass.lpfnWndProc = WndProc ; // 窗口过程 wndclass.cbClsExtra = 0 ; // 在类结构保存的窗口结构中预留一些额外空间 wndclass.cbWndExtra = 0 ; // 在Windows内部保存的窗口结构中预留一些额外空间 wndclass.hInstance = hInstance ; // 程序的实例句柄 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; // 加载图标资源 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; // 加载光标资源 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; // GetStockObject获取(画笔、刷子等)GDI对象 wndclass.lpszMenuName = NULL ; // 指定窗口类菜单 wndclass.lpszClassName = szAppName ; // 指定窗口类名 if (!RegisterClass (&wndclass)) // 注册窗口类 { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow( szAppName, // window class name TEXT ("The Hello Program"), // 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, // 父窗口句柄 NULL, // 菜单句柄 hInstance, // program instance handle NULL ) ; // creation parameters // 窗口创建成功后,系统将在内存中为其分配一块内存,但是此时窗口并未显示在显示器上,所以需要使用两个调用: ShowWindow (hwnd, iCmdShow) ; // 显示窗口 UpdateWindow (hwnd) ; // 将导致客户区被绘制 // 程序通过执行一个叫做“消息循环”的代码从消息队列中取出消息 // 注意GetMessage(阻塞,若没有消息则不返还控制权)和PeekMessage(非阻塞,若没有消息也会返回)的区别 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; // 将msg结构传给Windows,进行一些键盘转换 DispatchMessage (&msg) ; // 将msg结构传给Windows,然后Windows将里面的消息发给相应的窗口过程进行处理 } return msg.wParam ; } // CALLBACK类型(供系统调用) LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { // 当Windows在处理CreateWindow函数时,窗口过程就会接收到WM_CREATE消息 case WM_CREATE: PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ; return 0 ; // 当窗口客户区域的一部分或全部变成“无效”时,必须进行刷新,WM_PAINT将通知程序 case WM_PAINT: // BeginPaint调用使整个客户区有效,并返回一个“设备环境句柄 hdc = BeginPaint (hwnd, &ps) ; // 当改变窗口大小时,WndProc通过调用GetClientRect来获取变化后的窗口大小,重新绘制客户区 GetClientRect (hwnd, &rect) ; // 在指定的区域内输出格式化的文本 DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; // 当用户点击关闭按钮时发生 case WM_DESTROY: // 调用PostQuitMessage以标准方式响应WM_DESTROY消息; // PostQuitMessage(0); // 该函数在程序的消息队列插入一个WM_QUIT消息。 // GetMessage对于除了WM_QUIT消息之外的从消息队列中取出的所有消息都返回非0值。而当GetMessage取到一个WM_QUIT消息时,返回0 PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
1、代码分析:
(1)、在这里你除了winMain你还可以看到一个WndPro的函数这个函数就是传说中的消息处理函数,即消息循环就在这里运行的也是一个程序的心脏,有了他程序才可以得到正常的运行,他在窗口注册类的类里就被移植入窗口类中wndclass.lpfnWndProc=WndPro;要先申明后使用。
(2)、在这里我还要说一下下CreateWindow和WNDCLASS也就是建立窗口和注册窗口的联系,他们之间就好比造车厂和设计车的关系,RegisterClass函数注册了一个WNDCLASS的对象,然后CreateWindow通过其中的lpClassName这个参数也就是WNDCLASS对象的lpszClassName与窗口类联系起来,lpszClassName都是字符串的类型。
(3)、windows函数的调用:这个基本的框架里我们调用了18个windows 函数如下:
LoadIcon // 加载图标代程序使用 LoadCursor // 加载鼠标光标供程序使用 GetStockObject // 取得一个图形对象 RegisterClass // 为程序窗口注册窗口类别 MessageBox // 显示消息框 CreateWindow // 根据窗口类别建立一个窗口 ShowWindow // 在屏幕上显示窗口 UpdateWindow // 指示窗口自我更新 GetMessage // 从消息队列中取得消息 TranslateMessage // 转译某些键盘消息 DispatchMessage // 将消息发送给窗口消息处理程序 PlaySound // 播放一个声音文件 BeginPaint // 开始绘制窗口 GetClientRect // 取得窗口显示区域的大小 DrawText // 显示字符串 EndPaint // 结束绘制窗口 PostQuitMessage // 在消息队列中插入一个退出消息 DefWindowProc // 执行内定消息处理
(4)、在这个函数里面有很多大小写字母宏定义:这些解释一下下
在上面的函数中出现了如下几个大写字母宏定义:
前缀: 类别 使用 CS 窗口类别样式 WNDCLASS.style=CS_HREDRAW|CS_VREDRAW; CW 建立窗口 CreateWindow---(CW) DT 绘制文字 DrawText----(DT) IDI 图示ID LoadIcon(...) IDC 鼠标ID LoadCursor(...) MB 消息框 MessageBox------MB SND 声音 PlaySound (...) WM 窗口消息 WindowsMessage-------(WM) WS 窗口样式 WindowStyle-----(WS)
(5)、我们常会问WPARAM和LPARAM有什么区别干什么用: 其实这两个参数是win16和win32并存时留下的产物现在都是32位了,原来wParam(WORD)是一个16位的lParam(LONG)是32位的,现在的WPARAM和LPARAM都是MS重新定义的,他们通常是与一个消息有关的常量值如下: wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。 lParam 通常是一个指向内存中数据的指针。 由于wParam、lParam和指针都是32位的,需要时可以强制类型转换。具体表示什么,与message相关,他们是事先定义好的。
(6)、WndPro函数传回一个形态为LRESULT的值,议该值简单地被定义为一个LONG,在这里WndPro函数被指定成一个CALLBACK形态,其实也是一个和WINAPI一样的宏定义他是一个_stdcall一个从右到左的压栈方式,也就是传说中的回调函数(纸老虎不用怕)。
2、句柄简介: 句柄是一个(通常为32位的)整数,它代表一个对象。Windows中的够本类似传统C或MS-Dos程序设计中使用的文件句柄。程序几乎总是通过呼叫window函数取得句柄。程序在函数中使用够本,用以代表对象。句柄值对程序来说是无关紧要的,但是windows对于这个代号来说他就可以利用这个代号(句柄)使用相对应的对象。 一句话句柄是什么,比如你是一个对象那么句柄就是你的名字,如果windows要叫你去买酱油那么他会给你说:“XX句柄去买酱油”,和叫对象去买酱油的效果是一样,但是用了句柄会更好管理。(我不知道这样理解是否会好一点!) 三个大写标识符,用于不同形态的句柄:
标识符 含义
HINSTANCE 执行实体(程序自身)句柄
HWND 窗口句柄
HDC 设备内容句柄
句柄用的很频繁比如:HICON(图标句柄)、HCURSOR(鼠标光标句柄)、HBRUSH(画刷句柄)、windows中的每个窗口都有一个够本,程序用够本来使用窗口窗体句柄是windows程序所处理最重要的句柄之一。
3、我顺便也说一下instance吧,其实这就相当于是你运行QQ整个QQ就是一个instance!
三、注册窗口类别:
1、窗口依照某一窗口类别建立,窗口类别用以标识处理窗口消息的消息处理程序。窗口类别定义了窗口消息处理程序和依据引类别建立的窗口的其它特征。在建立窗口时,要定义一些该窗口所独有的特征。在为程序建立窗口之前,必须首先调用RegisterClass注册一个窗口类别。该函数只需要一个参数,即一个指向形态为WNDCLASS的结构指针。这些结构包括两个指向字符串的字段(LPCTSTR lpszMenuName; LPCTSTR lpszClassName;--ASCII。 LPCWSTR lpszMenuName ;LPCWSTR lpszClassName;--UNICODE)因此结构在WINUSER.H表文件中定义了两种不同的方式(ASCII,UNICODE)在编程过程我们不必去理会!因为在预定义时已经处理过了(WNDCLASSA,WNDCLASSW---都用WNDCLASS就行)! 2、在窗体类定义中最重要的是lpfnWndProc和最后一个字段lpszClassName。
3、窗口类别 他定义了窗口的一般特征,因此可以使用同一窗口类别建立许多不同的窗口,建立窗口时,可能指定有关窗口的更详细的信息。传递给RegisterClass函数的信息会在一个数据结构中设定好,而传递给CreateWindow函数的信息会在函数单独的参数中设定好。
四、消息循环:
Windows为当前执行的每个windows程序维护一个“消息队列”。在发生输入事件后,windows将事件转换为一个消息并将消息放入程序消息队列中。程序通过执行一块称之为消息循环的程序代码从消息队列中取出消息。
在这里我们了解一下下MSG结构:
Typedef struct tagMSG{ HWND hwnd; //接收消息的窗口句柄 UNIT message; //消息标识符WM_XXXX WPARAM wparam; //32位其含义和数值根据消息的不同而不同 LPARAM lparam; DWORD time; //消息放入消息队列中的时间 POINT pt; //消息放入消息队列时的鼠标坐标 } MSG,*PMSG; BOOL GetMessage( LPMSG lpMsg, // address of structure with message HWND hWnd, // handle of window UINT wMsgFilterMin, // first message UINT wMsgFilterMax // last message );
这个调用付给windows一个指标,指向名这msg的MSG结构。lpMsg是一个消息地址,hWnd如果为NULL表示程序接收它自己建立的所有窗口的所有消息,Windows用从消息队列中取出的下一个消息来填充消息结构的各个字段。只要从消息队列中取出消息的message字段不为WM_QUIT,GetMessage就传回一个非零值。
BOOL TranslateMessage( CONST MSG *lpMsg // address of structure with message ); //将msge结构传给windows,进行一些键盘转换 LONG DispatchMessage( CONST MSG *lpmsg // pointer to structure with message );
又将msg结构回传给windows。然后,windows将该消息发送给适当的窗口消息处理程序,让它进行处理。这里就是windows将会调用消息处理程序,也就是这个窗口的wndPro函数。处理完消息之后,WndProc传回到windows,此时window还停留在despatchMessage调用中,在结束dispatchMessage调用的处理之后,windows回到GetMessage调用开始消息循环。
五、窗口消息处理程序:
1、在上面我们介绍了一个窗口的产生过程和动作过程:注册窗口类别,建立窗口,然后在屏幕上显示窗口,程序进入消息循环,然后不断从消息队列中取出消息来处理。
2、实际的动作发生在窗口消息处理程序中。窗口消息处理程序确定了在窗口的显示区域中显示以及窗品怎样响应使用者输入。窗口消息处理程序可任意命名,一个windows程序中可以包含多个窗口消息处理程序。一个窗口消息处理程序总与调用的registerClass注册的特定窗口类别相关联。createWindow函数根据特定窗口类别建立一个窗口。但依据一个窗口类别,可以建立多个窗口。
窗口消息处理程序总是定义为如下形式:
LRESULT CALLBACK WndProc(HWND hwnd,UNIT message,WPARAM wparam,LPARAM lparam)
这四个参数和Msg结构的前四个字段是相同的。
3、程序通常不直接呼叫窗口消息处理程序,消息处理程序通常由windows本身调用。窗口消息处理程序在处理消息时,必须传回窗口消息处理程序不处理的所有消息应传给名为DefWindowProc的Window函数。Wndproc只先择处理三种消息:WM_CREATE,WM_PAINT,WM_DESTROY。 WM_CREATE:处理进行一次窗口初始化。
4、WM_PAINT:当窗口显示区域的一部分或者全部内容必要更新显示或无效时,将由这个消息通知程序。
什么叫无效,无效就是说如果窗口调用updateWindows时例如在窗口类中我们写的style=CS_HREDRAW|CS_VREDRAW这表示在窗口大小改变后就把整个窗口显示内容变为无效。
5、调用updateWindows进行重绘。在对WM_PAINT的处理几乎总是从一个BeginPaint(hwnd,&ps)调用开始,以EndPaint(hwnd,&ps)结束,ps包含一些窗口消息处理程序可以用来更新显示区域的内容。
6、在BeginPaint调用中,如果显示区域的前景还没被删除则由windows来删除,它使用窗口类别的WNDCLASS的结构中的hbrBackground字段中指定的画刷来删除前景。BeginPain返回一个只能在当前区域绘图的设备内容句柄。EndPain释放设备内容句柄。GetClientRect(hwnd,&rect)rect是返回一个窗口显示区域的尺寸。
7、WM_DESTROY:该消息是使用者单击Close按钮或者程序的系统菜单上选择Close时发生的。在这里用的是PostQuitMessage(0)来生效的。该函数在程序的消息队列中插入一个WM_QUIT消息使GetMessage得到一个WM_QUIT的消息,它传回0,终止程序然后执行return msg.wparam;
六、队列化消息与非队列化消息:
Windows给窗口发送消息,这意味着windows调用窗口消息处理程序。但是,windows程序也有一个消息循环,它调用GetMessage从消息队列中取出消息,并且调用dispatchMessage将消息发送给窗口消息处理程序。
消息被分为“队列化的”和“非队列化的”。
1、 队列化的消息:是由windows放入程序消息队列中的。在程序的消息循环中,重新传回并分配给窗口消息处理程序。如键盘消息,WM_PAINT windows消息等。
2、 非队列化的消息在windows调用窗口时直接送给窗口消息处理程序。非队列化消息多来自己于调用windows函数如CreateWindows,键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。
3、 队列化的消息被发送给消息队列,而非队列的消息则发送给窗口消息处理程序。
任何发问下,窗口消息处理程序都将获得窗口所有的消息――包括队列和非队列化的消息。窗口消息处理程序是窗口的消息中心。
4、在许多情况下,窗口消息处理程序必须保存它从消息中取得的信息,并在处理另一个消息时使用这些信息。这些信息可以储存在窗口的静态变量或整体变量中。