MFC-第二节 MFC入门

第一节是用windows系统API的C语言源码编写一个窗口 本节使用MFC来创建一个窗口

MFC入门

  1. 什么是MFC?
    1. 微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。
    2. 其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
    3. MFC把Windows SDK API函数包装成了几百个类,MFC给Windows操作系统提供了面向对象的接口,支持可重用性、自包含性以及其他OPP原则。
    4. MFC通过编写类来封装窗口、对话框以及其他对象,引入某些关键的虚函数(覆盖这些虚函数可以改变派生类的功能)来完成,并且MFC设计者使类库带来的总开销降到了最低。
    5. 即MFC就是Windows API的一层C++封装

编写一个MFC程序

  1. 代码的编写
    1. 项目的创建和之前一样,只是此次的源文件后缀为.cpp,因为MFC是由C++编写的,编写MFC程序需要包含#include <afxwin.h>头文件。
    2. 代码如下:

       //mfc.h文件
       #pragma once
      
       #include<afxwin.h>//mfc头文件
       //应用程序类CWinApp,派生类(子类)
       //CWinApp就相当于iOS的UIApplication
       class MyApp:public CWinApp
       {
       public:
       	//重写父类的虚函数
       	//MFC程序的入口地址
       	virtual  BOOL InitInstance();
              
       };
              
       //窗口框架类:CFrameWnd,派生类
       class MyFrame:public CFrameWnd
       {
       public:
       	MyFrame();//无参构造函数
              
       };
              
       //mfc.cpp文件
       #include"mfc.h"
       MyApp app;//有且只有一个全局的应用程序类对象
       BOOL MyApp::InitInstance() { //程序的入口地址
       	MyFrame *frame = new MyFrame;//1. 创建框架类对象
       	frame->ShowWindow(SW_SHOWNORMAL);//2. 显示窗口
       	frame->UpdateWindow();//3. 更新窗口
       	m_pMainWnd = frame;//4. 保存你的线程的主窗口对象的指针
       	return TRUE;//初始化正常返回true
       }
              
       MyFrame::MyFrame() {
       	Create(NULL, TEXT("MFC WINDOW"));//创建窗口
       }
      
    3. 配置环境后,代码才可编译运行:
      1. 右击项目->属性->常规->MFC的使用->选择“在共享DLL中使用MFC”->应用,确定
      2. 右击项目->属性->常规->字符集->使用unicode字符集(必须设置)
    4. 程序执行流程
      1. 程序开始时,先实例化应用程序对象(有且只有一个)
      2. 执行程序的入口函数InitInstance()
      3. 给框架类MyFrame对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create创建窗口
      4. 框架类对象显示窗口CWnd::ShowWindow
      5. 框架类对象更新窗口CWnd::UpdateWindow
      6. 保存框架类对象指针CWinThread::m_pMainWnd
    5. 代码分析:
      1. CFrameWnd 框架窗口类
        1. CFrameWnd是从CWnd(窗口基类)派生出来的。CFrameWnd模仿框架窗口行为,我们可以把框架窗口作为顶层窗口看待,它是应用程序与外部世界的主要接口。
        2. 如果想要创建一个窗口,可以在此类中调用CWnd::Create或CWnd::CreateEX函数:

           virtual BOOL Create(
           	LPCTSTR lpszClassName,
           	LPCTSTR lpszWindowName,
           	DWORD dwStyle,
           	const RECT& rect,
           	CWnd* pParentWnd,
           	UINT nID,
           	CCreateContext* pContext = NULL
           	);
          
          • Create接收的8个参数后6个有默认值定义。
          • lpszClassName指定了窗口基于WNDCLASS类的名称,为此将其设定为NULL将创建一个基于已注册的WNDCLASS类的默认框架窗口。
          • lpszWindowName参数指定将在窗口的标题栏出现的文字。
      2. CWinApp应用程序类
        1. MFC应用程序的核心就是基于CWinApp类的应用程序对象。CWinApp提供了消息循环来检索消息并将消息调度给应用程序窗口。它还包括可被覆盖的、用来自定义应用程序行为的主要虚函数。
        2. 一个MFC程序可以有且仅有一个应用程序对象,此对象必须声明为在全局范围内有效,以便它在程序开始时即在内存中被实例化。
      3. InitInstance函数
        1. CWinApp::InitInstance函数是一个虚函数,其默认操作仅有一条语句:

           BOOL MyApp::InitInstance()//程序入口地址
           {
           	return TRUE;
           }
          
        2. InitInstance的目的是给应用程序提供一个自身初始化的机会,其返回值决定了框架接下来要执行的内容,如果返回FALSE将关闭应用程序,如果初始化正常返回TRUE以便允许程序继续进行。此函数是MFC应用程序的入口。

      4. m_pMainWnd 成员变量
        1. 在CWinApp中有一个名为CWinThread::m_pMainWnd的成员变量。 该变量是一个CWnd类型的指针,它保存了应用程序框架窗口对象的指针。也就是说,是指向CFramWnd对象(框架窗口类对象)的指针。
  2. 消息映射
    1. 消息映射是一个将消息和成员函数相互关联的表。比如,框架窗口接收到一个鼠标左击消息,MFC将搜索该窗口的消息映射,如果存在一个处理WM_LBUTTONDOWN消息的处理程序,然后就调用OnLButtonDown。
    2. 下面是是将消息映射添加到一个类中所做的全部工作:
      1. 所操作类中,声明消息映射宏。
      2. 通过放置标识消息的宏来执行消息映射,相应的类将在对BEGIN_MESSAGE_MAP和END_MESSAGE_MAP的调用之间处理消息。
      3. 代码如下:

         //窗口框架类:CFrameWnd,派生类
         class MyFrame:public CFrameWnd
         {
         public:
         	MyFrame();//无参构造函数
                    
         	//声明消息映射,必须用在类声明中
         	DECLARE_MESSAGE_MAP();
         	//鼠标左击处理函数声明
         	afx_msg void OnLButtonDown(UINT,CPoint);
                    
         };
                    
         //mfc.cpp文件
                    
         //定义消息宏,必须用在类实现中
         BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)
         	ON_WM_LBUTTONDOWN()
         END_MESSAGE_MAP()//结束
         //鼠标左击处理函数定义
         void MyFrame::OnLButtonDown(UINT, CPoint)
         {
         	MessageBox(TEXT("鼠标点击左键"));
         }
                    
         MyFrame::MyFrame() {
         	Create(NULL, TEXT("MFC WINDOW"));//创建窗口
         }
        
  3. 代码示例:

     #pragma once
     #include<afxwin.h>//mfc头文件
     //应用程序类CWinApp,派生类(子类)
     //CWinApp就相当于iOS的UIApplication
     class MyApp:public CWinApp
     {
     public:
     	//重写父类的虚函数
     	//MFC程序的入口地址
     	virtual  BOOL InitInstance();
        
     };
        
     //窗口框架类:CFrameWnd,派生类
     class MyFrame:public CFrameWnd
     {
     public:
     	MyFrame();//无参构造函数
        
     	//声明消息映射,必须用在类声明中
     	DECLARE_MESSAGE_MAP();
     	//鼠标左击处理函数声明
     	afx_msg void OnLButtonDown(UINT,CPoint);
     	afx_msg void OnChar(UINT, UINT, UINT);
     	afx_msg void OnPaint();
     };
    
        
     #include"mfc.h"
     MyApp app;//有且只有一个全局的应用程序类对象
     BOOL MyApp::InitInstance() { //程序的入口地址
     	MyFrame *frame = new MyFrame;//1. 创建框架类对象
     	frame->ShowWindow(SW_SHOWNORMAL);//2. 显示窗口
     	frame->UpdateWindow();//3. 更新窗口
     	m_pMainWnd = frame;//4. 保存你的线程的主窗口对象的指针
     	return TRUE;//初始化正常返回true
     }
        
     //定义消息宏,必须用在类实现中
     BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)
     	ON_WM_LBUTTONDOWN()//鼠标左键按下
     	ON_WM_CHAR()//键盘的按下
     	ON_WM_PAINT()//绘图
     END_MESSAGE_MAP()//结束
     //鼠标左击处理函数定义
     void MyFrame::OnLButtonDown(UINT, CPoint point)
     {
     	//MessageBox(TEXT("鼠标点击左键"));
     	CString str;//mfc中使用的字符串
     	str.Format(TEXT("x= %d,y= %d"), point.x, point.y);
     	MessageBox(str);
     }	
        
     //键盘的按下操作
     void MyFrame::OnChar(UINT key, UINT, UINT)
     {
     	/*TCHAR buf[1024];
     	wsprintf(buf, TEXT("按下了%c"), key);
     	MessageBox(buf);*/
     	CString str;
     	str.Format(TEXT("按下了%c"), key);
     	MessageBox(str);
     }
     //绘图
     void MyFrame::OnPaint()
     {
     	CPaintDC dc(this);//画家画到当前窗口
     	dc.TextOutW(100, 100, TEXT("hello world"));
     	//画圆
     	dc.Ellipse(10, 10, 100, 100);
     }
     MyFrame::MyFrame() {
     	Create(NULL, TEXT("MFC WINDOW"));//创建窗口
     }
    

