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.
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);
}
}
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 #define
d 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.