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.
|
|
Fixed centering problem when checkbox is specified.
|
|
Replaced checkbox width calculation with one based on DLUs as specified in
Visual Design Specifications and Guidelines.
|
|
Added function
XMessageBoxGetCheckBox()
to check if a checkbox value has been stored in registry or ini file.
|
|
Added countdown indicator to caption when disabled timer is used.
See Disabled Buttons example in
Gallery dialog:
Note: you can define
XMESSAGEBOX_NO_DISABLED_COUNTDOWN to prevent this display.
|
|
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.
|
What's New in Version 1.7
|
Converted button sizes to dialog units. This will help in dealing with large fonts.
|
|
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:
|
|
Added Gallery dialog to preview individual XMessageBox options:
|
What's New in Version 1.6
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_CONTINUEABORT - creates message box with Continue and Abort buttons.
|
|
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.
|
|
MB_SKIPSKIPALLCANCEL - creates message box with Skip, Skip All, and Cancel buttons.
|
|
MB_IGNOREIGNOREALLCANCEL - creates message box with Ignore, Ignore All, and Cancel buttons.
|
|
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.
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
IDCANCEL value 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:
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)
{
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.
|
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.
|
|
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:
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
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();
}
}
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
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.