XAML Dialects: WPF, WinUI & MAUI

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

"XAML" names a markup syntax, not one framework. The same angle brackets drive three different stacks on Windows: WPF (the mature desktop framework that runs on .NET), WinUI 3 (the native UI layer of the Windows App SDK) and .NET MAUI (the cross-platform framework that renders through WinUI on Windows). They share the object-element, property-element and markup-extension syntax, and a developer who knows one of them reads the other two without trouble. They do not share a type system, a set of controls, a binding engine or a state mechanism, so markup is almost never copy-and-paste portable. This page lines the three dialects up on one example, the book row of Bookshelf Desktop, and then walks through the places where they diverge: namespaces, controls, binding, styles and visual states, resources and triggers. It ends with a porting checklist.

The versions assumed on this page, the same as everywhere in this section:

Item Version and notes

.NET

.NET 10 LTS, latest patch 10.0.13, with C# 14. .NET 11 is at release candidate 2 (11.0.0-rc.2) and general availability is expected at .NET Conf, 10-13 November 2026. .NET 8 and .NET 9 leave support on 2026-11-10.

Visual Studio

Visual Studio 2026, version 18.11.1, with the ".NET desktop development" workload (WPF), the "WinUI application development" workload (WinUI 3) and the ".NET Multi-platform App UI development" workload (.NET MAUI).

WinUI 3

Windows App SDK 2.5.1 (stable channel). From 2.0 on the NuGet package version equals the SDK version.

WPF

Ships with the .NET 10 SDK (UseWPF). The Fluent theme and the short Grid definition syntax arrived in .NET 9 and .NET 10.

.NET MAUI

The .NET MAUI workload for .NET 10. On Windows it runs on WinUI 3 and requires Windows 10 version 1809 or later.

MVVM Toolkit

CommunityToolkit.Mvvm 8.4.2. [ObservableProperty] works on partial properties with C# 14.

Windows

Windows 11 is the primary target. Windows 10 reached end of support on 2025-10-14, but WPF, WinUI 3 and .NET MAUI still run on Windows 10 version 1809 and later within their documented minimums.

The page that introduces XAML itself is XAML Language Fundamentals, and the markup extensions are explained on Markup Extensions & Namescopes. This page assumes both and concentrates on what changes between stacks. The platform comparison (when to pick which stack) is on Windows App Platforms: WPF, WinUI 3, WinForms, MAUI & More. The C# side of the examples (events, lambdas and async) is covered by Delegates, Lambdas and Events and Async and Await.

One Markup Syntax, Three Type Systems

Every XAML file is an XML document whose elements are .NET types and whose attributes are properties or events of those types. What changes between dialects is which types the default namespace resolves to, and therefore which controls, properties, binding engine and resource model are available.

Comparison of the WPF, WinUI 3 and .NET MAUI XAML dialects across type namespace, default xmlns, root element, binding source, compiled bindings, state changes and theme-aware values

The figure condenses the rest of the page. A few facts explain most of the differences:

  • WPF has been on .NET since .NET Core 3.0 and runs only on Windows. Its XAML is compiled to BAML (a binary form) inside the assembly, and its types live in System.Windows.

  • WinUI 3 descends from UWP XAML. The UWP types in Windows.UI.Xaml were re-published as Microsoft.UI.Xaml in the Windows App SDK, so a lot of UWP and WinUI 2 markup ports with a namespace change. Its XAML is compiled to a binary form at build time and it adds the compiled {x:Bind} markup extension.

  • .NET MAUI descends from Xamarin.Forms. Its pages and layouts are abstract (ContentPage, Label, Entry) and are mapped to native controls through handlers, which on Windows are WinUI controls. Its markup is parsed by its own XAML parser and can be compiled into the assembly at build time.

The three dialects keep the general language rules from the XAML specification family: x:Name, x:Key, x:Class, attached property syntax (Grid.Row="1"), property element syntax (<Grid.RowDefinitions>) and content properties all work the same way. The official XAML overview for Windows apps describes the shared concepts for WinUI and UWP, and the .NET MAUI documentation has its own XAML section.

Namespaces and Root Elements

The default namespace URI is the first thing that differs, and the most confusing one, because WPF and WinUI 3 use the same URI while pointing to different assemblies:

Item WPF WinUI 3 .NET MAUI

Default xmlns

http://schemas.microsoft.com/winfx/2006/xaml/presentation

http://schemas.microsoft.com/winfx/2006/xaml/presentation (resolves to Microsoft.UI.Xaml)

http://schemas.microsoft.com/dotnet/2021/maui

xmlns:x

http://schemas.microsoft.com/winfx/2006/xaml

http://schemas.microsoft.com/winfx/2006/xaml

http://schemas.microsoft.com/winfx/2009/xaml

Map your own types

xmlns:vm="clr-namespace:Bookshelf.Core.ViewModels;assembly=Bookshelf.Core"

xmlns:vm="using:Bookshelf.Core.ViewModels"

xmlns:vm="clr-namespace:Bookshelf.Core.ViewModels;assembly=Bookshelf.Core"

Design-time data

d: and mc:Ignorable="d"

d: and mc:Ignorable="d"

none in the classic sense; use the XAML Live Preview of Visual Studio

Typical root

Window, UserControl, Page, ResourceDictionary

Window, Page, UserControl, ResourceDictionary

ContentPage, Shell, ContentView, ResourceDictionary

Code-behind base type

System.Windows.Window and friends

Microsoft.UI.Xaml.Window, Page, …​

Microsoft.Maui.Controls.ContentPage and friends

Three consequences follow:

  1. The same xmlns text means different things in a WPF project and a WinUI project. The build resolves it through the project’s references, so a XAML file copied from one to the other compiles against the wrong types and fails with "type not found" errors, not with a namespace error.

  2. WinUI 3 maps CLR namespaces with using: and does not need an assembly= part. WPF and .NET MAUI use clr-namespace: and need ;assembly=... when the type is in another assembly. In the Bookshelf solution that is the case for every view model, because the shared Bookshelf.Core library holds them.

  3. The x: namespace points to the 2006 URI in WPF and WinUI and to the 2009 URI in .NET MAUI. In WPF, features of the XAML 2009 language are usable only in loose XAML, so a WPF file cannot take the MAUI URI.

