尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深入掌握MFC控件:从消息映射到自定义绘制的实战指南

深入掌握MFC控件:从消息映射到自定义绘制的实战指南 如果你在 Windows 桌面开发领域摸爬滚打过尤其是用 C 做过一些工具软件、上位机或者内部管理系统那么“MFC”这三个字母对你来说可能意味着一段复杂的感情它古老、稳定是 Windows GUI 开发的基石但它的文档分散、概念庞杂尤其是那些五花八门的界面控件常常让开发者陷入“会用但不知其所以然”的境地。很多人对 MFC 控件的理解停留在“拖拽、双击、写事件处理函数”的层面。这没错但远远不够。当你需要实现一个稍微复杂点的交互比如让一个按钮在特定条件下变灰或者让一个编辑框只接受数字输入又或者想让一个列表控件支持动态排序时如果只靠向导生成的代码往往会发现无从下手或者写出的代码既臃肿又脆弱。这篇文章要解决的正是这个核心痛点如何系统性地“掌握”MFC界面控件而不仅仅是“使用”它们。我们将超越 Visual Studio 资源编辑器的拖拽操作深入到控件的消息映射、样式设置、数据交换以及自定义绘制的层面。你会发现真正掌握这些控件后你不仅能快速实现功能更能写出易于维护、扩展性强的界面代码。无论你是正在维护一个遗留的 MFC 项目还是需要为新的 C 桌面应用选择技术栈这篇文章都将为你提供一套清晰的、可落地的实践指南。1. 为什么“掌握”MFC控件在今天依然重要在 Qt、WPF、Electron 等现代框架大行其道的今天再谈 MFC 似乎有些“复古”。但现实是大量的工业控制软件、专业仪器配套软件、金融交易终端以及企业内部的老牌系统其核心依然是基于 MFC 构建的。这些系统生命周期长重写成本极高因此“维护和迭代”成为了主要任务。在这种情况下“掌握 MFC 控件”就从一个可选技能变成了一个硬性需求。它的重要性体现在几个方面维护与调试当界面出现诡异 Bug比如文章开头热词提到的“旋转后按右下角旋转”如果你不理解控件的内部坐标系统、变换矩阵和窗口样式可能连调试的方向都找不到。功能增强现有控件功能不满足需求时如需要特殊验证的编辑框、带复杂表头的列表你需要在现有控件基础上进行派生和自定义这要求你对基类控件有深刻理解。性能优化在数据量大的列表或树形控件中错误的刷新方式会导致界面卡顿。掌握虚拟列表、自定义绘制等高级特性是优化性能的关键。技术债务清理很多老代码充斥着对控件直接操作的低级 API 调用耦合度高。掌握面向对象的设计模式与控件结合的方法可以有效重构这些代码。因此本文的目标读者非常明确正在或即将从事 Windows C 桌面开发需要与 MFC 打交道的开发者。无论你是新手还是有一定经验的老手我们都将从“掌握”的角度重新梳理这些控件的知识体系。2. MFC 控件基础从 CWnd 到你的对话框在深入具体控件之前必须理解 MFC 控件的继承体系。几乎所有标准控件都派生自CWnd类这意味着它们首先是一个“窗口”拥有窗口句柄HWND可以接收和处理 Windows 消息。核心基类CWndCWnd封装了 Windows 窗口的基本操作如创建 (Create)、显示 (ShowWindow)、移动 (MoveWindow)、销毁 (DestroyWindow) 以及最重要的——消息处理机制。控件的生命周期、样式、坐标都源于此。控件管理类CDialog,CFormView,CPropertyPage这些是控件的容器。我们通常在对话框编辑器里拖放控件对应的就是CDialog或其派生类。容器负责初始化时创建子控件并通过DDX (Dialog Data Exchange)和DDV (Dialog Data Validation)机制与控件进行数据交换和验证。关键概念控件ID、消息映射与命令路由每个控件都有一个唯一的整数 ID如IDC_EDIT_NAME。MFC 通过这个 ID 将控件与成员变量值变量或控件变量以及消息处理函数关联起来。当用户与控件交互点击、输入、选择时会产生特定的 Windows 消息如BN_CLICKED,EN_CHANGEMFC 的消息映射机制 (BEGIN_MESSAGE_MAP) 会将这些消息路由到对应的处理函数。理解了这个基础框架我们再看具体控件时就会明白它们无非是CWnd的特定子类拥有特定的窗口样式WS_* 和控件特有的样式如LVS_REPORT和专有的消息集。3. 环境准备搭建你的 MFC 学习与实验环境工欲善其事必先利其器。虽然 MFC 历史悠久但使用现代版本的 Visual Studio 进行开发体验会好很多。1. 开发环境IDE推荐使用Visual Studio 2019 或 2022。社区版完全免费且功能强大。它们对 C 标准支持更好调试器也更先进。工作负载安装 Visual Studio 时务必勾选“使用 C 的桌面开发”工作负载。这会包含 MFC 和 ATL 库。项目模板创建新项目时选择“MFC 应用程序”模板。向导会引导你创建基于对话框、单文档或多文档的应用程序。2. 创建实验项目我们创建一个最简单的基于对话框的应用程序作为实验床打开 VS新建项目 - 选择 “MFC 应用” - 命名如MfcControlsLab。在“应用程序类型”页选择“基于对话框”。后续页面保持默认完成创建。现在你得到了一个包含资源文件.rc、对话框模板和一个主对话框类如CMfcControlsLabDlg的项目。资源视图Resource View是你操作控件的主战场。3. 关键工具窗口工具箱包含所有可拖放的标准控件和公共控件。资源视图管理对话框、菜单、图标等资源。属性窗口选中控件或对话框后在这里修改其 ID、样式、扩展样式等属性。这是掌握控件的关键入口很多行为都由这里的样式决定。类向导老版本 VS 的“MFC 类向导”或新版本的“属性窗口”中的“事件”按钮用于为控件添加消息处理函数和成员变量。4. 核心控件详解与实战操作让我们挑选几个最具代表性、也最容易产生困惑的控件进行深度剖析。我们将按照“属性设置 - 成员变量绑定 - 消息处理 - 高级操作”的流程来讲解。4.1 编辑框控件 (CEdit)不只是输入文本编辑框 (CEdit) 常用于接收用户输入。它的高级用法远不止GetWindowText和SetWindowText。基础绑定与数据交换在对话框编辑器中拖入一个 Edit Control。将其 ID 改为IDC_EDIT_INPUT。右键控件 - “添加变量”。变量类型选择“值”类别为CString变量名m_strInput。这创建了一个用于数据交换的值变量。同时可以再添加一个“控件”变量类别为CEdit变量名m_editCtrl。这让你能直接调用 CEdit 的方法。在对话框类的DoDataExchange函数中你会看到自动生成的代码void CMfcControlsLabDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_INPUT, m_strInput); DDX_Control(pDX, IDC_EDIT_INPUT, m_editCtrl); // 如果你添加了控件变量 }DDX_Text会在对话框显示时用m_strInput初始化编辑框在调用UpdateData(TRUE)时用编辑框的内容更新m_strInput。高级样式与功能通过属性窗口设置样式或调用ModifyStyle动态修改数字限制设置Number属性为 True或添加ES_NUMBER样式。这会限制只能输入数字。密码框设置Password属性为 True或添加ES_PASSWORD样式。多行与滚动设置Multiline和Vertical scroll属性对应ES_MULTILINE和WS_VSCROLL样式。只读Read Only属性或ES_READONLY样式。常见操作示例// 1. 设置和获取文本通过控件变量 m_editCtrl.SetWindowText(_T(Hello MFC)); CString strText; m_editCtrl.GetWindowText(strText); // 2. 限制文本长度在初始化对话框时 OnInitDialog 中设置 m_editCtrl.SetLimitText(100); // 最多输入100个字符 // 3. 选中部分文本对应热词中的 .SetSel // 选中从第2个字符到第5个字符索引从0开始 m_editCtrl.SetSel(2, 5); // 也可以替换选中文本 m_editCtrl.ReplaceSel(_T(**Replaced**)); // 4. 禁用/启用控件对应热词中的禁用控件 m_editCtrl.EnableWindow(FALSE); // 禁用变灰 // m_editCtrl.EnableWindow(TRUE); // 启用4.2 按钮控件 (CButton)单选、复选与普通按钮CButton类涵盖了普通按钮、复选框、单选按钮和分组框。它们通过不同的样式BS_PUSHBUTTON,BS_CHECKBOX,BS_RADIOBUTTON,BS_GROUPBOX来区分。复选框的使用拖入一个 Button将其属性中的Caption改为“同意条款”并勾选Auto属性对应BS_AUTOCHECKBOX样式。这样用户点击时复选框会自动在勾选/未勾选间切换。添加变量添加一个int类型的值变量m_bAgree。DDX_Check交换机制会将复选框状态映射为0-未选1-选中2-不确定三态框。响应点击事件双击复选框会自动生成BN_CLICKED消息的处理函数。void CMfcControlsLabDlg::OnBnClickedCheckAgree() { UpdateData(TRUE); // 将界面数据更新到变量 if (m_bAgree 1) { // 用户勾选了复选框 AfxMessageBox(_T(感谢您的同意)); } else { // 用户取消了勾选 } }单选按钮组单选按钮必须成组工作同一时间组内只有一个被选中。依次拖入两个 Radio ButtonCaption 分别为“选项A”和“选项B”。将第一个单选按钮的Group属性设为 True。这标志着组的开始。将第二个的Group属性设为 False。为第一个单选按钮IDC_RADIO_A添加一个int类型的值变量m_nChoice。DDX_Radio会根据选中状态自动将m_nChoice设为0选中组内第一个、1选中第二个依此类推。分组框Group Box本身不参与数据交换主要起视觉分组和提示作用。4.3 列表控件 (CListCtrl)数据展示的核心列表控件 (CListCtrl) 功能强大支持图标、小图标、列表和报表详细四种视图。最常见的是报表视图。初始化报表列表拖入 List Control在属性窗口中设置View为Report。在对话框的OnInitDialog函数中初始化列表。BOOL CMfcControlsLabDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // ... 其他初始化 // 初始化列表控件假设控件变量为 m_listCtrl m_listCtrl.InsertColumn(0, _T(姓名), LVCFMT_LEFT, 100); m_listCtrl.InsertColumn(1, _T(年龄), LVCFMT_CENTER, 80); m_listCtrl.InsertColumn(2, _T(部门), LVCFMT_LEFT, 150); // 设置扩展样式增强用户体验 m_listCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT | // 整行选择 LVS_EX_GRIDLINES | // 显示网格线 LVS_EX_DOUBLEBUFFER); // 双缓冲减少闪烁 // 插入数据 int nItem m_listCtrl.InsertItem(0, _T(张三)); m_listCtrl.SetItemText(nItem, 1, _T(28)); m_listCtrl.SetItemText(nItem, 2, _T(技术部)); nItem m_listCtrl.InsertItem(1, _T(李四)); m_listCtrl.SetItemText(nItem, 1, _T(35)); m_listCtrl.SetItemText(nItem, 2, _T(市场部)); return TRUE; }处理项选择事件通常需要响应列表项被选中的事件。通过类向导或属性窗口的“事件”按钮为列表控件的LVN_ITEMCHANGED消息添加处理函数。在处理函数中可以获取选中的项。void CMfcControlsLabDlg::OnLvnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLISTVIEW pNMLV reinterpret_castLPNMLISTVIEW(pNMHDR); // 判断是否是选中状态发生了变化 if ((pNMLV-uChanged LVIF_STATE) (pNMLV-uNewState LVIS_SELECTED)) { // 获取选中项的索引 int nSelected pNMLV-iItem; if (nSelected 0) { CString strName m_listCtrl.GetItemText(nSelected, 0); // 根据选中项更新其他控件或状态... } } *pResult 0; }虚拟列表技术当数据量极大上万行时不应使用InsertItem一次性插入所有数据这会导致初始化极慢且内存占用高。应使用虚拟列表。设置列表控件样式为LVS_OWNERDATA。处理LVN_GETDISPINFO消息按需提供数据。// 在初始化时设置虚拟列表项总数 m_listCtrl.SetItemCount(10000); // 假设有1万条数据 // LVN_GETDISPINFO 消息处理函数 void CMfcControlsLabDlg::OnLvnGetdispinfoList(NMHDR *pNMHDR, LRESULT *pResult) { NMLVDISPINFO *pDispInfo reinterpret_castNMLVDISPINFO*(pNMHDR); LV_ITEM* pItem (pDispInfo)-item; int nItem pItem-iItem; if (pItem-mask LVIF_TEXT) { switch (pItem-iSubItem) { case 0: // 第一列 // 从你的真实数据源如vector、数据库中获取第nItem行的姓名 _tcscpy_s(pItem-pszText, pItem-cchTextMax, GetNameFromDataSource(nItem)); break; case 1: // 第二列 _tcscpy_s(pItem-pszText, pItem-cchTextMax, GetAgeFromDataSource(nItem)); break; // ... 其他列 } } *pResult 0; }4.4 组合框控件 (CComboBox)下拉选择与自由输入组合框 (CComboBox) 结合了编辑框和列表框的功能非常常用。初始化与数据填充BOOL CMfcControlsLabDlg::OnInitDialog() { // ... // 假设控件变量为 m_comboDept m_comboDept.AddString(_T(技术部)); m_comboDept.AddString(_T(市场部)); m_comboDept.AddString(_T(行政部)); m_comboDept.AddString(_T(财务部)); // 设置默认选中项 m_comboDept.SetCurSel(0); // 或者如果需要与CString变量绑定DDX // 在DoDataExchange中DDX_CBString(pDX, IDC_COMBO_DEPT, m_strDept); // 然后在此处设置 m_strDept _T(技术部); UpdateData(FALSE); return TRUE; }获取与设置选择// 获取当前选中项的索引 int nSel m_comboDept.GetCurSel(); if (nSel ! CB_ERR) { CString strSelected; m_comboDept.GetLBText(nSel, strSelected); // 使用 strSelected } // 或者如果绑定了CString变量 m_strDept UpdateData(TRUE); // m_strDept 现在包含了组合框编辑框中的文本可能是用户输入的5. 高级主题自定义绘制与坐标变换当标准控件的外观或行为无法满足需求时就需要更高级的技术。5.1 自定义绘制 (Owner Draw)例如你想让列表控件的某些行根据数据内容显示不同背景色。为列表控件添加LVS_OWNERDRAWFIXED样式在属性窗口的“样式”中设置 Draw 为 Fixed。在对话框类中处理WM_DRAWITEM消息对于按钮等或WM_MEASUREITEM/WM_DRAWITEM对于列表、组合框等自绘项。对于列表控件更常用的方式是使用Custom Draw通知消息 (NM_CUSTOMDRAW)它提供更精细的控制。// 通过类向导添加 NM_CUSTOMDRAW 消息处理函数 void CMfcControlsLabDlg::OnNMCustomdrawList(NMHDR *pNMHDR, LRESULT *pResult) { LPNMLVCUSTOMDRAW lpNMCustomDraw reinterpret_castLPNMLVCUSTOMDRAW(pNMHDR); *pResult CDRF_DODEFAULT; // 默认处理 switch (lpNMCustomDraw-nmcd.dwDrawStage) { case CDDS_PREPAINT: // 请求为每个项接收通知 *pResult CDRF_NOTIFYITEMDRAW; break; case CDDS_ITEMPREPAINT: // 为每个项绘制前 int nItem (int)lpNMCustomDraw-nmcd.dwItemSpec; // 根据你的业务逻辑判断是否高亮 if (nItem % 2 0) { // 示例偶数行灰色背景 lpNMCustomDraw-clrTextBk RGB(240, 240, 240); // 设置背景色 } // 可以继续设置文本颜色 lpNMCustomDraw-clrText *pResult CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT; // 应用新颜色并请求绘制后通知 break; } }5.2 坐标变换与旋转问题解析热词中提到“按中心点旋转执行 SetRotateTransform 后结果是按右下角旋转的”。这涉及到 GDI 绘图中的坐标变换问题在自定义绘制控件时很常见。问题根源在 GDI 中Graphics::SetRotateTransform默认是绕坐标系原点 (0,0)旋转。如果你在控件的OnPaint中直接绘制一个矩形并设置旋转旋转中心是客户区的左上角而不是矩形的中心或控件的中心。解决方案需要变换坐标系将旋转中心平移到你想要的位置。void CMyCustomControl::OnPaint() { CPaintDC dc(this); Graphics graphics(dc.GetSafeHdc()); // 假设我们要在控件中心 (width/2, height/2) 绘制一个旋转的矩形 CRect rectClient; GetClientRect(rectClient); int centerX rectClient.Width() / 2; int centerY rectClient.Height() / 2; // 1. 将坐标系原点平移到控件中心 graphics.TranslateTransform((REAL)centerX, (REAL)centerY); // 2. 绕新的原点即控件中心旋转45度 graphics.RotateTransform(45.0f); // 3. 绘制矩形。注意此时坐标原点在控件中心。 // 我们要画一个以原点为中心宽100高50的矩形。 Pen pen(Color(255, 0, 0, 255), 2.0f); // 蓝色笔 graphics.DrawRectangle(pen, -50, -25, 100, 50); // 左上角坐标为(-50, -25) // 4. 重要恢复变换否则会影响后续绘制。 // graphics.ResetTransform(); // 或者依次反向变换 }关键点变换操作平移、旋转、缩放是累积的且顺序很重要。通常的步骤是平移 - 旋转 - 绘制 - 重置。理解了这个原理就能解决大部分关于绘制位置和旋转中心不对的问题。6. 工程实践消息映射、DDX/DDV 与资源管理消息映射的实质MFC 使用宏将 Windows 消息与类的成员函数关联起来。在头文件中声明afx_msg函数在源文件中用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP进行绑定。理解这一点有助于手动添加非标准消息的处理。DDX/DDV 的最佳实践DDX (数据交换)在DoDataExchange中完成控件与成员变量的双向绑定。UpdateData(TRUE)从控件到变量UpdateData(FALSE)从变量到控件。DDV (数据验证)可以在DoDataExchange中使用DDV_MaxChars,DDV_MinMaxInt等宏进行简单的输入验证。对于复杂验证应在OnOK或按钮事件中调用UpdateData(TRUE)后手动检查变量并提示用户。作用域UpdateData的参数决定了数据流动方向。在对话框初始化 (OnInitDialog) 后调用UpdateData(FALSE)来用变量初始化控件。在用户点击“确定”后调用UpdateData(TRUE)来获取用户输入。资源与库文件管理热词中提到“vs 2008的mfc怎么项目link导入lib文件”。在现代 VS 中管理依赖更简单静态库 (.lib)将.lib文件放入你的项目目录如一个libs文件夹。在项目属性 - “链接器” - “输入” - “附加依赖项”中添加库文件名如MyLibrary.lib。在“链接器” - “常规” - “附加库目录”中添加库文件所在的路径如$(ProjectDir)libs。动态库 (.dll)除了上述.lib导入库的配置还需要确保运行时.dll文件在可执行文件的路径下。MFC 自身配置在项目属性 - “高级”中可以设置“MFC 的使用”为“在共享 DLL 中使用 MFC”或“在静态库中使用 MFC”。前者生成文件小但需要目标机器有 MFC 运行时库后者生成文件大但可独立运行。7. 常见问题与排查思路问题现象可能原因排查方式解决方案控件显示为空白或乱码1. 未正确调用UpdateData(FALSE)初始化。2. 字体设置不当特别是中文。3. 自定义绘制代码覆盖了默认绘制。1. 检查OnInitDialog中是否有UpdateData(FALSE)。2. 检查对话框或控件的字体属性。3. 在自定义绘制中确保处理了所有绘制阶段。1. 确保初始化流程正确。2. 将字体设置为“Microsoft YaHei”等支持中文的字体。3. 在自绘代码中对于不需要处理的项返回CDRF_DODEFAULT。点击按钮/改变编辑框内容无反应1. 消息映射未建立或错误。2. 事件处理函数未被正确添加。3. 控件被禁用 (EnableWindow(FALSE))。1. 在类视图右键对话框类 - “属性” - “事件”查看对应控件ID的事件是否已绑定。2. 检查源文件中的BEGIN_MESSAGE_MAP宏内是否有对应的条目。3. 检查代码中是否有EnableWindow(FALSE)调用。1. 通过属性窗口的“事件”按钮重新添加事件处理函数。2. 手动检查并修正消息映射条目。3. 在适当位置调用EnableWindow(TRUE)。列表/树控件数据多时滚动卡顿1. 一次性插入所有数据非虚拟列表。2. 在循环中频繁进行不必要的重绘。1. 检查数据插入代码是否在单次操作中插入了大量项。2. 使用性能分析工具查看瓶颈。1. 对于大数据集务必使用虚拟列表 (LVS_OWNERDATA)。2. 在批量插入/删除前调用SetRedraw(FALSE)操作完成后调用SetRedraw(TRUE)。自定义绘制时图形位置错误1. 坐标系理解错误如旋转中心问题。2. 未考虑控件边框、边距。3. 变换矩阵未重置。1. 绘制前输出或调试查看绘图区域的坐标。2. 检查是否使用了正确的客户区坐标 (GetClientRect)。1. 复习 GDI/GDI 坐标系系统明确变换顺序。2. 使用GetClientRect获取绘制区域。3. 每次绘制循环后调用Graphics::ResetTransform()。编译时提示“无法解析的外部符号”1. 缺少对应的库文件 (.lib) 链接。2. 函数声明与定义不一致C 名称修饰。3. 项目字符集设置不一致Unicode vs 多字节。1. 查看错误信息中缺失的符号名称。2. 检查项目属性 - “链接器” - “输入”中的附加依赖项。3. 检查项目属性 - “高级” - “字符集”。1. 将正确的 .lib 文件路径和名称添加到附加依赖项。2. 确保头文件包含正确函数签名一致。3. 统一项目字符集设置推荐使用 Unicode。运行时程序崩溃错误与控件操作相关1. 控件窗口还未创建就访问其成员函数。2. 控件指针或引用变量未初始化或已失效。3. 在多线程中直接操作UI控件。1. 在调用控件函数前使用GetSafeHwnd()判断窗口句柄是否有效。2. 检查控件变量是否通过DDX_Control正确绑定。3. 检查是否在非UI线程中调用了控件方法。1. 将控件初始化代码放在OnInitDialog中并确保已调用父类方法。2. 使用AfxGetMainWnd()或消息机制跨线程更新UI。8. 最佳实践与工程化建议资源管理为控件ID使用有意义的命名如IDC_EDIT_USERNAME而非IDC_EDIT1。在资源编辑器中合理使用“组”和“Tab 键顺序”功能提升用户体验。将字符串、图标等资源集中管理便于本地化。代码组织将复杂对话框的业务逻辑拆分到独立的类或命名空间中避免对话框类过于臃肿。对于自定义控件创建独立的CMyCustomCtrl类封装其绘制和行为。使用std::vector,std::map等现代 C 容器管理数据而非原始的数组。数据与UI分离不要将业务数据直接存储在控件中如CListCtrl的ItemData只存索引或轻量指针。定义清晰的模型Model类来持有数据UI 层仅负责展示和交互。使用观察者模式或 MFC 的消息机制在数据变更时更新UI。错误处理与健壮性对所有可能失败的外部调用如文件操作、数据库查询进行错误检查。在控件操作前使用ASSERT或GetSafeHwnd()验证窗口有效性特别是在PostMessage或回调函数中。为复杂的操作提供进度反馈如进度条、状态文本避免界面“假死”。兼容性与现代化如果维护老项目注意 Unicode 和多字节字符集的区别。新项目一律使用 Unicode 字符集。考虑使用较新的 Windows SDK 和 Visual Studio 工具集以获得更好的兼容性和安全性。对于全新的 C 桌面项目应优先评估 Qt 等现代框架。仅在对现有 MFC 代码进行维护或扩展时才深入使用 MFC。掌握 MFC 控件本质上是掌握 Windows GUI 编程的核心思想消息驱动、资源管理、GDI 绘图。即使未来转向其他框架这些底层知识依然极具价值。本文从实用问题出发拆解了控件的核心用法、高级特性与常见陷阱并提供了从环境搭建到工程实践的完整路径。建议你将此作为手册在遇到具体问题时回头查阅相关章节并结合实际项目代码进行调试和验证。理解原理善用工具你就能让那些看似古老的 MFC 控件在现代开发中继续稳定、高效地工作。
返回列表