Controls & the Content Model

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.

WPF ships roughly a hundred ready-made controls, but they are built on a very small set of ideas. Almost every control is one of four things: a control that shows one thing (ContentControl), a control that shows one thing under a title (HeaderedContentControl), a control that shows many things (ItemsControl) or a control that shows many things under a title (HeaderedItemsControl). Learning that content model once explains why a Button can hold a picture, why a ListBox can hold buttons and why a MenuItem can contain other MenuItem objects. This page was written against .NET 10 LTS (latest patch 10.0.13) with C# 14, Visual Studio 2026 18.11.1, Windows App SDK 2.5.1 and CommunityToolkit.Mvvm 8.4.2 on Windows 10 and Windows 11. .NET 11 is at release candidate 2 and general availability is expected at .NET Conf, 10 to 13 November 2026; its WPF release notes list bug fixes only, so nothing on this page changes for it. WPF is the stack in focus; WinUI 3 and Windows Forms are compared in a table below.

The running scenario is Bookshelf Desktop (namespace Bookshelf.Desktop, models in Bookshelf.Core). Here it provides a main window with a menu, a toolbar, a status bar, a search box, a genre filter, a details area in a tab control and a Popup-based hint, all driven by BooksViewModel and BookDetailsViewModel. Layout containers such as Grid are on the Layout & Panels page, lists, grids and trees on Lists, Grids & Trees, and commands on Commands; styling and re-templating controls is on Styles, Templates & Triggers.

The content model

WPF’s own documentation describes the content model as the rule that decides what a control may contain. The rule is expressed by the base class a control derives from, so you can predict the capabilities of a control from its ancestry.

classDiagram Control <|-- ContentControl Control <|-- ItemsControl Control <|-- TextBoxBase Control <|-- RangeBase ContentControl <|-- HeaderedContentControl ContentControl <|-- ButtonBase ContentControl <|-- Window ContentControl <|-- ListBoxItem ContentControl <|-- ToolTip ContentControl <|-- UserControl ButtonBase <|-- Button ButtonBase <|-- ToggleButton ToggleButton <|-- CheckBox ToggleButton <|-- RadioButton HeaderedContentControl <|-- Expander HeaderedContentControl <|-- GroupBox HeaderedContentControl <|-- TabItem ItemsControl <|-- HeaderedItemsControl ItemsControl <|-- Selector ItemsControl <|-- MenuBase ItemsControl <|-- TreeView ItemsControl <|-- StatusBar HeaderedItemsControl <|-- MenuItem HeaderedItemsControl <|-- TreeViewItem HeaderedItemsControl <|-- ToolBar Selector <|-- ListBox Selector <|-- ComboBox Selector <|-- TabControl ListBox <|-- ListView MenuBase <|-- Menu MenuBase <|-- ContextMenu TextBoxBase <|-- TextBox RangeBase <|-- Slider RangeBase <|-- ProgressBar class ContentControl { +object Content +DataTemplate ContentTemplate } class HeaderedContentControl { +object Header +DataTemplate HeaderTemplate } class ItemsControl { +IEnumerable ItemsSource +DataTemplate ItemTemplate +ItemsPanelTemplate ItemsPanel } class HeaderedItemsControl { +object Header }

The diagram shows only the classes used on this page. The full tree is in the API reference. The four content-model classes are summarized below; everything else in the catalogue is either one of them or a specialization.

Base class Holds Key properties Examples

ContentControl

One object, of any type

Content, ContentTemplate, ContentTemplateSelector, ContentStringFormat, HasContent

Button, CheckBox, Label, ListBoxItem, ToolTip, Window, UserControl, ScrollViewer

HeaderedContentControl

One object plus a header

as above, plus Header, HeaderTemplate, HeaderTemplateSelector, HeaderStringFormat

Expander, GroupBox, TabItem

ItemsControl

A collection of objects

