MessageBox() 的插入式替换






4.26/5 (9投票s)
其他文章介绍了带有“全选是”和“全选否”附加按钮的 MessageBox() 替代方案,但您仍然需要编写代码来处理这些按钮。本文介绍了一个类,它完成了所有工作。

引言
MessageBox() 在您想向用户提出“是/否”问题以在执行某些操作之前获得确认时很有用。如果您的应用程序一遍又一遍地执行操作,那么拥有额外的“全选是”和“全选否”按钮可能会很有用。
以下文章介绍了带有“全选是”和“全选否”附加按钮的 MessageBox() 替代方案
但它们只讲了一半故事,即如何添加新按钮。根据按钮点击情况,仍然需要编写代码来决定是否执行操作。
本文介绍 CRHYesNoToAllDialog 类,其中包含所有这些附加代码。CRHYesNoToAllDialog::CRHMessageBox() 是 ::MessageBox() 或 CWnd::MessageBox() 的即插即用替代品。只需更改四行代码,即可使用 CRHMessageBox 而不是 MessageBox。
背景
让我们看一下一个已经有“全选是”按钮的 Windows 对话框。“确认文件删除”对话框会在您尝试删除只读文件时出现,它有四个按钮:“是”、“全选是”、“否”和“取消”。但是这个对话框有许多令人不快的特性
- 它在每个问题之后消失,并在下一个问题时重新出现。
- 如果您点击“全选是”,它会消失,但会打开一个新的“正在删除...”对话框,显示一个进度条和一个“取消”按钮。
- 如果您随后点击“取消”,您将无法继续,必须重新开始。
CRHMessageBox() 将这两个对话框合并为一个
- 它有“是”、“否”、“全选是”、“全选否”、“停止”和“取消”按钮。
- 它有一个进度条。
- 它不会在每个问题之后消失并在下一个问题时重新出现。
- 如果您点击“全选是”,它不会消失。“停止”按钮会被启用,您可以点击它来停止操作。
- 如果您这样做,对话框仍然不会消失,但允许您从已经进行到的地方继续。
- 只有在 CRHYesNoToAllDialog对象被销毁时,它才会消失。
按钮的效果是让 CRHYesNoToAllDialog::CRHMessageBox() 返回以下值
- “是” - 返回 IDYES。
- “否” - 返回 IDNO。
- “全选是” - 进入自动应答模式,每次调用 CRHYesNoToAllDialog::CRHMessageBox()时返回IDYES,直到点击“停止”或“取消”。
- “全选否” - 与“全选是”相同,但返回 IDNO。
- “停止” - (仅在点击“全选是”或“全选否”后可点击) - 停止自动返回 IDYES或IDNO,重新开始提问。
- “取消” - 如果点击了“全选是”或“全选否”,则效果与“停止”相同,否则返回 IDCANCEL。
- 按键盘上的 Esc 键效果与点击“取消”相同。
示例程序
本文附带的示例程序只是将数字添加到列表中,首先询问您是否添加每个数字。

不同的按钮允许您比较 ::MessageBox()、CWnd::MessageBox() 和 CRHYesNoToAllDialog::CRHMessageBox() 的效果。
如果您点击“全选是”,您可能会发现 CRHMessageBox 能够很快完成工作并关闭。如果是这样,请尝试在“列表长度:”框中输入一个较大的数字。
每次提出的问题(“确定将 i 添加到列表中?”)在示例程序中用“blah”进行填充,以显示 CRHMessageBox 如何扩展以适应它,但它永远不会缩小,因为这可能会使其在自动应答模式下不可读。
更改四行代码
在示例程序中,使用 ::MessageBox() 的代码是
void CRHYesNoToAllDialogAppDlg::UseMessageBox(int NLines)
{
    CString vCString;
    for (int i = 1; i <= NLines; i++)
    {
        PrepareTheQuestion(i, &vCString);
        int rc = ::MessageBox(m_hWnd, vCString,
                              "::MessageBox",
                              MB_ICONQUESTION | MB_YESNOCANCEL);
        if (rc == IDCANCEL)
            break;
        if (rc == IDYES)
            AddToList(i);
    }
}
使用 CWnd::MessageBox() 的代码在一行上有所不同
int rc = MessageBox(vCString,
                    "CWnd::MessageBox",
                    MB_ICONQUESTION | MB_YESNOCANCEL);