Another difference is what the root Window is. In WPF, Window is a ContentControl, so it has resources, styles, a DataContext and a Background. In WinUI 3, Microsoft.UI.Xaml.Window is not a FrameworkElement: it has no DataContext, no Resources, no Style and no Loaded event. The idiom is to keep MainWindow almost empty and put the content in a Page or UserControl that hosts the resources, bindings and visual states. In .NET MAUI the unit is the page (ContentPage), and the native window is created by the framework.

The Shared Core of Bookshelf Desktop

All three UIs below bind to the same view model. The portable part is a plain class library targeting net10.0 that references only CommunityToolkit.Mvvm. That is the strategy this page recommends for a multi-stack solution: share view models, services and models, and write the XAML once per stack.

The types below are a deliberately reduced stand-in for the canonical Bookshelf Desktop types of the MVVM Toolkit and Navigation, Dialogs & Services in MVVM pages. Here Book is a single observable class (so the XAML can bind IsRead directly) and the repository has one search method; in the rest of the section Book is an immutable record, wrapped by a BookViewModel, and IBookRepository has GetAllAsync, GetByIdAsync, AddAsync, UpdateAsync and DeleteAsync. The binding paths (Title, Author, Year) are the same in both forms, which is all this page compares.

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Bookshelf.Core.Models;

public sealed partial class Book : ObservableObject
{
    public required string Title { get; init; }
    public required string Author { get; init; }
    public int Year { get; init; }
    public bool IsAvailable { get; init; } = true;

    [ObservableProperty]
    public partial bool IsRead { get; set; }
}

public interface IBookRepository
{
    Task<IReadOnlyList<Book>> GetBooksAsync(string? search, CancellationToken cancellationToken);
}

public interface IDialogService
{
    Task<bool> ConfirmAsync(string title, string message);
}

namespace Bookshelf.Core.ViewModels;

public sealed partial class BooksViewModel : ObservableObject
{
    private readonly IBookRepository _repository;
    private readonly IDialogService _dialogs;

    public BooksViewModel(IBookRepository repository, IDialogService dialogs)
    {
        _repository = repository;
        _dialogs = dialogs;
        SearchText = string.Empty;
    }

    public ObservableCollection<Book> Books { get; } = [];

    [ObservableProperty]
    public partial string SearchText { get; set; }

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(BorrowSelectedCommand))]
    public partial Book? SelectedBook { get; set; }

    [RelayCommand]
    private async Task LoadAsync(CancellationToken cancellationToken)
    {
        var books = await _repository.GetBooksAsync(SearchText, cancellationToken);
        Books.Clear();
        foreach (var book in books)
        {
            Books.Add(book);
        }
    }

    [RelayCommand(CanExecute = nameof(CanBorrowSelected))]
    private async Task BorrowSelectedAsync()
    {
        if (SelectedBook is { } book
            && await _dialogs.ConfirmAsync("Borrow", $"Borrow \"{book.Title}\"?"))
        {
            book.IsRead = true;
        }
    }

    private bool CanBorrowSelected() => SelectedBook is { IsAvailable: true };

    partial void OnSearchTextChanged(string value) => LoadCommand.Execute(null);
}

The sample has two deliberate properties. It contains no reference to System.Windows, Microsoft.UI.Xaml or Microsoft.Maui, so each UI project consumes it unchanged, and IDialogService is the only seam where each stack supplies its own implementation (MessageBox, ContentDialog or DisplayAlert). Neither the MVVM Toolkit nor the WPF documentation provides an official dialog service or a navigation service; these interfaces are a pattern used throughout this section, not a framework API. IBookRepository is implemented over EF Core and SQLite on another page.

Controls and Properties That Change Name

The three stacks cover the same needs with different type names, and sometimes different property names for the same idea. The table lists the ones met on the book screen:

Concept WPF WinUI 3 .NET MAUI

Static text

TextBlock (Text, Foreground, FontWeight)

TextBlock (Text, Foreground, FontWeight)

Label (Text, TextColor, FontAttributes)

Single-line input

TextBox

TextBox (PlaceholderText)

Entry (Placeholder); SearchBar for search

Button

Button (Click, Command)

Button (Click, Command)

Button (Clicked, Command)

Check box

CheckBox (IsChecked, bool?)

CheckBox (IsChecked, bool?)

CheckBox (IsChecked, bool)

List of items

ListBox, ListView, DataGrid, ItemsControl

ListView, GridView, ItemsView, ItemsRepeater; no DataGrid in the box

CollectionView (the older ListView still exists)

Selected item

SelectedItem, SelectedValue

SelectedItem, SelectedValue

SelectedItem, SelectionMode

Vertical stack

StackPanel

StackPanel

VerticalStackLayout, HorizontalStackLayout

Grid

Grid (RowDefinitions as a string since .NET 10)

Grid (RowSpacing, ColumnSpacing; definitions as elements)

Grid (RowDefinitions="Auto,*" as a string)

Visibility

Visibility (Visible, Hidden, Collapsed)

Visibility (Visible, Collapsed)

IsVisible (bool)

Alignment

HorizontalAlignment, VerticalAlignment

HorizontalAlignment, VerticalAlignment

HorizontalOptions, VerticalOptions

Spacing inside a stack

Margin on children

Spacing on StackPanel

Spacing on stack layouts

Page title

Window.Title

Window.Title (not on Page)

ContentPage.Title

Accessible name

AutomationProperties.Name

AutomationProperties.Name

SemanticProperties.Description

Parent-child content

Content of a ContentControl

Content of a ContentControl

Content of a ContentPage or ContentView

WPF and WinUI 3 look alike because WinUI inherited the UWP control model. MAUI uses its own vocabulary because its controls must map to iOS, Android and macOS as well. The practical result is that porting a WPF view to WinUI 3 is mostly a renaming and replacement job, while porting to MAUI is a rewrite of the markup on top of the same view model.

