Tutorial WPF Migration
Key Takeaways
  • A settings page or data display screen is the best first migration candidate; avoid the main shell or navigation host.
  • AI agents can analyze WPF XAML, flag incompatibilities, and generate WinUI equivalents for the 10 most common WPF-to-WinUI differences.
  • The Uno Platform App MCP gives your agent "eyes" into the running application, enabling it to inspect the visual tree, verify data bindings, and catch layout issues in real time.
  • Hot Reload and iterative agent-driven fixes compress the feedback loop from hours to minutes.
  • A single migrated screen can run on Windows, WebAssembly, Android, iOS, macOS, and Linux with no additional platform-specific code.

Who this is for: WPF developers with an existing application who want a practical, hands-on path to multi-platform. You should be comfortable with XAML, C#, and basic command-line tooling. No prior Uno Platform experience is required.

You have a WPF application that works. It runs on Windows, your users depend on it, and your team knows the codebase. But now there is pressure to go multi-platform: a WebAssembly version for remote workers, a mobile build for field teams, or a modernized stack running on .NET 10. Rewriting everything from scratch should not be necessary.

This article walks you through migrating a single WPF screen to Uno Platform using an AI agent to handle the mechanical translation work. By the end, you will have one fully migrated screen running on at least two platforms, verified through live visual tree inspection.

Prerequisites

Prerequisites

Before you start, make sure your environment is ready. You need four things.

.NET 10 SDK

Uno Platform 6.5 targets .NET 10. Verify with:

Terminal
dotnet --version

Uno Platform Project Template

Install or update the Uno Platform templates, then scaffold a new project:

Terminal
dotnet new install Uno.Templates
dotnet new unoapp -o MigratedApp

This gives you a solution with targets for Windows, WebAssembly, iOS, Android, macOS, and Linux. You will be adding your migrated screen into this project.

AI Coding Assistant with Both Uno Platform MCPs

Your AI agent needs access to two Model Context Protocol (MCP) servers:

  1. Uno Platform Remote MCP — Provides documentation search and development best practices, giving your agent up-to-date knowledge of Uno Platform APIs.
  2. Uno Platform App MCP — Provides runtime interaction with your running application: visual tree inspection, screenshots, pointer clicks, and behavior verification.

Once configured, your agent has access to tools like uno_platform_docs_search, uno_app_visualtree_snapshot, uno_app_get_screenshot, and uno_app_get_element_datacontext.

Choose a Screen

Pick the Right First Screen

Not every screen is a good migration candidate. Choosing poorly will burn time on edge cases before you have proven the approach works.

Characteristics of a Good First Screen

Self-contained. The screen should not depend on a complex navigation framework, shared state managers, or deeply nested view hierarchies. It should be a single UserControl or Window with its own ViewModel that you can extract cleanly.

Representative complexity. Pick something that uses real controls: TextBoxes, ComboBoxes, ListViews, data binding, maybe a few converters. You want something complex enough that a successful migration gives you a reliable pattern for the next ten screens.

No deep Win32 interop. If the screen calls into native Windows APIs through P/Invoke or COM interop, save it for later.

Good Candidates

  • A settings page with form inputs, toggles, and save/cancel buttons
  • A data display screen with a DataGrid or ListView bound to a collection
  • A detail view showing a single entity with read-only and editable fields
  • A search/filter panel with text inputs and result lists

Avoid for Now

  • The main shell or navigation host (too many dependencies)
  • Screens with custom Win32 window chrome or interop
  • Screens that rely on WPF-specific third-party controls without WinUI equivalents

For this tutorial, we will work with a settings page. It is a common pattern, uses a mix of input controls, and is typically self-contained.

Phase 1

Phase 1: Analyze

Take your chosen WPF XAML file and its code-behind, and feed them to your AI agent. The agent's job in this phase is to read the markup, identify every WPF-specific construct, and produce a clear list of what needs to change.

Sample WPF XAML

WPF
<Window x:Class="MyApp.SettingsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:MyApp"
        Title="Settings" Height="500" Width="400">
  <Window.Resources>
    <local:BoolToVisibilityConverter x:Key="BoolToVis"/>
    <Style TargetType="TextBox">
      <Style.Triggers>
        <Trigger Property="IsEnabled" Value="False">
          <Setter Property="Background" Value="LightGray"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </Window.Resources>

  <Grid Margin="16">
    <TextBlock Text="{Binding UserName, StringFormat='Welcome, {0}'}"/>
    <CheckBox Content="Enable notifications"
              IsChecked="{Binding NotificationsEnabled}"/>
    <TextBox Text="{Binding ServerUrl, UpdateSourceTrigger=PropertyChanged}"
             Visibility="{Binding IsAdvancedMode, Converter={StaticResource BoolToVis}}"/>
    <DataGrid ItemsSource="{Binding RecentConnections}" AutoGenerateColumns="False">
      <!-- columns -->
    </DataGrid>
    <Button Content="Save" Command="{Binding SaveCommand}"/>
  </Grid>
