Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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% %*
10 changes: 10 additions & 0 deletions components/Adorners/samples/Adorners.Samples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" Condition="Exists('$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))')" />

<PropertyGroup>
<ToolkitComponentName>Adorners</ToolkitComponentName>
</PropertyGroup>

<!-- Sets this up as a toolkit component's sample project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
</Project>
67 changes: 67 additions & 0 deletions components/Adorners/samples/Adorners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
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
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]

## TODO: Resize Example

Another common use case for adorners is to allow a user to resize a visual element.

// TODO: Make an example here for this w/ custom Adorner class...

## 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();
}
}
23 changes: 23 additions & 0 deletions components/Adorners/samples/InfoBadgeWithoutAdorner.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- 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.InfoBadgeWithoutAdorner"
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"
mc:Ignorable="d">

<Button HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<SymbolIcon HorizontalAlignment="Center"
Symbol="Mail" />
<muxc:InfoBadge Margin="0,-8,-16,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="{x:Bind IsNotificationVisible, Mode=OneWay}"
Value="5" />
</Grid>
</Button>
</Page>
16 changes: 16 additions & 0 deletions components/Adorners/samples/InfoBadgeWithoutAdorner.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("IsNotificationVisible", true, Title = "Is Notification Visible")]

[ToolkitSample(id: nameof(InfoBadgeWithoutAdorner), "InfoBadge w/o Adorner", description: "A sample for showing how one adds an infobadge to a component without an Adorner (from WinUI Gallery app).")]
public sealed partial class InfoBadgeWithoutAdorner : Page
{
public InfoBadgeWithoutAdorner()
{
this.InitializeComponent();
}
}
Loading
Loading