2.7 MFC编程基础

MFC的全称是Microsoft Foundation Class(微软基础类)。这样的类有很多,它们的合集就是一个大大的类库,简称微软基础类库(MFC Library)。这个MFC库是一个应用程序编程框架,有了框架,我们就可以往框架内添加自己的代码来实现我们所需要的Windows应用程序,这个过程好比开发商造好了整幢大楼,把毛坯房卖给了你,而你要做的就是装修,使其可以居住。

整个MFC库是用C++语言来实现的,它对Windows操作系统上的元素进行了C++方式的封装,比如对话框专门有一个对话框类CDialog、菜单有对应的菜单类CMenu、文件专门有文件类CFile、数据库有相应的数据类CDatabase,等等,而Windows API函数变成了这些类的方法。所以熟悉了传统的SDK方式编程后,相信学习MFC编程也不是很难。

2.7.1 MFC类库概述

要成为MFC编程高手,熟悉MFC类库是必需的。在Visual C++ 2013中,MFC的版本是9.0,里面大部分都是类,除此以外还有宏、全局变量和全局函数。如图2-68所示为MFC类库的类别层次结构。

图2-68

由图2-68可见,MFC 9.0中的类一部分继承自CObject(图中的上半部分),另外一部分不是从CObject派生。MFC 9.0的类库很大,我们也不需要去全部记忆,刚学习时只需要抓住几个头头,其他虾兵蟹将(子类)在用到的时候再学习即可。MFC 9.0全部类库图很大,现在把几个头头类拉出来,见图2-69。

图2-69

图2-69是MFC中比较重要类之间的继承关系,不需要背,了解即可。下面对几个头头做简单介绍。

(1)类CObject

这个类是皇帝,大多数MFC库中的类都继承自CObject,它是大多数MFC类的基类,也称根类。CObject主要提供4个方面的功能:串行化数据、运行时提供类的信息、对象诊断输出和与收集类兼容。

(2)类CCmdTarget

这个类是大内总管,所有皇帝发出的命令都要由它来传递出来。类CCmdTarget是MFC类库中消息映射体系的一个基类。消息映射把命令或消息引导给用户为之编写的响应函数。关于消息映射后面会详细讲到。

(3)类CWinApp

类CWinApp主要封装程序初始化、运行和结束等功能。比如类CWinApp有个成员函数InitInstance,在这个函数实现程序主窗口的创建,我们也可以在这个函数加入自己的代码,以便使程序刚运行时执行某些功能。如果需要在程序结束的时候加入一些功能,可以在其成员函数ExitInstance中加入自己的代码。

所有的MFC应用程序至少拥有两个对象,一个是由CWinApp定义应用程序对象,比如:

        CMFCApplicationApp theApp;

它是一个全局变量。

另外一个对象是从CWnd继承下来的主窗口对象。通常,每个MFC应用程序都有一个主窗口。

(4)类CWnd

类CWnd在MFC类结构中有着举足轻重的地位,几乎所有的窗口都从它派生而来。我们在屏幕上看到的一切对象都与窗口有关,CWnd是MFC中对话框、控件、框架、视图、窗格等的父类。CWnd继承于类CCmdTarget。以前我们进行SDK编程的时候,经常会碰到窗口句柄HWND,现在窗口句柄作为CWnd类的一个成员变量了,如果类CWnd有一个公共成员变量:

        HWND m_hWnd;

前面提到,每个MFC应用程序都有一个主窗口,不同的应用程序类型对应的主窗口类也不同的。通常MFC的程序类型有三种:单个文档程序、多个文档程序和基于对话框的程序,这三种程序的主窗口分别由类CFrameWnd、CMDIFrameWnd和CDialog来实现,这三个类都是由CWnd直接或间接派生。

(5)类CFrameWnd

