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 | |
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 | |
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
Added Ctrl-C feature, similar to standard MessageBox() . This copies XMessageBox caption, message, and button text to clipboard.
Example: This message box
is copied to clipboard as: ---------------------------
XMessageBox
---------------------------
Resistance is futile
---------------------------
Yes No Cancel Don't show again
--------------------------- | |
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 |
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:
To display an icon in the message box, specify one of the following values:
To indicate the default button, specify one of the following values:
To specify other options, use one or more of the following values:
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:
If a message box has a Cancel button, the function returns the |
XMSGBOXPARAMS Struct
XMSGBOXPARAMS
The XMSGBOXPARAMS struct defines the optional parameters for the 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:
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" . 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) 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. |
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()
, andXMessageBoxGetCheckBox()
. - 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
Theint
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 |
if (rc & MB_DONOTASKAGAIN)
Using Checkboxes
Saving the Return Value Locally
Here is a simple example of usingXMessageBox()
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) { // 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.
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 toXMessageBox()
. 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.
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 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 | |
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 |
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(); // 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
toXMSGBOXPARAMS::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
toXMSGBOXPARAMS::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
andcrBackground
, 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
andMB_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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
'C & C++ > MFC 컨트롤' 카테고리의 다른 글
[Dialog] 쪼개지는 다이얼로그 (0) | 2011.04.26 |
---|---|
[Button] 이동하는 버튼, 배경이미지 바꾸기 (0) | 2011.04.26 |
[Dialog] 투명 다이얼로그 (0) | 2011.04.26 |
[Dialog] Dialog 스킨 입히기 (0) | 2011.04.26 |
[Tip] 돋보기 (0) | 2011.04.26 |
댓글