Introduction

In any application with many features and non-trivial user interface,
it is necessary to display some message boxes. Sometimes you want to ask user,
"Do you want to continue or stop?". Unfortunately standard Windows
MessageBox()
does not have this option, unless you carefully word message so that
it can be answered with a yes or no. This leaves user re-reading your
message and thinking, "OK, if I hit Yes what will happen?"

Another situation is when you ask or tell the user something, like
"Do you really want to quit?" Some users get very annoyed at seeing this
message all the time, while others like having the extra
confirmation step. In some applications, it is often you will see
a checkbox that says "Do not ask me again" or maybe
"Do not tell me again". When the user clicks checkbox,
he will no longer be bothered with that message. So, software begins
to act more like what the user wants, instead of
what the programmer thinks best.

I got tired of doing custom dialogs to provide these features.
They took time to write, time to hook into application, and were
mostly non-reusable.
This article discusses a more general solution:
MessageBox(),
which is a fairly complete implementation of the Windows
MessageBox() API,
with some new features that make it more flexible.

What's New in Version 1.8

Added new bit flag VistaStyle to XMSGBOXPARAMS::dwOptions.
Setting this option bit will cause the message background to be painted with the
current window color (typically white), the buttons to be right-justified,
the position of icon and message to be adjusted,
and the buttons to be slightly bigger.

Example:

On Vista, this looks like:

The default on Vista is to automatically enable this option. You can
comment out the definition of XMESSAGEBOX_AUTO_VISTA_STYLE
(in XMessageBox.cpp) to prevent
this option from being automatically applied.

Added new bit flag Narrow to XMSGBOXPARAMS::dwOptions.
Setting this option bit will cause the message box to be no wider than SM_CXSCREEN / 3
(unless too many buttons force it to be wider).

Added two new members to XMSGBOXPARAMS: crText and
crBackground, allowing you to specify the message text and background colors.

Made all buttons auto-size, depending on button text. Buttons will
have the usual width, unless the text is too long to fit.

Eliminated requirement that you define all the user-defined button strings,
even if you just wanted to change a few strings.
All the button strings that you do not specify will have their default value.
The User Defined Buttons
example in the Gallery dialog
shows how to do this.

XMessageBox Features

Drop-in replacement for standard MessageBox() - can be used as a simple replacement for MessageBox(): the first four parameters are identical. Or, you can use the fifth parameter to pass the optional XMSGBOXPARAMS struct.

MB_DONOTASKAGAIN and MB_DONOTTELLAGAIN - creates message box with "Don't ask me again" or "Don't tell me again" checkbox. The user's response can be automatically saved to either the registry or an ini file.

MB_YESTOALL - add "Yes to all" button.

MB_NOTOALL - add "No to all" button.

MB_NORESOURCE - including this flag will prevent XMessageBox() from attempting to load string resources (button captions) from resources.

MB_NOSOUND - including this flag will cause XMessageBox() to suppress sounds.

MB_DONOTSHOWAGAIN - creates message box with "Don't show again" checkbox. The user's response can be automatically saved to either the registry or an ini file.

Strings loaded from specified HINSTANCE - specifying a string resource will cause XMessageBox() to load strings from that resource module.

lpszMessage and lpszCaption parameters may be resource ids - lpszMessage and lpszCaption parameters may be either pointers to strings or resource ids, passed using the MAKEINTRESOURCE() macro.

Custom icon - creates message box with a custom icon. Sound may be specified for the custom icon by using one of MB_ICON* flags.

Customizable Report button - adds a customizable "Report" button to the message box. This button will cause a user-supplied callback function to be invoked.

