What WPF is and how a project is structured

WPF on modern .NET versus .NET Framework, App.xaml and startup, the visual tree, and a project layout that keeps XAML and logic apart.

What WPF actually is

WPF is a retained-mode UI framework: you describe a tree of objects, and the framework keeps it painted. That is the key difference from WinForms, where you own the drawing and the state. It also explains why layout, styling and animation are declarative here and manual there.

AspectWPFWinForms
RenderingRetained mode, vector based, DirectX backedImmediate mode, GDI+ drawing
UI definitionXAML, a serialised object graphDesigner-generated code
LayoutMeasure and arrange passes, panelsDock, Anchor, absolute coordinates
StylingStyles, templates, resource dictionariesPer-control properties and owner-draw
ScalingResolution independent by designDPI awareness plus manual scaling
Data flowBinding and change notificationManual assignment and event handlers

Use WPF on .NET 8 or later. WPF on .NET Framework 4.8 still runs, but it misses the performance work, the modern C# language level and the smaller hosting story. The API is nearly identical, which makes the upgrade mostly a project-file change.

App.xaml, startup and the project layout

<!-- App.xaml: application-wide resources and the startup URI -->
<Application x:Class="Orders.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="Views/MainWindow.xaml">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Themes/Colors.xaml" />
        <ResourceDictionary Source="Themes/Controls.xaml" />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>
// App.xaml.cs - keep real startup work here instead of StartupUri when it grows
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        DispatcherUnhandledException += (s, args) =>
        {
            Log.Fatal(args.Exception, "Unhandled UI exception");
            MessageBox.Show("Something went wrong. The details were logged.");
            args.Handled = true;      // keep the app alive; remove if you prefer to crash
        };

        var services = new ServiceCollection();
        services.AddSingleton<OrderRepository>();
        services.AddTransient<MainViewModel>();

        var window = new MainWindow { DataContext = services.BuildServiceProvider().GetRequiredService<MainViewModel>() };
        window.Show();
    }
}
  • App.xaml compiles into App.g.cs; the Main method that the entry point uses is generated, not written by you.
  • Remove StartupUri the moment you need dependency injection or any pre-window work, or you will build the window twice.
  • A conventional layout is Views/, ViewModels/, Models/, Services/, Themes/. It is not a framework requirement, but it keeps the resource dictionaries findable.

The visual tree and code-behind

<Window x:Class="Orders.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Orders" Height="600" Width="900">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Text="{Binding Header}" />
    <DataGrid Grid.Row="1" ItemsSource="{Binding Orders}" />
  </Grid>
</Window>
// MainWindow.xaml.cs - the code-behind: only view concerns belong here
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();          // required: builds the XAML tree
        Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        // wiring a view-only concern is fine here
        FocusManager.SetFocusedElement(this, SearchBox);
    }
}

The visual tree is every rendered element, including the ones a template created; the logical tree is only what you declared. Resource lookup, event routing and ItemsControl containers follow the logical tree, while hit testing and rendering follow the visual tree - most confusing WPF behaviour is one of these two being the one you did not mean.

💡
The quickest way to understand an unfamiliar WPF window is a live visual tree tool. Enable it in your IDE, select an element, and look at the actual ancestor chain - that answers most "why does this style not apply" questions in seconds.

FAQ

Is XAML required?
No, but writing WPF entirely in C# gives up the tooling, the design surface and most of the value. Write the view in XAML and keep logic in C# classes the view binds to.
How long does the XAML parse cost at startup?
It is a real cost for large trees. Compile XAML to BAML at build time (the default), and for heavy repeated views create the template once and reuse instances rather than reloading markup.

XAML and layout panels Dependency properties and routed events

Last refreshed 2026-09-18.