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 source generators, handle WPF-UI (Lepo) to WinUI native control migration, or fix installer/build pipeline issues after migration. Keywords: WPF, WinUI, WinUI3, migration, porting, convert, namespace, XAML, Dispatcher, DispatcherQueue, imaging, BitmapImage, Window, ContentDialog, ThemeResource, DynamicResource, ResourceLoader, resw, resx, CommunityToolkit, ObservableProperty, WPF-UI, SizeToContent, AppWindow, SoftwareBitmap.
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.
System.Windows namespaces with Microsoft.UI.XamlDispatcher usage to DispatcherQueueObservable/RelayCommand to CommunityToolkit.Mvvm source generatorsSystem.Windows.Media.Imaging to Windows.Graphics.ImagingWindow vs WinUI Window differences (sizing, positioning, SizeToContent).resx to .resw with ResourceLoaderMicrosoft.WindowsAppSDK)net8.0-windows10.0.19041.0 TFM<UseWinUI>true</UseWinUI>.resx → .resw, introduce ResourceLoaderInstanceSystem.Windows types, async-ify imaging codeApp.xaml / App.xaml.cs — WinUI 3 has different application lifecycle boilerplate. Merge your resources and initialization code into the generated WinUI 3 App class.ProjectReference — Extract shared code to a Library project. This causes phantom build artifacts.Lazy<T> for resource-dependent statics — ResourceLoader is not available at class-load time in all contexts.| WPF | WinUI 3 |
|---|---|
System.Windows | Microsoft.UI.Xaml |
System.Windows.Controls | Microsoft.UI.Xaml.Controls |
System.Windows.Media | Microsoft.UI.Xaml.Media |
System.Windows.Media.Imaging | Microsoft.UI.Xaml.Media.Imaging (UI) / Windows.Graphics.Imaging (processing) |
System.Windows.Input | Microsoft.UI.Xaml.Input |
System.Windows.Data | Microsoft.UI.Xaml.Data |
System.Windows.Threading | Microsoft.UI.Dispatching |
System.Windows.Interop | WinRT.Interop |
| WPF | WinUI 3 | Notes |
|---|---|---|
Dispatcher.Invoke() | DispatcherQueue.TryEnqueue() | Different return type (bool) |
Dispatcher.CheckAccess() | DispatcherQueue.HasThreadAccess | Property vs method |
Application.Current.Dispatcher | Store DispatcherQueue in static field | See Threading |
MessageBox.Show() | ContentDialog | Must set XamlRoot |
DynamicResource | ThemeResource | Theme-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}" | Remove or use code-behind | x:Type not supported |
Properties.Resources.MyString | ResourceLoaderInstance.ResourceLoader.GetString("MyString") | Lazy-init pattern |
Application.Current.MainWindow | Custom App.Window static property | Must track manually |
SizeToContent="Height" | Custom SizeToContent() via AppWindow.Resize() | See Windowing |
MouseLeftButtonDown | PointerPressed | Mouse → Pointer events |
Pack URI (pack://...) | ms-appx:/// | Resource URI scheme |
Observable (custom base) | ObservableObject + [ObservableProperty] | CommunityToolkit.Mvvm |
RelayCommand (custom) | [RelayCommand] source generator | CommunityToolkit.Mvvm |
JpegBitmapEncoder | BitmapEncoder.CreateAsync(JpegEncoderId, stream) | Async, unified API |
encoder.QualityLevel = 85 | BitmapPropertySet { "ImageQuality", 0.85f } | int 1-100 → float 0-1 |
| WPF | WinUI 3 |
|---|---|
Microsoft.Xaml.Behaviors.Wpf | Microsoft.Xaml.Behaviors.WinUI.Managed |
WPF-UI (Lepo) | Remove — use native WinUI 3 controls |
CommunityToolkit.Mvvm | CommunityToolkit.Mvvm (same) |
Microsoft.Toolkit.Wpf.* | CommunityToolkit.WinUI.* |
| (none) | Microsoft.WindowsAppSDK |
| (none) | Microsoft.Windows.SDK.BuildTools |
| (none) | WinUIEx (optional, for window helpers) |
| (none) | CommunityToolkit.WinUI.Converters |
| WPF | WinUI 3 |
|---|---|
xmlns:local="clr-namespace:MyApp" | xmlns:local="using:MyApp" |
{DynamicResource Key} | {ThemeResource Key} |
{x:Static Type.Member} | {x:Bind} or code-behind |
{x:Type local:MyType} | Not supported |
<Style.Triggers> / <DataTrigger> | VisualStateManager |
{Binding} in Setter.Value | Not supported — use StaticResource |
Content="{x:Static p:Resources.Cancel}" | x:Uid="Cancel" with .Content in .resw |
<ui:FluentWindow> / <ui:Button> (WPF-UI) | Native <Window> / <Button> |
<ui:NumberBox> / <ui:ProgressRing> (WPF-UI) | Native <NumberBox> / <ProgressRing> |
BasedOn="{StaticResource {x:Type ui:Button}}" | BasedOn="{StaticResource DefaultButtonStyle}" |
IsDefault="True" / IsCancel="True" | Style="{StaticResource AccentButtonStyle}" / handle via KeyDown |
<AccessText> | Not available — use AccessKey property |
<behaviors:Interaction.Triggers> | Migrate to code-behind or WinUI behaviors |
Read only the section relevant to your current task:
| Pitfall | Solution |
|---|---|
ContentDialog throws "does not have a XamlRoot" | Set dialog.XamlRoot = this.Content.XamlRoot before ShowAsync() |
FilePicker throws error in desktop app | Call WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd) |
Window.Dispatcher returns null | Use Window.DispatcherQueue instead |
Resources on Window element not found | Move resources to root layout container (Grid.Resources) |
VisualStateManager on Window fails | Use 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 dir | Avoid Exe→WinExe ProjectReference; use Library project |
ResourceLoader crash at static init | Wrap in Lazy<T> or null-coalescing property — see Lazy Init |
SizeToContent not available | Implement manual content measurement + AppWindow.Resize() with DPI scaling |
x:Bind default mode is OneTime | Explicitly set Mode=OneWay or Mode=TwoWay |
DynamicResource / x:Static not compiling | Replace with ThemeResource / ResourceLoader or x:Uid |
IValueConverter.Convert signature mismatch | Last param: CultureInfo → string (language tag) |
| Test project can't resolve WPF types | Add <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 build | Use $(MSBuildThisFileDirectory) with relative paths instead |
| JPEG quality value wrong after migration | WPF: int 1-100; WinRT: float 0.0-1.0 |
| MSIX packaging fails in PreBuildEvent | Move to PostBuildEvent; artifacts not ready at PreBuild time |
| RC file icon path with forward slashes | Use double-backslash escaping: ..\\ui\\Assets\\icon.ico |
| Issue | Solution |
|---|---|
| Build fails after namespace rename | Check for lingering System.Windows usings; some types have no direct equivalent |
Missing PresentationCore.dll at runtime | Ensure ALL imaging code uses Windows.Graphics.Imaging, not System.Windows.Media.Imaging |
DataContext not working on Window | WinUI 3 Window is not a DependencyObject; use a root Page or UserControl |
| XAML designer not available | WinUI 3 does not support XAML Designer; use Hot Reload instead |
| NuGet restore failures | Run build-essentials.cmd after adding Microsoft.WindowsAppSDK package |
Parallel.ForEach compilation error | Migrate to Parallel.ForEachAsync for async imaging operations |
| Signing check fails on leaked artifacts | Run generateAllFileComponents.ps1; verify only WinUI3Apps\\ paths in signing config |