WinUI 3 & the Windows App SDK

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

WinUI 3 is the UI framework that Microsoft recommends for new native Windows desktop apps, and the Windows App SDK is the set of libraries that ships it together with windowing, the app lifecycle, notifications, resource management and other platform APIs. Where WPF is a part of .NET and Windows Forms wraps the Win32 controls, WinUI 3 is an independent XAML framework that is delivered as a NuGet package and runs on Windows 10 and Windows 11 without depending on the operating system’s own version of the controls. This page explains what that means in practice: how an app is assembled, how {x:Bind} replaces {Binding} for most work, how a shell with NavigationView, a custom title bar and Mica is built, how single-instance and activation work, and how a packaged app differs from an unpackaged one. The running example is the main screen of Bookshelf Desktop, re-implemented in WinUI 3.

The page was written against these versions, current in October 2026:

  • .NET 10 LTS, latest patch 10.0.13, supported until 14 November 2028, with C# 14. .NET 11 is at release candidate 2 (11.0.0-rc.2); general availability is expected at .NET Conf, 10 to 13 November 2026. None of the APIs on this page is listed as changed for .NET 11.

  • Visual Studio 2026, latest 18.11.1, with the "WinUI application development" workload.

  • Windows App SDK 2.5.1 on the stable channel. Version 2.0 (29 April 2026) was the first release to follow semantic versioning, and the NuGet package version equals the SDK version.

  • CommunityToolkit.Mvvm 8.4.2 for the view models.

  • Windows 11 and Windows 10. The support page lists Windows 10 version 1809 (build 17763) as the oldest client release supported by Windows App SDK 2, and Microsoft supports the SDK only on Windows releases that are themselves still in support.

The XAML language is the subject of the XAML Language Fundamentals page and the differences between the three XAML dialects are in XAML Dialects: WPF, WinUI & MAUI. Choosing WinUI over WPF or Windows Forms is covered on Windows App Platforms: WPF, WinUI 3, WinForms, MAUI & More. This page goes deeper into what is specific to WinUI 3.

WinUI 3 and the Windows App SDK

WinUI 3 is the native UI framework of the Windows App SDK. Microsoft describes it as the recommended native UI framework for new Windows desktop applications, with the Fluent Design System, high-performance rendering and a XAML programming model for C# and C++. It is built in the open (the microsoft-ui-xaml repository on GitHub), parts of the Windows shell and built-in apps use it, and so do open-source tools such as PowerToys.

The Windows App SDK (formerly Project Reunion) is the wider package. WinUI is one of its components. The others are exposed to any desktop app, including WPF and Windows Forms apps.

Component What it provides

WinUI

The XAML framework: controls, styles, Window, input, text, animation and composition.

Windowing (AppWindow)

A high-level abstraction of the top-level window (HWND): size and position, title bar, icon, presenters such as overlapped, full screen and compact overlay.

App lifecycle

Instancing (AppInstance), rich activation (file, protocol, startup task) and power management notifications.

MRT Core

Resource management and localization with the .resw format and qualifiers.

Notifications

App notifications (toasts) and push notifications.

Deployment

The Deployment API and the bootstrapper for apps that are unpackaged or packaged with external location.

Text rendering and widgets

DWriteCore text rendering and Windows Widgets support.

Windows AI and ML

Windows AI APIs and Windows ML in separate packages.

WinUI 3 is not part of Windows or of .NET. An app carries (self-contained) or references (framework-dependent) the copy of the framework it was built with, so the controls look and behave the same on every supported Windows version. This is the biggest difference from WPF, where the controls ship with .NET, and from Windows Forms, whose controls are wrappers around the Win32 common controls of the operating system.

Versioning and release channels

Until 1.8 the Windows App SDK used its own version scheme and shipped a major or minor release every six months or so. Version 2.0 is the first release that follows semantic versioning 2.0.0, and the NuGet package version now matches the SDK version. The package family name aligns with the major version, so the next side-by-side major release will be 3.0.0 and breaking changes are allowed only across major versions.

The SDK ships through three channels:

Channel Intent Supported Latest release (October 2026)

Stable

Production-ready. Only stable, supported APIs. Major releases no more than every six months, plus minor and patch servicing updates.

Yes

2.5.1, released 16 September 2026

Preview

An early look at the next stable release. May introduce breaking API changes before stabilization.

No

2.0 Preview 2 (2.0.0-preview2), 31 March 2026

Experimental

Early-stage features under active development. APIs may change, be removed, or never ship.

No

2.5.4-experimental, 29 September 2026

The servicing policy distinguishes Current releases (the latest stable one, which receives fixes more often) from Maintenance releases (no longer the latest, critical fixes only). Version 2.0 is listed as Current with an end of servicing on 29 April 2027, version 1.8 moved to Maintenance and its end of servicing was 24 September 2026, and versions 1.7 and earlier are out of support. Support is conditional on using the latest patch of the release and a supported operating system. Plan to move a 1.x app to 2.x if it is still on the older line; the release notes list the behavior changes between them (see the version history table near the end of this page).

Project Anatomy

A WinUI 3 project is an SDK-style project like any other, with a handful of properties and files that identify it as a WinUI app. The full property list is on Project Structure & Build. The project of the Bookshelf WinUI front end, with everything this page uses, looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
    <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
    <RootNamespace>Bookshelf.WinUI</RootNamespace>
    <ApplicationManifest>app.manifest</ApplicationManifest>
    <Platforms>x86;x64;ARM64</Platforms>
    <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
    <UseWinUI>true</UseWinUI>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- Packaged builds come from the single project; see the packaged versus unpackaged section -->
    <EnableMsixTooling>true</EnableMsixTooling>

    <!-- Needed for the custom Program.Main in the single-instance section -->
    <DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="2.5.1" />
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Bookshelf.Core\Bookshelf.Core.csproj" />
    <ProjectReference Include="..\Bookshelf.Data\Bookshelf.Data.csproj" />
    <ProjectReference Include="..\Bookshelf.Desktop.ViewModels\Bookshelf.Desktop.ViewModels.csproj" />
  </ItemGroup>

</Project>

Source: Package your app using single-project MSIX (the EnableMsixTooling property) and Create a single-instanced WinUI app (the DISABLE_XAML_GENERATED_MAIN symbol)

The three project references mirror the solution layout of the other Bookshelf pages: the model and repository interface in Bookshelf.Core, the EF Core and SQLite implementation in Bookshelf.Data, and the view models in a class library, so that the same BooksViewModel serves the WPF, WinUI 3 and Windows Forms front ends. The view models reference no UI types, which also makes them testable without a window (see the Testing View Models & UI page).

The files that a WinUI template creates, and what each is for:

File Purpose

App.xaml, App.xaml.cs

The Application subclass. Application resources (merge XamlControlsResources here) and the OnLaunched override, which creates the first window.

MainWindow.xaml, MainWindow.xaml.cs

The main Window. It derives from Microsoft.UI.Xaml.Window, which is not a FrameworkElement.

Package.appxmanifest

The package manifest of a packaged app: identity, publisher, capabilities, visual assets and extensions such as protocol and file-type associations.

Assets/

Logos and images, built as Content items and addressed with ms-appx:/// URIs.

app.manifest

The application manifest (DPI awareness, supported operating systems).

Properties/launchSettings.json

Visual Studio launch profiles. A MsixPackage profile starts the packaged app, an unpackaged profile starts the executable.

