Labsco
microsoft logo

wpf-to-winui3-migration

✓ Official136,050

by microsoft · part of microsoft/powertoys

Guide for migrating PowerToys modules from WPF to WinUI 3 (Windows App SDK). Use when asked to migrate WPF code, convert WPF XAML to WinUI, replace System.Windows namespaces with Microsoft.UI.Xaml, update Dispatcher to DispatcherQueue, replace DynamicResource with ThemeResource, migrate imaging APIs from System.Windows.Media.Imaging to Windows.Graphics.Imaging, convert WPF Window to WinUI Window, migrate .resx to .resw resources, migrate custom Observable/RelayCommand to CommunityToolkit.Mvvm so

🧰 Not standalone. This skill ships with microsoft/powertoys and only works together with that tool — install the tool first, then add this skill.

This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.

WPF to WinUI 3 Migration Skill

Migrate PowerToys modules from WPF (System.Windows.*) to WinUI 3 (Microsoft.UI.Xaml.* / Windows App SDK). Based on patterns validated in the ImageResizer module migration.

When to Use This Skill

  • Migrate a PowerToys module from WPF to WinUI 3
  • Convert WPF XAML files to WinUI 3 XAML
  • Replace System.Windows namespaces with Microsoft.UI.Xaml
  • Migrate Dispatcher usage to DispatcherQueue
  • Migrate custom Observable/RelayCommand to CommunityToolkit.Mvvm source generators
  • Replace WPF-UI (Lepo) controls with native WinUI 3 controls
  • Convert imaging code from System.Windows.Media.Imaging to Windows.Graphics.Imaging
  • Handle WPF Window vs WinUI Window differences (sizing, positioning, SizeToContent)
  • Migrate resource files from .resx to .resw with ResourceLoader
  • Fix installer/build pipeline issues after WinUI 3 migration
  • Update project files, NuGet packages, and signing config

Migration Strategy

Phase-by-Phase Scope

Work on bounded problems, not the entire codebase at once. Each phase should compile before moving to the next.

  1. Project file — Update TFM, NuGet packages, set <UseWinUI>true</UseWinUI>
  2. Data models and business logic — No UI dependencies, migrate first
  3. MVVM framework — Replace custom Observable/RelayCommand with CommunityToolkit.Mvvm
  4. Resource strings — Migrate .resx.resw, introduce ResourceLoaderInstance
  5. Services and utilities — Replace System.Windows types, async-ify imaging code
  6. ViewModels — Update Dispatcher usage, binding patterns
  7. Views/Pages — Starting from leaf pages with fewest dependencies
  8. Main page / shell — Last, since it depends on everything
  9. App.xaml / startup code — Merge carefully (do NOT overwrite WinUI 3 boilerplate)
  10. Installer & build pipeline — Update WiX, signing, build events
  11. Tests — Adapt for WinUI 3 runtime, async patterns

Migration Contract: Prohibited Patterns

These rules capture human judgment and must be applied consistently across every file. Do NOT deviate.

Architecture prohibitions:

  • Do NOT overwrite App.xaml / App.xaml.cs — WinUI 3 has different lifecycle boilerplate. Merge resources and init code into the generated WinUI 3 App class.
  • Do NOT create Exe→WinExe ProjectReference — Extract shared code to a Library project. Causes phantom build artifacts.
  • Do NOT instantiate services directly — Use DI and CommunityToolkit.Mvvm patterns.
  • Do NOT create a Window subclass for every dialog or sub-page — use ContentDialog for in-app dialogs and Frame/Page navigation for sub-views. Separate Window classes are reserved for distinct top-level surfaces (e.g., FancyZones editor, OOBE).
  • Do NOT omit WindowsPackageType=None and WindowsAppSDKSelfContained=true — Both are mandatory in the csproj for every WinUI 3 module in PowerToys. Without them the app crashes at startup with COMException: ClassFactory cannot supply requested class because the WinUI 3 runtime DLLs are not found.

