Skip to content

Instantly share code, notes, and snippets.

@DamianSuess
Last active September 7, 2023 21:22
Show Gist options
  • Save DamianSuess/5acb3e918223b61945ef82f136a133d0 to your computer and use it in GitHub Desktop.
Save DamianSuess/5acb3e918223b61945ef82f136a133d0 to your computer and use it in GitHub Desktop.
Avalonia Custom Behaviors

Avalonia Clickable Behaviors

Labels by default do not allow for users to add Command bindings, now you can!

This sample provides 2.5 unique ways of handling Tapped event via Avalonia Behaviors on a control. The 0.5 is the use-case of OnDoubleTapped where it uses a CompositeDisposable to handle the disposing of the event handler versus a traditional method for the event handler.

using System.Windows.Input;
using Avalonia;
using Avalonia.Xaml.Interactivity;
namespace SuessLabs.Tool.Common.Behaviors;
/// <summary>Base class for behavior command binding event on its source and executes its actions when that event is fired.</summary>
/// <typeparam name="T">Type of behavior.</typeparam>
public class CommandBehaviorBase<T>
: Behavior<T>
where T
: AvaloniaObject
{
/// <summary>Defines the <see cref="CommandParameter"/> property.</summary>
public static readonly StyledProperty<object> CommandParameterProperty =
AvaloniaProperty.Register<CommandBehaviorBase<T>, object>(nameof(CommandParameter));
/// <summary>Defines the <see cref="Command"/> property.</summary>
public static readonly DirectProperty<CommandBehaviorBase<T>, ICommand> CommandProperty =
AvaloniaProperty.RegisterDirect<CommandBehaviorBase<T>, ICommand>(
nameof(Command),
commandBehavior => commandBehavior.Command,
(commandBehavior, command) => commandBehavior.Command = command,
enableDataValidation: true);
private ICommand _command;
/// <summary>
/// Gets or sets an <see cref="ICommand"/> to be invoked when the button is clicked.
/// </summary>
public ICommand Command
{
get => _command;
set => SetAndRaise(CommandProperty, ref _command, value);
}
/// <summary>Gets or sets a parameter to be passed to the <see cref="Command"/>.</summary>
public object CommandParameter {
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
protected bool ExecuteCommand()
{
if (Command is { } && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
return true;
}
return false;
}
}
using System.Reactive.Disposables;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace SuessLabs.Common.Behaviors;
public class CommandOnDoubleTappedBehavior : CommandBehaviorBase<InputElement>
{
private CompositeDisposable _disposables;
protected override void OnAttached()
{
base.OnAttached();
_disposables.Add(AssociatedObject.AddDisposableHandler(InputElement.DoubleTappedEvent, (sender, e) => e.Handled = ExecuteCommand()));
}
protected override void OnDetaching()
{
base.OnDetaching();
_disposables?.Dispose();
}
}
/*
USAGE:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
xmlns:ic="using:SuessLabs.Tool.Common.Behaviors"
...>
<Label Grid.Column="1"
Content="{Binding MessageDisplayed}"
FontStyle="Italic">
<i:Interaction.Behaviors>
<ic:CommandOnClickBehavior Command="{Binding CmdCopyText}" CommandParameter="{Binding MessageDisplayed}" />
</i:Interaction.Behaviors>
</Label>
<Label Grid.Column="2" FontStyle="Italic">
<PathIcon Height="15" Data="{StaticResource document_copy_regular}" />
<i:Interaction.Behaviors>
<ic:CommandOnTappedBehavior Command="{Binding CmdCopyText}" CommandParameter="{Binding MessageDisplayed}" />
</i:Interaction.Behaviors>
</Label>
*/
using System;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace SuessLabs.Tool.Common.Behaviors;
public class CommandOnTappedBehavior : CommandBehaviorBase<InputElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Tapped += OnTapped;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Tapped += OnTapped;
}
private void OnTapped(object sender, RoutedEventArgs e) => e.Handled = ExecuteCommand();
}
/*
View USAGE:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
xmlns:ic="using:SuessLabs.Tool.Common.Behaviors"
...>
<Label Grid.Column="1"
Content="{Binding MessageDisplayed}"
FontStyle="Italic">
<i:Interaction.Behaviors>
<ic:LabelClickEventTriggerBehavior KeyModifiers="Control">
<ia:InvokeCommandAction Command="{Binding CmdCopyText}" CommandParameter="{Binding MessageDisplayed}" />
</ic:LabelClickEventTriggerBehavior>
</i:Interaction.Behaviors>
</Label>
<!-- Label with an Icon -->
<Label Grid.Column="2" FontStyle="Italic">
<PathIcon Height="15" Data="{StaticResource document_copy_regular}" />
<i:Interaction.Behaviors>
<ic:LabelClickEventTriggerBehavior KeyModifiers="Control">
<ia:InvokeCommandAction Command="{Binding CmdCopyText}" CommandParameter="{Binding MessageDisplayed}" />
</ic:LabelClickEventTriggerBehavior>
</i:Interaction.Behaviors>
</Label>
*/
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
namespace SuessLabs.Common.Behaviors;
/// <summary>
/// A behavior that listens for a <see cref="Button.ClickEvent"/> event on its source and executes its actions when that event is fired.
/// </summary>
public class LabelClickEventTriggerBehavior : Trigger<Label>
{
/// <summary>
/// Identifies the <seealso cref="KeyModifiers"/> avalonia property.
/// </summary>
public static readonly StyledProperty<KeyModifiers> KeyModifiersProperty =
AvaloniaProperty.Register<LabelClickEventTriggerBehavior, KeyModifiers>(nameof(KeyModifiers));
/// <summary>Gets or sets a value indicating whether the pointer still inside of the control or not.</summary>
private bool _isPointerInside;
/// <summary>
/// Gets or sets the required key modifiers to execute <see cref="Button.ClickEvent"/> event handler. This is a avalonia property.
/// </summary>
public KeyModifiers KeyModifiers
{
get => GetValue(KeyModifiersProperty);
set => SetValue(KeyModifiersProperty, value);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree()
{
if (AssociatedObject is { })
{
AssociatedObject.Tapped += OnTapped;
AssociatedObject.PointerEnter += OnPointerEnter;
AssociatedObject.PointerLeave += OnPointerLeave;
}
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree()
{
if (AssociatedObject is { })
{
AssociatedObject.Tapped -= OnTapped;
AssociatedObject.PointerEnter -= OnPointerEnter;
AssociatedObject.PointerLeave -= OnPointerLeave;
}
}
private void OnPointerEnter(object sender, PointerEventArgs e)
{
_isPointerInside = true;
}
private void OnPointerLeave(object sender, PointerEventArgs e)
{
_isPointerInside = false;
}
private void OnTapped(object? sender, RoutedEventArgs e)
{
if (AssociatedObject is { } && _isPointerInside)
{
Interaction.ExecuteActions(AssociatedObject, Actions, e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment