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

XMessageBox - A reverse-engineered MessageBox()

0.00/5 (No votes)
29 Nov 2008 120  
A reverse-engineered non-MFC MessageBox() that includes custom checkboxes.

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

screenshot 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:

screenshot

On Vista, this looks like:

screenshot

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.

screenshot 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).
screenshot Added two new members to XMSGBOXPARAMS: crText and crBackground, allowing you to specify the message text and background colors.
screenshot Made all buttons auto-size, depending on button text. Buttons will have the usual width, unless the text is too long to fit.
screenshot 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.
screenshot Fixed centering problem when checkbox is specified.
screenshot Replaced checkbox width calculation with one based on DLUs as specified in Visual Design Specifications and Guidelines.
screenshot Added function XMessageBoxGetCheckBox() to check if a checkbox value has been stored in registry or ini file.
screenshot Added countdown indicator to caption when disabled timer is used. See Disabled Buttons example in Gallery dialog:

screenshot

Note: you can define XMESSAGEBOX_NO_DISABLED_COUNTDOWN to prevent this display.

screenshot Added an internal message loop. See Implementation Notes - Version 1.8 for details.
screenshot Added new article sections on Handling the Return Code, Using Checkboxes, and Custom Buttons.

What's New in Version 1.7

screenshot Converted button sizes to dialog units. This will help in dealing with large fonts.
screenshot Made custom buttons auto-size, depending on button text. Custom buttons will have the usual width, unless the text is too long to fit.

Example:

screenshot

screenshot Added Gallery dialog to preview individual XMessageBox options:

screenshot

What's New in Version 1.6

screenshot Added Ctrl-C feature, similar to standard MessageBox(). This copies XMessageBox caption, message, and button text to clipboard.

Example: This message box

screenshot

is copied to clipboard as:

---------------------------
XMessageBox
---------------------------
Resistance is futile
---------------------------
Yes   No   Cancel   Don't show again   
---------------------------
screenshot Mods for 64-bit support, provided by wilfridc
screenshot Added VS2005 project
screenshot Fixed problem with Vista and NONCLIENTMETRICS struct
screenshot Fixed bug with using resource id for caption

XMessageBox Features

screenshot 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.
screenshot MB_CONTINUEABORT - creates message box with Continue and Abort buttons.
screenshot 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.
screenshot MB_YESTOALL - add "Yes to all" button.
screenshot MB_NOTOALL - add "No to all" button.
screenshot MB_NORESOURCE - including this flag will prevent XMessageBox() from attempting to load string resources (button captions) from resources.
screenshot MB_NOSOUND - including this flag will cause XMessageBox() to suppress sounds.
screenshot 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.
screenshot MB_SKIPSKIPALLCANCEL - creates message box with Skip, Skip All, and Cancel buttons.
screenshot MB_IGNOREIGNOREALLCANCEL - creates message box with Ignore, Ignore All, and Cancel buttons.
screenshot Strings loaded from specified HINSTANCE - specifying a string resource will cause XMessageBox() to load strings from that resource module.
screenshot 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.
screenshot Custom icon - creates message box with a custom icon. Sound may be specified for the custom icon by using one of MB_ICON* flags.
screenshot Customizable Report button - adds a customizable "Report" button to the message box. This button will cause a user-supplied callback function to be invoked.
screenshot 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.
screenshot Disabled time parameter - disables all buttons on the messagebox for n seconds (for nag dialogs).
screenshot Custom buttons with user-specified captions - up to four custom buttons may be specified.
screenshot Initial x,y coordinates - if the x,y members are specified, the message box will be displayed at these screen coordinates.
screenshot Right-justify buttons - the buttons will be right-justified, like the XP Explorer dialogs.
screenshot 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.

int XMessageBox(

HWND hwnd,
LPCTSTR lpszMessage,
LPCTSTR lpszCaption = NULL,
UINT nStyle = MB_OK | MB_ICONEXCLAMATION,
XMSGBOXPARAMS * pXMB = NULL
);

Parameters

hwnd
[in] Identifies the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.

lpszMessage
[in] Pointer to a null-terminated string containing the message to be displayed. lpszMessage can also be a resource ID (use MAKEINTRESOURCE).

