MessageBoxを親ウィンドウの中央に表示する

ダイアログ
この記事は約16分で読めます。
管理人
管理人

こんにちは!今回は、メッセージボックスをウィンドウの中央に表示する方法を紹介するよ。

質問者
質問者

普通の MessageBox.Show じゃダメなんですか?いつも画面中央に表示されるから、それで十分な気がしますけど。

管理人
管理人

いい質問だね!MessageBox.Show はデフォルトで画面の中央に表示されるんだけど、特定のウィンドウの中央に表示させたい場合には使いにくいんだ。例えば、複数のウィンドウを持つアプリケーションで、どのウィンドウに関連するメッセージかを明確にしたいときに便利なんだよ。

質問者
質問者

なるほど、それならウィンドウごとにメッセージボックスを中央に表示できると便利ですね。どんな風に実装するんですか?

管理人
管理人

実装には少しだけ工夫が必要なんだ。具体的には、Windows API のフックを使って、メッセージボックスが表示される瞬間に位置を調整するんだ。まず、以下のようなコードを書いて、CenterMessageBox クラスを作るんだよ。

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

namespace CenterMessageBoxApp
{
    public static class CenterMessageBox
    {
        /// <summary>
        /// オーナーウィンドウのハンドルを保持します。
        /// </summary>
        private static IntPtr hOwner = IntPtr.Zero;

        /// <summary>
        /// フックのハンドルを保持します。
        /// </summary>
        private static IntPtr hHook = IntPtr.Zero;

        /// <summary>
        /// 指定されたオーナーウィンドウの中央にメッセージボックスを表示します。
        /// </summary>
        /// <param name="owner">オーナーウィンドウ</param>
        /// <param name="text">メッセージボックスに表示するテキスト</param>
        /// <param name="caption">メッセージボックスのタイトル</param>
        /// <param name="buttons">メッセージボックスに表示するボタン</param>
        /// <param name="icon">メッセージボックスに表示するアイコン</param>
        /// <returns>ユーザーの選択を示す DialogResult</returns>
        public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
        {
            hOwner = owner.Handle;
            IntPtr hInstance = NativeMethods.GetWindowHInstance(hOwner);
            IntPtr threadId = NativeMethods.GetCurrentThreadId();

            // フックを設定
            hHook = NativeMethods.SetWindowsHookEx(new NativeMethods.HOOKPROC(HookProc), hInstance, threadId);

            // メッセージボックスの表示
            return MessageBox.Show(owner, text, caption, buttons, icon);
        }

        /// <summary>
        /// フックプロシージャ。メッセージボックスをオーナーウィンドウの中央に配置します。
        /// </summary>
        /// <param name="nCode">フックコード</param>
        /// <param name="wParam">メッセージボックスのウィンドウハンドル</param>
        /// <param name="lParam">メッセージ情報</param>
        /// <returns>次のフックプロシージャの戻り値</returns>
        private static IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode == NativeMethods.HCBT_ACTIVATE)
            {
                // オーナーウィンドウとメッセージボックスの位置を取得
                var ownerRect = NativeMethods.GetWindowRect(hOwner);
                var msgBoxRect = NativeMethods.GetWindowRect(wParam);

                // メッセージボックスをオーナーウィンドウの中央に配置
                int x = ownerRect.Left + (ownerRect.Width - msgBoxRect.Width) / 2;
                int y = ownerRect.Top + (ownerRect.Height - msgBoxRect.Height) / 2;
                NativeMethods.SetWindowPos(wParam, x, y);

                // フックの解除
                NativeMethods.UnhookWindowsHookEx(hHook);
                hHook = IntPtr.Zero;
            }

