Custom MessageBox

How to customize the standard MessageBox dialog

This tutorial will show a simple technique for customizing a standard Windows message-box.

A standard message-box is not usually customizable, because you are never normally able to find it's window handle. This is because the MessageBox API call only returns control to a program when the message-box has been destroyed. Also, your program will never see any of the message-box window messages, because the MessageBox API call contains its own internal message-loop (much like the DialogBox API call).

{short description of image}

The image above shows how this technique has been used to modify the text of the standard OK button.

Window Hooks

The best way to intercept messages for a "message-box" is to install a hook. This is achieved by using the SetWindowsHookEx API call, available on all 32bit versions of Windows (9x, ME, NT, 2000, XP etc).

There are many different types of hook in Windows, each one designed for a different purpose. I don't want to review hooks in this tutorial, because there is plenty of information on MSDN. Just do a search for SetWindowsHookEx and you will find plenty of resources.

The only thing I will say about hooks is this: Windows hooks will inevitably slow your program down. It is therefore desirable to choose a hook that is called for as few events as possible, but which still gives us sufficient flexiblity to perform our customization. At the one end of the spectrum are the WH_CALLWNDPROC and WH_GETMESSAGE hooks. Hooks of this type will be called whenever a message is sent to a window. This will obviously add a significant overhead to any application, so unless you specifically need to trap all messages, hooks of this type are best avoided.

At the other end of the spectrum is the WM_CBT hook (Computer Based Training hook). This hook is called for very few window messages - specifically, window activation (WM_ACTIVATE), window creation (WM_CREATE / WM_DESTROY), window sizing (WM_SIZE / WM_MOVE) plus a few others. This type of hook is ideal for our purposes, because it will add little overhead (it will only be called occasionally), and it also provides a method for customizing a window: namely, during window creation.

Our strategy will be this:

  1. Install a CBT hook.
  2. Call MessageBox as normal.
  3. Remove the hook.

It's that simple!

Install a hook

Installing a Windows hook is really easy. The following code shows how.

HHOOK hMsgBoxHook = SetWindowsHookEx(
        WH_CBT,                // Type of hook 
        CBTProc,               // Hook procedure (see below)
        NULL,                  // Module handle. Must be NULL (see docs)
        GetCurrentThreadId()   // Only install for THIS thread!!!
        );

It is important to highlight the call to GetCurrentThreadId. It is possible to install a system-wide hook by specifying the module-handle of a DLL in which the hook procedure resides. Whenever an hooked event occurs in another process, Windows automatically loads this DLL into that process, which then enables the hook procedure to be executed in that process's address space. This is obviously quite an overhead, and is a feature that we do not require. To avoid this happening, we can specify a NULL module handle, and just install the hook for the current thread.

Removing a hook

Removing a hook is even simpler than installing it. Just call UnhookWindowsHookEx and your done!

UnhookWindowsHookEx(hMsgBoxHook);

The hook procedure

The one thing I havn't shown yet is the actual hook procedure, that is called by Windows each time a CBT event occurs (i.e. whenever a WM_ACTIVATE / WM_CREATE message is sent to your application).

A CBT hook procedure needs to look something like this:

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    HWND hwnd;

    if(nCode < 0)
        return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);

    switch(nCode)
    {
    case HCBT_ACTIVATE:
        // Get handle to the message box!
        hwnd = (HWND)wParam;

        // Do customization!
        return 0;
    }
   
    // Call the next hook, if there is one
    return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
}

Bringing it all together

To tidy this all up, we can write a simple wrapper function around MessageBox, which hides the hook details. Whenever you want to display a custom MessageBox, just use this function instead.

static HHOOK hMsgBoxHook;
...

int MsgBoxEx(HWND hwnd, TCHAR *szText, TCHAR *szCaption, UINT uType)
{
    int retval;

    // Install a window hook, so we can intercept the message-box
    // creation, and customize it
    hMsgBoxHook = SetWindowsHookEx(
        WH_CBT, 
        CBTProc, 
        NULL, 
        GetCurrentThreadId()            // Only install for THIS thread!!!
        );

    // Display a standard message box
    retval = MessageBox(hwnd, szText, szCaption, uType);

    // remove the window hook
    UnhookWindowsHookEx(hMsgBoxHook);
    return retval;
}

Hopefully this tutorial has given you information for you to customize your own message-boxes. How you decide to do the customization is up to you - the technique described here takes you part of the way, but it is up to you to take it further.

Here are a few ideas for how you might customize a MessageBox:

  • Use CreateWindow(...) to insert a check-box at the bottom of the MessageBox window.
  • Modify the button-text of one of the buttons.
  • Change the icon image.

Have fun!