Как разместить MessageBox по центру окна в Windows Forms и WPF?

Рейтинг: 0Ответов: 1Опубликовано: 09.06.2023

Чтобы отцентрировать попап диалог по центру родительского окна, можно реализовать свой диалог, и использовать его вместо MessageBox. Но иногда это лишнее, надо по-простому и привычному, а также если есть старый проект, можно улучшить его поведение не затратив много времени на доработку.

MessageBox по умолчанию выскакивает в центре экрана, а как его сделать по центру окна?

Ответы

▲ 1Принят

Готового способа нет, Windows не поддерживает такие фокусы. Но можно реализовать самому.

У меня есть давно реализованное решение, назвал его MessageBoxEx, решил поделиться.

Класс MessageBoxEx требует для WPF требует наличия установленного NuGet пакета System.Drawing.Common. В Winforms этот пакет подключен по умолчанию, поэтому установка не требуется.

using System.Drawing;

Вот готовые классы:

Windows Forms

public static class MessageBoxEx
{
    private static Form _owner;
    private static readonly HookProc _hookProc = new HookProc(MessageBoxHookProc);
    private static IntPtr _hHook = IntPtr.Zero;

    public static DialogResult Show(Form owner, string text)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text);
    }

    public static DialogResult Show(Form owner, string text, string caption)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption);
    }

    public static DialogResult Show(Form owner, string text, string caption, MessageBoxButtons buttons)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons);
    }

    public static DialogResult Show(Form owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon);
    }

    public static DialogResult Show(Form owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defButton)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon, defButton);
    }

    public static DialogResult Show(Form owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defButton, MessageBoxOptions options)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon, defButton, options);
    }

    private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    private const int WH_CALLWNDPROCRET = 12;

    private enum CbtHookAction : int
    {
        HCBT_MOVESIZE = 0,
        HCBT_MINMAX = 1,
        HCBT_QS = 2,
        HCBT_CREATEWND = 3,
        HCBT_DESTROYWND = 4,
        HCBT_ACTIVATE = 5,
        HCBT_CLICKSKIPPED = 6,
        HCBT_KEYSKIPPED = 7,
        HCBT_SYSCOMMAND = 8,
        HCBT_SETFOCUS = 9
    }

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle lpRect);

    [DllImport("user32.dll")]
    private static extern int MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

    [DllImport("user32.dll")]
    private static extern int UnhookWindowsHookEx(IntPtr idHook);

    [DllImport("user32.dll")]
    private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

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

    [StructLayout(LayoutKind.Sequential)]
    private struct CWPRETSTRUCT
    {
        public IntPtr lResult;
        public IntPtr lParam;
        public IntPtr wParam;
        public CbtHookAction message;
        public IntPtr hwnd;
    };

    private static void Initialize(Form owner)
    {
        _owner = owner;
        if (owner is Form form && form.WindowState == FormWindowState.Normal)
        {
            if (_hHook != IntPtr.Zero)
            {
                throw new NotSupportedException("Multiple calls are not supported");
            }

            _hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, _hookProc, IntPtr.Zero, GetCurrentThreadId());
        }
    }

    private static IntPtr MessageBoxHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode < 0)
        {
            return CallNextHookEx(_hHook, nCode, wParam, lParam);
        }

        CWPRETSTRUCT msg = Marshal.PtrToStructure<CWPRETSTRUCT>(lParam);
        IntPtr hook = _hHook;

        if (msg.message == CbtHookAction.HCBT_ACTIVATE)
        {
            try
            {
                CenterWindow(msg.hwnd);
            }
            finally
            {
                UnhookWindowsHookEx(_hHook);
                _hHook = IntPtr.Zero;
            }
        }

        return CallNextHookEx(hook, nCode, wParam, lParam);
    }

    private static void CenterWindow(IntPtr hChildWnd)
    {
        if (_owner is Form form)
        {
            Rectangle recChild = new Rectangle(0, 0, 0, 0);
            _ = GetWindowRect(hChildWnd, ref recChild);

            int width = recChild.Width - recChild.X;
            int height = recChild.Height - recChild.Y;

            int x = form.Left + ((form.Width - width) / 2);
            int y = form.Top + ((form.Height - height) / 2);

            _ = MoveWindow(hChildWnd, x, y, width, height, false);
        }
    }
}

WPF