Countdown timer for default button - setting the nTimeoutSeconds member to a positive value will cause XMessageBox() to display a countdown timer on the default button. When the timeout expires, the default button id (OR'd with MB_TIMEOUT) will be returned as if the user had pressed the button.

Disabled time parameter - disables all buttons on the messagebox for n seconds (for nag dialogs).

Custom buttons with user-specified captions - up to four custom buttons may be specified.

Initial x,y coordinates - if the x,y members are specified, the message box will be displayed at these screen coordinates.

Right-justify buttons - the buttons will be right-justified, like the XP Explorer dialogs.

Automatically save state of Do Not Ask/Tell checkboxes - saves the user selection if the checkbox is selected. Depending on how XMessageBox is compiled, the checkbox selection will be saved to the registry or to an ini file.

The XMessageBox() Function

The XMessageBox() function is very similar to Windows' MessageBox() API.
In fact, the first four parameters are the same, while an optional fifth parameter allows you to
access extended parameters. Here is the function prototype:

XMessageBox

The XMessageBox function creates, displays, and operates a message box.
The message box contains an application-defined message and title, plus any combination
of predefined icons, push buttons, and checkboxes.

Adds a button with the caption "Yes to All". If the user clicks the button, the result code will be IDYESTOALL.

To display an icon in the message box, specify one of the following values:

Value

Meaning

MB_ICONEXCLAMATION, MB_ICONWARNING

An exclamation-point icon appears in the message box.

MB_ICONINFORMATION, MB_ICONASTERISK

An icon consisting of a lowercase letter i in a circle appears in the message box.

MB_ICONQUESTION

A question-mark icon appears in the message box.

MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND

A stop-sign icon appears in the message box.

To indicate the default button, specify one of the following values:

Value

Meaning

MB_DEFBUTTON1

The first button is the default button.

MB_DEFBUTTON2

The second button is the default button.

MB_DEFBUTTON3

The third button is the default button.

MB_DEFBUTTON4

The fourth button is the default button.

MB_DEFBUTTON5

The fifth button is the default button.

MB_DEFBUTTON6

The sixth button is the default button.

To specify other options, use one or more of the following values:

Value

Meaning

MB_NORESOURCE

Do not try to load button strings from resources. (See XMessageBox.h for string resource id numbers.) If this bit flag is used, English strings will be used for buttons and checkboxes. If this bit flag is not used, XMessageBox() will attempt to load the strings for buttons and checkboxes from string resources first, and then use English strings if that fails.

If the function succeeds, the return value is
one or more of the following values:

Value

Meaning

IDABORT

Abort button was selected.

IDCANCEL

Cancel button was selected.

IDCONTINUE

Continue button was selected.

IDCUSTOM1

Custom button 1 was selected.

IDCUSTOM2

Custom button 2 was selected.

IDCUSTOM3

Custom button 3 was selected.

IDCUSTOM4

Custom button 4 was selected.

IDIGNORE

Ignore button was selected.

IDIGNOREALL

Ignore All button was selected.

IDNO

No button was selected.

IDNOTOALL

No To All button was selected.

IDOK

OK button was selected.

IDRETRY

Retry button was selected.

IDSKIP

Skip button was selected.

IDSKIPALL

Skip All button was selected.

IDTRYAGAIN

Try Again button was selected.

IDYES

Yes button was selected.

IDYESTOALL

Yes To All button was selected.

MB_DONOTASKAGAIN

Returned if "Do Not Ask Again" checkbox is checked.

MB_DONOTSHOWAGAIN

Returned if "Do Not Show Again" checkbox is checked.

MB_DONOTTELLAGAIN

Returned if "Do Not Tell Again" checkbox is checked.

MB_TIMEOUT

Returned if timeout expired.

If a message box has a Cancel button, the function returns the
IDCANCELvalue if either the ESC key is pressed or the Cancel
button is selected. If the message box has no Cancel button, pressing
ESC has no effect.

XMSGBOXPARAMS Struct

XMSGBOXPARAMS

The XMSGBOXPARAMS struct defines the optional parameters for the XMessageBox API.

Specifies the help context ID for the message; 0 indicates the application's default Help context will be used.

nTimeoutSeconds

Specifies the number of seconds before the default button will be selected. During the countdown, the number of seconds left is displayed on the default button. A value of zero means there is no timeout. Note that if no MB_DEFBUTTONx is specified, the first button is the default one.

nDisabledSeconds

Specifies the number of seconds that all the buttons will be disabled - after nDisabledSeconds, all buttons will be enabled.

x, y

Specifies the initial x,y screen coordinates.

dwOptions

Specifies the options flags:

Identifier

Value

Meaning

None

0x0000

No option flags specified

RightJustifyButtons

0x0001

Buttons will be right-justified in message box, as in XP Explorer

VistaStyle

0x0002

The message background will be painted with the
current window color (typically white), and the buttons will be right-justified.
The default on Vista is to automatically enable this option. You can
comment out the definition of XMESSAGEBOX_AUTO_VISTA_STYLE
(in XMessageBox.cpp) to prevent
this option from being automatically applied.

Narrow

0x0004

The message box will be no wider than SM_CXSCREEN / 3
(unless too many buttons force it to be wider).

hInstanceStrings

If this handle is specified, it will be used to load strings for the button captions.

hInstanceIcon

If this handle is specified, it will be used to load the message box icon.

nIdIcon

Specifies the custom icon resource id.

szIcon

Specifies the custom icon resource name (if nIdIcon is 0).

nIdCustomButtons

Specifies the resource id for the custom button captions.

szCustomButtons

Specifies the strings to be used for the custom button captions (if nIdCustomButtons is 0). This string has the format

"Custom 1\nCustom 2\nCustom 3\nCustom 4".

Up to four buttons may be specified. When a custom button is clicked, it will return the value IDCUSTOM1, IDCUSTOM2, etc. When custom buttons are specified, no other buttons will be displayed.

nIdReportButtonCaption

Specifies the resource id for the report button caption.

szReportButtonCaption

Specifies the string to be used for the report button caption (if nIdReportButtonCaption is 0).

szCompanyName

Specifies the company name for the application. This is used when saving the "Do Not Ask/Tell" checkbox state in the registry or ini file.

lpszModule

Specifies the source module name for the application. This may be the actual source module name (for example, the name returned by the __FILE__ macro) or a name meaningful to the context (for example, "ConfirmFileDelete"). This is used when saving the "Do Not Ask/Tell" checkbox state in the registry or ini file.

It is up to the application to manage this registry entry. If it is necessary to clear out this entry each time the application starts up, the application must include the code to do this. All key/value pairs for XMessageBox are stored in the registry under HKEY_CURRENT_USER\Software\CompanyName\AppName\XMessageBox. If an ini file is being used, the checkbox state will be saved in the file XMessageBox.ini in the app's directory.

When the message box is displayed, if lpszModule has been specified in the XMSGBOXPARAMS struct, the message box will only be displayed if the registry entry does not exist. If the message box is displayed, and the user checks the "Do Not Ask/Tell" checkbox, the checkbox state will be saved in the registry.

nLine

Identifies the source module line number for the application. This is used when saving the "Do Not Ask/Tell" checkbox state in registry. Note that regardless of whether the lpszModule string is encoded, the line number will not be encoded.

dwReportUserData

Specifies the data that is sent to report callback function, when the report button is clicked.

lpReportFunc

Specifies the report callback function that is invoked when the report button is clicked. The report callback function has the prototype of

Specifies whether the button captions are stored in the UserDefinedButtonCaptions struct. If this parameter is TRUE, the button captions stored in the UserDefinedButtonCaptions struct will be used. The default is FALSE.

UserDefinedButtonCaptions

Struct that contains the button captions if bUseUserDefinedButtonCaptions is set to TRUE.
You only have to specify the new captions you want - other captions
will use default strings.
See XMessageBox.h for a list of the members of this struct.

The XMessageBoxGetCheckBox() Function

The XMessageBoxGetCheckBox() function allows you to check
if a value has been stored in the registry or ini file for a checkbox.
This enables you to do any necessary pre- or post-processing
for a particular call to XMessageBox().

XMessageBoxGetCheckBox

The XMessageBoxGetCheckBox function checks if a value has been stored in
the registry or ini file for one of the Do not ask/tell/show checkboxes.

DWORD XMessageBoxGetCheckBox(

LPCTSTRlpszCompanyName,LPCTSTRlpszModule,intnLine = NULL

);

Parameters

lpszCompanyName

[in] Specifies the company name for the application.
Should be the same as passed to XMessageBox().

lpszModule

[in] Specifies the source module name for the application.
Should be the same as passed to XMessageBox().

nLine

[in] Identifies the source module line number.
Should be the same as passed to XMessageBox().

Return Value

A non-zero value is returned if a checkbox value has been stored, otherwise 0.

There is also an alternate form:

XMessageBoxGetCheckBox

The XMessageBoxGetCheckBox function checks if a value has been stored in
the registry or ini file for one of the Do not ask/tell/show checkboxes.

DWORD XMessageBoxGetCheckBox(

XMSGBOXPARAMS&xmb

);

Parameters

xmb

[in] Struct that contains the company name, module and nLine parameters.
Should be the same as passed to XMessageBox().

Return Value

A non-zero value is returned if a checkbox value has been stored, otherwise 0.

Compile-Time Defines

XMESSAGEBOX_USE_PROFILE_FILE - define this identifier to store checkbox values in XMessageBox.ini. Otherwise, values will be stored in registry.

XMESSAGEBOX_DO_NOT_SAVE_CHECKBOX - define
this identifier if you do not want to automatically persist checkbox values.
If defined, it is up to the application to handle checkbox values, and determine
if message box should be displayed. If not defined, XMessageBox()
will persist checkbox values automatically, and determine if it is necessary to
display message box. If defined, the following functions will be unavailable:
encode(),
ReadRegistry(),
WriteRegistry(), and
XMessageBoxGetCheckBox().

XMESSAGEBOX_DO_NOT_ENCODE - define this identifier if you do not want to encrypt checkbox keys. You may wish to encrypt checkbox keys (which normally comprise the source module path and line number) if you do not want to expose implementation details, such as the source module name. On the other hand, by not encrypting the key, you could supply your own meaningful key name, such as "ConfirmFileDelete". Note that the encryption algorithm used is very weak.

XMESSAGEBOX_TIMEOUT_TEXT_FORMAT - this identifier specifies the format of the text displayed on the timeout button, which by default is "%s = %d" (example: OK = 10). You may change this to anything you wish, as long as 1) there is both a %s and a %d; and 2) the %s precedes the %d.

XMESSAGEBOX_INI_FILE - this identifier specifies the name of the ini file, which by default is "XMessageBox.ini".

XMESSAGEBOX_REGISTRY_KEY - this identifier specifies the registry key used to store checkbox values. By default it is "XMessageBox".

XMESSAGEBOX_NO_DISABLED_COUNTDOWN - define this identifier if you do not want to display the disabled timer countdown in the XMessageBox caption.

XMESSAGEBOX_AUTO_VISTA_STYLE - define this identifier if you want to use automatic Vista detection and style.

How To Use

To integrate this function into your own program, you first need
to add following files to your project:

XMessageBox.cpp

XMessageBox.h

If you do not include stdafx.h in XMessageBox.cpp, then you must
mark XMessageBox.cpp as not using precompiled headers
in the project settings.

Next, include the header file XMessageBox.h in the module
where you want to call XMessageBox() function. For an
example of how to call XMessageBox(),
see XMsgBoxTestDlg.cpp in the demo project,
and the examples in Gallery.cpp.

Handling the Return Code

The int return code returned by XMessageBox()
includes several pieces of information. Here's how to interpret it:
The low-order 8 bits are reserved for the button-click codes,
such a IDOK, IDCANCEL, etc.:

Code

Numeric Value

Meaning

IDOK