Other vocabulary differences are worth knowing before they cause a build error:

  • WPF has implicit data templates: a DataTemplate with only a DataType is applied automatically to any content of that type. WinUI 3 and MAUI have no such lookup; the template is set explicitly or chosen by a DataTemplateSelector.

  • A Brush in WPF and WinUI is a SolidColorBrush, LinearGradientBrush and so on. MAUI properties such as TextColor and BackgroundColor take a Color, and Background takes a Brush.

  • MAUI Margin and Padding accept Thickness strings with the same 1, 2 or 4 number forms as WPF and WinUI.

  • Standard dialogs differ: MessageBox and common dialogs in WPF, ContentDialog (which needs a XamlRoot) in WinUI 3, and DisplayAlertAsync or DisplayAlert on the page in MAUI.

The Same Book Row in Three Dialects

The book screen has a search box, a list and, in each row, the title, the author, the year, an availability label and a "read" check box. The three listings below bind to the view model shown above, and each is a complete view.

WPF

<UserControl x:Class="Bookshelf.Wpf.Views.BooksView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:models="clr-namespace:Bookshelf.Core.Models;assembly=Bookshelf.Core"
             xmlns:vm="clr-namespace:Bookshelf.Core.ViewModels;assembly=Bookshelf.Core"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance Type=vm:BooksViewModel}">
  <UserControl.Resources>
    <BooleanToVisibilityConverter x:Key="BoolToVisibility" />
    <DataTemplate x:Key="BookRowTemplate" DataType="{x:Type models:Book}">
      <Grid Margin="8,6" ColumnDefinitions="*, Auto">
        <StackPanel>
          <TextBlock Text="{Binding Title}" FontWeight="SemiBold" />
          <TextBlock Text="{Binding Author}" Opacity="0.7" />
          <TextBlock Text="{Binding Year, StringFormat=Published {0}}" Opacity="0.7" />
        </StackPanel>
        <StackPanel Grid.Column="1" VerticalAlignment="Center">
          <TextBlock Text="Available"
                     Visibility="{Binding IsAvailable, Converter={StaticResource BoolToVisibility}}" />
          <CheckBox Content="Read" IsChecked="{Binding IsRead, Mode=TwoWay}" />
        </StackPanel>
      </Grid>
    </DataTemplate>
  </UserControl.Resources>

  <Grid Margin="12" RowDefinitions="Auto, *">
    <TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
             AutomationProperties.Name="Search books" Margin="0,0,0,8" />
    <ListBox Grid.Row="1"
             ItemsSource="{Binding Books}"
             SelectedItem="{Binding SelectedBook, Mode=TwoWay}"
             ItemTemplate="{StaticResource BookRowTemplate}" />
  </Grid>
</UserControl>
using System.Windows.Controls;
using Bookshelf.Core.ViewModels;

namespace Bookshelf.Wpf.Views;

public partial class BooksView : UserControl
{
    // The view model is resolved by the Generic Host and handed in; there is no ViewModelLocator in the toolkit.
    public BooksView(BooksViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }
}

What is specific to WPF in this listing:

  • {Binding} reads from the inherited DataContext. The binding is resolved at run time by reflection and INotifyPropertyChanged.

  • The Grid uses the short definition syntax (ColumnDefinitions="*, Auto") introduced in .NET 10.

  • UpdateSourceTrigger=PropertyChanged is needed on the TextBox because its Text property updates the source on LostFocus by default.

  • BooleanToVisibilityConverter ships with WPF. Visibility also has a Hidden value that keeps the layout space.

  • d:DataContext with d:DesignInstance gives the designer a typed context and is removed from the compiled output by mc:Ignorable.

WinUI 3

<Page
    x:Class="Bookshelf.WinUI.Views.BooksPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:models="using:Bookshelf.Core.Models"
    mc:Ignorable="d">

  <Page.Resources>
    <DataTemplate x:Key="BookRowTemplate" x:DataType="models:Book">
      <Grid Padding="8,6" ColumnSpacing="12">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackPanel>
          <TextBlock Text="{x:Bind Title}" Style="{StaticResource BodyStrongTextBlockStyle}" />
          <TextBlock Text="{x:Bind Author}"
                     Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
          <TextBlock Text="{x:Bind Year}"
                     Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
        </StackPanel>
        <StackPanel Grid.Column="1" VerticalAlignment="Center">
          <TextBlock Text="Available" Visibility="{x:Bind IsAvailable}" />
          <CheckBox Content="Read" IsChecked="{x:Bind IsRead, Mode=TwoWay}" />
        </StackPanel>
      </Grid>
    </DataTemplate>
  </Page.Resources>

  <Grid Padding="12" RowSpacing="8">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBox PlaceholderText="Search books"
             Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             AutomationProperties.Name="Search books" />
    <ListView Grid.Row="1"
              ItemsSource="{x:Bind ViewModel.Books}"
              SelectedItem="{x:Bind ViewModel.SelectedBook, Mode=TwoWay}"
              ItemTemplate="{StaticResource BookRowTemplate}" />
  </Grid>
</Page>
using Bookshelf.Core.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls;

namespace Bookshelf.WinUI.Views;

public sealed partial class BooksPage : Page
{
    // x:Bind resolves "ViewModel.SearchText" against this class, so the view model must be a property here.
    public BooksViewModel ViewModel { get; }

    public BooksPage()
    {
        // App.Services is the host service provider exposed by the sample application class.
        ViewModel = App.Services.GetRequiredService<BooksViewModel>();
        InitializeComponent();
    }
}

What is specific to WinUI 3 in this listing:

  • The default namespace URI is the same as in WPF, but the types are Microsoft.UI.Xaml types; the custom namespace uses using:.

  • {x:Bind Title} has no DataContext: inside the DataTemplate the path starts at the type named by x:DataType (Book), and outside it the path starts at the BooksPage class, which is why ViewModel is a property in the code-behind.

  • Every x:Bind here is OneTime except the ones that declare a mode. IsAvailable is read-only, Title, Author and Year have init setters, so one-time evaluation is exactly right; IsRead needs TwoWay.

  • x:Bind converts a bool to a Visibility without a converter, so the WPF BooleanToVisibilityConverter has no counterpart. Binding in WinUI 3 still needs a converter for the same conversion.

  • Theme-aware colors come from {ThemeResource} keys defined by the WinUI control resources (XamlControlsResources, merged in App.xaml).

  • There is no implicit DataTemplate lookup, so the template is assigned through ItemTemplate.