</Window>

What the Agent Flags

When you pass this XAML to your agent with the instruction "Analyze this WPF XAML for migration to Uno Platform," it produces a report like this:

  1. Window root element — WinUI uses Page instead. Window management is handled differently.
  2. clr-namespace: prefix — Must change to using: syntax in WinUI XAML.
  3. Style.Triggers — Not supported in WinUI. Must convert to VisualStateManager.
  4. StringFormat in Binding — Not supported. Use multiple Run elements or x:Bind with a function.
  5. UpdateSourceTrigger=PropertyChanged — Not needed; WinUI TextBox updates on every keystroke by default.
  6. BoolToVisibilityConverter — Not needed; WinUI supports implicit bool to Visibility conversion.
  7. DataGrid — Not a built-in WinUI control. Use the Windows Community Toolkit DataGrid.
  8. Code-behind namespacesSystem.Windows becomes Microsoft.UI.Xaml.

This analysis takes the agent seconds. With access to the Uno Platform Remote MCP, it can look up exactly which APIs are supported and which need alternatives.

Phase 2

Phase 2: Translate

Now the agent generates WinUI XAML equivalents. This is where the bulk of the mechanical work happens. Here are the 10 most common WPF-to-WinUI changes, each with before and after code.

1 Window to Page

WPF uses Window as the top-level container. WinUI uses Page for content screens.

WPF
<Window x:Class="MyApp.SettingsWindow"
        Title="Settings" Height="500" Width="400">
Uno Platform
<Page x:Class="MyApp.SettingsPage">

The Title, Height, and Width properties move to your navigation or window configuration code.

2 System.Windows to Microsoft.UI.Xaml Namespaces

WPF
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
Uno Platform
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;

In XAML, the clr-namespace: prefix changes to using:

3 Triggers to VisualStateManager

WPF Style.Triggers and DataTriggers do not exist in WinUI. Replace them with VisualStateManager states and setters.

WPF
<Style TargetType="TextBox">
  <Style.Triggers>
    <Trigger Property="IsEnabled" Value="False">
      <Setter Property="Background" Value="LightGray"/>
    </Trigger>
  </Style.Triggers>
</Style>
Uno Platform
<!-- Built-in WinUI control templates already handle Disabled state.
     Override specific resources to customize: -->
<Style TargetType="TextBox">
  <Setter Property="Resources">
    <Setter.Value>
      <ResourceDictionary>
        <SolidColorBrush x:Key="TextBoxBackgroundDisabled"
                         Color="LightGray"/>
      </ResourceDictionary>
    </Setter.Value>
  </Setter>
</Style>

For simple property-based triggers, the built-in WinUI control templates already handle common states like Disabled, PointerOver, and Focused. Check the default template before writing custom visual states.

4 MultiBinding to x:Bind Functions

WPF's MultiBinding is not available in WinUI. Use x:Bind with a function or multiple Run elements.

WPF
<TextBlock.Text>
  <MultiBinding StringFormat="{}{0} - {1}">
    <Binding Path="FirstName"/>
    <Binding Path="LastName"/>
  </MultiBinding>
</TextBlock.Text>
Uno Platform
<TextBlock Text="{x:Bind FormatFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}"/>

// In code-behind:
public string FormatFullName(string first, string last) => $"{first} - {last}";

5 DataGrid Mapping

WPF includes DataGrid as a built-in control. In WinUI and Uno Platform, use the Windows Community Toolkit DataGrid.

WPF
<DataGrid ItemsSource="{Binding RecentConnections}"
          AutoGenerateColumns="False">
  <DataGridTextColumn Header="Server" Binding="{Binding Host}"/>
</DataGrid>
Uno Platform
<ctk:DataGrid ItemsSource="{Binding RecentConnections}"
              AutoGenerateColumns="False">
  <ctk:DataGridTextColumn Header="Server" Binding="{Binding Host}"/>
</ctk:DataGrid>

The API surface is nearly identical. The main difference is the namespace.

6 Button Hover/Pressed Triggers to Resource Overrides