1

OK button was selected

IDCANCEL

2

Cancel button was selected

IDABORT

3

Abort button was selected

IDRETRY

4

Retry button was selected

IDIGNORE

5

Ignore button was selected

IDYES

6

Yes button was selected

IDNO

7

No button was selected

IDTRYAGAIN

10

Try Again button was selected

IDCONTINUE

11

Continue button was selected

IDSKIP

14

Skip button was selected

IDSKIPALL

15

Skip All button was selected

IDIGNOREALL

16

Ignore All button was selected

IDYESTOALL

19

Yes To All button was selected

IDNOTOALL

20

No To All button was selected

IDCUSTOM1

23

Custom button 1 was selected

IDCUSTOM2

24

Custom button 2 was selected

IDCUSTOM3

25

Custom button 3 was selected

IDCUSTOM4

26

Custom button 4 was selected

These are
discrete codes and should be tested for equality after
being properly masked:

This would save the return value across calls, and has the advantage of
always returning to the default state when the program is restarted.
Of course, if the return value was saved in a class variable, you could allow
the user to reset it via menu command, etc.

Saving the Return Value in the Registry

By specifying a few more parameters, you can have the return value
automatically stored in the registry, which will persist the value across
program restarts:

Here we have specified a company and module name, and so the return value
will be stored in the registry, and recalled the next time this call
to XMessageBox() is made. This will happen transparently to the
calling program - it will be just as if the message box had been displayed,
and the user clicked on the same button as before.

