WinForms ShowDialog vs Show: Modal vs Modeless

ShowDialog or Show for a WinForms form on .NET 10: DialogResult, owner forms, who disposes what, the high-DPI default and ShowDialogAsync.

Revised November 2026. This tutorial first appeared in Korean in 2021. We recompiled its C# samples as a WinForms app on .NET 10 (net10.0-windows). WinForms is Windows-only, so everything here was compiled with .NET 10 for Windows; not run on this machine (an Apple Silicon Mac). That means there is no program output in this article. What we did verify is the compiler result (warnings and error codes), the code the project template generates, and a compile difference between .NET 9 and .NET 10. Run-time behavior is described from the Microsoft Learn pages named in the text. The two Visual Studio 2019 screenshots from 2021 were removed; the reason is at the end.

This article is for WinForms developers who open a second form — a settings window, an input dialog, a tool palette — and are not sure whether to call ShowDialog or Show. The rule of thumb: if the next line of code needs the user's answer, call ShowDialog(this) and dispose the form with using. If the user should work in both windows at once, call Show(this) and get results back through an event.

The C# samples from 2021 compiled unchanged with zero warnings. Only the comments are shortened here:

// Form2 instance
Form2 f = new Form2();
// open as a modal form, owner = this
f.ShowDialog(this);
// destroy the object when the window closes
f.Dispose();
// Form2 instance
Form2 f = new Form2();
// open as a modeless form
f.Show();

The two calls differ in three ways:

Decision diagram. Question: do you need the result before the next line runs? If yes, call ShowDialog(this): the caller waits, ShowDialog returns a DialogResult and the form is only hidden, so you dispose it yourself, for example with using var. If no, call Show(this): code after the call runs at once, closing the form disposes it, and you read results through FormClosed or your own event.
ShowDialog and Show differ in three things: whether the calling code waits, whether you get a DialogResult back, and who disposes the form.
  • Does the caller wait? ShowDialog does not return until the form closes. Meanwhile the other windows on the same thread, such as the main form, ignore input. Show displays the window and returns at once, so the next line runs immediately.
  • Is there a result? ShowDialog returns a DialogResult. Show returns void.
  • Who disposes it? The Microsoft Learn page for Form.ShowDialog explains that when a modal form is closed with the close button or by setting DialogResult, Close is not called; the form is only hidden. You have to dispose it. The Form.Close page lists the same exception: a form shown with ShowDialog is not disposed by Close. So the 2021 rule "dispose after ShowDialog, not needed after Show" was correct.

One sentence in the 2021 text needs a qualifier: "while a modal form is open, nothing else can be accessed." More precisely, windows on the same UI thread are blocked. Other programs, and windows created on another UI thread, keep working.

The 2026 way: using, owners, no duplicates

This is how we would write it today. The code compiled for net10.0-windows with zero warnings:

void OpenModal()
{
    using var dlg = new Form2();
    DialogResult result = dlg.ShowDialog(this);   // blocks here; owner disabled
    Text = $"closed with {result}";
}                                                 // disposed here

Form2? _tool;
void OpenModeless()
{
    if (_tool is { IsDisposed: false }) { _tool.Activate(); return; }
    _tool = new Form2();
    _tool.FormClosed += (_, _) => _tool = null;
    _tool.Show(this);                              // owned: stays above, minimizes with owner
}
  • using var disposes the dialog even if an exception is thrown. The 2021 version skipped Dispose if anything inside ShowDialog threw.
  • The 2021 modeless sample called f.Show() with no owner. The new form then drops behind the main form on the first click, and every click of the button opens yet another copy. The usual tool-window behavior is Show(this) with an owner, and Activate() if the window is already open. Keeping a form to a single copy is covered in detail in showing only one instance of a form.
  • A modeless form cannot hand back a value the way ShowDialog does, so pass results through FormClosed or an event of your own. Declaring events is covered in C# events.

Wiring DialogResult to OK and Cancel buttons, and keeping a dialog open when the input is invalid, continues in OK and Cancel buttons.

The .NET 10 template and its high-DPI default

The 2021 tutorial started from Visual Studio 2019's "Windows Forms App (.NET Framework)" template. On .NET 10 you can run dotnet new winforms; this is the Program.cs it generated:

[STAThread]
static void Main()
{
    // To customize application configuration such as set high DPI settings or default font,
    // see https://aka.ms/applicationconfiguration.
    ApplicationConfiguration.Initialize();
    Application.Run(new Form1());
}

ApplicationConfiguration.Initialize() is written by a source generator at build time. Building with EmitCompilerGeneratedFiles=true exposes the generated file:

