560 likes | 859 Views
C++ 面向对象程序设计. 第十二章 利用 MFC 开发 Windows 应用程序. 学习目标. ( 1 )理解 Windows 编程思想,理解 MFC 库及其结构 ( 2 )掌握利用 MFC Appwizard 创建 Windows 应用程序的步骤和方法,弄清 MFC Appwizard 所创建的应用程序中所产生的主要类及其功能、组成文件和程序的框架结构 ( 3 )理解消息映射、消息处理函数的概念。掌握利用 Class Wizard 增加、修改和删除窗口消息处理函数的方法和步骤,并理解程序代码的变化过程和特点 ( 4 )掌握文档 / 视图结构应用程序的开发过程
E N D
C++面向对象程序设计 第十二章 利用MFC开发Windows应用程序
学习目标 (1)理解Windows编程思想,理解MFC库及其结构 (2)掌握利用MFC Appwizard创建Windows应用程序的步骤和方法,弄清MFC Appwizard所创建的应用程序中所产生的主要类及其功能、组成文件和程序的框架结构 (3)理解消息映射、消息处理函数的概念。掌握利用Class Wizard增加、修改和删除窗口消息处理函数的方法和步骤,并理解程序代码的变化过程和特点 (4)掌握文档/视图结构应用程序的开发过程 (5)掌握菜单、工具栏、控件的使用 (6)掌握对话框应用程序的开发过程以及数据库访问操作
Windows应用程序的特点与消息驱动机制 • 基于Windows操作系统的应用程序的特点 • 标准的图形用户界面,比如窗口、菜单、按钮、列表框等。 • 应用程序与硬件无关的特性,对于同一类硬件,不论哪个厂家或哪个型号,程序都无需进行任何修改就可以运行。 • 所有的Windows应用程序都是消息驱动机制,也就是说Windows程序是通过操作系统发送的消息来处理用户的输入。 • 消息驱动是Windows操作系统的一大特色,操作系统将包括用户输入在内的各种事件,以消息的形式发送到目标,目标系统再根据消息的具体内容进行相应的处理。
典型的Windows应用程序结构 • 控制台应用程序:在本书第1章~第7章介绍的所有程序均为控制台应用程序。控制台应用程序结构简单,可以不使用MFC类库。 • 基于框架窗口的应用程序:某些应用程序仅需要最小的用户界面和简单的窗口结构,这时可以使用基于框架窗口的方案。 • 基于文档/视图结构的应用程序:文档/视图应用具有较复杂的结构,当然其功能也相应增强。又可分为单文档(SDI)和多文档界面(MDI)两种模式。 • 基于对话框的应用程序。
学习MFC的方法 • 不要一开始学习Visual C++就试图了解整个MFC类库。一般的学习方法是,先大体上对MFC有个了解,知道它的概念、组成等之后,从较简单的类入手,由浅入深,循序渐进、日积月累地学习。一开始使用MFC提供的类时,只需要知道它的一些常用的方法、外部接口,不必要去了解它的细节和内部实现。在学到一定程度时,再深入研究,采用继承的方法对原有的类进行修改和扩充,派生出自己所需的类。
利用MFC AppWizard创建Windows应用程序 • 利用MFC AppWizard创建一个Windows应用程序的步骤
第1步:启动Visual C++ 6.0,选择【File】菜单下的【New】菜单项,选择【Projects】标签,选择MFC AppWizard(exe)项目类型,在Project name中输入项目名MyExp,定位于“C:\EXAMPLE\”文件夹中,单击【OK】按钮。 • 第2步:选择【Single document】应用类型,即单文档应用程序,其他使用默认值,单击【Next】按钮。 • 第3步:让用户选择程序中是否加入数据库支持,在此使用默认值【None】,单击【Next】按钮。 • 第4步:让用户选择在程序中加入复合文档,自动化支持或ActiveX控件的支持,在此使用默认值,单击【Next】按钮。 • 第5步:让用户选择应用程序的一些特性,在此使用默认值,单击【Next】按钮。 • 第6步:让用户选择应用程序主窗口的风格、在源文件中选择是否加入注释和使用怎样的MFC类库,在此使用默认值,单击【Next】按钮。
第7步:用户可以对MFC AppWizard提供的缺省类名、基类名、头文件名、源文件名进行修改,在此使用默认值,单击【Finish】按钮,显示出用户在前面几个步骤中的选择内容,单击【OK】按钮,系统开始创建应用程序,并回到Visual C++ 6.0的主界面。 运行过的应用程序MyExp可以脱离Visual C++ 6.0单独运行,运行该文档可以双击“C:\EXAMPLE\Debug”下的MyExp.exe文件,运行结果如图12.1所示。可以看到,和所有的Windows应用程序一样,MyExp也包含标题栏、菜单栏、工具栏、状态栏等窗口元素。
MFC应用程序的类和文件 • AppWizard在生成应用程序时,共派生了5个类,单击“MyExp classes”左侧的“+”展开所有的类,即可显示出应用程序MyExp的5个类。其中: • CAboutDlg:关于About对话框的对话框类。 • CMainFrame:主框架窗口类。 • CMyExpApp:应用程序类。 • CMyExpDoc:文档类。 • CMyExpView:视图类。
在工程中,每个类都拥有自己的类定义文件(*.h)和类实现文件(*.cpp)。类定义文件主要保存各种类的定义,类实现文件主要保存各种类的成员函数的实现代码
AppWiZard生成的5个派生类 类定义和类实现文件
文件说明 • AppWizard在自动生成工程时,除了生成上面介绍的各个类的头文件和实现文件外,还生成了一些为建立应用程序所需要的其他文件。这些文件可以在应用程序生成时指定的路径(C:\EXAMPLE\MyExp)中找到。 如下图所示
关于这些文件详细说明 • 详见教材
在窗口的客户区输出文字和图形 SDI中显示字符串 12.2节中的第1步到第7步。 在左边的Workspace下面的ClassView中,点击类CMyExpView左边的+号,双击OnDraw(),如图所示。
在右边OnDraw()中加入以下阴影部分的代码,编译、运行结果如图所示。在右边OnDraw()中加入以下阴影部分的代码,编译、运行结果如图所示。 void CMyExpView::OnDraw(CDC * pDC) { CMyExpDoc * pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here pDC->Rectangle(10,10,250,60); //显示一个矩形框 pDC->TextOut(20,20,"我的VC++的第一个SDI程序。");//显示文字 }
程序说明 • 程序对OnDraw()进行了扩展,使用了CDC类的两个成员函数:画矩形的Rectangle()和文字输出TextOut()。 • CDC类中封装了大量的绘图和文字输出方法。 • 文字信息显示 • BOOL TextOut(int x,int y,LPCTSTR lpszString); • 功能:在指定坐标(x,y)处显示字符串lpszString的内容,显示成功返回非0值,否则返回0。 • 说明: • 坐标原点(0,0)在客户区左上角,Y轴向下。 • LPCTSTR为常量字符指针类型,BOOL为逻辑类型,两者都是Windows的数据类型。还有COLORREF、POINT、LPPOINT、LPCRECT等都是Windows API中定义的关键字,通常将它们看作Windows的数据类型。
画点 • COLORREF SetPixel(int x,int y, COLORREF color); • COLORREF SetPixel(POINT point, COLORREF color); • 功能: • 在指定坐标(用参数x,y或点point给出)处按给定颜色(color) 画点,返回值为原来此坐标处的颜色。
画线 • 画线工作需经两步完成: • 确定线的起始位置。 • CPoint MoveTo(int x,int y); • CPoint MoveTo(POINT point); • 功能:将绘图位置移至指定坐标处,返回移动前的绘图位置。 • 确定了线的起点后,使用成员函数LineTo()画线。 • BOOL LintTo(int x,int y);//参数为终点坐标 • BOOL LintTo(POINT point); • 功能:画线。
绘制矩形 • BOOL Rectangle(int x1,int y1,int x2,int y2); • BOOL Rectangle(LPCRECT lpRect); • 功能:绘制一个左上角坐标为(x1,y1),右下角坐标为(x2,y2)的矩形。 • 绘制椭圆 • BOOL Ellipse(int x1,int y1,int x2,int y2); • BOOL Ellipse (LPCRECT lpRect); • 功能:绘制一个左上角坐标为(x1,y1),右下角坐标为(x2, y2)的矩形所围住的椭圆
Windows消息处理 • 用户输入响应是Windows程序必不可少的功能。例如,当用户在窗口中按下鼠标左键时,Windows系统就会发送WM_LBUTTONDOWN消息给该窗口,如果程序需要对此消息做出反应,必然要调用相应的处理函数,如果没有定义处理函数,则该消息被忽略。编制消息处理函数有时又被称作消息映射或捕获消息。 • 通过消息映射,消息就和它的处理函数对应起来
消息处理函数指的是与某个消息对应的函数。消息处理函数的执行是由其对应的消息引发的,某个类对象中的消息处理函数是与这个类对象能够得到的一个消息对应的,当这个消息发生时,这个函数就会被执行。消息处理函数指的是与某个消息对应的函数。消息处理函数的执行是由其对应的消息引发的,某个类对象中的消息处理函数是与这个类对象能够得到的一个消息对应的,当这个消息发生时,这个函数就会被执行。 • 利用ClassWizard可以管理消息处理函数,ClassWizard是一个非常强大有用的工具,可以用ClassWizard来创建新类、定义消息处理函数、覆盖MFC的虚拟函数,从对话框、表单视图或记录视图的控件中获取数据。
可以用三种方法来激活ClassWizard对话框: • 选择【View】菜单中的【ClassWizard】菜单项; • 直接按下Ctrl+W键; • 在代码编辑窗口中单击鼠标右键(此时,代码编辑窗口中必须有打开的文件),在弹出菜单中选择【ClassWizard】菜单项。
弹出【MFC ClassWizard】对话框 • 在【MFC ClassWizard】对话框上面共有五个选项卡,分别说明如下: • 【Message Maps】选项卡用于进行消息映射的处理; • 【Member Variables】选项卡用于为对话框中的控件所用到的类创建成员变量; • 【Automation】选项卡帮助用户管理与OLE自动化相联系的方法和属性; • 【ActiveX Events】选项卡帮助用户管理ActiveX类支持的ActiveX事件; • 【Class Info】选项卡显示类的一般信息,包括定义它的头文件和源文件、类名以及与之相联系的基类。
Windows消息 • 消息就是操作系统通知应用程序某件事情已经发生的一种方式。例如,当用户移动或双击鼠标、改变窗口大小等,都将向适当的窗口发送消息,一个窗口可以向另一个窗口发送消息。 • Windows系统中的消息主要有三种类型:标准的Windows消息、控件消息和命令消息
标准的Windows消息 • 所有以WM_为前缀的消息都是标准的Windows消息(WM_COMMAND消息除外),如WM_PAINT、WM_QUIT等,这些消息通常含有用于确定如何对消息进行处理的一些参数。标准的Windows 消息一般由窗口对象和视图对象进行处理
标准的Windows消息可以分为三类,即键盘消息、鼠标消息和窗口消息 • 鼠标消息 • 无论何时移动鼠标或操作鼠标按键,Windows便产生一条或多条消息并将其发送给位于鼠标光标下的窗口 • 键盘消息 • 键盘消息的响应函数是OnKeyDown(),用于处理WM_KEYDOWN消息(按下键盘上的按键),该函数的原型为: • afx_msg OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
窗口消息 • 所有窗口的变化,包括窗口内容重绘WM_PAINT,窗口最大化WM_MAXIMIZE,窗口最小化WM_MINIMIZE,窗口重定义大小WM_RESIZE,窗口滚动WM_HSCROLL、WM_VSCROLL,窗口定时WM_TIMER等消息所带参数各不相同
控件消息 • 控件是一个小的子窗口,属于其他窗口(如对话框等),能够接受操作并象父窗口发送消息。常见的控件有按钮、列表框、编辑框、复合框、滚动条等 • 发送控件消息的控件在Visual C++中使用唯一ID号来进行标识,使用控件类来操纵 • 控件消息分为两类: • 从控件传给消息,通常这类消息前缀的最后一个字符为N • 由系统发送给控件的消息,这类消息前缀的最后一个字符为M
命令消息 • 命令消息主要包括由用户界面对象发送的WM_COMMAND消息,用户界面对象是指菜单、工具栏、快捷键等。它和控件消息的区别在于:控件消息只能由特定控件向Windows系统传送,而命令消息是由用户界面发送的,它可以被更多的对象处理。在文档对象、视图对象、窗体对象、控件对象中都能处理这种消息
消息的发送与接收的基本过程和机制 • 在Windows中,大部分的消息都是由用户和应用程序的相互作用而产生的。CWinApp类的成员函数Run()用于处理消息循环,它唯一的功能就是等待消息,并将消息发送到适当的窗口。 • 当消息循环接收到一条Windows消息时,它首先通过查询一种内部结构来确定消息要发送的窗口。这种内部结构把当前所有的窗口映像成其对应的窗口类。MFC的基类还能够检测这一目标类是否在其消息中为这一消息提供了处理函数入口。如果找到入口,则消息被送往处理函数,结束消息发送过程。如果消息无对应入口,则对目标类进行基类消息映射检测,沿着层次向上查找,直到找到入口函数为止。
文档/视图结构 • 文档/视图结构大大简化了多数应用程序的设计开发过程,有如下特点 • 将对数据的操作与数据显示界面分离,放在不同类的对象中处理。这种思路使得程序模块的划分更加合理。文档对象只负责数据的管理,不涉及用户界面;视图只负责数据输出和用户的交互,不考虑数据的具体组织结构细节,并且一个文档类可对应多个视图类。 • MFC在文档/视图结构中提供了许多标准的操作界面,如新建、打开、保存、打印等,还支持打印预览、电子邮件发送等功能(可定制),大大减轻了程序员的工作量。 • 下面两种情况下不宜采用文档/视图结构: • ① 不是面向数据的应用程序或数据量很少的应用程序,如Windows自带的磁盘扫描程序、时钟程序等工具软件,以及一些过程控制程序等。 • ② 不使用标准窗口界面的程序,如一些游戏软件等。
视图类 • 视图类CView是窗口类CWnd类的派生类。视图类对象完全覆盖框架窗口的用户区,没有自己的边框。视图规定了用户查看文档数据以及同数据交互的方式 • 视图类的几个重要成员函数: • GetDocument()成员函数用于从文档类中获取数据值 • OnDraw()成员函数用于更新视图 • OnInitialUpdate()虚成员函数在应用程序启动,或用户从File菜单中选择了New或者Open选项时被调用
对视图类的处理主要集中在以下4点 • 处理视图类的OnDraw()成员函数,该函数负责显示文档数据。 • 将Windows消息和用户界面对象(如菜单项等)与视图类中的消息处理函数连接。 • 实现消息处理函数,以便解释用户的输入。 • 根据需要,在派生的视图类中覆盖CView的其他成员函数 • MFC中提供了各种视图类,这些视图类可以增强应用程序视图的功能
文档类 • 文档类的几个重要成员函数: • OnNewDocument()成员函数完成对文档类的数据成员的初始化工作 • DeleteContents()成员函数完成对文档的清理工作
在使用文档类管理应用程序的数据时,必须做以下工作: • 从CDocument类派生出各种不同类型的文档类,每种类型对应一种文档。 • 添加用于存储文档数据的成员变量。 • 如果需要,可以覆盖CDocument类的其他成员函数。如覆盖OnNewDocument()以便初始化文档的数据成员,覆盖DeleteContents()以便销毁动态分配的数据。 • 在文档类中覆盖CDocument类的成员函数Serialize()。成员函数Serialize()用于从磁盘读文档数据或把文档数据存入磁盘中。
例:在视图中显示鼠标单击位置 • 第1步:建立SDI项目(My),见12.2节中的第1步到第7步。 • 第2步:修改文档类的定义,加入一个CPoint类型的变量(代码中的阴影部分),记录鼠标当前单击位置。 • 第3步:修改视图类的OnDraw()函数,加入显示位置代码 • 第4步:用ClassWizard建立鼠标左键按下的消息处理的函数OnLButtonDown(),并加入以下阴影部分的代码。 • 第5步:编译、运行。程序开始运行时,在窗口中显示出两个很大的数,用鼠标左键单击窗口客户区任意位置,窗口内显示出当前单击位置的两个坐标值。
例用键盘移动窗口客户区中的一个气球(椭圆) • 第1步:建立SDI项目(Mm),见8.2节中的第1步到第7步。 • 第2步:修改文档类的定义,加入一个CRect类型的变量记录椭圆的位置(以下阴影部分)。 • 第3步:修改文档类的OnNewDocument()函数,增加以下阴影部分的代码,对 m_rectBody进行初始化。 • 第4步:修改视图类的OnDraw()函数,加入以下阴影部分的代码。 • 第5步:用ClassWizard 建立键盘的消息处理函数OnKeyDown(),并加入以下阴影部分的代码。
菜单、工具栏 • 菜单是Windows应用程序窗口的一个重要组成部分。在MFC中菜单可以用于SDI(单文档界面)或MDI(多文档界面),以及基于对话框的应用程序。在基于对话框的应用程序中新建、设计并编辑菜单后,在该对话框的“属性”选项卡中,将Menu属性设置为该菜单的ID即可。
添加/删除菜单项 • 第1步:建立SDI项目(TestMenu),见12.2节中的第1步到第7步。 • 第2步:在工程工作区中单击资源视图标签ResourceView,切换到资源视图。 • 第3步:点击Menu左边的+号,展开Menu,可以看到IDR_MAINFRAME,双击之,在右边显示出IDR_MAINFRAME标志的菜单资源 • 第4步:添加菜单项。在要添加的位置上(如“帮助”右边)击右键,选“Properties”,在“Caption”编辑框中输入“测试菜单(&C)”,则在菜单栏中出现“测试菜单(&C)”菜单项
第5步:添加菜单子项。在“测试菜单(&C)”菜单下面的虚框上击右键,选“Properties”,在“Caption”编辑框中输入“显示(&X)”,在“ID”编辑框中输入“ID_MENUXS”,则在“测试菜单(&C)”菜单下面出现菜单子项“显示(&X)”第5步:添加菜单子项。在“测试菜单(&C)”菜单下面的虚框上击右键,选“Properties”,在“Caption”编辑框中输入“显示(&X)”,在“ID”编辑框中输入“ID_MENUXS”,则在“测试菜单(&C)”菜单下面出现菜单子项“显示(&X)”
第6步:调整菜单位置。选中要调整位置的菜单项,按住鼠标左键拖动到合适的位置放手即可。若要删除菜单项,选中要删除的菜单项,按Delete键即可。第6步:调整菜单位置。选中要调整位置的菜单项,按住鼠标左键拖动到合适的位置放手即可。若要删除菜单项,选中要删除的菜单项,按Delete键即可。 • 第7步:编译、运行,可以看到结果。但此时“显示”菜单子项为灰色,原因是还没有为其编制消息响应函数。 • 第8步:为菜单子项编制消息响应函数。在“显示”菜单子项上击右键,选“ClassWizard”,确保【Class name】选“CTestMenuView”,【Object IDs】中选中“ID_MENUXS”,【Messages】选中“COMMAND”, 单击右边的【Add Function】,为“显示”菜单子项增加消息响应函数OnMenuxs() • 第9步:编译、运行,测试结果
对话框与控件 • 对话框是Windows系统中应用程序与用户交互的重要手段,程序通过对话框获取用户的输入,用户通过消息框等对话框获得程序执行情况的说明。一般情况下,应用程序越复杂,需要使用的对话框就越多
对话框的种类 • 模式对话框和非模式对话框 • 模式对话框垄断了用户的输入,当一个模式对话框打开时用户只能与该对话框进行交互,其他用户界面对象均收不到用户的输入信息 • 非模式对话框类似普通窗口,不垄断用户的输入。当打开非模式对话框后,用户仍可以与其他窗口对象进行交互
自定义对话框的设计 • 四个设计步骤 • 向项目中添加对话框模板资源。 • 编辑对话框模板资源,加入所需控件。 • 使用ClassWizard创建新的从CDialog类派生的对话框类,加入与各控件相联的数据成员。 • 使用ClassWizard进行消息映射,即将对话框资源的控件与对话框类中的消息处理函数联系起来。
程序中使用对话框 • 两个步骤 • 在视图类或框架窗口类的消息响应函数中说明一个对话框类的对象。 • 调用CDialog::DoModal()成员函数。
添加一个版权说明的对话框,并使用鼠标右键弹出 • 详见教材
非模式对话框 • 非模式对话框的创建、显示和管理都要由程序员完成 • 在对话框的创建和删除过程中,非模式对话框和模式对话框有以下区别 :
非模式对话框的模板资源在设计时必须选中Visible属性(在属性对话框的More Styles页中设置),若没有选中,则必须调用对话框类的成员函数ShowWindow()函数;否则对话框不可见,而模式对话框无需设置此属性。 • 非模式对话框通过调用CDialog::Create()函数来启动,而模式对话框使用CDialog:: DoModal()函数来启动。由于Create()函数不会启动新的消息循环,非模式对话框与应用程序共用同一个消息循环,这样非模式对话框就不会垄断用户的输入。Create()函数在显示了非模式对话框后就立即返回,而DoModal()函数是在模式对话框被关闭后才返回的。 • 非模式对话框对象是用new操作符动态创建的,而模式对话框以对象变量的形式出现的。 • 非模式对话框的关闭是由用户单击OK或Cancel按钮完成的,与模式对话框不同,程序员必须分别重载这两个函数,并且在其中调用CWnd::DestroyWindow()函数来关闭对话框。该函数是用于关闭窗口。 • 必须有一个标志表明非模式对话框是否打开。应用程序根据该标志决定是打开一个新对话框,还是仅激活原来已经打开的对话框。通常可以用拥有者窗口中指向非模式对话框对象的指针(因为非模式对话框对象是用new操作符动态创建)作为这个标志,当对话框关闭时给该指针赋NULL值,表明该对话框对象已不存在了。