
简介面向具备C/C基础、希望掌握VC6.0下MFC开发的初学者学习内容围绕清晰的进阶路线展开先夯实语言基础再理解Windows消息机制、窗口句柄与消息映射最后借助MFC封装特性进行Windows程序开发。文档以doc格式呈现共1个文件压缩包约521KB篇幅精炼但覆盖面较完整便于按章节顺序阅读。目前已有728人学习。文档从“如何学好VC”的建议讲起包含多使用Help Online、学会阅读他人代码、慎选参考书等实用技巧随后详细拆解消息的组成、投递与处理流程结合伪代码展示窗口过程与消息循环的写法并对比16位与32位系统的多任务机制同时点明MFC利用C封装、继承、虚函数等特性带来的开发优势可帮助读者避开常见入门陷阱建立对Windows编程核心原理的整体认知。适合自学VC/MFC的初学者作为第一份路线参考。1. 先别被“VC6.0”吓住MFC 入门到底在学什么很多人第一次听到“VC6.0 MFC 入门”这串词心里都在打鼓MFC 是什么为什么要学一个看起来这么老的开发工具我连 C 都没写顺能学会吗我见过不少零基础的同学还没点开教程就先被名字吓住了。今天这篇就专门解决这个发怵的问题不讲复杂原理只讲一条能走通的路。MFC 是微软封装好的一套 C 类库专门用来写 Windows 窗口程序。VC6.0 是当年集成这套类库的开发环境。你学它不是为了追新而是为了搞懂 Windows 程序到底是怎么组织起来的。它能让你用比原始 API 少得多的代码创建窗口、处理鼠标点击、在窗口里画图。但前提是你得按顺序来先补 C/C 基础再理解消息机制然后才轮到 MFC 的类和向导工具。读完你会知道第一步干什么、第一个例子怎么跑也会明白那些老开发为什么反复强调“磨刀不误砍柴工”。现在我们从地基开始一步一步来。2. 为什么都说要先补 C/C把地基打牢再碰窗口2.1 C 和 C 学到什么程度才算“够用”任何房子都得从地基开始。MFC 这栋楼的地基就是 C/C。你跳过地基直接搬进二楼窗是有了但一遇震动就晃。我见过太多人卡在 MFC 的 OnDraw 和消息映射上回头一看全是不懂继承和虚函数导致的。还记得我第一次用 VC6.0 建 MFC 工程时满屏的代码我全不认识连 CWinApp 都看成乱码。后来一个前辈点醒我你连 C 的类继承都没写过看 MFC 当然像看天书。MFC 本身就是建立在 C 之上的你面对的所有窗口、按钮、对话框在 MFC 里都是一个 C 类。不懂封装、继承、多态你只能复制粘贴改一处错一处。入门的话建议先把 C 语言的基础语法过一遍变量、循环、函数、数组、指针。之后重点学 C 的类、构造函数、析构函数、继承、虚函数。很多人在这里犯一个毛病觉得“我又不做大型游戏学多态干嘛”。但 MFC 里最核心的消息处理以及 CView 的绘图机制全靠虚函数和基类指针在工作。你可以暂时不碰 STL、模板这些高阶内容但类的机制必须熟练。这里有一个实际的分寸你不需要变成 C 专家再碰 MFC。C 语言部分的指针、函数指针能读能写就行C 部分的类、构造、析构、继承、虚函数必须能独立写一个简单示例。判断标准很简单给你一个“动物类”和“狗类”你能写出继承关系并用基类指针调用子类重写的“叫”函数就算过关。这个能力直接映射到 MFCCFrameWnd 继承自 CWndCView 继承自 CWnd框架用基类指针管理所有窗口。你不会继承就没法理解为什么一个 CWnd 指针能代表不同的窗口。2.2 新手最好先写一段“字符界面程序”学 MFC 最好的前置练习不是马上拖窗口而是先在黑底白字的控制台程序里写代码。原因很实在控制台程序没有消息循环、没有窗口过程、没有设备上下文你只需要关注 C 语法本身。等你把 C 写顺了再进入 Windows 编程就能分清楚“语言问题”和“框架问题”不会一编译报错就懵。入门的话建议先在 VC6.0 里新建一个 Win32 Console Application也就是控制台工程然后在里面练 C。别觉得这个环境不带窗口很寒酸它反而是你最好的练习场。我第一次学的时候老老实实写了一个“模拟动物园”的控制台程序代码很傻但把继承、虚函数、指针全练了一遍。给你一个最小示例#include iostream using namespace std; class Animal { public: // virtual 是关键它允许子类重写这个方法 virtual void speak() { cout Animal speaks endl; } }; class Dog : public Animal { public: // 重写基类的 speak实现自己的行为 void speak() { cout Dog barks endl; } }; int main() { // 基类指针指向子类对象 Animal* p new Dog(); // 运行时调用的是 Dog 的版本这就是多态 p-speak(); delete p; return 0; }这段代码里最值得注意的就是virtual和Animal* p new Dog()。运行时p-speak()会根据对象真正的类型去调用Dog的版本这叫多态。MFC 里大量使用这种手法你写的 CView 派生类它的 OnDraw 会被框架通过基类指针自动调用。你现在把这段代码编译跑通以后看到 MFC 的类关系就不会慌。提示控制台程序只是练习 C不用把它写成正式项目。练到能独立写出上面的继承示例就可以向前走了。2.3 带着这三个 C 概念去读 MFC如果你不想在 MFC 里翻车请先记住三个词封装、继承、多态。封装MFC 把 Windows 的窗口句柄 HWND 装进了 CWnd 类你不再需要到处传句柄只需要操作一个 CWnd 对象。同理画笔、刷子、字体也各有封装类。这就是为什么 MFC 让开发更接近语言而不是整天和句柄打交道。继承你的主窗口类从 CFrameWnd 继承你的视图类从 CView 继承。继承之后你自动拥有了父类所有的窗口能力只需补充自己需要的部分。比如 CView 已经帮你处理了和文档、框架的协作你要做的只是重写 OnDraw。多态MFC 框架拿着基类指针调用你的重写函数。窗口需要重绘时框架调用 CView 内部的 OnPaintOnPaint 又调用 OnDraw最终通过虚函数机制执行到你写的那份 OnDraw。正因为多态你只需要写一份画图代码窗口显示、打印预览、打印机输出全部复用。到这一步你已经明白为什么所有正经的 MFC 入门资料都把 C/C 基础放在最前面。不是吓唬你而是 MFC 的类体系直接站在 C 这三块基石上面。下一章我会专门讲新手最容易踩的坑你提前看一眼能省掉很多瞎折腾的时间。3. 新手避坑这几个坎十个人有九个栽过很多 MFC 新手学不下去不是因为不努力而是在同一个坑里反复摔倒。这一章我把最常见的四个问题摆出来每个都按“现象 → 原因 → 解决”说清楚。你以后遇到直接对号入座。3.1 一上来就拖控件按钮有了程序还是一团浆糊现象刚学 MFC 就想用 VC6.0 往窗口上拖一个按钮双击按钮写代码弹个消息框是成功了但窗口是怎么创建的、按钮为什么能响应用户点击完全不知道。再遇到复杂界面就只会不停堆控件代码越堆越乱。原因跳过了 C/C 基础也跳过了字符界面程序练习直接进入“可视化开发”。可视化拖控件掩盖了后台的类、消息映射和窗口过程你就把 MFC 学成了“拼积木”。拖控件填不填得了界面填得了。但一旦程序要自己画图、自己处理复杂交互你立刻发现没有任何抓手。解决退回控制台程序先把 C 的类、继承、虚函数练熟同时把 Windows 消息机制的基本流程看懂。MFC 的控件操作只是表象消息处理才是骨架。我第一次学的时候就吃了这个亏拖了一下午按钮最后连一个最简单的按钮为什么能响应点击都说不清楚。后来沉下心补基础一周后再看界面代码一下子通透了。3.2 照着书抄编译却报一堆错现象代码明明和书上一模一样就是编译不过。VC6.0 有时报 error C2065意思是某个标识符没有声明有时报 error C2664意思是参数类型不匹配。很多人第一反应是“书太老”或者“我太笨”其实大多数时候都不是。原因大概率是工程类型选错了。把一段控制台代码贴进 MFC 工程或者把 MFC 代码贴进 Win32 工程头文件、链接库全对不上。另外 VC6.0 时代很多书上的代码用char和char*而 MFC 接口大量使用TCHAR两种写法混用就会出错。解决新建工程时先选对类型。练习 C 就选 Win32 Console Application练习 MFC 就选 MFC AppWizard (exe)。如果用到字符串优先用_T(...)或者 CString不要直接把...扔给 MFC 接口。看到编译错误不要慌双击错误信息跳到对应行先检查头文件和工程类型这两项对了大部分报错都会消失。3.3 消息处理函数写了却始终没有反应现象你在视图类里写了一个void OnLButtonDown(...)里面也写了弹消息框的代码但编译、运行、点鼠标窗口就是没反应。这时候很多人开始怀疑电脑坏了或者 MFC 有问题。原因MFC 不是靠“函数名字对了”就自动调用消息处理函数。它靠消息映射把消息和函数绑在一起。只写了函数定义没有在消息映射宏里加上ON_WM_LBUTTONDOWN这个函数就只是一个普通成员函数不会被消息系统调用。这是新手最常见也最让人抓狂的坑我当年在这个问题上卡了整整一个晚上。解决打开视图类的 .cpp 文件找到 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 中间确认有对应条目BEGIN_MESSAGE_MAP(CDemoView, CView) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP()然后在头文件里声明函数在 cpp 里实现函数。如果你用 VC6.0 的 ClassWizard 添加消息处理它会自动帮你生成这三处所以新手阶段建议尽量用向导等明白了原理再手动写。3.4 画好的图形一拖窗口就消失或者程序直接崩掉现象在 OnDraw 里画了一条线或一个圆窗口一最小化再恢复图就不见了有时程序还弹熟悉报错甚至直接退出。这一类问题在入门绘图时极其常见。原因窗口重绘时会重新调用 OnDraw所有绘制必须放在 OnDraw 里它才会在每次重绘时被重新执行。另一个更隐蔽的问题是创建了画笔、刷子这类 GUI 对象后没有选回旧对象也没有销毁GDI 对象越积越多最后系统资源耗尽。解决绘制代码一律写在 OnDraw 里所有 GDI 对象遵循“创建、选入、用完恢复、销毁”的流程。一个安全写法是这样的void CDemoView::OnDraw(CDC* pDC) { CPen pen; pen.CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); // 创建红色画笔 CPen* pOldPen pDC-SelectObject(pen); // 选入 DC记住旧画笔 pDC-MoveTo(10, 10); pDC-LineTo(100, 100); // 画一条线 pDC-SelectObject(pOldPen); // 恢复旧画笔 // pen 是局部对象函数结束时自动析构 }这段代码里关键是pOldPen。不保存旧对象就直接选入新对象后面恢复时就会出错不恢复旧对象就让局部画笔析构DC 里的指针就变成野指针。新手阶段先把这句“选入、保存旧对象、恢复”背下来能避免很多崩溃。4. 绕不开的核心Windows 消息机制到底怎么运转MFC 初学者最容易把代码复制成功却不知道程序为什么“活”着。这里最核心的就是消息机制。你可以把 Windows 程序理解成一家前台接待处所有事情都通过消息单传递系统负责把消息单送到正确的窗口窗口自己决定要不要处理。4.1 先认识消息本身名字、wParam、lParam一条消息由三部分组成消息名称UINT、wParam、lParam。消息名称告诉你“发生了什么”。WM_COMMAND 表示用户点了菜单或按钮WM_PAINT 表示窗口需要重画WM_LBUTTONDOWN 表示鼠标左键按下了。wParam 和 lParam 是附加参数具体含义随消息不同而不同。比如菜单点击时wParam 的高字节里是命令 ID通过这个 ID 你能知道用户点了哪一个菜单。这里不用死记参数先知道思路就行。MFC 已经把这些参数拆开封装成一个个处理函数比如 OnLButtonDown 会直接给你鼠标坐标。但如果你完全不懂原始消息结构遇到某些疑难 bug 时就只能瞎猜。所以我的建议是先理解“消息有名字和两个附带参数”等用 MFC 写几个例子后再回头对照 API会有种醍醐灌顶的感觉。4.2 窗口过程谁收到消息没处理的消息去了哪消息必须发给一个窗口。窗口靠窗口句柄 HWND 唯一标识。你可以把句柄想象成每个窗口的学生证号系统发消息时带上这个号码消息就不会送错门。比如两个窗口共用同一个窗口过程代码你在窗口一上按鼠标消息只会送给窗口一因为消息里带了窗口一的句柄。每个窗口都有一个窗口过程也就是一段处理消息的代码。它收到消息后先用 switch 判断消息类型自己感兴趣的就去处理不感兴趣的交给系统默认处理。比如窗口被拖动时会来一大批消息你根本不用管默认过程会帮你处理好。下面这个伪代码能让你看清窗口过程的骨架LRESULT YourWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: DoYourWindow(); // 窗口需要重绘时在这里输出内容 break; case WM_LBUTTONDOWN: DoYourWork(); // 鼠标左键按下在这里处理 break; default: return DefWindowProc(hWnd, msg, wParam, lParam); // 其它交给系统 } return 0; }MFC 把你写的 OnDraw、OnLButtonDown 这些函数通过消息映射挂到了类似上面这个 switch 上。你写的只是 case 分支里的处理代码switch 和窗口过程是 MFC 帮你搭好的。所以理解了消息机制你才不会觉得 MFC 是个黑匣子。4.3 消息循环和消息队列程序为什么不卡死系统会为每个程序维护消息队列。鼠标点击、键盘输入、窗口变化系统把这些事件转成消息放进对应程序的消息队列。程序里有一条消息循环它不断地问系统“有没有新消息”有就取出来发给对应窗口过程没有就把 CPU 控制权交回去让别的程序运行。下面的伪代码就是一个标准消息循环while (1) { BOOL bRet GetMessage(msg, NULL, 0, 0); // 没有消息就阻塞等待 if (bRet 0) break; // 收到 WM_QUIT退出循环 TranslateMessage(msg); // 把键盘消息转成字符消息 DispatchMessage(msg); // 把消息交给对应窗口过程 }这段代码最好的地方在于没有消息时 GetMessage 不会返回程序不会白白占着 CPU。在老的 Windows 3.x 时代所有程序共享一个队列一个程序卡住整个图形界面都可能卡住。到了 Windows 95/NT 之后每个程序都有独立队列所以一个程序卡住不易拖垮其它程序。MFC 的工程里启动代码早就把消息循环藏好了。你不需要自己写但要明白它存在。以后你写一个耗时的循环如果发现窗口拖不动、按钮没反应多半就是你的代码挡住了消息循环系统没法处理窗口重绘和鼠标点击。4.4 MFC 的消息映射把消息和函数绑在一起最初写 Windows 程序每个窗口都要写一个巨大的 switch把所有消息都列出来代码又长又乱。MFC 用了一组宏把消息和成员函数映射起来看起来很像在填表格。你在类里声明一个 OnLButtonDown 函数再在 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间加一行 ON_WM_LBUTTONDOWN鼠标消息就会自动路由到这个函数。MFC 中常见的一段消息映射是这种样子BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() // 窗口创建时调用 OnCreate ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) // 菜单命令绑定 DoNothing END_MESSAGE_MAP()它相当于告诉框架遇到 WM_CREATE就调用 OnCreate遇到 ID 为 ID_FONT_DROPDOWN 的命令就调用 DoNothing。这背后的实现原理比较复杂但你可以简单理解成宏定义帮你生成了一张任务表MFC 框架查表找到对应的函数指针然后调用。这也是为什么第 3 章强调“别漏了消息映射”。你写代码时漏一行 ON_XXX表格里没有这一项函数写得再对也没有入口。MFC 入门能把这条线理清楚后面看什么类都不虚。5. 动手跑第一个例子在窗口里输出“Hello MFC”前面讲了基础和原理这一章练真家伙。不要怕我们只做一个最小的事创建一个窗口然后在窗口里用 MFC 输出一行文字。整个过程不超过几十行自己写的代码你跟着做完就会拥有自己的第一个 MFC 程序。5.1 用向导生成一个单文档程序打开 VC6.0新建工程在“Projects”选项卡里选 MFC AppWizard (exe)。工程名你随便起比如 demo。向导会问你框架类型有三个选项单文档、多文档、对话框。第一次做我建议选“单文档”Single document。理由有两条第一单文档自动带一个 CView 视图类正好用来学习 OnDraw 绘图第二对话框程序虽然看起来更简单但它把很多消息处理隐藏得更深初学者反而不容易看到核心。一路点“完成”用默认设置就行。等向导结束代码就已经自动生成好了。你什么都不用改直接编译运行屏幕上会出现一个带菜单和工具栏的标准 Windows 窗口。到这里你其实已经用 MFC 写出一个能跑的窗口程序了。5.2 先认识自动生成的几个关键类打开 ClassView你会发现 MFC 帮你建了好几个类。对入门来说先认识三个就够。第一个是 CWinApp 的派生类。它代表“应用程序本身”负责启动、初始化、运行消息循环。程序入口不是 main而是这个 App 类。第二个是 CFrameWnd 的派生类它是主窗口也就是那个带标题栏、菜单、工具栏的大边框。第三个是 CView 的派生类它填在主窗口的客户区里你看到的白色区域就是它你要画的文字和图形全部写在这个类里。对初学者我只要求你记住MainFrame 管窗口框架View 类管内容App 类管整个程序运行。以后你写的 90% 绘图代码都会出现在 View 类里。看别人代码时先找这三个类在哪就不容易迷路。5.3 在 OnDraw 里输出一行文字现在打开 View 类的源代码找到 CView 派生类的 OnDraw 成员函数。它目前是空的或者只有一行 TODO 注释。把下面这段代码填进去void CDemoView::OnDraw(CDC* pDC) { // pDC 是当前窗口的设备上下文所有绘图都要通过它 // TextOut 在指定坐标画出一行文字 pDC-TextOut(100, 100, _T(Hello MFC)); }编译运行你会在白色客户区的坐标 (100, 100) 位置看到一行 Hello MFC。也许你觉得不过如此但这背后发生了好几件事第一窗口被创建后系统向它发送 WM_PAINT 消息第二MFC 框架收到 WM_PAINT 后调用 OnPaintOnPaint 又调用 OnDraw第三你的 TextOut 通过 pDC 把文字画到了屏幕上。这就是前面说的消息机制的完整闭环。TextOut的前两个参数是坐标单位默认是像素。x 往右增大y 往下增大。第三个参数是字符串前面加_T是为了同时兼容 char 和宽字符。你改一下坐标再运行会发现文字位置真的变了。这就是在 Windows 窗口里“画”字的感觉。5.4 再加一个鼠标响应亲手体验消息映射文字输出是窗口主动画的接下来加一个“被动响应”的例子鼠标左键点击窗口空白处弹出一个消息框。这样才能看到消息映射的全过程。在视图类头文件里手动加上消息处理函数声明然后到 cpp 的 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间加一行再在 cpp 里写函数体。三步代码如下// 头文件里类的声明内部加这句 afx_msg void OnLButtonDown(UINT nFlags, CPoint point);// cpp 消息映射区添加这一行 BEGIN_MESSAGE_MAP(CDemoView, CView) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP()// cpp 里实现函数 void CDemoView::OnLButtonDown(UINT nFlags, CPoint point) { CString str; str.Format(_T(你点击了坐标(%d, %d)), point.x, point.y); AfxMessageBox(str); // 弹出一个消息框 }这段代码干了什么字符串用 Format 按格式拼出坐标然后 AfxMessageBox 弹出提示。整个流程是消息映射把 WM_LBUTTONDOWN 路由到 OnLButtonDown你不需要知道底层窗口过程怎么分派框架帮你做了。现在把鼠标点进窗口消息框会告诉你点击的位置。你可以改一下坐标让输出的文字出现在鼠标点击的位置思路是把点击坐标存进一个成员变量再调用 Invalidate() 强制窗口重绘重绘时 OnDraw 就会用这个新坐标画文字。这一步留作练习它能把消息处理、成员变量、重绘机制串起来。做到这里你已经有能力看懂 MFC 自动生成的代码了也验证了消息映射不是玄学。6. 接下来怎么走一条照着做就好的学习路径到这里你已经跑通了第一个 MFC 程序也理解了消息机制。下一步不是急着啃全部源码而是按顺序把常用类练熟再做一个小工具。6.1 先掌握三个高频类MFC 里最常用的是 CString、CRect、CPoint。CString 是可变长字符串用起来比 char 数组省心它会自己分配内存常见的 Format、Find、Compare 都值得试一遍。CRect 表示矩形有 top、left、bottom、right 四个成员Width、Height、CenterPoint 都很常用。CPoint 就是坐标点配合鼠标消息里的 point 参数用。这三个类没有必要背写代码时多用几次就熟了。建议把刚才例子里 TextOut 的坐标改成 CPoint 对象来算把文字范围用 CRect 限制再用 CString 做格式化输出。用到不会的函数直接查 MSDN 或 VC6.0 自带的联机帮助不要翻厚厚的书。6.2 从文字走向绘图和 GUI 对象接下来学 CFont字体、CPen画笔、CBrush刷子、CBitmap位图。输出文字时你可以创建一种新字体再把字体选入 DC改变文字大小和风格。画线用画笔填充区域用刷子。建议用它们做一个小程序比如在窗口里画一个红边框、蓝色填充的圆把前面说的“选入、恢复、销毁”流程真真切切用一遍。GUI 对象使用有一套固定规则先创建再选入 DC用完恢复旧对象重新创建前先销毁。这套规则我前面提过等你真正画复杂图形时才能体会它有多关键。很多人都栽在忘记恢复旧对象这个点上一次两次炸出非法操作才知道血泪教训是什么。6.3 长期坚持的三个习惯第一遇到消息不会处理时先查 MSDN 里的消息列表再用 ClassWizard 自动生成处理函数。第二读别人的代码时先画一张“类关系 消息流向”的草图再往下看。第三每个知识点都做一个小程序验证别光看不练。如果你手里正好有这份《VC6.0 MFC入门学习》资料我建议把它当成一张地图而不是百科全书。第一次读到这里只需要把消息机制和第一个例子理解透后面的绘图章节等你做到那个红边框蓝色填充的小圆时再回头看。作为一个过来人我从那以后每次学新框架都强制自己先画一遍消息流转图再动手改代码。这个习惯让我少踩了无数坑无论是写 MFC 还是后来接触别的界面库第一件事永远是先搞清楚“消息从哪来、由谁处理、没处理去哪”。希望帮到你。本文还有配套的精品资源点击获取