One note concerning the nLine parameter: In the above example,
the made-up enum constant eConfirmDelete was used
as the nLine parameter. This can be any value, including
the __LINE__ compiler built-in macro. However,
if __LINE__ is used,
modifying the source code will break the link between
the line number and what is stored in the registry.
For this reason, you may want to use an enum or some other constant
value. This constant value should be unique within a module
for each XMessageBox() call.

Working with Stored Values

Sometimes it is necessary to know if there is already a value stored in
the registry, for a particular call to XMessageBox().
In this situation you can use the XMessageBoxGetCheckBox()
function to check the registry:

Deleting Checkbox Values in the Registry

XMessageBox() does not provide any option to delete
checkbox values in the registry. If you need to do this, you can take
a look at the code in XMessageBox() that reads and writes
the registry, to see how the key is generated.

You should be
extremely careful and fully test any code that deletes registry values.

Custom Buttons

There are two ways to add custom buttons to XMessageBox: you can use
XMSGBOXPARAMS::szCustomButtons to add up to four custom buttons,
or you can use XMSGBOXPARAMS::UserDefinedButtonCaptions to
rename any of the standard XMessageBox buttons.

Using Custom Buttons via szCustomButtons

There are four buttons available via szCustomButtons. Example 1 of the
Gallery dialog shows how to use them:

To use these buttons you must first set bUseUserDefinedButtonCaptions
to TRUE, and then copy the custom button captions to the strings defined
in the UserDefinedButtonCaptions struct, for the buttons
you wish to use. Note that the code returned for the button remains the same -
if you relabel the OK button, for example, you will still
get IDOK returned.

Try It Out

The demo project provides a UI that shows the effect of various flags.
You can also compare the XMessageBox() result with
the Windows
MessageBox().

Here is what the demo app looks like:

With some simple flags selected, here is what the message box looks like:

Here is what it looks like with a checkbox added:

Here is what it looks like with a report button:

Here is what it looks like with custom buttons:

Here is what it looks like with a timeout button selected:

Implementation Notes

The message box is constructed dynamically from a
DLGTEMPLATE,
along with a
DLGITEMTEMPLATE
for each control placed on dialog.
MSDN has complete details of what these structs are. The basic idea is that you
iterate through all flags, figuring out how many buttons there are, whether
there is a checkbox, and how big message is. The most difficult thing is to
keep track of size and position of all controls.

I have added comments to the code in XMsgBoxTestDlg.cpp, so you
may see how the dialog template is created, and how to call
DialogBoxIndirect().
If you add more checkboxes or other controls, be sure your new bit flags
do not collide with ones already defined (such as MB_OK, etc.).

Implementation Notes - Version 1.8

In Version 1.6, I added Ctrl-C functionality, that allows you
to hit Ctrl-C to copy the XMessageBox contents to the clipboard,
just like the Win32 MessageBox(). Since dialog boxes
do not receive WM_KEYDOWN messages, the way I did this was to use
a keyboard
thread hook.
This worked fine, but it had an undesirable side-effect:
to support the hook function, I had to create two static variables.
I knew from postings and email I receive that XMessageBox
is being used in multi-threaded applications, so I wanted very much
to get rid of these statics. In Version 1.8, I have done so.