该类实现单文档程序(SDI)的主窗口功能。我们的单文档程序的主窗口类从CFrameWnd派生。CFrameWnd实现的窗口也称框架窗口,框架窗口指构造应用程序或部分程序的窗口,它通常包含视图窗口、工具栏、状态栏等元素的窗口。视图窗口也是一种窗口,它通常和文档数据打交道,用来显示文档中的数据,它是框架窗口的客户区(用来显示数据的子窗口)。框架窗口和视图窗口的关系如图2-70所示。

图2-70

通常有三种方法创建一个框架窗口:直接通过CFrameWnd的成员函数Create来创建、通过成员函数LoadFrame来直接创建、通过文档模板来间接构造。

(6)类CMDIFrameWnd

该类继承于CFrameWnd,主要用来实现多文档程序(MDI)的框架窗口。

(7)类CDialog

类CDialog主要用来实现程序中的各种对话框功能,包括模态对话框和非模态对话框。

(8)类CCommonDialog

该类是各个通用对话框类的父类,通用对话框指的是系统提供的颜色选取对话框(CColorDialog)、字体设置对话框(CFontDialog)、文件打开保存对话框(CFileDialog)、搜索替换对话框(CFindReplaceDialog)、打印对话框(CPrintDialog)等。从继承关系可以看到,Windows标准对话框类(CCommonDialog)是继承自CDialog。

(9)类CView

CView称为视图类,它也是一种窗口,是框架窗口的客户区(显示数据的子窗口)。类CView的作用是为文档数据提供一个视图,视图就是显示数据、接受用户输入、编辑或选择数据的窗口。CView又派生了很多不同显示数据方式的视图子类,比如让视图以列表方式显示的CListView、让数据以树形方式显示的CTreeView、支持用户编辑功能的视图CEditView,让数据以超文本方式显示CHtmlView。

(10)类CDocTemplate

文档模板类主要用来整合文档、视图和框架窗口的创建。

(11)类CDocument

文档类CDocument主要用来管理程序中的数据,相当于视图和文件之间的媒介。

上面涉及了程序大框架方面的几个类。其他类都是针对某个功能的,比如CSocket是针对网络功能的实现,CMenu针对菜单功能的实现,CException针对异常功能的实现,CFile是针对文件功能的实现。以后用到的时候再理解不迟。

下面来介绍MFC库中一些常用类的用法,这些类虽然不是一个大话题,但经常会在各个场合中用到,所以熟悉是很有必要的。

2.7.2 MFC应用程序类型

在程序中使用了MFC类库中的类后生成的可执行程序(.exe)叫做MFC应用程序。通常有4种MFC应用程序类型:单文档程序、多文档程序、基于对话框的程序和多个顶级文档应用程序。在Visual C++ 2013的MFC应用程序向导中可以选择这4种类型中的一种,如图2-71所示。

图2-71

通过向导生成这4种类型的程序我们不需要写一行代码,但是向导生成的程序只是一个程序架构,我们所需的功能是需要自己手工敲入代码的。

文档程序通常是用来显示文档内容的,文档程序有一个主框架窗口,里面包含一个或多个视图窗口,视图窗口用于显示文档内容,文档通过文档类对象来管理,文档程序把数据的管理和显示分离开来。单文档程序顾名思义就是一个程序中只有一个视图窗口和一个文档对象,多文档程序则有多个视图窗口和多个文档对象。对话框程序没有视图窗口和文档对象等概念,这类程序通常是在对话框上放置控件,然后通过控件的操作和用户交互。下面我们来看几个简单的MFC应用程序。

【例2.36】 一个简单的单文档程序

(1)打开Visual C++ 2013,选择菜单“新建”|“项目”,或直接按Ctrl+Shift+N快捷键,弹出“新建项目”对话框,在该对话框上,在左边展开“模板”|Visual C++|MFC,然后在右边选择“MFC应用程序”,如图2-72所示。

图2-72

接着在下方“名称”中输入项目名称,在“位置”中选择项目存放路径,最后单击“确定”按钮。随后出现“MFC应用程序向导”对话框,在该对话框的左边选择“应用程序类型”,然后在右边选择应用程序类型为“单个文档”,项目类型为“MFC标准”,如图2-73所示。

