MFC入门指南:从Windows API封装到文档视图架构实战

发布时间:2026/8/23 18:28:59
MFC入门指南:从Windows API封装到文档视图架构实战 1. 从“Hello World”到窗口程序为什么是MFC如果你刚开始接触Windows桌面开发用C写一个带窗口的程序第一反应可能就是去搜“C 窗口”。然后你会发现网上充斥着各种教程有的让你直接调用CreateWindow这个Windows API写一大堆代码有的则推荐你用MFC说能简化开发。你可能会疑惑MFC到底是什么它和Windows API是什么关系为什么现在还有人提它简单来说MFCMicrosoft Foundation Classes是微软在90年代推出的一套C类库它的核心目标就是把Windows编程中那些繁琐、重复的底层API调用封装成一个个更符合C面向对象思想的类。想象一下你要在Windows上创建一个带按钮的窗口。如果只用原始的Windows API你需要定义一个WinMain入口函数。注册一个窗口类WNDCLASS或WNDCLASSEX指定窗口过程函数、图标、光标等。调用CreateWindow创建窗口。进入消息循环用GetMessage、TranslateMessage、DispatchMessage不停地处理鼠标点击、键盘输入等消息。在窗口过程函数里用一个巨大的switch-case语句来处理上百种可能的消息比如WM_PAINT绘制、WM_COMMAND按钮点击等。这个过程不仅代码量大而且全是面向过程的C风格代码管理和扩展起来非常头疼。MFC的出现就是为了解决这个问题。它把WinMain、窗口创建、消息循环这些“脏活累活”都藏在了框架内部。你只需要从CWinApp应用类和CFrameWnd框架窗口类这样的基类派生出自己的类然后重写几个关键的函数比如OnInitDialog初始化、OnPaint绘制、OnCommand处理命令就能快速搭建起一个应用程序的骨架。所以MFC不是一个独立的编程语言它是一套建立在C和Windows API之上的“脚手架”或“工具箱”。它没有取代API而是对API进行了更高层次的抽象和封装。对于新手而言学习MFC的曲线前期会比直接啃API平缓一些因为你不用一开始就面对令人望而生畏的WNDPROC和消息循环。但它的背后依然是那套经典的Windows消息机制。理解这一点是学好MFC乃至任何Windows GUI框架的关键。2. 搭建你的第一个MFC项目环境与向导在动手写代码之前我们得先把环境准备好。现在主流的开发环境是Visual StudioVS社区版是免费的。确保你安装时勾选了“使用C的桌面开发”工作负载这里面就包含了MFC的支持。注意从Visual Studio 2019开始MFC默认不是必选组件。如果你在新建项目时找不到MFC项目模板需要打开Visual Studio Installer找到你已安装的VS版本点击“修改”在“单个组件”选项卡中搜索并勾选“MFC和ATL支持x86和x64”然后安装。环境就绪后我们通过VS的项目向导来创建第一个MFC程序这是最快捷的方式。打开Visual Studio选择“创建新项目”。在项目模板搜索框中输入“MFC”选择“MFC应用”点击“下一步”。给你的项目起个名字比如MyFirstMFC选择好项目位置点击“创建”。这时会弹出“MFC应用程序向导”。对于第一个程序我们一路采用默认设置即可但有几个关键页面需要理解应用程序类型这里选择“单个文档”。你会看到还有“多个文档”、“基于对话框”等选项。“单个文档”类似记事本一次只能打开一个文件窗口“基于对话框”则是像计算器那样主界面就是一个对话框。我们从“单个文档”开始它能展示更完整的MFC框架结构。复合文档支持不需要取消勾选所有选项。文档模板属性这里可以设置你的程序默认的文件扩展名如.txt我们暂时不管。数据库支持无取消勾选。用户界面功能这里有很多复选框比如“初始状态栏”、“初始工具栏”、“打印和打印预览”。保持默认勾选这样向导会帮你生成带有标准菜单、工具栏和状态栏的框架方便我们观察。高级功能通常保持默认即可。生成的类这是最值得看的一页。向导会列出它即将为你生成的几个核心类比如CMyFirstMFCApp: 从CWinApp派生代表应用程序本身有且只有一个全局对象。CMyFirstMFCDoc: 从CDocument派生负责管理应用程序的数据文档。CMyFirstMFCView: 从CView派生负责显示文档数据视图。CMainFrame: 从CFrameWnd派生是程序的主框架窗口包含了菜单、工具栏、状态栏和视图区域。理解这几个类的关系是理解MFC“文档/视图”架构的基础。简单说Doc管数据View管显示Frame管容器App管全局。点击“完成”VS就会自动生成一个完整的、可直接编译运行的MFC应用程序。点击绿色的运行按钮或按F5你会看到一个标准的Windows窗口程序有菜单栏、工具栏、状态栏还能通过“文件”-“新建”创建多个窗口。虽然你一行代码都没写但一个功能相对完整的应用程序已经诞生了。这就是MFC框架和项目向导的威力——它为你搭建好了基础设施。3. 解剖麻雀理解向导生成的核心代码仅仅能运行还不够我们得知道这个程序是怎么跑起来的。让我们深入看看向导生成的几个关键文件。首先全局应用对象。在MyFirstMFC.cpp中你会看到这样一行代码CMyFirstMFCApp theApp;这定义了一个全局的应用程序对象theApp。当Windows启动你的程序时操作系统会调用真正的C入口函数通常是mainCRTStartup经过一系列运行时初始化后最终会调用到theApp的InitInstance成员函数。这个函数是应用程序初始化的核心。打开MyFirstMFC.cpp找到CMyFirstMFCApp::InitInstance()函数。里面有很多代码我们挑最关键的几行看// 注册应用程序的文档模板。文档模板将用作文档、框架窗口和视图之间的连接 CSingleDocTemplate* pDocTemplate; pDocTemplate new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS(CMyFirstMFCDoc), RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口 RUNTIME_CLASS(CMyFirstMFCView)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);这段代码创建了一个单文档模板CSingleDocTemplate。它就像一个“配方”定义了这三者如何绑定在一起当用户选择“文件”-“新建”时框架会根据这个模板同时创建一个文档对象CMyFirstMFCDoc、一个框架窗口对象CMainFrame和一个视图对象CMyFirstMFCView并把它们关联起来。IDR_MAINFRAME是一个资源ID它指向了菜单、图标、字符串等资源。接着下面这几行处理了命令行参数并最终创建并显示窗口// 分析标准 shell 命令、DDE、打开文件操作的命令行 CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // 调度在命令行中指定的命令。如果使用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序则返回 FALSE。 if (!ProcessShellCommand(cmdInfo)) return FALSE; // 唯一的一个窗口已初始化因此显示它并对其进行更新 m_pMainWnd-ShowWindow(SW_SHOW); m_pMainWnd-UpdateWindow();ProcessShellCommand函数是关键。如果命令行指定了要打开一个文件它会触发“打开”操作否则它会执行默认命令CCommandLineInfo::FileNew也就是创建一个新的文档-框架-视图组合。m_pMainWnd就是在这个过程中被赋值为新创建的主框架窗口CMainFrame对象的指针。最后两行调用Windows API显示并更新窗口。那么消息循环在哪里它被隐藏在了基类CWinApp的Run()成员函数里。在InitInstance成功返回后框架会自动调用theApp.Run()启动那个经典的GetMessage-TranslateMessage-DispatchMessage循环。这一切都对开发者透明。再看视图类。打开MyFirstMFCView.cpp找到OnDraw函数void CMyFirstMFCView::OnDraw(CDC* pDC) { CMyFirstMFCDoc* pDoc GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 }当窗口需要绘制比如刚显示、被其他窗口遮挡后重新露出、最大化最小化时框架会调用视图的OnDraw函数并传入一个设备上下文CDC* pDC指针。你可以在这个函数里使用pDC进行绘图比如画线、写文字。GetDocument()函数是视图获取其关联文档对象的标准方式这样视图就能显示文档中的数据。通过这番剖析你应该能感受到MFC框架帮你处理了应用程序生命周期中最固定、最模式化的部分初始化、消息循环、窗口创建关联而你只需要在它预留的“插槽”虚函数里填充自己的业务逻辑比如在OnDraw里绘图在文档类里管理数据。4. 消息映射MFC的“中枢神经系统”Windows程序是事件消息驱动的。点击鼠标、按下键盘、移动窗口都会产生一个消息。在纯API编程中你需要自己写一个窗口过程函数WNDPROC用switch-case来分发处理这些消息。MFC用一套名为“消息映射”的机制优雅地解决了这个问题它将特定的Windows消息映射到你的C成员函数。让我们给程序添加一个功能在视图窗口中点击鼠标左键就在点击的位置显示一个字符串。这需要处理WM_LBUTTONDOWN消息。首先在视图类的头文件MyFirstMFCView.h中声明一个消息处理函数。在// 生成的消息映射函数注释下方添加afx_msg void OnLButtonDown(UINT nFlags, CPoint point);afx_msg是一个宏它只是标记这个函数是消息处理函数编译后没有任何实际代码。然后在视图类的实现文件MyFirstMFCView.cpp中有两处需要修改。 第一找到消息映射表以BEGIN_MESSAGE_MAP开始以END_MESSAGE_MAP结束在里面添加一行BEGIN_MESSAGE_MAP(CMyFirstMFCView, CView) ON_WM_LBUTTONDOWN() // 添加这一行映射WM_LBUTTONDOWN消息到OnLButtonDown函数 // 标准打印命令 ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP()ON_WM_LBUTTONDOWN()是一个消息映射宏。MFC为许多标准Windows消息通常以WM_开头预定义了这样的宏。这个宏告诉框架当WM_LBUTTONDOWN消息发生时去调用OnLButtonDown这个成员函数。第二在文件某处实现OnLButtonDown函数void CMyFirstMFCView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CClientDC dc(this); // 获取当前视图的设备上下文 CString str; str.Format(_T(你点击了坐标: (%d, %d)), point.x, point.y); dc.TextOutW(point.x, point.y, str); // 在点击位置输出文字 CView::OnLButtonDown(nFlags, point); // 调用基类处理这是一个好习惯 }现在运行程序在视图的白色客户区点击鼠标你就会在点击处看到坐标信息。整个过程你不需要自己获取消息不需要写switch-case只需要声明函数、添加映射宏、实现函数。这就是消息映射的便利之处。为什么参数是UINT nFlags, CPoint point这是因为ON_WM_LBUTTONDOWN这个宏已经约定好了。每个消息映射宏都有其对应的函数签名。例如ON_WM_MOUSEMOVE对应的函数是OnMouseMove(UINT nFlags, CPoint point)ON_WM_PAINT对应的是OnPaint()。你需要查阅MSDN或查看MFC源代码来知道具体的签名。除了处理Windows标准消息更常见的是处理用户界面命令比如点击菜单项、工具栏按钮。这对应的是WM_COMMAND消息。MFC使用ON_COMMAND宏来处理。例如向导已经为我们生成了“文件”-“退出”菜单的处理。在CMainFrame类的消息映射中你可以找到ON_COMMAND(ID_APP_EXIT, CFrameWnd::OnAppExit)ID_APP_EXIT是“退出”菜单项的资源ID。当用户点击这个菜单框架会调用CFrameWnd::OnAppExit函数来关闭程序。如果你想为“编辑”-“复制”菜单假设ID是ID_EDIT_COPY添加处理可以在你的视图或框架类中这样写ON_COMMAND(ID_EDIT_COPY, CMyFirstMFCView::OnEditCopy)然后在类中实现OnEditCopy函数即可。消息映射机制是MFC的核心它让消息处理变得结构化、面向对象化是理解MFC程序流控制的关键。5. 对话框与控件打造用户交互界面基于对话框的应用程序是MFC的另一大应用场景它比单文档/多文档架构更简单适合工具类、设置类程序。我们来创建一个简单的对话框程序。在解决方案资源管理器中右键项目 - 添加 - 资源。在“添加资源”对话框中选择“Dialog”点击“新建”。这时会打开对话框编辑器中间是一个空对话框模板。从右侧的“工具箱”中拖拽一个“静态文本”Static Text控件和一个“编辑框”Edit Control控件到对话框上。静态文本的标题改为“请输入姓名”。再拖拽两个“按钮”Button控件标题分别改为“确定”和“取消”。调整好布局。右键对话框模板选择“添加类”。类名设为CMyDlg基类选择CDialogEx它是CDialog的增强版点击“完成”。这会生成MyDlg.h和MyDlg.cpp。我们需要将对话框上的编辑框控件与一个变量关联以便在代码中获取用户输入。在对话框编辑器中右键编辑框控件 - 添加变量。变量名设为m_strName类别选择“值”变量类型选择“CString”。点击“完成”。这个“添加成员变量向导”会自动在CMyDlg类中声明一个CString m_strName;并会在对话框初始化时DoDataExchange函数中将控件内容与这个变量进行数据交换。现在我们需要一个方式来弹出这个对话框。我们在主框架的“帮助”菜单下加一个新项。 7. 在资源视图中打开IDR_MAINFRAME菜单资源。在“帮助”菜单末尾添加一个新的菜单项ID设为ID_HELLO_DLG标题设为“显示对话框(D)…”D表示AltD为快捷键。 8. 我们需要为这个菜单项添加消息处理。在类视图Class View中右键CMainFrame类 - 属性。在属性窗口中点击顶部的“事件”按钮闪电图标。在事件列表中找到ID_HELLO_DLG展开它点击旁边的COMMAND下拉框选择“添加OnHelloDlg”。这会自动在CMainFrame类中添加消息映射和函数框架。 9. 在自动生成的CMainFrame::OnHelloDlg()函数中编写显示对话框的代码void CMainFrame::OnHelloDlg() { CMyDlg dlg; // 创建对话框对象 if (dlg.DoModal() IDOK) // 以模态方式弹出对话框并等待用户操作 { // 用户点击了“确定” CString strMessage; strMessage.Format(_T(你好%s), dlg.m_strName); AfxMessageBox(strMessage); // 弹出一个消息框显示输入内容 } // 如果用户点击“取消”则什么都不做 }最后别忘了在CMainFrame.cpp的开头包含对话框类的头文件#include MyDlg.h。运行程序点击“帮助”-“显示对话框”输入你的名字后点击“确定”程序就会用消息框向你问好。这个例子展示了MFC中对话框编程的基本流程设计对话框资源 - 为对话框添加类 - 关联控件变量 - 在代码中创建对话框对象并调用DoModal()显示。DoModal()是模态对话框它会阻塞调用它的代码直到对话框关闭。与之相对的是非模态对话框CreateShowWindow可以同时与主窗口交互实现更复杂一些。6. 数据管理与文档序列化在“文档/视图”架构中文档类CDocument派生类负责保存应用程序的数据。而“序列化”是MFC提供的一种将文档对象数据保存到文件序列化或从文件加载反序列化的机制。我们来实现一个简单的功能在视图点击鼠标时记录坐标并支持保存到文件和从文件打开。首先在文档类CMyFirstMFCDoc的头文件中添加一个数据成员用来存储点击坐标的列表// MyFirstMFCDoc.h class CMyFirstMFCDoc : public CDocument { ... public: CArrayCPoint, CPoint m_pointsArr; // 用于存储点击坐标的数组 ... };CArray是MFC提供的动态数组模板类这里我们用它来存储CPoint对象。然后在视图类的OnLButtonDown函数中将点击的坐标添加到文档的数组中并通知文档数据已修改最后请求重绘视图void CMyFirstMFCView::OnLButtonDown(UINT nFlags, CPoint point) { CMyFirstMFCDoc* pDoc GetDocument(); ASSERT_VALID(pDoc); pDoc-m_pointsArr.Add(point); // 1. 将坐标点加入文档数组 pDoc-SetModifiedFlag(TRUE); // 2. 标记文档为“已修改”关闭时会提示保存 Invalidate(); // 3. 通知窗口客户区无效触发重绘OnDraw CView::OnLButtonDown(nFlags, point); }接着修改视图的OnDraw函数将文档中存储的所有坐标点都画出来void CMyFirstMFCView::OnDraw(CDC* pDC) { CMyFirstMFCDoc* pDoc GetDocument(); ASSERT_VALID(pDoc); // 遍历文档中存储的所有点在每个点处画一个小十字 for (int i 0; i pDoc-m_pointsArr.GetSize(); i) { CPoint pt pDoc-m_pointsArr[i]; pDC-MoveTo(pt.x - 5, pt.y); pDC-LineTo(pt.x 5, pt.y); pDC-MoveTo(pt.x, pt.y - 5); pDC-LineTo(pt.x, pt.y 5); } }现在运行程序点击鼠标会在点击处留下十字标记。但关闭程序再打开这些标记就没了。我们需要实现序列化来保存和加载这些数据。找到文档类CMyFirstMFCDoc的Serialize函数向导已生成void CMyFirstMFCDoc::Serialize(CArchive ar) { if (ar.IsStoring()) { // TODO: 在此添加存储代码 } else { // TODO: 在此添加加载代码 } }CArchive对象ar就像一个数据流。ar.IsStoring()为真时表示正在保存写入文件为假时表示正在加载读取文件。MFC已经为我们处理了文件的打开、关闭我们只需要告诉它如何读写我们的数据。修改Serialize函数如下void CMyFirstMFCDoc::Serialize(CArchive ar) { m_pointsArr.Serialize(ar); // CArray本身支持序列化 }是的MFC的集合类如CArray,CList,CMap通常都内置了Serialize方法只要它们存储的元素类型也支持序列化CPoint是支持的我们就可以直接调用。这就是MFC序列化强大和方便的地方。现在运行程序点击几个点然后选择“文件”-“保存”给文件起个名比如test.pts。关闭程序再重新运行选择“文件”-“打开”打开刚才保存的test.pts文件你会发现之前点击的标记全部被还原了整个过程我们只写了三行关键代码添加数据成员、修改OnLButtonDown、修改Serialize就实现了数据的持久化。MFC框架隐藏了文件操作、版本控制等复杂细节这就是“文档/视图”架构和序列化机制带来的生产力提升。7. 调试与常见“坑点”实战解析学习任何技术都免不了踩坑MFC由于其历史包袱和复杂的宏展开有些错误信息对新手不太友好。这里分享几个我早期常遇到的典型问题及其排查思路。问题一编译错误“undefined reference to WinMain’”。这是新手在创建非MFC的Windows桌面项目时容易混淆的问题但在MFC学习过程中也可能因为项目配置错误而遇到。原因分析Windows GUI程序的入口点可以是WinMain传统API或wWinMainUnicode版本。控制台程序的入口点是main。当你创建一个“Windows桌面向导”项目但错误地选择了控制台选项或者MFC项目设置被意外更改时链接器会找不到正确的入口函数。解决方案检查项目类型确保你创建的是“MFC应用”项目而不是空项目或控制台项目。检查子系统设置右键项目 - 属性 - 链接器 - 系统。确保“子系统”设置为“Windows (/SUBSYSTEM:WINDOWS)”。MFC应用程序应该使用Windows子系统。检查入口点在链接器 - 高级属性中“入口点”通常是空的由MFC库自动提供WinMain实现。如果这里被错误设置请清空它。对于MFC项目确保在项目属性 - 高级中“MFC的使用”设置为“在共享DLL中使用MFC”或“在静态库中使用MFC”。问题二运行时错误“Debug Assertion Failed!”或程序崩溃。这是MFC调试版本中常见的断言失败通常是由于指针无效、对象状态错误或资源未加载引起。典型场景在视图的OnDraw函数中你调用了GetDocument()但返回的指针是NULL。排查思路仔细阅读断言对话框它会告诉你断言失败的文件.cpp和行号。双击可以跳转到源代码。理解断言条件断言失败的那行代码通常是一个ASSERT或ASSERT_VALID宏。比如ASSERT_VALID(pDoc)它断言pDoc指针不仅非空而且指向一个有效的CObject派生类对象。失败意味着pDoc为NULL或对象状态异常。回溯调用栈在Visual Studio的调试器中当断言弹出时点击“重试”可以中断到调试状态。查看“调用堆栈”窗口看看当前函数是被谁调用的。也许你的视图对象在文档对象还未完全创建时就被要求绘制了。检查对象生命周期MFC中许多对象如窗口、文档、视图的创建和销毁由框架管理。确保你在使用一个对象时它已经被正确创建例如在OnInitialUpdate之后使用视图且尚未被销毁例如不要在窗口已销毁后还试图访问其成员。实战案例我们之前在OnDraw里写了ASSERT_VALID(pDoc)。如果这个断言失败很可能是因为GetDocument()返回了NULL。这通常发生在视图尚未与文档关联时或者文档对象已被删除。一种可能的原因是你在处理某个消息时比如定时器消息错误地直接调用了Invalidate()导致在非法的上下文中触发了重绘。解决方法是确保只在视图与文档有效关联后才进行绘制操作或者在使用前增加判空逻辑if (pDoc ! nullptr) { ... }。问题三资源ID冲突或资源文件加载失败。MFC严重依赖资源对话框、菜单、字符串、图标等。资源ID定义在resource.h中。如果手动添加资源ID导致重复或者资源文件.rc损坏会引起各种奇怪问题。现象菜单显示乱码、对话框弹不出来、程序启动失败。排查与解决使用资源编辑器尽可能通过VS的资源视图和图形化编辑器来添加、修改资源让IDE自动管理ID。检查resource.h如果手动编辑确保#define的ID值唯一。VS通常会预留一定范围如IDR_MAINFRAME是128你的自定义ID可以从30000开始。清理并重新生成有时资源编译缓存会出错。尝试“生成”-“清理解决方案”然后“重新生成解决方案”。检查资源文件包含在资源视图右键.rc文件 - 查看代码检查#include语句是否正确特别是resource.h的路径。问题四Unicode与多字节字符集ANSI问题。MFC早期使用多字节字符集MBCS后来全面转向UnicodeUTF-16。这会导致字符串处理相关编译错误或运行时乱码。现象cannot convert parameter 1 from const char [XX] to LPCWSTR。解决方案统一字符集设置项目属性 - 高级 - 字符集设置为“使用Unicode字符集”。这是现代Windows开发的推荐设置。使用_T()或TEXT()宏在定义字符串字面量时使用_T(“Hello”)或TEXT(“Hello”)。这个宏在Unicode配置下会扩展为L”Hello”宽字符串在多字节配置下扩展为”Hello”。使用MFC/ATL字符串类优先使用CString它内部能很好地处理两种字符集。避免直接使用char*或wchar_t*与Windows API交互用CString的GetString()方法获取LPCTSTR。问题五动态创建控件或子窗口失败。有时需要在运行时通过代码创建按钮、编辑框等控件或者创建新的视图/对话框作为子窗口。如果创建失败通常有几个原因。常见原因与排查父窗口句柄无效创建控件时必须传入一个有效的父窗口句柄HWND或指向父窗口的指针。确保父窗口已经创建成功Create函数已调用并返回TRUE。控件ID冲突确保你指定的控件ID在父窗口范围内是唯一的并且没有与资源文件中定义的ID冲突。未调用SubclassWindow或Attach对于MFC控件对象如果你先创建了一个MFC控件类对象如CButton然后调用Windows API的CreateWindow或CreateWindowEx创建了实际的控件窗口你需要将MFC对象与这个窗口句柄关联起来。调用m_myButton.SubclassWindow(hWndButton)或m_myButton.Attach(hWndButton)。反之如果你用CButton::Create方法创建MFC会自动完成关联。消息映射未生效动态创建的控件如果你想在父窗口类中处理它的通知消息如BN_CLICKED必须在父窗口类的消息映射表中使用ON_CONTROL或ON_BN_CLICKED等宏并将控件ID与处理函数绑定。确保控件ID是正确的并且处理函数已声明和实现。调试MFC程序核心还是要善用Visual Studio的调试器设置断点、观察变量、查看调用堆栈、利用“输出”窗口查看TRACE宏的输出这是MFC中常用的调试打印方式如TRACE(_T(“坐标: %d, %d\n”), point.x, point.y);。遇到问题时不要慌按照“看错误信息 - 定位代码 - 分析上下文 - 检查假设条件”的思路大部分问题都能被解决。8. 超越向导手动构建一个迷你MFC程序为了更深刻地理解MFC框架的启动流程我们抛开应用程序向导手动从头构建一个极简的MFC程序。这将让你看清CWinApp、窗口创建和消息循环的本质。创建空项目在Visual Studio中新建一个“空项目”命名为MiniMFC。在“解决方案资源管理器”中右键源文件文件夹 - 添加 - 新建项 - C文件命名为MiniMFC.cpp。设置项目属性右键项目 - 属性。常规- MFC的使用选择“在共享DLL中使用MFC”。链接器- 系统 - 子系统选择“Windows (/SUBSYSTEM:WINDOWS)”。C/C- 预编译头选择“不使用预编译头”。编写代码将以下代码完整复制到MiniMFC.cpp中。#include afxwin.h // 核心MFC头文件 // 1. 派生我们自己的应用程序类 class CMyApp : public CWinApp { public: virtual BOOL InitInstance() override; }; // 2. 派生我们自己的主窗口类 class CMyFrameWnd : public CFrameWnd { public: CMyFrameWnd(); protected: afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP() }; // 3. 实现CMyFrameWnd BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd) ON_WM_PAINT() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() CMyFrameWnd::CMyFrameWnd() { // 调用Create创建窗口参数依次为窗口类名用NULL用默认、窗口标题、窗口样式、初始位置和大小、父窗口、菜单资源IDNULL用默认 Create(NULL, _T(手动创建的MFC窗口)); } void CMyFrameWnd::OnPaint() { CPaintDC dc(this); // 构造CPaintDC对象用于响应WM_PAINT消息 dc.TextOutW(100, 100, _T(你好手动MFC世界)); } void CMyFrameWnd::OnLButtonDown(UINT nFlags, CPoint point) { CClientDC dc(this); CString str; str.Format(_T(点击位置: (%d, %d)), point.x, point.y); dc.TextOutW(point.x, point.y, str); } // 4. 实现CMyApp::InitInstance BOOL CMyApp::InitInstance() { // 创建主窗口对象在堆上分配 CMyFrameWnd* pFrame new CMyFrameWnd(); // 显示并更新窗口 pFrame-ShowWindow(SW_SHOW); pFrame-UpdateWindow(); // 将主窗口指针保存在m_pMainWnd中这样CWinApp::Run()就知道哪个是主窗口 m_pMainWnd pFrame; // 返回TRUE表示初始化成功框架将继续执行 return TRUE; } // 5. 全局应用程序对象这是程序的入口枢纽。 CMyApp theApp;编译并运行按F5编译运行。你会看到一个标题为“手动创建的MFC窗口”的程序里面显示一行文字并且可以点击鼠标显示坐标。让我们一步步拆解这个迷你程序#include afxwin.h这是MFC核心头文件包含了CWinApp,CFrameWnd等基本类的声明。全局对象theApp这是整个程序的起点。操作系统启动程序后C运行时初始化全局和静态对象theApp被构造。MFC框架在内部安排好了执行顺序最终会自动调用theApp.InitInstance()。CMyApp::InitInstance()这是应用程序初始化的核心。我们在这里手动创建了主窗口对象CMyFrameWnd调用ShowWindow和UpdateWindow显示它并将指针赋给m_pMainWnd。返回TRUE后基类CWinApp::Run()被调用启动消息循环。CMyFrameWnd类我们自己的窗口类。在构造函数中调用Create来实际创建Windows窗口。Create的第一个参数是窗口类名传NULL表示使用MFC预注册的默认窗口类。我们定义了OnPaint和OnLButtonDown两个消息处理函数并通过BEGIN_MESSAGE_MAP将它们与对应的Windows消息WM_PAINT,WM_LBUTTONDOWN绑定。消息映射BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏定义了一个静态数据结构MFC在内部用它来查找哪个消息对应哪个处理函数。ON_WM_PAINT()和ON_WM_LBUTTONDOWN()是预定义的消息映射宏。这个不到100行的小程序剥离了文档/视图、资源、命令路由等复杂部分清晰地展示了MFC应用程序最核心的骨架一个全局应用对象、一个主窗口类、以及连接它们的消息映射机制。通过亲手编写它你会对MFC框架的启动和运行原理有更牢固的认识。当你再使用向导生成项目时就能更清晰地理解那些自动生成的代码各自扮演了什么角色。

相关新闻