背景:
最近在写一个键盘映射工具, 用MFC写应用层的配置程序, 当控制设备(CDO)打开失败时, 配置程序不能进行操作, 即禁用界面上的一些控件(主要是按钮等交互类控件).
问题描述:
参考"MFC批量启用/禁用控件", "MFC中控件的TAB顺序"这两篇文章, 调整好控件的Tab顺序后, 使用如下代码来禁用控件:
CWnd *p_Ctrl = GetDlgItem(IDC_KBDSEL); for (int i=0; i<6 && p_Ctrl!=NULL; ++i) { p_Ctrl->EnableWindow(FALSE); p_Ctrl = GetNextDlgTabItem(p_Ctrl, FALSE); }
运行, 控件被如愿禁用了, 但右上角的关闭按钮('X')也失效了, 窗口根本无法关闭.
解决过程:
思路一, 首先想到是不是上述代码错误的取到了主窗体的句柄, 而使主窗体disable了.
所以, 把循环变量改到5, 还是不行, 改成4, 成功'X'掉了主窗口. 在禁用的6个控件中, 除了第一个是个CComboBox, 其余均是PushButton,
不可能前面的PushButton都能禁用, 禁用第5个PushButton就把主窗体disable?
为了验证, 添加代码, 先输出要被禁用的控件句柄, 再禁用:
CString str; CWnd *p_Ctrl = GetDlgItem(IDC_KBDSEL); for (int i=0; i<6 && p_Ctrl!=NULL; ++i) { str.Format(TEXT("%.8X"), p_Ctrl->m_hWnd); m_srcList.AddString(str); p_Ctrl->EnableWindow(FALSE); p_Ctrl = GetNextDlgTabItem(p_Ctrl, FALSE); }
输出结果表明, 每次获取的控件句柄也都正确无误. 无解.
思路二, 我重写了了主窗体的OnDestroy()函数, 以在收到WM_DESTROY时释放设备.
EnableWindow的Remarks有这样一句话: " if an application is displaying a modeless dialog box and has disabled its main window, the application must enable the main window before destroying the dialog box". 那会不会是某些控件的disable状态影响了主窗体destroy?
(问题是, 第5个控件跟前几个一样, 都是PushButton, 不可能前几个不影响, 只有第5个影响)
既然点了'X'不能关闭窗口, 那就跟到WndProc看看WM_DESTROY的处理是否正常. 打开Spy++, 先看下关闭时的消息是否正常, 一看还真是:
点关闭后, 程序根本没收到WM_DESTROY. 还是无解.
思路三, 回到问题的根本, 既然后5个控件都是PushButton, 而且只有第4个出问题, 其余4个都没问题, 这第4个一定有什么特别的地方. 首先验证这个猜想:
把禁用控件的代码改为用ID直接禁用:
GetDlgItem(IDC_KBDSEL)->EnableWindow(FALSE); GetDlgItem(IDB_ADD)->EnableWindow(FALSE); GetDlgItem(IDB_DEL)->EnableWindow(FALSE); GetDlgItem(IDB_LOAD)->EnableWindow(FALSE); GetDlgItem(IDB_SAVE)->EnableWindow(FALSE); GetDlgItem(IDOK)->EnableWindow(FALSE);
测试结果表明, 只要有"GetDlgItem(IDB_SAVE)->EnableWindow(FALSE);"这一行, 问题就会出现. 那么, 究竟这个"IDB_SAVE"与其他PushButton有什么不同呢?
我修改了它的Caption, Tab顺序等, 问题依旧.
后来, 我在Resource.h中清除测试时使用的临时ID, 发现6个控件只有5个ID, 没有第6个控件的IDOK, "转到定义"发现这个IDOK是预定义在Winuser.h中的, 为了避免与
预定义的冲突, 我把它改为了IDB_APP, Resource.h对应新项
"#define IDB_APP 1006"
而原来IDOK在Winuser.h中定义为1, 再看看IDB_SAVE的定义, 居然是2, 这不也是预定义的吗? 试试看把IDB_SAVE的值改为1007, 运行, 关闭Done!
事实证明, 这个花了大半天时间才搞定的问题居然是资源ID引起的.
教训:
自定义的资源ID应避免与预定义的资源ID重复(也不知道为什么VS自动生成了这么个ID, 可能是我是直接复制的"确定","取消"按钮, 而不是从工具箱拖出来的)
还有一个疑问, IDB_LOAD的值在Resource.h定义为3, 与IDABORT也是重复的, 为什么没有问题?
我创建对话框时去掉了"关于"对话框, 同时生成的对话框上包含"确定"和"取消"按钮, 也就是IDOK和IDCANCEL.
即使IDB_SAVE和IDCANCEL重复, '2'现在标识的也应该是程序的第4个PushButton, 而打印出的句柄也证实了这一点, 怎么会使主窗体的关闭失效?