接着单击“完成”按钮。此时一个单文档类型的MFC程序的框架完成了,而我们无须写一行代码。

(2)开始运行工程。单击菜单“调试”|“开始执行(不调试)”,或直接按Ctrl+F5快捷键来运行工程,运行结果如图2-74所示。

图2-73

图2-74

【例2.37】 一个简单的对话框程序

(1)打开Visual C++ 2013,选择菜单“新建”|“项目”,或直接按快捷键Ctrl+Shift+N,弹出“新建项目”对话框,在该对话框上,在左边展开“模板”|“Visual C++”|“MFC”,然后在右边选择“MFC应用程序”。

接着在下方“名称”中输入项目名称,在“位置”中选择项目存放路径,最后单击“确定”按钮。随后出现“MFC应用程序向导”对话框,在该对话框的左边选择“应用程序类型”,然后在右边选择应用程序类型为“基于对话框”,项目类型为“MFC标准”,如图2-75所示。

接着单击“完成”按钮。此时一个简单的对话框类型的MFC程序的框架完成了,而我们无须写一行代码。

(2)开始运行工程。单击菜单“调试”|“开始执行(不调试)”,或直接按Ctrl+F5快捷键来运行工程,运行结果如图2-76所示。

图2-75

图2-76

2.7.3 添加菜单

无论文档程序还是对话框程序,都可以拥有菜单。默认情况下,文档程序已经有菜单了,而对话框程序则没有。添加菜单是文档程序中常见操作,文档程序很多时候是通过菜单来和用户交互的。我们通过例子来说明如何为文档程序添加菜单。

【例2.38】 单文档程序添加菜单项

(1)打开Visual C++ 2013,新建一个单文档程序。

(2)打开“资源视图”,并展开“Test|Test.rc|Menu|IDR_MAINFRAME”,如图2-77所示。

IDR_MAINFRAME是这个程序的菜单资源的名字,双击IDR_MAINFRAME来打开菜单设计界面,如图2-78所示。

图2-77

图2-78

在图2-78中,每个位于横向位置上的菜单下面都有下拉菜单,下拉菜单称为横向菜单的子菜单,如“文件”是横向菜单的第一个菜单项,它下面有“新建、打开…退出”这些子菜单项。我们既可以新增横向菜单项,也可以在下拉菜单中添加子菜单项。要添加菜单项,可以在“请在此处键入”的白色区域单击鼠标,然后输入我们需要新增的菜单名字,比如我们在“视图”菜单的下拉菜单中添加一个菜单项“显示你好”,如图2-79所示。

每个菜单项除了名字外,还有一个属性叫ID,菜单ID用来标记菜单项,相当于我们的身份证,必须唯一。新增菜单的时候系统会分配一个默认的ID,但不太直观,我们可以自己修改。右击“显示你好”菜单项,在右键菜单上选择“属性”,打开属性视图,找到ID,然后把其右边的值设为ID_VIEW_SHOWHELLO,如图2-80所示。

图2-79

图2-80

此时运行工程,单击“视图”菜单,可以发现其下拉菜单里有一个菜单项“显示你好”了,但单击它并没有什么反应,这是因为我们没有为这个菜单项添加事件处理。要为菜单项添加事件处理函数,可以通过可视化的方式,切换到菜单设计界面,然后单击“视图”菜单来打开其下拉菜单,并对“显示你好”菜单项右击,出现右键菜单,在右键菜单上选择“添加事件处理程序”,如图2-81所示。

图2-81

随后会出现“事件处理程序向导”对话框,在该对话框上,在“消息类型”下选中“COMMAND”,在类列表里选中“CMainFrame”,表示我们要添加的消息处理函数是类CMainFrame的成员函数,最后单击按钮“添加编辑”,此时会打开代码编辑窗口,并自动定位到CMainFrame::OnViewShowhello()函数处,这个函数就是系统为我们添加的消息处理函数,我们可以在这个函数中添加菜单的消息响应,这里我们就简单地显示一个消息框,代码如下:

        void CMainFrame::OnViewShowhello()
        {
            // TODO:  在此添加命令处理程序代码
            AfxMessageBox(_T("你好")); //显示消息框
        }