帮助文档的使用

  1. 帮助文档有2种方式
    1. MSDN搜索
      1. 下载MSDN,打开,点击索引,既可以搜索想要查找的类
    2. VC++之MFC类库中文手册(直接百度搜索这个名字,百度上很多)
      1. 通过此手册查找,必须加上成员所属的类作用域(类名::),否则,无法查找到匹配的关键字。
      2. 比如:CWnd::MessageBox
      3. 也是先下载,然后打开搜索使用
      4. 改手册滑动到最底层,全部是“消息映射”相关的文档。

Widnows字符集

  1. 设置位置:
    1. 右击项目->属性->常规->字符集->下拉选择相应字符集
  2. 字符集的类型
    1. 多字节字符集(8位的ANSI字符集)
      1. 在Windows98以及以前的版本使用8位ANSI字符集,它类似于我们程序员熟悉的ASCII字符集。
      2. 也就是数改字符集不支持中文、日文等

         char sz[] = "ABCDEFG";
         char *psz = "ABCDEFG";
         int len = strlen(sz); //计算多字节字符串的长度
        
    2. 宽字符集(16位的Unicode字符集)
      1. 在WindowsNT和Windows2000后开始使用16位的Unicode字符集,它是ANSI字符集的一个超集。Unicode适用于国际市场销售的应用程序,因为它包含各种各样来自非U.S.字母表的字符,比如中文,日文,韩文,西欧语言等。

         //在字符串前加字母L表示将ANSI字符集转换成Unicode字符集。
         wchar_t wsz[] = L"ABCDEFG"; 
         wchar_t *pwsz = L"ABCDEFG";
         int len = wcslen(wsz); //测试宽字节字符串的长度
        
  3. TEXT(_T)宏
    1. MFC中的TEXT宏可以自动适应字符类型,如果定义了预处理器程序符号_UNICODE,那么编译器将使用Unicode字符,如果没用定义该预处理器程序符号,那么编译器将使用ANSI字符。
    2. 也就是说TEXT宏会自动将“多字符集”转化为“宽字符集”。这就是为什么,在写代码时字符串签名要加上一个TEXT宏

       MessageBox(TEXT("鼠标左键"));
       MessageBox(_T("鼠标左键"));
      
  4. TCHAR类型
    1. 如果定义了_UNICODE符号TCHAR将变为wchar_t类型。如果没用定义_UNICODE符号,TCHAR将变为普通古老的char类型。
  5. 类型转换

     //CString与char*相互转换
     //char*->CString
     char * pp = "abc";
     CString str = CString(pp);
        
     //CString->char*
     CString str2 = TEXT("b");
     CStringA temp;
     temp = str2;
     char *p2 = temp.GetBuffer();
    

