WinForms cheat sheet

A scannable WinForms reference: 9 short snippets across 7 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Dialogs, multiple forms and MDIShow() returns immediately and the form is modeless. ShowDialog() blocks until the form closes and returns alesson
DataGridView in depthChaining BindingSource objects gives you master-detail for free: the child list is re-evaluated from the current masterlesson
Async work, threading and progressBefore .NET Core, touching a control from another thread threw InvalidOperationException. Modern WinForms still raiseslesson
Files, settings and persistenceWrite settings atomically: serialise to a temporary file in the same directory, then rename over the target. A crashlesson
High DPI, accessibility and themingCheck three things on any form: every control reachable with Tab only, no control that steals focus back, and everylesson
Deployment: ClickOnce, MSIX and single-fileClickOnce's real advantage is that the update path is part of the manifest, not a separate updater you maintain. Itslesson
Migrating from WinForms to WPF, WinUI or Blazor HybridA WinForms application hides its business rules in event handlers. The first job is to find out how much of the code islesson

Quick snippets

Dialogs, multiple forms and MDI

Modal and modeless forms

using (var dlg = new CustomerEditForm(customerId))
{
    dlg.StartPosition = FormStartPosition.CenterParent;
    if (dlg.ShowDialog(this) == DialogResult.OK)
        ReloadCustomer(dlg.SavedCustomer);
}

MDI parents and children

// parent
IsMdiContainer = true;

var child = new DocumentForm { MdiParent = this, Text = "Report 1" };
child.Show();

// arrange what is open
LayoutMdi(MdiLayout.TileHorizontal);

// track the active document
ActiveMdiChildChanged += (s, e) =>
    _statusLabel.Text = ActiveMdiChild?.Text ?? "No document";

Full lesson: Dialogs, multiple forms and MDI →

DataGridView in depth

Master-detail without extra work

var customers = new BindingSource { DataSource = customerList };
var orders    = new BindingSource { DataSource = customers, DataMember = "Orders" };

customerGrid.DataSource = customers;
orderGrid.DataSource    = orders;   // re-queries whenever the master selection moves

// and a live count in the header
customers.CurrentChanged += (s, e) =>
    orderGrid.Columns["Total"].HeaderText =
        "Total (" + orderGrid.Rows.Count + ")";

Full lesson: DataGridView in depth →

Async work, threading and progress

Cross-thread access and marshalling

// called from any thread
void AppendLog(string line)
{
    if (logBox.InvokeRequired)
    {
        logBox.BeginInvoke(new Action<string>(AppendLog), line);   // async, no deadlock
        return;
    }
    logBox.AppendText(line + Environment.NewLine);
}

Full lesson: Async work, threading and progress →

Files, settings and persistence

Recent files and safe handling

void RememberFile(string path)
{
    var recent = _settings.RecentFiles;
    recent.RemoveAll(p => string.Equals(p, path, StringComparison.OrdinalIgnoreCase));
    recent.Insert(0, path);
    if (recent.Count > _settings.RecentCount)
        recent.RemoveRange(_settings.RecentCount, recent.Count - _settings.RecentCount);
    AppSettings.Save(_settings);
    RebuildRecentMenu();
}

Full lesson: Files, settings and persistence →

High DPI, accessibility and theming

Per-monitor DPI

<!-- App.manifest: opt in to per-monitor v2, not just system aware -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
  </windowsSettings>
</application>

Full lesson: High DPI, accessibility and theming →

Deployment: ClickOnce, MSIX and single-file

The channels

# framework-dependent: small, needs the runtime installed
dotnet publish -c Release -r win-x64 --self-contained false -o out

# self-contained single file: no runtime needed, larger output
dotnet publish -c Release -r win-x64 --self-contained true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -p:EnableCompressionInSingleFile=true \
  -o out

# trimmed is not supported for WinForms - do not add PublishTrimmed

ClickOnce

<!-- in the .csproj -->
<PropertyGroup>
  <PublishUrl>\\fileserver\apps\OrderTool\</PublishUrl>
  <InstallUrl>https://apps.example.com/ordertool/</InstallUrl>
  <UpdateMode>Foreground</UpdateMode>
  <UpdateInterval>7</UpdateInterval>
  <UpdateIntervalUnits>Days</UpdateIntervalUnits>
  <ApplicationRevision>3</ApplicationRevision>
  <ApplicationVersion>1.4.0.*</ApplicationVersion>
  <SignManifests>true</SignManifests>
</PropertyGroup>

Full lesson: Deployment: ClickOnce, MSIX and single-file →

Migrating from WinForms to WPF, WinUI or Blazor Hybrid

Migrating incrementally

// host a WinForms control inside a WPF window while you migrate screen by screen
public partial class LegacyHost : System.Windows.Forms.Integration.WindowsFormsHost
{
    public LegacyHost()
    {
        Child = new OrderGridControl();       // the untouched WinForms control
    }
}

// and the reverse: a WPF control inside a WinForms form, via element host
var host = new ElementHost { Dock = DockStyle.Fill, Child = new WpfSummaryView() };
panel.Controls.Add(host);

Full lesson: Migrating from WinForms to WPF, WinUI or Blazor Hybrid →

FAQ

Is this WinForms cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 7 lessons of the WinForms course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full WinForms course — it carries the worked explanations, the edge cases and the exercises behind every line here.

.NET ASP.NET Core LINQ WPF

Last refreshed 2026-09-27.