Uno Platform
<Style TargetType="Button">
  <Setter Property="Resources">
    <Setter.Value>
      <ResourceDictionary>
        <SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#E0E0E0"/>
        <SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#C0C0C0"/>
      </ResourceDictionary>
    </Setter.Value>
  </Setter>
</Style>

Lightweight resource overrides instead of replacing the entire control template.

7 RoutedUICommand to ICommand

WPF's RoutedUICommand and CommandBinding pattern does not exist in WinUI. Use standard ICommand implementations. If you are already using MVVM with ICommand in your WPF app, this pattern carries over directly.

8 InputBindings to KeyboardAccelerators

WPF
<Window.InputBindings>
  <KeyBinding Key="S" Modifiers="Ctrl" Command="{Binding SaveCommand}"/>
</Window.InputBindings>
Uno Platform
<Page.KeyboardAccelerators>
  <KeyboardAccelerator Key="S" Modifiers="Control"
                       Invoked="SaveAccelerator_Invoked"/>
</Page.KeyboardAccelerators>

9 Adorners to Popup/Overlay

WPF's Adorner and AdornerLayer system does not exist in WinUI. For validation indicators, tooltips, or overlay elements, use Popup, TeachingTip, or overlay panels. For most validation scenarios, simple visual state changes are cleaner than custom adorners.

10 ControlTemplate Trigger → VisualStateManager

The pattern is consistent: triggers move into VisualStateManager, IsMouseOver becomes the PointerOver state, and setters use Target instead of TargetName plus Property.

Uno Platform
<ControlTemplate TargetType="Button">
  <Border x:Name="RootBorder"
          Background="{TemplateBinding Background}">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="CommonStates">
        <VisualState x:Name="Normal"/>
        <VisualState x:Name="PointerOver">
          <VisualState.Setters>
            <Setter Target="RootBorder.Background" Value="#E0E0E0"/>
          </VisualState.Setters>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <ContentPresenter/>
  </Border>
</ControlTemplate>

The Translated Settings Page

After the agent applies all ten transformations:

Uno Platform
<Page x:Class="MigratedApp.SettingsPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MigratedApp"
      xmlns:ctk="using:CommunityToolkit.WinUI.UI.Controls">

  <Grid Margin="16">
    <TextBlock Text="Application Settings"
               Style="{StaticResource TitleTextBlockStyle}"/>

    <StackPanel Grid.Row="1" Spacing="8">
      <TextBlock>
        <Run Text="Welcome, "/>
        <Run Text="{Binding UserName}"/>
      </TextBlock>
      <CheckBox Content="Enable notifications"
                IsChecked="{Binding NotificationsEnabled, Mode=TwoWay}"/>
      <TextBox Text="{Binding ServerUrl, Mode=TwoWay}"
               Visibility="{Binding IsAdvancedMode}"/>
    </StackPanel>

    <ctk:DataGrid Grid.Row="2"
                   ItemsSource="{Binding RecentConnections}"
                   AutoGenerateColumns="False">
      <ctk:DataGridTextColumn Header="Server" Binding="{Binding Host}"/>
      <ctk:DataGridTextColumn Header="Port" Binding="{Binding Port}"/>
      <ctk:DataGridTextColumn Header="Last Used" Binding="{Binding LastUsed}"/>
    </ctk:DataGrid>

    <StackPanel Grid.Row="3" Orientation="Horizontal"
                HorizontalAlignment="Right" Spacing="8">
      <Button Content="Save" Command="{Binding SaveCommand}"/>
      <Button Content="Cancel" Command="{Binding CancelCommand}"/>
    </StackPanel>
  </Grid>
</Page>

The Grid layout, StackPanel structure, data bindings, and ICommand patterns all transferred directly. The changes were mechanical: Window to Page, triggers removed, StringFormat replaced, implicit bool-to-Visibility conversion used, and the DataGrid namespace updated.

Phase 3

Phase 3: Verify

Build and run the Uno Platform application. Target the desktop (Windows) head first, since it is closest to your original WPF environment.

Terminal
dotnet build MigratedApp.csproj

Once the app is running, your AI agent can connect through the App MCP.

Inspect the Visual Tree

"Take a visual tree snapshot of the running app and tell me if the SettingsPage loaded correctly."

The agent calls uno_app_visualtree_snapshot and receives a structured representation of the UI hierarchy. It can immediately confirm the page structure is correct — and spot issues like empty bound values or collapsed elements.

Verify the DataContext

"Check the DataContext of the SettingsPage root Grid."

The agent uses uno_app_get_element_datacontext to inspect the actual data bound to any element, confirming whether bindings are connected.