            return NativeMethods.CallNextHookEx(hHook, nCode, wParam, lParam);
        }

        private static class NativeMethods
        {
            /// <summary>
            /// 指定されたウィンドウに関する情報を取得します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <param name="nIndex">情報のオフセット</param>
            /// <returns>ウィンドウ情報</returns>
            [DllImport("user32.dll")]
            private static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

            /// <summary>
            /// 現在のスレッドIDを取得します。
            /// </summary>
            [DllImport("kernel32.dll")]
            public static extern IntPtr GetCurrentThreadId();

            /// <summary>
            /// フックを設定します。
            /// </summary>
            /// <param name="idHook">フックの種類</param>
            /// <param name="lpfn">フックプロシージャ</param>
            /// <param name="hInstance">インスタンスハンドル</param>
            /// <param name="threadId">スレッドID</param>
            /// <returns>フックのハンドル</returns>
            [DllImport("user32.dll")]
            public static extern IntPtr SetWindowsHookEx(int idHook, HOOKPROC lpfn, IntPtr hInstance, IntPtr threadId);

            public static IntPtr SetWindowsHookEx(HOOKPROC lpfn, IntPtr hInstance, IntPtr threadId)
                => SetWindowsHookEx(WH_CBT, lpfn, hInstance, threadId);

            /// <summary>
            /// フックを解除します。
            /// </summary>
            /// <param name="hHook">フックのハンドル</param>
            /// <returns>成功した場合は true、失敗した場合は false</returns>
            [DllImport("user32.dll")]
            public static extern bool UnhookWindowsHookEx(IntPtr hHook);

            /// <summary>
            /// 次のフックプロシージャにメッセージを渡します。
            /// </summary>
            /// <param name="hHook">フックのハンドル</param>
            /// <param name="nCode">フックコード</param>
            /// <param name="wParam">メッセージパラメータ</param>
            /// <param name="lParam">メッセージパラメータ</param>
            /// <returns>次のフックプロシージャの戻り値</returns>
            [DllImport("user32.dll")]
            public static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);

            /// <summary>
            /// 指定されたウィンドウの位置を取得します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <param name="lpRect">ウィンドウの位置情報</param>
            /// <returns>成功した場合は true、失敗した場合は false</returns>
            [DllImport("user32.dll")]
            private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

            /// <summary>
            /// ウィンドウの位置とサイズを変更します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <param name="hWndInsertAfter">Zオーダー</param>
            /// <param name="x">新しいX座標</param>
            /// <param name="y">新しいY座標</param>
            /// <param name="cx">新しい幅</param>
            /// <param name="cy">新しい高さ</param>
            /// <param name="uFlags">ウィンドウ配置オプション</param>
            /// <returns>成功した場合は true、失敗した場合は false</returns>
            [DllImport("user32.dll")]
            private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

            /// <summary>
            /// アプリケーション インスタンスへのハンドルを取得するためのオフセット。
            /// </summary>
            public const int GWL_HINSTANCE = -6;

            /// <summary>
            /// CBTフックの識別子。
            /// </summary>
            public const int WH_CBT = 5;

            /// <summary>
            /// メッセージボックスがアクティブになるときのフックコード。
            /// </summary>
            public const int HCBT_ACTIVATE = 5;

            /// <summary>
            /// フックプロシージャのデリゲート。
            /// </summary>
            public delegate IntPtr HOOKPROC(int nCode, IntPtr wParam, IntPtr lParam);

            /// <summary>
            /// 指定されたウィンドウのインスタンスハンドルを取得します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <returns>インスタンスハンドル</returns>
            public static IntPtr GetWindowHInstance(IntPtr hWnd) => GetWindowLong(hWnd, GWL_HINSTANCE);

            /// <summary>
            /// ウィンドウの位置を取得します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <returns>ウィンドウのRECT構造体</returns>
            public static RECT GetWindowRect(IntPtr hWnd)
            {
                GetWindowRect(hWnd, out RECT rect);
                return rect;
            }

            /// <summary>
            /// ウィンドウの位置を指定した座標に移動します。
            /// </summary>
            /// <param name="hWnd">ウィンドウハンドル</param>
            /// <param name="x">新しいX座標</param>
            /// <param name="y">新しいY座標</param>
            /// <returns>成功した場合は true、失敗した場合は false</returns>
            public static bool SetWindowPos(IntPtr hWnd, int x, int y)
            {
                const uint SWP_NOSIZE = 0x0001;
                const uint SWP_NOZORDER = 0x0004;
                const uint SWP_NOACTIVATE = 0x0010;
                uint flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
                return SetWindowPos(hWnd, 0, x, y, 0, 0, flags);
            }

            /// <summary>
            /// ウィンドウの位置とサイズを表す構造体。
            /// </summary>
            public struct RECT
            {
                public int Left, Top, Right, Bottom;
                public int Width => Right - Left;
                public int Height => Bottom - Top;
            }
        }

    }

}
質問者
質問者

おお、なんだかすごそうですね!でもフックとかって難しそう…。

管理人
管理人

確かに、ちょっと難しそうに聞こえるかもしれないね。でも、心配しないで。このコードを使うことで、メッセージボックスを簡単にオーナーウィンドウの中央に表示できるようになるんだよ。

質問者
質問者

なるほど!このコードがメッセージボックスの位置を調整しているんですね。これなら、ユーザーにとっても見やすくなりそうです。

管理人
管理人

その通り!この方法を使えば、より直感的なユーザーインターフェースを作ることができるよ。ぜひ試してみてね!

コメント

タイトルとURLをコピーしました