Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Make MessageBoxes Center on their Parent Forms

0.00/5 (No votes)
17 Feb 2010 1  
Make MessageBoxes center on their parent forms
This article presents a simple mechanism for implementing MessageBoxes that appear centered on their parent.

Introduction

Everyone uses MessageBox - it's been a fixture of Windows since day 1, and its format and invocation have changed very little over the years.

For me, the single biggest drawback of MessageBox has been that it centers on the screen, not on its parent, and there's no way to tell it to center on its parent:

MessageBoxCenterOnParent/regular.PNG

Here's what I want:

MessageBoxCenterOnParent/centered.PNG

You'd think that at least one of MessageBox.Show()'s 21 overloads would have some way of doing this, or perhaps that there'd be a MessageBoxOptions.CenterOnParent flag, but no such luck.

This article presents a simple mechanism for implementing MessageBoxes that appear centered on their parent.

Background

My technique uses a custom class - I call it MsgBox - that wraps the standard MessageBox.Show call with a Windows hook. The hook is set before popping the MessageBox, the hookproc finds and centers the MessageBox before it's initially displayed, then the hook is released.

For simplicity, my example works only on a single thread. If you have multiple threads that pop MessageBoxes in an uncoordinated fashion, you'll need to add some code to handle that situation.

For additional references/articles, CodeProject has many articles on hooking. Search on "SetWindowsHookEx".

Microsoft's docs are available at this link.

Using the Code

As mentioned above, the core mechanism is a Windows hook. For the uninitiated, this is a Windows mechanism that allows your code to get access to some low-level Windows functionality; you essentially inject a bit of your app's code into the inner workings of Windows.

There are may types of hooks; my code uses the WH_CBT hook and acts on the HCBT_ACTIVATE event. (Read the Microsoft page linked above for details on WH_CBT.)

Hooks are not a part of .NET. To use them, you must use PInvoke to access the Win32 hooking APIs SetWindowsHookEx(), CallNextHookEx(), and UnhookWindowsHookEx(). These set local hooks, meaning that they only operate on windows within our process. This is exactly what we want - we do not want to handle message boxes displayed by other applications, only our own.

When you set a WH_CBT hook, your callback will receive notifications of window events such as creation, activation, moving/sizing, and destruction. We're interested in activation: when a message box is first activated (but before it's initially visible), we'll reposition it, and then we're done.

The code snippets below are lifted from the attached sample. In them, you'll see me using a Win32.* syntax - in the sample, I've collected all P/Invoke methods and defs in a separate class named Win32, a common practice that I've adopted for my projects.

Preparation

First, you need to import the hooking APIs:

using System.Runtime.InteropServices;

public class Win32
{
   public const int WH_CBT = 5;
   public const int HCBT_ACTIVATE = 5;

   public delegate int WindowsHookProc(int nCode, IntPtr wParam, 
                                       IntPtr lParam);

   [DllImport("user32.dll", CharSet = CharSet.Auto, 
              CallingConvention = CallingConvention.StdCall)]
   public static extern int SetWindowsHookEx(int idHook, 
          WindowsHookProc lpfn, IntPtr hInstance, int threadId);

   [DllImport("user32.dll", CharSet = CharSet.Auto, 
              CallingConvention = CallingConvention.StdCall)]
   public static extern bool UnhookWindowsHookEx(int idHook);

   [DllImport("user32.dll", CharSet = CharSet.Auto, 
              CallingConvention = CallingConvention.StdCall)]
   public static extern int CallNextHookEx(int idHook, int nCode, 
                            IntPtr wParam, IntPtr lParam);
}

and define a few variables for managing the hook:

private int _hHook = 0;
private Win32.WindowsHookProc _hookProcDelegate;
private static string _title = null;
private static string _msg = null;

Setting, Processing, and Releasing the Hook

Create a callback delegate then calls SetWindowsHookEx() to set the hook. It returns a hook ID that you'll use in your callback and when you release the hook.

// Remember the title & message that we'll look for.
// The hook sees *all* windows, so we need
// to make sure we operate on the right one.
_msg = msg;
_title = title;

Win32.WindowsHookProc hookProcDelegate = 
                      new Win32.WindowsHookProc(HookCallback);
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, hookProcDelegate, 
         IntPtr.Zero, AppDomain.GetCurrentThreadId()); 

Your hook callback looks something like this. Once you're done processing the notification, you must pass the event along to the next hook via CallNextHookEx(). (Note the use here of your hook ID, _hHook.)

private static int HookCallback(int code, IntPtr wParam, IntPtr lParam)
{
   if (code == Win32.HCBT_ACTIVATE)
   {
      // wParam is the handle to the Window being activated.
      if(TestForMessageBox(wParam))
      {
         CenterWindowOnParent(wParam);
         Unhook();   // Release hook - we've done what we needed
      }
   }
   return Win32.CallNextHookEx(_hHook, code, wParam, lParam); 
}

Then, finally, when you're done looking for the message box, you release the hook:

private static void Unhook()
{
   Win32.UnhookWindowsHookEx(_hHook);
   _hHook = 0;
   _hookProcDelegate = null;
   _title = null;
   _msg = null;
}

Finding Your Message Box

Simply watch for a dialog box which has the correct title and message:

private static bool TestForMessageBox(IntPtr hWnd)
{
   string cls = Win32.GetClassName(hWnd);
   if (cls == "#32770") // MessageBoxes are Dialog boxes
   {
      string title = Win32.GetWindowText(hWnd);
      string msg = Win32.GetDlgItemText(hWnd, 0xFFFF); // -1 aka IDC_STATIC
      {
         if ((title == _title) && (msg == _msg))
         {
            return true;
         }
      }
   }
   return false;
}

Centering the Message Box on Its Parent Form

Centering one window on another - nothing special here:

private static void CenterWindowOnParent(IntPtr hChildWnd)
{
   // Get child (MessageBox) size
   Win32.RECT rcChild = new Win32.RECT();
   Win32.GetWindowRect(hChildWnd, ref rcChild);
   int cxChild = rcChild.right - rcChild.left;
   int cyChild = rcChild.bottom - rcChild.top;

   // Get parent (Form) size & location 
   IntPtr hParent = Win32.GetParent(hChildWnd);
   Win32.RECT rcParent = new Win32.RECT();
   Win32.GetWindowRect(hParent, ref rcParent);
   int cxParent = rcParent.right - rcParent.left;
   int cyParent = rcParent.bottom - rcParent.top;

   // Center the MessageBox on the Form 
   int x = rcParent.left + (cxParent - cxChild) / 2;
   int y = rcParent.top + (cyParent - cyChild) / 2;
   uint uFlags = 0x15; // SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;

   Win32.SetWindowPos(hChildWnd, IntPtr.Zero, x, y, 0, 0, uFlags);
}

and that's about it!

In your calling code, just call MsgBox.Show() instead of MessageBox.Show(), and you'll get parent-centered pop-ups.

Points of Interest

I've frequently wondered why message boxes appear centered on the screen rather than on their parent. I assumed that there must be some method to the madness - Microsoft wouldn't let a bug like this slip for 20 years! (Well, maybe they would... :) )

While developing this code, I found a plausible explanation: say, for example, that your window is positioned outside the display boundaries and an error occurs that pops a message box. In this case, if the message box were centered on its parent, you'd never see it. I think that this is a valid concern, and you should consider it when using centered message boxes, maybe choosing to not parent-center some error messages, just in case.

History

  • 17th February, 2010: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here