Centering a Message Box on the Active Window in C#

One of the annoying caveats of using the built in .NET message box is that it provides no functionality to center a message box on the currently active window. Oddly, even when you specify the parent window using the proper overloaded version of the Show() method, the window still insists on centering itself on the desktop, instead of on the active window. This is annoying and confusing to the end user because it breaks the ability to set aside particular “screen real estate” for an application. Fortunately, there is a way to fix this, although it does involve the Win32 API.

To get started, create a class in your project called MessageBoxHelper (or name it whatever you wish). Replace the code in the file with the following code:


using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

internal static class MessageBoxHelper
{
    internal static void PrepToCenterMessageBoxOnForm(Form form)
    {
        MessageBoxCenterHelper helper = new MessageBoxCenterHelper();
        helper.Prep(form);
    }

    private class MessageBoxCenterHelper
    {
        private int messageHook;
        private IntPtr parentFormHandle;

        public void Prep(Form form)
        {
            NativeMethods.CenterMessageCallBackDelegate callBackDelegate = new NativeMethods.CenterMessageCallBackDelegate(CenterMessageCallBack);
            GCHandle.Alloc(callBackDelegate);

            parentFormHandle = form.Handle;
            messageHook = NativeMethods.SetWindowsHookEx(5, callBackDelegate, new IntPtr(NativeMethods.GetWindowLong(parentFormHandle, -6)), NativeMethods.GetCurrentThreadId()).ToInt32();
        }

        private int CenterMessageCallBack(int message, int wParam, int lParam)
        {
            NativeMethods.RECT formRect;
            NativeMethods.RECT messageBoxRect;
            int xPos;
            int yPos;

            if (message == 5)
            {
                NativeMethods.GetWindowRect(parentFormHandle, out formRect);
                NativeMethods.GetWindowRect(new IntPtr(wParam), out messageBoxRect);

                xPos = (int)((formRect.Left + (formRect.Right - formRect.Left) / 2) - ((messageBoxRect.Right - messageBoxRect.Left) / 2));
                yPos = (int)((formRect.Top + (formRect.Bottom - formRect.Top) / 2) - ((messageBoxRect.Bottom - messageBoxRect.Top) / 2));

                NativeMethods.SetWindowPos(wParam, 0, xPos, yPos, 0, 0, 0x1 | 0x4 | 0x10);
                NativeMethods.UnhookWindowsHookEx(messageHook);
            }

            return 0;
        }
    }

    private static class NativeMethods
    {
        internal struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        internal delegate int CenterMessageCallBackDelegate(int message, int wParam, int lParam);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool UnhookWindowsHookEx(int hhk);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("kernel32.dll")]
        internal static extern int GetCurrentThreadId();

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern IntPtr SetWindowsHookEx(int hook, CenterMessageCallBackDelegate callback, IntPtr hMod, int dwThreadId);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool SetWindowPos(int hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    }
}

I won’t go into too much detail describing the above code, because most of it is just Win32 API greek. The gist of it though is there’s a parent static class called MessageBoxHelper that contains two subclasses. The NativeMethods subclass is another static class that simply provides the interfaces into the Win32 API calls. The MessageBoxCenterHelper subclass is instantiated by the parent class, and performs the actual calls to center the message box. I’m not overly familiar with the way callbacks, delegates, and events work when using the Win32 API, so I won’t attempt to explain the logic behind these methods.

Usage

The above implementation may be fairly involved, but using it is extremely easy. Simply call the MessageBoxHelper.PrepToCenterMessageBoxOnForm static method before calling your message box:


using System;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Click(object sender, EventArgs e)
    {
        MessageBoxHelper.PrepToCenterMessageBoxOnForm(this);
        MessageBox.Show("Hello!", "Hello!", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, 0);
    }
}

It’s worth noting that this code does pass all Code Analysis rules, so you can be confident you won’t have to rewrite any of it to make it pass. I do have one small warning, however; make sure that you always call the PrepToCenterMessageBoxOnForm method immediately before your MessageBox.Show call. Calls to the PrepToCenterMessageBoxOnForm method that are not met with a MessageBox.Show call can interfere with whatever the next form that pops up happens to be, and can result in odd behavior. Still, you’re not likely to want to put the code anywhere else, anyway, and I’ve used it in many enterprise projects without running into any issues.

Let me know if you appreciate this or have any questions or suggestions in the comments!

Update: Many thanks to Matt Stan for pointing out in the comments that the class also works well for centering a folder browser dialog window (by simply calling the PrepToCenterMessageBoxOnForm method before calling the ShowDialog method). It may work for some other dialogs as well, and not just these two. Apparently, however, it does not work for a file open dialog. I’ll be doing a bit of research to see if I can modify the code to support all .NET standard dialogs. I may or may not be successful.

For now, you can assume that it is safe to use on other dialogs, if it works properly. It is probably guaranteed beyond a reasonable doubt to work regularly within the same .NET framework version for these other dialogs as well. Regardless, most likely it will work indefinitely for all future .NET framework versions (unless something is significantly restructured in .NET). The code simply attempts to get a handle to the upcoming window and center it via an event, so it should work generically for most types of windows, and should be plenty safe.

Thanks, Matt!

