Use this skill when migrating UWP apps to WinUI 3 / Windows App SDK, or when verifying that generated code uses correct WinUI 3 APIs instead of legacy UWP patterns.
// ❌ WRONG — Throws InvalidOperationException in WinUI 3
var dialog = new ContentDialog
{
Title = "Error",
Content = "Something went wrong.",
CloseButtonText = "OK"
};
await dialog.ShowAsync();
// ✅ CORRECT — Set XamlRoot before showing
var dialog = new ContentDialog
{
Title = "Error",
Content = "Something went wrong.",
CloseButtonText = "OK",
XamlRoot = this.Content.XamlRoot // Required in WinUI 3
};
await dialog.ShowAsync();
Note: this form of XamlRoot works inside a Window class. Inside a page or user control, use the XamlRoot of that element or of the main window content from App.MainWindow.
// ❌ AVOID — UWP API, needs window handle setup in WinUI 3 desktop
var dialog = new Windows.UI.Popups.MessageDialog("Are you sure?", "Confirm");
await dialog.ShowAsync();
// ✅ CORRECT — Use ContentDialog
var dialog = new ContentDialog
{
Title = "Confirm",
Content = "Are you sure?",
PrimaryButtonText = "Yes",
CloseButtonText = "No",
XamlRoot = this.Content.XamlRoot
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
// User confirmed
}
MessageDialog can work in WinUI 3 desktop if it is initialized with the window handle, the same way as pickers, but ContentDialog is preferred.
// ❌ WRONG — UWP style, no window handle
var picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".txt");
var file = await picker.PickSingleFileAsync();
// ✅ CORRECT — Initialize with window handle
var picker = new FileOpenPicker();
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add(".txt");
var file = await picker.PickSingleFileAsync();
Not available — for dispatcher access only, use DispatcherQueue.GetForCurrentThread()
Key difference: UWP uses ASTA (Application STA) with built-in reentrancy blocking. WinUI 3 uses standard STA without this protection. Watch for reentrancy issues when async code pumps messages. Avoid blocking waits on async results on the UI thread, guard event handlers that can run again with a flag or semaphore, turn off the triggering UI while awaiting, and marshal state changes through DispatcherQueue.TryEnqueue.
// UWP IBackgroundTask — still usable in packaged apps when registered through supported background task APIs
public sealed class MyTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance) { }
}
// ✅ Windows App SDK AppLifecycle — activation handling only, not a replacement for trigger based background work
using Microsoft.Windows.AppLifecycle;
// Register for activation
var args = AppInstance.GetCurrent().GetActivatedEventArgs();
if (args.Kind == ExtendedActivationKind.AppNotification)
{
// Handle background activation
}
Most GetForCurrentView() patterns depend on CoreWindow and are unsupported in WinUI 3 desktop apps; some have window based interop equivalents. Verify against current Microsoft Learn Windows App SDK documentation:
UWP API
WinUI 3 Replacement
UIViewSettings.GetForCurrentView()
Use AppWindow properties
ApplicationView.GetForCurrentView()
AppWindow.GetFromWindowId(windowId)
DisplayInformation.GetForCurrentView()
Win32 GetDpiForWindow() or XamlRoot.RasterizationScale
UWP unit test projects do not work with WinUI 3. You must migrate to the WinUI 3 test project templates.
UWP
WinUI 3
Unit Test App (Universal Windows)
Unit Test App (WinUI in Desktop)
Standard MSTest project with UWP types
Must use WinUI test app for Xaml runtime
[TestMethod] for all tests
[TestMethod] for logic, [UITestMethod] for XAML/UI tests
Class Library (Universal Windows)
Class Library (WinUI in Desktop)
// ✅ WinUI 3 unit test — use [UITestMethod] for any XAML interaction
[UITestMethod]
public void TestMyControl()
{
var control = new MyLibrary.MyUserControl();
Assert.AreEqual(expected, control.MyProperty);
}
Replace expected in the sample with the value your test expects before running it.
Key: The [UITestMethod] attribute tells the test runner to execute the test on the XAML UI thread, which is required for instantiating any Microsoft.UI.Xaml type.
Replace all Windows.UI.Xaml.* using directives with Microsoft.UI.Xaml.*
Replace Windows.UI.Colors with Microsoft.UI.Colors
Replace CoreDispatcher.RunAsync with DispatcherQueue.TryEnqueue
Replace Window.Current with App.MainWindow static property
Add XamlRoot to all ContentDialog instances
Initialize all pickers with InitializeWithWindow.Initialize(picker, hwnd)
Replace MessageDialog with ContentDialog
Replace ApplicationView/CoreWindow with AppWindow
Replace CoreApplicationViewTitleBar with AppWindowTitleBar
Replace GetForCurrentView() calls per the replacement table above
Update interop for Share and Print managers — like pickers, these managers need the window handle through interop; this guide does not detail them
Register background tasks again using supported background task APIs; use AppLifecycle only for activation handling
Update project file: TFM to net10.0-windows10.0.22621.0, add <UseWinUI>true</UseWinUI>, reference the Windows App SDK package pinned to a tested stable version rather than a floating range, and match the target framework to the installed SDK
Migrate unit tests to Unit Test App (WinUI in Desktop) project; use [UITestMethod] for XAML tests