public static void Initialize()
{
    global::System.Windows.Forms.Application.EnableVisualStyles();
    global::System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
    global::System.Windows.Forms.Application.SetHighDpiMode(HighDpiMode.SystemAware);
}

The default was SystemAware: the app renders for the scale of the primary monitor at start-up, and when a window moves to a monitor with a different scale, Windows stretches it as a bitmap, which can look blurry. One line in the project file changes the generated code:

<PropertyGroup>
  <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
global::System.Windows.Forms.Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);

The Microsoft Learn article "High DPI support in Windows Forms" recommends PerMonitorV2 for mixed-scale setups. Dialogs often end up on a different monitor from their owner, so this is worth checking in any app that opens extra forms. The Application.SetHighDpiMode page also notes that the mode must be set before the first window is created: no code may new a form (for example in a static field initializer) before Initialize() runs.

Async and ShowDialog: the 2021-era pitfalls

Around 2021, "run the slow work asynchronously, then show a dialog" led to three common mistakes. All three still apply.

  1. Calling ShowDialog inside Task.Run. The form is created on a thread-pool (MTA) thread. WinForms assumes an STA UI thread — the [STAThread] on Main — and controls may only be touched from the thread that created them. Passing a UI-thread form as the owner becomes a cross-thread call, and OLE features such as the clipboard or drag and drop fail with an exception about the thread not being STA.
  2. Blocking the UI thread with .Result or .Wait(). The UI thread is stuck, so the continuation after await, which needs the UI thread, can never run: a deadlock.
  3. The fix is an async event handler that calls ShowDialog after await. Thanks to the WinForms synchronization context, code after await runs on the UI thread again, so showing a form there is fine.
async void OpenAfterLoadAsync()
{
    string data = await Task.Run(() => "loaded");  // back on UI thread after await
    using var dlg = new Form2 { Text = data };
    dlg.ShowDialog(this);
}

.NET 9 added Form.ShowDialogAsync. The XML documentation in the reference pack says it returns a Task<DialogResult> that completes when the form is closed or disposed. We built the same method against both versions, and the results differed:

async Task OpenModalAsync()
{
    using var dlg = new Form2();
    DialogResult r = await dlg.ShowDialogAsync(this);
    Text = r.ToString();
}
# net9.0-windows (Microsoft.WindowsDesktop.App.Ref 9.0.22)
Program.cs(69,32): error WFO5002: 'System.Windows.Forms.Form.ShowDialogAsync(System.Windows.Forms.IWin32Window)' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

# net10.0-windows (Microsoft.WindowsDesktop.App.Ref 10.0.14)
Build succeeded. 0 Warning(s) 0 Error(s)

On .NET 9 the API was experimental and the build stopped with an error, not a warning. On .NET 10 it compiled with no diagnostics. We did not run it, so its run-time behavior is only as good as the documentation.

Summary

Situation Use
Need the answer before continuing using var dlg = …; dlg.ShowDialog(this)
Tool window used alongside the main form Show(this), Activate() if already open
Getting results from a modeless form FormClosed or your own event
Dialog after async work ShowDialog after await in an async handler (or ShowDialogAsync on .NET 10)
Several monitors with different scaling <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>

We deleted the two Visual Studio 2019 "Create a new project" screenshots from the 2021 post. Their red highlight boxes and file names match the images in Microsoft's own tutorial, so we could not confirm they were our own captures (copyright risk), and they show a .NET Framework template that no longer applies. Installing the tools is covered in installing Visual Studio. The 2021 post also had VB.NET versions; they call the same API, so this edition keeps C# only.

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

FAQ

Do I have to dispose a form opened with ShowDialog?

Yes. The Microsoft Learn page for Form.ShowDialog explains that a modal form is not closed when the user clicks the close button or when DialogResult is set; it is only hidden so it can be shown again. You must call Dispose yourself, and using var dlg = new Form2(); is the simplest way. A modeless form opened with Show is disposed when it closes.

What is the difference between Show(this) and Show()?

Show(this) makes the calling form the owner. An owned form always stays above its owner and is minimized or closed together with it. With plain Show() the second form can disappear behind the main form as soon as the user clicks the main form.

Can I use Form.ShowDialogAsync on .NET 10?

On net10.0-windows (reference pack 10.0.14) Form.ShowDialogAsync compiled with no diagnostics. The same code built for net9.0-windows failed with error WFO5002 (for evaluation purposes only). If you must target .NET 9 you have to suppress that diagnostic; on .NET 10 it is an ordinary API.

  • #C#
  • #WinForms
  • #ShowDialog
  • #modal
  • #modeless

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

↑