Take a Screenshot

The agent can also call uno_app_get_screenshot to capture what the user actually sees, comparing it against the expected layout.

Phase 4

Phase 4: Fix and Iterate

The first build rarely matches the original WPF screen pixel-for-pixel. The agent spots discrepancies and fixes them iteratively using Hot Reload.

Common Issues the Agent Catches

Missing margins or spacing. The visual tree snapshot shows elements stacking without space. The agent adds Spacing="8" to the StackPanel or adjusts Margin values.

DataGrid not rendering rows. The agent inspects the DataContext and finds the RecentConnections collection is null at startup. It suggests initializing the collection in the ViewModel constructor.

Button alignment off. The screenshot shows buttons left-aligned instead of right-aligned. The agent checks the visual tree and finds a parent Grid column is not sized correctly.

The Hot Reload Loop

With Hot Reload enabled in Uno Platform 6.5, XAML changes apply instantly to the running application:

  1. Agent identifies an issue via visual tree inspection or screenshot
  2. Agent modifies the XAML
  3. Hot Reload applies the change
  4. Agent takes another snapshot or screenshot to verify
  5. Repeat until the screen matches expectations

Each iteration takes seconds, not minutes. The agent can run through five or six fix-and-verify loops in the time it would take you to manually rebuild and visually compare.

Multi-Platform

The Migrated Screen Running on Multiple Platforms

The same XAML and ViewModel code you just migrated runs on every Uno Platform target with no additional platform-specific code.

WebAssembly

Terminal
dotnet run --project MigratedApp.csproj -f net10.0-browserwasm

Open the browser. The same Grid layout, same DataGrid, same buttons. Your field team can now access settings from any browser.

Android

Terminal
dotnet build MigratedApp.csproj -f net10.0-android

The page adapts to the mobile viewport with appropriately sized touch targets.

Cross-Platform Verification with the Agent

The App MCP works across all these targets. Run any build, connect your agent, and have it verify the visual tree. The agent confirms that element hierarchy, data bindings, and control states are consistent across platforms, catching discrepancies that manual testing would take hours to find.

What You Ship

What You Ship

At the end of this process, you have:

  • One fully migrated WPF screen running as an Uno Platform Page
  • Verified on at least two platforms (Windows and WebAssembly, plus Android if you chose to test there)
  • Visual tree inspection confirming correct element hierarchy and data binding
  • A repeatable pattern for migrating the next screen, and the one after that

The ViewModel likely required zero changes. The XAML changes were mechanical and followed the 10 patterns above. Your next step is to pick the second screen and repeat. Each subsequent migration goes faster.

FAQ

FAQ

Can I migrate my entire WPF application at once?

You can, but you should not. Migrating screen by screen lets you validate each piece independently, catch issues early, and keep shipping functional software throughout the process.

What if my WPF screen uses a third-party control library?

Check whether the library offers a WinUI or Uno Platform-compatible version. Many popular control vendors offer WinUI variants. If no equivalent exists, you may need to replace the control with a built-in WinUI control, a Community Toolkit control, or a custom implementation.

Do I need to change my ViewModel at all?

In most cases, no. If your ViewModel uses standard INotifyPropertyChanged, ICommand, and ObservableCollection<T>, it works with Uno Platform without changes. You only need to update namespace references if the ViewModel directly references System.Windows types.

What about converters I wrote for WPF?

Most IValueConverter implementations work after a namespace change from System.Windows.Data to Microsoft.UI.Xaml.Data. Note that Binding.StringFormat is not available, so converters doing string formatting may need updates or replacement with x:Bind function calls.

How do I handle WPF screens that use NavigationService?

Uno Platform supports frame-based navigation through Frame.Navigate(). If your WPF app uses a custom navigation system, consider adopting Uno Platform's Navigation Extensions. Migrate individual pages first, then tackle the navigation framework as a separate step.

Is the Community Toolkit DataGrid feature-complete compared to WPF's DataGrid?

It covers the most common scenarios: text columns, template columns, sorting, and selection. Some advanced features like row details and complex column reordering may require additional work. For most LOB screens, it is a sufficient replacement.

Can the AI agent handle screens with heavy code-behind?

The agent can translate code-behind, but heavy code-behind is a code smell regardless of framework. Consider refactoring code-behind logic into the ViewModel before or during migration.

What if I want to keep my WPF app running alongside the Uno Platform version?

This is a valid incremental strategy. Shared ViewModels, models, and services can live in a .NET 10 class library referenced by both projects. Only the XAML and platform-specific code differs.