Properties/PublishProfiles/*.pubxml

One publish profile per platform.

The XAML compiler turns each .xaml file into a binary .xbf file and generates a partial class in obj (MainWindow.g.cs) that connects the markup to your code, including the compiled {x:Bind} bindings. Opening these generated files and putting a breakpoint in them is a supported way to debug a binding.

The application class

The App class below registers the services with Microsoft.Extensions.DependencyInjection, creates the main window and hands the first activation to the same routine that later activations use. The generic host variant, with configuration, logging and hosted services, is on the Dependency Injection & the Generic Host page; here the container is plain so that the focus stays on WinUI.

using Bookshelf.Core;
using Bookshelf.Data;
using Bookshelf.Desktop;
using Bookshelf.Desktop.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;

namespace Bookshelf.WinUI;

public partial class App : Application
{
    /// <summary>The running application, typed.</summary>
    public static new App Current => (App)Application.Current;

    public IServiceProvider Services { get; }

    public MainWindow? MainWindow { get; private set; }

    public App()
    {
        Services = ConfigureServices();
        InitializeComponent();
    }

    private static ServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        services.AddBookshelfData();                 // IBookRepository: EF Core + SQLite, from Bookshelf.Data
        services.AddSingleton<WinUiDialogService>();
        services.AddSingleton<IDialogService>(sp => sp.GetRequiredService<WinUiDialogService>());

        // One BooksViewModel for the whole shell, so that a protocol activation can select a book
        // in the same instance that the page is bound to.
        services.AddSingleton<BooksViewModel>();

        return services.BuildServiceProvider();
    }

    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        // A plain ServiceCollection never starts IHostedService registrations, so run the ones
        // AddBookshelfData adds (DatabaseInitializer migrates the database) before any page queries it.
        foreach (var hostedService in Services.GetServices<IHostedService>())
        {
            await hostedService.StartAsync(CancellationToken.None);
        }

        MainWindow = new MainWindow();
        MainWindow.Activate();

        // Launch, file and protocol activations all arrive through the same routine.
        ActivationRouter.Handle(AppInstance.GetCurrent().GetActivatedEventArgs());
    }
}

Source: Data Binding with WinUI and MVVM Toolkit, step 3: add dependency injection for the App.Current and Services shape. AddBookshelfData and ActivationRouter are Bookshelf helpers, shown in the activation section. Because the container is not a Generic Host, OnLaunched starts the registered hosted services itself; a Generic Host starts them in its own StartAsync.

The tutorial registers its view models as transient, so that each page gets a fresh one. BooksViewModel is a singleton here only because the shell has one books screen and an activation handler must reach it; a page that edits one book at a time would be transient.

The Bookshelf Main Screen in WinUI 3

The WPF version of the main screen is a Window with a DataGrid, a details pane and a toolbar. The WinUI 3 version has the same parts, arranged as a typical Windows 11 app: a custom title bar, a NavigationView as the shell, a Frame that hosts the pages, and Mica as the window backdrop. Three files carry it: MainWindow (the shell), BooksPage (the screen) and the view models that already exist on the MVVM Toolkit page.

The shell: MainWindow

<Window
    x:Class="Bookshelf.WinUI.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Window.SystemBackdrop>
    <MicaBackdrop Kind="BaseAlt" />
  </Window.SystemBackdrop>

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <TitleBar x:Name="AppTitleBar"
              Title="Bookshelf"
              Subtitle="Desktop"
              IsBackButtonVisible="True"
              IsBackButtonEnabled="{x:Bind ContentFrame.CanGoBack, Mode=OneWay}"
              BackRequested="OnTitleBarBackRequested"
              IsPaneToggleButtonVisible="True"
              PaneToggleRequested="OnTitleBarPaneToggleRequested">
      <TitleBar.IconSource>
        <FontIconSource Glyph="&#xE8F1;" />
      </TitleBar.IconSource>
    </TitleBar>

    <NavigationView x:Name="NavView"
                    Grid.Row="1"
                    PaneDisplayMode="Auto"
                    IsBackButtonVisible="Collapsed"
                    IsPaneToggleButtonVisible="False"
                    IsSettingsVisible="True"
                    SelectionChanged="OnNavSelectionChanged">
      <NavigationView.MenuItems>
        <NavigationViewItem Content="Books" Tag="books" AutomationProperties.Name="Books">
          <NavigationViewItem.Icon>
            <FontIcon Glyph="&#xE8F1;" />
          </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Favorites" Tag="favorites" AutomationProperties.Name="Favorite books">
          <NavigationViewItem.Icon>
            <FontIcon Glyph="&#xE734;" />
          </NavigationViewItem.Icon>
        </NavigationViewItem>
      </NavigationView.MenuItems>

      <Frame x:Name="ContentFrame" IsTabStop="True" />
    </NavigationView>
  </Grid>
</Window>
using Bookshelf.WinUI.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Windows.Graphics;

namespace Bookshelf.WinUI;

public sealed partial class MainWindow : Window
{
    private static readonly Dictionary<string, Type> Pages = new()
    {
        ["books"] = typeof(BooksPage),
        ["favorites"] = typeof(FavoritesPage),
    };

    public MainWindow()
    {
        InitializeComponent();

        // In code, in the constructor: setting it in XAML is an error, and setting it later can flash
        // the system title bar first.
        ExtendsContentIntoTitleBar = true;
        SetTitleBar(AppTitleBar);

        Title = "Bookshelf";
        AppWindow.Resize(new SizeInt32(1200, 800));

        App.Current.Services.GetRequiredService<WinUiDialogService>().Attach(Content.XamlRoot);

        NavView.SelectedItem = NavView.MenuItems[0];
    }

    private void OnNavSelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
    {
        if (args.IsSettingsSelected)
        {
            Navigate(typeof(SettingsPage), args.RecommendedNavigationTransitionInfo);
        }
        else if (args.SelectedItemContainer?.Tag is string tag && Pages.TryGetValue(tag, out Type? pageType))
        {
            Navigate(pageType, args.RecommendedNavigationTransitionInfo);
        }
    }

    private void Navigate(Type pageType, NavigationTransitionInfo transition)
    {
        // Do not stack duplicate entries when the selected item is invoked again.
        if (ContentFrame.CurrentSourcePageType != pageType)
        {
            ContentFrame.Navigate(pageType, null, transition);
        }
    }

    private void OnTitleBarBackRequested(TitleBar sender, object args)
    {
        if (ContentFrame.CanGoBack)
        {
            ContentFrame.GoBack();
        }
    }

    private void OnTitleBarPaneToggleRequested(TitleBar sender, object args) =>
        NavView.IsPaneOpen = !NavView.IsPaneOpen;
}

What this shell uses, and where each piece is explained further down:

  • Window.SystemBackdrop with MicaBackdrop puts Mica behind everything. It shows through the title bar because the TitleBar control and the NavigationView content layer are transparent or low-opacity (see the backdrops section).

  • The TitleBar control replaces the system title bar. The back button and the pane toggle button live in the title bar, as the Fluent guidance recommends, so the NavigationView hides its own.

  • The NavigationView does not navigate by itself. It raises SelectionChanged (or ItemInvoked), and the code behind maps the Tag of the selected item to a page type and calls Frame.Navigate.

  • AppWindow.Resize takes physical pixels and sizes the whole window, including the border and title bar.

  • Content.XamlRoot is not available until the window has content, which is why the dialog service is attached after InitializeComponent. A ContentDialog needs it.

The screen: BooksPage

The page binds with {x:Bind} only. There is no DataContext: the page exposes a ViewModel property and every path starts there. The list row is a DataTemplate with an x:DataType, the details pane is created only when a book is selected (x:Load), and the price is formatted by a static function.

<Page
    x:Class="Bookshelf.WinUI.Views.BooksPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Bookshelf.WinUI"
    xmlns:vm="using:Bookshelf.Desktop.ViewModels"
    x:DefaultBindMode="OneWay"
    Loaded="OnLoaded">

  <Page.Resources>
    <DataTemplate x:Key="BookRowTemplate" x:DataType="vm:BookViewModel">
      <Grid Padding="8,6" ColumnSpacing="12">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackPanel>
          <TextBlock Text="{x:Bind Title, Mode=OneTime}" Style="{StaticResource BodyStrongTextBlockStyle}" />
          <TextBlock Text="{x:Bind local:BookFormat.ByLine(Author, Year), Mode=OneTime}"
                     Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
        </StackPanel>
        <ToggleButton Grid.Column="1"
                      AutomationProperties.Name="Favorite"
                      IsChecked="{x:Bind IsFavorite, Mode=TwoWay}">
          <FontIcon Glyph="&#xE734;" />
        </ToggleButton>
      </Grid>
    </DataTemplate>
  </Page.Resources>

  <Grid Padding="24,12,24,24" RowSpacing="12" ColumnSpacing="24">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="2*" />
      <ColumnDefinition Width="*" MinWidth="280" />
    </Grid.ColumnDefinitions>

    <CommandBar Grid.ColumnSpan="2" DefaultLabelPosition="Right" Background="Transparent">
      <AppBarButton Icon="Add" Label="Add" Command="{x:Bind ViewModel.AddBookCommand}" />
      <AppBarButton Icon="Edit" Label="Edit" Command="{x:Bind ViewModel.EditSelectedCommand}" />
      <AppBarButton Icon="Delete" Label="Delete" Command="{x:Bind ViewModel.DeleteSelectedCommand}" />
      <AppBarSeparator />
      <AppBarButton Icon="Refresh" Label="Reload" Command="{x:Bind ViewModel.LoadCommand}" />
    </CommandBar>

    <Grid Grid.Row="1" RowSpacing="8">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>

      <StackPanel Orientation="Horizontal" Spacing="8">
        <TextBox Width="320" PlaceholderText="Search books"
                 AutomationProperties.Name="Search books"
                 Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Button Content="Clear search" Click="{x:Bind ClearSearch}" />
      </StackPanel>

      <ListView Grid.Row="1"
                ItemsSource="{x:Bind ViewModel.Books, Mode=OneTime}"
                SelectedItem="{x:Bind ViewModel.SelectedBook, Mode=TwoWay}"
                ItemTemplate="{StaticResource BookRowTemplate}"
                AutomationProperties.Name="Books" />
    </Grid>

    <!-- Created only while a book is selected; unloaded again when the selection is cleared. -->
    <StackPanel x:Name="DetailsPane"
                x:Load="{x:Bind IsNotNull(ViewModel.SelectedBook)}"
                Grid.Row="1" Grid.Column="1" Spacing="8">
      <TextBlock Text="{x:Bind ViewModel.SelectedBook.Title, FallbackValue=''}"
                 Style="{StaticResource SubtitleTextBlockStyle}" />
      <TextBlock Text="{x:Bind ViewModel.SelectedBook.Author, FallbackValue=''}" />
      <TextBlock Text="{x:Bind local:BookFormat.Price(ViewModel.SelectedBook.Price)}" />
      <TextBlock Text="{x:Bind ViewModel.SelectedBook.Model.Isbn, FallbackValue='No ISBN'}"
                 Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
    </StackPanel>

    <InfoBar Grid.Row="2" Grid.ColumnSpan="2"
             IsOpen="True" IsClosable="False" Severity="Informational"
             Message="{x:Bind ViewModel.StatusMessage}" />
  </Grid>
</Page>
using Bookshelf.Desktop.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

namespace Bookshelf.WinUI.Views;

public sealed partial class BooksPage : Page
{
    // x:Bind resolves "ViewModel.SearchText" against this class, so the view model is a property here.
    public BooksViewModel ViewModel { get; }

    public BooksPage()
    {
        // Frame.Navigate creates the page with its parameterless constructor, so the page asks the container.
        ViewModel = App.Current.Services.GetRequiredService<BooksViewModel>();
        InitializeComponent();
    }

    private async void OnLoaded(object sender, RoutedEventArgs e) =>
        await ViewModel.LoadCommand.ExecuteAsync(null);

    // A parameterless method can be the target of an event binding: Click="{x:Bind ClearSearch}".
    private void ClearSearch() => ViewModel.SearchText = string.Empty;

    // A function binding: re-evaluated when ViewModel.SelectedBook changes.
    public static bool IsNotNull(object? value) => value is not null;
}

public static class BookFormat
{
    public static string ByLine(string? author, int year) =>
        year > 0 ? $"{author}, {year}" : author ?? string.Empty;

    public static string Price(decimal price) => price.ToString("C");
}

A few details of the listing deserve a closer look:

  • The page sets x:DefaultBindMode="OneWay" on the root, so every {x:Bind} below it that does not name a mode is OneWay. The rows that really are static declare OneTime to avoid the change-tracking code.

  • Click="{x:Bind ClearSearch}" binds an event to a method with no parameters. Event binding does not track changes to the model: the path is evaluated when the event fires.

  • Command="{x:Bind ViewModel.AddBookCommand}" binds to the IAsyncRelayCommand that the MVVM Toolkit generates from AddBookAsync. The command-bar buttons enable and disable themselves through CanExecuteChanged.

  • x:Load on DetailsPane uses an x:Bind expression, so the pane exists only while a book is selected. The expression is a function, IsNotNull, whose argument is the bound SelectedBook.

  • Loaded="OnLoaded" starts the first load. The tutorial loads from OnNavigatedTo instead, which also refreshes the list each time the page is navigated to.

  • FavoritesPage and SettingsPage, used by the shell, are ordinary pages and are not shown.

The ListView replaces the DataGrid of the WPF version. WinUI 3 has no DataGrid in the box; the Windows App SDK 2.x release notes list a TableView control as experimental only. The lists page of this section covers the options.

x:Bind in Detail

{x:Bind} is the most important syntactic difference between WinUI and WPF. At XAML compile time it is converted into generated C# code that reads the value from the source, sets it on the target and, if requested, subscribes to change notifications. The generated files are in obj (for example BooksPage.g.cs), so a breakpoint can be placed in them. Compared with {Binding}, which uses general-purpose runtime object inspection, compiled bindings run in less time and memory, are validated at build time and can be debugged.

Where the path starts

{x:Bind} does not use the DataContext. Outside a DataTemplate it uses the page, user control or window class itself, looking for properties, fields and methods in the code-behind. That is why ViewModel is a property on BooksPage. Named elements (x:Name) are fields of that class, so there is no ElementName: write {x:Bind SearchBox.Text, Mode=OneWay}. Inside a DataTemplate it uses the type named by x:DataType. The x:DataType can be an interface or a base class, and casts can then be used to reach members of the real type. A path can use indexers (Teams[0].Players), dictionary keys (Players['John Smith']), attached properties (Button22.(Grid.Row)) and casts (((TextBox)obj).Text).

Binding modes

Mode Behavior Notes

OneTime

The value is read once, when the page or template loads. This is the default of {x:Bind}.

Cheapest: no listener code is generated. {Binding} defaults to OneWay, so porting markup changes behavior.

OneWay

The target is refreshed when the source raises PropertyChanged (or the dependency property changes).

Needs INotifyPropertyChanged on the source. More generated code per binding.

TwoWay

Also pushes target changes back to the source.

UpdateSourceTrigger decides when: PropertyChanged for every property except TextBox.Text, which uses LostFocus.

x:DefaultBindMode changes the default for an element and its children, which is the cheapest way to make a whole page OneWay without repeating the mode, as BooksPage does. A TextBox that must filter while the user types needs UpdateSourceTrigger=PropertyChanged, because its default is LostFocus.

The properties of the extension

Property Meaning

Path

The property path, optional when written first. May end in a function call.

Mode

OneTime, OneWay or TwoWay.

Converter, ConverterParameter, ConverterLanguage

An IValueConverter instance referenced with {StaticResource}. Most conversions are better written as a function.

FallbackValue

Shown when the source or path cannot be resolved (for example a null link in the chain).

TargetNullValue

Shown when the path resolves to null. For a function binding it applies to the result of the call, not to the arguments.

BindBack

The function that writes a value back in a two-way function binding.

UpdateSourceTrigger

When a two-way binding writes to the source.

The expression cannot contain line breaks, and a single curly brace in a Path or ConverterParameter is written as \{ or enclosed in a second quotation set.

Functions in binding paths

A function can be the last step of a path. It can be static, written prefix:Class.Method(args), or an instance method found by the usual path rules. Arguments are bound paths, quoted string constants, numbers or booleans (x:True, x:False). With OneWay or TwoWay the binding is re-evaluated when any bound argument changes, and the binding engine also reacts to a PropertyChanged notification raised with the function name, which is how a view model can force an update. Functions replace both value converters and MultiBinding from WPF.

<!-- BookFormat.ByLine is defined on the page above; Describe, Currency and the sys: prefix
     (xmlns:sys="using:System") are illustrative helpers for this snippet -->
<!-- Static function with two bound arguments: re-evaluated if Author or Year change -->
<TextBlock Text="{x:Bind local:BookFormat.ByLine(Author, Year), Mode=OneWay}" />

<!-- A system function, with a string constant (single quotes) as the first argument -->
<TextBlock Text="{x:Bind sys:String.Concat('Price: ', local:BookFormat.Price(Price))}" />

<!-- Two-way function binding needs BindBack -->
<TextBox Text="{x:Bind local:Currency.Display(Price), BindBack=local:Currency.Parse, Mode=TwoWay}" />

<!-- Pathless cast: passes the item itself as an argument -->
<TextBlock Text="{x:Bind local:BookFormat.Describe((vm:BookViewModel))}" />

The function must be accessible to the generated code, must have a return type that matches the target property, and its argument types must match the data passed in: the binding engine does not perform narrowing conversions. Overloads are matched by number of arguments, and the first overload with that count is used.

Event binding

An event handler can be bound to a method of the page or of any object reachable from it: Click="{x:Bind ViewModel.Refresh}". The method must not be overloaded and must match the event signature, or have no parameters, or have parameters whose types are assignable from the event’s parameter types. This removes many one-line handlers from the code-behind. It is not a replacement for commands when CanExecute is needed.

Other facts about compiled bindings

  • Bindings.Update() refreshes all compiled bindings and hooks up listeners, Bindings.Initialize() does that only if not yet initialized, and Bindings.StopTracking() unhooks the listeners of OneWay and TwoWay bindings.

  • A resource dictionary that uses {x:Bind} needs a code-behind class, because the generated code has to live somewhere.

  • x:Phase renders the parts of a list item template progressively, to improve panning performance in long lists.

  • {x:Bind} is a markup extension only: there is no API to create or change these bindings from code.

  • The framework converts a bool to a Visibility in a property binding without a converter (Windows 10 version 1607 or later as the target SDK). That conversion is not applied inside function bindings.

  • {x:Bind} cannot bind to DataContext, because its type, object, is not known at compile time.

Deferred loading with x:Load

x:Load removes an element from memory until it is needed. While it is unloaded, a small placeholder (about 600 bytes per x:Load element) marks its place in the tree. It has a similar visual effect to Visibility, but the element’s memory is released. Put it on elements that are not visible in the first frame, such as a details pane, a rarely used panel, or UI that appears only after an interaction.

Way to realize or unload Notes

x:Load="{x:Bind Flag, Mode=OneWay}"

true loads, false unloads. Do not name the element with the same identifier as the binding path.

FindName("Name")

Realizes an element. In WinUI 3 it does not work when the XAML root is a Window: use an x:Bind expression instead.

GetTemplateChild, a VisualState setter or storyboard

Other ways to realize the element.

UnloadObject(element)

On a Page or UserControl; also XamlMarkupHelper.UnloadObject. Releases the references held by the generated code.

The restrictions: the element needs an x:Name, it must derive from UIElement or FlyoutBase, it cannot be the root element of a Window, Page, UserControl or DataTemplate, and it cannot be used in a ResourceDictionary or in XAML loaded with XamlReader.Load. Nested x:Load elements are realized from the outside in. When an element is unloaded all of its state is discarded, so state must come from bindings or be re-applied in Loaded. Overusing it can cost more than it saves, so measure.

NavigationView and Frame

NavigationView is the adaptive shell control of WinUI. It provides the pane, the menu items, the settings entry, the back and pane-toggle buttons and the header. It is a navigation chrome; the content is a Frame (or any content) that you drive yourself.

Member Behavior

PaneDisplayMode

Auto (default), Left, LeftCompact, LeftMinimal or Top.

Adaptive thresholds (Auto)

Expanded left pane at 1008 pixels and wider, icon-only LeftCompact between 641 and 1007, LeftMinimal (menu button) at 640 or less. CompactModeThresholdWidth and ExpandedModeThresholdWidth change the breakpoints.

MenuItems, FooterMenuItems

Navigation items at the top and bottom of the pane. All share one selection model, and only one can be selected.

IsSettingsVisible

Shows the built-in Settings item. SettingsItem is not part of MenuItems and has no Tag.

ItemInvoked

Raised every time the user taps an item, even the selected one. Navigating here reloads the page and stacks a duplicate unless you guard against it.

SelectionChanged

Raised when a different item becomes selected, by the user or by code. For user input, ItemInvoked fires first. Usually you handle one of the two, not both.

IsBackButtonVisible, IsBackEnabled, BackRequested

The back button never navigates by itself. IsBackEnabled can be bound to Frame.CanGoBack. In compact and minimal modes the pane is a flyout, and back closes the pane instead.

AutoSuggestBox, Header, PaneFooter

Slots for app-level search, the page title and non-navigation content at the bottom of the pane.

Source: NavigationView

Two Fluent layering points apply to the shell above. In Left mode NavigationView supplies a content layer; the card pattern needs the default background and border theme resources overridden. And Mica must be visible in the title bar, which means the title bar area is extended and transparent.

The Frame navigates to a page type with Navigate(Type, object?, NavigationTransitionInfo) and keeps a back stack. It creates the page through its parameterless constructor, so a page cannot receive constructor-injected services. The usual compromises are the one used in BooksPage (resolve from App.Current.Services) or a ViewModel-first navigation service that sets the view model in Navigated. The latter is on Navigation, Dialogs & Services in MVVM; the older WPF NavigationWindow form is on the windows and dialogs page.

WinUI 3 implements the Fluent Design System: rounded corners, the Segoe Fluent Icons font, theme resources such as TextFillColorSecondaryBrush and LayerFillColorDefaultBrush, light, dark and high-contrast themes, and accent-colored states. The control styles come from XamlControlsResources, merged into the application resources in App.xaml. Theming is covered on Themes & Fluent Design.

The WinUI 3 Gallery app is the reference for what exists. It has an interactive example, with its XAML and C#, for each control and feature, and it is available from the Microsoft Store and as source on GitHub (microsoft/WinUI-Gallery). It is also the best place to look for the idiomatic way to use TitleBar, ItemsView or SystemBackdropElement before reading the API reference.

Control Typical use in Bookshelf Desktop

NavigationView, TitleBar

The shell on this page.

ListView, GridView, ItemsView

The book list. ItemsView adds selection and layouts for card-style galleries.

CommandBar, AppBarButton

Add, edit, delete and reload.

InfoBar

The status line, and non-modal warnings such as a failed save.

ContentDialog

Confirm delete and the book editor, through IDialogService.

NumberBox, CalendarDatePicker, ComboBox

Year, price and format fields in the editor.

TeachingTip, Flyout

First-run hints and light-dismiss panels.

BreadcrumbBar, TabView, SelectorBar

Alternative shells for more complex navigation.

The list is from the Gallery’s control categories; check the Gallery for the exact status of each control in the SDK version you use. The Windows App SDK 2.x release notes mention TableView and Chart controls and an inking toolkit as experimental only, so they are not for production apps yet.

Backdrops: Mica, Acrylic and SystemBackdropElement

Materials are the visual effects that give a window depth. Mica is an opaque material that samples the desktop wallpaper once and blends it with the theme, so it is cheap; it is for the base layer of long-lived windows. Mica Alt tints the wallpaper more strongly and suits apps with a tabbed or custom title bar where the commanding area needs contrast with the title. Desktop Acrylic is semi-transparent, like frosted glass, and is for transient, light-dismiss surfaces such as flyouts and context menus.

Window.SystemBackdrop

The recommended way (since Windows App SDK 1.3) is to set the SystemBackdrop property to a XAML SystemBackdrop. The property exists on Window, Popup, FlyoutBase, MenuFlyoutPresenter, CommandBarFlyoutCommandBar, DesktopWindowXamlSource and ContentIsland.

<!-- Mica as the window backdrop -->
<Window.SystemBackdrop>
  <MicaBackdrop />
</Window.SystemBackdrop>

<!-- Mica Alt -->
<Window.SystemBackdrop>
  <MicaBackdrop Kind="BaseAlt" />
</Window.SystemBackdrop>

<!-- Acrylic on a flyout -->
<Flyout>
  <Flyout.SystemBackdrop>
    <DesktopAcrylicBackdrop />
  </Flyout.SystemBackdrop>
</Flyout>

SystemBackdropElement (Windows App SDK 2.0 and later)

Window.SystemBackdrop paints the whole window. SystemBackdropElement is a XAML control that renders a system backdrop inside its own bounds, so a card or a side panel can have Mica or Acrylic of its own. It requires Windows App SDK 2.0 or later (it became stable in 2.0.1), it can be placed anywhere in the tree, and it supports CornerRadius. The two approaches are independent and can be used together. Bookshelf Desktop uses it for the details card:

<Grid Width="300" Height="200">
  <SystemBackdropElement CornerRadius="8">
    <SystemBackdropElement.SystemBackdrop>
      <DesktopAcrylicBackdrop />
    </SystemBackdropElement.SystemBackdrop>
  </SystemBackdropElement>

  <StackPanel Padding="16" Spacing="4" VerticalAlignment="Center">
    <TextBlock Text="{x:Bind ViewModel.SelectedBook.Title, FallbackValue=''}"
               Style="{StaticResource SubtitleTextBlockStyle}" />
    <TextBlock Text="{x:Bind ViewModel.SelectedBook.Author, FallbackValue=''}" />
  </StackPanel>
</Grid>
// The backdrop can be switched at run time, for example from a settings page.
private void OnBackdropChanged(string kind)
{
    DetailsBackdrop.SystemBackdrop = kind switch
    {
        "Mica"    => new MicaBackdrop { Kind = MicaKind.Base },
        "MicaAlt" => new MicaBackdrop { Kind = MicaKind.BaseAlt },
        _         => new DesktopAcrylicBackdrop(),
    };
}

Source: System backdrops (Mica/Acrylic), section "Apply a system backdrop to any XAML element"

Behavior and rules

  • Mica needs Windows 11 build 22000 or later. On older Windows, with transparency turned off, in Battery Saver, on low-end hardware, while the window is deactivated and in High Contrast mode, Mica falls back to a solid color (SolidBackgroundFillColorBase for Mica, SolidBackgroundFillColorBaseAlt for Mica Alt). No code is needed for the fallback.

  • Set the background of every layer between the window and the place where Mica should show to transparent. A backdrop is not applied to a UI element: it shows only if all layers between the element and the window are transparent. Do not apply a window backdrop material more than once.

  • Content layers use LayerFillColorDefaultBrush, a low-opacity solid color, as their background. With Mica Alt, the commanding layer uses LayerOnMicaBaseAltFillColorDefaultBrush.

  • For the advanced cases (a custom MicaController or DesktopAcrylicController, SystemBackdropConfiguration, overriding TintColor or FallbackColor), the page documents the controller model. It needs a dispatcher queue, disposing the controller in Closed, and keeping the configuration’s IsInputActive and Theme in sync. MicaController.IsSupported() and DesktopAcrylicController.IsSupported() report system support.

AppWindow and the Title Bar

A WinUI Window is a wrapper around a top-level HWND, and it is not a FrameworkElement. That has consequences: it has no DataContext, no Resources, no FindName, no Loaded event and no RequestedTheme; those belong to its root content. What it does have is the Windows App SDK’s AppWindow, available through Window.AppWindow since Windows App SDK 1.3, with a 1:1 mapping to the HWND.

API Use

AppWindow.Title, Window.Title

The title text. Show a meaningful name; the template default is the project type.

AppWindow.Resize, ResizeClient, Move, MoveAndResize

Size and position in physical pixels. Resize sizes the whole window, ResizeClient the client area.

AppWindow.Size, ClientSize, Position

Read-only current values.

AppWindow.Changed

A single event; check DidSizeChange, DidPositionChange, DidPresenterChange and other flags.

AppWindow.SetPresenter, Presenter

OverlappedPresenter (default), FullScreenPresenter, CompactOverlayPresenter.

OverlappedPresenter properties

IsResizable, IsMaximizable, IsMinimizable, IsAlwaysOnTop, IsModal (needs an owner window), PreferredMinimumWidth, PreferredMinimumHeight, PreferredMaximumWidth, PreferredMaximumHeight.

AppWindow.SetIcon(string)

The window icon; the string overload works only with .ico files.

AppWindow.TitleBar

AppWindowTitleBar: colors, IconShowOptions, ExtendsContentIntoTitleBar, PreferredHeightOption.

Width, Height, MinWidth, MinHeight, MaxWidth and MaxHeight on Window, in logical (device-independent) pixels, are documented as experimental in the 2.3 experimental channel. Do not use them in a production app; keep using AppWindow.Resize and the presenter’s preferred minimum and maximum sizes.

Three levels of title bar customization

Level How

Simple

Set Title, the icon, and the colors of AppWindowTitleBar (ForegroundColor, BackgroundColor, the Button... colors). Check AppWindowTitleBar.IsCustomizationSupported() first. Color customization is ignored on Windows 10. The close button’s hover and pressed backgrounds always use system colors, null resets a color, and transparent colors are not allowed. If you set one color, set them all.

Full, with the TitleBar control

ExtendsContentIntoTitleBar = true, then SetTitleBar(titleBar). The control (Windows App SDK 1.7 and later) provides the back button, pane toggle, left header, icon, title, subtitle, content, right header and a minimal drag region next to the caption buttons. The system caption buttons stay.

Full, hand-built

ExtendsContentIntoTitleBar = true, a Grid as the title bar, SetTitleBar, and InputNonClientPointerSource.SetRegionRects for interactive areas. You reserve space for the caption buttons yourself.

ExtendsContentIntoTitleBar appears in XAML IntelliSense for Window, but setting it in XAML causes an error: set it in code, in the constructor (as MainWindow does).

A title bar with search and a status button, using the drag-region rules of the control:

<TitleBar x:Name="AppTitleBar" Title="Bookshelf">
  <TitleBar.Resources>
    <HorizontalAlignment x:Key="TitleBarContentHorizontalAlignment">Stretch</HorizontalAlignment>
  </TitleBar.Resources>
  <TitleBar.Content>
    <Grid ColumnSpacing="8">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
      </Grid.ColumnDefinitions>
      <AutoSuggestBox MaxWidth="480" VerticalAlignment="Center"
                      PlaceholderText="Search books" QueryIcon="Find"
                      AutomationProperties.Name="Search books" />
      <Button Grid.Column="1" x:Name="SyncButton" Content="Sync" />
    </Grid>
  </TitleBar.Content>
</TitleBar>
// Interactive controls are clickable and empty space is draggable by default (2.1.3 and later).
// Override on the smallest element that needs it:
TitleBar.SetIsDragRegion(SyncButton, false);   // clickable
SyncButton.ClearValue(TitleBar.IsDragRegionProperty);

// After adding or resizing title bar content at run time:
AppTitleBar.RecomputeDragRegions();

Source: TitleBar, section "Customize drag regions"

Before Windows App SDK 2.1.3 empty gaps in the content area could block window dragging; since then the control excludes only interactive controls. AutoRefreshDragRegions recomputes after every layout pass and is off by default because it can affect performance. Drag regions affect pointer input only, so interactive controls in the title bar must also be reachable with the keyboard.

App Lifecycle: Activation and Single Instance

A WinUI app is multi-instanced by default: launching it twice starts two processes with two main windows. The Windows App SDK app lifecycle API lets an app decide, at launch, whether to carry on or hand its activation to an instance that is already running, and receives rich activations (files, protocols, startup) in the same way.

The activation flow

The decision has to be taken before any window exists, so a single-instance WinUI app replaces the generated Main.

flowchart TD A["User launches Bookshelf.exe"] --> B["Program.Main: InitializeComWrappers"] B --> C["AppInstance.GetCurrent().GetActivatedEventArgs()"] C --> D["AppInstance.FindOrRegisterForKey with the app key"] D --> E{"keyInstance.IsCurrent?"} E -->|"yes: first instance"| F["Subscribe to AppInstance.Activated"] F --> G["Application.Start: new App()"] G --> H["App.OnLaunched: create MainWindow, Activate"] H --> I["Route the launch args: normal, file or protocol"] E -->|"no: another instance owns the key"| J["RedirectActivationToAsync on a worker thread"] J --> K["Wait without blocking the STA: CoWaitForMultipleObjects"] K --> L["Bring the first window to the foreground"] L --> M["Second process exits"] J -.->|"args delivered"| N["First instance: Activated event"] N --> O["Marshal to the UI thread, route the args, Activate the window"]

The first instance registers the key and keeps running. Every later process finds the key already registered, receives that instance from FindOrRegisterForKey, redirects its own activation arguments to it and exits without ever creating a window.

Replacing Main for single-instancing

The DISABLE_XAML_GENERATED_MAIN symbol (set in the project file above) disables the generated Program. In Windows App SDK 2.3.1 and later the symbol renames the generated entry point (to XamlGeneratedProgram.XamlGeneratedMain() in C#) instead of removing it, and a hand-written Main still works.

using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;

namespace Bookshelf.WinUI;

public static class Program
{
    private const string InstanceKey = "Bookshelf.Desktop.Main";

    [STAThread]
    private static int Main(string[] args)
    {
        WinRT.ComWrappersSupport.InitializeComWrappers();

        if (DecideRedirection())
        {
            return 0;
        }

        Application.Start(_ =>
        {
            var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
            SynchronizationContext.SetSynchronizationContext(context);
            _ = new App();
        });

        return 0;
    }

    private static bool DecideRedirection()
    {
        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        AppInstance keyInstance = AppInstance.FindOrRegisterForKey(InstanceKey);

        if (keyInstance.IsCurrent)
        {
            keyInstance.Activated += OnActivated;
            return false;
        }

        RedirectActivationTo(args, keyInstance);
        return true;
    }

    // Raised in the first instance when a later process redirects its activation to it.
    private static void OnActivated(object? sender, AppActivationArguments args) =>
        ActivationRouter.HandleFromAnyThread(args);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string? lpName);

    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);

    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(uint dwFlags, uint dwMilliseconds, ulong nHandles, IntPtr[] pHandles, out uint dwIndex);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    // Redirect on another thread and wait with a non-blocking COM wait, so the STA keeps pumping messages.
    private static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance)
    {
        IntPtr redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);

        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });

        const uint CWMO_DEFAULT = 0;
        const uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(CWMO_DEFAULT, INFINITE, 1, [redirectEventHandle], out _);

        // Give the first instance's window the foreground.
        using Process process = Process.GetProcessById((int)keyInstance.ProcessId);
        SetForegroundWindow(process.MainWindowHandle);
    }
}

Facts to know about the instancing model:

Topic Behavior

Keys

FindOrRegisterForKey registers a key for the current instance. Keys have no meaning to the API; the app chooses them. An instance has one key at a time (a new one overwrites the previous). If the key is already registered by another instance, the call returns that instance, and IsCurrent is false.

Redirection targets

Any instance can be a target, whether or not it registered a key. Redirection is not terminal in the Windows App SDK, unlike UWP: the redirecting process must exit itself. A request can be redirected more than once, so guard against redirect loops.

GetInstances

Returns all running instances that use the AppInstance API, for this version and user.

Arguments

RedirectActivationToAsync requires the AppActivationArguments to be passed explicitly, so the redirecting app can modify or replace them. The call is asynchronous and the caller must wait for it to finish, or the redirect fails.

Unregistering

UnregisterKey lets an instance stop being a target before it exits, which avoids a race with a process that is already closing.

Testing

Debugging in Visual Studio cannot start the same project twice, so test single-instancing by deploying and launching from the Start menu.

The docs sample uses DllImport; the .NET analyzers (SYSLIB1054) suggest LibraryImport for new code, which needs AllowUnsafeBlocks in the project. Redirect and exit as early as possible in Main, before any window or host is created.

Routing activations

All entry points (the first launch, a redirected launch, a file, a protocol link) end in the same routine. The kinds that unpackaged apps support are Launch (command line, double click, ShellExecute), File, Protocol and StartupTask. Packaged apps support all 44 UWP activation kinds and always receive arguments in the Activated event and in GetActivatedEventArgs.

using Bookshelf.Desktop.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;

namespace Bookshelf.WinUI;

public static class ActivationRouter
{
    /// <summary>Called from a thread that is not necessarily the UI thread.</summary>
    public static void HandleFromAnyThread(AppActivationArguments args)
    {
        // Marshal explicitly: the lifecycle documentation does not state which thread raises Activated.
        if (App.Current.MainWindow?.DispatcherQueue is { } queue)
        {
            queue.TryEnqueue(() => Handle(args));
        }
    }

    public static void Handle(AppActivationArguments args)
    {
        switch (args.Kind)
        {
            case ExtendedActivationKind.Protocol when args.Data is IProtocolActivatedEventArgs protocol:
                ShowBookFromUri(protocol.Uri);
                break;

            case ExtendedActivationKind.File when args.Data is IFileActivatedEventArgs file:
                // Import the first file: file.Files[0] is an IStorageItem.
                break;

            case ExtendedActivationKind.Launch when args.Data is ILaunchActivatedEventArgs launch:
                // launch.Arguments holds the raw command-line text.
                break;
        }

        // A reactivation should always bring the window forward.
        App.Current.MainWindow?.Activate();
    }

    // bookshelf://book/42 selects the book with Id 42.
    private static void ShowBookFromUri(Uri uri)
    {
        if (uri.Host == "book" && int.TryParse(uri.AbsolutePath.Trim('/'), out int id))
        {
            BooksViewModel viewModel = App.Current.Services.GetRequiredService<BooksViewModel>();
            viewModel.SelectedBook = viewModel.Books.FirstOrDefault(b => b.Id == id);
        }
    }
}

Source: Rich activation with the app lifecycle API. Matching the kind in C# against args.Data follows the C++ samples of that page; confirm the exact cast for your SDK version.

A packaged app declares the protocol in its manifest. An unpackaged app registers it at run time, for the current user:

<!-- Package.appxmanifest: inside <Application> ... <Extensions> -->
<uap:Extension Category="windows.protocol">
  <uap:Protocol Name="bookshelf">
    <uap:DisplayName>Bookshelf Desktop</uap:DisplayName>
  </uap:Protocol>
</uap:Extension>
// Unpackaged only: register a protocol (and file types) once, for the current user.
string exePath = Environment.ProcessPath!;
ActivationRegistrationManager.RegisterForProtocolActivation(
    "bookshelf",
    exePath + ",1",
    "Bookshelf Desktop",
    exePath);

Source: Rich activation with the app lifecycle API (the windows.protocol manifest extension is the standard UWP/MSIX form and is not shown on that page; see the MSIX page of this section)

Activation registrations are per user. Registering a file type again with a different set of verbs overwrites the previous verbs for that type. Unregister with the matching Unregister... method, using the same task id for startup activation.

The MVVM Toolkit with WinUI

The MVVM Toolkit works the same on WinUI as on WPF, and it is the library that Microsoft’s own WinUI guidance points to: the Windows data binding and MVVM page recommends it for ObservableObject, [ObservableProperty] and [RelayCommand], and a complete tutorial refactors the WinUI Notes app to it. Details of the library are on the MVVM Toolkit page; this section covers what is specific to WinUI.

  • Use partial properties, not fields. A field with [ObservableProperty] generates code that is not AOT compatible in WinRT scenarios, UWP and WinUI 3 included, and the analyzer reports MVVMTK0045. A partial property ([ObservableProperty] public partial string? Name { get; set; }) lets the CsWinRT generators produce the WinRT marshalling code. The Bookshelf view models already use the partial property form.

  • Expose the view model as a property of the page and bind with {x:Bind ViewModel.Property, Mode=OneWay}. The INotifyPropertyChanged that ObservableObject implements is what OneWay and TwoWay listen to.

  • Commands bind directly: Command="{x:Bind ViewModel.AddBookCommand}". The generated command types are IRelayCommand and IAsyncRelayCommand, which implement ICommand.

  • Collections: an ObservableCollection<T> bound OneTime is enough when the property itself never changes, because the ListView tracks INotifyCollectionChanged independently of the binding mode.

  • Dialogs and navigation stay behind interfaces (IDialogService), implemented over ContentDialog and Frame in the WinUI project, so that BooksViewModel remains free of UI types.

  • Threading: UI objects belong to the thread of their DispatcherQueue. A view model that raises PropertyChanged from a background thread must marshal with DispatcherQueue.TryEnqueue, not WPF’s Dispatcher. The threading page of this section covers the details.

  • Unit tests of the view models need no WinUI types, and the toolkit tutorial adds a separate unit test project.

A command that must read the view model’s state before it runs, for the status line, looks like the one on the MVVM Toolkit page:

using System.Collections.ObjectModel;
using Bookshelf.Core;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Bookshelf.Desktop.ViewModels;

public partial class BookViewModel(Book book) : ObservableObject
{
    public Book Model { get; } = book;
    public int Id => Model.Id;
    public string Title => Model.Title;
    public string Author => Model.Author;
    public int Year => Model.Year;
    public decimal Price => Model.Price;

    // Partial property: the WinUI-friendly form (no MVVMTK0045).
    [ObservableProperty]
    public partial bool IsFavorite { get; set; }
}

Packaged and Unpackaged WinUI Apps

A WinUI 3 app can run in two ways. A packaged app is built into an MSIX package and installed with package identity. An unpackaged app is a plain executable (it can be installed by MSI, a zip or an installer, or run from a folder), and a third mode, packaged with external location, gives an otherwise unpackaged app a package identity. The Visual Studio template "WinUI Blank App (Packaged)" and the single-project MSIX feature produce a packaged app without a separate packaging project: the manifest and images live in the app project and EnableMsixTooling turns the tooling on.

flowchart TD A["New WinUI 3 app"] --> B{"Needs package identity: Store, MSIX update, manifest extensions?"} B -->|"yes"| C["Packaged: single-project MSIX"] B -->|"no"| D["Unpackaged: WindowsPackageType None"] C --> E{"Where does the Windows App SDK runtime come from?"} D --> E E -->|"framework-dependent"| F["Runtime packages installed on the machine"] E -->|"self-contained"| G["Runtime files copied with the app"] F --> H["Packaged: framework dependency in the manifest"] F --> I["Unpackaged: bootstrapper API plus the runtime installer"]

What differs

Aspect Packaged (MSIX) Unpackaged

Project setting

EnableMsixTooling true; the manifest Package.appxmanifest is in the project.

<WindowsPackageType>None</WindowsPackageType>.

Windows App SDK runtime (framework-dependent)

The generated manifest has a PackageDependency on the framework package. The framework package is installed with the app.

The app must call the bootstrapper API to load the framework package; WindowsPackageType None does it for you. You deploy the runtime with WindowsAppRuntimeInstall.exe or by installing the MSIX packages.

Deployment API

DeploymentManager.GetStatus and Initialize install the Main and Singleton packages. Visual Studio’s property WindowsAppSdkDeploymentManagerInitialize defaults to true.

Not used; the bootstrapper and the installer cover the runtime.

Activation registration

Manifest extensions (protocols, file types, startup tasks).

ActivationRegistrationManager at run time, or registry keys.

Per-app data

ApplicationData.Current (the tutorials use LocalFolder).

ApplicationData.GetForUnpackaged(publisher, product) (Windows App SDK 2.2.0 and later), or normal special folders.

Install and update

MSIX: clean install and uninstall, Microsoft Store, App Installer. See the MSIX pages of this section.

Whatever installer you choose; you own updates.

Visual C++ and .NET

VCLibs framework package dependency; .NET 6 or later.

Visual C++ Redistributable required; .NET 6 or later.

An unpackaged project differs from the project above by one property:

<PropertyGroup>
  <WindowsPackageType>None</WindowsPackageType>
  <!-- Optional: carry the Windows App SDK runtime with the app instead of requiring it on the machine -->
  <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>

Source: Deployment guide for apps packaged with external location or unpackaged (WindowsPackageType); WindowsAppSDKSelfContained is named in the Windows App SDK 2.0 release notes and is covered with the other deployment options on the .NET publishing page of this section.

The runtime installer is silent when asked: WindowsAppRuntimeInstall.exe --quiet, and --force replaces the MSIX packages and shuts down running Windows App SDK processes. Without administrator rights it installs for the current user only; with them it provisions the packages for all users. Its common return codes are 0x0 (success), 0x80073d06 (a package failed) and 0x80070005 (system-wide install without elevation). An Experimental or Preview runtime needs sideloading (Developer Mode) enabled.

The first MSIX build of a packaged app is made with MSBuild: the important switch for the single-project layout is /p:GenerateAppxPackageOnBuild=true, without which the build succeeds but no package is produced. The packaging, signing and distribution of the result are on the MSIX Packaging & Signing and Distribution Channels pages.

Trimming and Native AOT

The single-instancing page warns that apps compiled with trimming in release mode have known crashes in the Windows App SDK, and advises setting PublishTrimmed to false in the .pubxml files. Native AOT, by contrast, is supported: since Windows App SDK 1.6 the .NET PublishAot property works for WinUI 3 apps. Because AOT builds on trimming, the same trimming work applies, some libraries aren’t AOT-compatible, and the 1.6 notes list a known hang after page navigation under AOT. Test the published output carefully before turning either option on.

Source: Windows App SDK 1.6 release notes ("Native AOT support")

WPF, WinUI 3 and Windows Forms Compared

Concern WPF WinUI 3 Windows Forms

Delivered by

.NET (UseWPF)

Windows App SDK NuGet package (UseWinUI)

.NET (UseWindowsForms)

XAML namespaces

System.Windows.*, clr-namespace:

Microsoft.UI.Xaml.*, using:

none (designer code)

Binding syntax

{Binding} against DataContext

{x:Bind} against the page (compiled), plus {Binding}

BindingSource and Binding objects

Default binding mode

Depends on the property

OneTime for {x:Bind}, OneWay for {Binding}

Two-way for Binding with DataSourceUpdateMode options

Converters

IValueConverter, IMultiValueConverter

IValueConverter, or functions in {x:Bind}

Format and Parse events

Window type

System.Windows.Window is a FrameworkElement

Microsoft.UI.Xaml.Window is not a FrameworkElement

Form is a Control

Windowing helper

Window properties, WindowInteropHelper

AppWindow

Form properties

Dispatcher

Dispatcher

DispatcherQueue

Control.Invoke

Dialogs

ShowDialog, MessageBox

ContentDialog with XamlRoot

ShowDialog, MessageBox

Materials

Fluent theme (.NET 9 and later), no Mica by default

MicaBackdrop, DesktopAcrylicBackdrop, SystemBackdropElement

none built in

Triggers

Style.Triggers, DataTrigger

VisualStateManager, state triggers

not applicable

Packaging

Any installer or MSIX

Packaged or unpackaged

Any installer or MSIX

Native AOT and trimming

Not supported (NETSDK1168)

PublishAot supported since Windows App SDK 1.6; trimming needs work; test carefully

Not supported

Single instance

Mutex, or AppInstance through the Windows App SDK

AppInstance through the Windows App SDK

Mutex, or AppInstance

The cells for WPF and Windows Forms summarize the pages dedicated to those stacks; the .NET Publishing Options page covers trimming and Native AOT for all three.

Pitfalls

  • Stale UI after a binding that never updates. The default of {x:Bind} is OneTime. A property that changes needs Mode=OneWay (or x:DefaultBindMode), and a source that implements INotifyPropertyChanged.

  • Looking for the DataContext. {x:Bind} does not use it. The view model must be a property or field of the page, or the template’s x:DataType must name its type. A DataTemplate cannot see the page; use a function, an event binding to a method of the item, or {Binding} with ElementName to reach it.

  • ExtendsContentIntoTitleBar in XAML. It produces an error. Set it in the constructor, in code.

  • ContentDialog throws. XamlRoot must be set, and only one ContentDialog can be open per window.

  • Treating the Window like a control. No DataContext, Resources, Loaded, FindName or RequestedTheme. Put those on the root content element. FindName cannot realize an x:Load element when the XAML root is a Window.

  • Duplicate navigation. Handling ItemInvoked and navigating unconditionally reloads the page and adds back-stack entries; compare CurrentSourcePageType first, or navigate from SelectionChanged.

  • Constructor injection into pages. Frame.Navigate uses the parameterless constructor. Resolve the view model from the container in the page, or navigate ViewModel-first.

  • Mica that does not show. An opaque background on any layer between the window and the element hides it. Set those layers to transparent and use the content-layer brush where a layer is wanted.

  • Redirecting too late. A single-instance check after the window or the host has started wastes time and can flash a second window. Keep Main minimal and finish the redirect before new App(). Test with a deployed app.

  • Field-based [ObservableProperty]. Reports MVVMTK0045 in WinUI because it is not AOT compatible; use partial properties.

  • Unpackaged app crashes at start. The runtime is missing or the bootstrapper was not initialized. Install the runtime or check that WindowsPackageType is None. Packaged-only APIs such as ApplicationData.Current throw without identity.

  • Window size in the wrong unit. AppWindow.Resize takes physical pixels, so a fixed number is a different size at 150% scale. Use the documented logical-size APIs only when they leave the experimental channel.

  • Blocking the UI thread in Activated. The handler may not run on the UI thread. Marshal with DispatcherQueue.TryEnqueue before touching the view model or any control.

What Changed Across Versions

Version Change

Windows App SDK 1.3

Window.SystemBackdrop (MicaBackdrop, DesktopAcrylicBackdrop) and Window.AppWindow.

Windows App SDK 1.4

The XAML Window and AppWindow share the same AppWindowTitleBar.

Windows App SDK 1.7

The TitleBar control.

Windows App SDK 1.8

Last 1.x line; in Maintenance, with end of servicing on 24 September 2026.

Windows App SDK 2.0 (29 April 2026)

First SemVer release; package version equals SDK version. SystemBackdropElement is stable in 2.0.1.

Windows App SDK 2.1.3

TitleBar.IsDragRegion, AutoRefreshDragRegions, RecomputeDragRegions; interactive-only drag exclusion.

Windows App SDK 2.2.0

ApplicationData.GetForUnpackaged(publisher, product) for unpackaged apps.

Windows App SDK 2.3.1

DISABLE_XAML_GENERATED_MAIN renames the generated entry point instead of removing it.

Windows App SDK 2.5.1

Current stable (16 September 2026).

.NET 8, 9, 10

.NET 10 LTS is the baseline for WinUI 3 projects (net10.0-windows10.0.19041.0). .NET 8 and .NET 9 end support on 10 November 2026.

CommunityToolkit.Mvvm 8.4.x

[ObservableProperty] on partial properties; from 8.4.1 the generators require C# 14 and Roslyn 5.

Tooling

Visual Studio 2026 and the winapp CLI (public preview, v0.7.1) create and run WinUI projects (winapp new --template winui, winapp run).

What the Official Documentation Does Not Provide

  • There is no Microsoft Learn sample of the Generic Host in a WinUI 3 app; the WinUI MVVM tutorial uses a plain ServiceCollection, which is what this page shows. The host variant is a pattern described on the dependency injection page.

  • The ViewModel-first INavigationService and the IDialogService are user patterns; Learn documents NavigationView, Frame and ContentDialog only.

  • The thread on which AppInstance.Activated is raised is not stated; the code marshals to the UI thread.

  • Turning on PublishAot or trimming without testing the published output. AOT is supported since Windows App SDK 1.6, but not every library is AOT-compatible.

  • XAML Islands wrapper controls for hosting WinUI 3 in WPF or Windows Forms do not exist; the interop page covers the options.