100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

The WPF Application Lifecycle

How a WPF application starts up, runs its message loop via the Dispatcher, and shuts down, including window lifecycle events and ShutdownMode.

FoundationsIntermediate10 min readJul 10, 2026
Analogies

The WPF Application Lifecycle

A WPF application's lifecycle starts before any of your code runs: the compiler generates a Main() method that constructs the App object, calls its auto-generated InitializeComponent() (which merges the resources declared in App.xaml), and then calls Application.Run(), which starts the message loop and, if StartupUri is set, creates and shows that window. From there the app lives inside that message loop, dispatching input, layout, and render work until something calls Application.Shutdown() or the last window closes, at which point the Exit event fires and the process winds down.

🏏

Cricket analogy: Like a match day starting with the toss and ground-readiness checks before a ball is even bowled, a WPF app's generated Main() sets up the Application object and merges App.xaml resources before Application.Run() actually 'bowls the first ball' by showing the first window.

Startup: From Main() to the First Window

The Startup event, or overriding OnStartup(StartupEventArgs e) in App.xaml.cs, is where you run logic that needs to happen before the main UI appears — reading command-line arguments from e.Args, configuring a dependency-injection container, checking for a required config file, or deciding which window to show based on runtime conditions. If you need that kind of conditional logic, it's common to remove StartupUri from App.xaml entirely and instead construct and Show() the chosen window manually inside OnStartup, giving you full control over what the user sees first instead of relying on a fixed XAML attribute.

🏏

Cricket analogy: Like a captain checking the pitch report and team fitness before deciding the final XI just before the toss, OnStartup lets a WPF app check conditions like config files or command-line args before deciding which window to show first.

The Dispatcher and the Message Loop

WPF UI runs on a single-threaded apartment: the thread that creates a window also owns its Dispatcher, and every DispatcherObject-derived UI element (which is nearly all of them) can only be touched safely from that owning thread. The Dispatcher itself maintains a priority queue of work items — input events, layout passes, render operations, and any code you've queued — and processes them roughly in priority order as part of the message loop that Application.Run() started; if a background thread (say, one doing an HTTP call or heavy computation) needs to update a UI element, it must marshal that call back onto the UI thread via Dispatcher.Invoke or Dispatcher.BeginInvoke rather than touching the control directly.

🏏

Cricket analogy: Like only the official scorer being allowed to update the master scoreboard while anyone else must relay their correction through that scorer, WPF's Dispatcher is the only thing allowed to touch UI elements directly, so background threads must relay updates through Dispatcher.Invoke.

Window Lifecycle Events

Each Window fires a predictable sequence of events: Loaded fires once the window's visual tree has been fully constructed and is ready for layout-dependent logic (measuring actual rendered sizes, for instance), Activated/Deactivated fire as the window gains or loses focus, and closing fires two events — Closing, which passes a CancelEventArgs so you can prompt 'save changes?' and abort the close by setting e.Cancel = true, followed by Closed, which fires once the window is actually gone and is the right place for final cleanup like unsubscribing event handlers or disposing resources.

🏏

Cricket analogy: Like a stadium's gates opening (Loaded) only once all seating and screens are actually installed and ready, and the 'last over' warning (Closing) giving officials one chance to call a rain delay before play actually ends (Closed), a WPF Window's Loaded fires when its visual tree is ready and Closing lets you cancel before Closed finalizes.

csharp
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        if (e.Args.Contains("--minimized"))
        {
            ShutdownMode = ShutdownMode.OnExplicitShutdown;
        }

        var mainWindow = new MainWindow();
        mainWindow.Show();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        Logger.Flush();
        base.OnExit(e);
    }
}
csharp
// Updating the UI safely from a background thread
Task.Run(() =>
{
    var result = LoadDataFromServer();

    Application.Current.Dispatcher.Invoke(() =>
    {
        StatusLabel.Text = $"Loaded {result.Count} records";
    });
});

ShutdownMode has three values: OnLastWindowClose (default) exits when every window closes, OnMainWindowClose exits when the designated MainWindow closes, and OnExplicitShutdown only exits when code calls Application.Shutdown() — the last one is common for system-tray apps.

Shutdown

How a WPF app decides to shut down is controlled by Application.ShutdownMode: the default, OnLastWindowClose, exits the app once every open window has closed; OnMainWindowClose exits as soon as the specific window marked MainWindow closes, even if other windows are still open; and OnExplicitShutdown disables automatic exit entirely, useful for tray-icon apps that should keep running with no visible window until code explicitly calls Application.Shutdown(). Whichever mode triggers it, the Exit event (or an OnExit override in App.xaml.cs) fires last, right before the process actually terminates, making it the correct place for final cleanup like flushing logs or releasing unmanaged resources.

🏏

Cricket analogy: Like a match ending differently depending on the format — a T20 ends strictly after 20 overs regardless of other conditions, while a Test can end early for a declaration — WPF's ShutdownMode decides differently whether the app exits on the last window closing, the main window closing, or only on an explicit call.

Touching a UI element from a background thread — e.g. StatusLabel.Text = ... inside a Task.Run — throws an InvalidOperationException because that DispatcherObject belongs to the UI thread. Marshal the update through Dispatcher.Invoke or Dispatcher.BeginInvoke instead.

  • WPF's generated Main() constructs the App object, merges App.xaml resources, then calls Application.Run() to start the message loop.
  • OnStartup(StartupEventArgs e) in App.xaml.cs is where you read args, configure services, or decide which window to show first.
  • WPF UI is single-threaded; only the owning thread's Dispatcher can touch a DispatcherObject-derived control directly.
  • Background threads must marshal UI updates through Dispatcher.Invoke or Dispatcher.BeginInvoke or they throw InvalidOperationException.
  • Window fires Loaded once its visual tree is built, then Activated/Deactivated on focus changes, then Closing (cancelable) and Closed.
  • Application.ShutdownMode controls whether the app exits on last-window-close, main-window-close, or only an explicit Shutdown() call.
  • The Exit event / OnExit override fires last, right before process termination, and is the place for final cleanup.

Practice what you learned

Was this page helpful?

Topics covered

#NET#WPFWindowsPresentationFoundationStudyNotes#MicrosoftTechnologies#TheWPFApplicationLifecycle#WPF#Application#Lifecycle#Startup#StudyNotes#SkillVeris