lpszCaption
[in] Pointer to a null-terminated string used for the dialog box title. If this parameter is NULL, the default title Error is used. lpszCaption can also be a resource ID (use MAKEINTRESOURCE).

nStyle
[in] Specifies a set of bit flags that determine the contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups of flags.

To indicate the buttons displayed in the message box, specify one of the following values:

Value Meaning
MB_ABORTRETRYIGNORE The message box contains three push buttons: Abort, Retry, and Ignore.
MB_CANCELTRYCONTINUE The message box contains three push buttons: Cancel, Try Again, Continue.
MB_CONTINUEABORT The message box contains two push buttons: Continue and Abort.
MB_DONOTASKAGAIN Adds a checkbox with the caption "Don't ask me again". If the user clicks on the checkbox, the result code returned by XMessageBox will be OR'd with the bit flag MB_DONOTASKAGAIN.
MB_DONOTSHOWAGAIN Adds a checkbox with the caption "Don't show again". If the user clicks on the checkbox, the result code returned by XMessageBox will be OR'd with the bit flag MB_DONOTSHOWAGAIN.
MB_DONOTTELLAGAIN Adds a checkbox with the caption "Don't tell me again". If the user clicks on the checkbox, the result code returned by XMessageBox will be OR'd with the bit flag MB_DONOTTELLAGAIN.
MB_HELP Adds a Help button to the message box.
MB_IGNOREIGNOREALLCANCEL The message box contains three push buttons: Ignore, Ignore All, and Cancel.
MB_NOTOALL Adds a button with the caption "No to All". If the user clicks the button, the result code will be IDNOTOALL.
MB_OK The message box contains one push button: OK. This is the default.
MB_OKCANCEL The message box contains two push buttons: OK and Cancel.
MB_RETRYCANCEL The message box contains two push buttons: Retry and Cancel.
MB_SKIPSKIPALLCANCEL The message box contains three push buttons: Skip, Skip All, and Cancel.
MB_YESNO The message box contains two push buttons: Yes and No.
MB_YESNOCANCEL The message box contains three push buttons: Yes, No, and Cancel.
MB_YESTOALL 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.
MB_NOSOUND Do not play sounds when message box is displayed.
MB_SETFOREGROUND The message box becomes the foreground window.
MB_SYSTEMMODAL Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate attention
MB_TOPMOST The message box is created as a topmost window.

pXMB
[in] Pointer to optional parameters. The parameters struct XMSGBOXPARAMS (see below) is defined in XMessageBox.h.

Return Values

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.

struct XMSGBOXPARAMS {

UINT nIdHelp;
int nTimeoutSeconds;
int nDisabledSeconds;
int x, y;
DWORD dwOptions;
HINSTANCE hInstanceStrings;
HINSTANCE hInstanceIcon;
UINT nIdIcon;
TCHAR szIcon[MAX_PATH];
UINT nIdCustomButtons;
TCHAR szCustomButtons[MAX_PATH];
UINT nIdReportButtonCaption;
TCHAR szReportButtonCaption[MAX_PATH];
TCHAR szCompanyName[MAX_PATH];
LPCTSTR lpszModule;
int nLine;
DWORD dwReportUserData;
XMESSAGEBOX_REPORT_FUNCTION lpReportFunc;
COLORREF crText;
COLORREF crBackground;
BOOL bUseUserDefinedButtonCaptions;
CUserDefinedButtonCaptions UserDefinedButtonCaptions;
};

Members

nIdHelp

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
void (* XMESSAGEBOX_REPORT_FUNCTION)(LPCTSTR lpszMessageText, DWORD dwUserData)
where lpszMessageText is the text displayed in the message box, and dwUserData is the value specified by dwReportUserData.

A typical use of the report callback function is to write to a log file or notify IT via email. For a ready-to-use email function, see the file SendEmail.cpp in XCrashReport : Exception Handling and Crash Reporting - Part 4.

crText

Specifies the message text color.

crBackground

Specifies the message background color.

