In this article we demonstrate how to use and write XAML Behaviors in a WinUI 3 Desktop app. We’ll cover some of the existing behaviors that are exposed via Community Toolkit and its sample app, and we also write a new one. XAML Behaviors have their name well chosen: they are a way to declaratively extend XAML elements with extra behavior. This avoids writing code-behind in the view that host the element, or in a child class. As an example, here’s how we decorate a TextBlock with the capability of automatically transforming the URLs in its text to real hyperlinks:
<TextBlock x:Name="url">
<interactivity:Interaction.Behaviors>
<sample:TextBlockHyperlinkBehavior />
</interactivity:Interaction.Behaviors>
</TextBlock>
All the code in this article comes from our WinUI 3 desktop sample app that looks like this:

Some history
XAML Behaviors are almost as old as XAML itself: they were introduced in Blend for Silverlight 3. Most of the classic behaviors (like Event-to-Command) were meant to ease the use of XAML controls in application frameworks and architectures such as MVVM. Very soon after Silverlight, WPF had its own XAML Behaviors repo – which is still actively maintained. In the Windows 8 era, the Microsoft.Xaml.Interactivity namespace with the core behavior classes disappeared from the ecosystem. A team of heroic Windows Developer MVPs resurrected these into the WinRtBehaviors repo. This repo later evolved into the XamlBehaviors for UWP that we still have today. The base classes are actively maintained there, and were made compatible with WinUI. Other XAML technologies also have Behavior repos in the open. All of these started with a port from the WPF or the UWP version: here’s one for .NET MAUI, one for Uno Platform, and one for Avalonia.
There are requests here and there to move the classic XamlBehaviors repo to Microsoft.UI.Xaml namespace, but there’s not much action around these. While it would make sense to move the Behavior base classes into Windows App SDK, there’s a lot in the repo that is less relevant. There’s for example a collection of reusable Actions to assemble behaviors in XAML. Most of these classic WPF/UWP behavior helpers make no sense in a WinUI context. In WinUI, controls expose Commands next to their Events and we’re capable of binding to methods. We can easily live without InvokeCommand and CallMethod. The repo also contains a bunch of sample behaviors that are collecting dust – the last modification in that area was 7 years ago.
For WinUI, here’s the good news: it’s Community Toolkit Behaviors that hosts the new XAML Behaviors. The package uses the ‘old’ XamlBehaviors sources via XamlBehaviors.WinUI.Managed which does not contain the legacy stuff we just mentioned. Community Toolkit adds a new base class and a handful of useful modern XAML behaviors, but the old base classes still do the heavy lifting.
How it works
XAML Behaviors are classes that are attached to a control, and then start listening to changes inside that control (typically a raised event or a property change) to trigger an action. A helper class –Interaction– puts Behaviors into a collection and assigns that collection to a control as a custom attached property (a dependency property that stores its value externally). You don’t need much infrastructure to make this happen:
- a description for a behavior -the IBehavior interface-,
- base classes to get you up to speed immediately, think Behavior and Behavior<T>, and
- a class to hook behaviors to a XAML control which then becomes the behavior’s AssociatedObject: Interaction.
All of these live in the Microsoft.Xaml.Interactivity.Shared project of the ‘classic’ XamlBehaviors package and are still relevant for WinUI:


