Forms and controls

How a WinForms application starts, the controls you will reach for first, and the designer files that are generated for you.

How the application starts

A WinForms app is a Windows message loop. Application.Run starts that loop and returns when the main form closes. The [STAThread] attribute on Main is required: Windows common dialogs and the clipboard need a single-threaded apartment.

// Program.cs
using System;
using System.Windows.Forms;

internal static class Program
{
    [STAThread]
    private static void Main()
    {
        ApplicationConfiguration.Initialize();   // sets DPI mode and visual styles
        Application.Run(new MainForm());
    }
}

// MainForm.cs - code only, no designer
using System.Windows.Forms;

public sealed class MainForm : Form
{
    public MainForm()
    {
        Text = "Inventory";
        Width = 900;
        Height = 600;
        StartPosition = FormStartPosition.CenterScreen;

        var label = new Label { Text = "Search", AutoSize = true, Location = new System.Drawing.Point(12, 15) };
        var box   = new TextBox { Location = new System.Drawing.Point(70, 12), Width = 240 };
        var grid  = new DataGridView { Dock = DockStyle.Bottom, Height = 420 };

        AcceptButton = new Button { Text = "Go", DialogResult = DialogResult.OK };

        Controls.Add(label);
        Controls.Add(box);
        Controls.Add(grid);
    }
}
  • ApplicationConfiguration.Initialize() replaces the older Application.EnableVisualStyles() plus SetCompatibleTextRenderingDefault pair and also sets the high-DPI mode from the project file.
  • Controls.Add order matters for Dock layout — the last control added docks first, so add the fill target last if you are not using a designer.
  • Set AcceptButton and CancelButton so Enter and Escape close a dialog as users expect.

The control map

ControlUse it forWatch out for
LabelStatic textSet AutoSize deliberately; the default can clip long text
TextBoxSingle or multi-line inputMultiline plus AcceptsReturn for free text
ButtonCommandsOne handler can serve many buttons; check the sender
ComboBoxChoose one from a listSet DropDownStyle; SelectedIndexChanged fires during load
ListBox / CheckedListBoxSimple selection listsUse BeginUpdate/EndUpdate when adding many items
DataGridViewTabular data with editingBound mode or virtual mode; never both at once
TreeViewHierarchiesLazy-populate on BeforeExpand rather than building everything
MenuStrip / ToolStripMenus and toolbarsMerge order matters when hosting MDI or plugin children
PropertyGridEditing an object's propertiesGreat for internal tools; not a user-facing pattern
FolderBrowserDialog / OpenFileDialogFile and folder pickingUse the modern OpenFolderDialog on .NET 8+
// Adding many rows without flicker: suspend layout and painting
listBox1.BeginUpdate();
try
{
    foreach (var item in LoadItems())
        listBox1.Items.Add(item);
}
finally
{
    listBox1.EndUpdate();
}

// A modal dialog returns a result instead of raising events
using var dialog = new SettingsForm();
if (dialog.ShowDialog(this) == DialogResult.OK)
    ApplySettings(dialog.Settings);
💡
ShowDialog returns a DialogResult and blocks the caller; Show returns immediately and makes the form modeless. A settings dialog should be modal, a log window should not.

The generated designer file

The Visual Studio designer splits a form into MainForm.cs (your handler code) and MainForm.Designer.cs (control declarations and layout). The generated file is rewritten on every designer save, so any edit you make there is lost.

// MainForm.Designer.cs - generated, do not hand-edit
partial class MainForm
{
    private System.ComponentModel.IContainer components = null;
    private System.Windows.Forms.Button saveButton;
    private System.Windows.Forms.DataGridView grid;

    protected override void Dispose(bool disposing)
    {
        if (disposing && components != null) components.Dispose();
        base.Dispose(disposing);
    }

    private void InitializeComponent()
    {
        this.saveButton = new System.Windows.Forms.Button();
        this.grid = new System.Windows.Forms.DataGridView();
        this.SuspendLayout();

        this.saveButton.Text = "Save";
        this.saveButton.Click += new System.EventHandler(this.SaveButton_Click);
        this.grid.Dock = System.Windows.Forms.DockStyle.Fill;

        this.Controls.Add(this.grid);
        this.Controls.Add(this.saveButton);
        this.ResumeLayout(false);
    }
}
  • Put custom logic in the hand-written partial class, never in InitializeComponent.
  • The designer does not understand loops or conditionals in layout; if you need dynamic controls, build them in code at runtime.
  • Review designer diffs in a pull request. A stray drag can silently re-parent a control or change a Dock value.
  • Deleting a control in the designer leaves its event handler method behind as dead code — clean it up.

FAQ

Can WinForms run on macOS or Linux?
No. WinForms is a Windows-only UI stack. For cross-platform desktop use MAUI, Avalonia or Uno; for a browser-first app, use a web UI. The business logic can still be shared through a .NET library.
Why do my controls look blurry on a high-DPI monitor?
The app is not per-monitor DPI aware. Set the application high-DPI mode in the project file and let ApplicationConfiguration.Initialize apply it, then verify at 150 percent scaling rather than assuming.

Events and layout Data binding and when not to use WinForms

Last refreshed 2026-09-18.