🪧

C#定石 - MessageBox - 親画面中央表示とXボタン非表示

2025/01/31に公開

はじめに

C# ソフト開発時に、決まり事として実施していた内容を記載します。

参考情報

下記情報を参考にさせて頂きました。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

Windows Server 系での確認として、Windows Server 2025 評価版を利用しました。

MessageBox

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.messagebox

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.messagebox

親画面中央表示

Windows Forms - MessageBox、および、WPF - MessageBox、双方ともに、画面中央に表示されます。このため、親画面の表示の位置を端にずらしている場合、MessageBox が、親画面から外れて表示されてしまい、みっともない状態となってしまいます。
MessageBox は、親画面の中央表示でないとダメですよね。

本件については、先駆者がいるので、ありがたく利用させて頂いています。

https://millyc.hatenadiary.org/entry/20080312/1205311545

Xボタン非表示

MessageBoxButtons.YesNo と MessageBoxButtons.AbortRetryIgnore については、ウィンドウタイトルバー右端 X ボタンは無効化されます。

Windows 11 24H2 で、MessageBoxButtons.OK と MessageBoxButtons.YesNo それぞれの表示と、X ボタン Mouse Over の結果です。

しかし、Windows Server 系では、MessageBoxButtons.YesNo と MessageBoxButtons.AbortRetryIgnore は、X ボタンは無効化されているのに、無効化されていることが視認できず、Mouse Over にも反応します。(Mouse Over に反応するが、Click はできません)
下記は、Windows 11 24H2 と同様の処理を Windows Server 2025 で実施した結果です。

あたかも X ボタンが有効に見えるこの挙動を抑止するために、MessageBoxButtons.YesNo と MessageBoxButtons.AbortRetryIgnore の場合、X ボタン(SYSMENU)非表示を追加するケースもありました。

親画面中央表示ソースコードに対する、具体的な修正内容を以下に記載します。

WIN32API 定義に SetWindowLong、GWL_STYLE、WS_SYSMENU を追加します。

#region defines
private static class NativeMethods
{
  // Xボタン非表示
  [DllImport("user32.dll")]
  public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, long dwLong);

<中略>

  public const int GWL_HINSTANCE = (-6);
  public const int WH_CBT = 5;
  public const int HCBT_ACTIVATE = 5;
  public const int SWP_NOSIZE = 0x0001;
  public const int SWP_NOZORDER = 0x0004;
  public const int SWP_NOACTIVATE = 0x0010;
  public const int GWL_STYLE = (-16);        // Xボタン非表示
  public const int WS_SYSMENU = 0x00080000;  // Xボタン非表示
}

内部変数として、HookButtons を追加します。

#region fields

private readonly IWin32Window Owner;
private IntPtr HookHandle = IntPtr.Zero;
private MessageBoxButtons HookButtons;     // Xボタン非表示

#endregion

Show メソッドで HookButtons に MessageBoxButtons の値を保持します。

#region methods

private DialogResult Show(
  string text,
  string caption,
  MessageBoxButtons buttons,
  MessageBoxIcon icon,
  MessageBoxDefaultButton defaultButton)
{
  IntPtr hInstance = NativeMethods.GetWindowLong(this.Owner.Handle, 
    NativeMethods.GWL_HINSTANCE);
  IntPtr threadId = NativeMethods.GetCurrentThreadId();
  this.HookHandle = NativeMethods.SetWindowsHookEx(NativeMethods.WH_CBT, 
    this.HookProc, hInstance, threadId);
  this.HookButtons = buttons;  // Xボタン非表示

  return MessageBox.Show(this.Owner, text, caption, buttons, icon, defaultButton);
}

HookProc メソッドに対して、SYSMENU 非表示するコードを追加します。

private IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
  if (nCode == NativeMethods.HCBT_ACTIVATE)
  {
<中略>
  NativeMethods.SetWindowPos(wParam, 0, x, y, 0, 0,
    NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE);

  // Xボタン非表示
  if ((this.HookButtons == MessageBoxButtons.YesNo)
   || (this.HookButtons == MessageBoxButtons.AbortRetryIgnore))
  {
    long style = (long)NativeMethods.GetWindowLong(wParam, NativeMethods.GWL_STYLE);
    NativeMethods.SetWindowLong(wParam, NativeMethods.GWL_STYLE, 
      style & ~NativeMethods.WS_SYSMENU);
  }

  try
  {
    return NativeMethods.CallNextHookEx(this.HookHandle, nCode, wParam, lParam);
  }
  finally
  {
    NativeMethods.UnhookWindowsHookEx(this.HookHandle);
    this.HookHandle = IntPtr.Zero;
  }
<以下省略>

上記対応で、下記表示となります。

サンプルコード

Windows Forms

画面中央表示、X ボタン無効化した CenterMessageBox を GitHub で公開します。

https://github.com/chaichai0917/SampleCode

  • MessageBox 親画面中央表示とXボタン非表示
    • CenterMessageBox-WindowsForms.cs

基本的に、参考ソースは、そのままにしています。
MessageBox.Show 引数「MessageBoxOptions options」「string helpFilePath」「HelpNavigator navigator」「object param」「bool displayHelpButton」「string keyword」については、利用することもなかったので、追加していません。
必要に応じて、追加してください。

利用方法を記載します。

MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "OK", MessageBoxButtons.OK, MessageBoxIcon.Information);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "YesNo", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "AbortRetryIgnore", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "OKCancel", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "RetryCancel", MessageBoxButtons.RetryCancel, MessageBoxIcon.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "YesNoCancel", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

// .NET 8 の場合、下記コメントを外す
// MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
//   "CancelTryContinue", MessageBoxButtons.CancelTryContinue, MessageBoxIcon.Question);

WPF

WPF の場合、UI ライブラリ利用、NuGet / GitHub などで公開されている MessageBox を利用して、標準 MessageBox をそのまま利用することは少ないと思いますが、、、
WPF 用に修正した CenterMessageBox を GitHub で公開します。

https://github.com/chaichai0917/SampleCode

  • MessageBox 親画面中央表示とXボタン非表示
    • CenterMessageBox-WPF.cs

WPF 用に修正した部分について、コメント付与すると、可読性が悪くなりそうだったので、コメント付与していません。修正部分を確認したい場合は、双方のソース差分を確認してください。
MessageBox.Show 引数「MessageBoxOptions options」については、未対応です。
必要に応じて、追加してください。

利用方法を記載します。

MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "OK", MessageBoxButton.OK, MessageBoxImage.Information);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "YesNo", MessageBoxButton.YesNo, MessageBoxImage.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "OKCancel", MessageBoxButton.OKCancel, MessageBoxImage.Question);
MyTools.CenterMessageBox.Show(this, "MessageBox 確認です",
  "YesNoCancel", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);

出典

本記事は、2025/01/31 Qiita 投稿記事の転載です。

C#定石 - MessageBox - 親画面中央表示とXボタン非表示

Discussion