The canonical example
The AutoFocus behavior is the ‘Hello World’ of XAML Behaviors. It sets the focus to the AssociatedObject when it’s loaded. Here’s how to use it:
<TextBox Text="My content is selected when loaded">
<interactivity:Interaction.Behaviors>
<behaviors:AutoFocusBehavior />
</interactivity:Interaction.Behaviors>
</TextBox>
As mentioned, the behavior is attached to the TextBox through an Interaction instance. That instance can carry multiple behaviors. In the next XAML snippet we not only focus the TextBox but also preselect its entire content:
<TextBox Text="My content is selected when loaded">
<interactivity:Interaction.Behaviors>
<behaviors:AutoFocusBehavior />
<behaviors:AutoSelectBehavior />
</interactivity:Interaction.Behaviors>
</TextBox>
Here’s the reason why AutoFocus is the canonical XAML Behavior example. Its custom class definition is literally a one-liner:
public sealed class AutoFocusBehavior : BehaviorBase<Control>
{
protected override void OnAssociatedObjectLoaded() => AssociatedObject.Focus(FocusState.Programmatic);
}
Yes: in code-behind this would also be an easy job. Apart from creating a Loaded event handler and giving TextBox an x:Name you would need to weave the behavior logic through the rest of the code. When there are multiple controls in a view and they require more complex behavior logic, then spaghetti code is guaranteed.
If you’re lucky, there’s no need to create a custom class. For the sake of completeness, here’s how to use the helper Actions that we mentioned before. Here’s how to assemble an obsolete(!) XAML Behavior that turns a Click event into a Command call:
<Button x:Name="button1">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="Click" SourceObject="{Binding ElementName=button1}">
<Interactions:InvokeCommandAction Command="{Binding UpdateCountCommand}"/>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Windows Community Toolkit Behaviors
All XAML Behavior goodies for WinUI are exposed via Windows Community Toolkit. Its WinUI Behaviors NuGet package hosts some useful behaviors that are more than just one-liners. Let’s take a look at some of these.
ViewportBehavior
ViewPortBehavior is a class that monitors its associated FrameworkElement entering or exiting a ScrollViewer viewport and raises appropriate events. The behavior is useful in an autoplay scenario.
It holds a field to store the ScrollViewer and defines four events:
public class ViewportBehavior : BehaviorBase<FrameworkElement>
{
private ScrollViewer _hostScrollViewer;
public event EventHandler EnteredViewport;
public event EventHandler ExitedViewport;
public event EventHandler EnteringViewport;
public event EventHandler ExitingViewport;
// ...
}
When it’s attached to the element, it looks up the ScrollViewer and registers an event handler for its ViewChanged:
_hostScrollViewer = AssociatedObject.FindAscendant<ScrollViewer>();
if (_hostScrollViewer == null)
{
throw new InvalidOperationException("This behavior can only be attached to an element which has a ScrollViewer as a parent.");
}
_hostScrollViewer.ViewChanged += ParentScrollViewer_ViewChanged;
That handler has the logic to raise in turn one of the four behavior events, check the source code here.
When hooking the behavior to an element, you provide your own event handlers for them, like in this code snippet from our sample app:
<ScrollViewer>
<Grid Height="3000">
<Border Width="100"
Height="100">
<interactivity:Interaction.Behaviors>
<behaviors:ViewportBehavior x:Name="ViewportBehavior"
IsAlwaysOn="True"
EnteringViewport="Element_EnteringViewport"
EnteredViewport="Element_EnteredViewport"
ExitingViewport="Element_ExitingViewport"
ExitedViewport="Element_ExitedViewport" />
</interactivity:Interaction.Behaviors>
<FontIcon Glyph=""
FontSize="40" />
</Border>
</Grid>
</ScrollViewer>
Our sample app just updates the text of a TextBlock. Here’s how the demo looks like in practice:

FadeHeaderBehavior
FadeHeaderBehavior makes the header of a ListView or a GridView fade away when scrolling. It shares some code with ViewportBehavior, but instead of raising events it applies an animation:
// Use the ScrollViewer's Y offset and the header's height to calculate the opacity percentage. Clamp it between 0% and 100%
var headerHeight = (float)HeaderElement.RenderSize.Height;
var opacityExpression = ExpressionFunctions.Clamp(1 - (-scrollPropSet.Translation.Y / headerHeight), 0, 1);
// Begin animating
var targetElement = ElementCompositionPreview.GetElementVisual(HeaderElement);
targetElement.StartAnimation("Opacity", opacityExpression);
Optionally, you can provide the part of the header to which the animation should apply. Behaviors can have dependency properties:
public static readonly DependencyProperty HeaderElementProperty = DependencyProperty.Register(
nameof(HeaderElement),
typeof(UIElement),
typeof(FadeHeaderBehavior),
new PropertyMetadata(null, PropertyChangedCallback));
public UIElement HeaderElement
{
get { return (UIElement)GetValue(HeaderElementProperty); }
set { SetValue(HeaderElementProperty, value); }
}
Here’s how our sample app configures the behavior:
<ListView>
<interactivity:Interaction.Behaviors>
<behaviors:FadeHeaderBehavior HeaderElement="{Binding ElementName=TheGrid}" />
</interactivity:Interaction.Behaviors>
<ListView.Header>
<Grid x:Name="TheGrid"
MinHeight="50"
Background="{ThemeResource SystemAccentColor}">
<!-- ... -->
</Grid>
</ListView.Header>
<!-- ... -->
</ListView>
And this is how it looks like in practice:

Hidden Gems
All of the previous samples are exposed by Community Toolkit and you can test them with their sample app. Many open-sourced desktop applications use behaviors internally. WinUI Gallery for example has its own ImageScrollBehavior, a more powerful version of FadeHeaderBehavior. Another of these hidden gems is TextBlockHyperlinkBehavior from the Community Toolkit Sample app itself. It conveniently automagically transforms the urls in a TextBlock to hyperlinks.
This is how the About page of our sample app does hyperlinks:
<TextBlock>
<Run>This WinUI 3 Desktop app demonstrates</Run>
<LineBreak />
<Run Text="* some of the Community Toolkit XAML Behaviors found in " /><Hyperlink NavigateUri="https://aka.ms/windowstoolkitapp">Windows Community Toolkit Sample App</Hyperlink><Run Text=", and" />
<LineBreak />
<Run Text="* a WinUI 3 version of a XAML Behavior found in " /><Hyperlink NavigateUri="https://github.com/CommunityToolkit/Maui">.NET MAUI Community Toolkit</Hyperlink><Run Text="." />
</TextBlock>
A hardcoded TextBlock-Run-Hyperlink construction is OK when the text is fixed, but what if the content came from a database or a resource file? Enter TextBlockHyperlinkBehavior. Here’s how our sample app applies it:
<TextBlock x:Name="url">
<interactivity:Interaction.Behaviors>
<sample:TextBlockHyperlinkBehavior />
</interactivity:Interaction.Behaviors>
</TextBlock>
At runtime we provide a text containing some URLs:
url.Text = "Please find the blog post at https://xamlbrewer.wordpress.com/ and the source code repo at https://github.com/XamlBrewer .";
This is what happens at runtime, the two URLs in the text are transformed to real hyperlinks:

Here’s the structure of the behavior class. It uses a regular expression to recognize URLs, and reacts on PropertyChanged of its associated element’s Text property. No surprises here:
public class TextBlockHyperlinkBehavior : BehaviorBase<TextBlock>
{
private static readonly Regex UrlRegex =
new Regex(@"(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?",
RegexOptions.Compiled);
private long? _textChangedRegistration;
protected override bool Initialize()
{
if (_textChangedRegistration == null)
{
_textChangedRegistration = AssociatedObject.RegisterPropertyChangedCallback(TextBlock.TextProperty, Text_PropertyChanged);
}
return base.Initialize();
}
protected override void OnDetaching()
{
if (AssociatedObject != null && _textChangedRegistration != null && _textChangedRegistration.HasValue)
{
AssociatedObject.UnregisterPropertyChangedCallback(TextBlock.TextProperty, _textChangedRegistration.Value);
}
base.OnDetaching();
}
// ...
}
You find the details on the transformation of the string into Run and Hyperlink instances in the full source code. We’re a huge fan of this behavior, but for the sake of completeness, allow us to mention the Community Toolkit’s MarkdownTextBlock as a decent alternative.
Rolling your own
After dissecting some existing complex XAML behaviors, we’re ready to roll one ourselves: an extension of AutoSuggestBox that notifies you when the user stopped typing in its text box and it’s time to run the potentially expensive query to lookup the suggestions. There’s a .NET MAUI version of this XAML Behavior right here. In our sample app, we first created a version in code-behind and then refactored it into a XAML behavior.
Here’s how to apply our UserStoppedTyping behavior:
<AutoSuggestBox PlaceholderText="Search"
QueryIcon="Find"
QuerySubmitted="AutoSuggestBox_QuerySubmitted">
<interactivity:Interaction.Behaviors>
<brewhaviors:UserStoppedTypingBehavior UserStoppedTyping="AutoSuggestBox_UserStoppedTyping"
MinimumDelay="1500"
MinimumCharacters="2" />
</interactivity:Interaction.Behaviors>
</AutoSuggestBox>
We used one of the classic base classes as parent, added a field for a timer, properties for the minimum delay to detect if the user stopped typing and the minimum length of the input for raising the event, and then the event itself:
public class UserStoppedTypingBehavior : Behavior<AutoSuggestBox>
{
private DispatcherTimer timer;
public int MinimumDelay { get; set; } = 3000;
public int MinimumCharacters { get; set; } = 3;
public event EventHandler UserStoppedTyping;
// ...
}
When the associated AutoSuggestBox is loaded, we register event handlers for its TextChanged and for our own timer’s Tick. We live to the “for every += there should be a -=” principle, so we nicely unregister everything when detaching:
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += AssociatedObject_TextChanged;
timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(MinimumDelay) };
timer.Tick += Timer_Tick;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= AssociatedObject_TextChanged;
timer.Tick -= Timer_Tick;
}
When the input text is long enough, and it was changed by the user typing (not by selecting a suggestion) then we start the timer:
private void AssociatedObject_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if ((args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) && (sender.Text.Length >= MinimumCharacters))
{
timer.Start();
}
else
{
timer.Stop();
}
}
When the timer elapses, we raise our event:
private void Timer_Tick(object sender, object e)
{
UserStoppedTyping?.Invoke(AssociatedObject, null);
timer.Stop();
}
Side note: if you prefer to raise a strongly typed event (one with AutoSuggestBox instead of object as sender) then it suffices to bring in the following delegate:
public delegate void EventHandler<in TSender, in TArgs>(TSender sender, TArgs args);
Then you can define the event as follows:
public event EventHandler<AutoSuggestBox, EventArgs> UserStoppedTyping;
Our AutoSuggestBox’s reaction to the UserStoppedTyping event is the same as if the user pressed <ENTER> or clicked the search icon (QuerySubmitted). Here’s the code in the view – the list of cats and the lookup code was borrowed from WinUI 3 Gallery:
private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
DisplaySuggestions(sender);
}
private void AutoSuggestBox_UserStoppedTyping(object sender, EventArgs e)
{
DisplaySuggestions(sender as AutoSuggestBox);
}
private void DisplaySuggestions(AutoSuggestBox sender)
{
var suitableItems = new List<string>();
var splitText = sender.Text.ToLower().Split(" ");
foreach (var cat in Cats)
{
var found = splitText.All((key) =>
{
return cat.ToLower().Contains(key);
});
if (found)
{
suitableItems.Add(cat);
}
}
if (suitableItems.Count == 0)
{
suitableItems.Add("No results found");
}
sender.ItemsSource = suitableItems;
}
Here’s how all of this looks like at runtime – with a minimum input length of 2 and a wait time of 1.5 seconds:

