简介这份VC6.0 MFC入门学习资料面向具备一定C/C基础、希望系统掌握Windows应用开发的初学者。内容围绕如何学好VC展开强调扎实的C面向对象、模板与异常处理能力建议先通过字符界面程序训练编程思维再深入理解Windows消息机制、窗口句柄与GUI句柄的用途并重点讲解MFC消息映射的作用与实现方式。资料还给出使用Help Online替代参考书、熟记常用消息名称与参数、阅读他人代码等实用学习策略并附有选择参考书的原则帮助读者循序渐进地理解消息队列、窗口过程与消息循环等核心概念。资源包为1个doc文档大小521KB结构紧凑适合作为入门阶段的系统阅读材料。目前已有727人学习可作为MFC初学者的入门参考。1. VC6.0 MFC 入门学习为什么今天还有人要啃这块硬骨头接手一台老设备的工控机系统是 Windows XP上面跑着一个用 VC6.0 编译的 MFC 程序源码还在但原开发者早已离职。老板说“加个按钮弹个对话框把实时数据画成曲线。”你打开 .dsw 工程文件发现 IDE 是 1998 年的界面连智能提示都时灵时不灵。这不是段子是我上个月的真实经历。VC6.0 MFC 入门学习这件事在今天看来像是考古但对维护老旧工业软件、军工配套系统、医疗设备上位机的工程师来说它是绕不开的必修课。MFC 教程网上不少但多数要么是 20 年前的扫描版教材要么直接跳到 VS2019 的 MFC 向导中间那段“怎么在 VC6.0 里把对话框和状态栏玩明白”的空白恰恰是新手翻车最多的地方。这篇文章不跟你扯 MFC 的宏大叙事只讲一件事在 VC6.0 这个老平台上从零把 MFC 的对话框、状态栏、按钮响应和实时数据图表跑通顺便聊聊桌面软件开发用 MFC 还是 Qt 这个老生常谈的问题在维护场景下到底怎么选。2. VC6.0 的 MFC 工程骨架从 .dsw 到消息映射2.1 为什么老项目还在用 VC6.0 和 MFC先把这个玄学问题说清楚。不是老工程师怀旧而是迁移成本高得吓人。一套跑了十几年的 MFC 程序底层可能绑着特定的硬件驱动、串口通信库、甚至内嵌汇编。你把它迁到 VS2022光是 MFC 库的版本差异就能让界面错位、消息循环异常。更别提 VC6.0 默认用的是 MBCS 字符集而新版 VS 默认 Unicode字符串处理稍不注意就是乱码。所以维护场景下原地修改是性价比最高的选择。MFC 本身是微软对 Win32 API 的一层 C 封装它把窗口、消息循环、GDI 绘图这些底层操作包装成类。VC6.0 自带的 MFC 版本是 4.2虽然老但胜在稳定生成的代码量小编译速度快。对于只需要对话框和简单绘图的工控上位机它完全够用。2.2 用 MFC 向导生成第一个对话框工程打开 VC6.0File → New → Projects → MFC AppWizard (exe)。工程名填DemoMFC路径选一个没有中文和空格的目录。点 OK 后向导第一步选Dialog based这是最常用的模式适合做工具类软件。后面几步保持默认点 Finish。向导会生成三个核心文件DemoMFC.cpp是应用类DemoMFCDlg.cpp是主对话框类StdAfx.h是预编译头。生成的对话框上默认有 OK 和 Cancel 两个按钮直接编译运行你能看到一个空白窗口。这一步的关键是理解 MFC 的文档-视图结构在这里被简化了Dialog based 模式没有文档和视图所有逻辑都堆在CDemoMFCDlg类里。对于小型工具这种结构反而清晰。2.3 消息映射MFC 最核心的机制MFC 不像 Qt 那样用信号槽它用的是消息映射表。每个能响应消息的类在头文件里有一组afx_msg开头的函数声明在 cpp 文件里有BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的宏。比如按钮点击你双击对话框上的按钮VC6.0 会自动生成OnButton1()函数并在消息映射表里加上ON_BN_CLICKED(IDC_BUTTON1, OnButton1)。这行宏的意思是当 ID 为 IDC_BUTTON1 的按钮被点击时调用 OnButton1 函数。理解这个机制你才能手动添加自定义消息。比如你想让子线程通知主线程更新界面就得自定义一个消息 ID然后在消息映射表里关联处理函数。常见做法是#define WM_UPDATE_DATA WM_USER100然后在对话框类里声明afx_msg LRESULT OnUpdateData(WPARAM wParam, LPARAM lParam);映射表里写ON_MESSAGE(WM_UPDATE_DATA, OnUpdateData)。这样线程里用PostMessage发消息主线程就能安全地刷新控件。3. 在 VC6.0 里给 MFC 对话框加按钮并弹出实时数据图表3.1 添加按钮和对话框资源在资源视图里展开Dialog双击IDD_DEMOMFC_DIALOG。从控件工具箱拖一个 Button 到对话框上右键属性把 Caption 改成“显示图表”ID 改成IDC_BTN_SHOW_CHART。再拖一个 Static TextID 改成IDC_STATIC_STATUSCaption 清空用来显示状态信息。双击按钮VC6.0 会自动跳到OnBtnShowChart()函数。如果你需要弹出一个新的对话框来显示图表还得插入一个 Dialog 资源右键 Dialog → Insert Dialog新对话框的 ID 设为IDD_DLG_CHART在上面放一个 Picture ControlID 为IDC_CHART_AREAType 选 Rectangle用来作为绘图区域。然后双击新对话框创建一个新类CChartDlg基类选CDialog。这样你就有了两个对话框类主对话框负责控制图表对话框负责显示。3.2 用 Picture Control 和 CDC 画实时曲线MFC 里绘图的核心是CDC类。在CChartDlg里你需要为 Picture Control 绑定一个CStatic变量比如m_chartArea。然后在定时器里刷新数据并重绘。下面是一个最小可运行的绘图代码片段放在CChartDlg::OnTimer里// 假设已经定义了成员变量int m_nData[100]; int m_nCount; void CChartDlg::OnTimer(UINT nIDEvent) { if (nIDEvent 1) { // 模拟产生新数据实际项目中从串口或网络读取 for (int i 0; i 99; i) m_nData[i] m_nData[i 1]; m_nData[99] rand() % 100; // 随机数代替真实数据 // 获取 Picture Control 的矩形区域 CRect rect; GetDlgItem(IDC_CHART_AREA)-GetWindowRect(rect); ScreenToClient(rect); // 获取设备上下文准备绘图 CDC* pDC GetDlgItem(IDC_CHART_AREA)-GetDC(); pDC-FillSolidRect(rect, RGB(0, 0, 0)); // 黑色背景 // 画坐标轴 CPen penAxis(PS_SOLID, 1, RGB(0, 255, 0)); pDC-SelectObject(penAxis); pDC-MoveTo(rect.left 10, rect.bottom - 10); pDC-LineTo(rect.right - 10, rect.bottom - 10); pDC-MoveTo(rect.left 10, rect.top 10); pDC-LineTo(rect.left 10, rect.bottom - 10); // 画数据曲线 CPen penData(PS_SOLID, 2, RGB(255, 0, 0)); pDC-SelectObject(penData); int xStep (rect.Width() - 20) / 99; for (int i 0; i 99; i) { int x1 rect.left 10 i * xStep; int y1 rect.bottom - 10 - m_nData[i] * (rect.Height() - 20) / 100; int x2 rect.left 10 (i 1) * xStep; int y2 rect.bottom - 10 - m_nData[i 1] * (rect.Height() - 20) / 100; pDC-MoveTo(x1, y1); pDC-LineTo(x2, y2); } ReleaseDC(pDC); } CDialog::OnTimer(nIDEvent); }这段代码的逻辑很直白每次定时器触发把数组左移一位末尾补一个新数据然后清空绘图区画坐标轴再根据数据点连线。参数说明m_nData数组存 100 个采样值范围 0 到 100xStep是每个数据点在 X 轴上的间距Y 轴坐标做了翻转因为屏幕坐标原点在左上角。注意GetDC和ReleaseDC必须成对出现否则资源泄漏。定时器在OnInitDialog里用SetTimer(1, 200, NULL)启动200 毫秒刷新一次对于大多数工控场景够用了。3.3 状态栏显示信息的两种做法MFC 状态栏在 Dialog based 模式里默认没有需要手动加。常见做法有两种。第一种是简单粗暴型在对话框底部放一个 Static TextID 为IDC_STATIC_STATUS需要更新时用SetDlgItemText(IDC_STATIC_STATUS, 正在采集...)。这种做法适合信息量少、不需要分栏的场景。第二种是标准状态栏在对话框类里声明CStatusBar m_wndStatusBar;然后在OnInitDialog里创建。但 Dialog 不是CFrameWnd不能直接Create状态栏。变通做法是创建一个CStatusBar对象手动指定位置和大小或者干脆用一个CStatic模拟。我一般会用一个CStatic放在底部设置SS_SUNKEN样式看起来就像状态栏。如果你非要用真正的CStatusBar得把主窗口改成CFrameWnd派生那就不是 Dialog based 了。对于维护老项目别折腾用 Static Text 最稳。更新状态栏信息时注意不要在子线程里直接调用SetDlgItemText会崩溃。正确做法是子线程PostMessage自定义消息主线程收到后更新。4. VC6.0 MFC 避坑那些让新手崩溃的编译和运行问题4.1 现象编译报错 “fatal error C1010: unexpected end of file while looking for precompiled header directive”原因VC6.0 默认使用预编译头StdAfx.h如果你新建的 cpp 文件没有在开头#include StdAfx.h就会报这个错。解决在每个 cpp 文件的第一行加上#include StdAfx.h。如果是从别处拷贝的代码检查一下有没有漏掉。另一个办法是在项目设置里关掉预编译头但老项目不建议改容易引发连锁问题。4.2 现象对话框上的控件运行时看不见或者位置错乱原因VC6.0 的资源编辑器用的是对话框单位DLU而实际显示时受系统 DPI 和字体影响。如果你在中文 Windows 上开发又在英文 Windows 上运行字体不同会导致控件偏移。解决在OnInitDialog里手动调整控件位置或者统一使用MS Sans Serif字体。更稳妥的做法是在对话框属性里把 Font 改成宋体 9这样在中英文系统下表现一致。如果控件完全看不见检查是不是被另一个控件遮挡或者Visible属性被误设为 false。4.3 现象程序在子线程里更新界面直接崩溃原因MFC 的控件不是线程安全的子线程直接调用SetWindowText或GetDlgItem会导致断言失败或内存访问违规。解决子线程通过PostMessage向主线程发送自定义消息主线程的消息处理函数里再更新控件。不要用SendMessage它会阻塞子线程直到主线程处理完容易死锁。自定义消息用WM_USER 100以上的值避免和系统消息冲突。4.4 现象Release 版本运行正常Debug 版本报 “Debug Assertion Failed”原因通常是数组越界或指针未初始化。MFC 在 Debug 版本里有严格的断言检查Release 版本会忽略。比如m_nData[100]访问了第 101 个元素Debug 下直接弹框。解决仔细检查数组下标和循环边界。另一个常见原因是GetDC后没有ReleaseDC导致 GDI 资源泄漏Debug 下会触发断言。养成成对调用的习惯。4.5 现象状态栏文字刷新闪烁严重原因每次SetDlgItemText都会触发重绘如果刷新频率高闪烁明显。解决用CWnd::SetRedraw(FALSE)暂停重绘更新完再SetRedraw(TRUE)并Invalidate。或者改用CStatic的SetWindowText直接设置减少中间重绘。如果还是闪考虑双缓冲在内存 DC 里画好再BitBlt到屏幕。5. 从 VC6.0 到现代工具链MFC 维护者的进阶习惯5.1 用版本控制管住 .dsw 和 .dsp 文件VC6.0 的工程文件是文本格式的.dsp和.dsw但编码是 ANSIGit 默认按二进制处理。我一般会在项目根目录加一个.gitattributes文件强制把.dsp和.dsw当文本处理这样合并冲突时能看清差异。另外.opt、.ncb、.plg这些文件是 IDE 自动生成的必须加入.gitignore否则每次打开工程都会变提交记录全是噪音。血泪经验有一次同事误提交了.ncb导致整个团队拉取后 VS 智能提示全部失效删掉才恢复。5.2 用 Spy 定位消息和控件句柄VC6.0 自带 Spy 工具在 Tools 菜单里。它可以实时查看窗口的消息流。比如你点击按钮没反应用 Spy 找到按钮的句柄看它有没有发出WM_COMMAND消息。如果消息发了但处理函数没执行说明消息映射表写错了。Spy 还能查看窗口层级帮你确认控件是不是被其他窗口遮挡。这个工具在排查界面问题时比调试器还直接。5.3 桌面软件开发用 MFC 还是 Qt 的决策表维护老项目时这个问题绕不开。我的判断标准很简单看三点现有代码量、团队技能、交付时间。如果现有 MFC 代码超过 5 万行且团队没人写过 Qt强行迁移就是灾难。如果是从零开始的新项目且需要跨平台Qt 明显更合适。下面这张表是我在实际选型时用的维度MFC (VC6.0)Qt学习曲线陡峭资料老旧平缓文档完善跨平台仅 WindowsWindows/Linux/macOS界面美观度依赖自绘默认丑样式表支持好硬件底层访问直接适合工控需要额外封装老项目维护成本低原地修改高需重写社区活跃度低高如果你的场景是维护一台 2005 年出厂的数控机床上位机选 MFC。如果是开发一套新的实验室数据采集软件选 Qt。别跟风看菜下饭。5.4 一个具体技巧用宏批量生成消息映射老项目里经常要加几十个按钮每个都要写消息映射手写容易漏。我习惯用宏来批量生成。比如定义一个MAP_BUTTON(id, func)宏展开成ON_BN_CLICKED(id, func)。然后在消息映射表里连续写MAP_BUTTON(IDC_BTN_1, OnBtn1)这样。虽然 VC6.0 的预处理器比较老但基本宏展开没问题。注意宏定义要放在BEGIN_MESSAGE_MAP之前否则编译报错。这个技巧能省不少时间但别滥用宏太多会让调试变困难。最后说个习惯每次改完老项目我都会在虚拟机里跑一遍完整流程因为 VC6.0 编译出来的程序在不同 Windows 版本上行为可能不一样。XP 上正常的Win7 上可能因为 UAC 权限问题写文件失败。别问我是怎么知道的问就是踩过。希望帮到你。本文还有配套的精品资源点击获取