XAML prohibitions:

  • Do NOT use {DynamicResource} — Replace with {ThemeResource} (theme-reactive) or {StaticResource}.
  • Do NOT use {Binding} in Setter.Value — Not supported in WinUI 3. Use {StaticResource}.
  • Do NOT use {x:Static} — Replace with {x:Bind}, x:Uid, or code-behind.
  • Do NOT use {x:Type} — Not supported. Use x:DataType for DataTemplate, or code-behind.
  • Do NOT use clr-namespace: — Replace with using: in all xmlns declarations.
  • Do NOT use Style.Triggers / DataTrigger / EventTrigger — Replace with VisualStateManager.
  • Do NOT use MultiBinding — Replace with x:Bind function binding or computed ViewModel property.
  • Do NOT use Visibility="Hidden" — WinUI only has Visible and Collapsed. Use Opacity="0" if layout must be preserved.
  • Do NOT use IsDefault / IsCancel — Use AccentButtonStyle for primary button; handle Enter/Escape in code-behind.
  • Do NOT omit BasedOn when overriding default styles — Without it, your style replaces the entire default. Always use BasedOn="{StaticResource DefaultButtonStyle}" etc.
  • Do NOT omit XamlControlsResources as first merged dictionary — It provides default Fluent styles. Without it, controls have no visual appearance.

Code-behind prohibitions:

  • Do NOT use Application.Current.Dispatcher — Store DispatcherQueue in a static field explicitly.
  • Do NOT use Window.Current — Not supported. Use a custom App.Window static property.
  • Do NOT put DataContext, Resources, or VisualStateManager on Window — WinUI 3 Window is NOT a DependencyObject. Use a root Page/UserControl/Grid.
  • Do NOT use tunneling/preview events (PreviewMouseDown, PreviewKeyDown) — WinUI has no tunneling. Use bubbling equivalents with Handled property or AddHandler(handledEventsToo: true).

Resource prohibitions:

  • Do NOT use Properties.Resources.MyString — Replace with ResourceLoaderInstance.ResourceLoader.GetString("MyString").
  • Do NOT initialize ResourceLoader-dependent values as static fields — Wrap in Lazy<T> or null-coalescing property.
  • Do NOT use pack:// URIs — Replace with ms-appx:/// scheme.

Quick Reference Tables

Namespace Mapping

WPFWinUI 3Notes
System.WindowsMicrosoft.UI.XamlRoot namespace
System.Windows.ControlsMicrosoft.UI.Xaml.ControlsCore controls
System.Windows.Controls.PrimitivesMicrosoft.UI.Xaml.Controls.PrimitivesLow-level primitives
System.Windows.MediaMicrosoft.UI.Xaml.MediaBrushes, transforms
System.Windows.Media.AnimationMicrosoft.UI.Xaml.Media.AnimationStoryboard, animations
System.Windows.Media.ImagingMicrosoft.UI.Xaml.Media.Imaging (UI) / Windows.Graphics.Imaging (processing)Split by purpose
System.Windows.Media.Media3DNo equivalentUse Win2D or Composition APIs
System.Windows.ShapesMicrosoft.UI.Xaml.ShapesRectangle, Ellipse, Path
System.Windows.InputMicrosoft.UI.Xaml.InputPointer, keyboard, focus
System.Windows.DataMicrosoft.UI.Xaml.DataBinding, IValueConverter
System.Windows.DocumentsMicrosoft.UI.Xaml.DocumentsLimited — RichTextBlock + Paragraph
System.Windows.MarkupMicrosoft.UI.Xaml.MarkupXAML parsing, markup extensions
System.Windows.AutomationMicrosoft.UI.Xaml.AutomationAccessibility / UI Automation
System.Windows.NavigationNo direct equivalentUse Frame.Navigate()
System.Windows.ThreadingMicrosoft.UI.DispatchingDispatcher → DispatcherQueue
System.Windows.InteropWinRT.Interop / Microsoft.UI.Xaml.HostingHWND interop

Control Replacements (No 1:1 Mapping)

These WPF controls have no direct counterpart and require a different control or third-party package:

