Skip to content

Commit d620e8b

Browse files
Merge pull request #760 from CommunityToolkit/llama/adorner-latest
[XAML] Modern Adorners
2 parents e648750 + 2a81bc4 commit d620e8b

File tree

55 files changed

+2894
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2894
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@ECHO OFF
2+
3+
powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project>
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))" Condition="Exists('$([MSBuild]::GetPathOfFileAbove(Directory.Build.props))')" />
3+
4+
<PropertyGroup>
5+
<ToolkitComponentName>Adorners</ToolkitComponentName>
6+
<LangVersion>preview</LangVersion>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
11+
12+
<ProjectReference Include="$(RepositoryDirectory)components\CanvasView\src\CommunityToolkit.WinUI.Controls.CanvasView.csproj" />
13+
</ItemGroup>
14+
15+
<!-- Sets this up as a toolkit component's sample project -->
16+
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
17+
</Project>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
title: Adorners
3+
author: michael-hawker
4+
description: Adorners let you overlay content on top of your XAML components in a separate layer on top of everything else.
5+
keywords: Adorners, Control, Layout, InfoBadge, AdornerLayer, AdornerDecorator, Adorner, Input Validation, Resize, Highlighting
6+
dev_langs:
7+
- csharp
8+
category: Controls
9+
subcategory: Layout
10+
discussion-id: 278
11+
issue-id: 0
12+
icon: assets/icon.png
13+
---
14+
15+
# Adorners
16+
17+
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.
18+
19+
## Background
20+
21+
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.
22+
23+
### Without Adorners
24+
25+
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.
26+
27+
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:
28+
29+
> [!SAMPLE InfoBadgeWithoutAdorner]
30+
31+
It also, by default, gets confined to the perimeter of the button and clipped, as seen above.
32+
33+
### With Adorners
34+
35+
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:
36+
37+
> [!SAMPLE AdornersInfoBadgeSample]
38+
39+
You can see how Adorners react to more dynamic content with this more complete example here:
40+
41+
> [!SAMPLE AdornersTabBadgeSample]
42+
43+
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.
44+
45+
## Highlight Example
46+
47+
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:
48+
49+
> [!SAMPLE ElementHighlightAdornerSample]
50+
51+
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.
52+
53+
## Custom Adorner Example
54+
55+
Adorners can be subclassed in order to encapsulate specific logic and/or styling for your scenario.
56+
For instance, you may want to create a custom Adorner that allows a user to click and edit a piece of text in place.
57+
The following example uses `IEditableObject` to control the editing lifecycle coordinated with a typical MVVM pattern binding:
58+
59+
> [!SAMPLE InPlaceTextEditorAdornerSample]
60+
61+
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.
62+
63+
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`.
64+
65+
## Migrating from WPF
66+
67+
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.
68+
69+
### Concepts
70+
71+
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.
72+
73+
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.
74+
75+
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.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!-- 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. -->
2+
<Page x:Class="AdornersExperiment.Samples.AdornersInfoBadgeSample"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
8+
xmlns:ui="using:CommunityToolkit.WinUI"
9+
mc:Ignorable="d">
10+
11+
<Button>
12+
<SymbolIcon HorizontalAlignment="Center"
13+
Symbol="Mail" />
14+
<ui:AdornerLayer.Xaml>
15+
<muxc:InfoBadge Margin="-4"
16+
HorizontalAlignment="Right"
17+
VerticalAlignment="Top"
18+
IsHitTestVisible="False"
19+
Opacity="0.9"
20+
Visibility="{x:Bind IsAdornerVisible, Mode=OneWay}"
21+
Value="5" />
22+
</ui:AdornerLayer.Xaml>
23+
</Button>
24+
</Page>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace AdornersExperiment.Samples;
6+
7+
[ToolkitSampleBoolOption("IsAdornerVisible", true, Title = "Is Adorner Visible")]
8+
9+
[ToolkitSample(id: nameof(AdornersInfoBadgeSample), "InfoBadge w/ Adorner", description: "A sample for showing how add an infobadge to a component via an Adorner.")]
10+
public sealed partial class AdornersInfoBadgeSample : Page
11+
{
12+
public AdornersInfoBadgeSample()
13+
{
14+
this.InitializeComponent();
15+
}
16+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!-- 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. -->
2+
<Page x:Class="AdornersExperiment.Samples.AdornersTabBadgeSample"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:animations="using:CommunityToolkit.WinUI.Animations"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
9+
xmlns:ui="using:CommunityToolkit.WinUI"
10+
mc:Ignorable="d">
11+
12+
<muxc:TabView TabCloseRequested="TabView_TabCloseRequested">
13+
<muxc:TabViewItem Header="Home">
14+
<ui:AdornerLayer.Xaml>
15+
<muxc:InfoBadge Margin="-4,-8"
16+
HorizontalAlignment="Left"
17+
VerticalAlignment="Top"
18+
IsHitTestVisible="False"
19+
Opacity="0.9"
20+
Visibility="{x:Bind IsAdornerVisible, Mode=OneWay}"
21+
Value="{x:Bind (x:Int32)BadgeValue, Mode=OneWay}">
22+
<animations:Implicit.ShowAnimations>
23+
<animations:RotationInDegreesAnimation From="45"
24+
To="0"
25+
Duration="0:0:1" />
26+
<animations:TranslationAnimation From="0, 1, 0"
27+
To="0"
28+
Duration="0:0:1" />
29+
<animations:OpacityAnimation From="0"
30+
To="1.0"
31+
Duration="0:0:1" />
32+
</animations:Implicit.ShowAnimations>
33+
34+
<animations:Implicit.HideAnimations>
35+
<animations:OpacityAnimation To="0.0"
36+
Duration="0:0:1" />
37+
<animations:ScalarAnimation Target="Translation.Y"
38+
To="5"
39+
Duration="0:0:1">
40+
<animations:ScalarKeyFrame Key="0.1"
41+
Value="-10" />
42+
<animations:ScalarKeyFrame Key="0.5"
43+
Value="0.0" />
44+
</animations:ScalarAnimation>
45+
</animations:Implicit.HideAnimations>
46+
</muxc:InfoBadge>
47+
</ui:AdornerLayer.Xaml>
48+
</muxc:TabViewItem>
49+
<muxc:TabViewItem Header="Document 1" />
50+
<muxc:TabViewItem Header="Document 2" />
51+
</muxc:TabView>
52+
</Page>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace AdornersExperiment.Samples;
6+
7+
[ToolkitSampleBoolOption("IsAdornerVisible", true, Title = "Is Adorner Visible")]
8+
[ToolkitSampleNumericOption("BadgeValue", 3, 1, 5, 1, true, Title = "Badge Value")]
9+
10+
[ToolkitSample(id: nameof(AdornersTabBadgeSample), "InfoBadge w/ Adorner in TabView", description: "A sample for showing how add an InfoBadge to a TabViewItem via an Adorner.")]
11+
public sealed partial class AdornersTabBadgeSample : Page
12+
{
13+
public AdornersTabBadgeSample()
14+
{
15+
this.InitializeComponent();
16+
}
17+
18+
private void TabView_TabCloseRequested(MUXC.TabView sender, MUXC.TabViewTabCloseRequestedEventArgs args)
19+
{
20+
sender.TabItems.Remove(args.Tab);
21+
}
22+
}
93.3 KB
Loading
2.16 KB
Loading
38.6 KB
Loading

0 commit comments

Comments
 (0)