简介:这份资源面向仍在使用 VC6.0 的开发者,尤其是 Windows 7/10 环境下遇到无法打开文件、无法添加工程文件等问题的用户。包内提供 FileTool 附加项完整源码与已编译好的 FileTool.dll,可直接注册使用,帮助恢复 VC6.0 的文件菜单与工程文件添加功能。资源共 51 个文件,包含 4 个 cpp 源文件、6 个头文件、2 个 dll 动态库、2 个 lib 库文件、2 个 bmp 位图及 dsp、dsw 工程文件等,覆盖源码、编译中间产物与可执行模块,压缩包约 4.9MB。已有 445 人学习下载。通过该附加项,读者可解决旧版 IDE 在新系统下的兼容性障碍,同时借助完整工程源码理解 VC6.0 附加项的注册与加载机制,便于自行编译、调试或二次修改,适合需要维护遗留项目或研究经典开发工具扩展的技术人员。
1. 在 VC6.0 里给 FileTool 加上文件菜单栏:老工具为什么还值得动刀
接手一个维护了十几年的 MFC 小工具时,最让人头疼的不是算法,而是它连一个像样的文件菜单都没有。这个叫 FileTool 的工程,功能其实不弱——能读写配置、能导出日志、能批量处理文件,但所有入口都塞在一个对话框的按钮堆里。用户想打开一个文件,得先点“浏览”,再点“加载”,最后点“执行”,三步操作没有任何菜单提示。更麻烦的是,快捷键一个都没有,Ctrl+O、Ctrl+S 这些肌肉记忆完全失效。于是需求很明确:给 FileTool 添加文件菜单栏,把打开、保存、另存为、退出这几个动作标准化。这件事在 VC6.0 的 MFC 框架下并不复杂,但老版本 IDE 的资源编辑器、消息映射机制和 Unicode 支持都有它自己的脾气。如果你手里也有一个基于对话框的 VC6.0 工程,想补上菜单栏却不知道从哪下手,或者加了菜单但命令响应死活不触发,这篇笔记就是按我实际改工程的顺序写的。从资源脚本到消息映射,再到状态栏联动和快捷键,每一步都给出可复现的操作和参数说明。
2. 先搞清楚 VC6.0 对话框工程加菜单的三种路径
2.1 为什么对话框工程默认没有菜单栏
MFC 的对话框模板(Dialog Template)在资源脚本里本身不包含菜单资源绑定。单文档(SDI)和多文档(MDI)工程的菜单是挂在主框架窗口(CFrameWnd)上的,而对话框工程的主窗口是 CDialog 派生类,它只负责显示控件和处理控件消息。所以你在资源视图里新建一个菜单资源,编译后对话框上也不会自动出现菜单栏。这不是 Bug,是 MFC 的设计边界。常见做法有三种:第一种是在对话框模板属性里直接指定菜单资源 ID,让系统在创建对话框时自动加载;第二种是在 OnInitDialog 里手动调用 SetMenu 加载菜单;第三种是创建一个隐藏的框架窗口来承载菜单,再把对话框作为子窗口嵌入。第三种适合需要工具栏和状态栏的复杂场景,但改动量大。对于 FileTool 这种中小型工具,第一种和第二种足够用,而且改动集中、回滚容易。
2.2 三种路径的选型对比与适用边界
| 路径 | 改动位置 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 对话框模板绑定菜单 ID | .rc 资源脚本 | 代码零改动,IDE 可视化 | 菜单在对话框创建时固定,动态换菜单麻烦 | 菜单结构稳定的小工具 |
| OnInitDialog 里 SetMenu | 对话框类实现文件 | 灵活,可按条件加载不同菜单 | 需要手动管理菜单生命周期 | 需要根据配置切换菜单 |
| 隐藏框架窗口承载 | 新增 CFrameWnd 派生类 | 支持工具栏、状态栏、停靠 | 改动大,消息路由复杂 | 需要完整主窗口体验 |
我一般会先看工程里有没有现成的 CMainFrame 类。如果有,说明这个工程原本可能是从 SDI 改过来的,那用第三种路径反而顺。如果纯粹是对话框工程,直接用第一种,在资源编辑器里把菜单 ID 填到对话框属性的 Menu 栏里,编译就能看到菜单栏。但这里有个坑:VC6.0 的资源编辑器在对话框属性下拉框里可能不显示你新建的菜单资源,需要手动输入 ID,比如 IDR_MENU_FILETOOL。输入后保存 .rc 文件,重新打开资源视图才能看到绑定生效。
2.3 最小改动方案:在对话框模板上绑定菜单资源
具体操作顺序是这样的。先在 ResourceView 里右键 Dialog 文件夹,选 Insert Dialog,然后右键新对话框选 Properties,把 ID 改成 IDD_FILETOOL_DIALOG(如果已有就跳过)。接着右键资源根节点,选 Insert,选 Menu,新建一个菜单资源,ID 设为 IDR_MENU_MAIN。在菜单编辑器里添加顶级项“文件(&F)”,下面加子项“打开(&O)\tCtrl+O”“保存(&S)\tCtrl+S”“另存为(&A)...”“退出(&X)”。注意 \t 是制表符,用来在菜单里显示快捷键提示,但真正让快捷键生效还需要加速键表。保存资源后,打开对话框模板属性,在 Menu 下拉框里手动输入 IDR_MENU_MAIN。如果下拉框里没有,直接键盘输入即可。编译运行,菜单栏应该出现在对话框顶部。如果没出现,检查 .rc 文件里对话框定义那一段有没有 MENU IDR_MENU_MAIN 这一行。有时候 VC6.0 的资源编辑器不会自动写入,需要手动在 .rc 文件里加。
// 在 .rc 文件中手动确认对话框定义包含菜单绑定 IDD_FILETOOL_DIALOG DIALOGEX 0, 0, 320, 200 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "FileTool" MENU IDR_MENU_MAIN // 这一行是关键,没有它菜单不会加载 FONT 8, "MS Sans Serif" BEGIN // 控件定义... END这段资源脚本里,MENU 关键字后面跟的就是菜单资源 ID。如果 VC6.0 资源编辑器没有自动加上,手动补上后重新编译即可。注意 ID 必须和菜单资源定义里的 ID 完全一致,大小写敏感。另外,对话框样式里如果有 WS_CHILD 而不是 WS_POPUP,菜单也不会显示,因为子窗口不能有菜单栏。检查 STYLE 行是否包含 WS_POPUP 和 WS_CAPTION。
3. 用 ClassWizard 把菜单命令接到对话框类上
3.1 消息映射的底层逻辑:WM_COMMAND 怎么走到你的函数
MFC 的菜单命令响应走的是 WM_COMMAND 消息。当用户点击菜单项时,Windows 发送 WM_COMMAND 给菜单所属的窗口,wParam 低字是命令 ID,lParam 是 0。MFC 的 CWnd::OnCommand 会解析这个消息,然后通过消息映射表(Message Map)查找对应的处理函数。消息映射表是 MFC 用宏定义的一套静态数组,BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间用 ON_COMMAND 宏把命令 ID 和成员函数关联起来。ClassWizard 在 VC6.0 里的作用就是帮你生成这些宏和函数声明,但它的界面有时候会抽风——比如你新加了菜单资源,ClassWizard 的 Object IDs 列表里却看不到新 ID。这时候需要关闭工程,删除 .clw 文件(ClassWizard 的数据库文件),重新打开工程,VC6.0 会提示重建 ClassWizard 数据库,重建后新 ID 就会出现。这个操作我做过很多次,是 VC6.0 的经典血泪经验。
3.2 用 ClassWizard 添加 ON_COMMAND 处理函数的完整步骤
打开 ClassWizard(Ctrl+W),选择 Message Maps 标签页。在 Class name 下拉框里选 CFileToolDlg(你的对话框类名)。在 Object IDs 列表里找到 ID_FILE_OPEN、ID_FILE_SAVE、ID_FILE_SAVEAS、ID_FILE_EXIT 这些菜单项 ID。如果列表里没有,说明 ClassWizard 数据库没同步,按上一节的方法删 .clw 重建。选中 ID_FILE_OPEN 后,在 Messages 列表里选 COMMAND,点 Add Function,函数名默认是 OnFileOpen。同样的方式给其他三个 ID 添加处理函数。ClassWizard 会自动在 .h 文件里加函数声明,在 .cpp 文件里加消息映射宏和空函数体。注意:ID_FILE_EXIT 的 COMMAND 处理函数里要调用 OnOK 或 OnCancel 来关闭对话框,但如果你希望点“退出”时走确认流程,就调 OnClose。另外,菜单项 ID 如果用了 MFC 预定义的 ID_FILE_OPEN 等标准 ID,MFC 框架可能会尝试走默认路由,导致你的处理函数不被调用。常见做法是自定义 ID,比如 IDM_FILE_OPEN,避免和框架内置 ID 冲突。如果非要用标准 ID,需要在处理函数里显式处理,并且确保没有其他框架窗口截获。
// FileToolDlg.h 中的声明 protected: afx_msg void OnFileOpen(); afx_msg void OnFileSave(); afx_msg void OnFileSaveAs(); afx_msg void OnFileExit(); DECLARE_MESSAGE_MAP() // FileToolDlg.cpp 中的消息映射 BEGIN_MESSAGE_MAP(CFileToolDlg, CDialog) ON_COMMAND(IDM_FILE_OPEN, OnFileOpen) ON_COMMAND(IDM_FILE_SAVE, OnFileSave) ON_COMMAND(IDM_FILE_SAVEAS, OnFileSaveAs) ON_COMMAND(IDM_FILE_EXIT, OnFileExit) END_MESSAGE_MAP() void CFileToolDlg::OnFileOpen() { CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, _T("所有文件 (*.*)|*.*||"), this); if (dlg.DoModal() == IDOK) { CString path = dlg.GetPathName(); // 这里调用你的文件加载逻辑 // LoadFile(path); } }这段代码里,ON_COMMAND 宏把菜单 ID 和成员函数绑定。CFileDialog 构造函数的第一个参数 TRUE 表示打开对话框,FALSE 表示保存对话框。第二个参数是默认扩展名,传 NULL 表示不限制。第三个参数是默认文件名。第四个参数是标志位,OFN_HIDEREADONLY 隐藏只读复选框,OFN_FILEMUSTEXIST 要求文件必须存在。第五个参数是过滤器字符串,注意用 | 分隔描述和模式,末尾用 || 结束。最后一个参数是父窗口指针,传 this 让对话框模态显示在 FileTool 窗口上方。保存和另存为的处理函数类似,只是 CFileDialog 第一个参数传 FALSE,并且可以在 IDOK 后调用 CFile::Write 或你的序列化逻辑。
3.3 命令 ID 命名冲突与路由失败的排查
菜单命令不响应,九成是 ID 冲突或消息映射没生效。先检查资源符号表(View 菜单 -> Resource Symbols),看有没有重复的 ID 值。VC6.0 的资源编辑器在新建菜单项时会自动分配 ID,但如果你手动改了 ID 名而值没变,就可能和别的命令撞车。比如 IDM_FILE_OPEN 的值是 32771,而某个控件 ID 也是 32771,那 WM_COMMAND 就会同时触发两个处理,行为不可预测。解决办法是在 Resource Symbols 里给每个菜单命令分配一个独立的、大于 40000 的值。另外,检查消息映射宏是否写在 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间,有没有拼写错误。ON_COMMAND 的第二个参数是函数名,不带括号。如果函数声明在 .h 里是 private,消息映射也能访问,但 ClassWizard 默认生成 protected,没问题。还有一个隐蔽的坑:如果对话框上有同 ID 的按钮控件,按钮点击也会发送 WM_COMMAND,可能先于菜单命令被处理。排查时可以在处理函数第一行加 AfxMessageBox(_T("hit")),看是否弹出。如果不弹,就是消息映射没接上;如果弹了但功能不对,就是 ID 冲突。
4. 加速键、状态栏提示和菜单项灰显的联动处理
4.1 用加速键表让 Ctrl+O 真正生效
菜单项文字里的 \tCtrl+O 只是视觉提示,真正让快捷键生效需要加速键表(Accelerator)。在 ResourceView 里右键 Accelerator 文件夹,Insert Accelerator,ID 设为 IDR_ACCELERATOR_MAIN。在表里添加条目:ID 选 IDM_FILE_OPEN,Key 填 O,Modifiers 勾 Ctrl。同样加 IDM_FILE_SAVE 对应 Ctrl+S,IDM_FILE_SAVEAS 对应 Ctrl+Shift+S,IDM_FILE_EXIT 对应 Ctrl+Q 或 Esc。保存后,在对话框的 OnInitDialog 里加载加速键表,并在 PreTranslateMessage 里调用 TranslateAccelerator。注意:对话框工程默认不处理加速键,必须手动加。具体做法是在 CFileToolDlg 类里加成员变量 HACCEL m_hAccel,在 OnInitDialog 里 m_hAccel = LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR_MAIN)),然后重写 PreTranslateMessage,在里面调用 TranslateAccelerator(m_hWnd, m_hAccel, pMsg)。如果返回 TRUE 表示消息已处理,不再往下分发。别忘了在析构函数里 DestroyAcceleratorTable(m_hAccel)。这个流程在 VC6.0 里没有向导支持,得手写。
// FileToolDlg.h protected: HACCEL m_hAccel; virtual BOOL PreTranslateMessage(MSG* pMsg); // FileToolDlg.cpp BOOL CFileToolDlg::OnInitDialog() { CDialog::OnInitDialog(); m_hAccel = LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR_MAIN)); return TRUE; } BOOL CFileToolDlg::PreTranslateMessage(MSG* pMsg) { if (m_hAccel && TranslateAccelerator(m_hWnd, m_hAccel, pMsg)) return TRUE; return CDialog::PreTranslateMessage(pMsg); }这段代码里,LoadAccelerators 的第一个参数是实例句柄,用 AfxGetInstanceHandle() 获取。第二个参数是加速键资源 ID,用 MAKEINTRESOURCE 宏转换。TranslateAccelerator 的第一个参数是窗口句柄,第二个是加速键表句柄,第三个是消息指针。如果加速键匹配成功,它会发送 WM_COMMAND 并返回非零,PreTranslateMessage 返回 TRUE 阻止消息继续传递。注意:如果对话框上有编辑框,Ctrl+O 可能会被编辑框截获作为输入,所以加速键表要放在 PreTranslateMessage 的最前面处理。另外,m_hAccel 要在对话框销毁时释放,可以在 OnDestroy 里调 DestroyAcceleratorTable。
4.2 菜单项灰显:用 ON_UPDATE_COMMAND_UI 控制可用状态
“保存”菜单在没有打开文件时应该灰显,这是基本体验。MFC 提供了 ON_UPDATE_COMMAND_UI 宏,在菜单弹出前调用处理函数,你可以在这里调 pCmdUI->Enable(TRUE/FALSE) 控制灰显。具体做法是在消息映射里加 ON_UPDATE_COMMAND_UI(IDM_FILE_SAVE, OnUpdateFileSave),然后实现 void CFileToolDlg::OnUpdateFileSave(CCmdUI* pCmdUI) { pCmdUI->Enable(m_bFileLoaded); }。m_bFileLoaded 是对话框类的成员变量,在 OnFileOpen 成功后设为 TRUE,在关闭文件时设为 FALSE。注意:ON_UPDATE_COMMAND_UI 只对菜单和工具栏有效,对加速键无效。也就是说,即使菜单灰显了,Ctrl+S 仍然会触发 OnFileSave。所以 OnFileSave 函数内部也要判断 m_bFileLoaded,如果为 FALSE 就直接返回或弹提示。这个细节很容易翻车,我见过有人只做了灰显没做函数内判断,结果快捷键照样执行。
4.3 状态栏提示:让鼠标悬停菜单时显示说明
VC6.0 对话框工程默认没有状态栏。如果想让鼠标悬停菜单项时在窗口底部显示提示文字,需要手动加一个状态栏控件。简单做法是在对话框资源里放一个静态文本控件,ID 设为 IDC_STATIC_STATUS,然后在菜单命令的 ON_UPDATE_COMMAND_UI 里用 pCmdUI->SetText 设置提示?不行,SetText 是设置菜单项文字,不是状态栏。正确做法是处理 WM_MENUSELECT 消息。在对话框类里加 afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu),消息映射里加 ON_WM_MENUSELECT()。在函数里根据 nItemID 判断当前选中的菜单项,然后 SetDlgItemText(IDC_STATIC_STATUS, _T("打开一个文件"))。当 nFlags 为 0xFFFF 时表示菜单关闭,清空状态栏文字。这个方案不需要真正的 CStatusBar 控件,用静态文本就能模拟。注意:WM_MENUSELECT 只在菜单弹出时发送,对话框的静态文本要放在底部并且足够宽,否则文字会被截断。
5. 避坑:VC6.0 菜单改造中最容易翻车的五个点
5.1 菜单栏出现了但点击没反应
现象:编译运行后菜单栏正常显示,点击“打开”没有任何弹窗,也没有报错。原因:菜单资源 ID 和对话框模板绑定了,但消息映射里没有对应的 ON_COMMAND 宏,或者 ClassWizard 生成的函数没有被正确链接。解决:打开 .cpp 文件,检查 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间有没有 ON_COMMAND(IDM_FILE_OPEN, OnFileOpen)。如果没有,手动加上。然后检查 .h 文件里有没有 afx_msg void OnFileOpen(); 声明。如果都有,在 OnFileOpen 第一行加 AfxMessageBox(_T("test")),重新编译。如果还不弹,说明资源 ID 不匹配——去 Resource Symbols 里确认 IDM_FILE_OPEN 的值,和消息映射里的宏参数是否一致。VC6.0 有时候会在资源编辑器里自动改 ID 值,导致宏参数对不上。
5.2 ClassWizard 里看不到新建的菜单 ID
现象:新建了菜单资源,但在 ClassWizard 的 Object IDs 列表里找不到 IDM_FILE_OPEN 等 ID。原因:ClassWizard 的数据库文件 .clw 没有同步资源变更。VC6.0 的 ClassWizard 不会实时监控 .rc 文件变化,需要手动重建数据库。解决:关闭工程,在工程目录下删除 .clw 文件(注意不是 .dsp 或 .dsw),重新打开 .dsw,VC6.0 会提示“ClassWizard 数据库不存在,是否重建”,选“是”。重建后所有资源 ID 都会出现在列表里。如果还不行,检查 .rc 文件里菜单资源是否真的保存了,有时候 VC6.0 崩溃会导致资源丢失,需要重新添加。
5.3 加速键 Ctrl+S 触发了两次保存
现象:按 Ctrl+S 时,OnFileSave 被调用了两次,导致文件被写两遍或者弹出两个保存对话框。原因:加速键表和菜单项都绑定了同一个命令 ID,TranslateAccelerator 发送一次 WM_COMMAND,而菜单的 ON_COMMAND 又处理一次。实际上 TranslateAccelerator 只发送一次,但如果 PreTranslateMessage 里没有正确返回 TRUE,消息会继续分发到对话框的 OnCommand,导致重复处理。解决:确保 PreTranslateMessage 里 TranslateAccelerator 返回非零时直接 return TRUE。另外检查有没有在 OnFileSave 里调用了 CDialog::OnOK 或类似会触发默认处理的操作。如果用了 ON_COMMAND 和 ON_UPDATE_COMMAND_UI 同时存在,确认没有在 OnUpdate 里做实际保存动作。
5.4 菜单项中文显示乱码
现象:菜单文字“文件”显示成“?”或方块。原因:VC6.0 默认使用 MBCS 编码,资源脚本里的中文如果保存为 UTF-8 会乱码。解决:确保 .rc 文件保存为 ANSI 编码(在 VC6.0 编辑器里 File -> Save As,编码选 Default)。另外,对话框字体设置里如果用了非中文字体,菜单可能显示异常。在 .rc 文件里检查 FONT 行,确保是 "MS Sans Serif" 或 "宋体",不要用 "System"。如果工程是 Unicode 的(VC6.0 默认不是),需要在资源里用 _T() 宏包裹字符串,但菜单资源文字是在 .rc 里直接写的,不受 _T() 影响,只能靠 ANSI 编码保证。
5.5 退出菜单导致对话框直接销毁而不是隐藏
现象:点“退出”后对话框关闭,但程序进程还在后台运行,或者主窗口消失但托盘图标还在。原因:OnFileExit 里调用了 OnOK 或 OnCancel,这两个函数会调用 EndDialog 销毁对话框。如果 FileTool 是主窗口,销毁后程序应该退出;但如果还有别的窗口或线程,进程不会结束。解决:如果希望“退出”是关闭程序,在 OnFileExit 里调 PostQuitMessage(0) 或者 AfxGetMainWnd()->PostMessage(WM_CLOSE)。如果希望“退出”只是隐藏窗口,调 ShowWindow(SW_HIDE)。注意:对话框工程里 OnOK 和 OnCancel 的默认行为不同,OnOK 会调用 UpdateData(TRUE) 然后 EndDialog(IDOK),OnCancel 直接 EndDialog(IDCANCEL)。如果对话框上有未保存的数据,OnCancel 可能丢失。建议在 OnFileExit 里先判断是否有未保存修改,弹确认框后再决定。
6. 一个可复用的菜单初始化封装与验证清单
6.1 把菜单加载和加速键绑定封装成独立函数
每次改工程都手写 OnInitDialog 里的加载代码太啰嗦。我一般会封装一个 InitMenuAndAccel 函数,放在对话框类里,OnInitDialog 里调一次。这个函数负责加载菜单资源、加载加速键表、设置菜单项初始状态。如果菜单资源 ID 是固定的,直接写死;如果支持多套菜单,可以传参。封装后,新工程直接拷贝这个函数和 PreTranslateMessage 重写,五分钟就能让菜单跑起来。
// FileToolDlg.h protected: HACCEL m_hAccel; BOOL InitMenuAndAccel(UINT nMenuID, UINT nAccelID); virtual BOOL PreTranslateMessage(MSG* pMsg); // FileToolDlg.cpp BOOL CFileToolDlg::InitMenuAndAccel(UINT nMenuID, UINT nAccelID) { // 加载菜单资源并设置到对话框窗口 CMenu menu; if (!menu.LoadMenu(nMenuID)) return FALSE; SetMenu(&menu); menu.Detach(); // 防止局部对象析构时销毁菜单 // 加载加速键表 m_hAccel = LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(nAccelID)); return m_hAccel != NULL; } BOOL CFileToolDlg::OnInitDialog() { CDialog::OnInitDialog(); if (!InitMenuAndAccel(IDR_MENU_MAIN, IDR_ACCELERATOR_MAIN)) { AfxMessageBox(_T("菜单或加速键加载失败")); } return TRUE; }这段代码里,LoadMenu 加载菜单资源到 CMenu 对象,SetMenu 把菜单设置到对话框窗口。关键点是 menu.Detach()——如果不调,CMenu 局部对象析构时会销毁菜单句柄,导致窗口菜单失效。Detach 把句柄所有权从 CMenu 对象剥离,交给窗口管理。加速键表加载后存在 m_hAccel 成员变量里,PreTranslateMessage 里使用。如果 LoadMenu 失败,返回 FALSE,OnInitDialog 里弹提示。这个封装的好处是菜单 ID 和加速键 ID 作为参数传入,换资源不用改函数体。
6.2 验证清单:改完菜单后必须检查的六项
改完菜单不要急着提交,按这个清单过一遍。第一,菜单栏是否显示在对话框顶部,所有顶级项和子项文字是否正确。第二,每个菜单项点击后是否触发对应函数,用 AfxMessageBox 打桩验证。第三,快捷键 Ctrl+O、Ctrl+S、Ctrl+Shift+S、Ctrl+Q 是否生效,注意在编辑框有焦点时测试,看会不会被编辑框截获。第四,没有打开文件时“保存”和“另存为”是否灰显,打开文件后是否恢复可用。第五,鼠标悬停菜单项时状态栏提示是否更新,菜单关闭后是否清空。第六,点“退出”后进程是否完全退出,用任务管理器确认没有残留。这六项里最容易漏的是第三和第六。快捷键在编辑框焦点下的行为,我踩过坑——编辑框会优先处理 Ctrl+O 作为插入字符?不会,但会处理 Ctrl+A 作为全选。所以加速键表里不要用 Ctrl+A 这种编辑框常用组合。退出残留进程的问题,如果对话框是主窗口,EndDialog 后 MFC 的 Run 函数会退出消息循环,进程结束。但如果还有工作线程没退出,进程会挂起。检查 OnFileExit 里有没有等待线程结束的逻辑。
6.3 从菜单改造延伸到工具栏和右键菜单
菜单栏跑通后,工具栏和右键菜单就是顺水推舟。工具栏可以用 CToolBarCtrl 或者直接在对话框上放一个 ToolBar 控件,但 VC6.0 对话框工程没有现成的工具栏停靠支持。简单做法是用一排按钮模拟工具栏,每个按钮的 ID 和菜单命令 ID 相同,这样 ON_COMMAND 处理函数直接复用。右键菜单更简单,处理 WM_CONTEXTMENU 消息,在 OnContextMenu 里用 CMenu::TrackPopupMenu 弹出同一个菜单资源,注意坐标转换——屏幕坐标转客户区坐标。TrackPopupMenu 的 TPM_RETURNCMD 标志可以让函数返回选中的命令 ID,然后手动 SendMessage(WM_COMMAND, id) 触发处理。这样菜单、工具栏、右键菜单三套入口共用同一套命令处理,维护成本最低。我一般会在 InitMenuAndAccel 里顺便把右键菜单的 CMenu 也加载好,存在成员变量里,OnContextMenu 直接 TrackPopupMenu。注意右键菜单弹出前要调 SetForegroundWindow,否则菜单可能不消失。这个坑在 VC6.0 里很常见,血泪经验。
希望帮到你。
本文还有配套的精品资源,点击获取