Programmatically adding a Behavior
XAML Behaviors were created to declaratively decorate an element. But what if the attachment depends on a runtime condition, or you want to attach a behavior to a control that is created programmatically? Well, here’s how to programmatically attach a behavior. We start with a named AutoSuggestBox:
<AutoSuggestBox x:Name="TheBox"
PlaceholderText="Search"
QueryIcon="Find"
QuerySubmitted="AutoSuggestBox_QuerySubmitted">
</AutoSuggestBox>
Then we do in C#-behind exactly the same as in the previous XAML snippets. We create a collection of behaviors and hook it to the element, instantiate and configure our behavior, and put it in the collection:
var behaviors = Interaction.GetBehaviors(TheBox);
var userStoppedTyping = new UserStoppedTypingBehavior { MinimumDelay = 1500, MinimumCharacters = 2 };
userStoppedTyping.UserStoppedTyping += AutoSuggestBox_UserStoppedTyping;
behaviors.Add(userStoppedTyping);
That’s all there is to attach a XAML behavior at runtime.
Sprinkling some Windows 11 dust
Since this is our first sample app that was developed on Windows 11, we took the opportunity to add some Windows 11 – only WinUI features.
Mica
The app uses Mica material as its background. When you look at the overly complex way of applying it to your WinUI app, you may be tempted to give up. Fortunately Morten Nielsen’s WinUIEx NuGet package provides a nice and much easier way. Using WindowEx you can not only declaratively assign Title and Icon, but also assign a Backdrop such as mica:
<winex:WindowEx x:Class="XamlBrewer.WinUI3.XamlBehaviors.Sample.Shell"
Title="XAML Brewer WinUI 3 XAML Behaviors Sample"
TaskBarIcon="Assets/Beer.ico"
xmlns:winex="using:WinUIEx">
<winex:WindowEx.Backdrop>
<winex:MicaSystemBackdrop />
</winex:WindowEx.Backdrop>
<!-- ... -->
</winex:WindowEx>
If Window were a DependencyObject, we would have refactored mica Backdrop as a XAML Behavior…
InfoBadge
The blue dot in the sample app’s UserStoppedTyping header is an instance of the new InfoBadge control. We followed this guide to create a custom accent color scheme around beer colors, and use the complementary color for the badge:

InfoBadge doesn’t seem to display icons correctly yet (numerical values show OK, however):
<InfoBadge Background="{ThemeResource SystemAccentColorComplementary}"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<!-- No show (neither does SymbolIcon) -->
<!--<InfoBadge.IconSource>
<FontIconSource Glyph="" />
</InfoBadge.IconSource>-->
</InfoBadge>
To bring us back to our original topic: decorating a XAML element with an InfoBadge can be written as a XAML Behavior.
After all these years, the XamlBehaviors base classes remain solid and relevant. So keep on writing new XAML Behaviors in your WinUI apps.
Our WinUI app lives here on NuGet. It has room for extra samples ….
Enjoy!