WinForms OK/Cancel Buttons: AcceptButton, DialogResult

OK and Cancel buttons for a WinForms dialog on .NET 10: AcceptButton and CancelButton, how DialogResult travels, and keeping the dialog open on bad input.

Revised November 2026. This tutorial first appeared in Korean in 2021. We recompiled its sample as a .NET 10 WinForms app (net10.0-windows) and added a dialog with input validation. WinForms is Windows-only, so the code was compiled with .NET 10 for Windows; not run on this machine (an Apple Silicon Mac). There is no program output here; the behavior described comes from Microsoft Learn and from the open-source WinForms code (Form.cs in dotnet/winforms). We also corrected one thing the 2021 sample got wrong: Console.WriteLine shows nothing in a WinForms app.

This article is for anyone building a small WinForms dialog — "enter your name", "pick a folder" — who wants Enter to confirm, Esc to cancel, and invalid input to keep the dialog open. In short: give the OK button DialogResult = OK, set the form's AcceptButton and CancelButton, and the keyboard works. For validation, set e.Cancel = true in FormClosing; WinForms then resets DialogResult to None for you.

AcceptButton, CancelButton and Button.DialogResult

The three steps from the 2021 version still hold:

  1. Set the OK button's DialogResult property to OK and the Cancel button's to Cancel.
  2. Find out which button was pressed from the return value of ShowDialog, or from the form's DialogResult property after it closes.
  3. Connect Enter to the form's AcceptButton and Esc to its CancelButton.

Two facts are worth adding.

  • CancelButton sets the button's DialogResult to Cancel for you. This is the setter in the WinForms source (Form.cs):
set
{
    Properties.AddOrRemoveValue(s_propCancelButton, value);

    if (value is not null && value.DialogResult == DialogResult.None)
    {
        value.DialogResult = DialogResult.Cancel;
    }
}

The AcceptButton setter, by contrast, only updates which button is drawn as the default; it never touches DialogResult. So set DialogResult = OK on the OK button yourself. Forget it, and Enter raises the button's Click event but the dialog stays open.

  • A button's DialogResult closes the form only if the form was opened with ShowDialog. The Microsoft Learn page for Button.DialogResult says that when the parent form is displayed with ShowDialog, clicking the button closes it without any event handler. In a modeless form opened with Show, the same button does not close anything. The difference between the two is covered in modal and modeless forms.

The 2021 sample on .NET 10

We pasted the 2021 C# sample unchanged into a method of Form1 and built it. No errors and no warnings. The code still had three problems (abbreviated with …):

// create the form
Form1 f = new Form1 ();
// show the form
if (f.ShowDialog ( this ) == DialogResult.OK)
{
    Console.WriteLine ("OK button was pressed.");
}
// check the DialogResult property
if (f.DialogResult == DialogResult.OK) { /* … */ }
else if (f.DialogResult == DialogResult.Cancel) { /* … */ }
f.Dispose();
  • Console.WriteLine shows nothing. The WinForms template builds a WinExe, which has no console attached. Use Debug.WriteLine for the Visual Studio Output window, or a label or MessageBox for the user.
  • Form1 opens another Form1. The name was reused for brevity, but a real app has a separate dialog class (see NameDialog below).
  • The same check runs twice. The return value and the DialogResult property hold the same value. Reading the property afterwards works, because a form shown with ShowDialog is only hidden when it closes, not disposed, but the return value alone is enough. Leave disposal to using.

How DialogResult travels

From the moment the user presses Enter until ShowDialog returns, this is the sequence:

Flow chart. Top row: pressing Enter or clicking OK sets the form's DialogResult to OK, which raises FormClosing. A decision asks whether the input is valid. If yes, ShowDialog returns OK. If no, the handler sets e.Cancel to true, WinForms resets DialogResult to None and the dialog stays open for another try. Bottom row: Esc, the Cancel button or the title-bar close button set DialogResult to Cancel and ShowDialog returns Cancel without the check.
What happens between the Enter key and the return of ShowDialog. Cancelling FormClosing resets DialogResult to None, so the dialog stays open and the caller keeps waiting.
  1. Enter → AcceptButton (the OK button) is clicked → the button's DialogResult (OK) is copied to the form's DialogResult.
  2. The modal loop notices that DialogResult is no longer None, starts closing, and raises FormClosing.
  3. If no handler sets e.Cancel, the form is hidden and ShowDialog returns OK.
  4. If a handler sets e.Cancel = true, WinForms resets DialogResult to None. The dialog stays open and the caller keeps waiting.