bUseUserDefinedButtonCaptions

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(

LPCTSTR lpszCompanyName,
LPCTSTR lpszModule,
int nLine = 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:

    if ((rc & 0xFF) == IDOK)

Other information returned includes the following bit values:

Code Numeric Value Meaning
MB_DONOTASKAGAIN 0x01000000 Checkbox "Do not ask me again" was checked
MB_DONOTTELLAGAIN 0x02000000 Checkbox "Do not tell me again" was checked
MB_DONOTSHOWAGAIN 0x04000000 Checkbox "Do not show again" was checked
MB_TIMEOUT 0x80000000 Timeout expired
These bit values should be tested like this:
    if (rc & MB_DONOTASKAGAIN)

Using Checkboxes

Saving the Return Value Locally

Here is a simple example of using XMessageBox() with a checkbox:
    int rc = XMessageBox(m_hWnd, 
        _T("Captain! We have been hit! Shields are at 10%."),
        _T("Example 5"), 
        MB_OK | MB_DONOTTELLAGAIN | MB_ICONINFORMATION);

    if (rc & MB_DONOTTELLAGAIN)
    {
        TRACE(_T("MB_DONOTTELLAGAIN\n"));
    }

which is one of the examples from the Gallery dialog:

screenshot

This example does not store the checkbox value or button click in the registry. To handle the return value, you could save it in a class variable or local static. For example, you could write:

    static int rc = 0;
    if ((rc & MB_DONOTTELLAGAIN) == 0)
    {
        // call XMessageBox if checkbox not checked previously
        rc = XMessageBox(m_hWnd, 
            _T("Captain! We have been hit! Shields are at 10%."),
            _T("Example 5"), 
            MB_OK | MB_DONOTTELLAGAIN | MB_ICONINFORMATION);
    }

    if ((rc & 0xFF) == IDOK)
    {
        [ process OK return ]
    }

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:
    XMSGBOXPARAMS xmb;
    _tcscpy(xmb.szCompanyName, _T("MyCompany"));
    xmb.lpszModule = _T(__FILE__);
    xmb.nLine = eConfirmDelete;
    int rc = XMessageBox(m_hWnd, 
        _T("Are you sure you want to delete that?"),
        _T("MyProgram"), 
        MB_OKCANCEL | MB_DONOTASKAGAIN | MB_ICONQUESTION, &xmb);

    if ((rc & 0xFF) == IDOK)
    {
        [ process OK return ]
    }

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:
    if (XMessageBoxGetCheckBox(_T("MyCompany"), _T(__FILE__), eConfirmDelete))
    {
        [ do some preprocessing ]
    }

    XMSGBOXPARAMS xmb;
    _tcscpy(xmb.szCompanyName, _T("MyCompany"));
    xmb.lpszModule = _T(__FILE__);
    xmb.nLine = eConfirmDelete;
    int rc = XMessageBox(m_hWnd, 
        _T("Are you sure you want to delete that?"),
        _T("MyProgram"), 
        MB_OKCANCEL | MB_DONOTASKAGAIN | MB_ICONQUESTION, &xmb);

    if ((rc & 0xFF) == IDOK)
    {
        [ process OK return ]
    }

It may be more convenient to use the alternate form of XMessageBoxGetCheckBox():

    XMSGBOXPARAMS xmb;
    _tcscpy(xmb.szCompanyName, _T("MyCompany"));
    xmb.lpszModule = _T(__FILE__);
    xmb.nLine = eConfirmDelete;

    if (XMessageBoxGetCheckBox(xmb))
    {
        [ do some preprocessing ]
    }

    int rc = XMessageBox(m_hWnd, 
        _T("Are you sure you want to delete that?"),
        _T("MyProgram"), 
        MB_OKCANCEL | MB_DONOTASKAGAIN | MB_ICONQUESTION, &xmb);

    if ((rc & 0xFF) == IDOK)
    {
        [ process OK return ]
    }

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.

screenshot Using Custom Buttons via szCustomButtons

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

    XMSGBOXPARAMS xmb;
    _tcscpy(xmb.szCustomButtons, _T("Copy Log to Clipboard\nOK"));
    int rc = XMessageBox(m_hWnd, 
        _T("Captain! We have been hit! Shields are at 10%."),
        _T("Example 1"), 
        MB_DEFBUTTON2, &xmb);

    if ((rc & 0xFF) == IDCUSTOM1)
    {
        TRACE(_T("Copy Log to Clipboard\n"));
    }

Note that use of the szCustomButtons buttons is mutually exclusive with use of other XMessageBox buttons such as OK, Cancel, etc. These four custom buttons return the IDCUSTOM1, ... IDCUSTOM4 codes.

screenshot Using User-Defined Buttons via UserDefinedButtonCaptions

User-defined buttons are actually standard XMessageBox buttons relabeled with custom captions.

    XMSGBOXPARAMS xmb;
    xmb.nDisabledSeconds = 5;
    xmb.bUseUserDefinedButtonCaptions = TRUE;
    _tcscpy(xmb.UserDefinedButtonCaptions.szOK, _T("Buy Now"));
    _tcscpy(xmb.UserDefinedButtonCaptions.szCancel, _T("Continue"));
    XMessageBox(m_hWnd, 
        _T("Your trial period has expired! \r\n\r\n")
        _T("Please visit www.bozosoft.com to order the registered\r\n")
        _T("product, or click the Buy Now button below."),
        _T("Example 11"), MB_OKCANCEL | MB_ICONEXCLAMATION | MB_NORESOURCE, &xmb);

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:

screenshot

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

screenshot

Here is what it looks like with a checkbox added:

screenshot

Here is what it looks like with a report button:

screenshot

Here is what it looks like with custom buttons:

screenshot

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

screenshot

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

    INT_PTR DialogBoxIndirectParam(      
        HINSTANCE hInstance,
        LPCDLGTEMPLATE hDialogTemplate,
        HWND hWndParent,
        DLGPROC lpDialogFunc,
        LPARAM dwInitParam
    );

There is another API very similar - CreateDialogIndirectParam() - that does everything that DialogBoxIndirectParam() does, except that it does not have an internal message loop. Here is its function prototype:

    HWND CreateDialogIndirectParam(      
        HINSTANCE hInstance,
        LPCDLGTEMPLATE lpTemplate,
        HWND hWndParent,
        DLGPROC lpDialogFunc,
        LPARAM lParamInit
    );

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:

    while (!IsEnded())
    {
        if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
        {
            if (msg.message == WM_QUIT) 
            {
                break;
            } 
            else if (msg.message == WM_KEYDOWN)
            {
                OnKeyDown(hDlg, msg.wParam, msg.lParam);
            }
            else if (!::IsDialogMessage(hDlg, &msg)) 
            {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
        }
        else if (!IsEnded()) 
        {
            ::WaitMessage();  // suspend thread until new message arrives
        }
    }

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.

Revision History

Version 1.10 - 2008 November 29

  • Fixed problem with XMESSAGEBOX_DO_NOT_SAVE_CHECKBOX, reported by Sims

Version 1.9 - 2008 November 22

  • Fixed keyboard processing bug introduced in 1.8 that prevented handling of Enter and Esc keys
  • 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
  • Added countdown indicator to caption when disabled timer is used
  • Added an internal message loop. See Implementation Notes - Version 1.8 for details.
  • Added new article sections on Handling the Return Code, Using Checkboxes, and Custom Buttons.

Version 1.7 - 2008 November 9

  • Converted button sizes to dialog units, suggested by Brian
  • Made custom buttons auto-size (depending on text), suggested by Albert Weinert
  • Added Gallery dialog to preview individual options

Version 1.6 - 2008 November 6

  • Added Ctrl-C feature
  • Mods for 64-bit support, provided by wilfridc
  • Added VS2005 project
  • Fixed problem with Vista and NONCLIENTMETRICS struct
  • Fixed bug with using resource id for caption

Version 1.5 - 2006 August 21

  • Fixed bugs reported by kingworm, TMS_73, Curtis Faith, ladislav Hruska, Tim Hodgson, DrJohnAir
  • Incorporated Uwe Keim's changes for dynamic button captions

Version 1.4 - 2003 December 10

  • Implemented MB_TOPMOST
  • Implemented MB_SETFOREGROUND
  • Added MB_SKIPSKIPALLCANCEL and MB_IGNOREIGNOREALLCANCEL, suggested by Shane L
  • Added HINSTANCE parameter for loading strings from extra-exe resource
  • 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.

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