.NET MAUI

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="Bookshelf.Maui.Views.BooksPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:Bookshelf.Core.Models;assembly=Bookshelf.Core"
             xmlns:vm="clr-namespace:Bookshelf.Core.ViewModels;assembly=Bookshelf.Core"
             x:DataType="vm:BooksViewModel"
             Title="Bookshelf">

  <Grid Padding="12" RowDefinitions="Auto,*" RowSpacing="8">
    <SearchBar Placeholder="Search books"
               Text="{Binding SearchText}"
               SemanticProperties.Description="Search books" />
    <CollectionView Grid.Row="1"
                    ItemsSource="{Binding Books}"
                    SelectionMode="Single"
                    SelectedItem="{Binding SelectedBook}">
      <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Book">
          <Grid Padding="8,6" ColumnDefinitions="*,Auto" ColumnSpacing="12">
            <VerticalStackLayout>
              <Label Text="{Binding Title}" FontAttributes="Bold" />
              <Label Text="{Binding Author}" Opacity="0.7" />
              <Label Text="{Binding Year, StringFormat='Published {0}'}" Opacity="0.7" />
            </VerticalStackLayout>
            <VerticalStackLayout Grid.Column="1" VerticalOptions="Center">
              <Label Text="Available" IsVisible="{Binding IsAvailable}" />
              <HorizontalStackLayout>
                <CheckBox IsChecked="{Binding IsRead}" />
                <Label Text="Read" VerticalOptions="Center" />
              </HorizontalStackLayout>
            </VerticalStackLayout>
          </Grid>
        </DataTemplate>
      </CollectionView.ItemTemplate>
    </CollectionView>
  </Grid>
</ContentPage>
using Bookshelf.Core.ViewModels;

namespace Bookshelf.Maui.Views;

public partial class BooksPage : ContentPage
{
    public BooksPage(BooksViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

Source: .NET MAUI XAML

What is specific to .NET MAUI in this listing:

  • The root has a Title, and the page is registered with the MauiAppBuilder services so the constructor injection works. The binding source is the BindingContext, not a DataContext.

  • x:DataType="vm:BooksViewModel" on the page and x:DataType="models:Book" on the template turn every {Binding} below them into a compiled binding (see the next section).

  • Entry.Text, SearchBar.Text, CheckBox.IsChecked and CollectionView.SelectedItem bind two-way by default, so no Mode is written. MAUI has no UpdateSourceTrigger: those controls push changes as the user edits.

  • IsVisible is a bool, so no converter is needed for availability.

  • CheckBox has no Content, so the label is a separate Label. Layouts use Spacing and Padding instead of child margins by convention.

  • Colors and text styles would come from the Resources/Styles dictionaries generated by the project template.

Binding: Binding, x:Bind and Compiled Bindings

All three stacks use the same data-binding idea: a target property on a UI element reads (and sometimes writes) a source property, and INotifyPropertyChanged or ObservableCollection<T> tell the binding when to refresh. How the binding is declared, resolved and compiled is where the dialects differ.

flowchart TD A[Binding declared in XAML] --> B{Which stack?} B -->|WPF| C[Binding] B -->|WinUI 3| D{Which markup extension?} B -->|.NET MAUI| E{x:DataType in scope?} C --> F[Path resolved at run time against DataContext] D -->|Binding| F D -->|x:Bind| G[Path compiled against the page class or the x:DataType of the template] E -->|yes| H[Compiled binding generated at build time against BindingContext type] E -->|no| I[Path resolved at run time by reflection against BindingContext]
Aspect WPF {Binding} WinUI 3 {x:Bind} .NET MAUI {Binding} with x:DataType

Resolution

Run time, reflection

Build time, generated code

Build time when x:DataType is in scope, otherwise run time

Source of the path

DataContext, or Source, ElementName, RelativeSource

The page, user control or window class; in a template, the x:DataType type

BindingContext, or Source, x:Reference, RelativeSource

Default mode

OneWay, or TwoWay for properties that bind two-way by default (such as TextBox.Text)

OneTime

OneWay, or TwoWay for properties that bind two-way by default (such as Entry.Text)

Type checking

None; errors appear in the output window at run time

Compile errors for wrong paths and types

Compile errors for wrong paths when compiled; none otherwise

Event binding

No; use commands or behaviors

Yes, Click="{x:Bind ViewModel.Save}" binds an event to a method

No; use commands

Function binding

No; use converters

Yes, {x:Bind local:Format.ByLine(Author, Year)}

No; use converters and StringFormat

UpdateSourceTrigger

PropertyChanged, LostFocus, Explicit

PropertyChanged, LostFocus, Explicit

Not available

Debugging

Binding errors in the Output window, PresentationTraceSources

Step into the generated .g.cs code

Compile-time diagnostics for compiled bindings

Binding in WPF and WinUI 3

{Binding} exists in WPF and WinUI 3 with similar properties (Path, Mode, Converter, ConverterParameter, FallbackValue, TargetNullValue, ElementName, Source, StringFormat, RelativeSource). Differences to remember when porting:

  • WPF supports MultiBinding and IMultiValueConverter; WinUI 3 has no MultiBinding. Use function binding with x:Bind, or compute the value in the view model.

  • WPF IValueConverter.Convert receives a CultureInfo; the WinUI interface receives a language string, and it lives in Microsoft.UI.Xaml.Data.

  • WPF has IsAsync, binding validation rules and BindingGroup; WinUI 3 has no equivalent of the last two.

  • Binding in WinUI 3 is still available and is the way to use ElementName and RelativeSource in a template that must reach the page’s data context.

x:Bind in Detail

{x:Bind} exists only in WinUI (and UWP). WPF does not have it, and .NET MAUI does not use that syntax. It differs from {Binding} in three ways that matter every day:

  1. It is compiled and strongly typed. The XAML compiler turns each {x:Bind} into generated C# code, which reads the property directly and subscribes to change notifications. A wrong property name is a build error.

  2. It resolves against the page, not the DataContext. Outside a DataTemplate, the path is relative to the page, user control or window class that owns the markup, so code-behind members (including a ViewModel property) are directly bindable. Inside a DataTemplate, it is relative to the type given in x:DataType.

  3. Its default mode is OneTime. The generated code reads the value once, when the page loads. For updates, write Mode=OneWay or Mode=TwoWay and implement INotifyPropertyChanged on the source.

<Page
    x:Class="Bookshelf.WinUI.Views.BookDetailsPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Bookshelf.WinUI">

  <StackPanel Spacing="8" Padding="16">
    <!-- OneTime (the default): evaluated when the page loads -->
    <TextBlock Text="{x:Bind ViewModel.SelectedBook.Title}" />

    <!-- OneWay: refreshed when SelectedBook or its Title raise PropertyChanged -->
    <TextBlock Text="{x:Bind ViewModel.SelectedBook.Author, Mode=OneWay}" />

    <!-- TwoWay with a fallback for a null chain -->
    <TextBox Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, FallbackValue='(none)'}" />

