Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e704d1
Create base Adorners project from base template
michael-hawker Nov 18, 2025
c32b1eb
[WIP] Initial updating of old Adorners branch import
michael-hawker Nov 18, 2025
8bff99e
Fix build time error with XAML Compilers from overloaded namespace/cl…
michael-hawker Nov 19, 2025
72b2c6e
Bring over Adorner improvements from XAML Studio
michael-hawker Nov 19, 2025
18395e4
Add some notes on differences with WPF and TODOs
michael-hawker Nov 19, 2025
a8bacd0
Fix for WindowsAppSDK Interface error (from CsWinRT)
michael-hawker Nov 19, 2025
bcdc3f3
Apply XAML Styler...
michael-hawker Nov 19, 2025
b420c9e
Add more xmldoc comments for AdornerLayer and AdornerDecorator
michael-hawker Nov 23, 2025
bb120de
Add proper `Adorner` wrapper class for coordination of size/layout wi…
michael-hawker Nov 23, 2025
2f8d387
Add another Adorner example with a TabViewItem InfoBadge adorner
michael-hawker Nov 23, 2025
558d40c
Fix XAML Styling again...
michael-hawker Nov 23, 2025
1f71527
Add more to TabViewItem Adorner sample Animations + handling unloadin…
michael-hawker Nov 23, 2025
e2844ec
Add Data Binding to Adorner TabViewItem sample
michael-hawker Nov 23, 2025
f2d9b79
Add missing header
michael-hawker Nov 23, 2025
460c3f3
Fix UWP build of Sample
michael-hawker Nov 23, 2025
f37a46b
Add an initial custom adorner sample
michael-hawker Nov 25, 2025
9eb63f5
Move more complex InPlaceTextEditor Sample to its own subdirectory (n…
michael-hawker Nov 26, 2025
f1c8767
Make InPlaceTextEditor example more interesting using IEditableObject…
michael-hawker Nov 26, 2025
581f74c
Add initial InputValidationAdorner Sample test
michael-hawker Nov 26, 2025
11384ff
Fix XAML Styling
michael-hawker Nov 26, 2025
b79819e
Mark AdornerOfT as partial
michael-hawker Nov 26, 2025
d79c2df
Update Labs-Windows Tooling to use latest SDK on UWP for Adorners con…
michael-hawker Dec 3, 2025
5e37f1b
Simplify the InPlaceTextEditorAdorner sample
michael-hawker Dec 3, 2025
b7637ca
Move InputValidationAdorner from sample to Custom Adorner in NuGet Pa…
michael-hawker Dec 3, 2025
64ef7d3
Fix issue with InputValidationAdorner not resolving once validation e…
michael-hawker Dec 3, 2025
c7b1810
Add some customization property resources to InputValidationAdorner
michael-hawker Dec 3, 2025
7c20776
Initial staging/layout of ResizeElementAdorner
michael-hawker Dec 4, 2025
cb59341
Setup ResizeThumb control for ResizeElementAdorner, almost works...
michael-hawker Dec 4, 2025
31297b0
Get ResizeElementAdorner sizing to work, though Adorner isn't updatin…
michael-hawker Dec 4, 2025
35d0230
Hook event into ResizeThumb to know when element has been changed so …
michael-hawker Dec 4, 2025
94ad9ca
Apply XAML Styler
michael-hawker Dec 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/Adorners/OpenSolution.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@ECHO OFF

powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
15 changes: 15 additions & 0 deletions components/Adorners/samples/Adorners.Samples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" Condition="Exists('$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))')" />

<PropertyGroup>
<ToolkitComponentName>Adorners</ToolkitComponentName>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>

<!-- Sets this up as a toolkit component's sample project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
</Project>
75 changes: 75 additions & 0 deletions components/Adorners/samples/Adorners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: Adorners
author: michael-hawker
description: Adorners let you overlay content on top of your XAML components in a separate layer on top of everything else.
keywords: Adorners, Control, Layout, InfoBadge, AdornerLayer, AdornerDecorator, Adorner, Input Validation, Resize, Highlighting
dev_langs:
- csharp
category: Controls
subcategory: Layout
discussion-id: 278
issue-id: 0
icon: assets/icon.png
---

# Adorners

Adorners allow a developer to overlay any content on top of another UI element in a separate layer that resides on top of everything else.

## Background

Adorners originally existed in WPF as an extension part of the framework. [You can read more about how they worked in WPF here.](https://learn.microsoft.com/dotnet/desktop/wpf/controls/adorners-overview) See more about the commonalities and differences to WinUI adorners in the migration section below.

### Without Adorners

Imagine a scenario where you have a button or tab that checks a user's e-mail, and you'd like it to display the number of new e-mails that have arrived.

You could try and incorporate a [`InfoBadge`](https://learn.microsoft.com/windows/apps/design/controls/info-badge) into your Visual Tree in order to display this as part of your icon, but that requires you to modify quite a bit of your content, as in this example:

> [!SAMPLE InfoBadgeWithoutAdorner]

It also, by default, gets confined to the perimeter of the button and clipped, as seen above.

### With Adorners

However, with an Adorner instead, you can abstract this behavior from the content of your control. You can even more easily place the notification outside the bounds of the original element, like so:

> [!SAMPLE AdornersInfoBadgeSample]

You can see how Adorners react to more dynamic content with this more complete example here:

> [!SAMPLE AdornersTabBadgeSample]

The above example shows how to leverage XAML animations and data binding alongside the XAML-based Adorner with a `TabViewItem` which can also move or disappear.

## Highlight Example

Adorners can be used in a variety of scenarios. For instance, if you wanted to highlight an element and show it's alignment to other elements in a creativity app:

> [!SAMPLE ElementHighlightAdornerSample]

The above examples highlights how adorners are sized and positioned directly atop the adorned element. This allows for relative positioning of elements within the context of the Adorner's visuals in relation to the Adorned Element itself.

## Custom Adorner Example

Adorners can be subclassed in order to encapsulate specific logic and/or styling for your scenario.
For instance, you may want to create a custom Adorner that allows a user to click and edit a piece of text in place.
The following example uses `IEditableObject` to control the editing lifecycle coordinated with a typical MVVM pattern binding:

> [!SAMPLE InPlaceTextEditorAdornerSample]

Adorners are template-based controls, but you can use a class-backed resource dictionary to better enable usage of x:Bind for easier creation and binding to the `AdornedElement`, as seen here.

You can see other example of custom adorners with the other Adorner help topics for the built-in adorners provided in this package, such as the `InputValidationAdorner` and `ResizeElementAdorner`.

## Migrating from WPF

The WinUI Adorner API surface adapts many similar names and concepts as WPF Adorners; however, WinUI Adorners are XAML based and make use of the attached properties to make using Adorners much simpler, like Behaviors. Where as defining Adorners in WPF required custom drawing routines. It's possible to replicate many similar scenarios with this new API surface and make better use of XAML features like data binding and styling; however, it will mean rewriting any existing WPF code.

### Concepts

The `AdornerLayer` is still an element of the visual tree which resides atop other content within your app and is the parent of all adorners. In WPF, this is usually already automatically a component of your app or `ScrollViewer`. Like WPF, adorners parent's in the visual tree will be the `AdornerLayer` and not the adorned element. The WinUI-based `AdornerLayer` will automatically be inserted in many common scenarios, otherwise, an `AdornerDecorator` may still be used to direct the placement of the `AdornerLayer` within the Visual Tree.

The `AdornerDecorator` provides a similar purpose to that of its WPF counterpart, it will host an `AdornerLayer`. The main difference with the WinUI API is that the `AdornerDecorator` will wrap your contained content vs. in WPF it sat as a sibling to your content. We feel this makes it easier to use and ensure your adorned elements reside atop your adorned content, it also makes it easier to find within the Visual Tree for performance reasons.

The `Adorner` class in WinUI is now a XAML-based element that can contain any content you wish to overlay atop your adorned element. In WPF, this was a non-visual class that required custom drawing logic to render the adorner's content. This change allows for easier creation of adorners using XAML, data binding, and styling. Many similar concepts and properties still exist between the two, like a reference to the `AdornedElement`. Any loose XAML attached via the `AdornerLayer.Xaml` attached property is automatically wrapped within a basic `Adorner` container. You can either restyle or subclass the `Adorner` class in order to better encapsulate logic of a custom `Adorner` for your specific scenario, like a behavior, as shown above.
24 changes: 24 additions & 0 deletions components/Adorners/samples/AdornersInfoBadgeSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="AdornersExperiment.Samples.AdornersInfoBadgeSample"
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:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">

<Button>
<SymbolIcon HorizontalAlignment="Center"
Symbol="Mail" />
<ui:AdornerLayer.Xaml>
<muxc:InfoBadge Margin="-4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsHitTestVisible="False"
Opacity="0.9"
Visibility="{x:Bind IsAdornerVisible, Mode=OneWay}"
Value="5" />
</ui:AdornerLayer.Xaml>
</Button>
</Page>
16 changes: 16 additions & 0 deletions components/Adorners/samples/AdornersInfoBadgeSample.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace AdornersExperiment.Samples;

[ToolkitSampleBoolOption("IsAdornerVisible", true, Title = "Is Adorner Visible")]

[ToolkitSample(id: nameof(AdornersInfoBadgeSample), "InfoBadge w/ Adorner", description: "A sample for showing how add an infobadge to a component via an Adorner.")]
public sealed partial class AdornersInfoBadgeSample : Page
{
public AdornersInfoBadgeSample()
{
this.InitializeComponent();
}
}
52 changes: 52 additions & 0 deletions components/Adorners/samples/AdornersTabBadgeSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="AdornersExperiment.Samples.AdornersTabBadgeSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">

<muxc:TabView TabCloseRequested="TabView_TabCloseRequested">
<muxc:TabViewItem Header="Home">
<ui:AdornerLayer.Xaml>
<muxc:InfoBadge Margin="-4,-8"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsHitTestVisible="False"
Opacity="0.9"
Visibility="{x:Bind IsAdornerVisible, Mode=OneWay}"
Value="{x:Bind (x:Int32)BadgeValue, Mode=OneWay}">
<animations:Implicit.ShowAnimations>
<animations:RotationInDegreesAnimation From="45"
To="0"
Duration="0:0:1" />
<animations:TranslationAnimation From="0, 1, 0"
To="0"
Duration="0:0:1" />
<animations:OpacityAnimation From="0"
To="1.0"
Duration="0:0:1" />
</animations:Implicit.ShowAnimations>

<animations:Implicit.HideAnimations>
<animations:OpacityAnimation To="0.0"
Duration="0:0:1" />
<animations:ScalarAnimation Target="Translation.Y"
To="5"
Duration="0:0:1">
<animations:ScalarKeyFrame Key="0.1"
Value="-10" />
<animations:ScalarKeyFrame Key="0.5"
Value="0.0" />
</animations:ScalarAnimation>
</animations:Implicit.HideAnimations>
</muxc:InfoBadge>
</ui:AdornerLayer.Xaml>
</muxc:TabViewItem>
<muxc:TabViewItem Header="Document 1" />
<muxc:TabViewItem Header="Document 2" />
</muxc:TabView>
</Page>
22 changes: 22 additions & 0 deletions components/Adorners/samples/AdornersTabBadgeSample.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace AdornersExperiment.Samples;

[ToolkitSampleBoolOption("IsAdornerVisible", true, Title = "Is Adorner Visible")]
[ToolkitSampleNumericOption("BadgeValue", 3, 1, 5, 1, true, Title = "Badge Value")]

[ToolkitSample(id: nameof(AdornersTabBadgeSample), "InfoBadge w/ Adorner in TabView", description: "A sample for showing how add an InfoBadge to a TabViewItem via an Adorner.")]
public sealed partial class AdornersTabBadgeSample : Page
{
public AdornersTabBadgeSample()
{
this.InitializeComponent();
}

private void TabView_TabCloseRequested(MUXC.TabView sender, MUXC.TabViewTabCloseRequestedEventArgs args)
{
sender.TabItems.Remove(args.Tab);
}
}
Binary file added components/Adorners/samples/Assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions components/Adorners/samples/Dependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!--
WinUI 2 under UWP uses TargetFramework uap10.0.*
WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.

MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.

For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
For UWP / WinAppSDK / Uno packages, place the package references here.
-->
<Project>
<!-- WinUI 2 / UWP / Uno -->
<ItemGroup Condition="'$(IsUwp)' == 'true' OR ('$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2')">
<PackageReference Include="CommunityToolkit.Uwp.Animations" Version="8.2.250402"/>
</ItemGroup>

<!-- WinUI 3 / WinAppSdk / Uno -->
<ItemGroup Condition="'$(IsWinAppSdk)' == 'true' OR ('$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3')">
<PackageReference Include="CommunityToolkit.WinUI.Animations" Version="8.2.250402"/>
</ItemGroup>
</Project>
60 changes: 60 additions & 0 deletions components/Adorners/samples/ElementHighlightAdornerSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<Page x:Class="AdornersExperiment.Samples.ElementHighlightAdornerSample"
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:local="using:AdornersExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">

<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="8">
<Button Content="Test Button">
<ui:AdornerLayer.Xaml>
<Canvas x:Name="AdornerCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ui:FrameworkElementExtensions.EnableActualSizeBinding="True"
Background="Cyan"
IsHitTestVisible="False"
Opacity="0.3"
Visibility="{x:Bind IsAdornerVisible, Mode=OneWay}">

<!-- Horizontal Bound Lines -->
<Line Stroke="Cyan"
StrokeThickness="1"
X1="-10000"
X2="10000"
Y1="0"
Y2="0" />
<!-- TODO: Can't use x:Bind due to Uno bug: https://github.com/unoplatform/uno/issues/7893 -->
<Line Stroke="Cyan"
StrokeThickness="1"
X1="-10000"
X2="10000"
Y1="{Binding ElementName=AdornerCanvas, Path=(ui:FrameworkElementExtensions.ActualHeight)}"
Y2="{Binding ElementName=AdornerCanvas, Path=(ui:FrameworkElementExtensions.ActualHeight)}" />

<!-- Vertical Bound Lines -->
<Line HorizontalAlignment="Left"
Stroke="Cyan"
StrokeThickness="1"
X1="0"
X2="0"
Y1="-10000"
Y2="10000" />
<Line HorizontalAlignment="Right"
Stroke="Cyan"
StrokeThickness="1"
X1="{Binding ElementName=AdornerCanvas, Path=(ui:FrameworkElementExtensions.ActualWidth)}"
X2="{Binding ElementName=AdornerCanvas, Path=(ui:FrameworkElementExtensions.ActualWidth)}"
Y1="-10000"
Y2="10000" />
</Canvas>
</ui:AdornerLayer.Xaml>
</Button>
<!-- TODO: Maybe we want to use XAML Behaviors to have PointerEntered on Button and then PointerExit on Adorner to show/hide? -->
<TextBlock Text="Click the checkbox to see the highlight." />
</StackPanel>
</Page>
19 changes: 19 additions & 0 deletions components/Adorners/samples/ElementHighlightAdornerSample.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace AdornersExperiment.Samples;

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
[ToolkitSampleBoolOption("IsAdornerVisible", false, Title = "Is Adorner Visible")]

[ToolkitSample(id: nameof(ElementHighlightAdornerSample), "Highlighting an Element w/ Adorner", description: "A sample for showing how to highlight an element's bounds with an Adorner.")]
public sealed partial class ElementHighlightAdornerSample : Page
{
public ElementHighlightAdornerSample()
{
this.InitializeComponent();
}
}
Loading
Loading