npx skills add ...
npx skills add microsoft/win-dev-skills --skill winui-wpf-migration
npx skills add microsoft/win-dev-skills --skill winui-wpf-migration
Migrate WPF applications to WinUI 3 — namespace replacement (System.Windows → Microsoft.UI.Xaml), control mapping (DataGrid→ListView, WrapPanel→ItemsRepeater, TabControl→TabView), threading (Dispatcher→DispatcherQueue), imaging (System.Drawing→BitmapImage), MVVM conversion to CommunityToolkit.Mvvm, and DynamicResource→ThemeResource. Use when converting WPF code, replacing WPF namespaces, or fixing migration build errors.
Before writing code, inventory WPF-specific APIs:
List: WPF controls used, custom MVVM framework, imaging APIs, threading patterns, Win32 interop.
Immediately set <RootNamespace> in .csproj to match the WPF namespace. Update x:Class in App.xaml, MainWindow.xaml and their code-behind files. Build to verify before porting any code.
| WPF | WinUI 3 |
|---|---|
System.Windows | Microsoft.UI.Xaml |
System.Windows.Controls | Microsoft.UI.Xaml.Controls |
System.Windows.Media | Microsoft.UI.Xaml.Media |
System.Windows.Input | Microsoft.UI.Xaml.Input |
System.Windows.Data | Microsoft.UI.Xaml.Data |
System.Windows.Threading.Dispatcher | Microsoft.UI.Dispatching.DispatcherQueue |
PresentationCore / PresentationFramework | Remove entirely |
| WPF Control | WinUI 3 Equivalent |
|---|---|
DataGrid | ListView with Grid column headers |
WrapPanel | ItemsRepeater + UniformGridLayout |
TabControl | TabView |
StatusBar | Grid row at bottom with TextBlock elements |
Menu / MenuItem | MenuBar / MenuBarItem / MenuFlyoutItem |
ToolBar | CommandBar |
Expander (custom) | Expander (built-in) |
Get via DispatcherQueue.GetForCurrentThread(). No Application.Current.Dispatcher in WinUI 3.
Critical: PresentationCore.dll and System.Windows.Media.Imaging crash the WinUI XAML compiler. This is an architectural incompatibility — no workaround exists.
System.Windows.Media.Imaging references at migration startWindows.Graphics.Imaging (WinRT) or Microsoft.UI.Xaml.Media.Imaging.BitmapImage<UseWPF>true</UseWPF> — it silently corrupts the buildDelete custom ObservableObject/RelayCommand/DelegateCommand. Use CommunityToolkit.Mvvm:
INotifyPropertyChanged base → ObservableObject with [ObservableProperty] partial propertiesRelayCommand → [RelayCommand] attribute{Binding} → {x:Bind Mode=OneWay}DynamicResource → {ThemeResource}.resx → .resw (copy + rename to Strings\en-us\){x:Static} → x:Uid for localized stringsProperties.Resources.Key → ResourceLoader.GetString("Key")PresentationCore, PresentationFramework, or System.Windows.Controls assemblies<UseWPF>true</UseWPF> or <WindowsPackageType>None</WindowsPackageType>Package.appxmanifestApp.xaml / App.xaml.cs — merge WPF code into the WinUI 3 boilerplatewinapp run to launch — never run the .exe directlydotnet new winui-mvvm -n <AppName>// WPF
Application.Current.Dispatcher.Invoke(() => { /* UI work */ });
// WinUI 3
dispatcherQueue.TryEnqueue(() => { /* UI work */ });# Check for remaining WPF references (should return nothing)
Select-String -Path (Get-ChildItem -Recurse -Filter "*.cs" | Where-Object { $_.FullName -notlike "*\obj\*" }) -Pattern "System\.Windows\."
# Verify packaging preserved
Test-Path "Package.appxmanifest" # should be True
# Build and run
.\BuildAndRun.ps1