    <!-- Function binding: a static method evaluated with bound arguments -->
    <TextBlock Text="{x:Bind local:Formatting.ByLine(ViewModel.SelectedBook.Author, ViewModel.SelectedBook.Year), Mode=OneWay}" />

    <!-- Binding to an element by name: the element is a field of the page -->
    <TextBlock Text="{x:Bind SearchBox.Text, Mode=OneWay}" />
    <TextBox x:Name="SearchBox" />
  </StackPanel>
</Page>
namespace Bookshelf.WinUI;

public static class Formatting
{
    public static string ByLine(string? author, int year) =>
        author is null ? string.Empty : $"{author}, {year}";
}

Points about the listing:

  • x:Bind can also bind an event such as Click directly to a method of the page or of the view model, and the x:Bind page lists which method signatures are accepted. This page does not use it, because the toolkit exposes commands, which bind with Command.

  • ElementName is not needed: because the elements named with x:Name are fields of the page, they can be addressed directly in the path.

  • RelativeSource is not supported by x:Bind; the parent is reached through the page.

  • Bindings.Update() forces a refresh of OneTime and OneWay bindings, and Bindings.StopTracking() releases them. x:Phase lets large lists render progressively. x:Load (deferred loading) uses the same compiled model.

The other consequence of resolving against the page is that code inside a DataTemplate cannot see the page. A Button in a book row that must call BooksViewModel.BorrowSelected has three options in WinUI 3: put the command on the item (a row view model), bind the event to a method of the item type, or use {Binding} with ElementName to reach the page’s DataContext. In WPF the same case is written with {Binding DataContext.BorrowSelectedCommand, RelativeSource={RelativeSource AncestorType=ListBox}}.

Compiled Bindings in .NET MAUI

NET MAUI does not have {x:Bind}. It compiles ordinary {Binding} expressions when it knows the type of the

binding source, which you declare with the x:DataType attribute. The XAML compiler then checks each path against that type and replaces reflection with generated accessors.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:Bookshelf.Core.Models;assembly=Bookshelf.Core"
             xmlns:vm="clr-namespace:Bookshelf.Core.ViewModels;assembly=Bookshelf.Core"
             x:Class="Bookshelf.Maui.Views.BookDetailsPage"
             x:DataType="vm:BooksViewModel">

  <VerticalStackLayout Padding="16" Spacing="8">
    <!-- Compiled: the path is checked against BooksViewModel at build time -->
    <Label Text="{Binding SelectedBook.Title}" />

    <!-- A template that binds to another type redeclares x:DataType -->
    <VerticalStackLayout BindableLayout.ItemsSource="{Binding Books}">
      <BindableLayout.ItemTemplate>
        <DataTemplate x:DataType="models:Book">
          <Label Text="{Binding Title}" />
        </DataTemplate>
      </BindableLayout.ItemTemplate>
    </VerticalStackLayout>

    <!-- Turn compilation off for one element that uses a dynamic source -->
    <Label x:DataType="{x:Null}" Text="{Binding DynamicValue}" />

    <!-- Reaching the page view model from inside a template -->
    <Button Text="Borrow"
            Command="{Binding Source={RelativeSource AncestorType={x:Type vm:BooksViewModel}}, Path=BorrowSelectedCommand}" />
  </VerticalStackLayout>
</ContentPage>

Rules that follow from the documented model:

  • x:DataType applies to the element and all of its descendants. It is the way to switch on compiled bindings for a whole page, and every DataTemplate must restate it with the item type because a template does not inherit the page’s data type.

  • x:DataType="{x:Null}" switches compilation off for a subtree, for example where the source is a dictionary or a type decided at run time.

  • A Source or RelativeSource changes the binding source type, so the compiled check applies to that source and not to the page’s x:DataType. The compiler needs to know the type of the RelativeSource ancestor.

  • Without x:DataType the binding still works, through reflection, and the build can report warnings suggesting you add it. Treating those warnings as errors is a reasonable way to keep every binding compiled.