要使用 CRHYesNoToAllDialog::CRHMessageBox(),您必须更改下面用**粗体**标记的四行代码
#include "CRHYesNoToAllDialog.h"
...
void CRHYesNoToAllDialogAppDlg::UseCRHYesNoToAllDialog(int NLines)
{
    CString vCString;
    CRHYesNoToAllDialog db(false, this);
    for (int i = 1; i <= NLines; i++)
    {
        PrepareTheQuestion(i, &vCString);
        int rc = db.CRHMessageBox(vCString,
                                  "CRHYesNoToAllDialog::CRHMessageBox",
                                  MB_ICONQUESTION | MB_YESNOCANCEL);
        if (rc == IDCANCEL)
            break;
        if (rc == IDYES)
            AddToList(i);
       
        db.CRHUpdateProgressBar(i, NLines);
    }
   
    // db goes out of scope and the CRHMessageBox disappears
}
您只需要做这些。
使用 CRHYesNoToAllDialog 和 CRHMessageBox()
<!-- 在您的应用程序中使用CRHYesNoToAllDialog 和 CRHMessageBox() 必须- 将以下文件添加到您的项目中- CRHYesNoToAllDialog.cpp
- CRHYesNoToAllDialog.h
 
- 在您当前使用 ::MessageBox()或CWnd::MessageBox()的 .cpp 文件中,按上述描述更改 4 行代码。
CRHYesNoToAllDialog 的接口如下
class CRHYesNoToAllDialog : public CDialog
{
public:
    CRHYesNoToAllDialog(bool Topmost, CWnd* pParent = NULL);
    virtual ~CRHYesNoToAllDialog();
    int CRHMessageBox(LPCTSTR lpszText, 
        LPCTSTR lpszCaption = NULL, UINT nType = MB_OK);
    void CRHUpdateProgressBar(int SoFar, int Total);
};
CRHYesNoToAllDialog::CRHYesNoToAllDialog() 几乎是您在使用 ClassWizard 添加基于 CDialog 的新类时得到的标准构造函数。新参数 Topmost 可以设置为 true,以使 CRHMessageBox 成为一个置顶窗口(这在父窗口是置顶窗口时很有用,而您不希望 CRHMessageBox 弹出在它后面!)。
CRHYesNoToAllDialog::CRHMessageBox() 的接口与 CWnd::MessageBox() 相同。但是,因为 CRHMessageBox 始终具有相同的按钮,所以 nType 中只有以下值才有效
    MB_DEFBUTTON1
    MB_DEFBUTTON2
    MB_DEFBUTTON3
    MB_DEFBUTTON4
    MB_DEFBUTTON5
   
    MB_ICONEXCLAMATION, MB_ICONWARNING
    MB_ICONINFORMATION, MB_ICONASTERISK
    MB_ICONQUESTION
    MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND
这些都与 CWnd::MessageBox() 或 ::MessageBox() 的作用相同。Windows 没有 MB_DEFBUTTON5,所以这个值在 CRHYesNoToAllDialog.h 中被 #define 了。
CRHYesNoToAllDialog::CRHUpdateProgressBar() 按预期更新 CRHMessageBox 中的进度条。
CRHMessageBox 的行为
CRHMessageBox 试图尽可能模仿 MessageBox 的行为。但是,因为它不会一直消失和重新出现,所以存在以下差异
- 第一次调用 CRHYesNoToAllDialog::CRHMessageBox()时传递的nType值用于确定是否以及显示哪个图标。后来调用的值在图标方面被忽略(尽管MB_DEFBUTTONn值每次都会被使用)。
- CRHMessageBox的大小由问题所需的空间和是否显示图标决定。如果问题在稍后调用- CRHYesNoToAllDialog::CRHMessageBox()时变大,那么- CRHMessageBox将会扩展以适应它,但它永远不会缩小,因为这可能会使其在自动应答模式下不可读。示例程序演示了这种行为。
- 使用 MessageBox时,点击“取消”会关闭MessageBox并返回IDCANCEL。使用CRHMessageBox时,点击“取消”将返回IDCANCEL,除非点击了“全选是”或“全选否”,在这种情况下,它与点击“停止”的效果相同。再次点击“取消”将返回IDCANCEL。
- 点击任何按钮时,MessageBox都会关闭。CRHMessageBox不会。只有当CRHYesNoToAllDialog对象被销毁时,CRHMessageBox才会消失。
- CRHMessageBox在第一次调用- CRHYesNoToAllDialog::CRHMessageBox()时出现,而不是在- CRHYesNoToAllDialog对象实例化时出现。这是为了防止在未调用- CRHYesNoToAllDialog::CRHMessageBox()时- CRHMessageBox短暂闪烁。在示例程序中,尝试将列表大小设置为 0。
- MessageBox有时会换行问题,如果它认为问题太长。- CRHMessageBox不会。调用- CRHYesNoToAllDialog::CRHMessageBox()的人需要自行在问题字符串中放置 '\n' 字符来控制换行位置。这使您可以更好地控制对话框的外观。
关注点
无需资源
CRHYesNoToAllDialog::CRHMessageBox() 创建一个空的对话框,然后向其中添加控件。这是为了更方便地在您的程序中使用 CRHMessageBox(您无需导入任何资源)。但我唯一能让它工作的方式是调用 CDialog::CreateIndirect(),它需要一个 DLGTEMPLATE 参数。这非常麻烦,但似乎有效。
OnEraseBkgnd()
为了防止在点击“全选是”或“全选否”后按钮闪烁或消失,当 CRHMessageBox 正在自动回答问题时,CRHYesNoToAllDialog::OnEraseBkgnd() 不会擦除背景。
这就导致了唯一一个我未能解决的问题——如果您开始一个漫长的操作序列,然后点击“全选是”或“全选否”,然后将 CRHMessageBox 隐藏在某个其他应用程序的窗口后面,然后最小化该窗口,CRHMessageBox 的背景和按钮将不会重绘。但是您仍然可以通过按空格键或回车键来按下“停止”(因为键盘焦点在“停止”按钮上)。
进度条同样忽略 OnEraseBkgnd() 以防止大量闪烁。
无疑,所有这些问题都可以通过使用内存设备上下文来解决。这留给读者作为练习。
<!--CRHMessageBox 是模态还是非模态对话框?
它是模态的。当它在那里等待您点击按钮时,它绝对是模态的。但只要您点击一个按钮,它就会停留在那儿等待 CRHYesNoToAllDialog::CRHMessageBox() 再次被调用或 CRHYesNoToAllDialog 对象被销毁。当它等待时,调用 CRHYesNoToAllDialog::CRHMessageBox() 的代码仍在忙于处理之前的消息,并很快会再次调用 CRHYesNoToAllDialog::CRHMessageBox() 或销毁 CRHYesNoToAllDialog 对象。因此,应用程序将不会响应任何键盘或鼠标尝试将焦点移至其其他窗口的尝试。所以 CRHMessageBox 仍然是模态的。
如果您使用 new 创建一个 CRHYesNoToAllDialog 对象,并在 CRHMessageBox 不等待输入时将其保留在那里,您会发现它拒绝被停用,您的应用程序将会锁定。请勿这样做。-->
致谢
非常感谢以下人员
- Hans Dietrich 提供了 这篇文章,它教会了我如何使用 ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ...)来了解消息框应该使用哪种字体。
- Lim Bio Liong 提供了 他的优秀程序,使我能够捕获上面的窗口快照。
- Nish Nishant 提供了关于“让您的对话框保持在顶层”的 技巧,并且还提供了 这篇文章,其中解释了 CWnd::RunModalLoop()在模态对话框中的作用。没有这些帮助,我就无法写出这篇文章。
历史
- 2005 年 1 月 2 日 - 首次提交。