Items, ItemsSource, ItemTemplate, ItemsPanel, ItemContainerStyle, DisplayMemberPath, ItemStringFormat, HasItems

ListBox, ComboBox, ListView, TabControl, TreeView, Menu, ContextMenu, StatusBar, DataGrid

HeaderedItemsControl

A collection plus a header

ItemsControl members plus Header, HeaderTemplate

MenuItem, TreeViewItem, ToolBar

Two properties of the model matter in practice. First, Content is typed object, so the control does not care what you put in it. Second, every other control on this page is a composition: a ComboBox is a Selector whose items are rendered through ComboBoxItem containers, which are themselves ContentControl objects. The rendering of that content is done by a ContentPresenter inside the control’s template (see Styles, Templates & Triggers), which is why the content model and the look of the control can be changed independently.

How Content is rendered

A ContentPresenter inside the control template decides how the content object is drawn. The rules, in order, are the ones documented in the WPF content model and data templating pages:

Content is WPF draws it as

A UIElement

The element itself, inserted into the visual tree. An element can have one logical parent only, so the same instance can never be the content of two controls at once.

A non-visual object and a ContentTemplate is set

The template applied to that object.

A non-visual object with a DataTemplate found by type

The implicit template whose DataType matches the object’s type, searched through the resource chain (see Resources & Resource Dictionaries).

A string

A text block showing the string (optionally through ContentStringFormat).

Anything else

The result of calling ToString() on it, which is rarely what you want.

Because of the third row, the content of a window area can be a view model and the visual is chosen by a template. This is the ViewModel-first pattern, and it is a convention rather than an API: nothing in WPF or the MVVM Toolkit calls it a framework feature.

<!-- MainWindow.xaml (excerpt): the Details area shows whichever view model is current -->
<Window x:Class="Bookshelf.Desktop.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:Bookshelf.Desktop.ViewModels"
        xmlns:views="clr-namespace:Bookshelf.Desktop.Views"
        Title="Bookshelf Desktop" Width="1000" Height="640">
    <Window.Resources>
        <!-- Implicit templates: no x:Key, matched by DataType -->
        <DataTemplate DataType="{x:Type vm:BookDetailsViewModel}">
            <views:BookDetailsView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:EmptySelectionViewModel}">
            <TextBlock Text="Select a book to see its details." Margin="16" />
        </DataTemplate>
    </Window.Resources>

    <!-- One ContentControl, bound to an object: the template decides the visual -->
    <ContentControl Content="{Binding CurrentDetails}" />
</Window>

When the bound object changes from a BookDetailsViewModel to an EmptySelectionViewModel, the ContentPresenter looks up the matching template again and replaces the visual. That is the entire mechanism; no code-behind is needed.

Content in code

Everything the markup does can be done in code, which is useful to understand because the XAML <Button>text</Button> is only a shorthand for assigning Content.

using System.Windows;
using System.Windows.Controls;

namespace Bookshelf.Desktop;

public static class ContentExamples
{
    public static Button CreateAddButton()
    {
        // Content accepts any object, including a panel with its own children.
        var icon = new TextBlock { Text = "", FontFamily = new System.Windows.Media.FontFamily("Segoe MDL2 Assets") };
        var label = new TextBlock { Text = "Add book", Margin = new Thickness(6, 0, 0, 0) };

        var panel = new StackPanel { Orientation = Orientation.Horizontal };
        panel.Children.Add(icon);
        panel.Children.Add(label);

        return new Button { Content = panel, MinWidth = 96 };
    }
}

The control catalogue by category

The official control library groups controls by purpose. The table lists the ones used in line-of-business apps and the base class that tells you how to fill them.

Category Controls Content model

Buttons

Button, RepeatButton, ToggleButton, CheckBox, RadioButton

ContentControl (via ButtonBase)

Text display

TextBlock, Label

TextBlock is a FrameworkElement with Inlines, not a control; Label is a ContentControl with an access key

Text input