AfxMessageBox是系统API函数,用来显示消息框,其参数是一个字符串,就是消息框上的文本字符串。

(3)保存工程并运行,然后单击菜单“视图”|“显示你好”,运行结果如图2-82所示。

图2-82

2.7.4 窗口客户区

无论是画图还是存放控件,通常都是针对窗口客户区的。因此我们有必要先了解下窗口客户区的概念。在Win32程序中,窗口客户区比较简单,通常指除去菜单栏、滚动条后的中间区域部分,如图2-83所示。

MFC程序的窗口客户区稍微复杂,尤其是文档程序,主窗口的客户区与视图窗口的客户区是不同的,通常画图程序是在视图窗口的客户区上进行的。我们先来看下单文档程序的客户区。

单文档程序的客户区分为主窗口客户区和视图窗口客户区,主窗口的客户区和Win32程序的客户区类似,通常指除去菜单栏后的中间区域部分,但要注意单文档程序的工具栏和状态栏是显示在其客户区之内的,即主窗口客户区是包括工具栏和状态栏区域的,如图2-84所示。

上面红线框起来的部分就是主窗口的客户区部分,白色区域部分和白色区域四周的边框合起来就是视图窗口,而单单白色区域部分就是视图窗口的客户区,如图2-85所示。

图2-83

图2-84

图2-85

或许光看图我们看不出白色区域的四周边框,但它却是存在的(其实仔细看还是能看的出来,如果把其边框去掉就更能区别开来了,见下例)。默认情况下,视图窗口是有边框的。

单文档程序中,视图窗口的宽度和主窗口客户区的宽度是一样的,而视图窗口客户区的宽度比主窗口客户区的宽度少了两个边框(视图窗口的边框)的宽度。我们可以来看个例子。

【例2.39】 视图窗口的客户区

(1)打开Visual C++ 2013,新建一个单文档程序。

(2)切换资源视图,双击菜单资源IDR_MAINFRAME,然后在视图下添加两个菜单项“主框架窗口的客户区尺寸”和“视图窗口的客户区尺寸”,并修改它们的ID为ID_MAIN_SIZE和ID_VIEW_SIZE。然后为“主框架窗口的客户区尺寸”菜单项添加CMainFrame类的事件处理函数,代码如下:

        void CMainFrame::OnMainSize()
        {
            // TODO:  在此添加命令处理程序代码
            CRect rt; //定义矩形尺寸
            CString str; //定义字符串
            GetClientRect(&rt); //获取客户区大小尺寸
            str.Format(_T("主窗口客户区的宽度:%d,高度:%d"), rt.Width(), rt.Height());//格式化到字符串
            AfxMessageBox(str); //显示结果
        }

CRect是一个MFC类,用于表示一个矩形的大小,它常见的成员函数如下表所示。

CString也是一个MFC类,用来表示字符串,功能十分强大,并且会经常接触,后续章节我们会详细阐述,这里只用到了其成员函数Format,用于把不同类型的数据转换为字符类型,并组成一个新的字符串。

函数GetClientRect是CWnd的成员函数,因为CMainFrame从CWnd派生下来,所以也可以调用CWnd的成员函数GetClientRect,并且在类CMainFrame的成员函数OnMainSize中调用GetClientRect,则得到的是主框架窗口的客户区尺寸,其结果保存在参数rt中。

同样,再为“视图窗口的客户区尺寸”菜单项添加CTestView类的事件处理函数,代码如下:

        void CTestView::OnViewSize()
        {
            // TODO:  在此添加命令处理程序代码
            CRect rt;
            CString str;
            GetClientRect(&rt);
            str.Format(_T(" 视图窗口客户区的宽度 : %d , 高度 : %d"),  rt.Width(),rt.Height());
            AfxMessageBox(str);
        }