  • A compile-time failure appears for a path that does not exist on the declared type, which is the MAUI counterpart of the compile error WinUI 3 gives for a wrong {x:Bind} path.

Task Write in WinUI 3 Write in .NET MAUI

Show a property once

{x:Bind Title}

{Binding Title, Mode=OneTime}

Keep a property in sync

{x:Bind Title, Mode=OneWay}

{Binding Title} (default OneWay)

Two-way input

{x:Bind SearchText, Mode=TwoWay}

{Binding SearchText} on Entry.Text

Reach the page from a template

not possible with x:Bind; use {Binding ElementName=...}

RelativeSource AncestorType or Source={x:Reference page}

Choose the source type

the page class or x:DataType

x:DataType on any ancestor

Neither the x:Bind page nor the MAUI compiled bindings page describes WPF, and the WPF documentation has no equivalent of either: in WPF, run-time {Binding} is the only binding syntax in XAML. The MVVM source generators of the toolkit reduce boilerplate in all three stacks without changing this.

Styling and Visual States

All three stacks use Style with TargetType and Setter elements, implicit styles keyed by type, and BasedOn inheritance. They diverge in how a style reacts to state.

Aspect WPF WinUI 3 .NET MAUI

Style with TargetType

Yes; an implicit style applies to every element of the type in scope

Yes; implicit styles apply to every element of that exact type in scope

Yes; implicit styles apply to the type in scope, and a Class plus StyleClass offers named style groups

BasedOn

Yes

Yes

Yes

State inside a style

Style.Triggers: Trigger, DataTrigger, MultiTrigger, MultiDataTrigger, EventTrigger

None; use the VisualStateManager in the control template or the page

Triggers on elements, and VisualStateManager.VisualStateGroups set in a style

Control templates

ControlTemplate with Triggers; VisualStateManager also available

ControlTemplate with VisualStateManager and VisualTransition

ControlTemplate only on a few templated types such as ContentPage, ContentView and TemplatedView; most controls are not re-templatable

Responsive layout

SizeChanged handlers and code

AdaptiveTrigger in VisualState.StateTriggers

OnIdiom, OnPlatform and state triggers such as OrientationStateTrigger

Light and dark values

DynamicResource, ThemeMode (Fluent)

ThemeResource with ThemeDictionaries

AppThemeBinding

The control-template difference is the largest one: WPF and WinUI 3 let an application replace the visuals of nearly every control, while a MAUI control is a thin wrapper over a native control and is customized through its properties, handlers or ControlTemplate on the few templated controls.

WPF: Styles With Triggers

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Style x:Key="BookTitleStyle" TargetType="TextBlock">
    <Setter Property="FontSize" Value="16" />
    <Setter Property="FontWeight" Value="SemiBold" />
  </Style>

  <Style TargetType="ListBoxItem">
    <Setter Property="Padding" Value="8,6" />
    <Style.Triggers>
      <!-- A data trigger reads a property of the item, no code-behind needed -->
      <DataTrigger Binding="{Binding IsAvailable}" Value="False">
        <Setter Property="Opacity" Value="0.55" />
      </DataTrigger>
      <!-- A property trigger reacts to a dependency property of the element itself -->
      <Trigger Property="IsMouseOver" Value="True">
        <Setter Property="Background" Value="{DynamicResource BookHoverBrush}" />
      </Trigger>
      <!-- Several conditions at once -->
      <MultiTrigger>
        <MultiTrigger.Conditions>
          <Condition Property="IsSelected" Value="True" />
          <Condition Property="IsMouseOver" Value="True" />
        </MultiTrigger.Conditions>
        <Setter Property="Background" Value="{DynamicResource BookSelectedHoverBrush}" />
      </MultiTrigger>
    </Style.Triggers>
  </Style>
</ResourceDictionary>

WinUI 3: Styles With Visual States

<Page
    x:Class="Bookshelf.WinUI.Views.BooksShellPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <Page.Resources>
    <Style x:Key="BookTitleStyle" TargetType="TextBlock"
           BasedOn="{StaticResource BodyStrongTextBlockStyle}">
      <Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
    </Style>
  </Page.Resources>

  <Grid x:Name="RootGrid">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition x:Name="DetailsColumn" Width="0" />
    </Grid.ColumnDefinitions>

    <ListView x:Name="BookList" />
    <StackPanel x:Name="DetailsPane" Grid.Column="1" Visibility="Collapsed" />

    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="WidthStates">
        <VisualState x:Name="Wide">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowWidth="900" />
          </VisualState.StateTriggers>
          <VisualState.Setters>
            <Setter Target="DetailsPane.Visibility" Value="Visible" />
            <Setter Target="DetailsColumn.Width" Value="360" />
          </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Narrow">
          <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowWidth="0" />
          </VisualState.StateTriggers>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </Grid>
</Page>

WinUI 3 has no Style.Triggers and no DataTrigger. The VisualStateManager replaces them: a VisualState declares Setters (and optionally a Storyboard) that apply while a state is active, and StateTriggers activate the state. AdaptiveTrigger reacts to window size; StateTrigger has an IsActive property that can be bound, which is the closest analogue to a WPF DataTrigger. A custom StateTriggerBase subclass can drive a state from your own logic. Target in a setter takes an ElementName.Property path and, unlike a Style setter, can reach any named element on the page.

.NET MAUI: Style Classes, Visual States and Triggers

<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

  <Style x:Key="BookTitleStyle" TargetType="Label">
    <Setter Property="FontSize" Value="16" />
    <Setter Property="FontAttributes" Value="Bold" />
  </Style>

  <!-- A style class is applied with StyleClass="Caption" and several can be combined -->
  <Style TargetType="Label" Class="Caption">
    <Setter Property="Opacity" Value="0.7" />
    <Setter Property="FontSize" Value="12" />
  </Style>