Step 4 is visible in Form.CheckCloseDialog in dotnet/winforms: if (e.Cancel) followed by _dialogResult = DialogResult.None;. Esc, the Cancel button and the title-bar close button (×) all end in Cancel; the Microsoft Learn page for Form.DialogResult explains that clicking the close button of a modal dialog hides the form and sets DialogResult to Cancel. Assigning this.DialogResult = DialogResult.OK; in code takes the same path as a button click.

Keep the dialog open on invalid input: FormClosing and e.Cancel

Here is a dialog that refuses to close when OK is pressed with an empty name, and shows an error icon instead. It compiled for net10.0-windows with zero warnings:

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; also sets _cancel.DialogResult = Cancel
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if (DialogResult == DialogResult.OK && UserName.Length == 0)
        {
            e.Cancel = true;                       // WinForms resets DialogResult to None
            _error.SetError(_name, "Enter a name.");
            _name.Focus();
        }
        base.OnFormClosing(e);
    }
}

And the caller:

void AskName()
{
    using var dlg = new NameDialog();
    if (dlg.ShowDialog(this) == DialogResult.OK)
        Text = $"Hello, {dlg.UserName}";
    else
        Debug.WriteLine($"cancelled: {dlg.DialogResult}");
}

The design choices, briefly:

  • The check includes DialogResult == DialogResult.OK. Without it, Cancel and the close button (×) would also hit the validation, and the user could never leave the dialog.
  • Always call base.OnFormClosing(e). Skip it and any FormClosing handlers attached from outside never run.
  • The Cancel button has CausesValidation = false. If you add a Validating check to the text box, this stops it from firing — and trapping the user — when focus moves to Cancel.
  • The alternative: leave the OK button's DialogResult at None and, in its Click handler, assign this.DialogResult = DialogResult.OK; only when validation passes. That keeps the check in one obvious place. The OnFormClosing version has a different strength: every route that closes the dialog with OK — Enter, the button, an assignment in code — goes through the same check.

FormClosing also fires for a Windows shutdown (CloseReason.WindowsShutDown) and for Application.Exit. Blocking those would get in the way of sign-out, which is another reason to limit the check to DialogResult == OK. The same trap appears in the "hide instead of close" section of showing only one instance of a form.

Summary

Goal Setting
Enter confirms AcceptButton = okButton + okButton.DialogResult = OK
Esc cancels CancelButton = cancelButton (its DialogResult becomes Cancel automatically)
Read the result using var dlg …; if (dlg.ShowDialog(this) == DialogResult.OK)
Stay open on bad input In OnFormClosing: DialogResult == OK && invalid → e.Cancel = true
Skip validation on Cancel cancelButton.CausesValidation = false
Debug output Debug.WriteLine, not Console.WriteLine

The 2021 post also had a VB.NET version; it calls the same API, so this edition keeps C# only. The original post had no images, so none were removed.

Environment: .NET SDK 10.0.403, Microsoft.WindowsDesktop.App.Ref 10.0.14, C# 14, macOS on an Apple Silicon Mac. WinForms code was compiled with dotnet build -p:EnableWindowsTargeting=true and not run.

FAQ

If I set CancelButton, do I also have to set the button's DialogResult to Cancel?

No. In dotnet/winforms, the CancelButton setter in Form.cs changes the button's DialogResult to Cancel when it is None. AcceptButton is different: it only maps the Enter key to that button and does not touch DialogResult, so the OK button needs DialogResult = OK set explicitly.

How do I keep the dialog open when the input is invalid?

In FormClosing (or an OnFormClosing override), set e.Cancel = true when DialogResult is OK and the input is invalid. When closing is cancelled, WinForms resets DialogResult to None, so the dialog stays open and ShowDialog does not return. Alternatively, leave the OK button's DialogResult at None and set this.DialogResult = DialogResult.OK in its Click handler only when validation passes.

Why does Console.WriteLine from the 2021 sample print nothing?

A WinForms app is built as WinExe, which has no console window, so Console.WriteLine output goes nowhere. Use System.Diagnostics.Debug.WriteLine for the Visual Studio Output window while debugging, or a label or MessageBox for anything the user should see.

  • #C#
  • #WinForms
  • #DialogResult
  • #AcceptButton
  • #FormClosing

t.laboratory

A technical blog that records real problems from the apps, servers and services we build and run — and the code that fixed them.

About · Apps

↑