简介:本资源是《精通MFC》配套光盘源代码,面向Windows桌面开发初学者与中级C++程序员,系统解决MFC框架下窗口编程、消息机制、文档/视图架构、GDI/GDI+绘图、多线程、DLL及COM组件等核心难点。压缩包含1114个文件,总计8.01MB,以279个头文件(.h)和229个实现文件(.cpp)构成完整项目骨架,辅以56个Visual Studio工程文件(.vcproj)、50个资源脚本(.rc)、42个清单文件(.manifest)及大量图标(.ico)、位图(.bmp)和HTML界面资源(.htm/.html),全面支撑书中16章全部示例工程的编译与调试。内容覆盖从面向对象基础、MFC类体系(CObject、CWinApp、CDHtmlView等)到.NET托管扩展的演进路径,目录结构严格对应教材章节,便于边学边练、逐章验证。目前已有516人学习下载,是深入理解MFC底层机制与企业级Windows应用开发实践的高质量配套代码资源。
1. 这不是一本“过时”的MFC书:它是一套能直接编译、调试、修改的完整工程集合,专治“学完MFC却写不出可运行对话框”的玄学困境
你有没有试过翻完一本厚达六百页的《精通MFC》,合上书却发现——连最基础的“点击按钮弹出编辑框内容”都卡在资源ID找不到、消息映射不触发、DoDataExchange崩溃这三座大山?这不是你手生,是绝大多数MFC学习者的真实血泪现场。这套名为《精通MFC(光盘)源代码》的资源,根本不是PDF附录里几行示意代码,而是一整套按章节组织、带完整项目文件(.dsp/.dsw 或 .vcproj)、含全部资源脚本(.rc)、图标位图(.ico/.bmp)、甚至调试用的测试数据的可执行工程包。它覆盖从CWinApp初始化、CDialog生命周期、CEdit/CListCtrl控件深度定制,到多线程UI安全更新、自绘OwnerDraw控件、DLL插件式扩展等真实开发场景。适合两类人:一是刚啃完MFC理论、急需一个“能跑通→能打断点→能改逻辑→能加功能”的锚点项目的新手;二是需要快速复现某个经典交互模式(比如带进度条的文件批量处理对话框)的老手——它不讲原理,但每行代码都在告诉你“这里必须这么写,否则VC6/VS2008会当场报错”。
2. 环境准备与工程结构解剖:从VC6到VS2019的兼容性落地实操
2.1 开发环境选择:为什么我坚持用VC6 SP6 + Platform SDK 2003做基准验证
这套源码诞生于MFC 6.0(对应VC6)时代,其资源脚本语法、宏定义(如AFX_MANAGE_STATE)、甚至#pragma comment(lib, "comctl32.lib")的写法,都深度绑定VC6的链接器行为。直接用VS2019打开会触发大量“无法识别资源ID”“CDialog::OnInitDialog未重载”等误报。我的做法是:先用VC6 SP6 + 最新Platform SDK 2003编译通过,再逐个迁移至VS2019。关键点在于:VC6中#include <afxwin.h>隐式包含<windows.h>,而VS2019要求显式包含;VC6的_T("xxx")在Unicode工程中需改为L"xxx"。我一般会在VC6下确认所有对话框能正常弹出、按钮响应无延迟、列表控件滚动流畅后,再导出为VS2019工程。
2.2 工程目录结构还原:看清每个文件的真实职责
原始光盘解压后典型结构如下(以Chapter05_ListCtrl为例):
Chapter05_ListCtrl/ ├── Chapter05_ListCtrl.dsp ← VC6工程文件,定义编译选项、依赖库 ├── Chapter05_ListCtrl.dsw ← 工作区文件,含多个子工程 ├── Chapter05_ListCtrl.cpp ← CWinApp派生类,含InitInstance() ├── MainFrm.cpp ← CFrameWnd主框架,含菜单/工具栏注册 ├── ChildView.cpp ← 视图类,若为SDI则此处处理绘图 ├── ListCtrlDemoDlg.cpp ← 核心对话框类,含OnBnClickedButton1()等消息处理 ├── Resource.h ← 所有ID定义:IDC_LIST1, IDC_BUTTON_ADD等 ├── Chapter05_ListCtrl.rc ← 资源脚本:定义对话框布局、字符串表、图标 ├── res/ │ ├── Chapter05_ListCtrl.ico ← 应用图标 │ └── toolbar.bmp ← 工具栏位图(256色,非PNG)提示:
.rc文件是MFC的“黑匣子”入口。双击打开后,你会看到DIALOGEX IDD_LISTCTRL_DEMO_DIALOG块,其中CONTROL "", IDC_LIST1, "SysListView32", ...这行直接决定了CListCtrl控件的窗口类名和ID——任何C++代码里使用的IDC_LIST1,必须与此处完全一致,大小写都不能错。
2.3 关键编译参数配置:解决“LNK2001未解析外部符号”的根源
VC6默认不启用Unicode,而部分示例(如含中文路径处理)需手动开启。操作路径:Project → Settings → General → Character Set → Use Multi-Byte Character Set。若需Unicode,则改为Use Unicode Character Set,并同步修改:
#include <tchar.h>替代<stdio.h>AfxMessageBox(_T("成功"));替代AfxMessageBox("成功");CString str = _T("文本");
更隐蔽的坑是comctl32.lib版本:VC6默认链接comctl32.lib(v5.82),但XP风格控件需v6+。解决方案是在Chapter05_ListCtrl.cpp的InitInstance()开头插入:
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")否则即使代码正确,列表控件也显示为灰色古董样式。
3. 核心模块拆解:CListCtrl自绘与数据绑定的完整链路
3.1 消息映射机制:从WM_NOTIFY到NM_CLICK的穿透式追踪
MFC的ON_NOTIFY宏是理解控件交互的关键。以列表控件单击选中行为为例,在ListCtrlDemoDlg.h中声明:
// ListCtrlDemoDlg.h class CListCtrlDemoDlg : public CDialog { // ... afx_msg void OnNMDblclkList1(NMHDR *pNMHDR, LRESULT *pResult); // 双击事件 DECLARE_MESSAGE_MAP() };对应ListCtrlDemoDlg.cpp中的映射:
// ListCtrlDemoDlg.cpp BEGIN_MESSAGE_MAP(CListCtrlDemoDlg, CDialog) ON_NOTIFY(NM_DBLCLK, IDC_LIST1, &CListCtrlDemoDlg::OnNMDblclkList1) END_MESSAGE_MAP()注意:
ON_NOTIFY第一个参数是通知码(NM_DBLCLK),第二个是控件ID(IDC_LIST1),第三个是函数指针。若此处ID写错,或函数签名不符(如漏掉LRESULT* pResult),VC6编译不报错但运行时点击无响应——这是新手最常翻车的点。
3.2 自绘OwnerDraw实现:让列表项显示图标+进度条的硬核步骤
原书Chapter07演示了如何让CListCtrl每行显示状态图标和动态进度条。核心在于三步:
- 设置控件风格:在对话框
OnInitDialog()中调用m_listCtrl.ModifyStyle(0, LVS_OWNERDRAWFIXED); // 必须!否则OnDrawItem不触发 - 重载
OnDrawItem:处理每一行的绘制逻辑void CListCtrlDemoDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if (nIDCtl == IDC_LIST1) { CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); CRect rect = lpDrawItemStruct->rcItem; int nItem = lpDrawItemStruct->itemID; // 绘制背景(选中态高亮) if (lpDrawItemStruct->itemState & ODS_SELECTED) { pDC->FillSolidRect(&rect, RGB(0, 102, 204)); // 蓝色选中背景 } else { pDC->FillSolidRect(&rect, RGB(255, 255, 255)); // 白色背景 } // 获取该行数据(假设m_dataVec存储CMyData对象) if (nItem >= 0 && nItem < m_dataVec.size()) { CMyData& data = m_dataVec[nItem]; // 绘制图标(使用ImageList) m_imageList.Draw(pDC, data.m_nIconIndex, CPoint(rect.left + 5, rect.top + 2), ILD_TRANSPARENT); // 绘制进度条(模拟) CRect progressRect(rect.left + 30, rect.top + 5, rect.left + 30 + data.m_nProgress * 2, rect.top + 15); pDC->FillSolidRect(&progressRect, RGB(50, 205, 50)); } } } - 关联ImageList:在
OnInitDialog()中加载图标资源m_imageList.Create(IDB_ICON_LIST, 16, 1, RGB(255,0,255)); // 16x16图标,透明色品红 m_listCtrl.SetImageList(&m_imageList, LVSIL_SMALL);
关键参数说明:
IDB_ICON_LIST是资源脚本中定义的位图ID;16是图标宽度;1表示位图中图标列数(单列);RGB(255,0,255)是透明色,位图中该色区域将透出背景。
3.3 数据绑定与内存管理:避免CListCtrl闪烁与崩溃的实践
MFC的CListCtrl不自动管理数据内存,所有InsertItem传入的字符串指针必须长期有效。常见错误是:
// ❌ 危险!局部变量地址在函数返回后失效 void BadExample() { CString str = _T("临时字符串"); m_listCtrl.InsertItem(0, str); // 此处str可能被析构 }正确做法是使用LVITEM结构体显式管理:
// ✅ 安全:字符串存入控件内部缓冲区 LVITEM lvi; lvi.mask = LVIF_TEXT | LVIF_IMAGE; lvi.iItem = 0; lvi.iSubItem = 0; lvi.pszText = const_cast<LPTSTR>((LPCTSTR)_T("持久化文本")); // MFC内部复制 lvi.iImage = 0; m_listCtrl.InsertItem(&lvi);对于复杂数据(如结构体指针),应使用SetItemData():
struct CMyData { int id; CString name; double progress; }; CMyData* pData = new CMyData{1, _T("任务A"), 0.75}; m_listCtrl.SetItemData(0, (DWORD_PTR)pData); // 存储指针 // 使用时:CMyData* p = (CMyData*)m_listCtrl.GetItemData(nItem); // 记得在对话框销毁时遍历释放:delete (CMyData*)m_listCtrl.GetItemData(i);4. 常见问题排查:那些让你怀疑人生却只需改一行代码的坑
4.1 现象:对话框弹出后立即崩溃,调用堆栈指向CDialog::DoModal()
原因:DoModal()内部调用CreateDialogIndirect()时,资源脚本(.rc)中对话框模板的STYLE属性缺失WS_VISIBLE,或EXSTYLE未设WS_EX_CONTROLPARENT。VC6默认生成的对话框模板有时会漏掉这些标志。
解决:用记事本打开.rc文件,找到对应对话框块(如IDD_LISTCTRL_DEMO_DIALOG DIALOGEX),确保首行包含:
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW | WS_EX_CONTROLPARENT注意:
WS_VISIBLE是关键,缺了它对话框创建后不可见,DoModal()认为初始化失败而抛异常。
4.2 现象:列表控件显示空白,GetItemCount()返回0,但InsertItem()返回值正常
原因:CListCtrl未正确关联到资源ID。常见于两种情况:
- 在
DoDataExchange()中漏写DDX_Control(pDX, IDC_LIST1, m_listCtrl); .rc文件中控件ID写为IDC_LIST_1(带下划线),而C++代码用IDC_LIST1(无下划线)
解决:
- 检查
ListCtrlDemoDlg.cpp的DoDataExchange()函数,确认存在且ID匹配; - 在VC6资源编辑器中右键列表控件 → Properties → General → ID,与代码中
#define IDC_LIST1 1001比对是否一致。
4.3 现象:编译通过,但按钮点击无响应,OnBnClickedButton1()断点永不命中
原因:消息映射宏ON_BN_CLICKED未正确添加,或函数声明/定义不匹配。VC6对函数签名极其敏感。
解决:
- 在头文件中声明必须为:
afx_msg void OnBnClickedButton1();(无参数) - 在CPP中定义必须为:
void CListCtrlDemoDlg::OnBnClickedButton1()(无参数,无返回值) - 在
BEGIN_MESSAGE_MAP中必须为:ON_BN_CLICKED(IDC_BUTTON1, &CListCtrlDemoDlg::OnBnClickedButton1)
血泪经验:曾因在声明中多写了一个
void(afx_msg void void OnBnClickedButton1();),VC6静默忽略该映射,调试器完全不提示。
4.4 现象:Unicode工程下中文乱码,AfxMessageBox(_T("你好"))显示方块
原因:VC6默认字体不支持Unicode,且_T()宏在Unicode模式下展开为L"你好",但对话框默认字体(如MS Sans Serif)无中文字形。
解决:在对话框OnInitDialog()末尾强制设置字体:
CFont* pFont = GetFont(); LOGFONT lf; pFont->GetLogFont(&lf); _tcscpy_s(lf.lfFaceName, _T("Microsoft YaHei")); // 设置微软雅黑 CFont newFont; newFont.CreateFontIndirect(&lf); m_listCtrl.SetFont(&newFont); // 对列表控件生效 // 同理设置其他控件:m_editCtrl.SetFont(&newFont);5. 二次开发实战:给Chapter09的多线程文件扫描器增加实时进度反馈
5.1 原始逻辑分析:Chapter09_ThreadScanner的瓶颈在哪
该示例实现了一个后台线程扫描指定目录下所有.txt文件,并将文件名插入列表控件。但原代码采用“线程结束→一次性刷新列表”的模式,用户无法感知进度。其核心问题是:工作线程与UI线程直接操作同一CListCtrl,违反MFC线程安全规则(只有创建控件的线程能调用其方法)。原代码用PostMessage发送自定义消息,但未处理消息队列积压导致的卡顿。
5.2 改造方案:用PostMessage+WM_USER+100实现零延迟反馈
第一步:定义自定义消息(在Resource.h中添加)
#define WM_UPDATE_PROGRESS (WM_USER + 100) // 消息参数:wParam=当前扫描序号,lParam=文件路径指针(需全局缓存)第二步:在线程函数中分段发送进度
// ScannerThread.cpp UINT ScanThreadProc(LPVOID pParam) { CListCtrlDemoDlg* pDlg = (CListCtrlDemoDlg*)pParam; std::vector<CString> files = GetFileList(_T("C:\\Test")); for (int i = 0; i < files.size(); i++) { // 模拟耗时扫描 Sleep(10); // 发送进度消息(注意:CString不能直接传lParam,需转存) static CString s_lastPath; // 全局缓存,避免线程间CString析构冲突 s_lastPath = files[i]; pDlg->PostMessage(WM_UPDATE_PROGRESS, (WPARAM)i, (LPARAM)(LPCTSTR)s_lastPath); } return 0; }第三步:在对话框中处理消息并安全更新UI
// ListCtrlDemoDlg.cpp LRESULT CListCtrlDemoDlg::OnUpdateProgress(WPARAM wParam, LPARAM lParam) { int nIndex = (int)wParam; LPCTSTR lpszPath = (LPCTSTR)lParam; // 安全插入:MFC保证OnUpdateProgress在UI线程执行 int nItem = m_listCtrl.InsertItem(nIndex, lpszPath); m_listCtrl.SetItemText(nItem, 1, _T("扫描中...")); // 第二列为状态 // 更新进度条(假设有IDC_PROGRESS1) m_progressCtrl.SetPos(nIndex * 100 / m_totalFiles); return 0; } // 在BEGIN_MESSAGE_MAP中添加 ON_MESSAGE(WM_UPDATE_PROGRESS, &CListCtrlDemoDlg::OnUpdateProgress)关键设计点:
PostMessage异步发送,不阻塞工作线程;OnUpdateProgress在UI线程序列化执行,避免CListCtrl并发访问;用静态CString缓存路径,规避跨线程CString生命周期问题。
5.3 验证与压力测试:如何证明改造有效
- 断点验证:在
OnUpdateProgress第一行设断点,启动扫描后观察是否每10ms触发一次(对应Sleep(10)); - UI响应测试:拖动对话框边缘,确认列表控件持续刷新不卡顿;
- 内存泄漏检查:用
_CrtDumpMemoryLeaks()在程序退出时输出,确认无CString相关泄漏(因我们用了静态缓存而非new分配)。
从那以后我每次改造MFC多线程UI,都强制走一遍“PostMessage消息定义→线程安全数据传递→UI线程单次处理”三步验证,宁可多写十行代码,也不碰SendMessage跨线程调用控件方法。希望帮到你。
本文还有配套的精品资源,点击获取