I mentioned that dialog boxes
do not receive WM_KEYDOWN messages. I knew that with MFC,
it is possible to use PreTranslateMessage() to trap
keyboard messages. Essentially, what MFC does is to examine messages
in the message loop, and call PreTranslateMessage(). This
gives CDialog-based code an opportunity to
handle any of these keyboard messages. The problem is that
PreTranslateMessage() is available only to MFC apps.
Since I wanted to use XMessageBox in Win32 apps,
I had to find another way.

Fortunately, Win32 has a number of APIs dealing with dialog creation and
management. The one I was using -
DialogBoxIndirectParam() -
has its own internal message loop, that I could not access. Its
function prototype is

As you can see, it was simply a matter of changing the function name, and
saving the HWND of the dialog box that is returned.

But there was still some work to do. Since
CreateDialogIndirectParam()
doesn't have a message loop, I had to supply one. This brought me to the
second problem: How to know when the dialog box ended?
Usually, you can just use EndDialog() to close
a dialog. (Or, in MFC, you call DoModal(),
and you know the dialog box is ended when DoModal()
returns.) Neither of these solved my problem: When could I exit
the message loop?

I turned again to the message functions available in Win32,
and this is the message loop I came up with:

The function IsEnded() returns the current value of a
BOOL flag,
which is set TRUE when a button is clicked,
timeout timer expires, ESC key is hit, etc. The entire message loop
executes in the context of the CXDialogTemplate class,
which means no static variables are necessary.

Acknowledgments

I began this software with an SDK sample I found several years ago.
Unfortunately after many edits I have lost the original link to the sample.

My thanks to Anne Jan Beeks for rearranging the code into classes
and eliminating all the statics.

Special thanks to Obliterator for his many comments and suggestions.

My thanks to everyone who have posted suggestions and sent me email for
improving XMessageBox.

Version 1.10 - 2008 November 29

Version 1.9 - 2008 November 22

Fixed problem in demo app where return code was
misreported as having come from registry

Version 1.8 - 2008 November 19

Added new bit flag VistaStyle to
XMSGBOXPARAMS::dwOptions, suggested by Joerg Hoffmann.
Setting this option bit will cause the message background to be painted with the
current window color (typically white), and the buttons to be right-justified.

Added new bit flag Narrow to XMSGBOXPARAMS::dwOptions.
Setting this option bit will cause the message box to be no wider than SM_CXSCREEN / 3.

Added two new members to XMSGBOXPARAMS: crText and
crBackground, allowing you to specify the message text and background colors.

Made all buttons auto-size, depending on button text. Buttons will
have the usual width, unless the text is too long to fit.

Eliminated requirement that you define all the user-defined button strings,
even if you just wanted to change a few strings.
All the button strings that you do not specify will have their default value.
The User Defined Buttons
example in Gallery shows how to do this.

Fixed centering problem when checkbox is specified

Replaced checkbox width calculation with one based on DLUs

Added function XMessageBoxGetCheckBox() to check if a checkbox
value has been stored in registry or ini file, suggested by timbolicus_prime

Version 1.4 - 2003 December 10

Added "report function" parameter for optional report function. This will cause a "Report" button to be added. The button caption of the Report button (by default, "Report") can be changed.

Added custom button parameters to allow definition of custom buttons. A resource ID or string may be specified. Thanks to Obliterator for comments and review

Added timeout parameter to automatically select default button after timeout expires, thanks to Obliterator for suggestion. The bit flag MB_TIMEOUT is OR'd with the return code when a timeout causes the return. Clicking anywhere in dialog will stop the timer.

Added custom icon parameter; also a HINSTANCE parameter for icon.

The XMessageBox dialog will now be centered even in non-MFC apps, thanks to Tom Wright for suggestion

The message text and caption text can now be passed as either a string or a resource ID (using MAKEINTRESOURCE)

The initial x,y screen coordinates can now be specified.

The buttons can now be centered (default) or right-justified, as in XP Explorer.

Gathered all optional parameters into one optional XMSGBOXPARAMS struct.

Version 1.3 - 2001 July 31

Miscellaneous improvements and bug fixes

Version 1.2 - 2001 July 13

Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

Share

About the Author

I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.