TextBox, PasswordBox, RichTextBox

Text properties, not the content model

Selection

ComboBox, ListBox, ListView, TabControl, DataGrid

ItemsControl (via Selector)

Range and progress

Slider, ProgressBar, ScrollBar

RangeBase: Minimum, Maximum, Value

Date and time

Calendar, DatePicker

Date properties such as SelectedDate

Containers with a header

Expander, GroupBox, TabItem

HeaderedContentControl

Menus and bars

Menu, ContextMenu, ToolBar (inside ToolBarTray), StatusBar

ItemsControl or HeaderedItemsControl

Hierarchies

TreeView, TreeViewItem

HeaderedItemsControl

Overlays and hints

ToolTip, Popup

ToolTip is a ContentControl; Popup has a single Child

Layout and decoration

Grid, StackPanel, Border, ScrollViewer, Viewbox, GridSplitter

Panels hold Children; Border and Viewbox hold one Child

Documents and media

FlowDocumentReader, DocumentViewer, MediaElement, Image

Specific; see the Documents, Printing & Text and Graphics, Animation & Multimedia pages

TextBlock, Image, Border and the panels are not Control objects, so they do not support Template, Foreground-style inheritance rules of Control, or focus by default. That distinction matters when you restyle them.

Text input

TextBox is the workhorse. The properties you reach for most often are below.

Property Effect

Text

The content. The default binding trigger is LostFocus, so a bound TextBox does not update its source on every keystroke unless you ask for PropertyChanged.

AcceptsReturn, AcceptsTab

Allow multi-line input and tab characters.

TextWrapping, VerticalScrollBarVisibility

Wrapping and scroll bars for multi-line boxes.

MaxLength, CharacterCasing

Input restrictions.

IsReadOnly

The user can select and copy but not edit.

SpellCheck.IsEnabled

Attached property that turns on the built-in spell checker.

TextChanged, SelectionChanged

Events. Prefer a binding to the property over handling TextChanged.

PasswordBox is different on purpose: its Password property is not a dependency property, so it cannot be a binding target, and SecurePassword returns a SecureString. The usual workarounds (an attached behavior, or reading the password in a command handler) are user patterns; Learn does not offer an official bound-password control. RichTextBox edits a FlowDocument and is covered on the documents page.

The search box in Bookshelf Desktop binds Text with a short delay so the list is filtered once the user pauses typing.

<TextBox x:Name="SearchBox"
         Width="240"
         ToolTip="Search by title or author"
         Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged, Delay=300}" />

Source: TextBox

Buttons and toggles

Button is a ContentControl that raises Click and, more usefully, executes an ICommand. The relevant properties are Command, CommandParameter, IsDefault (Enter activates it) and IsCancel (Esc activates it). CheckBox and RadioButton are ToggleButton controls whose IsChecked is a bool? (three-state when IsThreeState is set), and RadioButton groups itself by GroupName or by sharing the same parent. RepeatButton raises Click repeatedly while it is pressed.

<StackPanel Orientation="Horizontal" Margin="8">
    <Button Content="_Add book" Command="{Binding AddBookCommand}" IsDefault="True" />
    <Button Content="_Delete" Command="{Binding DeleteBookCommand}"
            CommandParameter="{Binding SelectedBook}" Margin="8,0,0,0" />
    <CheckBox Content="Only unread" IsChecked="{Binding OnlyUnread}" Margin="16,0,0,0" VerticalAlignment="Center" />
    <RadioButton GroupName="Sort" Content="Title" IsChecked="True" Margin="16,0,0,0" VerticalAlignment="Center" />
    <RadioButton GroupName="Sort" Content="Author" Margin="8,0,0,0" VerticalAlignment="Center" />
</StackPanel>

Source: Button

The underscore in _Add book defines an access key (Alt+A); the control that interprets it is the AccessText inside the presenter, so it works only when the content is a string (or contains an AccessText).

Selection controls

