Repost: Centering a Message Box on the Active Window in C# by Jason Carr

David Meego - Click for blog homepageThis is a repost of an original article by Jason Carr from August 2009 that I found very useful but is no longer available online. Originally published as

Thanks to the Internet Archive Wayback Machine I was able to recover the last version of the article from November 2020 and am posting it here as a reference.

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.


Thanks Jason for this useful code.

David

This article was originally posted on https://www.winthropdc.com/blog.

Leave a Reply