用向导生成一个MFC应用程序

  1. 向导流程
    1. 在VS中选择“文件” – “新建” – “项目”:命名(MFCGuide)、路径
    2. 选择 MFC – MFC应用程序,确定
    3. 应用程序类型:
      1. 应用程序类型:单个文档
      2. 项目样式(里面有4种样式):选择MFC standard(MFC 标准)
    4. 一路按默认值next,到最后一个页面:
      1. 生成的类:点开下来箭头,从这里可以看到,自动为我们生成4个类
        1. CMFCGuideApp:继承自CWinAppEx;
        2. CMFCGuideDoc: 继承自CDocument
        3. CMainFrame: 继承自CFrameWndEx //相当于相框
        4. CMFCGuideView: 继承自CView
      2. 选择每个类可以看到当前类所在的头文件与cpp文件
      3. 点击完成
    5. 同时也能够看到,源文件夹下面一共5个文件
      1. MainFrm.cpp
      2. MFCGuide.cpp
      3. MFCGuideDoc.cpp
      4. MFCGuideView.cpp
      5. stdafx.cpp
    6. 一个项目常用的三个功能:
      1. 解决方案资源管理器:用于显示当前项目的所有源文件(头文件、源文件、引用、资源文件、依赖库文件)
      2. 类视图:用于查看,当前项目有哪些类,以及每个类都有哪些成员函数
      3. 资源视图:用户查看,当前项目有哪些资源,图片、视图(相当于iOS的xib/sb)等
    7. 项目产生的文件夹(项目编译运行之后会生成文件目录如下)
      1. MFCGuide.sln vs的工程文件,双节这个文件直接会用vs打开项目
      2. MFCGuide文件夹(项目根目录文件夹)
        1. res文件夹:用于存放项目的资源(图片等)
        2. 源文件目录
  2. 视图类:
    1. 用于查看,当前项目有哪些类,以及每个类都有哪些成员函数
    2. 工具栏->视图->类视图
    3. 可以看到当前项目中生成的5个类(CAboutDlg、CMainFrame、CMFCGuideApp、CMFCGuideDoc、CMFCGuideView)
    4. 双击类名即可以进入该类的.h文件,分栏下部分显示该类的成员函数
    5. 双击成员函数,既可以跳转到改类cpp文件中的实现
  3. 文档/视图结构体系
    1. MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象(CMFCGuideDoc)保存应用程序的数据,并依靠视图对象(CMFCGuideView)控制视图中显示的数据,把数据本身与它的显示分离开。
    2. 数据的存储和加载由文档类(CDocument)来完成,数据的显示和修改则由视类(CView)来完成。
      1. MFC在类CDocument和CView中为稳定视图提供了基础结构。
      2. CWinApp、CFrameWnd和其他类与CDocument和CView合作,把所有的片段连在了一起。
    3. CView类也派生于CWnd类,框架窗口是视图窗口的一个父窗口。主框架窗口(CFrameWnd)是整个应用程序外框所包括的部分,即图中粗框以内的内容,而视类窗口只是主框架中空白的地方。 图4
  4. 消息处理的添加
    1. 在主框架类(CMainFrame)中添加WM_LBUTTONDOWN消息的响应函数,具体操作如下:
      1. 从类视图中找到CMainFrame(继承自CFrameWnd),右击->属性,在属性面板中找到消息按钮,在消息列表中找到WM_LBUTTONDOWN消息,点击右边的白色下拉箭头->添加。
      2. 就会跳转到相应的cpp文件( MainFrm.cpp),并且实现消息函数
    2. 上面的步骤我们会看见工程文件自动增加了几处改变:
      1. 第一处:在框架类头文件( MainFrm.h)中,可以看见自动添加了鼠标左键消息函数的函数声明

         public:
         afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        
      2. 第二处:在框架类cpp文件(MainFrm.cpp)中添加了消息映射宏

         BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx)
         	...
         	ON_WM_LBUTTONDOWN()
         END_MESSAGE_MAP()
        
      3. 第三处:在框架列cpp文件中添加了处理鼠标左键消息的函数定义

         void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
         {
         	// TODO: 在此添加消息处理程序代码和/或调用默认值
                    
         	CFrameWndEx::OnLButtonDown(nFlags, point);
         }
        
    3. 我们再此OnLButtonDown函数中添加一个MessageBox消息,鼠标左键按下弹出一个提示框,然后执行程序。

       void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
       {
       	// TODO: 在此添加消息处理程序代码和/或调用默认值
       	CString str;
       	str.Format(TEXT("鼠标点击左键"));
       	MessageBox(str);
       	CFrameWndEx::OnLButtonDown(nFlags, point);
       }
      
      1. 我们会惊奇的发现程序并未如我们所愿弹出消息框。
      2. 因为,框架窗口视窗口的父窗口,那么视类窗口就应该始终覆盖在框架类窗口之上。就好比框架窗口是一面墙,视类窗口就是墙纸,它始终挡在这面墙前边。
      3. 也就是说,所有操作,包括鼠标单击、鼠标移动等操作都只能有视类窗口捕获。
      4. 因此需要到视类窗口(MFCGuideView)中添加
        1. 仍然需要在该类中添加消息处理,跟CMainFrame中操作方式一样
        2. 在MFCGuideView.cpp找到OnLButtonDown函数,代码复制进去,运行程序,会发现可以
    4. 总结添加消息处理步骤
      1. 在主框架中添加消息处理CMainFrame
      2. 在视图类中添加消息处理MFCGuideView
      3. 在视图类中MFCGuideView相应的函数中实现消息处理的具体代码。
      4. 疑问:貌似直接在视图类中MFCGuideView添加消息处理就可以了,不需要在CMainFrame中添加,也可以。就是只有2、3步骤即可。
  5. MFC框架中一些重要的函数
    1. InitInstance函数
      1. 应用程序类的一个虚函数,MFC应用程序的入口。
      2. 在MFCGuide.cpp中,是CMFCGuideApp类的成员函数
    2. PreCreateWindow函数
      1. 当框架调用CreateEx函数创建窗口时,会首先调用PreCreateWindow函数。
      2. 通过修改传递给PreCreateWindow的结构体类型参数CREATESTRUCT,应用程序可以更改用于创建窗口的属性。在产生窗口之前让程序员有机会修改窗口的外观。
      3. 最后再调用CreateWindowEx函数完成窗口的创建。
      4. 在MainFrm.cpp文件中
    3. OnCreate函数
      1. OnCreate是一个消息响应函数,是响应WM_CREATE消息的一个函数,而WM_CREATE消息是由Create函数调用的。一个窗口创建(Create)之后,会向操作系统发送WM_CREATE消息,OnCreate()函数主要是用来响应此消息的。
      2. OnCreate与Create的区别:
        • Create()负责注册并产生窗口,像动态创建控件中的Create()一样,窗口创建之后会向操作系统发送WM_CREATE消息。
        • OnCreate()不产生窗口,只是在窗口显示前设置窗口的属性如风格、位置等。
        • OnCreate()是消息WM_CREATE的消息响应函数。
      3. 在MainFrm.cpp文件中
    4. OnDraw和OnPaint
      1. OnPaint是WM_PAINT消息的消息处理函数,在OnPaint中调用OnDraw,一般来说,用户自己的绘图代码应放在OnDraw中。
        • OnPaint()是CWnd的类成员,负责响应WM_PAINT消息。
        • OnDraw()是CView的成员函数,没有响应消息的功能。
      2. 当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创建CPaintDC类的DC对象来响应该消息并调用视图的OnDraw成员函数。OnPaint最后也要调用OnDraw,因此一般在OnDraw函数中进行绘制。
      3. 通常我们不必编写OnPaint处理函数。当在View类里添加了消息处理OnPaint()时,OnPaint()就会覆盖掉OnDraw()。
  6. 拓展知识点
    1. MFC中后缀名为Ex的函数都是扩展函数。
    2. 在MFC中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们。
Table of Contents