ListBox, ComboBox and TabControl all derive from Selector, which adds the selection members shown in the table. ListBox and ListView are examined in detail on Lists, Grids & Trees; the part that belongs to the content model is that ItemsSource, ItemTemplate and DisplayMemberPath work identically on all of them.

Member Meaning

SelectedItem, SelectedIndex

The selected object and its position.

SelectedValue and SelectedValuePath

A property of the selected item (for example Id) used as the value. This is what you bind when the view model holds a key rather than the whole object.

IsSynchronizedWithCurrentItem

When true, the selection follows the collection view’s current item (see Converters, Collection Views & Data Templates).

ItemsSource versus Items

Set one or the other. Once ItemsSource is set, Items is read-only and changes must go through the source collection.

DisplayMemberPath versus ItemTemplate

DisplayMemberPath shows one property as text; ItemTemplate builds a full visual and takes precedence.

IsEditable (ComboBox)

Turns the closed state into a TextBox; Text is the typed value.

The genre filter of the main window uses a ComboBox bound to the view model’s genre list:

<ComboBox Width="160" Margin="8,0,0,0"
          ItemsSource="{Binding Genres}"
          SelectedItem="{Binding SelectedGenre}"
          ToolTip="Filter the list by genre" />

Source: ComboBox

Headered containers: Expander, GroupBox and TabControl

HeaderedContentControl adds a Header that has its own HeaderTemplate, so a header can be text, an icon with text or a bound object.

  • GroupBox draws a captioned frame around one child.

  • Expander adds IsExpanded and ExpandDirection, and raises Expanded and Collapsed. It is the simplest way to offer an "advanced options" section.

  • TabControl is an ItemsControl whose containers are TabItem (a HeaderedContentControl). Each tab has a Header and one content. TabStripPlacement moves the tab strip; SelectedIndex and SelectedItem come from Selector.

A TabControl created from ItemsSource needs two templates, one for the header and one for the content, because the data item itself is neither:

<TabControl ItemsSource="{Binding OpenBooks}" SelectedItem="{Binding ActiveBook}">
    <TabControl.ItemTemplate>
        <!-- Template for the tab header -->
        <DataTemplate>
            <TextBlock Text="{Binding Title}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- Template for the tab body (applies to the selected item) -->
        <DataTemplate>
            <StackPanel Margin="12">
                <TextBlock Text="{Binding Author}" FontWeight="SemiBold" />
                <Expander Header="Notes" Margin="0,8,0,0">
                    <TextBox Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}"
                             AcceptsReturn="True" TextWrapping="Wrap" MinHeight="80" />
                </Expander>
            </StackPanel>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

Source: TabControl

By default a TabControl keeps only the selected tab’s content in the visual tree, so a heavy view is rebuilt every time the user returns to it, and the state of controls inside it (scroll position, caret) is lost unless it lives in the view model. See the pitfalls below.

Menus, toolbars and status bars

Menus show how HeaderedItemsControl composes: Menu is an ItemsControl of MenuItem, and each MenuItem has a Header and its own Items. A MenuItem can execute a command, display an InputGestureText, have an Icon, be checkable with IsCheckable, and use a Separator. Built-in routed commands such as ApplicationCommands.Copy supply the display text, the shortcut and the enabled state automatically when the command target can handle them. How routed commands work is on the Commands page.

