Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A drop-in replacement for MessageBox()

0.00/5 (No votes)
4 Jan 2005 1  
Other articles describe replacements for MessageBox() with extra buttons for Yes to All and No to All, but you still have to write the code to handle those buttons. This article presents a class that does all the work for you.

A dialog box that handles Yes / No / Yes to All / No to All.

Introduction

MessageBox() is useful when you want to ask the user a Yes/No question to get confirmation before performing some action. If your application is performing the action over and over again, it can be useful to have additional "Yes to All" and "No to All" buttons.

The following articles describe replacements for MessageBox() with extra buttons for "Yes to All" and "No to All":

But they tell only half the story, which is how to add the new buttons. Someone still has to write the code to decide whether or not to do the actions, based on those button clicks.

This article presents the CRHYesNoToAllDialog class, which contains all this extra code. CRHYesNoToAllDialog::CRHMessageBox() is a drop-in replacement for ::MessageBox() or CWnd::MessageBox(). Only four lines of code need to be changed to use a CRHMessageBox instead of a MessageBox.

Background

Let's look at a Windows dialog box that already has a "Yes to All" button. The "Confirm File Delete" dialog box that appears when you try to delete read-only files, has four buttons, "Yes", "Yes to All", "No", and "Cancel". But this dialog box has many unpleasant features:

  • It disappears after each question and reappears for the next one.
  • If you click on "Yes to All", it disappears, but a new "Deleting ..." dialog box opens showing a progress bar and a "Cancel" button.
  • If you then click on "Cancel", you can't resume, you have to start again.

CRHMessageBox() combines both these dialog boxes into one:

  • It has "Yes", "No", "Yes to All", "No to All", "Stop", and "Cancel" buttons.
  • It has a progress bar.
  • It does not disappear after each question and reappear for the next one.
  • If you click on "Yes to All", it does not disappear. The "Stop" button gets enabled, and you can click on it to stop the operation.
  • If you do that, the dialog box still does not disappear, but allows you to continue from however far it's got.
  • It disappears only when the CRHYesNoToAllDialog object is destroyed.

The effect of the buttons is to make CRHYesNoToAllDialog::CRHMessageBox() return the following values:

  • "Yes" - return IDYES.
  • "No" - return IDNO.
  • "Yes to All" - go into autoanswer mode, returning IDYES every time CRHYesNoToAllDialog::CRHMessageBox() is called until "Stop" or "Cancel" is clicked.
  • "No to All" - same as "Yes to All" but returning IDNO.
  • "Stop" - (only clickable after "Yes to All" or "No to All" is clicked) - stop automatically returning IDYES or IDNO, start asking again.
  • "Cancel" - if "Yes to All" or "No to All" has been clicked, then do the same as "Stop", otherwise return IDCANCEL.
  • Hitting the Esc key on the keyboard has the same effect as clicking on "Cancel".

The example program

The example program accompanying this article simply adds numbers to a list, asking you first whether to add each one.

The example program

Different buttons allow you to compare the effects of ::MessageBox(), CWnd::MessageBox() and CRHYesNoToAllDialog::CRHMessageBox().

If you click on "Yes to All", you might find that the CRHMessageBox does its job and closes very quickly. If that's the case, then try a larger number in the "Length of list:" box.

The question being asked each time ("Ok to add i to list?") is padded out with "blah"s in the example program to show how the CRHMessageBox expands to accommodate it, but doesn't ever shrink, because that would probably make it unreadable in autoanswer mode.

Change four lines of code

In the example program, the code using ::MessageBox() is:

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);
    }
}

The code using CWnd::MessageBox() differs in one line:

int rc = MessageBox(vCString,
                    "CWnd::MessageBox",
                    MB_ICONQUESTION | MB_YESNOCANCEL);

To use CRHYesNoToAllDialog::CRHMessageBox(), you must change the four lines marked in bold below:

#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
}

That's all you have to do.

Using CRHYesNoToAllDialog and CRHMessageBox()

<!-- To use CRHYesNoToAllDialog and CRHMessageBox() in your application you must:
  • Add the following files to your project:
    • CRHYesNoToAllDialog.cpp
    • CRHYesNoToAllDialog.h
  • In the .cpp file where you currently use ::MessageBox() or CWnd::MessageBox(), change the 4 lines as described above.
-->

