"Do not ask again" Message Boxes now appear in a lot of applications. Doing individual dialogs for each message box turns out to be a full nightmare that I did not even try myself! This article proposes a very straight way to implement these: with a Windows hook. The idea is to catch the creation of message boxes (see below how this is done) and then add the necessary controls (a separator and a checkbox) to the message box after having resized it. As Shog9 suggested I am going to do this in two different ways.
First way: Hooking
Hooking Message Boxes
Windows hooking is a nice feature WIN32 offers us: it allows us to intercept a lot of system calls especially regarding window management. In particular, we are able to catch the
WM_INITDIALOG message for every dialog our application opens: our own dialog classes but more important Common Control dialogs or whatever. When we catch the
WM_INITDIALOG for a new dialog, we can decide to subclass it or not. When we subclass it, a function of our own will be called each time the dialog gets a message (
WM_COMMAND for instance).
Detecting a new message box is not an exact science: once it is created it can't be differentiated from another dialog. The method used here is simply to inspect all the children of the dialogs. The criteria for a dialog to be a message box is: 1 static icon, 1 static text, between 1 and 3 buttons and no control of any other type. We use the
EnumChildWindows function to accomplish this. Moreover we check that the static text is one we have recorded before opening the message box.
Once a message box has been detected we subclass it. Our subclassing performs the following on
- Resize the window to hold the new controls (separator line and checkbox)
- If the width of the window was changed, then move the buttons so that they still appear centered
- Create the new controls
It also catches
WM_COMMAND to handle mouse click on the checkbox. The cleanup (some
delete) is done on
WM_NCDESTROY which is the very last message we get for the dialog.
Before being able to use the hook you must install it. Best place to do this is in the
InitInstance of your application. In the same way, the hook should be uninstalled in the
ExitInstance of your application. Two utility methods are provided to do this:
UninstallMessageBoxHook(). The install function takes two optional parameters that are the label of the checkbox: this is for easy localization.
The usage is very straightforward using the new function
NabMessageBox() which replaces
AfxMessageBox(), but that keeps the same interface. Whenever you want a dialog box to have a "Do Not Ask Again" checkbox, simply use
NabMessageBox() instead of
AfxMessageBox(). The return code will be exactly the same except that it will be opposite (negative I mean) if the "Do not..." box was checked (eg.
-MB_OK instead of
Second way: MFC subclassing
Subclassing Message Boxes
The way suggested by Shog9 is to use the MFC subclassing and have a class encapsulate the whole subclassing. This leads to a much much cleaner code. This relies on MFC hooking through the
AfxHookWindowCreate function. With this, we redirect the message box messages to us and therefore are able to catch (once more)
WM_INITDIALOG. Here we resize the window, add the control and so on...
The big advantage is that our class is in a way the message box itself. Therefore we do not have to detect if we are a message box by inspecting our children, our any of the fancy tricks used in the 1st way. Also the click on the button is simply handled with a normal
ON_COMMAND entry in the message map.
There is no need to install or uninstall a hook with this method. Simply instantiate a
CnbMessageBox passing the parent, then call
MessageBox() passing the same arguments as to
AfxMessageBox(). To know if the "Do not ... again" box was checked simply call
GetChecked() on the dialog.
In both cases it is your responsibility to uniquely identify each prompt of your application and serialize (in the registry or in a file) the "do not ask again" answers, that were made to make sure to not prompt the same message twice!
You will probably need a unique persistent object (a singleton) that will load the answer when your app starts and save them on exit. Each time you want to display a message box, then something like this should be called (this is for the hooking way):
int CMessagePrompter::DoMessageBox(CString strMessage,
int nType, int nUniqueId)
int rc = NabMessageBox(strMessage, nType);
Hooking exhibits a big limitation: you cannot display two message boxes with the exact same text at the same time. You can display two message boxes (from different threads) without any problem though. The subclassing routine uses this text to know if it is supposed to modify the dialog or not. If two message boxes with the exact same text are displayed at the same time, then we will run into problems. Other than that, everything seems to work fine!
The MFC subclassing does not show any problem so it is probably better :-)
We have seen two simple ways to implement such message boxes in an application. The MFC subclassing method has the big advantage of having a much more simple and cleaner code. So it is up to you to decide!