Windows应用程序的面向对象认识面向对象作为一种方法学要求将程序中的数据和操作(代码)归结到某些对象名下将数据看作对象的属性要改变这些属性必须通过操作来进行 进行面向对象的程序设计最好使用面向对象的语言如C++SamllTalk等面向对象的语言的语言所起的作用就是给程序员们提供一些进行面向对象的程序设计时必需的约束使数据和操作的衔接有一种显式的描述并进行一些技术性的事务管理但是如果我们能理解面向对象程序设计的原理和方法即使不使用面向对象的语言也能实现面向对象的程序设计 Windows本身并不是一个面向对象的程序设计环境但Windows的某些部分还是明显地受到面向对象的软件的概念的影响从某种程度上说在进行Windows程序设计时程序员是在进行面向对象的程序设计理解Windows的面向对象的思想和应用程序设计的面向对象方法对设计结构合理的应用程序会有很大的帮助 前面已给出了对象的定义每个对象包含有数据和代码代码描述了对象可执行的一系列预定义的动作而数据是对象私有的它们由相关的可执行代码存取预定义的动作和私有数据的结合称为封装在C中我们使用一个函数来封装一个对象的私有数据和动作使用switch语句来定义预定义的动作这些动作只存取为该函数本身所知道的数据 Windows和Windows应用程序是怎样发送消息的呢?在Windows及其应用程序中消息被表示为一个数据结构并能在对象之间传递发送消息等价于执行其参数表示消息数据的函数调用参数之一是一个标识该消息的预定义的消息标识符当一个对象接受到一条消息时消息标识符决定该对象执行何种动作消息传递是以函数调用的形式来实现的这种调用可以发生在程序的任何地方 Windows程序员必须清楚用消息引发动作的技术不同的对象能以不同的动作响应同样的消息这样一个特定的消息可代表一个通用事件例如按键操作移动鼠标或绘制用户区等而任何一个特定的消息可以在不同的对象中引发不同的动作例如不同的窗口对象以不同的动作处理同样的WM_KEYDOWNWM_MOUSEMOVE或WM_PAINT消息 一个消息可以有一个对象发送到另一个对象或由Windows发送到某个对象例如WM_KEY_DOWN之类的消息是由Windows产生的有些消息在对象的窗口函数对其处理完毕后就消失了而有些消息在处理时有产生新的消息一个对象通过向其它对象或自己发送一条或多条消息来处理一条消息这样Windows应用程序的控制流程不象MSDOS应用程序那样易于跟蹤程序的调试也比MSDOS应用程序困难 除了个别消息以外对象接受消息的顺序是不可预知的但对象处理每条消息所采取的动作是显式定义在窗口函数中的对象并不显式地定义所有可能消息的动作对于不显示处理的消息都交由DefWindowProc进行缺省处理 消息传递的途径很简单从一个对象传递到另一对象但由于DefWindowProc对有些消息提供了缺省处理因此程序员在设计程序时必须考虑在一个窗口函数中捕获某条消息时是否还应交给DefWindowProc函数作进一步的处理DefWindowProc能处理所有的消息但对大部消息只是简单地废弃之不作具有实际意义的处理在窗口函数捕获这些废弃消息是安全的若要捕获其它消息则必须了解DefWindowProc是怎样处理这条消息的并在窗口函数的处理代码中能提供类似的处理(或将该消息交由DefWindowProc作进一步的处理) 现在我们讨论窗口函数对对象的私有数据的处理问题窗口类也说明了对象的私有数据当调用CreateWindow创建一个窗口对象时Windows为创建的窗口对象分配私有数据存储区其中存储有窗口的实例句柄父窗口句柄窗口函数的地址和其它Windows用于管理窗口对象的数据对这些私有数据的的操作只能使用GetWindowWord/GetWindowLong等函数对于程序中说明的变量如何在窗口函数中将它们与相关的对象衔接在一起就比较复杂因为窗口函数为该类的所有对象共享该类的所有对象在接收到消息时都执行相同的代码 在过去Windows推荐使用的程序设计语言是C由于C语言不具备将一个对象的私有数据和操作这些私有数据的代码衔接在一起的语言成份(面向对象的语言的事务性工作之一就是为程序完成这个工作)这个工作只能由程序员来作程序员心中必须清楚程序中所说明或分配的变量私有于哪个对象并采用合适的数据结构来表示它们以便程序在使用它们时能根据不同的对象将它们区别开来 有几种方法可用于区分对象的私有数据 程序员编制额外的代码来判断一个对象应使用哪些数据 使用窗口附加字节 使用属性表 当使用第一种方法时程序实际是使用对象句柄作索引来检索与该对象相关的私有数据Windows也使用这种方法使用句柄来检索一张表这个表中存储着该句柄所标识的对象的私有数据Windows的许多函数需要一个对象的句柄作为第一参数其原因就是为区分对象的私有数据以便使用相同的函数处理不同的对象(的数据) 后两种方法与第一种方法本质是一样的(我们会将在后面的章节对其进行介绍)只是Windows提供了一些相关的函数来简化程序的工作 由于C没有继承这种语言成分因为也就不能形成对象的等级结构继承是面向对象语言的另一个重要成分继承使得程序中的对象形成一个分层次的对象结构低层次的对象可以将它不处理的消息发送到高层对象上进行缺省处理由于在C中不能(或说很难)建立对象的这种等级结构但为了简化应用程序的设计又必须要求支持消息的缺省处理(否则应用程序要定义一个窗口对象可能接收到的所有消息的处理代码)因此只能使用DefWindowProc提供消息的缺省处理这就要求对一个窗口对象所有消息的处理定义在一个函数中就带来了定义窗口函数的返回值和参数类型时使用了一种较难为人理解的方法因为不同的消息可以带有不同类型和个数的参数并且返回数据的类型也不相同Windows的设计者采用了一个折中的方法为消息规定一个十六位的参数和一个位的参数将返回类型指定为LRESULT这种类型的长度能容下C中所有预定义类型的数据 由于不同类的窗口对象定义有自己的窗口函数但C语言不具备根据接受消息的对象自动决定调用该对象的窗口函数的能力(在面向对象的语言中这种能力被称为多态性)因此向不同的窗口对象发送消息时使用函数SendMessage对窗口函数作间接调用由Windows根据该函数调用中所使用的对象标识符来调用该对象的窗口函数 在程序设计中由于窗口函数的限制需经常进行各种各样的数据类型转换例如 SendMessage(hwnd WM_USER (WPARAM) MAKELPARAM( )); 在这个例子中为了组建一个LPARAM类型的数据使用了宏MAKEPARAM它将两个十六位的数据组装成一个位的数据(低位字为MAKEPARAM的第一个参数高位字为第二个参数)当需要从一个LPARAM类型的数据中分离出低位字和高位字时使用宏LOWORD和HIWORD例如处理上个例子中所发送的WM_USER消息的窗口函数的代码可能为 WORD wStart = LOWORD(lParam); WORD wStart = LOWORD(lParam); 宏MAKELRESULT与MAKELPARAM类似它被用于装配LRESULT类型的数据宏MAKELONG用于装配LONG类型的数据当需要从LRESULT或LONG类型的数据中分离出高位字和低位字时使用宏HIWORD和LOWORD 基于上面的介绍我们在设计Windows应用程序时要明确程序中存在哪些对象对象之间是如何通过消息传递程序控制的哪些数据是对所有对象公有的 哪些数据是私有于某一个对象的公有数据和对象的私有数据必须是存储在静态生存期的变量中(局部生存期的变量在窗口函数返回后就消失了不能在下次调用该函数时保存上次的值换句话说存储对象的数据的变量的生存期不应小于对象的生存期) 由于Windows应用程序各个模块之间主要是通过消息传递控制因此Windows应用程序的逻辑结构就不同于MSDOS应用程序的逻辑结构如图所示从图可以看出Windows应用程序的各个模块通过消息传递被联系在一起因此如果正确地组织程序程序的模块性和结构较MSDOS应用程序要好 图 DOS应用程序与Windows应用程序逻辑结构的比较示意说明 Windows程序的组织 将节介绍的程序按照C/C++语言的要求组织起来就得到一个完整的Windows程序一个Windows程序必须有一个名为WinMain的主函数 // c 代码片段 #include <windowsh> LRESULT CALLBACK WndProc(HWND UINT WPARAM LPARAM); int PASCAL WinMain( HINSTANCE hInstance// 应用程序的实例句柄 HINSTANCE hPrevInstance// 该应用程序前一个实例的句柄 LPSTR lpszCmdLine// 命令行参数串 int nCmdShow) // 程序在初始化时如何显示窗口 { char szAppName[] = Window; HWND hwnd; MSGmsg; WNDCLASS wndclass; if (!hPrevInstance) { // 该实例是程序的第一个实例注册窗口类 wndclassstyle = CS_VREDRAW | CS_HREDRAW; wndclasslpfnWndProc = WndProc; wndclasscbClsExtra = ; wndclasscbWndExtra = ; wndclasshInstance = hInstance; wndclasshIcon = LoadIcon(hInstance IDI_APPLICATION); wndclasshCursor = LoadCursor(NULL IDC_ARROW); wndclasshbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndclasslpszMenuName = NULL; wndclasslpszClassName = szAppName; if (!RegisterClass(&wndclass)) // 如果注册失败 return FALSE; } // 对每个实例创建一个窗口对象 hwnd = CreateWindow( szAppName Sample Program WS_OVERLAPPEDWINDOW CW_USEDEFAULT CW_USEDEFAULT CW_USEDEFAULT CW_USEDEFAULT NULL NULL hInstance NULL); ShowWindow(hwnd nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg NULL )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msgwParam; } WinMain函数是Windows应用程序开始执行时的入口点它的返回类型为intWinMain函数的作用十分类似于MSDOS中的C应用程序的main函数 WinMain带有四个参数参数hInstance和hPrevInstance是程序的实例句柄在Windows环境下可以运行同一个程序的多个拷贝每一个拷贝都是该应用程序的一个句柄每个实例使用一个实例句柄进行标识hInstance是标识当前程序的实例的句柄它的值不会为NULL如果在此之前Windows中已经运行了该程序的另一个实例则这个实例的句柄由参数hPrevInstace给出如果在运行该程序时Windows环境中不存在该程序的另一个实例则hPrevInstance为NULL 我们曾经说过对同一个类不能向Windows注册一次以上在这个程序中通过判别hPrevInstance的值是否为NULL来决定是否应向Windows注册窗口类这样的程序逻辑保证了只在该程序的第一个实例中注册窗口类 参数lpszCmdLine中包含有运行程序时传递给程序的命令行参数例如若以这样的命令运行该程序Sampleexe Programming Windows则lpszCmdLine将指向字符串Programming Windows 最后一个参数nCmdShow是一个int类型的整数用以说明在程序被装如内存时Windows以何种方式显示这个程序的窗口根据运行程序的方式不同该参数被设置为SW_SHOWNORMAL或SW_SHOWMINNOACTIVESW的含义是Show Window(显示窗口)这两个参数的含义在后面介绍 在程序SampleCPP中有几个函数我们未曾介绍表给出了这些函数的说明表 ShowWindow 函数 用途显示或改变给定的窗口原型BOOL ShowWindow( HWND hWnd指定一个窗口对象int nCmdShow 指定窗口的显示方式);返回值返回该窗口更新前的窗口状态对先前可见的窗口其值为非零对先前隐藏的窗口其值为零 显示方式(nCmdShow)可以是下列常量之一 类型说明SW_HIDE隐藏该窗口(并是另一个窗口激活)SW_MINIMIZE使窗口变成图标(并激活窗口管理表的顶层窗口)SW_SHOW激活一个窗口并根据其当前的尺寸和位置显示该窗口SW_SHOWMAXIMIZED激活并以全屏方式显示一个窗口SW_SHOWMINIMIZED激活并以图标方式显示一个窗口SW_SHOWMINNOACTIVE以图标方式显示一个窗口当前活动的窗口仍保持活动SW_SHOWNA以当前状态显示一个窗口当前活动的窗口仍保持活动SW_SHOWNOACTIVE以最近的大小和位置显示一个窗口当前活动的窗口仍保持活动SW_SHOWNORMAL激活并显示一个窗口若其为图标或全屏方式显示则恢复为它的原始大小和位置SW_RESTORE同SH_SHOWNORMAL 表 UpdateWindow 函数 用途若应用程序的消息队列中存在WM_PAINT消息(绘制用户区消息)则该函数使Windows立即调用窗口函数向其传递WM_PAINT否则该函数不作为任何动作原型VOID UpdateWindow( HWND hWnd标识被刷新的窗口的句柄);返回值无 表 GetMessage 函数 用途从应用程序中的消息队列中检索一条消息原型BOOL GetMessage( LPMSG lpMsg指向MSG类型的变量的远指针它包含有从应用程序消息队列中检索到的一条消息的数据HWND hWnd指定为哪个窗口检索消息如果hWnd为NULL则检索调用该函数的应用程序的所有的消息(不检索属于其它应用程序的消息)UINT wMinUINT wMax以下两个基本参数指定检索在wMin和wMax范围内的消息如果这两个参数都为零该函数检索所有的可用的消息);返回值在检索出WM_QUIT消息时返回零值在其它情况下返回非零值 表 DispatchMessage 函数 用途将消息发送到指定的窗口对象上(窗口函数被调用)原型LRESULT DispatchMessage(LPCMSG lpMsg指向MSG类型变量的远指针该变量中存储有来自应用程序消息队列中的消息);返回值若有一个WM_CHAR消息被放到应用程序的消息队列中返回非零否则返回零该函数不改变lpMsg所指向的变量中存储的消息数据 Windows的主函数都是首先以初始化(注册类创建对象等)这一步开始而且紧跟着就是消息循环运行这一步这些步骤对所有的Windows应用程序都大同小异Windows应用程序主要的不同点在窗口函数的定义上由于一个应用程序所解决的任务不同它的窗口函数对消息的处理方式也就不相同因而每个应用程序需要定义不同的窗口函数 LRESULT CALLBACK WndProc (HWND hwnd UINT message WPARAM wParam LPARAM lParam) { switch (message) { case WM_DESTROY: PostQuitMessage(): return ; } return DefWindowProc(hwnd message wParam lParam); } 这个窗口函数仅处理一条WM_DESTROY消息这条消息是在用户关闭了屏幕上的窗口时Windows发送给窗口对象的该函数对这条消息的处理只是简单地调用Windows函数PostQuitMessage表给出了函数PostQuitMessage的说明当主函数的消息循环中的GetMessage函数检索出WM_QUIT消息时函数GetMessage返回零这样消息循环终止程序也随之被终止存储消息数据的变量msg的wParam域的值是在调用函数PostQuitMessage时所提供的实参的值如果程序正常结束调用PostQuitMessage函数时使用零作为该函数的参数如果需要表示程序由于出现了异常或错误而必须终止时使用非零值(一般使用)作为该函数的参数在调用PostQuitMessage使用的参数值被主函数用语句 return msgwParam; 返回给Windows供Windows或其它应用程序使用因此我们也称PostQuitMessage使用的参数为程序的退出码 表 PostQuitMessage 函数 用途通知Windows应用程序希望中止它一般用于响应WM_DESTROY消息该函数将消息WM_QUIT消息放入应用程序的消息队列中原型PostQuitMessage(int nExitCode指定应用程序的退出代码它用作WM_QUIT消息的wParam参数);返回值无 小结 本章首先介绍了图形用户界面的优点和面向对象的程序设计方法从某种意义上说Windows是面向对象的它主要建立在把窗口作为一个对象的概念上窗口之间通过消息进行消息传递 Windows支持直接操作技术直接操作是对屏幕对象的操作数据和函数的封装允许该对象自己响应它们接收到的消息在用户界面上发生的任何事件被作为消息发送给窗口对象程序员在设计程序时只须关心一个对象要接受哪些消息和怎样处理这些消息消息传递工作由Windows负责因而使用Windows操作环境可以极大地方便程序开发用户界面的工作并使程序的结构合理模块化程序高更重要的是支持直接操作技术的Windows支持用户进行有创造性的界面设计 |