The interface to CRHYesNoToAllDialog is as follows:

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() is almost the standard constructor you get when using ClassWizard to add a new class based on CDialog. The new parameter Topmost can be set to true to make the CRHMessageBox a topmost window (this is useful if the parent window is a topmost window, and you don't want the CRHMessageBox to pop up behind it!).

CRHYesNoToAllDialog::CRHMessageBox() has the same interface as CWnd::MessageBox(). But because a CRHMessageBox always has the same buttons, the only values of nType that have any effect are:

    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

These all have the same affect as they do with CWnd::MessageBox() or ::MessageBox(). Windows does not have a MB_DEFBUTTON5, so this is #defined in CRHYesNoToAllDialog.h.

CRHYesNoToAllDialog::CRHUpdateProgressBar() updates the progress bar in the CRHMessageBox, as you might expect.

Behavior of a CRHMessageBox

A CRHMessageBox tries to copy closely the behavior of a MessageBox. But because it doesn't disappear and reappear all the time, there are the following differences:

  • The value of nType passed in to CRHYesNoToAllDialog::CRHMessageBox() the first time it's called is used to determine whether an icon appears, and which one. The value passed in to later calls is ignored as far as the icon is concerned (although the MB_DEFBUTTONn values are used every time).
  • The size of the CRHMessageBox is determined by the space required for the question and whether an icon is displayed. If the question gets bigger in later calls to CRHYesNoToAllDialog::CRHMessageBox(), then the CRHMessageBox will expand to accommodate it, but it doesn't ever shrink, because that would probably make it unreadable in autoanswer mode. This behavior is demonstrated by the example program.
  • With a MessageBox, clicking on "Cancel" closes the MessageBox and returns IDCANCEL. With a CRHMessageBox, clicking on "Cancel" returns IDCANCEL unless "Yes to All" or "No to All" has been clicked, in which case, it has the same effect as clicking "Stop". Another click on "Cancel" will then return IDCANCEL.
  • A MessageBox closes when you click on any of the buttons. A CRHMessageBox does not. It's only when the CRHYesNoToAllDialog object is destroyed that the CRHMessageBox disappears.
  • The CRHMessageBox appears the first time that CRHYesNoToAllDialog::CRHMessageBox() is called, not when the CRHYesNoToAllDialog object is instantiated. This is to prevent a CRHMessageBox flashing up briefly if CRHYesNoToAllDialog::CRHMessageBox() is not called. In the example program, try a list size of 0.
  • A MessageBox sometimes wraps the question if it thinks it's too long. A CRHMessageBox does not. It's up to the caller of CRHYesNoToAllDialog::CRHMessageBox() to place '\n' characters in the question string where it wants the question to wrap. This gives you more control over the appearance of the dialog box.

Points of Interest

No resources needed

CRHYesNoToAllDialog::CRHMessageBox() creates an empty dialog box and then adds the controls to it. This is to make it easier to use a CRHMessageBox in your program (you don't have to import any resources). But the only way I could get this to work was to call CDialog::CreateIndirect(), which takes a DLGTEMPLATE parameter. This is very messy, but it seems to work.

OnEraseBkgnd()

In order to prevent the buttons from flashing or disappearing after "Yes to All" or "No to All" is clicked, CRHYesNoToAllDialog::OnEraseBkgnd() doesn't erase the background while the CRHMessageBox is auto-answering the questions.

This gives rise to the only problem that I haven't been able to resolve - if you start a lengthy sequence of operations, then click on "Yes to All" or "No to All", then hide the CRHMessageBox behind some other application's window, then minimize that window, the background and buttons of the CRHMessageBox are not repainted. But you can still hit Space or Enter to press "Stop" (because the keyboard focus is on the "Stop" button).

The progress bar similarly ignores OnEraseBkgnd() to prevent lots of flashing.

No doubt all these problems could be solved using memory device contexts. This is left as an exercise for the reader.

<!--

Is a CRHMessageBox a modal or modeless dialog box?

It's modal. While it's sitting there waiting for you to click a button it's certainly modal. But as soon as you click a button it stays there waiting for CRHYesNoToAllDialog::CRHMessageBox() to be called again or for the CRHYesNoToAllDialog object to be destroyed. While it's waiting the code that called CRHYesNoToAllDialog::CRHMessageBox() is still busy processing some earlier message and will shortly call CRHYesNoToAllDialog::CRHMessageBox() again or destroy the CRHYesNoToAllDialog object. So the application will not respond to any keyboard or mouse attempts to move the focus to another of its windows. So the CRHMessageBox is still modal.

If you use new to create a CRHYesNoToAllDialog object and leave it lying around while the CRHMessageBox is not waiting for input then you will find that it refuses to be deactivated and your application will lock up. Don't do it. -->

Acknowledgments

Thank you very much to the following people:

History

  • 2nd January 2005 - first submitted.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here