WinFormsのOK・キャンセルボタン:AcceptButtonとDialogResult
WinFormsのダイアログのOK・キャンセルボタンを.NET 10で整理。AcceptButtonとCancelButton、DialogResultの伝わり方、FormClosingのe.Cancelで入力ミス時に閉じない方法。
2026年11月改訂: この記事は2021年に韓国語で書いた記事を、日本語向けに書き直したものです。例を .NET 10 の WinForms(
net10.0-windows)でコンパイルし直し、入力チェック付きのダイアログの例を新しく足しました。WinForms は Windows 専用のため、Windows向けに.NET 10でコンパイル済み、このマシン(Apple Silicon の Mac)では実行していません。 実行結果は載せておらず、動作の説明は Microsoft Learn と WinForms のオープンソースのコード(dotnet/winforms のForm.cs)に基づいています。2021年版の例のConsole.WriteLineが WinForms アプリでは何も表示しない点も直しました。
WinForms で「名前を入力」のような小さなダイアログを作るときに、Enter で確定、Esc でキャンセル、そして入力ミスなら閉じないようにしたい人向けの記事です。要点を先に書くと、OK ボタンに DialogResult = OK、フォームに AcceptButton と CancelButton を設定すればキーボード操作は完成です。入力チェックは FormClosing で e.Cancel = true にすれば止められ、そのとき WinForms が DialogResult を None に戻してくれます。
AcceptButton・CancelButton・Button.DialogResult
2021年版に書いた3つの手順は、今もそのまま正しいです。
- OK ボタンの
DialogResultプロパティをOKに、キャンセルボタンをCancelにする。 - どのボタンが押されたかは、
ShowDialogの戻り値か、閉じた後のフォームのDialogResultプロパティで調べる。 - Enter キーはフォームの
AcceptButton、Esc キーはCancelButtonに結び付ける。
これに2つ付け加えます。
CancelButtonはボタンのDialogResultを自動でCancelにします。 WinForms のソース(Form.cs)の setter はこうなっています。
set
{
Properties.AddOrRemoveValue(s_propCancelButton, value);
if (value is not null && value.DialogResult == DialogResult.None)
{
value.DialogResult = DialogResult.Cancel;
}
}
一方 AcceptButton の setter は既定ボタンの表示を更新するだけで、DialogResult には触れません。そのため OK ボタンの DialogResult = OK は自分で設定します。これを忘れると、Enter を押しても Click イベントが起きるだけでダイアログは閉じません。
- ボタンの
DialogResultでフォームが閉じるのは、ShowDialogで開いたときだけです。 Microsoft Learn のButton.DialogResultのページには、親フォームがShowDialogで表示されている場合はクリックするとイベント処理なしでフォームが閉じる、と書かれています。Showで開いたモードレスフォームでは、同じボタンを押しても閉じません。両者の違いはモーダル・モードレスフォームで扱っています。
2021年版の例を .NET 10 でビルドすると
2021年版の C# の例をそのまま Form1 のメソッドに入れてビルドしました。エラーも警告もありません(警告0件)。ただしコードには問題が3つありました(… は省略)。
// フォームを作成
Form1 f = new Form1 ();
// フォームを表示
if (f.ShowDialog ( this ) == DialogResult.OK)
{
Console.WriteLine ("OK ボタンが押されました。");
}
// DialogResult プロパティを確認
if (f.DialogResult == DialogResult.OK) { /* … */ }
else if (f.DialogResult == DialogResult.Cancel) { /* … */ }
f.Dispose();
Console.WriteLineは何も表示しません。 WinForms テンプレートの出力の種類はWinExeで、コンソールが付いていません。確認用ならDebug.WriteLine(Visual Studio の出力ウィンドウ)、ユーザー向けならラベルやMessageBoxを使います。Form1の中でForm1をもう1つ開いています。 説明のために名前を使い回しただけですが、実際にはダイアログ用のクラスを別に作ります(下のNameDialog)。- 同じ判定を2回しています。 戻り値と
DialogResultプロパティは同じ値です。ShowDialogで開いたフォームは閉じても非表示になるだけで破棄されないので、後からプロパティを読むこともできますが、戻り値だけで十分です。Disposeはusingに任せます。
DialogResult が伝わる順番
Enter を押してから ShowDialog が戻るまでは、次の順に進みます。
- Enter →
AcceptButton(OK ボタン)がクリックされる → ボタンのDialogResult(OK)がフォームのDialogResultにコピーされる。 - モーダルループが
DialogResultがNoneでないことに気づいてクローズを始め、FormClosingを発生させる。 - どのハンドラーも
e.Cancelにしなければ、フォームは非表示になりShowDialogがOKを返す。 e.Cancel = trueにすると、WinForms がDialogResultをNoneに戻す。ダイアログは開いたままで、呼び出し側は待ち続ける。
4は dotnet/winforms の Form.CheckCloseDialog に、if (e.Cancel) の次の _dialogResult = DialogResult.None; として書かれている動作です。Esc、キャンセルボタン、タイトルバーの閉じるボタン(×)はどれも Cancel で終わります。Microsoft Learn の Form.DialogResult のページによると、モーダルダイアログの閉じるボタンを押すとフォームは非表示になり、DialogResult は Cancel になります。コードで this.DialogResult = DialogResult.OK; と代入した場合も、ボタンのクリックと同じ経路で閉じます。
入力ミスなら閉じない:FormClosing と e.Cancel
名前が空のまま OK を押すと、閉じずにエラーアイコンを出すダイアログです。net10.0-windows で警告0件でコンパイルできました。
public sealed class NameDialog : Form
{
readonly TextBox _name = new() { Left = 12, Top = 12, Width = 260 };
readonly Button _ok = new() { Text = "OK", Left = 116, Top = 48, DialogResult = DialogResult.OK };
readonly Button _cancel = new() { Text = "Cancel", Left = 197, Top = 48, CausesValidation = false };
readonly ErrorProvider _error = new();
public string UserName => _name.Text.Trim();
public NameDialog()
{
Text = "Your name";
ClientSize = new Size(284, 84);
FormBorderStyle = FormBorderStyle.FixedDialog;
MinimizeBox = MaximizeBox = false;
ShowInTaskbar = false;
StartPosition = FormStartPosition.CenterParent;
Controls.AddRange([_name, _ok, _cancel]);
AcceptButton = _ok; // Enter
CancelButton = _cancel; // Esc。_cancel.DialogResult も Cancel になる
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (DialogResult == DialogResult.OK && UserName.Length == 0)
{
e.Cancel = true; // WinForms が DialogResult を None に戻す
_error.SetError(_name, "Enter a name.");
_name.Focus();
}
base.OnFormClosing(e);
}
}
呼び出し側はこうです。
void AskName()
{
using var dlg = new NameDialog();
if (dlg.ShowDialog(this) == DialogResult.OK)
Text = $"Hello, {dlg.UserName}";
else
Debug.WriteLine($"cancelled: {dlg.DialogResult}");
}
設計上の判断をメモしておきます。
- チェックの条件に
DialogResult == DialogResult.OKを入れています。 これがないと、キャンセルや閉じるボタン(×)でもチェックに引っかかり、ユーザーがダイアログから抜けられなくなります。 base.OnFormClosing(e)は必ず呼びます。 呼ばないと、外から登録したFormClosingイベントハンドラーが動きません。- キャンセルボタンは
CausesValidation = false。 テキストボックスにValidatingのチェックを付けたとき、キャンセルボタンにフォーカスが移った瞬間にチェックが走ってユーザーを引き留めるのを防ぎます。 - 別の方法として、OK ボタンの
DialogResultをNoneのままにし、Clickハンドラーで検証が通ったときだけthis.DialogResult = DialogResult.OK;を代入する書き方もあります。チェックが1か所(Click)にまとまるのが利点です。上の方法は、Enter・ボタン・コードからの代入などどの経路で OK として閉じても同じチェックを通るのが利点です。
FormClosing は Windows のシャットダウン(CloseReason.WindowsShutDown)や Application.Exit でも発生します。それまで止めるとサインアウトの邪魔になるので、チェックは DialogResult == OK のときに限るのが安全です。同じ落とし穴はフォームを1つだけ表示する方法の「閉じずに隠す」の節でも出てきます。
まとめ
| やりたいこと | 設定 |
|---|---|
| Enter で確定 | AcceptButton = okButton + okButton.DialogResult = OK |
| Esc でキャンセル | CancelButton = cancelButton(DialogResult は自動で Cancel) |
| 結果を受け取る | using var dlg …; if (dlg.ShowDialog(this) == DialogResult.OK) |
| 入力ミスなら閉じない | OnFormClosing で DialogResult == OK && 不正 → e.Cancel = true |
| キャンセル時はチェックしない | cancelButton.CausesValidation = false |
| デバッグ出力 | Console.WriteLine ではなく Debug.WriteLine |
2021年版の VB.NET の例は同じ API を呼ぶだけなので、今回は C# のみにしました。元の記事には画像がなかったので、削除した画像はありません。
確認環境:.NET SDK 10.0.403、Microsoft.WindowsDesktop.App.Ref 10.0.14、C# 14、macOS(Apple Silicon の Mac)。WinForms のコードは dotnet build -p:EnableWindowsTargeting=true でコンパイルのみ行いました。
よくある質問
CancelButton を設定したら、ボタンの DialogResult も Cancel にする必要がある?
不要です。dotnet/winforms の Form.cs では、CancelButton プロパティの setter が、指定したボタンの DialogResult が None なら Cancel に変えます。一方 AcceptButton は Enter キーをそのボタンに結び付けるだけで DialogResult は変えないので、OK ボタンには DialogResult = OK を自分で設定します。
入力が正しくないとき、OK を押してもダイアログを閉じないようにするには?
FormClosing(または OnFormClosing のオーバーライド)で、DialogResult が OK かつ入力が不正なら e.Cancel = true にします。クローズが取り消されると WinForms が DialogResult を None に戻すので、ダイアログは開いたまま、ShowDialog も戻りません。OK ボタンの DialogResult を None のままにして、Click ハンドラーで検証が通ったときだけ this.DialogResult = DialogResult.OK とする方法もあります。
2021年版の例の Console.WriteLine が何も表示されない
WinForms アプリは出力の種類が WinExe でコンソールウィンドウがないため、Console.WriteLine の出力はどこにも表示されません。デバッグ用なら System.Diagnostics.Debug.WriteLine(Visual Studio の出力ウィンドウ)、ユーザーに見せるならラベルや MessageBox を使います。