WPF ControlWinUI 3 ReplacementNotes
DataGridWinUI.TableViewCommunity library; the Toolkit DataGrid is no longer maintained. Legacy code may still pin v7 CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2
RibbonCommandBar / NavigationView, or Toolkit Labs RibbonNo first-party Ribbon in WinUI; Labs component is experimental/partial
Menu / MenuItemMenuBar / MenuBarItem / MenuFlyoutMenuBar for classic menu, MenuFlyout for context
ContextMenuMenuFlyoutAssign to ContextFlyout property
ToolBar / ToolBarTrayCommandBar + AppBarButton
StatusBarCustom Grid/StackPanel or InfoBarNo StatusBar control
TabControlTabView or NavigationView (top mode)TabView for closeable tabs
DocumentViewerWebView2Render PDFs/XPS inside WebView2
FlowDocumentRichTextBlockPartial replacement only
RichTextBoxRichEditBoxRich text editing
GroupBoxExpander (built-in) or HeaderedContentControl (Toolkit)See Layout & Header Controls from CommunityToolkit.WinUI below
LabelTextBlockWPF Label is a ContentControl; use TextBlock + AccessKey
TreeViewTreeView (native)Available natively, but data binding model differs significantly
MessageBoxContentDialogMust set XamlRoot before ShowAsync()
MediaElementMediaPlayerElementDifferent API
AccessTextNot availableUse AccessKey property on target control

Layout & Header Controls from CommunityToolkit.WinUI

These WPF controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. The NuGet package id and the XAML namespace differ intentionally: package names end in .Primitives / .HeaderedControls, but the registered XAML namespace is the shorter CommunityToolkit.WinUI.Controls (confirmed in the official Microsoft Q&A).

WPF ControlWinUI 3 ReplacementNuGet PackageXAML Namespace
WrapPanelWrapPanelCommunityToolkit.WinUI.Controls.Primitivesusing:CommunityToolkit.WinUI.Controls
UniformGridUniformGridCommunityToolkit.WinUI.Controls.Primitivesusing:CommunityToolkit.WinUI.Controls
DockPanelDockPanelCommunityToolkit.WinUI.Controls.Primitivesusing:CommunityToolkit.WinUI.Controls
GroupBox (alt.)HeaderedContentControlCommunityToolkit.WinUI.Controls.HeaderedControlsusing:CommunityToolkit.WinUI.Controls

No Equivalent — Requires Architectural Rework

These WPF features have no WinUI counterpart and require redesign, not find-and-replace:

WPF FeatureWinUI 3 Replacement Strategy
Style.Triggers / DataTriggerVisualStateManager with StateTrigger — see XAML Migration
MultiBindingx:Bind function binding: {x:Bind local:Converters.Format(VM.A, VM.B), Mode=OneWay}
RoutedUICommand / CommandBindingICommand / [RelayCommand] from CommunityToolkit.Mvvm. WinUI also has StandardUICommand / XamlUICommand for platform commands.
AdornerLayer / AdornerDepends on use case: TeachingTip/InfoBar (validation), Popup (overlays), PlaceholderText (watermarks), Canvas overlay (decorations)
Visibility.HiddenOpacity="0" with Visibility="Visible" (preserves layout space)
Window.Resources / Window.DataContextMove to root Grid.Resources / root Page/UserControl — WinUI Window is NOT a DependencyObject
Tunneling events (Preview*)Use bubbling equivalents + Handled property or AddHandler(handledEventsToo: true)

Critical API Replacements