Update #2: I’ve done some research on the .NET dialogs, relating to the centering mechanism. As well as message boxes, the ColorDialog, FontDialog, and FolderBrowserDialog dialogs do center properly as well. However, the OpenFileDialog and the SaveFileDialog dialogs unfortunately do not. This is because somewhere in the order of events after the new position is given these dialogs automatically center themselves on the screen. This functionality is somehow different from the other dialogs. You can observe this happening if you add “0x40” (SWP_SHOWWINDOW) to the SetWindowPos method’s flags. If you do this, you can see the dialog come up in the correct spot, centered on the form, and then quickly jump to the center of the screen. Unfortunately, I can’t seem to get around this. The only solutions I’ve seen involve extending the OpenFileDialog and SaveFileDialog controls, which is a completely different direction than this article is taking.

This entry was posted in C#, Technology, Visual Studio 2008. Bookmark the permalink.

29 Responses to Centering a Message Box on the Active Window in C#

  1. Justin Chmura says:

    Nothing like finding work-arounds to fix what Microsoft should have thought of in the first place.

  2. Jason Carr says:

    Haha, yeah.  You woulda thunk they woulda thoughta this one.  Especially after so many releases… ;)

  3. Matt Stan says:

    Brilliant, exactly what I needed. Many thanks.
    Are you aware that using it with the FolderBrowserDialog method ShowDialog() also works, the dialog gets nicely centered on the form if you call MessageBoxHelper.PrepToCenterMessageBoxOnForm immediately before ShowDialog(). However it does not work with the OpenFileDialog method ShowDialog(). I feel bad asking but would there be any chance of you making alterations so that it does work with OpenFileDialog as well please. It seems cheeky to ask but it’s such a useful class and it would be great if it worked for the other standard .net dialogs too, and my system internals knowledge is just not up to doing the alterations myself. I’d better check: is it safe to use with FolderBrowserDialog? Thanks again.

  4. Jason Carr says:

    Thanks very much, Matt, for pointing that out.  I’ve added an update to the article that reflects this and confirms that it is safe to use on other dialogs.  I’ll also be looking into usage with the rest of the .NET standard dialogs soon (hopefully tonight).

  5. Jason Carr says:

    Unfortunately, it’s a no-go, Matt.  I’ve updated the article with details.

  6. Alexander says:

    Thanks a lot. This is really what I need

  7. David says:

    I stumbled across this article and thought you might like to try a simpler approach, this is the code I use to ensure my AboutBox is always centered on the parent form:

    private void menuItem_About_Click(object sender, EventArgs e)
    {
    AboutBox myAboutBox = new AboutBox();
    // find the center of our parent form then subtract out half the size of the child
    int XLocation = Location.X + (Size.Width / 2) – (myAboutBox.Size.Width / 2);
    int YLocation = Location.Y + (Size.Height / 2) – (myAboutBox.Size.Height / 2);
    myAboutBox.SetDesktopLocation(XLocation, YLocation);
    myAboutBox.Show();
    }

    • David says:

      Forgot to mention – make sure the child form has the startPosition property set to Manual.

      Enjoy,
      David S. Mitchell

      • Jason Carr says:

        David –

        I’m afraid you’re trying to solve a completely different problem with the above code. This article specifically focuses on centering a .NET MessageBox, which does not allow you to use the code you’ve used above (it’s quite different than a custom form, as you’re using above).

        In addition, there’s a much better and easier way to do what you’re doing above; all you have to do in order to make sure your custom form pops up in the center of the parent window is to set the StartPosition property to CenterParent on the child form and call the child Form’s ShowDialog(parent) method when showing the form. For example:

        private void Form1_Click(object sender, EventArgs e)
        {
        Form2 form2 = new Form2();
        form2.StartPosition = FormStartPosition.CenterParent;
        form2.ShowDialog(this);
        }

        This will automatically center and show Form2 on Form1 when Form1 is clicked.

  8. AH says:

    Unfortunately this does not work for WPF.
    An hints on changing the code for use in WPF would be appreciated.
    Thanks!

  9. AH says:

    Ok. Got a solution.

    In the code change “Form” to “Window” and get the handle like this:
    parentFormHandle = new WindowInteropHelper(window).Handle;

  10. Jason Carr says:

    Cool; thanks, AH. I haven’t yet had the need to delve into WPF…

  11. AH says:

    My pleasure – you delivered 99% of the code :-)

  12. RG says:

    Thank you very much. This is exactly what I was looking for!

  13. Jason Carr says:

    Glad to hear, RG. :)

  14. Peter Darrow says:

    I’d like to use this is a commercial product. What’s the license for your code here?

  15. Jason Carr says:

    No problem, Peter. If I had to give it a license I’d probably put it under the GPL, but you’re free to use it in whatever commercial products you’d like.

    Thanks for asking. :)

  16. Brian says:

    Thank you for making the centre MessageBox code available! It is fantastic!!

  17. ram says:

    Thanks it is really very good, it is like a ready made food. But this does not work inside timer tick

  18. Thanks a lot…Helped me a lot.

  19. The code is really helpful. Thank you for sharing it.

  20. JC says:

    Thanks very much! Works great in WPF with the suggestion you made to:
    In the code change “Form” to “Window” and get the handle like this:
    parentFormHandle = new WindowInteropHelper(window).Handle;

  21. gabriel says:

    fantantic help! thanks you very much, gabriel from argentina

  22. Cosmin says:

    Thank you so much for this solution!

  23. test020682 says:

    Very nice many thanks!

Leave a Reply