  <!-- Visual states inside a style: the selected row gets a themed background -->
  <Style TargetType="Grid">
    <Setter Property="VisualStateManager.VisualStateGroups">
      <VisualStateGroupList>
        <VisualStateGroup x:Name="CommonStates">
          <VisualState x:Name="Normal" />
          <VisualState x:Name="Selected">
            <VisualState.Setters>
              <Setter Property="BackgroundColor"
                      Value="{AppThemeBinding Light=#DCE9F7, Dark=#2B3F55}" />
            </VisualState.Setters>
          </VisualState>
        </VisualStateGroup>
      </VisualStateGroupList>
    </Setter>
  </Style>
</ResourceDictionary>

Source: .NET MAUI XAML

MAUI visual states use VisualStateManager with groups such as CommonStates, whose Normal, Selected, Disabled and Focused states are set by the control. State triggers (AdaptiveTrigger, OrientationStateTrigger, DeviceStateTrigger, CompareStateTrigger) can activate a custom state, and the Triggers collection of an element supports Trigger, DataTrigger, MultiTrigger and EventTrigger as described below.

Resources and Theming

ResourceDictionary exists in all three stacks, with x:Key, merged dictionaries and a lookup that walks from the element up through its parents to the application. The differences are in the markup extensions and in how themes are expressed.

Aspect WPF WinUI 3 .NET MAUI

Static lookup

{StaticResource Key}, resolved once when parsed

{StaticResource Key}

{StaticResource Key}

Dynamic lookup

{DynamicResource Key}, re-evaluated when the resource changes

{ThemeResource Key} re-evaluates on a theme change; no DynamicResource

{DynamicResource Key} for runtime changes, {AppThemeBinding} for light and dark

Merged dictionaries

ResourceDictionary.MergedDictionaries

ResourceDictionary.MergedDictionaries

ResourceDictionary.MergedDictionaries

Per-theme values

Fluent theme dictionary, ThemeMode on Application (experimental, diagnostic WPF0001)

ResourceDictionary.ThemeDictionaries with Light, Dark and HighContrast keys

AppThemeBinding Light=..., Dark=...

File URIs

Pack URIs (pack://application:,,,/Assembly;component/Themes/Colors.xaml), relative paths

ms-appx:///Styles/Colors.xaml package URIs

Relative paths such as Resources/Styles/Colors.xaml

Control look

Per-control templates in the theme dictionary; Fluent (opt-in since .NET 9)

Fluent by default through XamlControlsResources

Native look per platform; shared Styles.xaml from the project template

<!-- WPF App.xaml -->
<Application x:Class="Bookshelf.Wpf.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             ThemeMode="System">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Themes/BookColors.xaml" />
        <ResourceDictionary Source="Themes/BookStyles.xaml" />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>
<!-- WinUI 3 App.xaml -->
<Application x:Class="Bookshelf.WinUI.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
        <ResourceDictionary Source="ms-appx:///Styles/BookStyles.xaml" />
      </ResourceDictionary.MergedDictionaries>
      <ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary x:Key="Light">
          <SolidColorBrush x:Key="BookAccentBrush" Color="#1C4B73" />
        </ResourceDictionary>
        <ResourceDictionary x:Key="Dark">
          <SolidColorBrush x:Key="BookAccentBrush" Color="#8DB8E0" />
        </ResourceDictionary>
      </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>
<!-- .NET MAUI App.xaml -->
<Application x:Class="Bookshelf.Maui.App"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
        <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
      </ResourceDictionary.MergedDictionaries>
      <Color x:Key="BookAccentLight">#1C4B73</Color>
      <Color x:Key="BookAccentDark">#8DB8E0</Color>
      <Style x:Key="BookAccentLabel" TargetType="Label">
        <Setter Property="TextColor"
                Value="{AppThemeBinding Light={StaticResource BookAccentLight}, Dark={StaticResource BookAccentDark}}" />
      </Style>
    </ResourceDictionary>
  </Application.Resources>
</Application>

The WPF ThemeMode="System" attribute is the Fluent theme opt-in introduced in .NET 9 and extended in .NET 10. Its API is marked experimental: code that sets ThemeMode raises the diagnostic WPF0001 as an error, which has to be suppressed with NoWarn in the project file or a #pragma around the assignment (the Themes and Fluent Design page shows both). The documentation for it is in the what’s new pages for .NET 9 and .NET 10 only; there is no dedicated Fluent theming guide for WPF. Without ThemeMode, WPF keeps the classic Aero2 look.

Triggers: Where They Exist

The outline of this section calls triggers "WPF only", and that statement needs a precise reading. Only WPF has the full trigger model that lives in styles, control templates and data templates and can start storyboards. WinUI 3 has no triggers in that sense. MAUI has a smaller family of triggers that apply to a single element.

Trigger WPF WinUI 3 .NET MAUI

Property trigger (Trigger)

Yes, in Style.Triggers, ControlTemplate.Triggers, DataTemplate.Triggers

No

Yes, in the Triggers collection of an element (not inside control templates)

Data trigger (DataTrigger)

Yes

No; bind StateTrigger.IsActive or use x:Bind to a computed property

Yes, TargetType is required

Multi-condition

MultiTrigger, MultiDataTrigger

Several state triggers on one VisualState

MultiTrigger

Event trigger

EventTrigger starts a Storyboard (routed events only)

No; use Storyboard in code or behaviors

EventTrigger runs a TriggerAction written in C#

State and adaptive triggers

No; WPF has VisualStateManager but no StateTriggers or AdaptiveTrigger, so states are switched from code with VisualStateManager.GoToState or GoToElementState

Yes, StateTrigger, AdaptiveTrigger, custom StateTriggerBase

Yes, StateTrigger, AdaptiveTrigger, OrientationStateTrigger, CompareStateTrigger, DeviceStateTrigger

<DataTemplate x:DataType="models:Book">
  <Label Text="{Binding Title}">
    <Label.Triggers>
      <DataTrigger TargetType="Label" Binding="{Binding IsAvailable}" Value="False">
        <Setter Property="Opacity" Value="0.55" />
        <Setter Property="TextDecorations" Value="Strikethrough" />
      </DataTrigger>
    </Label.Triggers>
  </Label>
</DataTemplate>

Source: .NET MAUI XAML

The XAML Behaviors package (Microsoft.Xaml.Behaviors.Wpf, with a sibling for WinUI) gives WPF and WinUI projects declarative Interaction.Triggers and Actions such as InvokeCommandAction. That is a separate, Microsoft-hosted package, not part of any of the three frameworks, and it is covered on the page about dependency injection and the Generic Host.

Porting Markup Between Stacks

Moving a screen from one stack to another is two separate tasks: the code-behind and the view model, which are C#, and the markup, which is not portable. Treat the view model as shared, the XAML as rewritten, and port in this order.

A Porting Checklist

Step WPF to WinUI 3 WPF to .NET MAUI WinUI 3 to .NET MAUI

1. Namespaces

clr-namespace:X;assembly=Y becomes using:X; the default xmlns stays

Replace the default xmlns with the MAUI URI; x: takes the 2009 URI

Replace the default xmlns; using: becomes clr-namespace:

2. Root element

Window content moves to a Page or UserControl

Window becomes ContentPage

Page becomes ContentPage

3. Layout and controls

Mostly the same names; DataGrid and Ribbon need community or third-party replacements

StackPanel to stack layouts, ListBox to CollectionView, TextBlock to Label

Same table as in the controls section

4. Binding source

Add a typed ViewModel property and switch hot paths to x:Bind; keep Binding where needed

DataContext becomes BindingContext; add x:DataType

x:Bind becomes Binding; add x:DataType

5. Converters

IValueConverter signature changes (language string instead of CultureInfo)

Drop converters for bool and use IsVisible, or keep MAUI converters

Re-implement against the MAUI interface

6. Triggers

Style.Triggers and ControlTemplate.Triggers become VisualStateManager setters

DataTrigger and Trigger map to MAUI triggers; templates trigger logic must move

VisualState.StateTriggers map to MAUI state triggers

7. Resources

DynamicResource becomes ThemeResource or a normal StaticResource; pack URIs become ms-appx

DynamicResource stays; colors become Color resources; file URIs become relative

ThemeDictionaries become AppThemeBinding

8. Events

MouseLeftButtonDown becomes PointerPressed or Tapped

Click becomes Clicked; gestures become gesture recognizers

Tapped becomes a TapGestureRecognizer

9. Threading

Dispatcher becomes DispatcherQueue (TryEnqueue)

Dispatcher becomes MainThread.BeginInvokeOnMainThread or Dispatcher.Dispatch

DispatcherQueue becomes the MAUI dispatcher

10. Dialogs and windows

MessageBox becomes ContentDialog with XamlRoot

MessageBox becomes a page alert or popup service

ContentDialog becomes a page alert

11. Resources and strings

.resx becomes .resw with x:Uid

.resx stays; localization is code based

.resw becomes .resx

Strategy for a Multi-Stack Solution

  1. Move the logic first. Put models, view models, services and tests in a net10.0 library with no UI dependency, as in Bookshelf.Core. CommunityToolkit.Mvvm works in WPF, WinUI 3 and .NET MAUI projects alike.

  2. Keep each stack’s XAML native. Do not look for an abstraction layer that generates three dialects from one file; the differences listed above are exactly the places it would break.

  3. Port screen by screen. Rebuild one view in the target stack, bind it to the same view model, and compare behaviors before moving the next one.

  4. Mind the host. WPF and WinUI 3 apps are unpackaged or packaged desktop apps; a .NET MAUI app on Windows is packaged by default. The packaging and distribution pages cover the consequences.

  5. Consider hosting instead of porting. The Windows App SDK and the interop pages of this section cover embedding WinUI 3 content in a WPF app and the reverse. Official documentation does not provide wrapper controls for hosting WinUI 3 in WPF through XAML Islands, so this path needs more code than the main ones.

  6. Use the official migration guidance. The migration decision guide of the Windows App SDK documentation and the WPF upgrade pages cover moving from .NET Framework and from UWP.

Third-party frameworks such as Avalonia and Uno Platform are also XAML based and promise one markup file across platforms. They are third-party projects, not part of Microsoft’s stack, with their own XAML dialects and namespaces. This page does not teach them; evaluate them as separate products if one markup file across operating systems is a hard requirement.

From .NET Framework to .NET 8, 9 and 10

The XAML itself rarely changes when a WPF app moves from .NET Framework to modern .NET; the project file does.

Area .NET Framework .NET 8, 9 and 10

Project

Old-style .csproj with explicit file lists

SDK-style project with UseWPF and net8.0-windows, net9.0-windows or net10.0-windows

Binding

Same run-time {Binding}

Same; new tooling and the toolkit source generators

Theme

Aero2 and classic themes

Fluent theme opt-in with ThemeMode (.NET 9, extended in .NET 10)

Grid definitions

Elements only

String shorthand such as ColumnDefinitions="*, Auto" in .NET 10

Trimming and Native AOT

Not applicable

Not supported for WPF (the SDK reports NETSDK1168); WinUI 3 can use PublishAot

What the Official Documentation Does Not Provide

Be careful with these gaps when planning a port, and do not assume the documentation covers them:

  • There is no single official page that compares WPF, WinUI 3 and .NET MAUI XAML side by side. The comparison on this page is assembled from the XAML overview for Windows apps, the x:Bind page, the .NET MAUI XAML section, the .NET MAUI compiled-bindings page and the WPF reference pages.

  • There is no official XAML conversion tool between the stacks. The migration pages for WPF and the Windows App SDK describe the decisions and API differences, but they do not translate markup automatically.

  • The documentation has no WPF Fluent theming deep dive, only the what’s new pages for .NET 9 and .NET 10, and the theme API is experimental (WPF0001 when ThemeMode is set from code).

  • WPF has no x:Bind, and no official MVVM guidance specific to WPF. The WinUI data-binding and MVVM pages and the .NET MAUI enterprise application patterns book chapters are the closest official material.

  • Whether .NET MAUI will add its own compiled-binding markup beyond x:DataType is not something this page can state; read the .NET MAUI release notes for the version you target.

Pitfalls

  • Same URI, different types. Copying WPF XAML into a WinUI project compiles against Microsoft.UI.Xaml. Expect errors for Triggers, DataGrid, Hidden, MultiBinding and DynamicResource.

  • x:Bind defaults to OneTime. A label that never updates in WinUI 3 is almost always a missing Mode=OneWay. In WPF and MAUI the same binding updates, because those engines default to OneWay.

  • Pages are not DataContext holders. In WinUI 3, Window has no DataContext, so {Binding} on a window element has nothing to bind to; use a Page or x:Bind with a property.

  • Templates and the data type. MAUI DataTemplate elements need x:DataType, and WinUI 3 DataTemplate elements that use x:Bind need it too. Missing it leaves MAUI on reflection and fails the WinUI build.

  • Changing a collection versus changing an item. ObservableCollection<T> notifies about adds and removes only; an IsRead change on a Book needs INotifyPropertyChanged, which the toolkit generates for [ObservableProperty].

  • UI thread. Updating Books from a background thread throws in all three stacks. Marshal to the dispatcher (Dispatcher, DispatcherQueue or MainThread), or produce the collection and assign it on the UI thread.

  • Trimming and AOT. Reflection-based {Binding} is the part least friendly to trimming. In MAUI, compiled bindings remove reflection; in WinUI 3, x:Bind does; in WPF, trimming and Native AOT are not supported at all.

  • Accessibility names do not port. AutomationProperties.Name (WPF, WinUI 3) becomes SemanticProperties.Description (MAUI).