此时运行工程,分别单击新增的两个菜单,可以发现主窗口客户区的宽度比视图窗口客户区的宽度大了4,而这多出来的4正是视图窗口左右两个边框的宽度之和。我们可以把视图窗口的边框去掉再看结果。

(3)切换到类视图,然后单击类CTestView,在下方找到其成员函数PreCreateWindow,双击它,出现代码编辑窗口。在函数CTestView::PreCreateWindow中添加去除视图窗口边框的代码如下:

        BOOL CTestView::PreCreateWindow(CREATESTRUCT& cs)
        {
            // TODO:  在此处通过修改
            //  CREATESTRUCT cs来修改窗口类或样式
            cs.style &= ~WS_BORDER; //把边框风格去掉
            return CView::PreCreateWindow(cs);
        }

函数PreCreateWindow是类CWnd的虚拟函数,因为视图窗口也是由CWnd派生下来,因此可以调用PreCreateWindow,该函数在窗口被创建之前调用,使得用户可以设置增减窗口风格,但要注意不要直接调用这个函数,该函数的声明如下:

        virtual BOOL PreCreateWindow( CREATESTRUCT& cs );

其中参数cs是一个CREATESTRUCT结构,它传递给应用程序的窗口过程的初始化参数,用户如果要修改初始化值,可以通过这个参数来进行。CREATESTRUCT结构定义如下:

        typedef struct tagCREATESTRUCT
        {
          LPVOID  lpCreateParams; //指向将被用于创建窗口的数据的指针
          HANDLE  hInstance;  //标识了拥有新窗口的模块的模块实例的句柄
          HMENU   hMenu;  //标识了要被用于新窗口的菜单。如果是子窗口,则包含整数ID
          HWND     hwndParent; //标识了拥有新窗口的窗口。如果新窗口是一个顶层窗口,该参数可为NULL
          int     cy; //指定了新窗口的高
          int     cx; //指定了新窗口的宽
          int     y; //指定了新窗口的左上角的y轴坐标
          int     x; //指定了新窗口的左上角的x轴坐标
          LONG    style; //指定了新窗口的风格
          LPCSTR  lpszName; //指定了新窗口的名字
          LPCSTR  lpszClass;  //指定了新窗口的Windows类名
          DWORD   dwExStyle; //指定了新窗口的扩展风格
        } CREATESTRUCT;

程序中,首先WS_BOARD取反,然后再和cs. style进行与操作,就能把cs. style中WS_BOARD标记位去掉了,这样视图窗口就没有边框了。

此时运行工程,分别单击两个新增菜单,可以发现主框架窗口的客户区宽度和视图窗口的客户区宽度一样大了。如果我们单击“视图”下的菜单项“工具栏”和“状态栏”(就是隐藏它们,如果它们前面的勾没有就表示不显示),然后再看两个客户区的大小,会发现现在连高度也一样大了。说明原来视图窗口的客户区的高度比主框架窗口的客户区高度小,是因为工具栏和状态栏占用了地方,现在把它们隐藏后,视图窗口客户区和主窗口客户区的宽度高度都一样大了。这样说明,停靠着的工具栏和状态栏是包括在主窗口的客户区内的。

(4)保存工程并运行,运行结果如图2-86所示。

上面说了单文档程序的客户区情况,下面看下多文档程序的客户区。同样,多文档程序的客户区也分为主框架窗口的客户区和视图窗口的客户区。其实在多文档程序下,因为视图窗口可以浮动出来了,所以更能区别出两种窗口(主窗口和视图窗口)的客户区,如图2-87所示。

图2-87中,红线围起来的部分是视图窗口的客户区,绿线围起来的部分是主框架窗口的客户区。

对话框的客户区相对比较简单,如图2-88所示。

图2-86

图32-87

图2-88