<DockPanel>
    <Menu DockPanel.Dock="Top">
        <MenuItem Header="_File">
            <MenuItem Header="_New book..." Command="{Binding AddBookCommand}" InputGestureText="Ctrl+N" />
            <MenuItem Header="_Import" Command="{Binding ImportCommand}" />
            <Separator />
            <MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
        </MenuItem>
        <MenuItem Header="_Edit">
            <MenuItem Command="ApplicationCommands.Copy" />
            <MenuItem Command="ApplicationCommands.Paste" />
        </MenuItem>
        <MenuItem Header="_View">
            <MenuItem Header="_Dark theme" IsCheckable="True" IsChecked="{Binding IsDarkTheme}" />
        </MenuItem>
    </Menu>

    <ToolBarTray DockPanel.Dock="Top">
        <ToolBar Band="0" BandIndex="0">
            <Button Command="{Binding AddBookCommand}" Content="Add" ToolTip="Add a book (Ctrl+N)" />
            <Button Command="{Binding DeleteBookCommand}" Content="Delete" />
            <Separator />
            <ComboBox Width="140" ItemsSource="{Binding Genres}" SelectedItem="{Binding SelectedGenre}" />
        </ToolBar>
    </ToolBarTray>

    <StatusBar DockPanel.Dock="Bottom">
        <StatusBarItem Content="{Binding StatusMessage}" />
        <StatusBarItem HorizontalAlignment="Right">
            <ProgressBar Width="100" Height="12" IsIndeterminate="True"
                         Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibility}}" />
        </StatusBarItem>
    </StatusBar>

    <ContentControl Content="{Binding CurrentDetails}" />
</DockPanel>

Source: Menu overview

ToolBar items that do not fit move into an overflow drop-down; the attached property ToolBar.OverflowMode decides whether an item can overflow (AsNeeded, Always or Never). A ContextMenu is a MenuBase assigned to the ContextMenu property of an element; it is not part of the visual tree, so bindings that use ElementName need PlacementTarget (a well-known pitfall of every popup-like control). StatusBar is an ItemsControl of StatusBarItem objects.

The view model behind the shell

