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, |
Windowing ( |
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 ( |
MRT Core |
Resource management and localization with the |
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. |
Source: WinUI 3 and Windows App SDK release channels
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 |
Source: Windows App SDK release channels
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 |
|---|---|
|
The |
|
The main |
|
The package manifest of a packaged app: identity, publisher, capabilities, visual assets and extensions such as protocol and file-type associations. |
|
Logos and images, built as |
|
The application manifest (DPI awareness, supported operating systems). |
|
Visual Studio launch profiles. A |
|
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="" />
</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="" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Favorites" Tag="favorites" AutomationProperties.Name="Favorite books">
<NavigationViewItem.Icon>
<FontIcon Glyph="" />
</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;
}
Source: TitleBar, NavigationView and Manage app windows
What this shell uses, and where each piece is explained further down:
-
Window.SystemBackdropwithMicaBackdropputs Mica behind everything. It shows through the title bar because theTitleBarcontrol and theNavigationViewcontent layer are transparent or low-opacity (see the backdrops section). -
The
TitleBarcontrol replaces the system title bar. The back button and the pane toggle button live in the title bar, as the Fluent guidance recommends, so theNavigationViewhides its own. -
The
NavigationViewdoes not navigate by itself. It raisesSelectionChanged(orItemInvoked), and the code behind maps theTagof the selected item to a page type and callsFrame.Navigate. -
AppWindow.Resizetakes physical pixels and sizes the whole window, including the border and title bar. -
Content.XamlRootis not available until the window has content, which is why the dialog service is attached afterInitializeComponent. AContentDialogneeds 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="" />
</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 isOneWay. The rows that really are static declareOneTimeto 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 theIAsyncRelayCommandthat the MVVM Toolkit generates fromAddBookAsync. The command-bar buttons enable and disable themselves throughCanExecuteChanged. -
x:LoadonDetailsPaneuses anx:Bindexpression, so the pane exists only while a book is selected. The expression is a function,IsNotNull, whose argument is the boundSelectedBook. -
Loaded="OnLoaded"starts the first load. The tutorial loads fromOnNavigatedToinstead, which also refreshes the list each time the page is navigated to. -
FavoritesPageandSettingsPage, 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 |
|---|---|---|
|
The value is read once, when the page or template loads. This is the default of |
Cheapest: no listener code is generated. |
|
The target is refreshed when the source raises |
Needs |
|
Also pushes target changes back to the source. |
|
Source: x:Bind markup extension
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 |
|---|---|
|
The property path, optional when written first. May end in a function call. |
|
|
|
An |
|
Shown when the source or path cannot be resolved (for example a null link in the chain). |
|
Shown when the path resolves to |
|
The function that writes a value back in a two-way function binding. |
|
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))}" />
Source: Functions in x:Bind with WinUI
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, andBindings.StopTracking()unhooks the listeners ofOneWayandTwoWaybindings. -
A resource dictionary that uses
{x:Bind}needs a code-behind class, because the generated code has to live somewhere. -
x:Phaserenders 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
boolto aVisibilityin 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 toDataContext, 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 |
|---|---|
|
|
|
Realizes an element. In WinUI 3 it does not work when the XAML root is a |
|
Other ways to realize the element. |
|
On a |
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.
Source: x:Load attribute
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 |
|---|---|
|
|
Adaptive thresholds ( |
Expanded left pane at 1008 pixels and wider, icon-only |
|
Navigation items at the top and bottom of the pane. All share one selection model, and only one can be selected. |
|
Shows the built-in Settings item. |
|
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. |
|
Raised when a different item becomes selected, by the user or by code. For user input, |
|
The back button never navigates by itself. |
|
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.
Fluent Controls and the WinUI Gallery
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 |
|---|---|
|
The shell on this page. |
|
The book list. |
|
Add, edit, delete and reload. |
|
The status line, and non-modal warnings such as a failed save. |
|
Confirm delete and the book editor, through |
|
Year, price and format fields in the editor. |
|
First-run hints and light-dismiss panels. |
|
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>
Source: System backdrops (Mica/Acrylic)
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 (
SolidBackgroundFillColorBasefor Mica,SolidBackgroundFillColorBaseAltfor 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 usesLayerOnMicaBaseAltFillColorDefaultBrush. -
For the advanced cases (a custom
MicaControllerorDesktopAcrylicController,SystemBackdropConfiguration, overridingTintColororFallbackColor), the page documents the controller model. It needs a dispatcher queue, disposing the controller inClosed, and keeping the configuration’sIsInputActiveandThemein sync.MicaController.IsSupported()andDesktopAcrylicController.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 |
|---|---|
|
The title text. Show a meaningful name; the template default is the project type. |
|
Size and position in physical pixels. |
|
Read-only current values. |
|
A single event; check |
|
|
|
|
|
The window icon; the string overload works only with |
|
|
Source: Manage app windows
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 |
Full, with the |
|
Full, hand-built |
|
Source: Title bar customization and TitleBar
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.
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 |
|
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. |
|
Returns all running instances that use the AppInstance API, for this version and user. |
Arguments |
|
Unregistering |
|
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}. TheINotifyPropertyChangedthatObservableObjectimplements is whatOneWayandTwoWaylisten to. -
Commands bind directly:
Command="{x:Bind ViewModel.AddBookCommand}". The generated command types areIRelayCommandandIAsyncRelayCommand, which implementICommand. -
Collections: an
ObservableCollection<T>boundOneTimeis enough when the property itself never changes, because theListViewtracksINotifyCollectionChangedindependently of the binding mode. -
Dialogs and navigation stay behind interfaces (
IDialogService), implemented overContentDialogandFramein the WinUI project, so thatBooksViewModelremains free of UI types. -
Threading: UI objects belong to the thread of their
DispatcherQueue. A view model that raisesPropertyChangedfrom a background thread must marshal withDispatcherQueue.TryEnqueue, not WPF’sDispatcher. 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.
What differs
| Aspect | Packaged (MSIX) | Unpackaged |
|---|---|---|
Project setting |
|
|
Windows App SDK runtime (framework-dependent) |
The generated manifest has a |
The app must call the bootstrapper API to load the framework package; |
Deployment API |
|
Not used; the bootstrapper and the installer cover the runtime. |
Activation registration |
Manifest extensions (protocols, file types, startup tasks). |
|
Per-app data |
|
|
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. |
Source: Deployment guide for framework-dependent packaged apps, Deployment guide for apps packaged with external location or unpackaged and Package your app using single-project MSIX
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 ( |
Windows App SDK NuGet package ( |
.NET ( |
XAML namespaces |
|
|
none (designer code) |
Binding syntax |
|
|
|
Default binding mode |
Depends on the property |
|
Two-way for |
Converters |
|
|
|
Window type |
|
|
|
Windowing helper |
|
|
|
Dispatcher |
|
|
|
Dialogs |
|
|
|
Materials |
Fluent theme (.NET 9 and later), no Mica by default |
|
none built in |
Triggers |
|
|
not applicable |
Packaging |
Any installer or MSIX |
Packaged or unpackaged |
Any installer or MSIX |
Native AOT and trimming |
Not supported (NETSDK1168) |
|
Not supported |
Single instance |
Mutex, or |
|
Mutex, or |
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}isOneTime. A property that changes needsMode=OneWay(orx:DefaultBindMode), and a source that implementsINotifyPropertyChanged. -
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’sx:DataTypemust name its type. ADataTemplatecannot see the page; use a function, an event binding to a method of the item, or{Binding}withElementNameto reach it. -
ExtendsContentIntoTitleBarin XAML. It produces an error. Set it in the constructor, in code. -
ContentDialogthrows.XamlRootmust be set, and only oneContentDialogcan be open per window. -
Treating the
Windowlike a control. NoDataContext,Resources,Loaded,FindNameorRequestedTheme. Put those on the root content element.FindNamecannot realize anx:Loadelement when the XAML root is aWindow. -
Duplicate navigation. Handling
ItemInvokedand navigating unconditionally reloads the page and adds back-stack entries; compareCurrentSourcePageTypefirst, or navigate fromSelectionChanged. -
Constructor injection into pages.
Frame.Navigateuses 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
Mainminimal and finish the redirect beforenew 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
WindowsPackageTypeisNone. Packaged-only APIs such asApplicationData.Currentthrow without identity. -
Window size in the wrong unit.
AppWindow.Resizetakes 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 withDispatcherQueue.TryEnqueuebefore touching the view model or any control.
What Changed Across Versions
| Version | Change |
|---|---|
Windows App SDK 1.3 |
|
Windows App SDK 1.4 |
The XAML |
Windows App SDK 1.7 |
The |
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. |
Windows App SDK 2.1.3 |
|
Windows App SDK 2.2.0 |
|
Windows App SDK 2.3.1 |
|
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 ( |
CommunityToolkit.Mvvm 8.4.x |
|
Tooling |
Visual Studio 2026 and the winapp CLI (public preview, v0.7.1) create and run WinUI projects ( |
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
INavigationServiceand theIDialogServiceare user patterns; Learn documentsNavigationView,FrameandContentDialogonly. -
The thread on which
AppInstance.Activatedis raised is not stated; the code marshals to the UI thread. -
Turning on
PublishAotor 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.