WPFWinUI 3Notes
Dispatcher.Invoke()DispatcherQueue.TryEnqueue()Different return type (bool), async by default
Dispatcher.CheckAccess()DispatcherQueue.HasThreadAccessProperty vs method
Application.Current.DispatcherStore DispatcherQueue in static fieldSee Threading
Window.CurrentCustom App.Window static propertyNot supported in Windows App SDK
Application.Current.MainWindowCustom App.Window static propertyMust track manually
MessageBox.Show()ContentDialogMust set XamlRoot
System.Windows.ClipboardWindows.ApplicationModel.DataTransfer.ClipboardDifferent API surface
RoutedUICommand / CommandBindingICommand / [RelayCommand]Remove CommandBinding; bind ICommand directly
Properties.Resources.MyStringResourceLoaderInstance.ResourceLoader.GetString("MyString")Lazy-init pattern
DynamicResourceThemeResourceTheme-reactive only
clr-namespace:using:XAML namespace prefix
{x:Static props:Resources.Key}x:Uid or ResourceLoader.GetString().resx → .resw
DataType="{x:Type m:Foo}"x:DataType="m:Foo"x:Type not supported
SizeToContent="Height"Custom SizeToContent() via AppWindow.Resize()See Windowing
Pack URI (pack://...)ms-appx:///Resource URI scheme
Observable (custom base)ObservableObject + [ObservableProperty]CommunityToolkit.Mvvm
RelayCommand (custom)[RelayCommand] source generatorCommunityToolkit.Mvvm
JpegBitmapEncoderBitmapEncoder.CreateAsync(JpegEncoderId, stream)Async, unified API
encoder.QualityLevel = 85BitmapPropertySet { "ImageQuality", 0.85f }int 1-100 → float 0-1

Event Replacements (Mouse → Pointer)

WPF EventWinUI 3 EventNotes
MouseLeftButtonDownPointerPressedCheck IsLeftButtonPressed on args
MouseLeftButtonUpPointerReleasedCheck pointer properties
MouseRightButtonDownRightTappedOr PointerPressed with right button check
MouseMovePointerMovedMouseEventArgsPointerRoutedEventArgs
MouseWheelPointerWheelChangedDifferent event args
MouseEnter / MouseLeavePointerEntered / PointerExited
MouseDoubleClickDoubleTappedDifferent event args
PreviewMouseDownPointerPressedNo tunneling — use Handled or AddHandler
PreviewKeyDownKeyDownKeyEventArgsKeyRoutedEventArgs

Property Replacements

WPFWinUI 3Context
Visibility.HiddenVisibility.Collapsed or Opacity="0"Use Opacity="0" to preserve layout
TextWrapping.WrapWithOverflowTextWrapping.WrapWinUI doesn't distinguish
Focusable="True"IsTabStop="True"Different property name
ContextMenu=ContextFlyout=On any UIElement
MediaElementMediaPlayerElementDifferent API
SnapsToDevicePixelsNot availableWinUI handles pixel snapping internally

NuGet Package Migration

WPFWinUI 3Notes
Microsoft.Xaml.Behaviors.WpfMicrosoft.Xaml.Behaviors.WinUI.Managed
WPF-UI (Lepo)Remove — use native WinUI 3 controls
CommunityToolkit.MvvmCommunityToolkit.Mvvm (same)
Microsoft.Toolkit.Wpf.*CommunityToolkit.WinUI.*
(none)Microsoft.WindowsAppSDKRequired
(none)Microsoft.Windows.SDK.BuildToolsRequired
(none)WinUIExOptional, window helpers
(none)CommunityToolkit.WinUI.ConvertersOptional
(none)CommunityToolkit.WinUI.Controls.PrimitivesOptional — WrapPanel, UniformGrid, DockPanel, ConstrainedBox
(none)CommunityToolkit.WinUI.Controls.HeaderedControlsOptional — HeaderedContentControl, HeaderedItemsControl, HeaderedTreeView
(none)CommunityToolkit.WinUI.Controls.SettingsControlsOptional — SettingsCard, SettingsExpander
(none)CommunityToolkit.WinUI.Controls.SizersOptional — GridSplitter
(none)CommunityToolkit.WinUI.UI.Controls.DataGridLegacy v7 — only for migrating existing DataGrid code; prefer WinUI.TableView

XAML Syntax Changes

WPFWinUI 3Notes
xmlns:local="clr-namespace:MyApp"xmlns:local="using:MyApp"CLR → using syntax
{DynamicResource Key}{ThemeResource Key}Re-evaluates on theme change
{StaticResource Key}{StaticResource Key}Same — resolved once at load
{x:Static Type.Member}{x:Bind} or code-behind
{x:Type local:MyType}Not supportedUse x:DataType for DataTemplate
{x:Array}Not supportedCreate collections in code-behind
<Style.Triggers> / <DataTrigger>VisualStateManagerSee XAML Migration
{Binding} in Setter.ValueNot supported — use StaticResource
Content="{x:Static p:Resources.Cancel}"x:Uid="Cancel" with .Content in .resw
sys:String / sys:Int32 / etc.x:String / x:Int32 / etc.XAML intrinsic types
<ui:FluentWindow> (WPF-UI)<Window>Native + ExtendsContentIntoTitleBar
<ui:NumberBox> / <ui:ProgressRing> (WPF-UI)Native <NumberBox> / <ProgressRing>
BasedOn="{StaticResource {x:Type ui:Button}}"BasedOn="{StaticResource DefaultButtonStyle}"Named style keys
IsDefault="True" / IsCancel="True"Style="{StaticResource AccentButtonStyle}" / KeyDown
<AccessText>Not available — use AccessKey property
<behaviors:Interaction.Triggers>Code-behind or WinUI behaviors
Window.ResourcesRoot container's Resources (e.g. Grid.Resources)Window is not a DependencyObject

Binding: {Binding} vs {x:Bind}

Both work in WinUI 3. Prefer {x:Bind} for new/migrated code.

Feature{Binding}{x:Bind}
Default modeOneWayOneTime — add Mode=OneWay explicitly!
Default sourceDataContextPage/UserControl code-behind
Compile-time validationNoYes
Function bindingNoYes (replaces MultiBinding)
PerformanceReflection-basedCompiled, no reflection
MultiBinding supportNo (not in WinUI)Use function binding

Detailed Reference Docs

Read only the section relevant to your current task:

Common Pitfalls (from ImageResizer migration)

PitfallSolution
ContentDialog throws "does not have a XamlRoot"Set dialog.XamlRoot = this.Content.XamlRoot before ShowAsync()
FilePicker throws error in desktop appCall WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd)
Window.Dispatcher returns nullUse Window.DispatcherQueue instead
Resources on Window element not foundMove resources to root layout container (Grid.Resources)
VisualStateManager on Window failsUse UserControl or Page inside the Window
Satellite assembly installer errors (WIX0103)Remove .resources.dll refs from Resources.wxs; WinUI 3 uses .pri
Phantom .exe/.deps.json in root output dirAvoid Exe→WinExe ProjectReference; use Library project
ResourceLoader crash at static initWrap in Lazy<T> or null-coalescing property — see Lazy Init
SizeToContent not availableImplement manual content measurement + AppWindow.Resize() with DPI scaling
x:Bind default mode is OneTimeExplicitly set Mode=OneWay or Mode=TwoWay
DynamicResource / x:Static not compilingReplace with ThemeResource / ResourceLoader or x:Uid
IValueConverter.Convert signature mismatchLast param: CultureInfostring (language tag)
Test project can't resolve WPF typesAdd <UseWPF>true</UseWPF> temporarily; remove after imaging migration
Pixel dimension type mismatch (int vs uint)WinRT uses uint for pixel sizes — add u suffix in test assertions
$(SolutionDir) empty in standalone project buildUse $(MSBuildThisFileDirectory) with relative paths instead
JPEG quality value wrong after migrationWPF: int 1-100; WinRT: float 0.0-1.0
MSIX packaging fails in PreBuildEventMove to PostBuildEvent; artifacts not ready at PreBuild time
RC file icon path with forward slashesUse double-backslash escaping: ..\\ui\\Assets\\icon.ico
COMException: ClassFactory cannot supply requested class at startupMissing <WindowsPackageType>None</WindowsPackageType> and/or <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> in csproj. Without these, the app tries to locate the Windows App SDK framework package (not installed) instead of using bundled runtime DLLs. Both properties are mandatory for every WinUI 3 module in PowerToys.
CombinedGeometry not available in WinUI 3WinUI 3 UIElement.Clip only accepts RectangleGeometry. For overlay hole effects (exclude region), use a Path element with GeometryGroup FillRule="EvenOdd" containing two RectangleGeometry children — the EvenOdd rule creates a transparent hole where geometries overlap.