The commands and properties used by the XAML above live in the view model. BooksViewModel uses partial properties ([ObservableProperty] on partial properties needs the MVVM Toolkit 8.4 or later; with 8.4.1 and later they work with C# 14 without LangVersion preview).

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

namespace Bookshelf.Desktop.ViewModels;

public sealed partial class BooksViewModel(IBookRepository repository, IDialogService dialogs) : ObservableObject
{
    public ObservableCollection<BookViewModel> Books { get; } = [];

    public IReadOnlyList<string> Genres { get; } = ["All", "Fiction", "Science", "History"];

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

    [ObservableProperty]
    public partial string SelectedGenre { get; set; } = "All";

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

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

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

    [ObservableProperty]
    public partial object CurrentDetails { get; set; } = new EmptySelectionViewModel();

    [ObservableProperty]
    public partial string StatusMessage { get; set; } = "Ready";

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

    partial void OnSelectedBookChanged(BookViewModel? value) =>
        CurrentDetails = value is null ? new EmptySelectionViewModel() : new BookDetailsViewModel(value, repository);

    [RelayCommand]
    private async Task AddBookAsync(CancellationToken cancellationToken)
    {
        Book? draft = await dialogs.EditBookAsync(null);
        if (draft is null)
        {
            return;
        }

        Book saved = await repository.AddAsync(draft, cancellationToken);
        Books.Add(new BookViewModel(saved));
        StatusMessage = $"Added \"{saved.Title}\"";
    }

    [RelayCommand(CanExecute = nameof(CanDeleteBook))]
    private async Task DeleteBookAsync(BookViewModel? book)
    {
        if (book is null || !await dialogs.ConfirmAsync("Delete book", $"Delete \"{book.Title}\"?"))
        {
            return;
        }

        IsBusy = true;
        try
        {
            await repository.DeleteAsync(book.Id, CancellationToken.None);
            Books.Remove(book);
        }
        finally
        {
            IsBusy = false;
        }
    }

    private bool CanDeleteBook(BookViewModel? book) => book is not null;

    [RelayCommand]
    private void Exit() => System.Windows.Application.Current.Shutdown();
}

public sealed class EmptySelectionViewModel;

The Delete button passes {Binding SelectedBook} as its CommandParameter; the toolkit generates an IAsyncRelayCommand<BookViewModel> for the one-parameter method. ImportCommand is omitted for brevity. The official ObservableProperty page still shows the field form; the partial-property form comes from the toolkit’s 8.4 release notes and the MVVMTK0042 analyzer.

ToolTip and Popup

A ToolTip is a ContentControl, so a tip can hold any content, not only text. Setting the ToolTip property to a string creates one for you. The attached ToolTipService controls timing and behavior without declaring a ToolTip element.

<Button Content="Details" ToolTipService.InitialShowDelay="600" ToolTipService.ShowDuration="8000"
        ToolTipService.ShowOnDisabled="True">
    <Button.ToolTip>
        <StackPanel>
            <TextBlock Text="Open the details tab" FontWeight="Bold" />
            <TextBlock Text="Shortcut: Ctrl+D" />
        </StackPanel>
    </Button.ToolTip>
</Button>

Popup is not a control and has no content-model base: it derives from FrameworkElement and has a single Child. It displays that child in a separate top-level window above the application, positioned by Placement relative to PlacementTarget. IsOpen shows and hides it, StaysOpen="False" closes it when the user clicks elsewhere, and AllowsTransparency and PopupAnimation shape its appearance.

<ToggleButton x:Name="HintToggle" Content="?" Width="24" />
<Popup IsOpen="{Binding IsChecked, ElementName=HintToggle, Mode=OneWay}"
       PlacementTarget="{Binding ElementName=HintToggle}"
       Placement="Bottom" StaysOpen="False" AllowsTransparency="True">
    <Border Background="White" BorderBrush="Gray" BorderThickness="1" Padding="8">
        <TextBlock Text="Type part of a title or an author. The list filters after a short pause." MaxWidth="240" TextWrapping="Wrap" />
    </Border>
</Popup>

Source: Popup overview

The Ribbon

WPF includes a Ribbon in the System.Windows.Controls.Ribbon namespace (the types Ribbon, RibbonWindow, RibbonTab, RibbonGroup, RibbonButton and related). Microsoft Learn documents it only in the API reference: there is no conceptual page, no tutorial and no guidance for .NET 10. This page therefore does not describe Ribbon behavior beyond what the API reference lists, and the snippet below is a minimal skeleton rather than an official sample. Because it follows the Office 2010 look, it does not pick up the Fluent theme the way the core controls do; test it on your own target before committing to it.

<RibbonWindow x:Class="Bookshelf.Desktop.RibbonShell"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:ribbon="clr-namespace:System.Windows.Controls.Ribbon;assembly=System.Windows.Controls.Ribbon"
              Title="Bookshelf Desktop">
    <DockPanel>
        <ribbon:Ribbon DockPanel.Dock="Top">
            <ribbon:RibbonTab Header="Home">
                <ribbon:RibbonGroup Header="Books">
                    <ribbon:RibbonButton Label="Add" Command="{Binding AddBookCommand}" />
                    <ribbon:RibbonButton Label="Delete" Command="{Binding DeleteBookCommand}" />
                </ribbon:RibbonGroup>
            </ribbon:RibbonTab>
        </ribbon:Ribbon>
        <ContentControl Content="{Binding CurrentDetails}" />
    </DockPanel>
</RibbonWindow>

For a new app that wants a command surface denser than a toolbar, the practical alternatives are a Menu plus ToolBar as above, or a third-party ribbon; third-party products are outside this section.

Controls across the Windows UI stacks

All three stacks ship the same everyday controls, but only the XAML stacks share the content-model idea.

Aspect WPF WinUI 3 (Windows App SDK) Windows Forms

Base of a control

FrameworkElement, with Control for templated controls

FrameworkElement, with Control for templated controls

System.Windows.Forms.Control

Content model

ContentControl, HeaderedContentControl, ItemsControl, HeaderedItemsControl

ContentControl and ItemsControl; there is no HeaderedContentControl, and controls such as Expander and TextBox expose their own Header

None: Text, Image and the Controls collection; owner-drawn lists via events

Menus and bars

Menu, ToolBar, StatusBar, ContextMenu

MenuBar, CommandBar, MenuFlyout, InfoBar

MenuStrip, ToolStrip, StatusStrip, ContextMenuStrip

Overlays

ToolTip, Popup

ToolTip, Flyout, TeachingTip, ContentDialog

ToolTip component, Form as a dialog

Tabs

TabControl with TabItem

TabView (closable, draggable) and NavigationView

TabControl with TabPage

Numeric and date input

Slider, DatePicker, no numeric box

NumberBox, CalendarDatePicker, DatePicker

NumericUpDown, DateTimePicker, TrackBar

Ribbon

System.Windows.Controls.Ribbon (API reference only)

None in WinUI 3; the Windows App SDK does not include one

None in the box

Look

Aero2 by default; Fluent via the theme options (see Themes & Fluent Design)

Fluent, with Mica and Acrylic backdrops

Native Win32 controls; dark mode via Application.SetColorMode

The WinUI rows come from the WinUI control library documentation and should be re-checked against the Windows App SDK version you target.

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

  • The controls and the content model are unchanged between .NET Framework 4.8 and .NET 10. Existing markup compiles without edits once the project targets net10.0-windows.

  • The Ribbon assembly (System.Windows.Controls.Ribbon) is part of the WPF runtime in .NET, so a Ribbon app migrates without a third-party package.

  • The WebBrowser control still exists but is limited; use the WebView2 control for web content (see Interop: Win32, WinForms & Web Content).

  • .NET 9 introduced the Fluent theme as an opt-in, and .NET 10 extends its availability; the Fluent look changes the appearance of the standard controls without altering their content model. Details are on Themes & Fluent Design.

  • .NET 10 adds a short form for Grid row and column definitions and new MessageBox buttons (see Layout & Panels and Windows, Dialogs & Navigation).

Pitfalls, performance and security

  • A UIElement has one parent. Putting the same element instance in two Content properties throws; use a DataTemplate so each container creates its own copy.

  • ToString() as a fallback. A ListBox of view models without a template shows the type name. Add an implicit DataTemplate or DisplayMemberPath.

  • Items versus ItemsSource. Mixing them throws an InvalidOperationException.

  • TabControl content is not cached. Tabs rebuild their visuals when selected; keep the state in the view model, or use a template that does not rebuild expensive children.

  • ContextMenu and ToolTip are outside the visual tree. ElementName and RelativeSource bindings inside them often fail; bind through PlacementTarget or the DataContext instead.

  • Popup is a separate window. It floats above everything (including other applications' windows in some situations), can be left behind when its parent window moves, and has airspace limits with hosted Win32 content.

  • Text updates on LostFocus by default. A Save button can run before the last edit reaches the view model; set UpdateSourceTrigger=PropertyChanged for fields a command depends on.

  • PasswordBox. Keep the secret in the control and read it only when needed. Never log it, store it in a view model property or place a real credential in sample code.

  • Performance. Controls with large Items collections need UI virtualization (see Lists, Grids & Trees); deep nested panels cost layout time.

  • Accessibility. Standard controls expose UI Automation automatically; custom ContentControl content should set AutomationProperties.Name (see Accessibility & UI Automation).

What Microsoft Learn does not provide

  • There is no conceptual Ribbon page for WPF; only the API reference exists. Ribbon guidance cannot be derived from Learn.

  • The control library page lists controls but gives no rules for choosing between them, and no Fluent-specific control gallery for WPF.

  • There is no official bound PasswordBox, no numeric up/down and no official docking or tab-reorder control in WPF.

  • The ViewModel-first ContentControl plus implicit DataTemplate technique is shown in the data templating pages without the name or guidance used here; the term and the Bookshelf wiring are a user pattern.

  • Some Learn control pages are short API-style overviews written for .NET Framework; behavior on .NET 10 is the same for the properties used here, but check the API reference before relying on an obscure member.