public static class MessageBoxEx
{
    private static Window _owner;
    private static readonly HookProc _hookProc = new HookProc(MessageBoxHookProc);
    private static IntPtr _hHook = IntPtr.Zero;

    public static MessageBoxResult Show(Window owner, string text)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text);
    }

    public static MessageBoxResult Show(Window owner, string text, string caption)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption);
    }

    public static MessageBoxResult Show(Window owner, string text, string caption, MessageBoxButton buttons)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons);
    }

    public static MessageBoxResult Show(Window owner, string text, string caption, MessageBoxButton buttons, MessageBoxImage icon)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon);
    }

    public static MessageBoxResult Show(Window owner, string text, string caption, MessageBoxButton buttons, MessageBoxImage icon, MessageBoxResult defButton)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon, defButton);
    }

    public static MessageBoxResult Show(Window owner, string text, string caption, MessageBoxButton buttons, MessageBoxImage icon, MessageBoxResult defButton, MessageBoxOptions options)
    {
        Initialize(owner);
        return MessageBox.Show(owner, text, caption, buttons, icon, defButton, options);
    }

    private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    private const int WH_CALLWNDPROCRET = 12;

    private enum CbtHookAction : int
    {
        HCBT_MOVESIZE = 0,
        HCBT_MINMAX = 1,
        HCBT_QS = 2,
        HCBT_CREATEWND = 3,
        HCBT_DESTROYWND = 4,
        HCBT_ACTIVATE = 5,
        HCBT_CLICKSKIPPED = 6,
        HCBT_KEYSKIPPED = 7,
        HCBT_SYSCOMMAND = 8,
        HCBT_SETFOCUS = 9
    }

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle lpRect);

    [DllImport("user32.dll")]
    private static extern int MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

    [DllImport("user32.dll")]
    private static extern int UnhookWindowsHookEx(IntPtr idHook);

    [DllImport("user32.dll")]
    private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

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

    [StructLayout(LayoutKind.Sequential)]
    private struct CWPRETSTRUCT
    {
        public IntPtr lResult;
        public IntPtr lParam;
        public IntPtr wParam;
        public CbtHookAction message;
        public IntPtr hwnd;
    };

    private static void Initialize(Window owner)
    {
        _owner = owner;
        if (owner is Window window && window.WindowState == WindowState.Normal)
        {
            if (_hHook != IntPtr.Zero)
            {
                throw new NotSupportedException("Multiple calls are not supported");
            }

            _hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, _hookProc, IntPtr.Zero, GetCurrentThreadId());
        }
    }

    private static IntPtr MessageBoxHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode < 0)
        {
            return CallNextHookEx(_hHook, nCode, wParam, lParam);
        }

        CWPRETSTRUCT msg = Marshal.PtrToStructure<CWPRETSTRUCT>(lParam);
        IntPtr hook = _hHook;

        if (msg.message == CbtHookAction.HCBT_ACTIVATE)
        {
            try
            {
                CenterWindow(msg.hwnd);
            }
            finally
            {
                UnhookWindowsHookEx(_hHook);
                _hHook = IntPtr.Zero;
            }
        }

        return CallNextHookEx(hook, nCode, wParam, lParam);
    }

    private static void CenterWindow(IntPtr hChildWnd)
    {
        if (_owner is Window window)
        {
            Rectangle recChild = new Rectangle(0, 0, 0, 0);
            _ = GetWindowRect(hChildWnd, ref recChild);

            int width = recChild.Width - recChild.X;
            int height = recChild.Height - recChild.Y;

            PresentationSource source = PresentationSource.FromVisual(window);
            double scaleX = source.CompositionTarget.TransformToDevice.M11;
            double scaleY = source.CompositionTarget.TransformToDevice.M22;

            int x = (int)((window.Left + (window.Width - width / scaleX) / 2) * scaleX);
            int y = (int)((window.Top + (window.Height - height / scaleY) / 2) * scaleY);

            _ = MoveWindow(hChildWnd, x, y, width, height, false);
        }
    }
}

Оба класса, как можно заметить почти одинаковые, но адаптированы под нужный UI движок.

Использование

Точно так же как обычный MessageBox, только первым параметром передать окно (или форму), относительно которого его надо отцентрирвоать.

Использование одинаково для Winforms и WPF:

MessageBoxEx.Show(this, "Hello World");

где this это Form для Winforms, либо Window для WPF.