• 1. Visual C++编程基础1 Windows编程基础 2 创建VC应用程序框架 3 文档与视图 4 对话框及其常用控件 5 MFC编程基础 6 Windows图形设备接口
  • 2. Visual C++编程基础首先要对Windows API 有一定了解,否则无法深入学习MFC。 不要过分依赖于VC提供的框架程序自动生成工具Wizards。 Wizards能做许多工作,但同时掩饰了太多细节。除非你理解生成的代码的含义,否则无法了解程序是如何运行的。 学会抽象的把握问题,不求甚解。 最重要的一点是理解和使用MFC类库,而不是记忆。学习VC编程的方法
  • 3. 1 Windows编程基础1.1 可视化程序设计的概念 1.2 Windows程序的特点 1.3 VC开发程序的方法 1.4 Windows运行机制 1.5 Windows图形界面 1.6 Windows基本数据类型 1.7 VC的命名规则 1.8 基本术语 1.9 Windows应用程序的基本结构
  • 4. 1 Windows编程基础1.1 可视化程序设计的概念在软件开发过程中,用直观的具有一定含义的图标按钮、图形化的对象进行编辑、运行、浏览操作。 软件开发过程表现为鼠标点击按钮和拖放图形化的对象以及指定对象的属性、行为的过程。 这种可视化的编程方法易学易用,而且大大提高了工作效率。
  • 5. 1 Windows编程基础1.2 Windows程序的特点(1)用户界面统一、友好 Windows应用程序拥有相同或相似的基本外观。 (2)独立于设备的图形操作 Windows下的应用程序使用图形设备接口(GDI)屏蔽了不同设备的差异,提供了设备无关的图形输出能力。只要对虚拟设备句柄进行操作,就能完成各种设备的图像输出操作。 (3)支持多任务 Windows是一个多任务的操作系统,允许同时运行多个应用程序。每个应用程序对应的窗口在屏幕上可以重叠,相互共享屏幕。 (4)队列化消息输入 Windows把从键盘、鼠标、定时器等输入设备接收的输入信息放入应用程序的队列中,这个队列由Windows操作系统管理。应用程序需要输入信息时,不读硬件端口,只读消息队列。
  • 6. 1 Windows编程基础1.2 Windows程序的特点(5)支持队列特征的消息驱动模型 Windows操作系统主要包括3个内核基本元件:GDI、KERNEL和USER。 GDI负责屏幕绘制与打印 KERNEL支持与操作系统密切相关的功能,如进程加载、文本切换、文件I/O、内存管理以及线程管理等 USER为所有用户界面对象提供支持,它用于接收和管理所有输入消息和系统消息,并把它们发给相应窗口的消息队列。 消息队列是一个系统定义的内存块,用于临时存储消息。 每个窗口维护自己的消息队列,并从中取出消息,利用窗口函数进行处理
  • 7. 1 Windows编程基础1.2 Windows程序的特点(6)事件驱动的程序设计 Windows程序由事件的顺序来控制。事件的发生是随机的、不确定的。 (7)资源共享 实现资源共享的基本模式如下: 向Windows操作系统请求资源; 使用该资源; 释放该资源给Windows操作系统,以供别的程序使用。 (8)程序与资源分开 全部资源定义都放在资源文件中,通常以.RC为后缀名。 (9)支持应用程序间的数据交换 动态数据交换DDE,剪贴板,内存映射文件、OLE(对象连接与嵌入),COM/DCOM(组件对象模型/分布式组件对象模型)以及Socket编程等
  • 8. 1 Windows编程基础1.3 VC开发程序的方法(1)使用Windows提供的Windows API函数开发Windows应用程序最基本的开发工具 是Windows操作系统的编程接口 提供了C语言的API(应用程序接口函数)进行Windows程序设计 所有的接口均以C函数和C结构的形式提供,这使得开发应用程序的代码量很大 Windows应用程序比传统的命令行程序有了更多的界面要求,这就使应用程序用于界面交互处理的任务更重。 API函数包括: 窗口管理函数 图形设备管理函数 系统服务函数
  • 9. 1 Windows编程基础1.3 VC开发程序的方法(2)使用Microsoft提供的MFC类库微软公司在对Windows API进行封装的基础上提供了一个开发Windows应用程序的类库MFC(Microsoft Foundation Class),它是一个面向对象的Windows编程接口,大大简化了Windows编程工作。 首先,MFC提供了一个标准化的结构,程序员不必从头创建一个标准Windows应用程序的框架。 其次,它提供了大量的代码,指导用户编程时实现某些技术和功能。 使用Visual C++ MFC类库开发Windows应用程序的主要过程是使用MFC中的各种类构建一个程序。学习MFC类库中各种类的使用也就成为学习Visual C++开发的主要任务。
  • 10. 1 Windows编程基础1.4 Windows运行机制(1)Windows程序的工作原理Windows程序设计是一种完全不同于传统的DOS方式的程序设计方法。 在Windows程序中,并不是所有的函数之间都有一种固定的先后执行关系。 Windows程序设计是一种消息驱动方式的程序设计模式 程序提供给用户的界面中有许多可操作的可视对象 用户从所有可能的操作中任意选择 被选择的操作会产生某些特定的事件 这些事件发生后的结果是向程序中的某些对象发出消息 这些对象调用相应的消息处理函数来完成特定的操作。
  • 11. 1 Windows编程基础Windows应用程序最大的特点就是程序没有固定的流程,而只是针对某个事件的处理有特定的子流程,Windows应用程序就是由许多这样的子流程构成的。 Windows应用程序在本质上是面向对象的程序 程序提供给用户界面的可视对象在程序的内部一般也是一个对象 用户对可视对象的操作通过事件驱动模式触发相应对象的可用方法 程序的运行过程就是用户的外部操作不断产生事件,这些事件又被相应的对象处理的过程。
  • 12. 1 Windows编程基础开始输入姓名输入第一次测验成绩输入第二次测验成绩输入第三次测验成绩计算平均成绩结束过程驱动
  • 13. 1 Windows编程基础开始输入姓名输入第一次测验成绩输入第二次测验成绩输入第三次测验成绩计算平均成绩结束发送消息取事件并处理退出消息驱动
  • 14. 1 Windows编程基础1.4 Windows运行机制消息驱动的程序在执行过程中,如果用户需要输入什么参数或作出选择,程序将等待用户的输入。只有用户提供了足够的数据程序才能继续进行下去,否则它将一直等待下去,应用程序不会自己选择其它的途径或完成其它的功能,用户也不能干预程序的运行过程。 消息驱动程序的控制流程是由实际运行时各种事件的实际发生来触发,而事件的发生可能是随机的、不确定的,并没有预先的顺序。 消息驱动是一种面向用户的程序设计方法,在程序设计过程中除了完成所需要的程序功能之外,更多的是要考虑用户可能的各种输入消息,并有针对性地设计相应的处理程序。(2)Windows程序运行机制-----消息驱动
  • 15. 1 Windows编程基础1.4 Windows运行机制USER.EXE消息1消息2消息3消 息 队 列窗口函数窗口1消 息 队 列窗口函数窗口1消息驱动是靠消息循环机制来实现的。 Windows应用程序的消息来源有以下四种: 输入消息 控制消息 系统消息 用户消息
  • 16. 1 Windows编程基础1.4 Windows运行机制USER.EXE消息1消息2消息3消 息 队 列窗口函数窗口1消 息 队 列窗口函数窗口1消息驱动程序设计一种被动式的程序设计方法,程序开始运行时,处于等待消息状态,当取得消息后就对其作出相应反应,处理完毕后又返回等待消息的状态。消息驱动的程序就是一个不断产生消息和处理消息的过程是一个更加模块化、更加独立的程序。
  • 17. 1 Windows编程基础Windows程序设计是消息驱动的,消息传递是它的核心,而消息管理是由 Windows完成的,应用程序要获得消息就要从操作系统中获得。应用程序有两种获得消息的方式: 应用程序调用Windows提供的获取消息函数,如GetMessage(); 由Windows调用程序员提供的一种特殊函数——回调函数。 回调函数一经设计好,就成了Windows系统的扩展,就会在发生别的事件时,由操作系统自动调用该回调函数。 在应用程序的任何地方找不到明显调用该函数的地方,用DOS编程观点来看,似乎此函数并没有用到,事实上回调函数不仅用到了,而且起了很大作用,正是通过它,应用程序才获得了消息,才知道当前发生了什么事件,才根据不同的事件采取不同的处理措施。
  • 18. 1 Windows编程基础1.5 Windows图像界面Windows程序使用图形用户界面进行输出,其基本特点是: Windows的每一个应用程序对屏幕的一部分进行处理 Windows是一个多窗口的操作系统,由操作系统来统一管理屏幕输出。 程序的所有输出都是图形。 Windows下的输出是设备无关的(图形设备接口(GDl))Windows支持丰富的图形用户界面对象,包括窗口、图标、对话框等。
  • 19. 1 Windows编程基础
  • 20. 1 Windows编程基础1.6 Windows基本数据类型在标准的C/C++语言中,int、char、float、double都是常见的数据类型,而在32位Windows的C/C++开发平台中,为了满足数据定义需要,又添加了一系列新的数据类型。 下表列出了较为常见的数据类型,大体包括了字符型、整型、浮点型、布尔型、指针型以及Windows应用程序所特有的句柄型,表示指针型的数据类型往往以P或LP作为前缀,而句柄型则总是冠以H。
  • 21. 1 Windows编程基础1.6 Windows基本数据类型类型定义BOOL布尔型(逻辑型)变量(应为TRUE或FALSE)BYTE字节(8位)CHARWindows字符COLORREFRGB(红绿蓝)颜色值(32位)CONST在执行时其值保持不变的变量DLGPROC指向应用程序定义的对话框回调函数的指针DWORD双字(32位)FARPROC指向回调函数的指针FLOAT浮点型变量
  • 22. 1 Windows编程基础1.6 Windows基本数据类型HACCEL加速键表句柄HANDLE对象句柄HBRUSH画刷句柄HDC设备描述表句柄HFILE文件句柄HINSTANCE实例句柄HMENU菜单句柄HWND窗口句柄LONG32位无符号数
  • 23. 1 Windows编程基础LPARAM32位消息参数LPSTR指向Windows字符串(以空字符结束)的指针LPVOID指向任意类型的指针UCHAR无符号Windows字符UINT无符号整数VOID任意类型WNDPROC指向在应用程序中定义的窗口函数的指针WORD无符号字(16位)WPARAM32位消息参数1.6 Windows基本数据类型
  • 24. 1 Windows编程基础1.7 VC的命名规则匈牙利命名法成为大家一致使用的变量名约定,是事实上的标准,它的名字来源于它的匈牙利籍开发者,Microsoft程序员Charles Simonyi。 匈牙利命名规则建议变量名称以一个或多个小写字母开头,这些小写字母用以标识其变量类型,类型标识字母的下一个字母一般采用大写。类型定义使用相同的类型标识符作为前缀,唯一不同的是,类型定义名称一般全部使用大写。 例如,变量iNum自身名称就说明它为整型,变量hWnd说明其为指向应用程序窗口的句柄,变量lpszString表示它是一个指向以NULL结束的字符串的长指针。 下表列出了一般的匈牙利命名原则。(1)匈牙利命名规则
  • 25. 1 Windows编程基础1.7 VC的命名规则前缀数据类型例子bBooLeanIsPresent,bValidbybyte,unsigned charbyFlag,byBlockchcharcchArray, chTextCClassCString,CmainCsCStringCsName,csLabeldwDWORDdwFlagshhandlehWnd,hDlg,hBrushiintiCount,iNumnunsigned intnMax,nLimitppointerPszString(指向以NULL结尾的字符串)szASCII stringszName(指向以NULL结尾的字符串)vvoidvPointer
  • 26. 1 Windows编程基础1.7 VC的命名规则(2)其它前缀约定AFX和MFC对象类使用的其它一些前缀在下表中中列出。 在这组前缀中,只有两个C类前缀和m_成员变量可能在自己写的程序代码中出现,其余前缀主要用于MFC/Windows库中。前缀类型例子C类和结构CDocument,CStringm类成员变量m_nVal,m_bFlagAfx应用程序框架公共函数AfxGetMainWnd()Afx应用程序框架公共变量afxDump_Afx应用程序框架内部函数_AfxGetPtrFromFarPtr()_afx应用程序框架内部变量_afxExLinkAFX_应用程序框架内部结构AFX_CMDHANDLERAFX_ID应用程序框架内部标识符AFX_ID_PREVIEW_PRINT
  • 27. 1 Windows编程基础1.7 VC的命名规则(3) AFX和AppWizard前缀VC AppWizard和ClassWizard不但能生成大量有用代码帮助你链接已有代码和对话框资源元素,而且还能产生使用标准符号前缀的标识符。前缀符号类型例子ID_菜单项或工具栏按钮ID_TOOL_SEARCHIDB_位图资源IDB_LOGOIDC光标资源IDC_TARGET_CURSORIDC_对话框控件IDC_REPORTIDD_对话框资源标识符IDD_SEARCHIDI_图标资源IDI_APP_ICONIDP_消息框提示IDP_SEARCH_FORIDR_多种类型共享的资源IDR_MAINFRAMIDS_字符串IDS_CAUTION
  • 28. 1 Windows编程基础1.8 基本术语 一个实例代表一个可执行程序在内存中的拷贝。在Windows系统中,可执行程序的每次运行都代表完全独立的进程,各个进程都有自己的虚拟地址空间。若一个应用程序执行许多次,在内存中就有多个程序的拷贝,也可以说是一个应用程序在内存中有多个实例。 应用程序的实例句柄对于管理整个应用程序的资源很重要,因此我们常常在Windows程序的入口WinMain()处将实例句柄保存在全局变量中。 (1)实例
  • 29. 1 Windows编程基础1.8 基本术语Windows对绝大多数对象的引用都是通过句柄来进行的。 在Windows环境中,句柄可以用来标识模块、任务、实例、文件、内存块、菜单、控件、字体、资源、,包括图标、光标、字符串、GDI对象(包括位图)、画刷、元文件、调色板、画笔、区域以及设备描述表。 Windows程序并不是用物理地址来标识一个内存块、文件、任务或动态装入模块的。 Windows API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。 句柄是内部表格的索引值,Windows通过句柄到内部表格中去存取相关信息。因此应用程序只能处理到句柄的层次,并不能直接获取表格中的数据。 下表是常见的句柄类型及其含义。(2)句柄
  • 30. 1 Windows编程基础1.8 基本术语句柄类型含义句柄类型含义HWND窗口句柄HDC设备环境句柄HINSTANCE实例句柄HBITMAP位图句柄HCURSOR光标句柄HICON图标句柄HBRUSH画刷句柄HMENU菜单句柄HPEN画笔句柄HFONT字体句柄
  • 31. 1 Windows编程基础1.8 基本术语一个Windows程序,往往有菜单、工具条、对话框、位图、图标、加速键、字符串等,这些元素都被称为资源。 应用程序不仅可以使用操作系统所提供的资源,而且可以在VC开发环境中利用相应的资源编辑器建立自己的资源。 应用程序的资源被定义在.RC文件中,资源可以预先编辑、独立编译,最后与可执行模块链接在一起,形成可独立执行的.EXE文件或动态链接库文件.DLL。 当应用程序需要这些资源时,可以利用Windows的API函数将这些资源调入。 可见,Windows应用程序的资源是自己携带的,这要比将资源作为外部文件方便的多。由于资源与程序代码是分离的,“招之即来,挥之即去”,当使用时才调入内存,这减少了程序运行时对内存的消耗量。(3)资源
  • 32. 1 Windows编程基础1.8 基本术语Windows应用程序的全部输出(包括显示和打印)都必须通过图形设备接口(Graphic Device Interface,简称GDI)中的函数来完成。 GDI是Windows系统重要组成部分,负责系统与用户或绘图程序之间的信息交换,并控制在输出设备上显示图形或文字。 其最大作用就是将程序员与具体的物理设备相隔离,使得程序员没有必要考虑具体设备的操作细节(如显示缓存的地址、打印机的端口等),无论是使用显示还是打印,使用的都是同一个API函数。 通俗的来说,我们可以把GDI看作一个大的绘图工具箱,所有在窗口上所绘制的东西,都必须使用工具箱中的东西,即GDI中函数。学习GDI,关键是要搞清楚这些函数的分类及其用法。(4)图形设备接口
  • 33. 1 Windows编程基础1.8 基本术语回调函数,顾名思义,就是写好了等着别人来调用的函数。这些函数的原型都是由调用者自己定义好的,使用的时候,只要按照原型定义一个函数,然后将函数指针传递过去就行了。它们只能通过Windows操作系统来调用,这些函数我们可以根据自己的需要设计,但不能直接调用它们。 回调函数必须严格地按Windows系统的规定进行说明和定义。前面所所的“窗口函数”就是一个典型的回调函数,当注册窗口类时,要将函数地址告诉Windows,Windows通过调用该窗口函数来让窗口处理消息。当发送消息给程序时,Windows调用此函数进行消息的处理。窗口函数调用约定、返回值以及参数都是固定的,程序员必须按其规定进行才能正常工作。(5)回调函数
  • 34. 1 Windows编程基础1.9 Windows应用程序的基本结构一个Windows程序可以分解成两个基本部分:命名为WinMain的主函数和窗口函数。 WinMain主函数:应用程序的入口点,类似于C的main()函数。 初始化工作,包括定义窗口类、注册窗口类、创建窗口、显示窗口和其它必要的初始化工作。 进入消息循环,以处理来自应用程序队列的消息。 当消息循环检测到WM_QUIT消息时,终止应用程序。 窗口函数 处理应用程序接收到的消息的函数。其中包含了应用程序对各种可能接受到的消息的处理过程。 函数由一个或多个switch语句组成,每条case语句对应一种消息。
  • 35. 1 Windows编程基础(1)WinMain主函数int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow ) 应用程序的当前实例句柄,Windows运行程序时赋给它一个值应用程序的前一个驱动实例句柄,标识同一程序最近的一个仍然活动的实例句柄,如果本实例是第一个实例,则Windows置该参数为NULL。指向以空字符串结尾的命令行字符串的指针,该字符串包含有任何传送给程序的命令行参数。如果应用程序通过Windows的Dos Executive窗口启动,lpszCmdLine的值为NULL。用以指定应用程序的窗口初始化时的类型,即指出它是窗口(SW_SHOWNORMAL)还是图标(SW_SHOWMINNOACTIVE)。应用程序在调用该函数时,把该值传给ShowWindows函数,以显示应用程序的主窗口。
  • 36. 1 Windows编程基础(2)定义窗口类窗口类是定义窗口属性的模板,这些属性包括窗口式样、光标形状、色彩及标题横条等等。窗口类也指定处理该类中所有窗口消息的窗口函数。 只有先建立窗口种类,才能根据窗口种类来创建Windows应用程序的一个或多个窗口。 窗口类定义通过给窗口类数据结构WNDCLASS赋值完成。该数据结构包含窗口类的各种属性。
  • 37. 1 Windows编程基础(2)定义窗口类typedef struct _WNDCLASS { UINT style; //窗口的风格* WNDPROC lpfnWndProc;//指定窗口的消息处理函数的远指针* int cbClsExtra; //指定分配给窗口类结构之后的额外字节数* int cbWndExtra; //指定分配给窗口实例之后的额外字节数 HANDLE hInstance; //指定窗口过程所对应的实例句柄* HICON hIcon; //指定窗口的图标 HCURSOR hCursor; //指定窗口的鼠标 HBRUSH hbrBackground; //指定窗口的背景画刷 LPCTSTR lpszMenuName; //窗口的菜单资源名称 LPCTSTR lpszClassName; //该窗口类的名称* } WNDCLASS;
  • 38. 1 Windows编程基础style风 格含 义 CS_HREDRAW如果窗口客户区宽度发生改变,重绘整个窗口 CS_VREDRAW如果窗口客户区高度发生改变,重绘整个窗口 CS_DBLCLKS能感受用户在窗口中的双击消息 CS_NOCLOSE禁用系统菜单中的“关闭”命令 CS_OWNDC为该窗口类的各窗口分配各自独立的设备环境 CS_CLASSDC为该窗口类的各窗口分配一个共享的设备环境 CS_PARENTDC指定子窗口继承其父窗口的设备环境 CS_SAVEBITS把被窗口遮掩的屏幕图象部分作为位图保存起来。当该窗口被移动时,Windows使用被保存的位图来重建屏幕图象
  • 39. 1 Windows编程基础WNDCLASS wndclass ; char lpszClassName[] = "窗口示例"; //窗口类名 //窗口类的定义 wndclass.style = 0; //窗口类型为缺省类型 wndclass.lpfnWndProc = WndProc ;//窗口处理函数WndProc wndclass.cbClsExtra = 0 ; //窗口类无扩展 wndclass.cbWndExtra = 0 ; //窗口实例无扩展 wndclass.hInstance = hInstance ; //当前实例句柄 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION) ; //窗口的最小化图标为缺省图标 wndclass.hCursor = LoadCursor( NULL,IDC_ARROW) ;//窗口采用箭头光标 wndclass.hbrBackground = GetStockObject(WHITE_BRUSH) ;//背景为白色 wndclass.lpszMenuName = NULL ; //窗口中无菜单 wndclass.lpszClassName = lpszClassName ; //窗口类名为"窗口示例"
  • 40. 1 Windows编程基础(3)注册窗口类窗口类不能重名。所以在建立窗口类之后,必须向Windows登记,这由Windows的注册函数RegisterClass来实现。 如果没有首先注册窗口类,则不能创建窗口。可以在程序中判断注册是否成功。若注册成功返回值为TRUE,否则返回FALSE。 if( !RegisterClass( &wndclass)) //如果注册失败则发出警告声音 { MessageBeep(0) ; return FALSE ; }
  • 41. 1 Windows编程基础(4)创建窗口注册窗口类后,就可以使用此窗口类创建应用程序窗口的某一实例。HWND CreateWindow( LPCTSTR lpszClassName,//窗口类名 LPCTSTR lpszTitle, //窗口实例的标题名 DWORD dwStyle, //窗口的风格 int x, int y, //窗口左上角坐标为缺省值 int nWidth, int nHeight, //窗口的高和宽为缺省值 HWND hwndParent, //此窗口的父窗口句柄 HMenu hMenu, //此窗口主菜单句柄 HINSTANCE hInstance, //创建此窗口的应用程序的当前句柄 LPVOID lpParam ) ; //指向一个传递给窗口的参数值的指针
  • 42. 1 Windows编程基础创建窗口实例 hwnd = CreateWindow( lpszClassName, //窗口类名 lpszTitle, //窗口实例的标题名 WS_OVERLAPPEDWINDOW, //窗口的风格 CW_USEDEFAULT, CW_USEDEFAULT, //窗口左上角坐标为缺省值 CW_USEDEFAULT, CW_USEDEFAULT, //窗口的高和宽为缺省值 NULL, //此窗口无父窗口 NULL, //此窗口无主菜单 hInstance, //创建此窗口的应用程序的当前句柄 NULL) ; //不使用该值
  • 43. 1 Windows编程基础标 识说 明WS_BORDER带边框的窗口WS_CAPTION带标题栏的窗口WS_HSCROLL带水平滚动条的窗口WS_MAXIMIZEBOX带最大化按钮的窗口WS_MAXIMIZE最大化的窗口WS_MINIMIZEBOX带最小化按钮的窗口WS_MINIMIZE最小化的窗口WS_OVERLAPPED带边框和标题栏的窗口WS_OVERLAPPEDWINDOW带边框、标题栏、系统菜单及最大、最小化按钮的窗口WS_POPUP弹出式窗口WS_POPUPWINDOW带边框和系统菜单的弹出式窗口WS_SYSMENU带系统菜单的窗口WS_VSCROLL带垂直滚动条的窗口
  • 44. 1 Windows编程基础(5)显示和更新窗口创建窗口后,还要用ShowWindow函数才能将窗口显示在屏幕上。 调用形式为: ShowWindow(hWnd,nCmdShow); hWnd:是窗口句柄 nCmdShow:窗口初始显示的形式。 对应用程序的主窗口来说,WinMain在创建窗口以后显示窗口时,把其nCmdShow参数传给ShowWindow函数。显示窗口之后,WinMain还要调用UpdateWindow函数更新和绘制用户区。 UpdateWindow函数传送消息给特定窗口的窗口函数。窗口函数WndProc处理输入的信息,并根据这些信息再送出信息。 例如,调用UpdateWindow会导致产生WM_PAINT消息,由此实现重画用户区的内容,即更新窗口。 UpdateWindow函数的调用格式如下:UpdateWindow(hWnd);
  • 45. 1 Windows编程基础(6)创建消息循环应用程序主窗口显示之后,WinMain函数就开始履行它的主要职责,即处理消息。 Windows并不是把键盘和鼠标等的输入直接送给应用程序,而是把所有输入放到应用程序队列中去。应用程序队列还接收包括来自Windows和其它应用程序的消息。 应用程序的WinMain函数通过执行一段代码从应用程序队列中检索输入消息,并把它们分配给相应的窗口函数以便处理它们,这段代码实际上是一段循环代码,称为“消息循环”。
  • 46. 1 Windows编程基础“消息循环”的典型形式为; MSG Msg; …… while( GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage( &Msg) ; DispatchMessage( &Msg) ; }这几条语句组成主消息循环,是Windows应用程序的关键所在。 Windows为每一个正在运行的应用程序保持一个消息队列。 当任一键或鼠标按钮被按下时,Windows将输入的事件翻译成一个消息,并将此消息放在该程序的消息队列中去。 应用程序从消息循环接受消息。
  • 47. 1 Windows编程基础在Windows中,消息往往用一个结构体MSG来表示。 MSG的结构在Windows.h中定义如 typedef struct tagMSG{ HWND hwnd; //接收消息的窗口句柄 WORD message; //消息号 WORD wParam; //消息的附加信息,确切含义取决于消息号 LONG lParam; //消息的另一附加信息,确切含义取决于消息号 DWORD time; //消息被传送的时间 POINT pt; //消息被发送时光标的位置。POINT结构有X,Y两个域 } MSG;
  • 48. 1 Windows编程基础GetMessage函数:从消息队列中读取一条消息,并将消息放在MSG结构中 BOOL GetMessage( LPMSG lpMsg, //指向MSG结构的指针 HWND hWnd, // 窗口句柄 // 为NULL表示要获取应用程序要创建的所有窗口的消息。 UINT wMsgFilterMin, // 最小消息值号 UINT wMsgFilterMax //最大消息值号 //第三、第四参数指定消息范围,均为NULL表示获取所有消息。 ); 当消息队列中没有消息(或只有WM_PAINT和WM_TIMER消息)时,GetMessage在这期间可以将控制权交给其它应用程序。只有当获取的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。
  • 49. 1 Windows编程基础TranslateMessage函数:负责将消息的虚拟键转换为字符信息。 BOOL TranslateMessage( CONST MSG *lpMsg // 指向MSG结构的指针 ); 当应用程序中需要处理键盘输入时,消息循环中必须用TranslateMessage函数转换接收到的每个消息。TranslateMessage函数检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符。
  • 50. 1 Windows编程基础DispatchMessage函数:将参数lpmsg指向的消息传送给指定的窗口函数 LONG DispatchMessage( CONST MSG *lpmsg //指向MSG结构的指针 ); DispatchMessage函数将再次传送已经传送到Windows里的消息,当Windows收到从DispatchMessage传来的消息时,将把它传送到此窗口的窗口函数中去。
  • 51. 1 Windows编程基础(7) 终止应用程序 一旦WinMain函数进入消息循环,终止循环的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。 当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息循环。 通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。
  • 52. 1 Windows编程基础(8) 窗口函数每个Windows应用程序必须有一个窗口函数,且Windows直接调用本函数。 函数名必须是前面注册窗口类时,向域wndclass.lpfnWndProc所赋的值。 窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。 窗口函数检查每一条消息,然后根据这些消息执行特定的动作,未被处理的消息通过DefWindowProc函数传回给Windows作缺省处理。LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { //上面四个参数分别对应于MSG结构的前四个域,窗口句柄,消息类型,附加消息 switch(message) { case WM_DESTROY: PostQuitMessage(0); //调用PostQuitMessage发出WM_QUIT消息 default: //默认时采用系统消息默认处理函数 return DefWindowProc(hwnd,message,wParam,lParam); } }
  • 53. 1 Windows编程基础 可以发往窗口函数的消息约有220种,所有窗口消息都以WM_开头,这些消息在windows.h中定义为常量。Windows应用程序常用的窗口消息WM_LBUTTONDOWN WM_LBUTTONUP WM_RBUTTONDOWN WM_RBUTTONUP WM_LBUTTONDBLCLK WM_RBUTTONDBLCLK鼠标产生的消息:键盘产生的消息WM_KEYDOWN WM_KEYUP WM_CHAR与窗口创建和管理有关的消息WM_CREATE WM_CLOSE WM_DESTROY WM_QUIT WM_PAINT
  • 54. 1 Windows编程基础消息的分类不同的前缀符号,用于标志不同类型的消息 BM:按钮控制消息 CB:组合框控制消息 DM:默认下压式按钮控制消息 EM:编辑框控制消息 LB:列表框控制消息 SBM:滚动条控制消息 WM:窗口消息
  • 55. 1 Windows编程基础在窗口函数WndProc()的消息处理分支switch-case结构中添加WM_LBUTTONDOWN鼠标单击消息及其处理代码: case WM_LBUTTONDOWN: MessageBox(NULL, "You pressed the left button of mouse !","Message",NULL); break; 在窗口函数WndProc()的switch消息处理分支中添加键盘消息的处理代码: case WM_KEYDOWN: // 处理键盘消息 if(wParam==0x41) // A或a键的虚键码为0x41H MessageBox(NULL, "The key you pressed is A or a !","KEYDOWN",NULL); else MessageBox(NULL, "The key you pressed is not A or a !","KEYDOWN",NULL); break;
  • 56. 2 创建VC应用程序框架2.1 应用程序向导 2.2 应用程序向导生成的文件 2.3 ClassWizard类向导 同一类型应用程序的结构大致相同,并有很多相同的源代码,因此可以通过一个应用程序框架AFX(Application FrameWorks)编写同一类型应用程序的通用源代码。
  • 57. 与其它可视化开发工具一样,Visual C++提供了创建应用程序框架的向导AppWizard和相关的开发工具。 在可视化开发环境下,生成一个应用程序要做的工作主要包括编写源代码、添加资源和设置编译方式。向导实质上是一个源代码生成器,利用应用程序向导可以快速创建各种风格的应用程序框架,自动生成程序通用的源代码,这样大大减轻了手工编写代码的工作量。 即使不非常熟悉Visual C++编程,也可以利用它的应用程序向导创建一个简单的应用程序。2.1 应用程序向导2 创建VC应用程序框架 创建一个应用程序,首先要创建一个项目。项目用于管理组成应用程序的所有元素,并由它生成应用程序。Visual C++集成开发环境包含了创建各种类型应用程序的向导,执行File菜单中的New命令即可看到向导类型。
  • 58. Datebase Project:创建数据库项目 MFC ActiveX Control Wizard:创建基于MFC的ActiveX控件 MFC AppWizard[dll]:创建基于MFC的动态链接库 MFC AppWizard[exe]:创建基于MFC的应用程序(最常用) New Database Wizard:在SQL服务器上创建一个SQL Server数据库 Win32 Application:创建Win32应用程序,可不使用MFC,采用SDK方法编程 Win32 Console Application:创建DOS下的Win32控制台应用程序,采用C++或C语言进行编程 Win32 Dynamic-link Library:创建Win32动态链接库,采用SDK方法 Win32 Static Library:创建Win32静态链接库,采用SDK方法2 创建VC应用程序框架(1)主要向导类型
  • 59. (2)使用MFC AppWizard[exe] 区别于DOS程序,即使一个简单的Windows程序,它也必须显示一个程序运行窗口,需要编写复杂的程序代码。而同一类型应用程序的框架窗口风格是相同的,如相同的菜单栏、工具栏、状态栏和用户区。并且,基本菜单命令的功能也是一样的,如相同的文件操作和编辑命令。所以,同一类型应用程序建立框架窗口的基本代码都是一样的,尽管有些参数不尽相同。为了避免程序员重复编写这些代码,一般的可视化软件开发工具都提供了创建Windows应用程序框架的向导。2 创建VC应用程序框架
  • 60. MFC AppWizard[exe]的功能MFC AppWizard[exe]是创建基于MFC的Windows应用程序的向导。当利用MFC AppWizard[exe]创建一个项目时,它能够自动生成一个MFC应用程序的框架。 即使不添加任何代码,当执行编译、链接命令后,Visual C++ IDE 将生成一个Windows界面风格的应用程序。 MFC应用程序框架将那些每个应用程序都共同需要使用的代码封装起来,如完成默认的程序初始化功能、建立应用程序界面和处理基本的Windows消息,使程序员不必做这些重复的工作,把精力放在编写实质性的代码上。 MFC AppWizard[exe]向导提供了一系列选项,程序员通过选择不同的选项,可以创建不同类型和风格的MFC应用程序,并可定制不同的程序界面窗口。例如,单文档、多文档、基于对话框的程序,是否支持数据库操作、是否可以使用ActiveX控件以及是否具有联机帮助等。2 创建VC应用程序框架(2)使用MFC AppWizard[exe]
  • 61. 例 编写一个单文档应用程序Mysdi,程序运行后在程序视图窗口显示信息“这是一个单文档程序!”。在Visual C++ 中执行File|New命令,出现New对话框。 确认New对话框的当前页面为Project,在左栏的项目类型列表框中选择MFC AppWizard[exe]项,在Project Name框输入要创建项目的名称。在location栏中输入项目所在的目录,可单击右侧“…”浏览按钮来对默认的目录进行修改。向导将在该目录下存放项目的所有文件。单击OK按钮出现MFC AppWizard-Step1对话框。 在MFC AppWizard-Step1对话框中选择应用程序的类型。2 创建VC应用程序框架
  • 62. 2 创建VC应用程序框架
  • 63. 2 创建VC应用程序框架
  • 64. Single document:单文档界面应用程序,程序运行后出现标准的Windows界面,它由框架(包括菜单栏、工具栏和状态栏)和用户区组成。并且程序运行后一次只能打开一个文档,如Windows自带的记事本Notepad。 Multiple documents:多文档界面应用程序,程序运行后出现标准的Windows界面,并且可以同时打开多个文档,如Word。 Dialog based:基于对话框的应用程序,程序运行后首先出现一个对话框界面,如计算器Calculator。MFC AppWizard[exe]创建应用程序的类型:2 创建VC应用程序框架
  • 65. None:向导创建的应用程序不包括对数据库的操作功能,但以后可以手工添加对数据库的操作代码(默认项)。 Header files only:提供了最简单的数据库支持,仅在项目的stdAfx.h文件中使用#include指令包含afxdb.h和afxdao.h两个用于定义数据库类的头文件,但并不生成与数据库相关的类,用户需要时可以自己生成。 Database view without file support:包含了所有的数据库头文件,并生成了相关的数据库类和视图类,但不支持文档的序列化,向导创建的应用程序的File主菜单中将不包含有关文件操作的菜单命令项。 Database view with file support:包含了所有的数据库头文件,生成了相关的数据库类和视图类,支持文档的序列化。在MFC AppWizard-Step 2 of 6选择应用程序所支持的数据库方式,包括以下选项:2 创建VC应用程序框架
  • 66. 2 创建VC应用程序框架
  • 67. None:应用程序不支持任何复合文档(默认项)。 Container:应用程序作为复合文档容器,能容纳所嵌入或链接的复合文档对象。 Mini-server:微型复合文档服务器,应用程序可以创建和管理复合文档对象,但对于它所创建的复合文档对象,集成应用程序可以嵌入,但不能链接。微型服务器不能作为一个单独的程序运行,而只能由集成应用程序来启动。 Full-server:完全复合文档服务器,除了具备上面微型服务器的功能外,应用程序支持链接式对象,并可作为一个单独的程序运行。 Both container and server:应用程序既可作为一个复合文档容器,又可作为一个可单独运行的复合文档服务器。在MFC AppWizard-Step 3 of 6选择应用程序所支持的数据库方式复合文档类型,包括以下选项:2 创建VC应用程序框架
  • 68. 2 创建VC应用程序框架
  • 69. Docking toolbar:应用程序具有标准的工具栏(默认项) Initial status bar:应用程序具有标准的状态栏(默认项) Printing and print preview:应用程序支持打印和打印预览功能(默认项) Context-sensitive Help:应用程序具有上下文相关帮助功能 3D controls:应用程序界面具有三维外观(默认项) Normal:应用程序采用传统风格的工具栏(默认项) Internet Explorer ReBars:应用程序采用IE风格的工具栏 How many files would you like on your recent file list: 在File主菜单可列出文档的最多个数。 Advanced按钮:进行更高一级的设置,如修改文件名或扩展名,调整程序用户界面窗口的样式(边框厚度和最小化、最大化、关闭按钮)。在MFC AppWizard-Step 4 of 6设置应用程序界面特征(工具栏和状态栏),包括以下选项:2 创建VC应用程序框架
  • 70. 2 创建VC应用程序框架
  • 71. MFC Standard:应用程序采用MFC标准风格(默认项) Windows Explorer:应用程序采用Windows资源管理器 风格 选择向导是否为源代码生成注释 设置MFC库与应用程序的链接方式: (1) As a shared DLL:采用共享动态链接库的方式(默认项) ,即在程序运行时才调用MFC库。采用此方式可减少应用程序的代码长度。 (2) As a statically linked library:采用静态链接库的方式,即在编译时把MFC库与应用程序相链接。采用此方式能提高运行速度,且不用考虑程序最终运行环境中是否安装了MFC库。在MFC AppWizard-Step 5 of 6设置项目的风格,包括以下选项:2 创建VC应用程序框架
  • 72. 2 创建VC应用程序框架
  • 73. 在New Project Information对话框,根据用户在前面各步所做的选择列出将要创建的应用程序的有关信息,如应用程序的类型、创建的类和文件名、应用程序的特征以及项目所在的目录。若要修改这些内容,可单击Cancel按钮返回到前一个对话框。单击OK按钮,MFC AppWizard[exe]向导将开始创建应用程序框架。 在MFC AppWizard-Step 6 of 6,列出了向导将创建的类,用户可以修改一些类默认的类名和对应的头文件名、实现文件名。对某些类还可以选择不同的基类。2 创建VC应用程序框架
  • 74. 2 创建VC应用程序框架
  • 75. 利用MFC AppWizard[exe]向导创建应用程序Mysdi的框架后,用户无需添加任何代码,就可以对程序进行编译、链接,生成一个应用程序。但一般情况下,用户应根据程序具体功能需要,利用Developer Studio中的集成工具向应用程序框架添加具体的代码。 当应用程序框架创建成功后,Developer Studio将装入应用程序项目,并在项目工作区窗口打开这个项目。值得说明的是,若想在同一个目录下重新创建一个同名的项目,必须首先将原来的项目删除或移走。2 创建VC应用程序框架
  • 76. 在视图类的成员函数OnDraw()中实现屏幕输出void CMysdiView::OnDraw(CDC* pDC) { CMysdiDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here pDC->TextOut(100, 100, “这是一个单文档程序!”); // 在坐标(100, 100)处显示文本串 } 执行Build(F7)命令编译链接程序,程序运行后将在程序视图区域显示文本串。2 创建VC应用程序框架
  • 77. 2 创建VC应用程序框架
  • 78. 例2 编写一个基于对话框的应用程序MyDialog,程序运行后显示一个对话框。 编程说明与实现 1. 执行File|New命令,在New对话框中选择MFC AppWizard[exe]项,输入程序名MyDialog。单击OK按钮,出现MFC AppWizard-Step 1。 2. 在MFC AppWizard-Step 1中选择Dialog Based选项,单击Next按钮,出现MFC AppWizard-Step 2 of 4。 3. 在MFC AppWizard-Step 2 of 4中设置应用程序界面特征。 4. 创建对话框应用程序后续的步骤与创建单文档或多文档应用程序的Step 5和Step 6 相同。 2 创建VC应用程序框架
  • 79. 2 创建VC应用程序框架
  • 80. 利用Visual C++编写Windows应用程序可以采取几种不同的途径: 利用 Win32应用程序接口API(Application Programming Interface)提供的函数,用C或C++语言编写程序。 由于MFC通过类对API的绝大部分功能进行了封装,可以利用MFC,用C++语言编写程序。 利用MFC和MFC AppWizard[exe],首先利用 MFC AppWizard[exe]应用程序向导生成基本的应用程序框架,然后按照MFC机制和原理向框架添加具体的应用代码。4.1.3 MFC应用程序的开发流程 2 创建VC应用程序框架
  • 81. 根据应用程序特性在MFC AppWizard[exe]应用程序向导各步骤对话框中进行选择,创建一个应用程序的框架。 利用资源编辑器为程序编辑或添加资源,如编辑菜单、添加对话框等。 利用ClassWizard类向导或手工添加类、成员变量和成员函数的声明。 根据需要编写具体的函数代码。 编译、链接程序。如果程序有语法错误,需要修改源程序。直到没有编译、链接错误,才能得到可执行程序。 测试应用程序各项功能,如果程序没有实现程序设计所要求的功能,启动Debug调试器进行调试,找出并修改程序设计中的逻辑错误。编写一个实现具体功能的MFC应用程序的步骤:2 创建VC应用程序框架
  • 82. ClassWizard类向导: Visual C++ IDE为MFC提供了大量的支持工具,除了MFC AppWizard[exe]向导,还提供了ClassWizard类向导,利用它程序员可以方便地增加或删除对某个消息的处理。资源编辑器: 为程序添加具体的代码时还经常要用到资源编辑器。下图形象地说明了编写一个MFC应用程序的流程、所用到的工具及主要生成的文件。2 创建VC应用程序框架
  • 83. 2 创建VC应用程序框架
  • 84. 编程时,除了编辑现有的资源,有时需要向项目添加新的资源,这时可以利用Insert菜单创建一个新的资源。 打开Insert菜单,选择Resource菜单项,出现Insert Resource对话框,如下图所示,在Resource Type框中选择一个资源类型,单击New按钮即可向项目添加一个资源。2 创建VC应用程序框架
  • 85. MFC类库将所有图形用户界面的元素如窗口、菜单和按钮等都以类的形式进行了封装,编程时需要利用C++类的继承性从MFC类中派生出自己的类,实现标准Windows应用程序的功能。 MFC AppWizard[exe]向导对Windows应用程序进行了分解,并利用MFC的派生类对应用程序重新进行了组装,同时还规定了应用程序中所用到的MFC派生类对象之间的相互联系,这就是向导生成的MFC应用程序框架。MFC应用程序框架实质上是一个标准的Windows应用程序,它具有标准的窗口、菜单栏和工具栏。4.2 应用程序向导生成的文件 2 创建VC应用程序框架
  • 86. 为了生成一个可执行程序,MFC AppWizard[exe]向导必须首先创建一个项目,并为项目生成一系列初始文件,如C++头文件、C++源文件、资源文件和项目文件,其中的C++文件都是以MFC派生类为单元来组织的。 如果要从项目中删除文件,首先在项目工作区FileView页面中选中要删除的文件,然后按Delete键。但注意这并没有从硬盘上真正删除了该文件。 Visual C++中的文件类型很多,根据项目类型不同而产生不同类型的文件。下面列出了MFC AppWizard[exe]应用程序向导生成的文件类型。当进行编辑、编译和链接时,还要生成一些临时文件。 4.2.1 应用程序向导生成的文件类型 2 创建VC应用程序框架
  • 87. Visual C++中通用的文件类型后缀 类型 说明 dsw 工作区文件 将项目的详细情况组合到工作区中 dsp 项目文件 存储项目的详细情况并替代mak文件 h C++头文件 存储类的定义代码 cpp C++源文件 存储类的成员函数的实现代码 rc 资源脚本文件 存储菜单、工具栏和对话框等资源 rc2 资源文件 用来将资源包含到项目中 ico 图标文件 存储应用程序图标 bmp 位图文件 存储位图 clw 类向导文件 存储ClassWizard类向导使用的类信息2 创建VC应用程序框架
  • 88. Visual C++中类的支持文件一般而言,C++中的一个类由头文件h和源文件cpp两类文件支持。 头文件用于定义类,包括指明派生关系、声明成员变量和成员函数。 源文件用于实现类,主要定义成员函数的实现代码和消息映射。 例如,应用程序视图类CMysdiView的两个支持文件是MysdiView.h和MysdiView.cpp。2 创建VC应用程序框架
  • 89. 4.2.2 应用程序向导生成的头文件 MFC AppWizard[exe]向导为一般的SDI应用程序生成了五个类,这些类都是MFC类的派生类。这里的“一般的SDI应用程序”是指向导每一步都采用默认选项,如不支持数据库和OLE对象等。 一般应用程序框架中所有类的名字由MFC AppWizard[exe]向导根据一定的规则自动命名,但用户可以在向导的第6步改变类名和有关类的文件名。应用程序框架中类(框架窗口类例外)的命名规则一般遵照如下要求: Class Name = C + ProjectName + ClassType2 创建VC应用程序框架
  • 90. 注:在定义类的头文件中的开始位置(类的正式定义前)有一段预处理命令代码,这些代码是为Developer Studio自身准备的,如用于保证头文件在编译时仅被编译一次。Developer Studio中的资源编辑器、ClassWizard类向导和编译器都可能用到这些代码。这些代码如下形式所示: #if !defined(AFX_MAINFRM_H__DE1F30C9_677C_11D6_888 #define AFX_MAINFRM_H__DE1F30C9_677C_11D6_888D_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 10002 创建VC应用程序框架
  • 91. 向导为项目Mysdi生成了框架窗口类的头文件MainFrm.h,该头文件用于定义框架窗口类CMainFrame。不同的SDI应用程序,其框架窗口类名和文件名是统一的。 CMainFrame类是MFC的CFrameWnd类的派生类,它主要负责创建标题栏、菜单栏、工具栏和状态栏。 CMainFrame类中声明了框架窗口中的工具栏m_wndToolBar、状态栏m_wndStatusBar两个成员变量和四个成员函数。1. 框架窗口类头文件 2 创建VC应用程序框架
  • 92. 向导为项目Mysdi生成了文档类的头文件MysdiDoc.h,该头文件用于定义文档类CMysdiDoc。CMysdiDoc类是MFC的CDocument类的派生类,它主要负责应用程序数据的保存和装载,实现文档的序列化功能。2. 文档类头文件 2 创建VC应用程序框架
  • 93. 向导为项目Mysdi生成了视图类的头文件MysdiView.h,该头文件用于定义视图类CMysdiView。视图类用于处理客户区窗口,它是框架窗口中的一个子窗口。 CMysdiView类是MFC的CView类的派生类,它主要负责客户区文档数据的显示,以及如何进行人机交互。 3. 视图类头文件 2 创建VC应用程序框架
  • 94. 向导为项目Mysdi生成了应用程序类的头文件Mysdi.h,该头文件用于定义应用程序类CMysdiApp。CMysdiApp类是MFC的CWinApp类的派生类,它主要负责完成应用程序的初始化、程序的启动和程序运行结束时的清理工作。 4. 应用程序类头文件 2 创建VC应用程序框架
  • 95. 在项目中,资源通过资源标识符加以区别,通常将一个项目中所有的资源标识符放在头文件Resourde.h中定义。 向导为项目Mysdi生成了资源头文件Resource.h,该文件用于定义项目中所有的资源标识符,给资源ID分配一个整数值。 标识符的命名有一定的规则,如IDR_MAINFRAME代表有关主框架的资源,包括主菜单、工具栏及图标等。标识符以不同的前缀开始,见下表。 5. 资源头文件 2 创建VC应用程序框架
  • 96. 标识符前缀 说明 IDR_ 主菜单、工具栏、应用程序图 标和快捷键表 IDD_ 对话框 IDC_ 控件和光标 IDS_ 字符串 IDP_ 提示信息对话框的字符串 ID_ 菜单命令项MFC中资源标识符前缀 2 创建VC应用程序框架
  • 97. 向导为项目Mysdi生成了标准包含头文件StdAfx.h,该文件用于包含一般情况下要用到且的头文件,如MFC类的声明文件afxwin.h、使用工具栏和状态栏的文件afxext.h,这些头文件一般都存放在路径“…\Microsoft Visual Studio\VC98\MFC\Include”下。 StdAfx.h文件和StdAfx.cpp文件用来生成预编译文件。6. 标准包含头文件 2 创建VC应用程序框架
  • 98. 对应于一个头文件中定义的类,都有一个类的实现文件。在实现文件中主要定义在头文件中声明的成员函数的实现代码和消息映射。MFC AppWizard[exe]生成的实现文件也包括六种。 值得说明的是,向导生成的成员函数有很多,不要因为没有使用某个成员函数而删除其声明和实现代码。并且,用户一般不要轻易修改文件中那些以灰色字体显示的代码,因为这些代码是通过资源编辑器或ClassWizard类向导进行维护的。4.2.3 应用程序向导生成的实现文件2 创建VC应用程序框架
  • 99. 向导为项目Mysdi生成了框架窗口类的实现文件Mainfrm.cpp,该文件包含了窗口框架类CMainFrame的实现代码,主要是CMainFrame类成员函数的实现,它实现的框架窗口是应用程序的主窗口。1. 框架窗口类实现文件 CMainFrame类的四个主要成员函数: AssertValid():诊断CMainFrame对象是否有效,调试用。 Dump():输出CMainFrame对象的状态信息,调试用。 OnCreate():创建工具栏m_wndToolBar和状态栏m_wndStatusBar。而视图窗口是由基类CFrameWnd的成员函数OnCreate()通过调用OnCreateClient()函数创建的。 PreCreateWindow():如果要创建一个非默认风格的窗口,可以重载该函数(虚函数),在函数中通过修改CREATESTRUCT结构参数cs来改变窗口类、窗口风格、窗口大小和位置等。2 创建VC应用程序框架
  • 100. 在项目工作区ClassView页面打开CMainFrame类,双击成员函数PreCreateWindow()在编辑窗口打开该函数,添加代码:例 修改程序Mysdi,使程序运行窗口没有最大化按钮。BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; // TODO: Modify the Window class or styles here cs.style&=~WS_MAXIMIZEBOX; // 取消窗口的最大化按钮 return TRUE; }2 创建VC应用程序框架
  • 101. 向导为项目Mysdi生成了文档类的实现文件MysdiDoc.cpp。与框架类CMainFrame类似,文档类CMysdiDoc也定义了两个用于调试的成员函数AssertValid()和Dump。2. 文档类实现文件 文档类CMysdiDoc的成员函数: AssertValid() Dump() OnNewDocument():当用户执行File菜单中New命令时,MFC应用程序框架会调用函数OnNewDocument()来完成新建文档的工作。 Serialize():负责文档数据的磁盘读写操作。2 创建VC应用程序框架
  • 102. 注意: 由于SDI单文档应用程序中只处理一个文档对象,当执行New命令时,文档对象已经生成,因此文档类CMysdiDoc的构造函数不会再被调用。所以,SDI应用程序不要在构造函数中进行文档对象成员变量的初始化,而应在OnNewDocument()函数中(看后面的例子)。 在文档派生类CMysdiDoc重载的OnNewDocument()函数中,首先需要调用基类CDocument的OnNewDocument()函数。2 创建VC应用程序框架
  • 103. 3. 视图类实现文件 向导为项目Mysdi生成了视图类的实现文件MysdiView.cpp,该文件主要定义了视图类的成员函数。视图对象是用来显示文档对象的内容。与框架类和文档类一样,视图类CMysdiView也定义了两个用于调试的成员函数AssertValid()和Dump()。 视图类CMysdiView的成员函数: GetDocument():用于获取当前文档对象的指针m_pDocument。如果是建立程序的Release发行版,函数GetDocument()作为内嵌(inline)函数来实现。 OnDraw():虚函数,它负责文档对象的数据在用户视图区的显示输出。2 创建VC应用程序框架
  • 104. 例 修改程序Mysdi,为CMysdiDoc文档类定义一个字符串类型的成员变量,在OnNewDocument()函数中初始化成员变量。在OnDraw()函数中访问该成员变量,并在屏幕上输出它的值。 (1)在头文件MysdiDoc.h文档类CMysdiDoc的定义中增加成员变量m_szText,用于保存将要显示的文本信息: public: char* m_szText;2 创建VC应用程序框架
  • 105. (2)在文档类实现文件MysdiDoc.cpp中找到成员函数OnNewDocument(),添加初始化m_szText的代码: BOOL CMysdiDoc::OnNewDocument( ) { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here, … m_szText = “这是一个单文档程序!”; // 初始化 return TRUE; } 2 创建VC应用程序框架
  • 106. (3)在视图类实现文件MysdiView.cpp中找到成员函数OnDraw(),向导创建的函数框架中已自动添加了函数GetDocument()的调用语句,以获取与当前视图相关联的文档指针pDoc。手工添加如下代码,通过pDoc访问文档类CMysdiDoc的成员变量m_szText,用于在屏幕上输出。 void CMysdiView::OnDraw(CDC* pDC) // pDC是当前输出设备环境的指针 { CMysdiDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // 得到当前文档指针pDoc // TODO: add draw code for native data here pDC->TextOut(100, 100, pDoc->m_szText); // 通过pDoc访问文档对象的成员变量 }2 创建VC应用程序框架
  • 107. 向导为项目Mysdi生成了应用程序类的实现文件Mysdi.cpp,该文件是应用程序的主文件,MFC应用程序的初始化、启动运行和结束都是由应用程序对象完成。 在Mysdi.cpp文件中定义了应用程序类CMysdiApp的成员函数,以下列出了应用程序类实现文件Mysdi.cpp的部分源代码。 4. 应用程序类实现文件 2 创建VC应用程序框架
  • 108. . . . . . . BEGIN_MESSAGE_MAP(CMysdiApp, CWinApp) //{{AFX_MSG_MAP(CMysdiApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // ClassWizard将在此处添加和删除消息映射宏 . . . . . . END_MESSAGE_MAP() // CMysdiApp construction CMysdiApp::CMysdiApp() { // TODO: 在此处添加构造函数代码 // 把所有的重要的初始化信息放在InitInstance过程当中 } // 声明唯一的CMysdiApp对象theApp CMysdiApp theApp;2 创建VC应用程序框架
  • 109. // CMysdiApp的初始化 BOOL CMysdiApp::InitInstance() { // 标准初始化 // 如果不使用这些特征并希望减少最终可执行代码的长度, // 你可以去掉以下专门的初始化代码。 . . . // 设置应用程序的注册键 // TODO: 你应该为这个字符串设置适当的内容,如公司名 SetRegistryKey(_T("Local AppWizard-Generated Applications")); // 装入应用程序INI文件中的设置信息,如 “最近使用的文件列表”项 LoadStdProfileSettings(); // 注册应用程序文档模板,文档模板用于链接文档、框架窗口和视图 CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CMysdiDoc), RUNTIME_CLASS(CMainFrame), // SDI框架窗口 RUNTIME_CLASS(CMysdiView)); AddDocTemplate(pDocTemplate); . . . // 主窗口已经初始化,在此显示并刷新窗口 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE; }2 创建VC应用程序框架
  • 110. 向导为项目Mysdi生成了标准包含文件StdAfx.cpp,该文件用于包含StdAfx.h标准包含头文件。StdAfx.cpp文件用于生成项目的预编译头文件(Mysdi.pch)和预编译类型信息文件(StdAfx.obj)。 由于大多数MFC应用程序的实现文件都包含StdAfx.h头文件(其中包含了一些共同要使用的头文件),如果在每个实现文件中都重新编译StdAfx.h头文件,整个编译过程将浪费大量的时间。为了提高编译速度,可以首先将项目中那些共同要使用的头文件编译出来,首次编译后将结果存放在一个名为预编译头文件的中间文件中,以后再编译时直接读出存储的结果,无需重新编译,节约了编译时间。5. 标准包含文件 2 创建VC应用程序框架
  • 111. Windows编程的一个主要特点是资源和代码的分离,即将菜单、工具栏、字符串表、对话框等资源与基本的源代码分开。优点: 这样使得对这些资源的修改独立于源代码。例如,可以将字符串表翻译成另一种语言,而无需改动任何源代码。 当Windows装入一个应用程序时,一般情况下,程序的资源数据并不同时装入内存,而是在应用程序执行过程中需要时如创建窗口、显示对话框或装载位图,才从硬盘读取相应的资源数据。4.2.4 应用程序生成的资源文件 2 创建VC应用程序框架
  • 112. 1.资源文件 向导为项目Mysdi生成了资源文件Mysdi.rc和Mysdi.rc2。Mysdi.rc是Visual C++生成的脚本文件,它使用标准的Windows资源定义语句,可通过资源编译器转换为二进制资源。一般利用资源编辑器对资源进行可视化编辑,也可通过Open命令以文本方式打开一个资源文件进行编辑。Mysdi.rc2文件一般用于定义资源编辑器不能编辑的资源。 2.图标文件 向导为项目Mysdi生成了应用程序的图标文件Mysdi.ico。在Visual C++中,可利用图形编辑器编辑应用程序的图标。 3.文档图标文件 向导为项目Mysdi生成了文档图标文件MysdiDoc.ico。文档图标一般用于多文档应用程序中,在程序Mysdi中没有显示这个图标,但编程时用户可以利用相关函数来获取该图标资源并显示图标(ID为IDR_MYSDITYPE)。 4.工具栏按钮位图文件 向导为项目Mysdi生成了工具栏按钮的位图文件Toolbar.bmp,该位图是应用程序工具栏中所有按钮的图形表示。2 创建VC应用程序框架
  • 113. 除了上述用于生成可执行程序的源代码文件和资源文件,向导还为项目生成了其它一些在开发环境中必须使用的文件,如项目文件、项目工作区文件和ClassWizard类向导文件。 1.项目文件 项目用项目文件DSP(Developer Studio Project)来描述,向导为项目Mysdi生成了项目文件Mysdi.dsp,该文件将项目中的所有文件组织成一个整体。项目文件保存了有关源代码文件、资源文件以及你所指定的编译设置等信息。4.2.5 应用程序生成的其他文件 2 创建VC应用程序框架
  • 114. 2.项目工作区文件 为了创建应用程序,必须在Developer Studio的工作区中打开项目,这些应用程序项目工作区设置信息保存在项目工作区文件DSW(Developer Studio Workspace)中。向导为项目Mysdi生成了项目工作区文件Mysdi.dsw,该文件将一个DSP项目文件与具体的Developer Studio结合在一起,它保存了上一次操作结束时窗口状态、位置以及工作区设置信息。 3.类向导文件 向导为项目Mysdi生成了类向导文件Mysdi.clw,该文件存储了ClassWizard类向导使用的类信息,如类信息的版本、类的数量、每个类的头文件和实现文件。利用ClassWizard类向导时要使用该文件。如果从项目中删除了clw文件,下次使用ClassWizard类向导时会出现提示对话框,询问是否想重新创建这个文件。2 创建VC应用程序框架
  • 115. 4.3 ClassWizard类向导利用应用程序向导生成MFC应用程序框架后,用户需要为自己的MFC派生类添加消息处理成员函数和对话框控件的成员变量,有时还需要为程序添加新的MFC派生类,这时用户需要使用ClassWizard类向导。 MFC ClassWizard类向导根据程序员的要求以半自动的方式添加程序代码,它是进行MFC应用程序设计时一个必不可少的交互式工具,在今后的程序设计中经常用到它。2 创建VC应用程序框架
  • 116. 4.3.1 ClassWizard的功能定制现有的类和建立新的类,如把消息映射为类的成员函数,把一个控件与类的成员变量关联起来。 用户编程时,利用ClassWizard类向导能够大大简化一些细节问题,如成员变量和成员函数的声明和定义放在何处、成员函数如何与消息映射联系在一起。 ClassWizard有五个页面:Message Maps页面用来处理消息映射,为消息添加或删除处理函数;Member Variables面页用来给对话框类添加或删除成员变量(与控件关联);Automation页面提供了对OLE自动化类的属性和方法的管理;ActiveX Events页面用于管理ActiveX类所支持的ActiveX事件;Class Info页面显示应用程序中所包含类的信息。2 创建VC应用程序框架
  • 117. 2 创建VC应用程序框架
  • 118. 4.3.2 添加消息处理函数 ClassWizard类向导的Message Maps页面主要用于添加与消息处理有关的代码,包括添加消息映射宏和消息处理函数。Message Maps页面有五个列表框: Project框列出当前工作区中的项目 Class name框列出当前项目中的类 Object IDs框列出当前类所有能接收消息的对象(ID),包括类、菜单项和控件 Messages框列出在Object IDs框中选择的对象可处理的消息和可重载的MFC虚函数 Member functions框列出当前类已创建的消息处理函数,其中的“V”标记表示该函数是虚函数,“W”标记表示该函数是窗口消息处理函数2 创建VC应用程序框架
  • 119. 当在Messages框选择一个消息后: 单击Add Function按钮添加一个消息处理函数。 单击Edit Code按钮将退出ClassWizard,打开源代码编辑器并定位到指定的消息处理函数。 单击Delete Function按钮可以删除已建立的消息处理函数。注意:为了避免不小心删除了函数代码,此时只在头文件中删除了函数声明,在源文件中删除了消息映射项,实际的函数代码必须用户自己手工删除。添加、编辑、删除成员函数2 创建VC应用程序框架
  • 120. 例 编写一个SDI应用程序MyMessage,程序运行后在程序视图窗口左击或右击鼠标时分别弹出不同提示信息的对话框,显示左击或右击鼠标的次数。 1.利用MFC AppWizard[exe]建立一个单文档应用程序MyMessage。 2.为视图类CMyMessageView添加两个private属性、int类型的成员变量m_nLeft和m_nRight。在项目工作区的ClassView类视图中右击CMyMessageView,在弹出式菜单中选择Add Member Variable命令项出现添加成员变量对话框,在对话框中输入变量类型和变量名、选择属性。也可以采用手工方法直接在头文件中添加一般的成员变量。2 创建VC应用程序框架
  • 121. 2 创建VC应用程序框架
  • 122. CMyMessageView::CMyMessageView() { // TODO: add construction code here m_nLeft=0; // 初始化成员变量 m_nRight=0; }3.在视图类CMyMessageView的构造函数中添加初始化成员变量的代码:4.分别添加鼠标左击或右击的消息处理函数。2 创建VC应用程序框架
  • 123. 按Ctrl+W键弹出MFC ClassWizard对话框,选择Message Maps页面。在Class name和Object IDs框选择CMyMessageView,在Messages框分别选择WM_LBUTTONDOWN和WM_RBUTTONDOWN消息,单击Add Function按钮。最后单击OK按钮退出MFC ClassWizard对话框。 ClassWizard类向导完成的工作: 在视图类CMyMessageView的头文件中声明了消息处理函数OnLButtonDown()和OnRButtonDown(); 在视图类CMyMessageView的实现文件中生成了消息处理函数的框架代码; 在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP中定义了消息映射。2 创建VC应用程序框架
  • 124. void CMyMessageView::OnLButtonDown (UINT nFlags, CPoint point) { // TODO: Add your message handler code here m_nLeft++; // 左击鼠标次数加一 CString strOutput; // 产生用于输出的格式化字符串 strOutput.Format("The times of left button down: %d", m_nLeft); MessageBox(strOutput); // 弹出提示信息框 CView::OnLButtonDown(nFlags, point); }5. 在消息处理函数OnLButtonDown()和OnRButton-Down()中指定位置添加如下代码,以累加左击或右击鼠标的次数,并弹出一个信息框。2 创建VC应用程序框架
  • 125. 2 创建VC应用程序框架
  • 126. 添加一般的成员函数除了采用手工方法直接在头文件和实现文件中分别添加函数声明和函数代码外,也可以在Workspace项目工作区ClassView页面中右击鼠标,从弹出式菜单项执行Add Member Function命令。2 创建VC应用程序框架
  • 127. 利用ClassWizard类向导为项目添加一个常用MFC类的派生类。 选择执行“Insert|New Class…”菜单命令添加: (1)其它MFC类的派生类 (2)非MFC类的派生类 (3)普通类4.3.3 为项目添加新类2 创建VC应用程序框架
  • 128. 利用ClassWizard类向导添加新类利用“Insert|New Class”命令添加类2 创建VC应用程序框架
  • 129. 3 文档与视图3.1 文档与视图结构的工作原理 3.2 文档的读写操作机制 3.3 菜单编程 3.4 工具栏编程 3.5 状态栏编程 文档与视图结构是MFC应用程序最基本的程序结构,适用于大多数Windows应用程序。文档和视图完成了程序的大部分功能,它们是MFC应用程序的核心。文档与视图结构是MFC的基石,掌握文档与视图结构对于利用MFC编程有着至关重要的意义。本章对文档与视图结构进行更深入的讨论。
  • 130. 信息管理是计算机的一个主要应用,而信息是用数据表示的,因此数据的处理是一般软件都要完成的一项主要工作。 采用传统的编程方法,数据处理是一项复杂的任务,并且每一个程序员都可能有不同的处理方法。为了统一和简化数据处理方法,Microsoft公司在MFC中提出了文档/视图结构的概念,其产品Word就是典型的文档/视图结构应用程序。5.1 文档与视图结构
  • 131. 标题栏 主菜单 工具栏 客户区 状态栏 不同程序的相同菜单项和工具栏按钮表示相同的操作。5.1.1 文档与视图结构概述Windows应用程序界面特点:
  • 132. 分为数据的管理和显示 文档用于管理和维护数据 视图用来显示和编辑数据 MFC通过其文档类和视图类提供了大量有关数据处理的方法。MFC文档/视图结构数据处理工作分工:
  • 133. 文档的概念在MFC应用程序中的适用范围很广,一般说来,文档是能够被逻辑地组合的一系列数据,包括文本、图形、图象和表格数据。 一个文档代表了用户存储或打开的一个文件单位。文档的主要作用是把对数据的处理从对用户界面的处理中分离出来,集中处理数据,同时提供了一个与其它类交互的接口。 什么是文档?
  • 134. 视图是文档在屏幕上的一个映像,它就像一个观景器,用户通过视图看到文档,也是通过视图来改变文档,视图充当了文档与用户之间的媒介物。 应用程序通过视图向用户显示文档中的数据,并把用户的输入解释为对文档的操作。 一个视图总是与一个文档对象相关联,用户通过与文档相关联的视图与文档进行交互。当用户打开一个文档时,应用程序就会创建一个与之相关联的视图。 什么是视图?
  • 135. 视图负责显示和编辑文档数据,但不负责存储。用户对数据的编辑需要依靠窗口上的鼠标与键盘操作才得以完成,这些消息都是由视图类接收后进行处理或通知文档类,如收到窗口刷新消息时调用视图类的成员函数OnDraw()显示文档内容。 视图还可在打印机上输出。 文档负责数据的读写操作,数据通常被保存在文档类的成员变量中,文档类通过一个称为序列化的成员函数将成员变量的数据保存到磁盘文件中。MFC应用程序为数据的序列化提供了默认支持。 视图和文档的功能:
  • 136. 文档、视图、框架窗口之间的关系一个视图是一个没有边框的窗口,它位于主框架窗口中的客户区。视图是文档对外显示的窗口,但它并不能完全独立,它必须依存在一个框架窗口内。 一个视图只能拥有一个文档,但一个文档可以同时拥有多个视图。
  • 137. 视图是文档在屏幕上的一个映像,它就像一个观景器
  • 138. 文档/视图结构的优点:把数据处理类从用户界面处理类中分离出来,使得每一个类都能集中地执行一项工作。 把Windows程序通常要做的工作分成若干定义好的类,这样有助于应用程序的模块化,程序也易于扩展,编程时只需修改所涉及的类。 虽然文档/视图结构牵涉到许多类,其中的也关系比较复杂,但MFC AppWizard向导建立的MFC应用程序框架已经把程序的主要结构完成了,模块间的消息传递以及各函数的功能都已确定。 MFC应用程序框架起到了穿针引线的作用,按照消息处理函数功能的不同,将不同消息的响应分别分布在文档类和视图类中。
  • 139. 文档/视图结构并没有完全要求所有数据都属于文档类,视图类也可以有自己的数据。如果在视图类中不定义任何数据,在需要时都从文档类中获取,这样做会影响程序的效率。 例如,在文本编辑程序中,往往在视图中缓存部分数据,这样可以避免对文档的频繁访问,提高运行效率。在视图类中定义数据
  • 140. 包含多个类的MFC文档/视图结构应用程序要管理这些类中的数据,除了考虑在程序的哪一部分拥有数据和在哪一部分显示数据,一个主要的问题是文档数据更改后如何保持视图显示的同步,即文档与视图如何进行交互。 在文档、视图和应用程序框架之间包含了一系列复杂的相互作用过程,文档与视图的交互是通过类的公有成员变量和成员函数实现的。 5.1.2 文档与视图之间的相互作用
  • 141. 1.视图类的成员函数GetDocument() 一个视图对象只有一个与之相关联的文档对象。在MFC应用程序中,视图对象通过调用成员函数函数GetDocument()得到当前文档。GetDocument()是视图类的成员函数,调用它可以返回与视图相关联的文档对象的指针,利用这个指针可以访问文档类及其派生类的公有成员。 当利用MFC AppWizard向导创建一个SDI单文档应用程序Mysdi时,生成了视图类的一个派生类,并在派生类中定义了函数GetDocument()。文档和视图类常用的成员函数
  • 142. CMysdiDoc* CMysdiView::GetDocument() { ASSERT(m_pDocument-> IsKindOf(RUNTIME_CLASS(CMysdiDoc))); return (CMysdiDoc*)m_pDocument; // m_pDocument是CArchive类的数据成员, // 指向当前文档对象 } GetDocument()的Debug版函数代码:
  • 143. 一个文档对象可以有多个与之相关联的视图对象,当一个文档的数据通过某个视图被修改后,与它关联的每一个视图都必须反映出这些修改。因此,视图在需要时必须进行重绘,即当文档数据发生改变时,必须通知到所有相关联的视图对象,以便更新所显示的数据。 更新与该文档有关的所有视图的方法是调用成员函数CDocument::UpdateAllViews()。 2.CDocument类的成员函数UpdateAllViews()
  • 144. 如果在文档派生类的成员函数中调用UpdateAllViews()函数,其第一个参数pSender设为NULL,表示所有与当前文档相关的视图都要重绘(参见例5-3)。 如果在视图派生类的成员函数中通过当前文档指针调用UpdateAllViews()函数,其第一个参数pSender设为当前视图,如下形式: GetDocument()->UpdateAllViews(this)函数声明: void UpdateAllViews(CView* pSender, LPARAM lHint = 0L, CObject* pHint=NULL );
  • 145. 当程序调用CDocument::UpdateAllViews()函数时,实际上是调用了所有相关视图的OnUpdate()函数,以更新相关的视图。需要时,可以直接在视图派生类的成员函数中调用该函数刷新当前视图。3.视图类的成员函数OnUpdate()void CView::OnUpdate(CView* pSender, LPARAM /*lHint*/, CObject* /*pHint*/) { ASSERT(pSender != this); UNUSED(pSender); // unused in release builds // invalidate the entire pane, erase background too Invalidate(TRUE); // 使整个窗口矩形无效,通过调 // 用OnDraw()更新整个视图窗口 }基类CView的成员函数
  • 146. 在OnUpdate()中通过调用函数CWnd::Invalidate()刷新整个客户区,我们也可以在自己的CWnd派生类中直接调用函数Invalidate()。总结:刷新视图时默认的函数调用过程: CDocument::UpdateAllViews() →CView::OnUpdate() →CWnd::Invalidate() →OnPaint() →OnDraw()
  • 147. MFC基于文档/视图结构的应用程序分为单文档和多文档两种类型,一个多文档应用程序有一个主窗口,但在主窗口中可以同时打开多个子窗口,每一个子窗口对应一个不同的文档。 利用MFC AppWizard[exe]向导可以很方便地建立一个多文档应用程序,只需在MFC AppWizard向导第1步选择Multiple documents程序类型。 SDI和MDI使用不同框架窗口。SDI的框架窗口是唯一的主框架窗口,窗口类是CMainFrame,由CFrameWnd派生而来。 5.1.3 多文档
  • 148. MDI的框架窗口分为主框架窗口和子框架窗口,区别于SDI,MDI的主框架窗口不包含视图,分别由每个子框架窗口包含一个视图。MDI的主框架窗口类不与某个打开的文档相关联,而只与子框架窗口相关联。 MDI主框架窗口类CMainFrame由CMDIFrameWnd派生而来,而MDI子框架窗口类CChildFrame由CMDIChildWnd派生而来。
  • 149. 在文档/视图结构中,数据以文档类对象的形式存在。文档对象通过视图对象显示,而视图对象又是主框架窗口的一个子窗口,并且涉及文档操作的菜单和工具栏等资源也是建立在主框架窗口上。这样,文档、视图、框架类和所涉及的资源形成了一种固定的联系,这种固定的联系就称为文档模板。也就是说,文档模板描述了相对应每一种类型文档的视图和窗口的风格类型。 当打开某种类型的文件时,应用程序必须确定那一种文档模板用于解释这种文件。在初始化程序时,必须首先注册文档模板,以便程序利用这个模板来完成主框架窗口、视图、文档对象的创建和资源的装入。 文档模板的概念:
  • 150. 标准Windows应用程序界面窗口组成: 客户区 非客户区:5.2 菜单设计 窗口的边框 标题栏 菜单栏 工具栏 状态栏 滚动条 菜单、工具栏、状态栏是用户与应用程序进行交互的重要工具。菜单和工具栏为应用程序提供了传递用户命令的选择区域,而状态栏提供了提示信息的输出区域。
  • 151. 5.2.1 建立菜单资源 使用MFC AppWizard向导创建文档/视图结构应用程序时,向导将自动生成Windows标准的菜单资源和命令处理函数。但这个默认生成的主框架菜单资源往往不能满足实际的需要,因此我们需要利用菜单资源编辑器对其进行修改和添加。例 编写一个单文档应用程序DrawCoin,为程序添加一个“画硬币”主菜单,并在其中添加“增加硬币”和“减少硬币”两个菜单项。
  • 152. 1.利用MFC AppWizard[exe]向导创建SDI应用程序。在项目工作区的ResourceView页面中选择Menu并展开它,双击下面的IDR_MAINFRAME项弹出菜单资源编辑器,显示应用程序向导所创建的菜单资源。 2.为程序添加主菜单。双击菜单栏右边虚空白框,弹出属性对话框,在Caption框输入主菜单的标题“画硬币(&C)”,字符&用于在显示C时加上下划线,并表示其快捷键为Alt+C。 3.在主菜单“画硬币(C)”下方双击带虚框的空白菜单项,弹出属性对话框。在ID栏输入ID_COIN_ADD。在Caption框输入菜单项的标题“增加硬币(&A)\tCtrl+A”。
  • 153. (本页无文本内容)
  • 154. 5.2.2 添加菜单命令处理函数 菜单实际上是一系列命令的列表,当一个菜单项被选中后,一个含有该菜单项ID标识的WM_COMMAND命令消息将发送到应用程序窗口,应用程序将该消息转换为一个函数(命令消息处理函数)调用。命令消息来自于用户界面对象,是由菜单项、工具栏按钮和快捷键等程序界面元素发送的WM_COMMAND消息。 在MFC应用程序中,许多类都能接收菜单被选中而引发的消息。总的来说,从类CCmdTarget派生出来的类都可以加入应用程序的消息循环。
  • 155. 应该将菜单命令映射到哪个类中,需要由该命令的功能决定。如果一个命令同视图的显示有关,就应该将其映射到视图类;如果同文档的读写有关,就映射到文档类中;如果命令完成通用功能,一般映射到窗口框架类。 有时无法对功能进行准确分类,则可以将菜单命令映射到任意一个类,看看是否能够完成指定的功能。将菜单命令映射到哪个类?
  • 156. 利用ClassWizard类向导添加菜单命令WM_COMMAND消息处理函数后,向导将自动添加一个如下格式消息映射: ON_COMMAND(MenuItemID, MemberFuntion) 其中参数MenuItemID是菜单项的ID标识,参数MemberFuntion是处理该消息的成员函数名。一个菜单项的WM_COMMAND消息意味着选择了该菜单项,或选择了对应的工具栏按钮、键盘快捷键。向导还生成了消息处理函数的框架代码。 例: 利用ClassWizard添加菜单命令处理函数
  • 157. 例 为程序DrawCoin添加菜单命令处理函数。1. 启动ClassWizard类向导,在Class Name栏选择类CDrawCoinDoc,在Object IDs栏选择ID_COIN_ADD,在Messages栏选择COMMAND,单击Add Funtion按钮。同样为ID_COIN_SUB添加命令处理函数。函数的代码如下: void CDrawCoinDoc::OnCoinAdd() { m_nCoins++; // 硬币数量加一 UpdateAllViews(NULL); // 刷新视图 } void CDrawCoinDoc::OnCoinSub() { if(m_nCoins>0) m_nCoins--; // 硬币数量减一 UpdateAllViews(NULL); }
  • 158. 定义成员变量m_nCoins并初始化:2. 为文档派生类CDrawCoinDoc添加一个类型为int、属性为public的成员变量m_nCoins。 按下Ctrl+W启动ClassWizard类向导,在Class Name栏和Object IDs栏选择类CDrawCoinDoc,在Messages栏选择DeleteContents,单击Add Funtion按钮和Edit Code按钮,在生成的虚函数中添加如下初始化成员变量m_nCoins的代码,该函数在用户重新使用(打开或创建)一个文档时调用。void CDrawCoinDoc::DeleteContents() { // TODO: Add your . . . m_nCoins=1; // 初始化成员变量 CDocument::DeleteContents(); }
  • 159. 在客户区画出硬币:3. 在OnDraw()函数中根据m_nCoins画出指定个数的硬币。 void CDrawCoinView::OnDraw(CDC* pDC) { CDrawCoinDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here for(int i=0; im_nCoins; i++) { int y=200-10*i; pDC->Ellipse(200,y,300,y-30); // 用两个偏移的椭圆表示一枚硬币 pDC->Ellipse(200,y-10,300,y-35); } }
  • 160. (本页无文本内容)
  • 161. 利用ClassWizard类向导为菜单项添加命令处理函数时,在Messages栏除了WM_COMMAND消息,还有一个UPDATE_COMMAND_UI消息,它称为更新命令用户界面消息。 有时一个菜单项可以有可用和不可用两种状态,即允许或禁止菜单项的使用(处于灰色状态)。例如,初始状态下,菜单项“减少硬币”不可用,因为开始时客户区一个硬币也没有画出。 UPDATE_COMMAND_UI消息为程序员根据程序当前运行情况对菜单项的状态进行动态设置而提供了一个简便的方法。参阅例5-4。何谓更新命令用户界面消息?
  • 162. 菜单分为两类: 依附于框架窗口的固定菜单 浮动的弹出式菜单,快捷菜单,上下文菜单 当用户单击鼠标右键,弹出式菜单出现在光标所在位置。弹出式菜单是通过利用CMenu类和其成员函数,在程序运行过程中动态建立的。 一般而言,弹出式菜单是利用现有的菜单项来进行创建,但也可以为弹出式菜单专门建立一个菜单资源,然后通过调用函数CMenu::LoadMenu()装入所创建的菜单资源。5.2.3 弹出式菜单
  • 163. 当右击鼠标并释放后,WM_CONTEXTMENU消息将发给应用程序。因此,在程序中可通过为WM_CONTEXTMENU添加消息处理函数来实现弹出式菜单。 WM_CONTEXTMENU消息是在收到WM_ RBUTTONUP消息后,由Windows产生的。但如果在WM_RBUTTONUP的消息处理函数中没有调用基类的处理函数,那么应用程序将不会收到WM_CONTEXTMENU消息。 例:有关弹出式菜单的消息处理
  • 164. 利用ClassWizard为视图类添加WM_CONTEXTMENU的消息处理函数,添加如下代码: void CDrawCoinView::OnContextMenu(CWnd* pWnd, CPoint point) { CMenu menuPopup; // 声明菜单对象 if (menuPopup.CreatePopupMenu()) // 创建弹出式菜单 { // 向菜单menuPopup中添加菜单项 menuPopup.AppendMenu(MF_STRING, ID_COIN_ADD, "增加硬币\tCtrl+A"); menuPopup.AppendMenu(MF_STRING, ID_COIN_SUB, "减少硬币\tCtrl+B"); // 显示弹出式菜单,并跟踪用户的菜单项的选择 menuPopup.TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this); } } 例 为程序DrawCoin添加弹出式菜单。
  • 165. 函数TrackPopupMenu()用于在指定位置显示弹出式菜单,并响应用户的菜单项选择。函数第1个参数是位置标记,TPM_LEFTALIGN表示以x坐标为标准左对齐显示菜单;第2、3个参数指定弹出式菜单的屏幕坐标;第4个参数指定拥有此弹出式菜单的窗口。 函数AppendMenu()用于向菜单menuPopup添加菜单项,函数第1个参数指定加入菜单项的风格,值MF_STRING表示菜单项是一个字符串;第2个参数指定要加入菜单项的ID,如ID_COIN_ADD;第3个参数指定菜单项的显示文本。
  • 166. (本页无文本内容)
  • 167. Windows是基于事件驱动、消息传递的操作系统。用户所有的输入都是以事件或消息的形式传递给应用程序的,鼠标也不例外。 鼠标驱动程序将鼠标硬件信号转换成Windows可以识别的信息,Windows根据这些信息构造鼠标消息,并将它们发送到应用程序的消息队列中。5.3 鼠标消息处理
  • 168. 鼠标构成:左键、右键(中键和滚动滑轮) 鼠标操作:单击、双击、释放和移动 主要鼠标消息: WM_MOUSEMOVE:移动 WM_LBUTTONDOWN:按下左键 WM_LBUTTONUP:释放左键 WM_RBUTTONDOWN:按下右键 WM_RBUTTONUP:释放右键 WM_LBUTTONDBLCLK:双击左键 5.3.1 鼠标消息
  • 169. 鼠标消息分为两类: 客户区鼠标消息 非客户区鼠标消息: WM_NCLBUTTONDOWN:按下鼠标左键消 WM_NCRBUTTONDOWN:按下鼠标右键 . . . . . . 非客户区鼠标消息由Windows操作系统处理,应用程序一般不需要处理。客户区鼠标消息发送到应用程序后,可以由应用程序自己处理。 通过消息结构中的消息参数wParam来区分客户区鼠标消息和非客户区鼠标消息。
  • 170. 利用MFC ClassWizard类向导生成的鼠标消息处理函数一般都有两个参数: nFlags:类型为UINT,表示鼠标按键和 键盘上控制键的状态。 point:类型为CPoint,表示鼠标当前所 在位置坐标。 鼠标消息处理函数参数
  • 171. 使用鼠标的一个典型例子就是绘图程序,鼠标被用作画笔,绘图过程中要进行不同鼠标消息的处理,如按下鼠标、释放鼠标和移动鼠标等。当用户按下鼠标左键时必须记录下当前鼠标的位置,当用户移动鼠标时,如果鼠标左键被按住,则从上一个鼠标位置到当前位置画一段直线,并保存当前鼠标的位置,供下一次画线用。当用户弹起鼠标左键时释放鼠标。 5.3.1 鼠标消息处理举例:绘图程序 例 编写一个绘图程序,程序运行后,当用户在客户区窗口按下鼠标左键并移动时,根据鼠标移动的轨迹绘制出指定的线段。
  • 172. 1.利用MFC AppWizard[exe]向导创建一个SDI应用程序MyDraw,为视图类CMyDrawView添加成员变量: protected: // 定义有关鼠标作图的成员变量 CPoint m_ptOrigin; // 起始点坐标 bool m_bDragging; // 拖拽标记 HCURSOR m_hCross; // 光标句柄
  • 173. 2.在视图类CMyDrawView构造函数中设置拖拽标记和十字光标。 CMyDrawView::CMyDrawView() { // TODO: add construction code here m_bDragging=false; // 初始化拖拽标记 // 获得十字光标句柄 m_hCross=AfxGetApp()-> LoadStandardCursor(IDC_CROSS); }
  • 174. 3.利用ClassWizard类向导为视图类添加按下鼠标左键WM_LBUTTONDOWN的消息处理函数。void CMyDrawView::OnLButtonDown( UINT nFlags, CPoint point) { // TODO: Add your message . . . . . . SetCapture(); // 捕捉鼠标 ::SetCursor(m_hCross); // 设置十字光标 m_ptOrigin=point; m_bDragging=TRUE; // 设置拖拽标记 // CView::OnLButtonDown(nFlags, point); }
  • 175. 利用ClassWizard类向导为视图类添加鼠标移动WM_MOUSEMOVE的消息处理函数。void CMyDrawView::OnMouseMove( UINT nFlags, CPoint point) { // TODO: Add your message . . . . . . if(m_bDragging) { CClientDC dc(this); dc.MoveTo(m_ptOrigin); dc.LineTo(point); // 绘制线段 m_ptOrigin=point; // 新的起始点 } // CView::OnMouseMove(nFlags, point); }
  • 176. void CMyDrawView::OnLButtonUp( UINT nFlags, CPoint point) { // TODO: Add your message . . . . . . if(m_bDragging) { m_bDragging=false; // 清拖拽标记 ReleaseCapture(); // 释放鼠标,还原鼠标形状 } // CView::OnLButtonUp(nFlags, point); } 系统中任一时刻只有当前窗口才能捕获鼠标。在程序中需要时通过调用函数CWnd::SetCapture()捕获鼠标,使用鼠标画图结束后应该调用函数ReleaseCapture()释放鼠标。 利用ClassWizard类向导为视图类添加左键释放WM_LBUTTONUP的消息处理函数。
  • 177. (本页无文本内容)
  • 178. MyDraw程序有一个缺陷,当改变窗口大小或将窗口最小化后再重新打开,原来的线段没有显示出来。其原因是此时调用的是视图类的刷新函数OnDraw(),而在该函数中并没有实现绘制线段的功能。 MyDraw程序存在的问题 完善绘图程序MyDraw
  • 179. 1.为线段定义新类CLine。选择“Insert|New Class”菜单命令,弹出New Class对话框中,在Class type栏选择Generic Class,在类名Name栏输入CLine,在类名Base class[es]栏输入CObject,单击OK按钮,自动生成了类CLine的头文件Line.h和实现文件Line.cpp的框架。例 完善绘图程序MyDraw,在重绘窗口时能够显示已绘制的线段。
  • 180. 2.为类CLine定义成员变量和成员函数。 class Cline : CObject { private: // 定义成员变量,表示一条直线起点和终点的坐标 CPoint m_pt1; CPoint m_pt2; public: CLine(); virtual ~CLine(); CLine(CPoint pt1, CPoint pt2); // 构造函数 void DrawLine(CDC *pDC); // 绘制线段 };
  • 181. CLine::CLine(CPoint pt1, CPoint pt2) { m_pt1=pt1; m_pt2=pt2; } void CLine::DrawLine(CDC* pDC) { pDC->MoveTo(m_pt1); pDC->LineTo(m_pt2); }在Line.cpp中编写成员函数的实现代码:
  • 182. 3.一般都使用数组来保存多条线段的数据,而且MFC提供了实现动态数组的类模板。类CObArray支持CObject指针数组,用它定义的对象可以动态生成。这样,可将存放每条线段数据的变量的指针存到CObArray类的对象中。为此在文档类CMyDrawDoc中定义有关的成员变量和成员函数,需要包含CLine类定义的头文件。 存储线段采用什么数据结构?
  • 183. #include "Line.h" #include // 使用MFC类模板 class CMyDrawDoc : public CDocument { . . . . . . protected: CTypedPtrArray m_LineArray; // 存放线段对象指针的动态数组 public: CLine* GetLine(int nIndex); // 获取指定序号线段对象的指针 void AddLine(CPoint pt1, CPoint pt2); // 向动态数组中添加新的线段对象的指针 int GetNumLines(); // 获取线段的数量 . . . . . . };
  • 184. 成员变量m_LineArray是类模板CTypedPtrArray的对象。使用数组类模板CTypedPtrArray需要指定两个模板参数: CTypedPtrArray 参数BASE_CLASS指定基类,可以是CObArray或CPtrArray;参数TYPE指定存储在基类数组中元素的类型。本例中,这两个参数分别为CObArray和CLine*,表示m_LineArray是CObArray的派生类对象,用来存放CLine对象的指针。 为了使用MFC类模板,须包含头文件afxtempl.h。 使用MFC数组类模板CTypedPtrArray
  • 185. 其他主要成员函数: void CMyDrawDoc::AddLine(CPoint pt1, CPoint pt2) { CLine* pLine=new CLine(pt1, pt2); // 新建一条线段对象 m_LineArray.Add(pLine); // 将该线段加到动态数组 } CLine* CMyDrawDoc::GetLine(int nIndex) { if(nIndex<0||nIndex>m_LineArray.GetUpperBound()) // 判断是否越界 return NULL; return m_LineArray.GetAt(nIndex); // 返回给定序号线段对象的指针 }
  • 186. int CMyDrawDoc::GetNumLines() { return m_LineArray.GetSize(); // 返回线段的数量 } 4.当鼠标移动时,除了绘制线段,还要保存当前线段的起点坐标和终点坐标。需要在视图类CMyDrawView的OnMouseMove()鼠标移动消息处理函数中添加有关代码。
  • 187. void CMyDrawView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if(m_bDragging) { CMyDrawDoc *pDoc=GetDocument(); //获得文档对象指针 ASSERT_VALID(pDoc); //测试文档对象是否运行有效 pDoc->AddLine(m_ptOrigin, point); //加入线段到指针数组 CClientDC dc(this); dc.MoveTo(m_ptOrigin); dc.LineTo(point); // 绘制线段 m_ptOrigin=point; // 新的起始点 } // CView::OnMouseMove(nFlags, point); }
  • 188. 为了在改变程序窗口大小或最小化窗口后重新打开窗口时保留窗口中原有的图形,必须在OnDraw()函数中重新绘制前面利用鼠标所绘制的线段。这些线段的坐标作为类CLine对象的成员变量,所有CLine对象的指针已保存在动态数组m_LineArray中。 5. 修改OnDraw()函数
  • 189. void CMyDrawView::OnDraw(CDC* pDC) { CMyDrawDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here int nIndex=pDoc->GetNumLines(); // 取得线段的数量 // 循环画出每一段线段 while(nIndex--) // 数组下标从0到nIndex-1 { pDoc->GetLine(nIndex)->DrawLine(pDC); // 类CLine的成员函数 } }
  • 190. 5.4 工具栏和状态栏设计 5.4.1 添加工具栏按钮 我们知道单击工具栏按钮也产生命令消息。而且,事实上工具栏按钮和菜单项的功能往往是一致的。为了使工具栏上某个按钮的功能与某个菜单命令的功能相同,只需让该按钮的ID值与对应菜单命令的ID值相同即可。参看例5-10。
  • 191. 状态栏用于显示当前操作的提示信息和程序的运行状态。 MFC应用程序默认的状态栏分为四个部分: 第1部分:显示菜单或工具栏的提示信息 第2部分: Caps Lock,显示键盘的大小写状态 第3部分: Num Lock,显示键盘的数字状态 第4部分: Scroll Lock,显示键盘的滚动状态 状态栏上的每个部分称为一个面板(pane)。 5.4.2 定制状态栏
  • 192. static UINT indicators[ ] = { ID_SEPARATOR, // 定义分隔符,作为提示信息行的面板标识 ID_INDICATOR_CAPS, // 大写指示器面板标识 ID_INDICATOR_NUM, // 数字指示器面板标识 ID_INDICATOR_SCRL, // 滚动指示器面板标识 }; 利用MFC AppWizard向导创建应用程序时,在CMainFrame类中定义了一个成员变量m_wndStatusBar,它是状态栏CStatusBar类的对象。 在MFC应用程序框架的实现文件MainFrm.cpp中,为状态栏定义了一个静态数组indicators。
  • 193. 为了显示硬币数量,添加一个ID为ID_INDICATOR_COIN的指示器面板,将数组indicators作如下修改: static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_INDICATOR_COIN, // 显示硬币数量指示器 ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; 例 修改程序DrawCoin,在状态栏显示硬币的数量。
  • 194. 修改OnDraw()函数,添加显示硬币数量的代码: . . . . . . CString strCoins; // 先获得主窗口, 再获得状态栏的指针 CStatusBar* pStatus= (CStatusBar*)AfxGetApp()->m_pMainWnd-> GetDescendantWindow(ID_VIEW_STATUS_BAR); if(pStatus) { strCoins.Format("硬币:%d", pDoc->m_nCoins); // 设置要显示的信息 pStatus->SetPaneText(1, strCoins); // 显示硬币数量,面板编号从0开始 } . . . . . .
  • 195. 涉及到数据处理的应用程序一般都要考虑文档数据的永久保存。虽然我们可以直接利用类CFile来实现文件的读写操功能,但在MFC应用程序中,序列化(Serialize)使得程序员可以不直接面对一个物理文件而进行文档的读写。序列化实现了文档数据的保存和装入的幕后工作,MFC通过序列化实现应用程序的文档读写功能。 5.5 文档的读写
  • 196. 1. 序列化的基本思想: 一个类应该能够对自己的成员变量的数据进行读写操作,对象可以通过读操作而重新创建。即对象可以将其当前状态(由其成员变量的值表示)写入永久性存储体(通常是指磁盘)中,以后可以从永久性存储体中读取(载入)对象的状态,从而重建对象。类的对象自己应该具备将状态值写入磁盘或从磁盘中读出的方法(即成员函数),这种对象的保存和恢复的过程称为序列化。 5.5.1 序列化工作原理
  • 197. 一个可序列化的类必须有一个称作为序列化的成员函数Serialize(),文档的序列化在文档类的成员函数Serialize()中进行。MFC AppWizard应用程序向导在生成应用程序时只创建了文档派生类序列化Serialize()函数的框架,由于不同程序的数据结构各不相同,可序列化的类应该重载Serialize()函数,使其支持对特定数据的序列化。并且,任何需要保存的变量(数据)都应该在文档派生类中声明。 2. 序列化函数Serialize()
  • 198. void CMyDoc::Serialize(CArchive& ar) { if(ar.IsStoring()) { // TODO:add storing code here. } else { // TODO:add loading code here. } } 3. MFC AppWizard向导生成的Seralize()函数
  • 199. 函数参数ar是一个CArchive类的对象,文档数据的序列化操作通过CArchive类对象作为中介来完成。CArchive类对象由应用程序框架创建,并与用户正在使用的文件关联在一起。CArchive类包含一个类CFile指针的成员变量,当创建一个CArchive类对象时,该对象与一个类CFile或其派生类的对象联系在一起,代表一个已打开的文件。 C++主要通过文件句柄来实现磁盘输入和输出,一个文件句柄与一个磁盘文件相关联。而MFC中物理文件的读写操作是由CFile类及其派生类来完成的,它们对文件句柄进行了封装。CArchive类对象为读写CFile类对象中的可序列化数据提供了一种安全的缓冲机制,它们之间形成了如下关系: Serialize()函数 CArchive类对象CFile类对象 磁盘文件
  • 200. 1. CDocument::OnFileSave()完成的工作: 文档对象获取一个当前文件CFile指针,创建一个CArchive对象; 文档对象调用成员函数Serialize(),并把创建的CArchive对象作为参数传递给函数Serialize(); Serialize()函数根据函数IsStoring()的返回值(true)执行if语句的第一个分支,调用要读写对象的序列化函数Serialize(),而读写对象使用CFile来写入数据。 5.5.2 MFC应用程序的序列化
  • 201. 类必须直接或间接地从CObject类派生而来,因为是利用CArchive类把用户的CObject类的派生类对象序列化; 类必须定义一个不带参数的构造函数,当从磁盘文件载入文档时调用该构造函数来创建一个可序列化的对象,使用从文件中读出来的数据填充对象的成员变量; 在类的头文件中使用DECLARE_SERIAL宏,在类的实现文件中使用IMPLEMENT_SERIAL宏; 在自定义类中重载序列化成员函数Serialize()。 2 MFC类的序列化必须满足的四个条件:
  • 202. 1. 前面已定义了用来保存线段数据的CLine类,它已是CObject的派生类,现在添加函数Serialize()的声明和DECLARE_SERIAL宏。 2. 在实现源文件Line.cpp中成员函数定义前添加IMPLEMENT_SERIAL宏: IMPLEMENT_SERIAL(CLine, CObject, 1) // 实现序列化类Cline 3. 编写类CLine的序列化函数Serialize()。 例 完善绘图程序MyDraw,以便能将绘制好的图形保存在磁盘上。
  • 203. void CLine::Serialize(CArchive &ar) { if(ar.IsStoring()) ar<>m_pt1>>m_pt2; // 读出对象的数据 }4.现在我们实现了CLine类的序列化,但只是一条线段的序列化。前面定义了CObArray类型的变量m_LineArray,由于CObArray类自身提供了序列化函数,所以作为CObArray类的对象m_LineArray可以直接序列化。变量m_LineArray中存放的是CLine类对象的指针,自然会调用类CLine的序列化函数,完成了多个CLine类对象的序列化。
  • 204. void CMyDrawDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here m_LineArray.Serialize(ar); // 调用CObArray类的序列化函数 } else { // TODO: add loading code here m_LineArray.Serialize(ar); } }
  • 205. MFC为应用程序提供了多种不同的视图,除了我们平常使用最多的一般视图CView,我们还可以使用其它视图,如滚动视图CScrollView、文本编辑视图CEditView、对话框视图CFormView、列表视图CListView和树型视图CTreeView等,这些视图都是从类CView派生而来。在利用应用程序向导创建一个文档/视图结构的应用程序时,在向导的第6步我们可以为应用程序选择不同的视图。 5.6 使用不同视图
  • 206. 1. 滚动视图类CScrollView 为了实现滚动视图的,MFC提供了滚动视图类CScrollView。在使用类CScrollView时,一般情况下,我们使用默认的滚动值,且不需要程序员自己处理滚动消息。编程时可使用CScrollView类的一些常用成员函数: SetScrollSizes():用于设置整个滚动视图的大小、每一页和每一行的大小; GetTotalSize():用于获取滚动视图的大小; GetScrollPosition():用于获取当前可见视图左上 角的坐标。 5.6.1 滚动视图
  • 207. 利用向导创建程序时采用CScrollView作为基类 如果为用类CView作为基类的程序增加滚动功能,可直接在原来的程序基础上作相应修改: (1)手工将视图类CView改为CScrollView。 (2)重载视图类虚函数OnInitialUpdate()或WM_CREATE的消息处理函数OnCreate(),根据文档大小设置滚动视图。 (3)注意设备坐标与逻辑坐标的转换。如在鼠标消息处理函数中使用CClientDC客户设备环境,其坐标是客户区坐标,而在OnDraw()函数中使用的坐标是逻辑坐标。参见例5-13。2. 滚动视图程序的2种设计方法:
  • 208. 5.6.2 多视图文档与视图分离使得一个文档对象可以和多个视图相关联,这样可更容易实现多视图应用程序。 一般多视图应用程序都是MDI程序,但SDI程序也可以实现多视图。对于单文档应用程序一般采用拆分窗口的方式实现多视图。除了采用拆分窗口实现多视图,还可以利用子框架窗口实现多视图,例5-14就是采用这种方式实现多视图。 视图总是通过文档模板与一个框架窗口和一个文档相关联,文档模板对象是在应用程序的初始化函数InitInstance()中创建的。在利用向导创建一个MDI应用程序后,程序自动具有多视图的功能。
  • 209. 利用类向导建立新的视图类; 在函数InitInstance()中构建一个与新的视图类相关联的文档模板对象,但暂时不要加入它;在函数ExitInstance()中删除构建的文档模板对象; 在相关菜单的命令处理函数中调用函数CDocTemplate::CreateNewFrame()为构键的文档模板创建新的框架窗口,调用函数CDocTemplate::InitialUpdateFrame()更新视图。 MFC实现多视图的基本方法和步骤:
  • 210. 4 对话框及其常用控件4.1 对话框概述 4.2 对话框的使用 4.3 标准控件
  • 211. 对话框是Windows应用程序中一种常用的资源,其主要功能是输出信息和接收用户的输入数据。控件是嵌入在对话框中或其它父窗口中的一个特殊的小窗口,它用于完成不同的输入、输出功能。 对话框与控件关系密切,在每个对话框上一般都有一些控件,对话框依靠这些控件与用户进行信息的交互。本章主要介绍对话框的工作原理和编程方法,并通过实例学习一些标准控件和公共控件的使用方法。
  • 212. 6.1 对话框概述就用户交互输入功能而言,菜单和工具栏的功能是极其有限的。而对话框除了用来显示提示信息(如程序启动时显示版权和运行进度信息),一个主要功能是用于接收用户的输入数据。 在MFC中,对话框的功能被封装在CDialog类中,而CDialog类是CWnd类的派生类。作为窗口,对话框和其它窗口一样具有窗口的一切功能。 对话框的一个典型应用是通过菜单命令或工具栏按钮打开一个对话框,当然,对话框也可以作为一个程序的主界面。
  • 213. 6.1.1 基于对话框的应用程序可以直接利用MFC AppWizard向导创建一个基于对话框的应用程序,这种程序运行后首先出现一个对话框。在向导的第一步选择Dialog Based项。此时,应用程序向导将出现与单文档和多文档程序向导不同的操作步骤,因为对话框应用程序一般不包含文档,故不支持数据库和复合文档。 按照对话框应用程序向导提示的步骤进行操作就得到一个对话框应用程序项目,出现对话框编辑器和控件工具栏浮动窗口,它们采用的是一种所见即所得的可视化工作方式。利用向导创建应用程序的框架后,程序员可根据程序具体功能要求添加代码。
  • 214. 例 编写一个对话框应用程序MyDialog,程序运行后显示一个对话框,并在对话框上显示文本串。1.执行File|New命令出现New对话框,选择MFC AppWizard[exe]项,输入程序名MyDialog,单击OK按钮。在随后出现的MFC AppWizard-Step 1对话框窗口中选择Dialog Based选项,单击Finish按钮就创建了应用程序项目,并在Developer Studio中打开了对话框编辑器和控件工具栏。 2.去掉对话框中标题为“TODO: 在这里设置对话控制”的静态文本控件,调整对话框大小,在成员函数CMyDialogDlg::OnPaint()中添加代码。
  • 215. void CMyDialogDlg::OnPaint() { . . . . . . else { CPaintDC dc(this); dc.SetBkMode(TRANSPARENT); dc.TextOut(20, 50, "这是一个对话框应用程序!"); CDialog::OnPaint(); } }
  • 216. 6.1.2 对话框类CDialog MFC提供了一系列对话框类,并实现了对话框消息响应和处理机制。CDialog类是对话框类中最重要的类,我们在程序中创建的对话框类一般都是CDialog类的派生类。CDialog类还是其它所有对话框类的基类,其派生关系如下所示: CObject CCmdTarget CWnd CDialog
  • 217. 对话框类为程序员提供了管理对话框的编程接口。 CDialog类从CWnd类派生而来,所以它继承了CWnd类的成员函数,具有CWnd类的基本功能,可以编写代码移动、显示或隐藏对话框,并能根据对话框的特点增加新的成员函数,扩展它的功能。在你的CDialog类的派生类中可以直接调用CDialog类的成员函数。大部分的成员函数是虚函数,可以在你的派生类中重载,以实现特定的目的。除了CDialog类成员函数,类CWnd和CWinApp也提供了一些成员函数用于对话框的管理。
  • 218. 有关对话框的常用处理函数 CDialog::CDialog() 通过调用派生类构造函数,根据对话框资源模板定义一个对话框。 CDialog::DoModal() 激活模态对话框,显示对话框窗口。 CDialog::Create() 根据对话框资源模板创建非模态对话框窗口。如果对话框不是Visible属性,还需通过调用CWnd::ShowWindow()函数显示非模态对话框窗口。 CDialog::OnOk() 单击OK按钮时调用该函数,接收对话框输入数据,关闭对话框。 CDialog::OnCancel() 单击Cancel按钮或按Esc键时调用该函数,不接收对话框输入数据,关闭对话框。
  • 219. CDialog::OnInitDialog() WM_INITDIALOG消息处理函数,在调用DoModal或Create函数时系统发送WM_INITDIALOG消息,在显示对话框前调用该函数进行初始化。 CDialog::EndDialog() 用于关闭模态对话框窗口。 CWnd::ShowWindow() 显示或隐藏对话框窗口 CWnd::DestroyWindow() 关闭并销毁非模态对话框 CWnd::UpdateData() 通过调用DoDataExchange()设置或获取对话框控件的数据 CWnd::DoDataExchange() 被UpdateData()调用以实现对话框数据交换,不能直接调用。
  • 220. CWnd::GetWindowText() 获取对话框窗口的标题 CWnd::SetWindowText() 修改对话框窗口的标题 CWnd::GetDlgItemText() 获取对话框中控件的文本内容 CWnd::SetDlgItemText() 设置对话框中控件的文本内容 CWnd::GetDlgItem() 获取控件或子窗口的指针 CWnd::MoveWindow() 用于移动对话框窗口 CWnd::EnableWindow() 使窗口处于禁用或可用状态
  • 221. 6.1.3 对话框数据交换DDX和验证DDV在对话框中实现用户数据输入和输出的一般方法是通过ClassWizard类向导将对话框控件与成员变量相关联,利用对话框数据交换DDX(Dialog Data Exchange)和验证DDV(Dialog Data Validation)机制实现数据的输入和输出。 当利用ClassWizard类向导添加成员变量时,用户如果在窗口的左下角输入数据的范围,ClassWizard将自动加入函数DDV的调用代码。
  • 222. 例如,当添加一个UINT型的成员变量m_nInput时指定它的最小值和最大值分别为0和100,则ClassWizard类向导将在成员函数DoDataExchange()中添加以下DDV函数的调用语句: DDV_MinMaxUInt(pDX, m_nInput, 0, 100); 程序运行后,如果用户的输入数据超出0~100的范围,DDV将显示一个提示信息对话框(例7-2c),提示用户有效的输入范围。
  • 223. UpdateData()的用法: 当调用UpdateData(TRUE)时,MFC通过调用DDX函数将数据从控件传递到关联的成员变量;当调用UpdateData(FALSE)时,MFC通过调用DDX函数将数据从成员变量传递到关联的的控件。利用UpdateData()函数实现了在刷新后重新获取成员变量。
  • 224. 注意: 表面上看程序并没有调用UpdateData(),但是,当程序调用CDialog::DoModal()创建并显示对话框时,将自动调用CDialog::OnInitDialog()完成初始化的工作。在OnInitDialog()中调用了UpdateData(FALSE),将数据从成员变量传递到关联的控件,从而显示在控件中。而单击OK按钮将调用CDialog::OnOk()函数,在OnOK()中调用了UpdateData(TRUE),将数据从控件传递到关联的成员变量。 由此看来,不管MFC将DDX技术如何复杂化,我们只需知道,DDX就如同一条双向通道,而方向控制开关就是UpdateData()函数中的BOOL类型参数是TRUE还是FALSE。
  • 225. 6.1.4 提示信息对话框 提示信息对话框也称消息对话框,用来显示有关的提示信息。提示信息对话框是一种最简单的对话框,不需要用户自己创建它就可以直接使用,Visual C++提供了相应的函数实现这样的功能。: int AfxMessageBox(LPCTSTR lpText, UINT nType=MB_OK, UINT nlDHelp=0); int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT nType); int CWnd::MessageBox(LPCTSTR lpText, LPCTSTR lpCaption=NULL, UINT nType=MB_OK);
  • 226. 这三个函数分别是MFC全局函数、API函数和CWnd类的成员函数,它们的功能基本相同,但适用范围有所不同。AfxMessageBox()和::MessageBox()可以在程序中任何地方使用,而CWnd::MessageBox()只能用于控件、对话框、窗口等一些窗口类中。 例如,在软件安装过程中为了弹出如下图所示的警告提示信息对话框并进行相应的处理:
  • 227. 编写的代码如下: int nChoice=MessageBox("文件复制失败!", "错误", MB_ICONWARNING | MB_ABORTRETRYIGNORE ); switch(nChoice) { case IDABORT: // 用户按下"终止"按钮 . . . . . . case IDRETRY: // 用户按下"重试"按钮 . . . . . . case IDIGNORE: // 用户按下"忽略"按钮 . . . . . . }
  • 228. 6.2 使用对话框 为了在屏幕上显示对话框: (1)首先需要建立对话框资源及其与资源相关的对话框类; (2)然后添加控件、与控件关联的成员变量和消息处理函数; (3)最后在程序中显示对话框并访问与控件关联的成员变量。 在Visual C++中可以利用集成工具如对话框编辑器和ClassWizard类向导很方便地创建对话框,不需要程序员手工编写太多的源代码。
  • 229. 6.2.1 一般对话框工作流程 当定义了一个对话框类后,就可以利用这个对话框类声明一个对话框对象,即一个能够在屏幕上显示的对话框。 声明了一个对话框对象后,可以调用对话框类的成员函数DoModal()建立对话框窗口并显示对话框。例如,假设定义了一个名为CMyDialog的对话框类,为了在屏幕上显示一个对话框,可以编写如下代码: CMyDialog myDlg; myDlg.DoModal();
  • 230. 函数OnOK()、 OnCancel()与DoModal()的关系: 对话框中一般都有OK和Cancel按钮,单击OK按钮时调用CDialog::OnOK()函数,单击Cancel按钮时调用CDialog::OnCancel()函数。OnOK()函数和OnCancal()函数都将结束DoModal()函数调用,但令DoModal()返回不同的值。OnOK()函数令DoModal()返回IDOK,OnCancel()函数令DoModal()返回IDCANCAL。用户可以根据DoModal()的返回值做出不同的选择。如: if (myDlg.DoModal() = = IDOK ) { …… }
  • 231. 对话框的初始化可以在三个不同的阶段所调用的函数中进行: (1)对话框类构造函数; (2)WM_CREATE消息处理函数; (3)WM_INITDIALOG消息处理函数。 通常是在消息WM_INITDIALOG的消息处理函数OnInitDialog()中进行初始化。在收到WM_INIT- DIALOG消息时,对话框的框架已创建,对话框中的每个控件也已建立起来,但它们还没有在屏幕上显示。此时自然可以设置或优化对话框中各个控件的外观、尺寸、位置以及其它属性。函数OnInitDialog()将被成员函数DoModal()调用。
  • 232. 6.2.2 创建对话框 在Windows中对话框是作为一种资源被使用,在程序中要创建一个对话框,首先要创建一个对话框模板资源,然后创建一个基于该对话框模板资源的对话框类。对话框模板资源指定了对话框本身的属性(如大小、位置、风格、类型等)和对话框中的控件及属性,而对话框类规定了对话框和对话框中每个控件的行为。通过对话框模板资源才能创建对话框类和对象。
  • 233. 为了向应用程序项目中添加一个对话框资源,执行Insert|Resource命令或直接按Ctrl+R键,弹出Insert Resource资源列表框。一般要加入通用对话框资源,所以在Insert Resource框中直接选择Dialog项,然后单击New按钮。系统将给对话框资源设置一个默认的ID标识,对话框的默认标题Caption为Dialog,对话框有OK和Cancel两个按钮。对话框资源的添加:添加对话框资源的简单方法: 在项目工作区鼠标右击资源项Dialog,从弹出式菜单中选择“Insert Dialog”命令项。
  • 234. 对话框是在对话框模板资源和对话框类的基础上形成的,在完成了对话框的属性和外观设计后,再来设计对话框的行为。我们需要利用ClassWizard类向导创建一个对话框类,并将这个类同先前创建的对话框资源连接起来。在创建对话框类之前或之后,都可以为对话框资源添加控件。而在创建对话框类之后,还可以为对话框资源添加与对话框控件相关联的成员变量和消息处理函数。
  • 235. 创建对话框类: 如果按Ctrl+W键或在对话框资源的非控件区域双击鼠标,ClassWizard类向导将发现已添加了一个对话框模板资源,却没有设计其对应的类,因此将弹出Adding a Class对话框,询问用户是否需要利用该对话框资源创建一个对话框类。
  • 236. Name框:用于输入对话框类的名称; File Name框:列出类的文件名,单击Change按钮可改变文件名; Base class下拉框:列出可选择的基类; Dialog ID下拉框:列出可选择对话框资源的ID。 例 假设已利用MFC AppWizard应用程序向导建立了一个名为Mysdi的单文档应用程序,请向应用程序中添加对话框资源和对话框类。 在Adding a Class对话框单击OK按钮,弹出New Class对话框:
  • 237. 1.首先向应用程序项目添加一个对话框资源,在项目工作区鼠标右击资源项Dialog,从弹出式菜单中执行Insert Dialog命令,插入一个对话框资源。 2.设置对话框的属性,将光标指向对话框的空白位置,单击鼠标右键,从弹出式菜单中选择Properties项,或直接按Enter键,弹出Dialog Properties属性对话框。该对话框包括General、Styles、More Styles、Extended Styles、More Extended Styles等页面,用于设置对话框多种不同的属性。 3.创建对话框类,鼠标双击对话框资源的非控件区域。
  • 238. 6.2.3 添加控件及关联的成员变量如果没有控件,对话框完成不了什么具体功能,对话框与控件有着密不可分的关系。结合对话框编辑器,利用Controls控件工具栏可给当前正在编辑的对话框模板资源添加控件。 若Visual C++窗口中没有出现Controls工具栏,只需将光标指向菜单栏并单击鼠标右键,从弹出式菜单中选择Controls项。 控件工具栏上的每一个图标都代表了一种控件,如果不能确定控件的类型,只要将光标在该控件图标上停留片刻,就会显示该控件类型的提示。
  • 239. 向对话框添加控件时,先在Controls工具栏单击要添加的控件,再将光标指向对话框空白区域并单击鼠标。
  • 240. 添加与控件关联的成员变量 在生成自己的对话框类并添加需要的控件后,可以利用ClassWizard类向导在对话框类的定义中为对话框资源上的每一个控件添加一个或多个对应的成员变量。ClassWizard类向导的Member Variables页面主要用来为对话框类添加和删除与对话框控件关联的成员变量,我们在编写对话框程序时经常同该页面打交道。
  • 241. (本页无文本内容)
  • 242. Class name下拉框:选择要添加成员变量的对话 框类; Control IDs栏:选择控件,因为要添加的成员变量总是与一个对话框控件ID联系在一起,它们代表控件对象本身或控件的某项属性; Add Variable按钮:添加与控件关联的成员变量; Delete Variable按钮:删除控件的某个成员变量。 Control IDs栏:列出对话框资源上已有的控件,第1列Control IDs表示控件的ID,第2列Type表示变量的类型,第3列Member表示成员变量名。在Member Variables页面:
  • 243. Add Member Variable对话框Member variable name框:输入成员变量名; Category下拉框:选择成员变量的类别,可为Control或 Value; Variable type下拉框:选择成员变量的类型。
  • 244. 如果在Category下拉框选择Value项,表示要为该控件的某项属性定义一个变量,这意味着程序关心的是控件中的值,而不是控件对象本身。这时,还可以通过Variable type下拉框为变量选择不同的类型,可以选择一般的C++数据类型或Visual C++自定义的数据类型。 例如,对于编辑框控件,此时成员变量的类型可以是int、float、long、BOOL等C++一般数据类型或者是UINT、CString等Visual C++自定义数据类型。
  • 245. 如果在Category下拉框中选择Control项,则表示定义的变量代表控件对象本身, Control类别的变量实质是一个控件对象,其类型是MFC控件类。 例如,对于编辑框控件,此时成员变量的类型为CEdit。因此,可以通过添加的控件对象访问控件类的成员变量和调用控件类的成员函数,实现对控件行为的控制和管理。可以为一个控件同时定义一个Control类别的变量和一个Value类别的变量。这些变量都是作为对话框派生类的成员变量,为了在程序其它地方能够直接访问添加的成员变量,它们都被声明为public属性。
  • 246. 例 完善Mysdi程序,向对话框资源添加需要使用的控件,并添加与控件关联的成员变量。1.向对话框资源添加控件。添加一个静态文本控件,其Caption为“请输入半径”。添加一个Edit Box编辑框控件,其ID标识为IDC_EDIT_ RADIUS,该控件用于接收用户的输入数据。 2.添加与控件关联的成员变量。按Ctrl+W键启动ClassWizard类向导,单击Member Variables标签。在Class name下拉框选择类CRadiusDialog,在Control IDs栏选择编辑框IDC_EDIT_RADIUS,单击Add Variable按钮。通过Add Member Variable对话框添加成员变量m_nRadius,其数据类型为UINT。 3.使用对话框数据验证DDV功能,在该页面的左下角输入成员变量m_nRadius的最小值5和最大值250。
  • 247. 例 完善Mysdi程序,通过“编辑”菜单中的“输入半径(I)”命令打开上述标题为“输入半径”的对话框,并根据输入的半径画一个圆。 1.为了在视图对象中接收并存储对话框编辑控件的值,在视图类CMysdiView中手工定义一个UINT类型的成员变量m_nCViewRadius。 2.利用菜单编辑器在“编辑”菜单增加一个菜单项“输入半径(I)”,其ID标识为ID_EDIT_INPUTRADIUS,Caption为“输入半径(&I)...”。按Ctrl+W键启动ClassWzard类向导,在视图类中为ID_EDIT_INPUTRADIUS菜单项添加消息COMMAND的处理函数,在函数中添加代码。在程序中使用创建的对话框
  • 248. void CMysdiView::OnEditInputradius() { // TODO: Add your command handler code here CRadiusDialog dlg; // 定义一个对话框对象 dlg.m_nRadius=100; // 设置编辑框显示的初始值 if (dlg.DoModal()==IDOK) // 显示对话框 { m_nCViewRadius=dlg.m_nRadius; // 接收并存储编辑框数据 Invalidate(); // 刷新视图 } }
  • 249. 3.在视图类构造函数CMysdiView::CMysdiView()中将成员变量m_nCViewRadius初始化为0。在函数CMysdiView::OnDraw()中添加如下画圆的语句: pDC->Ellipse(0, 0, 2*m_nCViewRadius, 2*m_nCViewRadius); 在视图类实现文件MysdiView.cpp的开始位置加入包含对话框类头文件的语句: #include "RadiusDialog.h”
  • 250. 6.3 标准控件 Windows提供的控件分为两类:标准控件和公共控件。 标准控件:静态控件、编辑框、按钮、列表框、组合框和滚动条等。利用标准控件可满足大部分用户界面程序设计的要求。 公共控件:滑块、进度条、列表视控件、树视控件和标签控件等,利用公共控件实现应用程序用户界面风格的多样性。
  • 251. 6.3.1 控件概述控件是Windows提供的完成特定功能的独立小部件,它使应用程序对话功能的设计更容易完成,在对话框与用户的交互过程中担任主要角色,用于完成用户输入和程序运行过程中的输出。 控件对应一个CWnd派生类的对象,它实际上也是一个窗口,可以通过调用窗口类的成员函数实现控件的移动、显示或隐藏、禁用或可用等操作,也可以重新设置它们的尺寸和风格等属性。 MFC以类的形式对标准控件和公共控件进行了封装,这些类大部分是从CWnd类直接派生而来。
  • 252. 常用的MFC控件类 MFC类控 件CStatic 静态文本、图片控件 CEdit 编辑框 CButton 按钮、复选框、单选按钮、组框 CComboBox 组合框 CListBox 列表框 CScrollBar 滚动条 CSpinButtonCtrl 旋转按钮 CProgressCtrl 进度条 CSliderCtrl 滑块 CListCtrl 列表视控件
  • 253. 常用的MFC控件类 MFC类控 件CTreeCtrl 树视控件 CTabCtrl 标签 CAnimateCtrl 动画控件 CRichEditCtrl 复合编辑框 CDateTimeCtrl 日期时间选取器 CMonthCalCtrl 日历 CComboBoxEx 扩展组合框 CStatusBarCtrl 状态条控件 CToolBarCtrl 工具条控件 CImageList 图像列表
  • 254. 控件通知Notification消息:用户对控件的操作将引发控件事件,Windows产生对应的控件通知Notification消息,消息由其父窗口(如对话框)接收并处理。 标准控件发送WM_COMMAND控件通知消息,公共控件一般发送WM_NOTIFY控件通知消息,有时也发送WM_COMMAND消息。 通过WM_COMMAND消息参数标识发出消息的控件和具体的事件,消息参数中包含了控件的ID标识和通知码(如BN_CLICKED单击按钮事件)。控件通知码前缀最后一个字母为N。 程序员不必关心消息具体的发送和接收,只需利用ClassWizard将控件映射到成员变量,将控件消息映射到成员函数,然后编写具体的处理代码。
  • 255. 控件的创建方法:控件在程序中可作为对话框控件或独立的窗口两种形式存在,因此,控件的创建方法也有两种。 一种方法是在对话框模板资源中指定控件,这样当应用程序创建对话框时,Windows就会为对话框创建控件,编程时我们一般都采用这种方法。 一种方法是通过调用MFC控件类的成员函数Create()创建控件,也可以调用API函数CreateWindow()或CreateWindowEx()创建控件。 控件也可以用于其它窗口,如在程序视图窗口显示控件。这时,需要首先声明一个MFC控件类的对象,然后调用Create()函数创建控件。
  • 256. 6.3.2 控件的组织1. 添加或删除控件 打开对话框编辑器和控件工具栏,在控件工具栏中单击要添加的控件,此时,当光标指向对话框时将变成十字形状,在对话框指定位置处单击鼠标左键,则该控件被添加到对话框中指定的位置。也可以将光标指向控件工具栏中的控件,然后按住鼠标不放,采用鼠标拖曵的方法将控件拖入到对话框中。 要删除已添加的控件,先单击对话框中的控件,再按Delete键即可删除指定的控件。
  • 257. 2. 设置控件属性 将光标指向对话框中需设置属性的控件,按回车键(或右击鼠标,在弹出式菜单中选择Properties项)弹出Properties对话框,在Properties对话框中设置控件属性。可以将属性对话框始终保持打开,只需要按下属性对话框左上角的图钉按钮。 3. 调整控件的大小 对于静态文本控件,当输入标题内容时,控件的大小会自动改变。对于其它控件,先单击控件,然后利用控件周围的尺寸调整点来改变控件的大小。所选对象的位置和大小将显示在状态栏的右端。
  • 258. 4. 同时选取多个控件 一种方法是在对话框内按住鼠标不放,拖曵出一个大的虚线框,然后释放鼠标,则被该虚线框所包围的控件都将被同时选取;另一种方法是按住Shift(或Ctrl)键不放,然后用鼠标连续选取控件。 5. 移动和复制控件 当单个或多个控件被选取后,按方向键或用鼠标拖动选择的控件可移动控件。若在鼠标拖动过程中按住Ctrl键则复制控件,复制的控件保持原来控件的大小和属性。并且,控件能够通过复制和粘贴操作而加入到其它对话框中。
  • 259. 6. 编排控件 编排控件主要是指同时调整对话框中一组控件的大小或位置。编排控件有两种方法,一种方法是使用控件布局工具栏,自动编排对话框中同时选定的多个控件;另一种方法是使用Layout菜单,当打开对话框编辑器时,Layout菜单将出现在菜单栏上。为了便于用户在对话框内精确定位各个控件,系统还提供了网格、标尺等辅助功能。 测试对话框和控件的方法: 通过按下Ctrl+T组合键就能测试对话框运行时的界面效果。
  • 260. 6.3.3 控件共有属性控件的属性决定了控件的外观和功能,我们只有通过控件属性对话框才能设置控件的属性。 控件属性对话框上有若干选项卡,如General(通用属性)、Style(风格)及Extend Styles(扩展风格)选项卡等,其中General页用于设置控件的通用属性,Styles和Extendcd Styles页用来设置控件的外观和辅助属性。 不同控件有不同的属性,但它们都具有通用属性,如控件标识ID、标题Caption等项,
  • 261. 控件的General属性项 目说 明 ID 控件的标识,对话框编辑器会为每一个加入的控件分配一个默认的ID Caption 控件的标题,作为程序执行时在控件位置上显示的文本 Visible 指明显示对话框时该控件是否可见 Group 用于指定一个控件组中的第一个控件 HelpID 表示为该控件建立一个上下文相关的帮助标识ID Disabled 指定控件初始化时是否禁用 Tab Stop 表示对话框运行后该控件可以通过使用Tab键来获取焦点
  • 262. 6.3.4 静态控件静态控件(Static control)是用来显示一个文本串或图形信息的控件,它包括静态文本控件、图片控件和组框。 静态文本控件用来显示一般不需要变化的文本;图片控件用来显示边框、矩形、图标或位图等图形;组框用来显示一个文本标题和一个矩形边框,通常用来作为一组控件周围的虚拟边界,并将一组控件组织在一起。 管理静态文本控件和图片控件的MFC类是CStatic类,而管理组框的MFC类是CButton类。
  • 263. 所有静态控件默认的ID标识都为IDC_STATIC,如果要为一个静态控件添加成员变量或消息处理函数,必须重新为它指定一个唯一的ID标识。 编程时用的最多是静态文本控件,它被用来作为其它控件的标题。每一个静态文本控件最多可以显示255个字符,可以使用“\n”换行符,并可以通过Styles页面设置控件的风格。
  • 264. 6.3.5 编辑框编辑框(Edit box)又称文本框或编辑控件,它一般与静态文本控件一起使用,用于数据的输入和输出。编辑框提供了完整的键盘输入和编辑功能,可以输入各种文本、数字或者口令。 编辑框为用户提供了良好的输入、输出功能,能够将键盘输入的字符串转化为要求的数据类型,并验证它是否符合输入要求(字符串长度或数值范围)。通过前述对话框内容的学习我们已经知道,完成上述工作是利用了MFC提供的对话框数据交换(DDX)和数据验证(DDV)技术。
  • 265. 编辑框常用属性: Multiline设置单行或多行编辑,Align text设置文本对齐方式,Number表示只能输入数字,Password表示键入编辑框的字符都将显示为“*”,Uppercase或Lowercase表示键入编辑框的字符全部转换成大写或小写形式,Read-Only表示只能输出数据。 当编辑框的文本被修改,会向父窗口发送消息,可以利用ClassWizard在对话框类中添加消息处理函数。编辑框发送消息有:EN_CHANGE、EN_KILLFOCUS、EN_SETFOCUS、EN_MAX TEXT、EN_UPDATE等。
  • 266. 例 编写一个SDI应用程序Password,程序启动后首先弹出一个如图所示的用户身份确认对话框,当用户键入正确的口令后才能进入程序的主界面。
  • 267. 6.3.6 按钮 按钮(Button)包括按键按钮、单选按钮、复选框和组框等4种类型。虽然封装按钮控件的MFC类都是CButton类,但它们具有不同的功能。按键按钮在被按下时会立即执行某个命令,也被称为命令按钮;单选按钮用于在一组互相排斥的选项中选择其中一项;复选框用于在一组选项中选择其中一项或多项;组框可以使一组控件关联起来。
  • 268. 1. 按键按钮 几乎所有的对话框都使用简单的按键按钮,如OK按钮。通过Properties对话框可以设置按钮的不同风格,如通过设置Default button属性设置一个默认按钮。默认按钮是指当对话框刚显示时的命令执行按钮,此时按下Enter键将执行该按钮的命令功能。一个对话框只能有一个Default button默认按钮,通常情况下将OK按钮设置为默认按钮。 其它属性:Owner draw利用WM_DRAWITEM消息处理函数定制按钮的外观,Icon表示用图标代替原来的文本标题,Bitmap表示用位图代替原来的文本标题,Client edge、Static edge、Modal frame用于设置按钮的外观。
  • 269. 2. 单选按钮单选按钮是由一个圆圈和紧随其后的文本标题组成,当它被选中时,圆圈中就标上一个黑点。 单选按钮设置Auto自动属性为默认属性,Auto属性表示用户若选中同组中的某个单选按钮,则自动清除其余单选按钮的选中状态,保证一组选项中始终只有一项被选中。 一般将一组单选按钮放在一个组框中,在一组单选按钮中,第一个(Tab键顺序)按钮最重要,其ID值可用于在对话框中为控件建立关联的成员变量。必须为同组中的第一个单选按钮设置Group属性,而同组的其它单选按钮不可再设置Group属性。
  • 270. 3. 复选框复选框是由一个空心方框和紧随其后的文本组成,当它被选中时,空心方框中就加上一个“√”或“×”标记。 不同于单选按钮,在一组复选框中每次可以同时选择多项。除了选中和没选中两种状态,复选框还有第三种状态,此时选中标记显示为暗色,它表示该项不可以由用户选择。通过设置Tri-state属性得到这种三态复选框。另外,我们可以通过设置Push-like属性使单选按钮或复选框具有普通按钮的外观。
  • 271. 按钮控件发送的消息:按钮控件只能发送通知码为BN_CLICKED(单击按钮)和BN_DOUBLECLICKED(双击按钮)的WM_COMMAND消息,我们经常需要编写按钮的BN_CLICKED消息处理函数。 CButton类提供了一些成员函数实现对按钮控件对象的控制和管理,如利用成员函数GetCheck()或SetCheck()获取或设置单选按钮或复选框的当前状态,利用成员函数GetButtonStyle()或SetButtonStyle()获取或改变按钮控件的风格。
  • 272. 例 编写一个对话框应用程序ColrButn,对话框中有两个用于选择颜色模式的单选按钮和三个用于选择具体颜色的复选框,只有在彩色模式下才能选择三种不同颜色的组合。当用户单击“应用”按钮,对话框右边的按键按钮将根据选择的颜色实现按钮的自画。
  • 273. 6.3.7 列表框Windows提供了几个列表类型控件,如列表框、组合框、列表视控件和树视控件,其中列表框是一种最简单的列表类型控件。 列表框(List box)是一个列出了一些文本项的窗口,常用来显示类型相同的一系列清单,如文件、字体和用户等。与复选框类似,用户可以选择其中一项或多项,但同时列表框中选项的数目和内容可以动态变化,用户可往列表框中添加或删除某些选项。 列表框有单选Single、多选Multiple、扩展多选Extended、不选None四种风格,在控件Properties属性对话框的Selection下拉框中设置。
  • 274. 当列表框中发生了某个事件,列表框就会向其父窗口发送一条通知消息。列表框常用的通知消息有:用户双击列表框中的列表项时发送消息LBN_DBLCLK,列表框失去键盘输入焦点时发送消息LBN_KILLFOCUS,列表框获得键盘输入焦点时发送消息LBN_SETFOCUS,列表框中的当前选择项发生改变时发送消息LBN_SELCHANGE。 封装列表框控件的MFC类是CListBox类,当列表框创建之后,在程序中可以通过调用CListBox类成员函数来实现列表项的添加、删除、修改和获取等操作。
  • 275. 例 编写一个对话框应用程ExmpList,对话框中有一个列表框,当用户单击列表框中的一个列表项(一个国家)时,在四个编辑框分别显示指定国家的名称、首都、面积和人口。单击“添加”按钮时,“国家”编辑框中的文本将被添加到列表框中;单击“删除”按钮时,当前的列表项将被删除。
  • 276. 6.3.8 组合框编辑框和列表框在使用时受到一些限制,编辑框允许用户输入文本内容,但用户却不能直接选择以前已输入的文本内容。列表框可列出各种可能的选项,但用户却不能在列表框中输入新的列表项。而组合框(Combo box)吸收了列表框和编辑框的优点,它可以显示列表项供用户进行选择,也允许用户输入新的列表项。实质上,组合框是多个控件的组合,包括编辑框、列表框和按钮。 组合框有简单组合框(Simple)、下拉组合框(Dropdown)和下拉列表框(Drop List)等三种形式,通过控件Properties属性对话框Styles页面的Type下拉框设置这三种形式。
  • 277. 组合框控件的Data属性: 与编辑框或列表框相比,组合框有一个新的功能属性,可以通过组合框控件属性对话框的Data页面添加初始的列表项。注意,每输入完一个列表项,按下Ctrl+Enter键后才能换行输入下一项。也可以在对话框类的初始化成员函数OnInitDialog()中编写代码添加列表项。
  • 278. 组合框发送的消息 : 组合框消息有:关闭组合框消息CBN_CLOSEUP,打开列表框消息CBN_DROPDOWN,双击列表项消息CBN_DBLCLK,选择一个列表项并按下Enter键或单击下拉按钮隐藏列表框时发送消息CBN_SELENDOK,当前选项被取消时(如重新选择其它控件或关闭对话框)发送消息CBN_SELENDCANCEL,当前选项改变消息CBN_SELCHANGE;当组合框的编辑框中的文本被修改且新的文本显示之后发送消息CBN_EDIT CHANGE,当编辑框中的文本被修改且新的文本显示之前发送消息CBN_EDITUPDATE;组合框失去键盘输入焦点消息CBN_KILLFOCUS,组合框获得键盘输入焦点消息CBN_SETFOCUS。
  • 279. 例 编写一个单文档应用程序ExmpCombo,执行“测试控件|组合框”菜单命令时打开一个对话框,初始的对话框有一个用于显示标准控件名的组合框,当用户在组合框下拉的列表框中选择一个控件时,该控件将显示在对话框的右部。当用户在组合框的编辑框中输入一个列表项并单击“应用”按钮,一个指定标题的静态文本控件将出现在对话框的右部。
  • 280. 6.3.9 滚动条滚动条作为一个独立的控件,其主要作用通过可视化的滚动操作实现程序设计所要求的功能,如滚动显示数据内容、在一个有效范围内选取合适的数值等。 滚动条分为垂直滚动条和水平滚动条两种类型。滚动条两端有两个箭头按钮,中间有一个可沿滚动条方向移动的滚动块。 当移动滚动条时发送消息WM_HSCROLL或WM_VSCROLL。滚动消息含有通知码,表示用户对滚动条的操作方式,如SB_LINEUP和SB_LINEDOWN表示向上或下滚动一行(或一个单位),SB_PAGEUP表示向上滚动一页。
  • 281. 5 MFC编程基础1.1 可视化程序设计的概念 1.2 Windows程序的特点 1.3 VC开发程序的方法 1.4 Windows运行机制 1.5 Windows基本数据类型 1.6 VC的命名规则 1.7 基本术语 1.8 Windows应用程序的基本结构
  • 282. 一个优秀的编程工具往往匹配一个功能强大的类库,类库封装了大量Windows编程需要使用的函数和数据结构。与Visual C++捆绑在一起的MFC(Microsoft Foundation Class)微软基础类就是这样一个由Microsoft公司设计的类库。采用MFC方式编程提高了Windows应用程序的开发效率,但由于MFC应用程序结构的复杂性和透明性,要想完全掌握MFC应用程序的内部机制需要付出很大的努力。 认识MFC只是一个过程、一个手段,最终目的是为了良好并熟练地运用MFC。
  • 283. 本章主要学习内容: Windows的编程机制 MFC的基本原理和使用方法: MFC类 MFC应用程序框架 MFC消息管理 MFC宏 常用的MFC类
  • 284. 7.1 Windows编程基础 Windows是一个多进程的图形窗口操作系统,Windows应用程序与DOS应用程序有很大的区别。DOS应用程序采用顺序执行过程,而Windows是一个基于事件的消息(Message)驱动系统。 Windows应用程序是按照“事件→消息→处理”非顺序的机制运行。当有某个事件(如单击鼠标、键盘输入和执行菜单命令等)发生时,Windows会根据具体的事件产生对应的消息,并发送到指定应用程序的消息队列;应用程序从消息队列中取出消息,并根据不同的消息进行不同的处理。
  • 285. Windows API(Application Programming Interface)是Windows操作系统与应用程序之间的标准接口,它提供了上千个标准函数、宏和数据结构的定义。 在使用Visual C++、Visual Basic和Delphi编程时都可以调用Windows API函数,Windows应用程序可通过调用标准Windows API函数使用系统提供的功能。 Windows API函数定义在一些DLL动态链接库中,其中最主要的DLL是User32.dll、Gdi32.dll和Kernel32.dll三个库文件。 7.1.1 关于API和SDK
  • 286. 传统SDK编程:程序员通过调用API函数,自己动手、按部就班地实现程序各部分的功能。SDK应用程序的结构比较清晰,但程序员必须编写所有的功能代码。 利用Visual C++编写一个类SDK应用程序:首先利用Win32 Application向导建立一个Windows应用程序框架,然后根据需要可以向程序项目中添加一些头文件、实现源文件和资源文件,并编写具体的程序代码。例 编写一个名为Hello的类SDK应用程序,当单击鼠标时通过调用API函数,以弹出一个提示信息对话框。
  • 287. 1.执行“File|New”菜单命令,在New对话框的Project页面中选择Win32 Application项目类型,输入程序名Hello。在向导第1步选择“A typital “Hello world!” application”项,单击Finish按钮。 2.在窗口函数WndProc()的消息处理分支switch-case结构中添加WM_LBUTTONDOWN鼠标单击消息及其处理代码: case WM_LBUTTONDOWN: MessageBox(NULL, "You pressed the left button of mouse !","Message",NULL); break;
  • 288. SDK应用程序结构:一个由API函数构造的Windows程序的功能由三个部分组成:入口函数、窗口函数和Windows系统。 每一个程序都有一个主函数,WinMain()函数就是Windows程序的入口主函数。该函数的主要任务是完成一些初始化工作并维护一个消息循环。当消息循环结束后,就退出了WinMain()函数,也就退出了应用程序。此外,WinMain()函数还负责完成窗口的注册、创建和显示。 Windows程序以窗口的形式存在,在不同窗口之间传递消息是Windows和应用程序进行交流的主要形式。程序具体功能由不同的窗口函数实现。
  • 289. 7.1.2 句柄 在Windows中,用句柄(Handle)标识应用程序中不同的对象和同类对象中不同的实例,如一个具体的窗口、按钮、输出设备、画笔和文件等。通过句柄可以获得相应的对象信息。常用的句柄类型有: HWND 、 HINSTANCE 、 HDC 、HCURSOR、HICON、HMENU等。 句柄常作为Windows消息和API函数的参数,在采用API方法编写Windows应用程序时要经常使用句柄。而采用MFC方法编写Windows应用程序时,由于对应的MFC类已对句柄进行了封装,大多数情况下不再需要访问句柄。
  • 290. 获取MFC类对象的句柄的两种方法: 通过访问类的public属性成员变量,如类CWnd的成员变量m_hWnd就是一个窗口对象的句柄。 先定义一个句柄,然后调用MFC类的成员函数Attach()将句柄与一个MFC类对象联系在一起,此时的句柄就成为该MFC类对象的句柄。在退出对象作用域之前,调用成员函数Detach()将句柄和对象进行分离。如下所示: CWnd myWnd; HWND hWnd; myWnd.Attach(hWnd); . . . . . . myWnd.Detach();
  • 291. 所谓消息就是用于描述某个事件发生的信息,而事件是对于Windows的某种操作。 事件和消息密切相关,事件是因,消息是果,事件产生消息,消息对应事件。所谓消息的响应,其实质就是事件的响应。 消息驱动是Windows应用程序的核心,所有的外部响应(如键盘、鼠标和计时器等)都被Windows先拦截,转换成消息后再发送到应用程序中的目标对象,应用程序根据消息的具体内容进行处理。 消息不仅可由Windows发出,它也可由应用程序本身或其它程序产生。Windows为每一个应用程序都维护一个或多个消息队列,发送到每个程序窗口的消息都排成一个队列。 7.1.3 事件和消息
  • 292. 消息队列和在应用程序中的轮询处理
  • 293. Windows消息分为三种类型:标准Windows消息:以WM_前缀(但不包括WM_COMMAND)开始的消息,包括鼠标消息、键盘消息和窗口消息,如WM_MOVE 、WM_PAINT等。 控件通知(Control Notification)消息:对控件操作引起的消息,是控件和子窗口向其父窗口发出的WM_COMMAND通知消息。例如,当用户修改了编辑控件中的文本后,编辑控件向其父窗口发送WM_COMMAND通知消息。 命令(Command)消息:由菜单项、工具栏按钮、快捷键等用户界面对象发出的WM_COMMAND消息。命令消息与其它消息不同,它可被更广泛的的对象如文档、文档模板、应用程序对象、窗口和视图等处理。
  • 294. typedef struct tagMSG { HWND hWnd; // 目标窗口句柄 UINT message; // 消息标识 WPARAM wParam; // 消息参数1(附加信息,16位) LPARAM lParam; // 消息参数2(附加信息,32位) DWORD time; // 消息发送时间 POINT pt; // 消息发送时鼠标的屏幕坐标 } MSG;消息用MSG结构表示:
  • 295. 例 为程序Hello添加键盘消息处理功能,判断当前按下的键是不是A或a键,并给出相应的提示。 打开程序项目Hello,在文件Hello.cpp的窗口函数WndProc()的switch消息处理分支中添加键盘消息的处理代码: case WM_KEYDOWN: // 处理键盘消息 if(wParam==0x41) // A或a键的虚键码为0x41H MessageBox(NULL, "The key you pressed is A or a !","KEYDOWN",NULL); else MessageBox(NULL, "The key you pressed is not A or a !","KEYDOWN",NULL); break;
  • 296. 利用Windows API开发程序的用户有这样的体会,即使开发一个简单的Windows应用程序也需要对Windows的编程原理有很深刻的认识,需要手工编写冗长的代码。由于程序的出错率是随着代码长度的增加呈几何级数增长的,而且当程序长度逐渐膨胀时,调试程序会变得越来越困难。因此,传统的Windows应用程序设计需要程序员有极大的耐心和丰富的编程经验。 Visual C++捆绑了微软的基础类MFC,编程时我们就可以利用类的可重用性和可扩充性,大大降低Windows应用程序设计的难度和工作量。 7.2 MFC微软基础类
  • 297. 7.2.1 MFC概述类库是一个可以在应用程序中使用的相互关联的C++类的集合。 MFC作为一个Windows编程类库,它包含了200多个类,封装了Windows的大部分编程对象以及与它们有关的操作。 虽然程序在功能上千差万别,但从本质上看,都可以分为用户界面设计、文件操作、数据库访问及多媒体使用等几部分,这些都可以通过一些类来实现。MFC提供了一个标准化的程序结构,使开发人员不必从头设计一个Windows应用程序。 MFC实际上是一个庞大的文件库,它由几百个执行文件和源代码文件(如H文件)组成。
  • 298. 使用标准化的程序代码结构,有利于程序员之间的交流。 Visual C++为MFC提供了大量的工具支持,提高了编程效率。如利用MFC AppWizard创建MFC应用程序框架,利用ClassWizard方便地对Windows消息进行管理。 MFC应用程序的效率较高,只比传统的Windows C程序低5%左右。并且,在MFC应用程序中还允许混合使用传统的Windows API函数。 其它优势:完全支持Windows所有的函数、控件、消息、菜单及对话框;具有良好的稳定性和可移植性,更符合微软的风格等。 采用MFC编程的优点:
  • 299. 7.2.2 MFC体系结构MFC主要组成部分:类、宏和全局函数。 类是MFC中最主要的内容。MFC类是以层次结构方式组织起来的。MFC中的类分成两部分,除了一些辅助类,大多数的MFC类是直接或间接从根类CObject派生而来。 几乎每一个派生层次都与一具体的Windows实例相对应,如文档类、窗口类和视图类等。 MFC宏主要功能:消息映射、运行时对象类型服务、诊断服务、异常处理。 MFC约定:全局函数以“Afx”为前缀,全局变量以“afx”为前缀。
  • 300. 首先要对Windows编程概念和API函数有一定的了解,如Windows API有哪些功能和哪些常用的数据结构等。 学会抽象地把握问题,不求甚解,不要一开始学习Visual C++就试图了解整个MFC类库。从理解和使用两个方面学习MFC,理解MFC应用程序的框架结构。 先大体上了解MFC的概念、组成和基本约定,从简单的类入手,结合程序设计,由浅入深,循序渐进、日积月累。 编程时如果MFC某个类能完成所需要的功能,可以直接调用已有类的方法(成员函数)。否则,可以利用面向对象技术中的“继承”方法对MFC类的行为进行扩充和修改,从MFC中已有的类派生出自己需要的类。 学习MFC,另一点就是不要过分依赖于向导(Wizard)工具。向导能做许多工作,但同时掩饰了太多的细节。 7.2.3 学习MFC的方法
  • 301. 尽管每个应用程序具体实现的功能不同,但同一类程序的基本结构是相同的。因此,通常采用MFC AppWizard创建一个MFC应用程序框架。 MFC不仅仅是一个类库,它还提供了一层建立在MFC类对象封装上的附加应用程序框架。应用程序框架是为了生成一般的应用程序所必需的各种软组件的集成,是类库的一种超集。 类库只是一种可以嵌入到任何程序中的、提供某些特定功能的类的集合。而应用程序框架却定制了应用程序的结构和源代码,其中的类对象既相互独立、又相互作用,形成一个统一的整体。 7.3 MFC应用程序框架
  • 302. MFC应用程序框架提供了构建应用程序所需要的类,在程序运行时能够生成运行时类的对象,如代表应用程序对象、文档对象、视图对象和框架窗口对象。应用程序对象theApp是一个唯一的全局变量,它的主要功能是通过调用WinMain()主函数启动程序的运行。 MFC应用程序框架也有一个作为程序入口点的WinMain()主函数,但在源程序中看不见该函数,它在MFC中已定义好并同应用程序相链接。 7.3.1 应用程序框架中的对象
  • 303. MFC应用程序对象之间的关系
  • 304. 在MFC应用程序的CWinApp派生类对象theApp是一个全局变量,代表了应用程序运行的主线程。它在程序整个运行期间都存在,它的销毁意味着运行程序的消亡。 MFC应用程序启动时,首先创建应用程序对象theApp,这时将自动调用应用程序类的构造函数初始化对象theApp,然后由应用程序框架调用MFC提供的AfxWinMain()主函数。 AfxWinMain()主函数首先通过调用全局函数AfxGetApp()获取应用程序对象theApp的指针pApp,然后通过pApp调用应用程序对象的有关成员函数,完成程序的初始化和启动工作,最后调用成员函数Run(),进入消息循环。 程序运行后将收到WM_PAINT消息,调用OnPaint()函数绘制客户区窗口。如果Run()收到WM_QUIT消息,则结束消息循环,然后调用函数ExitInstance(),结束程序运行。 7.3.2 MFC应用程序的生存与消亡
  • 305. MFC应用程序运行后各函数的调用关系 InitInstance()函数是派生类唯一需要重载的函数,它负责应用程序的初始化,如初始化数据、创建文档模板、处理命令行以及显示应用程序主窗口。
  • 306. 7.3.3 常用的MFC文件 文 件 名 称 说 明 afxwin.h声明MFC核心类afxext.hMFC扩展文件,声明工具栏、状态栏、拆分窗口等类afxdisp.h声明OLE类afxdtctl.h声明支持IE 4公用控件的MFC类,如CImageList等afxcmn.h声明Windows公共控件类Mfc42.libMFCxx.DLL的导入函数库(Release版)Mfc42D.libMFCxx.DLL的导入函数库(Debug版)MfcS42.libMFCSxx.DLL的导入函数库(Static Release版)MfcS42D.libMFCSxxD.DLL的导入函数库(Static Debug版)Mfc42U.libMFCxxU.DLL的导入函数库(Unicode Release版)
  • 307. Mfc42UD.libMFCxxUD.DLL的导入函数库(Unicode Debug版)MfcO42D.libMFCOxxD.DLL的导入函数库(OLE Debug版)MfcD42D.libMFCDxxD.DLL的导入函数库(Database Debug版)Nafxcw.libMFC静态链接库(Release版)NafxcwD.libMFC静态链接库(Debug版)gdi32.libGDI32.DLL的导入函数库user32.libUSER32.DLL的导入函数库kernel32.libKERNEL32.DLL的导入函数库msvcrt.libMSVCRT.DLL(C运行函数库)的导入函数库msvcrtd.libMSVCRTD.DLL(Debug版C运行函数库)的导入函数库libcmt.libC运行函数静态链接库(多线程)libc.libC运行函数静态链接库(单线程)
  • 308. MFC消息管理是MFC编程的一个重要内容,也是编写MFC应用程序的基础。 MFC应用程序消息处理的方式与SDK应用程序有所不同。MFC应用程序框架截取了Windows向应用程序发出的消息,再确定将消息发送给哪一个对象,可以根据需要利用函数重载对消息进行处理,但不需要处理的消息将由应用程序框架自动处理。 消息管理包括消息的发送和处理。对于消息发送,MFC提供了类似于API函数功能的消息发送函数,而MFC消息处理的内部机制则相对复杂一些。从编程的角度出发,我们只需了解其大致的原理。 7.4 MFC消息管理
  • 309. MFC采用消息映射(Message Map)机制取代C/C++语言中的switch-case结构来处理消息。 MFC消息映射机制包括一组消息映射宏。一条消息映射宏把一个Windows消息和其消息处理函数联结起来。 MFC应用程序框架提供了消息映射功能。 在类的实现源文件中用BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()宏来定义消息映射。 在类定义的结尾用DECLARE_MESSAGE_MAP()宏来声明使用消息映射。 7.4.1 MFC消息映射机制
  • 310. BEGIN_MESSAGE_MAP(theclass, baseclass) //{{AFX_MSG_MAP(theclass) ON_ . . . . . . // MFC预定义的消息映射宏 ON_MESSAGE(message , memberFxn) // 用户自定义的消息映射宏 . . . . . . //}}AFX_MSG_MAP END_MESSAGE_MAP()MFC应用程序MESSAGE_MAP消息映射形式: 注意:特殊注解“//{{AFX_MSG_MAP”是ClassWizard类向导用于维护消息映射宏的标记,用户不要删除注解轻易修改注解内的代码。
  • 311. 启动ClassWizard,添加要求的三个消息处理函数,ClassWizard将在类的实现文件中添加三个消息映射宏和消息处理函数。消息映射宏如下: BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() // 由MFC AppWizard程序向导自动生成的消息映射 ON_WM_CLOSE() // 由ClassWizard类向导添加 ON_WM_DESTROY() ON_COMMAND(ID_EDIT_COPY, OnEditCopy) //}}AFX_MSG_MAP END_MESSAGE_MAP() 例 利用ClassWizard为框架类添加消息WM_CLOSE、WM_DESTROY及菜单项“Edit|Copy”的消息处理函数,分析ClassWizard完成了哪些工作。
  • 312. class CMainFrame : CFrameWnd { public: CMainFrame(); protected: //{{AFX_MSG(CMainFrame) // 声明消息处理函数原形 afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); // 由程序向导自动生成 afx_msg void OnClose(); // 由ClassWizard类向导添加 afx_msg void OnDestroy(); afx_msg void OnEditCopy(); //}}AFX_MSG DECLARE_MESSAGE_MAP() // 声明使用消息映射宏 }; ClassWizard在类的定义中声明了消息处理函数 :
  • 313. 发送消息到一个窗口可以采用传送(Send)或寄送(Post)方式,这两种方式之间的主要区别是消息被接收对象收到后是否立即被处理。Windows提供了三个API函数用于消息的发送。 函数SendMessage()用于向一个或多个窗口传送消息,该函数将调用目标窗口的窗口函数,直到目标窗口处理完收到的消息,该函数才返回。 函数PostMessage()用于向一个或多个窗口寄送消息,它把消息放在指定窗口创建的线程的消息队列中,然后不等消息处理完就返回。7.4.2 消息的发送
  • 314. 函数SendDlgItemMessage()函数用于向对话框中指定的控件发送消息,直到目标控件处理完收到的消息,该函数才返回。 MFC将这三个函数封装为CWnd窗口类的成员函数,封装了目标窗口句柄,它们将向调用它的窗口对象发送或寄送消息,如pMyView->SendMessage()的调用形式表示向pMyView所指对象发送消息。 与用户输入相关的消息(如鼠标消息和键盘消息)通常是以寄送(Post)的方式发送,以便这些用户输入可以由运行较缓慢的系统进行缓冲处理。而其它消息通常是以传送(Send)的方式发送
  • 315. 由于程序逻辑设计结构的限制或不同窗口之间数据的同步,程序员需要手工自定义一些消息。例如,如果需要在特定时间间隔内通知所有数据输出窗口重新取得数据,就必须采用定时器事件来产生特定的消息。 MFC允许用户自定义消息,常量WM_USER(为0x0400)与第一个自定义消息值相对应,用户必须为自己的消息定义相对于WM_USER的偏移值,。利用#define语句定义自己的消息,例如: #define WM_USER1 WM_USER+0 #define WM_USER2 WM_USER+17.4.3 自定义消息处理
  • 316. 必须在函数返回类型前面加上afx_msg标识,如: afx_msg LRESULT memberFxn ( WPARAM wParam , LPARAM lParam); 其中,参数wParam、lParam用于传递消息的两个附加信息自定义消息处理函数的声明: ON_MESSAGE(message, memberFxn) 其中,message是消息名标识,memberFxn是对应的 消息处理函数。如: ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)自定义消息映射宏的定义:
  • 317. 1.首先利用MFC AppWizard[exe]向导创建一个名为Rotate的应用程序。利用ClassWizard类向导为CRotateView类生成消息WM_CREATE的消息处理函数,通过设置定时器在指定的时间间隔向窗口发送WM_TIMER消息。 SetTimer(1,200,NULL); // 启动定时器 2.在文件RotateView.cpp开始位置定义一个用户自定义消息: #define WM_MYMESSAGE WM_USER+1 利用ClassWizard为CRotateView类生成消息WM_TIME的消息处理函数:SendMessage(WM_MYMESSAGE); 例 编写一个自定义消息应用程序,程序启动后设置一个定时器,在WM_TIMER的消息处理函数中发送一个用户自定义消息,在对应的自定义消息处理函数中以动画的形式旋转显示一行文本。
  • 318. 3.在类CRotateView的定义中声明自定义消息处理函数: afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); 在文件RotateView.cpp消息映射BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加自定义消息映射宏: ON_MESSAGE(WM_MYMESSAGE,OnMyMyessage) 4.在类CRotateView的定义中声明一个private属性、int型的成员变量m_dEscapement,它表示文本显示角度,并在类CRotateView的构造函数中初始化:m_dEscapement=0; 5.在文件RotateView.cpp中手工添加自定义消息处理函数实现代码,完成以动画形式旋转显示一行文本的功能。 6.利用ClassWizard生成消息WM_DESTROY的消息处理函数,在销毁已创建的定时器:KillTimer(1);
  • 319. 宏就是用预处理指令#define定义一个标识符,用它来表示一个字符串或一段源代码。MFC宏作为MFC类库的一个组成部分在MFC应用程序中经常出现。MFC宏在Afxwin.h、Afx.h及Afxmsg_.h等MFC头文件中分别进行了定义。 MFC提供的宏有很多,常用的包括消息映射宏、运行时类型识别的宏、调试宏和异常宏等,下表列出了一般MFC编程时要用到的宏。 7.5 MFC宏
  • 320.   RUNTIME_CLASS 获得运行时类的CRuntimeClass结构的DECLARE_DYNAMIC 提供基本的运行时类型识别(声明) IMPLEMENT_ DYNAMIC 提供基本的运行时类型识别(实现) DECLARE_DYNCREATE 动态创建(声明) IMPLEMENT_DYNCREATE 动态创建(实现) DECLARE_SERIAL 对象序列化(声明) IMPLEMENT_SERIAL 对象序列化(实现) DECLARE_MESSAGE_MAP 声明消息映射表 BEGIN_MESSAGE_MAP 开始建立消息映射表 END_MESSAGE_MAP 结束建立消息映射表 ON_COMMAND 命令消息映射宏 ON_MESSAGE 自定义消息映射宏 ON_WM_… MFC预定义消息映射宏 ON_BN_… , ON_CBN_… 等控件通知(Notification)消息映射宏
  • 321. 运行时类型识别RTTI(Run Time Type Information)是指在程序运行时允许确定对象的类型。MFC扩充了一般C++中运行时类型识别的功能,当一个类支持MFC的运行时类型识别时,它允许程序获取对象的信息(如类名、所占存储空间大小及版本号等)和基类信息。 1. 宏RUNTIME_CLASS(class_name): 返回参数class_name所指定类的静态成员变量class##class_name的指针,该指针指向一个CRuntimeClass结构。7.5.1 有关运行时类型识别的宏
  • 322. 2. 动态支持宏DECLARE_DYNAMIC()和IMPLEMENT_DYNAMIC(): 使用动态支持宏能够使CObject派生类的对象具有基本的类型识别机能,可以通过调用成员函数CObject::IsKindOf()测试对象与给定类的关系。 3. 动态创建宏DECLARE_DYNCREATE()和IMPLEMENT_DYNCREATE(): 动态创建是动态支持的一个超集,除了基本的类型识别机能,使用动态创建宏能够使CObject的派生类具有在运行时动态创建对象的功能。 4. 序列化宏DECLARE_SERIAL() 和IMPLEMENT_SERIAL()
  • 323. // 在头文件MyClass.h中 class CMyClass : public CObject { DECLARE_DYNAMIC(CMyClass) public: void SomeFunction(void); }; 例 定义类MyClass,使用RUNTIME_CLASS()宏的基本对象诊断功能。
  • 324. // 在实现文件MyClass.cpp中 #include "MyClass.h" IMPLEMENT_DYNAMIC(CMyClass, CObject) void CMyClass::SomeFunction(void) { CObject* pObject=new CMyClass; if(pObject->IsKindOf(RUNTIME_CLASS(CMyClass))) { CMyClass* pMyObject=(CMyClass*) pObject; AfxMessageBox("MyObject is an object of the class CMyClass"); } else AfxMessageBox("MyObject is not an object of the class CMyClass"); delete pObject; }
  • 325. 7.5.2 MFC调试宏 跟踪声明和断言在查找程序设计错误时是非常有用的。跟踪声明可以让程序在运行过程中遇到跟踪声明时在输出窗口显示指定的信息,而断言使程序在断言条件不成立时暂停程序的运行。MFC提供了一些跟踪声明和断言宏用于程序调试,这些宏只能用于DEBUG版本应用程序的调试状态。 1.TRACE()宏语法说明如下: TRACE (<表达式>); 其中参数<表达式>是由输出格式和变量等组成的输出表达式,其格式与函数printf()的参数一样,它指定调试时要在Output窗口输出的内容。
  • 326. char* szName = “LiMing”; int nAge = 18; TRACE(“Name = %s, Age = %d \n”, szName, nAge ); 例1 对于以下代码: 调试时在Output窗口输出以下内容: Name = LiMing, Age = 18 例2 设自定义一个CFrameWnd的派生类CMyFrame,在程序中构建一个与CMyFrame相关联的文档模板对象,并为构键的文档模板创建框架窗口: CMyFrame* pFrame=(CMyFrame*) AfxGetMainWnd(); ASSERT(pFrame->IsKindOf(RUNTIME_CLASS (CMyFrame))); // 判断pFrame的类型 pFrame->DoSomeOperation();
  • 327. 7.6.1 CRuntimeClass结构 CRuntimeClass在MFC中是以结构的方式定义,它含有使用CRuntimeClass结构的类的有关信息,与CObject类一同实现运行时类型识别RTTI的功能。CRuntimeClass结构包含了类名、对象所占存储空间大小及类的版本号等成员变量和动态对象创建、派生关系判断等成员函数。 每个从CObject类派生的类都有一个CRuntimeClass对象同它关联。要使用CRuntimeClass结构,必须结合使用RUNTIME_CLASS()宏和其它有关运行时类型识别的MFC宏。 7.6 常用的MFC类
  • 328. 7.6.2 CObject类  由于MFC中大部分类是从CObject类继承而来的,CObject类描述了几乎所有的MFC类的一些公共特性,CObject类为程序员提供了对象诊断、运行时类型识别和序列化等功能。 对象诊断。MFC提供了两种诊断特性:利用成员函数AssertValid()进行对象有效性检查,使得类可以在继续运行前对自己进行正确性检查;利用成员函数Dump()输出对象的数据成员的值。 运行时访问类的信息:提供GetRuntimeClass()和IskindOf()两个成员函数来支持运行时类型识别。 对象序列化。
  • 329. void CHuman::AssertValid() const { CObject::AssertValid(); ASSERT(m_year>0); ASSERT(m_year<120); } 例1 通过“Insert|New Class”定义一个CObject的派生类CHuman。在类CHuman中重载AssertValid()函数,利用ASSERT宏把人的年龄限制在一个合理的范围。 void CAssertView::OnDraw(CDC* pDC) { CHuman man; man.m_year=200; man.AssertValid(); // 超出年龄范围,将断言失败 }
  • 330. void CHuman::Dump(CDumpContext &dc ) const { CObject::Dump(dc); dc<<"Age = "<m_year=39; #ifdef _DEBUG // 调试时在调试器输出窗口显示m_year的值 pMyPerson->Dump(afxDump); #endif
  • 331. 7.6.3 CCmdTarget类 CCmdTarget类由CObject类直接派生而来,它负责将消息发送到能够响应这些消息的对象。它是所有能进行消息映射的MFC类的基类。 设置光标。CCmdTarget类定义3个函数用于改变光标状态:BeginWaitCursor()将光标改为沙漏形状,EndWaitCursor()将光标改回调用BeginWait- Cursor()之前的形状,RestoreWaitCursor()用于将光标还原为等待状态。 void CMyView::OnSomeCommand() { BeginWaitCursor(); // 显示沙漏状光标 . . . . . . // 进行某种操作 EndWaitCursor(); // 恢复原来光标的形状 }
  • 332. 7.6.4 CWinApp类 在MFC应用程序中,CWinApp类取代了WinMain()主函数在SDK应用程序中的地位。传统SDK应用程序WinMain()函数完成的工作现在由类CWinApp的InitApplication()、InitInstance()和Run()三个成员函数承担。 在任何MFC应用程序中有且仅有一个CWinApp派生类的对象,它代表了程序中运行的主线程,也代表了应用程序本身。
  • 333. 7.6.5 CWnd类 CWnd类由CCmdTarget类直接派生而来,该类及其派生类的实例是一个窗口。CWnd类代表了MFC中最基本的GUI对象,它是一个功能最完善、成员函数最多的MFC类。 窗口的实例包括应用程序主窗口、对话框和控件等。 CWnd类提供的功能包括注册新窗口类、创建窗口及子窗口、获取窗口、管理窗口、访问窗口及控件、控制窗口光标、创建和使用句柄和支持工具提示等,
  • 334. 7.6.6 CFrameWnd类 CFrameWnd类是CWnd类的派生类,主要用来掌管一个窗口,它取代了SDK应用程序中窗口函数WndProc()的地位。 CFrameWnd类的对象是一个框架窗口,包括边框、标题栏、菜单、最大化按钮、最小化按钮和一个激活的视图。 CFrameWnd支持SDI界面,对于MDI界面,使用其两个派生类CMDIFrameWnd和CMDIChildWnd。 CFrameWnd提供了若干个成员函数用于获得和设置活动文档、视图、图文框、标题栏、状态栏等操作。
  • 335. 7.6.7 CDocument和CView类 CDocument类在应用程序中作为用户文档类的基类,它代表了用户存储或打开的一个文件。CDocument类的主要功能是把对数据的处理从对用户的界面处理中分离出来,同时提供了一个与视图类交互的接口。 CDocument类支持标准的文件操作,如创建、打开和存储一个文档等。 CView类是MFC中一个很基本的类,它作为其它MFC视图类和用户视图派生类的基类。
  • 336. 6 Windows图形设备6.1 图形设备接口 6.2 画笔和画刷 6.3 文本与字体 6.4 位图、图标和光标
  • 337. Windows是一个图形操作系统,其所有的图形可视效果都是通过绘制操作而完成的。图形显示的实质就是利用Windows提供的图形设备接口将图形绘制在显示器上。大多数应用程序都需要在客户区绘制一些图形,如绘制文本、几何图形、位图和光标等。 前面几章已经涉及到有关图形处理的内容,只是使用了Windows系统默认的图形设备接口和设备环境,绘制的图形没有颜色、线型和字体的变化。
  • 338. 本章主要学习内容: 图形处理的基本原理: 图形设备接口 设备环境 GDI坐标系 映射模式 使用画笔和画刷绘制图形 文本与字体 位图、图标和光标
  • 339. 8.1 图形设备接口Windows提供了一个称为图形设备接口GDI(Graphics Device Interface)的抽象接口。GDI作为Windows的重要组成部分,它负责管理用户绘图操作时功能的转换。用户通过调用GDI函数与设备打交道,GDI通过不同设备提供的驱动程序将绘图语句转换为对应的绘图指令,避免了直接对硬件进行操作,从而实现所谓的设备无关性。 编程时采用MFC方法绘制图形也很方便,MFC对GDI函数和绘图对象进行了封装。
  • 340. 图形设备接口GDI管理Windows应用程序图形的绘制,在应用程序中,通过调用GDI函数绘制不同尺寸、颜色、风格的几何图形、文本和位图。这些图形处理函数组成了图形设备接口GDI。 GDI是形成Windows核心的三种动态链接库之一,MFC将GDI函数封装在一个名为CDC的设备环境类中,因此我们可以通过调用CDC类的成员函数来完成绘图操作。 所谓设备无关性,是指操作系统屏蔽了硬件设备的差异,使用户编程时一般无需考虑设备的类型,如不同种类的显示器或打印机。 8.1.1 概述
  • 341. Windows绘图过程和设备无关性的实现: GDI处于设备驱动程序的上一层,当程序调用绘图函数时,GDI将绘图命令传送给当前设备的驱动程序,以调用驱动程序提供的接口函数。驱动程序的接口函数将Windows绘图命令转化为设备能够执行的输出命令,实现图形的绘制。不同设备具有不同的驱动程序,设备驱动程序是设备相关的。
  • 342. 8.1.2 设备环境为了实现设备无关性,应用程序的输出不直接面向显示器等物理设备,而是面向一个称之为设备环境DC(Device Context)的虚拟逻辑设备。 设备环境也称设备描述表或设备上下文,它是由Windows管理的一个数据结构,它保存了绘图操作中一些共同需要设置的信息,如当前的画笔、画刷、字体和位图等图形对象及其属性,以及颜色和背景等影响图形输出的绘图模式。 形象地说,一个设备环境提供了一张画布和一些绘画的工具,我们可以使用不同颜色的工具在上面绘制点、线、圆和文本。
  • 343. 设备环境中的“设备”是指任何类型的显示器或打印机等输出设备,绘图时用户不用关心所使用设备的编程原理和方法。所有的绘制操作必须通过设备环境进行间接的处理,Windows自动将设备环境所描述的结构映射到相应的物理设备上。 从根本上来说,设备环境DC是一个Windows数据结构,该结构存储着程序向设备输出时所需要的信息,应用程序利用它定义图形对象及其属性,并实现应用程序、设备驱动程序和输出设备之间绘图命令的转换。 在Windows中不使用DC无法进行输出,在使用任何GDI绘图函数之前,必须建立一个设备环境。
  • 344. 获取设备环境DC的方法: 在程序中不能直接存取DC数据结构,只能通过系统提供的一系列函数或使用设备环境的句柄HDC来间接地获取或设置设备环境结构中的各项属性,如显示器高度和宽度、支持的颜色数及分辨率等。 如果采用SDK方法编程,获取DC的方法有两种:在WM_PAINT消息处理函数中通过调用API函数BeginPaint()获取设备环境,在消息处理函数返回前调用API函数EndPaint()释放设备环境。在其他函数中通过调用API函数GetDC()获取设备环境,调用API函数ReleaseDC()释放设备环境。
  • 345. 如果采用MFC方法编程,MFC提供了不同类型的DC类,每一个类都封装了DC句柄,并且它们的构造函数自动调用获取DC的API函数,析构函数自动调用释放DC的API函数。因此,在程序中通过声明一个MFC设备环境类的对象就自动获取了一个DC,而当该对象被销毁时就自动释放了获取的DC。MFC AppWizard应用程序向导创建的OnDraw()函数自动支持所获取的DC。 MFC的DC类包括CDC、CPaintDC、CClientDC、CWindowDC和CMetaFileDC等,其中CDC类是MFC设备环境类的基类,其它的MFC设备环境类都是CDC的派生类。
  • 346. CDC类既作为其它MFC设备环境类的基类,又可以作为一个一般的设备环境类使用。利用它可以访问设备属性和设置绘图属性。CDC类对GDI的所有绘图函数进行了封装。 CPaintDC类是OnPaint()函数使用的设备环境类,它代表一个窗口的绘图画面。如果添加WM_PAINT消息处理函数OnPaint(),就需要使用CPaintDC类来定义一个设备环境对象。 CClientDC类代表了客户区设备环境。当在客户区实时绘图时,需要利用CClientDC类定义一个客户区设备环境。 CWindowDC类代表了整个程序窗口设备环境,可以在整个窗口区域绘图。 MFC设备环境类:
  • 347. 8.1.3 GDI坐标系和映射模式 Windows坐标系分为逻辑坐标系和设备坐标系两种,GDI支持这两种坐标系。一般而言,GDI的文本和图形输出函数使用逻辑坐标,而在客户区移动或按下鼠标的鼠标位置是采用设备坐标。 逻辑坐标系是面向DC的坐标系,这种坐标不考虑具体的设备类型,在绘图时,Windows会根据当前设置的映射模式将逻辑坐标转换为设备坐标。 设备坐标系是面向物理设备的坐标系,这种坐标以像素或设备所能表示的最小长度单位为单位,X轴方向向右,Y轴方向向下。设备坐标系的原点位置(0, 0)不限定在设备显示区域的左上角。
  • 348. 设备坐标系分为屏幕坐标系、窗口坐标系和客户区坐标系三种相互独立的坐标系。 屏幕坐标系以屏幕左上角为原点,一些与整个屏幕有关的函数均采用屏幕坐标,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。弹出式菜单使用的也是屏幕坐标。 窗口坐标系以窗口左上角为坐标原点,它包括窗口标题栏、菜单栏和工具栏等范围。 客户区坐标系以窗口客户区左上角为原点,主要用于客户区的绘图输出和窗口消息的处理。鼠标消息的坐标参数使用客户区坐标,CDC类绘图成员函数使用与客户区坐标对应的逻辑坐标。屏幕坐标系、窗口坐标系和客户区坐标系
  • 349. 坐标之间的相互转换编程时,有时需要根据当前的具体情况进行三种设备坐标之间或与逻辑坐标的相互转换。 MFC提供了两个函数CWnd::ScreenToClient()和CWnd::ClientToScreen()用于屏幕坐标与客户区坐标的相互转换。 MFC提供了两个函数CDC::DPtoLP()和CDC:: LPtoDP()用于设备坐标与逻辑坐标之间的相互转换。
  • 350. 例 修改例5-13中的程序MyDraw,采用将设备坐标转换为逻辑坐标的方法实现滚动视图的功能。 Windows鼠标位置使用设备坐标系,以客户区窗口原点作为基准,而在OnDraw()函数中使用逻辑坐标。因此,为了在滚动视图中重绘图形,必须在存储线段起点和终点之前将其坐标转换为逻辑坐标。实质上,OnDraw()函数由OnPaint()函数调用,在调用OnDraw()函数前,OnPaint()函数已经调用了函数OnPrepareDC()对设备环境进行了调整。 打开应用程序项目MyDraw,修改单击鼠标和鼠标移动的消息处理函数。
  • 351. void CMyDrawView::OnLButtonDown( UINT nFlags, CPoint point) { // TODO: Add your message handler code here . . . CClientDC dc(this); OnPrepareDC(&dc); // 调整设备环境的属性 dc.DPtoLP(&point); // 将设备坐标转换为逻辑坐标 SetCapture(); // 捕捉鼠标 ::SetCursor(m_hCross); // 设置十字光标 m_ptOrigin=point; m_bDragging=TRUE; // 设置拖拽标记 // CScrollView::OnLButtonDown(nFlags, point); }
  • 352. void CMyDrawView::OnMouseMove(UINT nFlags, CPoint point) { if(m_bDragging) { CMyDrawDoc *pDoc=GetDocument(); ASSERT_VALID(pDoc); CClientDC dc(this); OnPrepareDC(&dc); dc.DPtoLP(&point); pDoc->AddLine(m_ptOrigin, point); dc.MoveTo(m_ptOrigin); dc.LineTo(point); m_ptOrigin=point; } // CScrollView::OnMouseMove(nFlags, point); }
  • 353. 映射模式映射模式确定了在绘制图形时所依据的坐标系,它定义了逻辑单位的实际大小、坐标增长方向,所有映射模式的坐标原点均在设备输出区域(如客户区或打印区)的左上角。此外,对于某些映射模式,用户还可以自定义窗口的长度和宽度,设置视图区的物理范围。 Windows定义了8种映射模式:MM_TEXT、MM_LOMETRIC、MM_HIMETRIC、MM_LOE- NGLISH、MM_HIENGLISH、MM_TWIPS、MM _ISOTROPIC、MM_ANISOTROPIC 映射模式使得程序员可不必考虑输出设备的具体设备坐标系,而在一个统一的逻辑坐标系中进行图形的绘制。
  • 354. 映射模式 逻辑单位 坐标系设定 MM_TEXT 一个像素 X轴正方向朝右,Y轴正方向朝下 MM_LOMETRIC 0.1毫米 X轴正方向朝右,Y轴正方向朝上 MM_HIMETRIC 0.01毫米 X轴正方向朝右,Y轴正方向朝上 MM_LOENGLISH 0.01英寸 X轴正方向朝右,Y轴正方向朝上 MM_HIENGLISH 0.001英寸 X轴正方向朝右,Y轴正方向朝上 MM_TWIPS 1/1440英寸 X轴正方向朝右,Y轴正方向朝上 MM_ISOTROPIC 系统确定 X、Y轴可任意调节,X、Y轴比例为1:1MM_ANISOTROPIC 系统确定 X、Y轴可任意调节,X、Y轴比例任意Windows映射模式
  • 355. 设置原点的坐标:通过调用函数CDC::SetWindowOrg()设置设备环境的窗口原点的坐标,调用CDC::SetViewportOrg()重新设置设备的视口原点的坐标。这里,窗口是对应于逻辑坐标系(设备环境)由用户设定的一个区域,而视口是对应于实际输出设备由用户设定的一个区域。 窗口原点是指逻辑窗口坐标系的原点在视口(设备)坐标系中的位置,视口原点是指设备实际输出区域的原点。 除了映射模式,窗口和视口也是决定一个点的逻辑坐标如何转换为设备坐标的一个因素。一个点的逻辑坐标按照如下式子转换为设备坐标: 设备(视口)坐标 = 逻辑坐标 – 窗口原点坐标 + 视口原点坐标
  • 356. 例 分别在OnDraw()函数中添加如下代码,设置不同的窗口原点和视口原点,结果有什么不同。 (1) pDC->SetMapMode(MM_TEXT); pDC->Rectangle(CRect(50, 50, 100, 100)); (2) pDC->SetMapMode(MM_TEXT); pDC->SetWindowOrg(50, 50); pDC->Rectangle(CRect(50, 50, 100, 100)); (3) pDC->SetMapMode(MM_TEXT); pDC->SetViewportOrg(50,50); pDC->Rectangle(CRect(50, 50, 100, 100)); (4) pDC->SetMapMode(MM_TEXT); pDC->SetViewportOrg(50,50); pDC->SetWindowOrg(50, 50); pDC->Rectangle(CRect(50, 50, 100, 100));
  • 357. (本页无文本内容)
  • 358. 8.1.4 颜色的设置Windows用COLORREF类型的数据存放颜色,它是一个32位整数。任何一种颜色都是由红、绿、蓝三种基本颜色组成,COLORREF类型数据的低位字节存放红色强度值,第2个字节存放绿色强度值,第3个字节存放蓝色强度值,高位字节为0,每一种颜色分量的取值范围为0到255。 直接设置COLORREF数据不太方便,Windows提供了RGB宏用于设置颜色,将其中的红、绿、蓝分量值转换为COLORREF类型的颜色数据: RGB(byRed, byGreen, byBlue) 其中参数byRed、byGreen和byBlue分别表示红、绿、蓝分量值(范围0到255)。
  • 359. RGB宏的使用: 很多涉及到颜色的GDI函数都需要使用COLORREF类型的参数,如设置背景色的成员函数CDC::SetBkColor()、设置文本颜色的成员函数CDC::SetTextColor()。例如: COLORREF rgbBkClr=RGB(192,192,192); // 定义灰色 pDC->SetBkCorlor(rgbBkClr); // 背景色为灰色 pDC->SetTextColor(RGB(0,0,255)); // 文本颜色为兰色
  • 360. 标准彩色的RGB值 颜色 RGB分量值 颜色 RGB分量值 浅红 255,0,0 深红 128,0,0 浅绿 0,255,0 深绿 0,128,0 浅蓝 0,0,255 深蓝 0,0,128 浅黄 255,255,0 深黄 128,128,0 浅青 0,255,255 深青 0,128,128 紫色 255,0,255 灰色 192,192,192 白色 255,255,255 黑色 0,0,0
  • 361. 在默认状态下,当用户创建一个设备环境并在其中绘图时,系统使用设备环境缺省的绘图工具及其属性。如果要使用不同风格和颜色的绘图工具进行绘图,用户必须重新为设备环境设置自定义的画笔和画刷等绘图工具。 画笔和画刷是Windows中两种最重要的绘图工具,画笔用于绘制点、线、矩形和椭圆等几何图形,画刷用指定的颜色和图案来填充绘图区域,这些绘图工具又统称为GDI对象。 8.2 画笔和画刷
  • 362. 8.2.1 GDI对象Windows GDI提供了一些绘图对象,程序通过这些GDI对象设置绘图的工具和风格,这里的对象是指Windows数据结构,而不是C++类的对象。 GDI对象是Windows图形设备接口的抽象绘图工具。除了画笔和画刷,其它GDI对象还包括字体、位图和调色板。 MFC对GDI对象进行了很好的封装,提供了封装GDI对象的类,如CPen、CBrush、CFont、CBitmap和CPalette等,这些类都是GDI对象类CGdiObject的派生类。
  • 363. CDC类提供了成员函数SelectObject()选择用户自己创建的GDI对象,该函数有多种重载形式,可以选择用户已定制好的画笔、画刷、字体和位图等不同类型的GDI对象。 CPen* SelectObject(CPen* pPen); CBrush* SelectObject(CBrush* pBrush); virtual CFont* SelectObject(CFont* pFont); CBitmap* SelectObject(CBitmap* pBitmap); 函数参数是一个指向用户已定制好的GDI对象的指针,选择操作成功函数将返回以前GDI对象的指针,否则返回NULL。选择一个GDI对象:
  • 364. 8.2.2 使用画笔 当用户创建一个用于绘图的设备环境时,该设备环境自动提供了一个宽度为一个像素单位、风格为实黑线(BLACK_PEN)的缺省画笔。如果要在设备环境使用自己的画笔绘图,首先需要创建一个指定风格的画笔,然后将创建的画笔选入设备环境,最后,在使用该画笔绘图结束后需要释放该画笔。 1. 创建画笔 创建画笔最简单的方法是调用CPen类的一个带参数的构造函数来构造一个CPen类画笔对象,以下代码创建了一个红色虚线画笔: CPen PenNew (PS_DASH, 1, RGB(255, 0, 0));
  • 365. 创建画笔的第二种方法是首先构造一个没有初始化的CPen类画笔对象,然后调用成员函数CPen::CreatePen()创建定制的画笔工具: CPen PenNew; PenNew.CreatePen(PS_DASH, 1, RGB(255,0,0)); 函数CreatePen()的参数类型与带参数的CPen类构造函数完全一样。当画笔对象的声明与创建不在同一个地方时(如需要多次改变画笔)只有采用这种方法。 样 式 说 明 样 式 说 明 PS_SOLID 实线 PS_DASHDOTDOT 双点划线 PS_DOT 点线 PS_NULL 空的边框 PS_DASH 虚线 PS_INSIDEFRAME 边框实线 PS_DASHDOT 点划线    
  • 366. 创建画笔后必须调用成员函数CDC:: SelectObject()将创建的画笔选入当前设备环境。如果选择成功,函数SelectObject()将返回以前画笔对象的指针。选择新的画笔时应该保存以前的画笔对象,如下代码所示: CPen* pPenOld pPenOld =pDC->SelectObject(&PenNew); 2. 选择创建的画笔
  • 367. 创建和选择画笔工具后,应用程序就可以使用该画笔绘图。当绘图完成后,应该通过调用成员函数CDC::SelectObject()恢复设备环境以前的画笔工具,并通过调用成员函数CGdiObject::DeleteObject()释放GDI对象所占的内存资源,如下代码所示: pDC->SelectObject(pPenOld); // 恢复设备环境DC中原来的画笔 PenNew.DeleteObject(); // 删除底层的GDI对象3. 还原画笔
  • 368. 在OnDraw()函数中添加如下所示的代码: CPen *pPenOld, PenNew; int nPenStyle[]= { PS_SOLID, // 实线 PS_DOT, // 点线 PS_DASH, // 虚线 PS_DASHDOT, // 点划线 PS_DASHDOTDOT, // 双点划线 PS_NULL, // 空的边框 PS_INSIDEFRAME, // 边框实线 }; char *strStyle[]={"Solid","Dot","Dash","DashDot", "DashDotDot","Null","InsideFrame"}; pDC->TextOut(60,10,"用不同样式的画笔绘图"); 例 编写一个SDI应用程序,绘制不同风格、宽度和颜色的直线。
  • 369. for(int i=0; i<7; i++) // 用不同样式的画笔绘图 { if(PenNew.CreatePen(nPenStyle[i],1,RGB(0,0,0))) //创建画笔 { pPenOld=pDC->SelectObject(&PenNew); // 选择画笔 pDC->TextOut(10,30+20*i,strStyle[i]); pDC->MoveTo(100,40+20*i); pDC->LineTo(200,40+20*i); pDC->SelectObject(pPenOld); // 恢复原来的画笔 PenNew.DeleteObject(); // 删除底层的GDI对象 } else { MessageBox("不能创建画笔!"); } }
  • 370. char *strWidth[]={"1","2","3","4","5","6","7"}; pDC->TextOut(260,10,"用不同宽度的画笔绘图"); for(i=0; i<7; i++) // 用不同宽度的画笔绘图 { if(PenNew.CreatePen(PS_SOLID,i+1,RGB(0,0,0))) // 创建画笔 { pPenOld=pDC->SelectObject(&PenNew); // 选择画笔 pDC->TextOut(260,30+20*i,strWidth[i]); pDC->MoveTo(300,40+20*i); pDC->LineTo(400,40+20*i); pDC->SelectObject(pPenOld); // 恢复原来的画笔 PenNew.DeleteObject(); // 删除底层的GDI对象 } else { MessageBox("不能创建画笔!"); } }
  • 371. char *strColor[]={"红","绿","蓝","黄","紫","青","灰"}; COLORREF rgbPenClr[]={RGB(255,0,0),RGB(0,255,0), RGB(0,0,255),RGB(255,255,0),RGB(255,0,255), RGB(0,255,255),RGB(192,192,192)}; pDC->TextOut(460,10,"用不同颜色的画笔绘图"); for(i=0; i<7; i++) // 用不同颜色的画笔绘图 { CPen *pPenNew=new CPen(PS_SOLID,2,rgbPenClr[i]); // 创建画笔的另一种方法 pPenOld=pDC->SelectObject(pPenNew); // 选择创建的画笔 pDC->TextOut(460,30+20*i, strColor[i]); pDC->MoveTo(500,40+20*i); pDC->LineTo(600,40+20*i); pDC->SelectObject(pPenOld); // 恢复原来的画笔 delete pPenNew; // 自动删除底层的GDI对象 } }
  • 372. (本页无文本内容)
  • 373. 当创建一个设备环境时,该设备环境自动提供了一个填充色为白色(WHITE_BRUSH)的缺省画刷。与画笔一样,也可以利用MFC画刷类CBrush创建自己的画刷,用于填充图形的绘制。 画刷有三种基本类型:纯色画刷、阴影画刷和图案画刷,CBrush类提供了多个不同重载形式的构造函数。以下创建三种不同类型的画刷: CBrush brush1(RGB(255,0,0)); // 创建纯色画刷 CBrush brush2(HS_DIAGCROSS, RGB(0,255,0)); // 创建阴影画刷 CBrush brush3(&bmp); // 创建图案画刷8.2.3 使用画刷
  • 374. 创建画刷也可先构造一个没有初始化的CBrush类画刷对象,然后调用CBrush类的初始化成员函数创建定制的画刷工具。CBrush类提供的常用创建函数有:CreateSolidBrush()用指定的颜色创建一个纯色画刷;CreateHatchBrush()用指定的阴影样式和颜色创建一个阴影画刷;CreatePatternBrush()用位图创建一个图案画刷;CreateSysColorBrush()用系统默认颜色创建一个指定阴影样式的画刷。 如下代码创建了一个填充色为红色、图案为垂直相交阴影线的画刷: CBrush BrushNew; BrushNew.CreateHatchBrush(HS_CROSS, RGB(255, 0, 0));
  • 375. 1.建立一个基于对话框的应用程序UseBrush,为对话框类CUseBrushDlg添加一个CBrush类型的成员变量m_BrushBkClr。在对话框初始化成员函数OnInitDialog()中创建一个自定义颜色的画刷。 BOOL CUseBrushDlg::OnInitDialog() { . . . . . . // TODO: Add extra initialization here m_BrushBkClr.CreateSolidBrush(RGB(0,0,255)); // 创建一个蓝色画刷 return TRUE; // return TRUE unless you set the focus . . . }例 编写一个对话框应用程序,并重新设置对话框的背景色。
  • 376. 2.利用ClassWzard为对话框类CUseBrushDlg添加WM_CTLCOLOR的消息处理函数,返回用户自己创建的画刷m_BrushBkClr。 HBRUSH CUseBrushDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { //HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); // 不使用缺省的画刷 // TODO: Return a different brush if the default . . . return m_BrushBkClr; }
  • 377. (本页无文本内容)
  • 378. Windows预定义了一些简单风格的GDI对象,用户使用这些GDI对象时,无需自己创建它们,可以直接将它们选入当前的设备环境,这些GDI对象称作为堆(Stock)对象。堆对象包括堆画笔、堆画刷和堆字体等。 通过调用成员函数CDC::SelectStockObject()可以选择一个堆对象绘图工具,以下代码将堆画笔和堆画刷作为当前的绘图工具: pPenOld=(CPen*) pDC->SelectStockObject(NULL_PEN); // 使用堆画笔对象 pBrhOld=(CBrush*) pDC->SelectStockObject(LTGRAY _BRUSH); // 使用堆画刷对象8.2.4 使用GDI堆对象
  • 379. 堆画笔、画刷的样式及说明 样 式 说 明 样 式 说 明 BLACK_PEN 黑色画笔 WHITE_PEN 白色画笔 NULL_PEN 空画笔 BLACK_BRUSH 黑色画刷 WHITE_BRUSH 白色画刷 NULL_BRUSH 空画刷 GRAY_BRUSH 灰色画刷 DKGRAY_BRUSH 深灰色画刷 LTGRAY_BRUSH 浅灰色画刷HOLLOW_BRUSH 虚画刷
  • 380. 也可以利用CGdiObject::CreateStockObject()将GDI对象设置成指定的堆对象,这时需要首先声明一个GDI对象,最后还需要调用函数SelectObject()将与堆对象关联的GDI对象选入当前的设备环境,如下代码所示: CBrush *pBrhOld, BrhNew; BrhNew.CreateStockObject(LTGRAY_BRUSH); // 设置堆画刷对象 pBrhOld= pDC->SelectObject(&BrhNew);
  • 381. 利用MFC AppWizard向导创建一个SDI应用程序UseStock,利用ClassWizard为类CUseStockView添加WM_PAINT消息处理函数OnPaint(): void CUseStockView::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here CPen *pPenOld, PenNew; CBrush *pBrhOld, BrhNew; pPenOld=(CPen*)dc.SelectStockObject(BLACK_PEN); // 使用堆画笔对象例 编写一个SDI应用程序,使用堆画笔和堆画刷绘制图形。
  • 382. pBrhOld=(CBrush*)dc.SelectStockObject(GRAY_BRUSH); // 使用堆画刷对象 dc.Rectangle(100,100,300,300); PenNew.CreateStockObject(NULL_PEN); // NULL_PEN用于绘制无边界的填充图形 dc.SelectObject(&PenNew); BrhNew.CreateStockObject(LTGRAY_BRUSH); dc.SelectObject(&BrhNew); dc.Ellipse(400,100,600,200); dc.SelectObject(pPenOld); // 恢复系统默认的GDI对象 dc.SelectObject(pBrhOld); dc.Ellipse(400,210,600,310); // Do not call CView::OnPaint() for painting messages }
  • 383. (本页无文本内容)
  • 384. 生成设备环境、设置绘图属性和选择绘图工具后,就可以开始绘制不同形状的几何图形,Windows中可以绘制的基本几何图形包括点、直线、曲线、矩形、椭圆、弧、扇形、弦形和多边形等。 GDI为提供了绘制基本图形的成员函数,这些函数封装在MFC的CDC类中。 绘图函数使用的坐标都是逻辑坐标。 8.2.5 绘制基本图形
  • 385. 函 数 功 能 SetPixel 用指定的颜色在指定的坐标画一个点MoveTo 移动当前位置到指定的坐标,函数返回以前位置的坐标。 LineTo 从当前位置到指定位置画一条直线Polyline 从当前位置开始,根据函数参数绘制多条折线。 PolyBezier 根据两个端点和两个控制点绘制贝济埃(Bezier)曲线。 Rectangle 根据指定的左上角和右下角坐标绘制一个矩形RoundRect 绘制一个圆角矩形。 Ellipse 根据指定的矩形绘制一个内切椭圆Arc 根据指定的矩形绘制内切椭圆上的一段弧边ArcTo 该函数功能与Arc函数相同,不同之处在于画弧成功后Pie 绘制扇形Chord 绘制弦形,弦形是一条椭圆弧和其对应的弦所组成的封闭图形。 Polygon 根据两个或两个以上顶点绘制一个多边形DrawIcon 在指定位置画一个图标,如果成功函数返回非0,否则返回0。
  • 386. 利用MFC AppWizard建立一个SDI应用程序,在OnDraw()函数中添加如下程序代码: void CMyGraphView::OnDraw(CDC* pDC) { CMyGraphDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here for(int xPos=20;xPos<100;xPos+=10) pDC->SetPixel(xPos,30,RGB(0,0,0)); // 绘制像素点 POINT polylpt[5]={{10,100},{50,60},{120,80}, {80,150},{30,130}}; pDC->Polyline(polylpt,5); // 绘制五条折线例 编写一个绘图程序,利用表8-6中的成员函数绘制几种常见的几何图形。
  • 387. POINT polybpt[4]={{150,160},{220,60},{300,180}, {330,20}}; pDC->PolyBezier(polybpt,4); // 绘制贝济埃曲线 CBrush *pBrhOld; pBrhOld=(CBrush*)pDC->SelectStockObject( LTGRAY_BRUSH); // 使用浅灰色堆画刷 pDC->RoundRect(400,30,550,100,20,20); // 绘制圆角矩形 pDC->Arc(20,200,200,300,200,250,20,200); // 绘制椭圆弧 pDC->Pie(220,200,400,380,380,270,240,220); // 绘制扇形 pDC->Chord(420,120,540,240,520,160,420,180); // 绘制弦形 POINT polygpt[5]={{450,200},{530,220},{560,300}, {480,320},{430,280}}; pDC->Polygon(polygpt,5); // 绘制五边形 pDC->SelectObject(pBrhOld); // 恢复系统默认的画刷 }
  • 388. (本页无文本内容)
  • 389. 很多Windows应用程序都需要显示文本,并且,文本还是一些应用程序的主要处理对象,如MS Word字处理软件。 文本与字体密切相关,输出文本时选择不同类型的字体在很大程度上影响程序的界面风格,合适的字体可以增强程序的感染力。因此,对软件用户来说,文本输出也是很重要的。 Windows为文本的显示提供了多种物理字体支持,而在程序中我们可以创建不同风格的逻辑字体来输出文本。 8.3 文本与字体
  • 390. 以图形方式进行文本的输出是Windows操作系统一个特性,文本输出实际上是按照指定的字体样式将文本中的每个字符绘制出来。 Windows图形设备接口GDI提供了很多有关文本输出的函数,MFC的CDC类对这些GDI文本输出函数进行了封装。 编程时最常用的文本输出函数是TextOut()函数,该函数只能输出单行文本。要绘制多行文本可以调用DrawText()函数,另一个函数ExtTextOut()可以用一个矩形框对输出文本串进行裁剪。8.3.1 绘制文本
  • 391. 在缺省情况下绘制文本时,字体颜色是黑色,背景颜色是白色,背景模式为不透明模式。可以通过调用CDC类成员函数重新设置字体颜色、背景颜色和文本对齐方式等文本属性。 SetTextColor() 设置显示文本的颜色 GetTextColor 获得当前文本的颜色 SetBkColor() 设置显示文本的背景颜色 GetBkColor() 获得当前文本的背景颜色 SetBkMode() 设置文本的背景模式GetBkMode() 获得当前文本的背景模式 SetTextAlign() 设置显示文本的对齐方式GetTextAlign() 获得当前文本的对齐方式
  • 392. 文本与字体密切相关,输出文本的大小和外观是由字体描述的。字体是指采用某种字样的一套字符和符号,每一种字体都有字符集。 决定字体的三个要素是字样、风格和大小。字样是字母的样式和文本的视觉外观,字体的风格是字体的粗细和倾斜度。 Windows支持光栅字体、矢量字体和TrueType三种字体。光栅字体即点阵字体,这种字体需要为每一种大小的字体创建独立的字体文件。矢量字体以一系列线段存储字符。TrueType字体是与设备无关的字体,字符以轮廓的形式存储,包括线段和曲线。8.3.2 字体概述
  • 393. TrueType字体正成为真正的主流,这种字体能够以一种非常出色的字体技术绘制文本。TrueType字体能够缩放为任何大小的字体,而不会降低图形的质量。Windows中提供的TrueType字体主要有Arial、Courier、Symbol、Time New Roman等,可以通过Windows“控制面板|字体”浏览系统已安装的字体。 输出文本时,默认情况下使用系统提供的缺省字体,如果需要可以改变显示文本的字体。与画笔和画刷一样,字体也是一种GDI对象,MFC类CFont对GDI字体对象进行了封装,我们一般利用CFont类创建自己的字体(GDI对象),然后把创建的字体选入设备环境,以用于在设备环境中绘制文本。
  • 394. 除了选择任意尺寸TrueType字体,也可以选择固定尺寸的系统字体(堆字体)。当选择堆字体作为文本输出的字体时,无需创建字体对象,只需简单地调用成员函数CDC::SelectStockObject()将堆字体对象选入设备环境。 Windows提供了以下六种堆字体对象:ANSI_ FIXED_FONT、ANSI_VAR_FONT、SYSTEM_ FONT、SYSTEM_FIXED_FONT、DEVICE _DEFAULT_ FONT、OEM_FIXED_FONT。 例如: pDC->SelectStockObject( ANSI_FIXED_FONT); 选择堆字体:
  • 395. 输出文本时,Windows使用一个矩形框以位图的方式绘制出每一个字符的形状。文本的显示是以像素为单位,有时需要精确地知道文本的详细属性,如高度、宽度等。 编程时我们可以通过访问TEXTMETRIC结构来获取显示器关于文本字符的属性信息,因为每一种物理字体的信息由数据结构TEXTMETRIC描述。调用函数CDC::GetTextMetrics()可得到当前字体的TEXTMETRIC结构。 TEXTMETRIC结构:
  • 396. 8.3.3 创建字体Windows本身提供了丰富的字体,直接选用其中的字体就能满足一般需要。也可以根据Windows提供的字体创建自己的字体,但利用CFont类创建自定义字体并不是创建一种新的物理字体,而是创建一种逻辑字体。 逻辑字体是一种抽象的字体描述,是用与设备无关的方式来描述一个字体。逻辑字体只定义了字体的一般特征,如高度、宽度、旋转角度、黑体、斜体及下划线等宏观特性,它并没有描述字体详细的微观特性,也没有生成对应的字库文件。
  • 397. 值得注意的是,有时不知道机器上是否安装了需要的字体,因此,程序运行时显示文本的字体可能并不是你想要的字体。实际上,在程序中创建一种字体并不是真正创建一种完全满足程序要求的字体,而是仅寻找匹配的Windows字体并与之相关联。 当利用CFont类创建逻辑字体并利用成员函数CDC::SelectObject()将它选入设备环境时,GDI字体映射器根据逻辑字体给出的特性,从现有的物理字体中选择与之最匹配的物理字体,这就是所谓的字体实现(Font realization)。
  • 398. 1. 使用成员函数CFont::CreatPointFont() 2. 使用成员函数CFont::CreateFontIndirect() 3. 使用成员函数CFont::CreateFont()创建字体的方法: CClientDC dc(this); CFont fntZdy, *pfntOld; VERIFY(fntZdy.CreatePointFont(200, "Arial", &dc)); pfntOld=dc.SelectObject(&fntZdy); // 选入设备环境 dc.TextOut(100, 100, "Hello! This is 20 Pt Arial Font."); dc.SelectObject(pfntOld); // 恢复原来字体 fntZdy.DeleteObject(); // 删除自定义字体 例:
  • 399. CFont font; LOGFONT LogFnt; memset(&LogFnt, 0, sizeof(LOGFONT)); // 清零结构LogFont LogFnt.lfHeight = 22; // 字体高度为22像素 strcpy(LogFnt.lfFaceName, "Courier"); // 匹配字体为Courier VERIFY(font.CreateFontIndirect(&LogFnt)); // 创建字体 CClientDC dc(this); CFont* def_font = dc.SelectObject(&font); // 选入设备环境 dc.TextOut(100, 130, "Hello! This is 22-pixel-height Courier Font."); dc.SelectObject(def_font); font.DeleteObject();例:使用CreateFontIndirect()函数和LOGFONT结构。
  • 400. 例 编写一个文本输出程序UseFont,采用不同方法创建字体,并根据创建的字体输出不同的文本串。
  • 401. Windows还提供了一个公用字体对话框,很多程序都利用它来选择不同的字体,并可以设置字体的大小和颜色。公用字体对话框对应的MFC类是类CFontDialog,编程时可以通过访问CFontDialog类的有关成员变量或调用成员函数获得用户所选择的字体及其属性,程序员无须具体定义这种字体就可以通过调用函数CreateFontIndirect()创建字体。公用字体对话框
  • 402. 例 编写一个单文档应用程序FontDlg,当执行菜单命令“字体”时,使用公用字体对话框动态设置字体。
  • 403. 使用图象形式的标志可以使用户很快地找到某个程序或了解一个程序的大致功能,因此在Windows环境中大量使用各种图形图像标志。Windows应用程序中主要使用位图、图标和光标等几种图形资源。 利用Visual C++集成开发环境中的资源编辑器可以创建或编辑这几种图形资源,在程序中需要时可以通过编写源代码使用创建的图形资源。 8.4 位图、图标和光标
  • 404. 位图是一个由位构成的图象,它是由一系列数据排列而成的点阵结构,这些数据分别表示各点的颜色信息。Windows支持两种不同形式的位图:设备相关位图DDB(Device Dependent Bitmap)和设备无关位图DIB(Device Independent Bitmap)。 DDB又称GDI位图,它是某种显示设备的内部表示。DDB是针对某个设备创建的位图,显示它依赖具体硬件的调色板。 DIB是不依赖硬件的位图,它包含了创建DIB位图时所在设备的颜色格式、分辨率和调色板等信息。DIB位图通常以BMP文件形式保存在磁盘中,或者以资源形式存在于EXE或DLL执行文件中。 8.4.1 位图
  • 405. MFC只提供了处理DDB位图的类CBitmap,要显示DIB位图,可以先将一个DIB位图转换为DDB位图。类CBitmap提供了一个成员函数,用于从程序的资源中装载位图,并可以将基于资源的DIB位图转换成GDI位图,该函数声明如下: BOOL LoadBitmap( LPCTSTR lpszResourceName ); BOOL LoadBitmap( UINT nIDResource ); 其中参数lpszResourceName或nIDResource分别为资源名称或资源标识,载入成功返回值为真,否则返回值为假。 MFC处理位图的方法:
  • 406. 位图在显示之前必须先装入内存,当驻留在内存的位图数据送到视频内存时,位图就在显示器上显示。显示一个DDB位图步骤: (1) 调用CDC::CreateCompatibleDC()创建一个兼容的内存设备环境; (2) 调用CBitmap::LoadBitmap()装入位图资源或调用CBitmap::CreateCompatibleBitmap()创建一个与内存设备环境兼容的位图; (3) 调用CDC::SelectObject()将位图选入设备环境; (4) 调用CDC::BitBlt()或CDC::StretchBlt()将位图从内存设备环境中复制到指定设备如显示器。 显示位图的编程方法:
  • 407. 利用向导创建一个SDI应用程序MyBMP。执行菜单命令Insert|Resource插入一个Bitmap资源。利用资源编辑器对位图进行编辑,并将其ID改为IDB_MYBITMAP。在函数OnDraw()中添加代码: void CMyBMPView::OnDraw(CDC* pDC) { CMyBMPDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CDC MemDC; MemDC.CreateCompatibleDC(pDC); // 创建内存设备环境例 编写一个应用程序MyBMP,程序运行后在客户区显示一幅位图。
  • 408. CBitmap Bitmap; Bitmap.LoadBitmap(IDB_MYBITMAP); // 装入位图资源 CBitmap *pOldBitmap=MemDC.SelectObject(&Bitmap); // 将位图对象选入设备环境 BITMAP bm; Bitmap.GetObject(sizeof(BITMAP), &bm); // 读取位图信息 // 将内存中的位图复制到屏幕上 pDC->BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &MemDC, 0, 0, SRCCOPY); MemDC.SelectObject(pOldBitmap); // 恢复原来位图对象 }
  • 409. (本页无文本内容)
  • 410. 在Windows中,每个文件都有一个图标(Icon)。应用程序图标通常会出现在程序标题栏的左上角、Windows底部的任务栏、资源管理器窗口和Windows桌面上。 图标实质上是特殊形式的位图,但图标与位图有两个不同之处。首先,图标大小尺寸只能有三种,一种是用于标题栏和最小化时的16×16图标,另外两种是用于桌面、资源管理器的32×32和48×48图标。其次,设计图标时可以指定像素的颜色为屏幕颜色或屏幕反转色,如图所示。这样,Windows在显示图标时,采用屏幕颜色的像素位置颜色不变,该位置图标部分看起来是透明的,而屏幕反转色部分在任何彩色背景下都能显示。 8.4.2 图标
  • 411. (本页无文本内容)
  • 412. 一般应用程序使用MFC提供的缺省图标,也可以添加自己的图标。通过Insert|Resource菜单命令插入Icon图标资源,利用图形资源编辑器编辑图标。图标创建后通过调用函数CWinApp::LoadIcon()加载图标并获得其句柄,该函数原型为: HICON LoadIcon(LPCTSTR lpszResourceName) const; HICON LoadIcon(UINT nIDResource) const; 对于图标,MFC没有提供对应的类,编程时只有采用句柄的方式使用一个图标。 用户也可以通过调用CWinApp::LoadStandardIcon()加载Windows系统提供的预定义图标。添加自己的图标:
  • 413. 图标被加载后,为了在窗口显示图标,可以调用成员函数CDC::DrawIcon(),该函数原型为: BOOL DrawIcon(int x, int y, HICON hIcon); BOOL DrawIcon(POINT point, HICON hIcon); 其中,参数x、y或point指定图标显示的左上角坐标,hIcon为图标句柄。 在初始化函数InitInstance()中可以通过调用成员函数CWnd::SetIcon()安装图标,此时应该同时安装16×16和32×32大小标准的图标,该函数原型为: HICON SetIcon( HICON hIcon, BOOL bBigIcon ); 其中,参数hIcon为要安装的图标句柄,bBigIcon确定安装何种大小的图标。显示图标:
  • 414. 1.利用MFC AppWizard[exe]创建一个SDI应用程序MyIcon,然后执行Insert|Resource菜单命令,要创建的资源选为Icon,单击New按钮。也可通过Import命令导入一个图标文件。打开图形编辑器后就可以开始创建图标,新创建图标的ID设为IDI_ICON1。 2.为了在标题栏显示创建的图标IDI_ICON1,在初始化函数InitInstance()中添加如下代码: HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON1); m_pMainWnd->SetIcon(hIcon,TRUE); // 设置32×32图标 m_pMainWnd->SetIcon(hIcon,FALSE); // 设置16×16图标例 编写一个应用程序MyIcon,程序运行后在标题栏删除程序原来默认的图标,显示自己创建的图标,并在客户区显示该图标和Windows预定义图标。
  • 415. 3.在函数OnDraw()中使用图标,需要编写代码加载和显示图标。在OnDraw()函数中添加如下代码: HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON1); // 加载自定义图标 pDC->DrawIcon(0, 0, hIcon); // 显示图标 DestroyIcon(hIcon); // 释放图标资源 hIcon=AfxGetApp()->LoadStandardIcon( IDI_EXCLAMATION); // 加载系统预定义图标 pDC->DrawIcon(50, 0, hIcon); DestroyIcon(hIcon); 也可以通过发送消息WM_SETICON设置应用程序的图标,这时需要调用消息发送函数SendMessage()。
  • 416. MyIcon 运行结果
  • 417. 光标(Cursor)是一种特殊的、可移动的32×32点阵图形,它是用来作为鼠标指针的图形标志。 光标与图标最大的区别是光标有一个热点,用于确定光标当前的像素位置。利用光标资源编辑器可以重新设置光标的热点,如图所示,光标资源编辑器的右上部有一个热点设置按钮“*”,单击这个按钮,在光标编辑区会出现一个十字光标,将十字光标的中心放在要设定热点的位置,单击鼠标就可以设置光标的热点。 8.4.3 光标
  • 418. 设置光标的热点
  • 419. 在Windows环境下,光标形状的变化代表了程序当前状态的改变。不需要用户专门处理,Windows就支持三种最普通的光标:箭头、沙漏和I形光标。一般情况下,光标为箭头形状;如果应用程序忙,光标呈沙漏状;如果光标超出程序窗口或进行文本输入,光标为I形状。 MFC类CCmdTarget提供成员函数BeginWait- Cursor()、EndWaitCursor()和RestoreWaitCursor()用于改变光标的状态。 光标与图标一样,MFC也没有提供对应的类,一般采用句柄的方式使用光标。 改变光标的形状:
  • 420. 1.利用MFC AppWizard创建SDI应用程序WaitCur,利用ClassWizard添加消息WM_LBUTTONDOWN的处理函数,并添加如下代码: void CWaitCurView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here . . . SetCapture(); BeginWaitCursor(); // 显示沙漏状光标 SetTimer(1,2000,NULL);// 启动一个2秒的计时器 CView::OnLButtonDown(nFlags, point); }例 编写一个应用程序WaitCur,程序运行后单击鼠标光标处于沙漏状,2秒钟光标恢复正常状态。
  • 421. 2.利用ClassWizard添加消息WM_TIMER的处理函数,在函数中添加如下代码: void CWaitCurView::OnTimer(UINT nIDEvent) // 2秒后调用该消息处理函数 { // TODO: Add your message handler code here . . . ReleaseCapture(); EndWaitCursor(); // 2秒后恢复光标原来的形状 KillTimer(1); // 删除计时器 CView::OnTimer(nIDEvent); }
  • 422. WaitCur运行结果
  • 423. 首先利用Insert|Resource菜单命令添加和编辑Cursor光标资源,然后通过调用CWinApp:: LoadCursor()加载光标。加载成功后,调用函数SetCursor()设置光标。 一般在WM_SETCURSOR的消息处理函数OnSet- Cursor()中重新设置光标。 用户也可以直接使用Windows系统提供的光标,首先需要调用CWinApp::LoadStandardCursor()来加载,通过不同的参数加载不同的系统光标。 如:IDC_ARROW、IDC_IBEAM、IDC_WAIT、IDC_CROSS、IDC_UPARROW。添加光标:
  • 424. 1.利用MFC AppWizard创建应用程序MyCursor,执行菜单命令Insert|Resource插入一个Cursor资源。利用光标资源编辑器绘制一个手形光标,设置光标热点,并将ID改为IDC_HAND。利用ClassWizard添加消息WM_SETCURSOR的处理函数: HCURSOR hcursor; hcursor=AfxGetApp()->LoadCursor(IDC_HAND); SetCursor(hcursor); // 设置光标 return TRUE;例 编写应用程序MyCursor,程序运行后,当光标移到客户区时变为自定义的形状。执行菜单命令“查看|系统光标”打开一个对话框,当光标移到对话框时,光标变为Windows预定义的四个箭头光标。
  • 425. 2.添加一个ID为IDD_MYDLG、标题为“使用系统光标”的对话框资源和对话框类CMyDlg。利用ClassWzard添加消息WM_SETCURSOR的处理函数: BOOL CMyDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { // TODO: Add your message handler code here . . . HCURSOR hcursor; hcursor=AfxGetApp()->LoadStandardCursor(IDC_SIZEALL); // 加载系统光标 SetCursor(hcursor); return TRUE; // return CDialog::OnSetCursor . . . }
  • 426. 3.在菜单资源中增加菜单项“查看|系统光标”,其ID为ID_VIEW_SYSCUR。利用ClassWizard类向导在CMyCursorView类中添加该菜单项的命令处理函数: void CMyCursorView::OnViewSyscur() { // TODO: Add your command handler code here CMyDlg dlg; dlg.DoModal(); }
  • 427. MyCursor运行结果