Fancy Custom MessageBox

Introduction

One of the most used Windows API functions is MessageBox. You easily can set its contents (caption, text, and buttons) and behavior (application, task, or system modal) but it's not flexible enough to allow changing, for example, its appearance. Of course, you can make your own dialog box, based on a dialog resource template, but this is not always the handiest choice. This article shows a simple method for customizing a message box's appearance, but not only the appearance.

Here is an example of message box with a custom background (it can be your company logo as well) and changed text color.

A First Try

After searching SDK headers, some people could be tempted to handle the WM_CTLCOLORMSGBOX message (not documented but defined in WINUSER.H). It seems to be destined, like the other WM_CTLCOLORxxx messages (WM_CTLCOLORSTATIC, WM_CTLCOLOREDIT, and so forth), to be sent to the parent window when a message box is about to be drawn. Then, it's just "a walk in the park" to change the message box text color, backround color, and so on.

That's no use! You can try handling WM_CTLCOLORMSGBOX as in the following example and see that the WM_CTLCOLORMSGBOX message is never sent.

BOOL CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                         LPARAM lParam)
{
   switch(uMsg)
   {
      //...
      HANDLE_MSG(hWnd, WM_CTLCOLORMSGBOX, Dlg_OnCtlColorMsgBox);
   }
   return FALSE;
}

HBRUSH Dlg_OnCtlColorMsgBox(HWND hWnd, HDC hdc, HWND hWndChild,
                            int type)
{
   HBRUSH hbr = NULL;
   // Just for testing WM_CTLCOLORMSGBOX message.
   if(CTLCOLOR_MSGBOX == type)
   {
      // hbr = ... get a valid brush handle to return ...
      // But it NEVER enters here!!!
   }
   return hbr;
}

What Can be Done?

One idea is to trap the dialog box creation to subclass it, and then use the application-defined window procedure to handle messages that allow changing the appearance like in any other dialog window procedure. For that, you can use a CBT (computer-based training) hook.

CbtHookProc: the CBT Hook Procedure

Here is the implementation of the hook procedure:

LRESULT CALLBACK CbtHookProc(int nCode, WPARAM wParam,
                             LPARAM lParam)
{
   if(nCode < 0)
   {
      return ::CallNextHookEx(cmbv.hHook, nCode, wParam, lParam);
   }
   switch(nCode)
   {
   case HCBT_CREATEWND:    // a window is about to be created
      {
         LPCBT_CREATEWND lpCbtCreate = (LPCBT_CREATEWND)lParam;
         if(WC_DIALOG == lpCbtCreate->lpcs->lpszClass)
         {
            // WC_DIALOG is the class name of message box but it
            // has not yet had a window procedure set.
            // So keep in mind the handle to sublass it later when
            // its first child is about to be created.
            cmbv.hWnd = (HWND)wParam;
         }
         else
         {
            if((NULL == cmbv.lpMsgBoxProc) && (NULL != cmbv.hWnd))
            {
               // subclass the dialog 
               cmbv.lpMsgBoxProc = 
                  (WNDPROC)::SetWindowLong(cmbv.hWnd, GWL_WNDPROC,
                     (LONG)CustomMessageBoxProc);
            }
         }
      }
      break;
   case HCBT_DESTROYWND:    // a window is about to be destroyed
      {
         if(cmbv.hWnd == (HWND)wParam)    // it's our messge box
         {
            // so set back its default procedure
            ::SetWindowLong(cmbv.hWnd, GWL_WNDPROC,
                            (LONG)cmbv.lpMsgBoxProc);
         }
      }
   }
   return 0;
}

You can observe a little trick in the code above: The dialog box is not subclassed when its own creation is signaled. That's because, at that time, it has not yet associated a procedure. You know that any message box contains at least one control, so you subclass it a little bit later, when its first child is about to be created.

Fancy Custom MessageBox

The CustomMessageBox Function

Next, you have to write a function that installs the hook procedure, then calls the MessageBox function, and finally removes the hook procedure when it's not needed anymore. This is the function to be called instead of a "standard" MessageBox.

int CustomMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption,
                     UINT uType)
{
   // init CustomMessageBoxValues structure values
   cmbv.hHook        = NULL;
   cmbv.hWnd         = NULL;
   cmbv.lpMsgBoxProc = NULL;

   HINSTANCE hInstance = ::GetModuleHandle(NULL);
   //  installs the hook procedure into a hook chain
   cmbv.hHook = ::SetWindowsHookEx(WH_CBT, CbtHookProc, hInstance,
      GetCurrentThreadId());

   // call "standard" MessageBox Windows API function
   int nRet = ::MessageBox(hWnd, lpText, lpCaption, uType);

   // removes the hook procedure from the hook chain
   ::UnhookWindowsHookEx(cmbv.hHook);
   
   return nRet;
}

CustomMessageBoxProc: the Message Box Procedure

Finally, you have to implement the application-defined window procedure for your subclassed message box.

LRESULT CALLBACK CustomMessageBoxProc(HWND hWnd, UINT uMsg,
   WPARAM wParam, LPARAM lParam)
{
   switch(uMsg)
   {
   case WM_INITDIALOG:
      {
         // load the brush used for backround
         cmbv.crText = RGB(0, 0, 255); 
         HINSTANCE hInstance = ::GetModuleHandle(NULL);
         cmbv.hBitmap = (HBITMAP)::LoadBitmap(hInstance,
            MAKEINTRESOURCE(IDB_BACK));
         cmbv.hBrush = ::CreatePatternBrush(cmbv.hBitmap);
      }
      break;
   case WM_CTLCOLORDLG:
   case WM_CTLCOLORSTATIC:
      {
         // set the custom background and text color
         HDC hDC = (HDC)wParam;
         ::SetBkMode(hDC, TRANSPARENT);
         ::SetTextColor(hDC, RGB(0, 0, 128));
         return (LRESULT)cmbv.hBrush;
      }
      break;
   case WM_COMMAND:
      {
         // performing cleanup before exit
         ::DeleteObject(cmbv.hBrush);
         ::DeleteObject(cmbv.hBitmap);
      }
      break;
   }
   return CallWindowProc(cmbv.lpMsgBoxProc, hWnd, uMsg, wParam,
                         lParam);
}

To make it easier to understand, it only demonstrates how to change the background and text colors. You can further add any kind of customization. As for examples, you can make owner drawn buttons, create additional controls, set non-standard icons, change the window styles, and so on.

And for giving the code complete, here is the cmbv structure that I have used to keep necessary data along custom message box functions and the hook procedure.

struct CustomMessageBoxValues
{
   HHOOK hHook;             // hook handle
   HWND  hWnd;              // message box handle
   WNDPROC lpMsgBoxProc;    // window procedure
   COLORREF crText;         // text color
   HBRUSH hBrush;           // background brush
   HBITMAP hBitmap;         // hadle to backround bitmap
} static __declspec(thread) cmbv;

The Demo Application

You can find more implementation details in the simple Win32 demo application attached here.



About the Author

Ovidiu Cucu

Graduated at "Gh. Asachi" Technical University - Iasi, Romania. Programming in C++ using Microsoft technologies since 1995. Microsoft MVP awardee since 2006. Moderator and article reviewer at Codeguru.com, the number one developer site. Co-founder of Codexpert.ro, a website dedicated to Romanian C++ developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

Most Popular Programming Stories

More for Developers

RSS Feeds