Using RoutedEventTriggerBehavior, ie. no reflection
ADD
WalletWasabi.Fluent/Behaviors/RoutedEventTriggerBehavior.cs
+using Avalonia;
+using Avalonia.Interactivity;
+using Avalonia.Xaml.Interactivity;
+
+namespace WalletWasabi.Fluent.Behaviors
+{
+ /// <summary>
+ /// A behavior that listens for a <see cref="RoutedEvent"/> event on its source and executes its actions when that event is fired.
+ /// </summary>
+ public class RoutedEventTriggerBehavior : Trigger<Interactive>
+ {
+ /// <summary>
+ /// Identifies the <seealso cref="RoutedEvent"/> avalonia property.
+ /// </summary>
+ public static readonly StyledProperty<RoutedEvent?> RoutedEventProperty =
+ AvaloniaProperty.Register<RoutedEventTriggerBehavior, RoutedEvent?>(nameof(RoutedEvent));
+
+ /// <summary>
+ /// Identifies the <seealso cref="RoutingStrategies"/> avalonia property.
+ /// </summary>
+ public static readonly StyledProperty<RoutingStrategies> RoutingStrategiesProperty =
+ AvaloniaProperty.Register<RoutedEventTriggerBehavior, RoutingStrategies>(nameof(RoutingStrategies), RoutingStrategies.Direct | RoutingStrategies.Bubble);
+
+ /// <summary>
+ /// Identifies the <seealso cref="SourceInteractive"/> avalonia property.
+ /// </summary>
+ public static readonly StyledProperty<Interactive?> SourceInteractiveProperty =
+ AvaloniaProperty.Register<RoutedEventTriggerBehavior, Interactive?>(nameof(SourceInteractive));
+
+ private bool _isInitialized;
+ private bool _isAttached;
+
+ /// <summary>
+ /// Gets or sets routing event to listen for. This is a avalonia property.
+ /// </summary>
+ public RoutedEvent? RoutedEvent
+ {
+ get => GetValue(RoutedEventProperty);
+ set => SetValue(RoutedEventProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the routing event <see cref="RoutingStrategies"/>. This is a avalonia property.
+ /// </summary>
+ public RoutingStrategies RoutingStrategies
+ {
+ get => GetValue(RoutingStrategiesProperty);
+ set => SetValue(RoutingStrategiesProperty, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the source object from which this behavior listens for events.
+ /// If <seealso cref="SourceInteractive"/> is not set, the source will default to <seealso cref="Behavior.AssociatedObject"/>. This is a avalonia property.
+ /// </summary>
+ public Interactive? SourceInteractive
+ {
+ get => GetValue(SourceInteractiveProperty);
+ set => SetValue(SourceInteractiveProperty, value);
+ }
+
+ static RoutedEventTriggerBehavior()
+ {
+ RoutedEventProperty.Changed.Subscribe(OnValueChanged);
+ RoutingStrategiesProperty.Changed.Subscribe(OnValueChanged);
+ SourceInteractiveProperty.Changed.Subscribe(OnValueChanged);
+ }
+
+ private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
+ {
+ if (args.Sender is not RoutedEventTriggerBehavior behavior || behavior.AssociatedObject is null)
+ {
+ return;
+ }
+
+ if (behavior._isInitialized && behavior._isAttached)
+ {
+ behavior.RemoveHandler();
+ behavior.AddHandler();
+ }
+ }
+
+ /// <inheritdoc />
+ protected override void OnAttachedToVisualTree()
+ {
+ _isAttached = true;
+ AddHandler();
+ }
+
+ /// <inheritdoc />
+ protected override void OnDetachedFromVisualTree()
+ {
+ _isAttached = false;
+ RemoveHandler();
+ }
+
+ private void AddHandler()
+ {
+ var interactive = ComputeResolvedSourceInteractive();
+ if (interactive is { } && RoutedEvent is { })
+ {
+ interactive.AddHandler(RoutedEvent, Handler, RoutingStrategies);
+ _isInitialized = true;
+ }
+ }
+
+ private void RemoveHandler()
+ {
+ var interactive = ComputeResolvedSourceInteractive();
+ if (interactive is { } && RoutedEvent is { } && _isInitialized)
+ {
+ interactive.RemoveHandler(RoutedEvent, Handler);
+ _isInitialized = false;
+ }
+ }
+
+ private Interactive? ComputeResolvedSourceInteractive()
+ {
+ return GetValue(SourceInteractiveProperty) is { } ? SourceInteractive : AssociatedObject;
+ }
+
+ private void Handler(object? sender, RoutedEventArgs e)
+ {
+ var interactive = ComputeResolvedSourceInteractive();
+ if (interactive is { })
+ {
+ Interaction.ExecuteActions(interactive, Actions, e);
+ }
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/Views/MainView.axaml
WalletWasabi.Fluent/Views/MainView.axaml
xmlns:navbar="using:WalletWasabi.Fluent.Views.NavBar"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
- xmlns:behaviors="using:WalletWasabi.Fluent.Behaviors"
+ xmlns:b="using:WalletWasabi.Fluent.Behaviors"
xmlns:ext="using:WalletWasabi.Fluent.Helpers"
xmlns:vm="using:WalletWasabi.Fluent.ViewModels"
xmlns:statusBar="clr-namespace:WalletWasabi.Fluent.Views.StatusBar"
<Border x:Name="SearchDropDownDismissArea" Background="Transparent" Grid.RowSpan="2" Grid.ColumnSpan="3" IsVisible="False">
<i:Interaction.Behaviors>
- <ia:EventTriggerBehavior EventName="PointerPressed">
+ <b:RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.PointerPressedEvent}">
<ia:ChangePropertyAction TargetObject="{Binding #SearchBarDropdown}" PropertyName="IsVisible" Value="False" />
<ia:ChangePropertyAction TargetObject="{Binding #SearchDropDownDismissArea}" PropertyName="IsVisible" Value="False" />
- </ia:EventTriggerBehavior>
+ </b:RoutedEventTriggerBehavior>
</i:Interaction.Behaviors>
</Border>
<searchBar:SearchBar x:Name="SearchBar" VerticalAlignment="Center" DataContext="{Binding SearchBar}" Grid.Column="1"
HorizontalAlignment="Center" Width="280">
<i:Interaction.Behaviors>
- <behaviors:PointerPressTriggerBehavior>
+ <b:RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.PointerPressedEvent}" RoutingStrategies="Tunnel">
<ia:ChangePropertyAction TargetObject="{Binding #SearchBarDropdown}" PropertyName="IsVisible" Value="True" />
<ia:ChangePropertyAction TargetObject="{Binding #SearchDropDownDismissArea}" PropertyName="IsVisible" Value="True" />
- </behaviors:PointerPressTriggerBehavior>
+ </b:RoutedEventTriggerBehavior>
</i:Interaction.Behaviors>
</searchBar:SearchBar>
<searchBar:SearchBarDropdown x:Name="SearchBarDropdown" Width="500" MaxHeight="500" VerticalAlignment="Top" IsVisible="False" Grid.Row="1"

MODIFY
WalletWasabi.Fluent/Views/SearchBar/SearchBarDropdown.axaml
WalletWasabi.Fluent/Views/SearchBar/SearchBarDropdown.axaml
x:CompileBindings="False"
x:Name="UserControl"
x:DataType="searchBar:SearchBarViewModel"
- xmlns:conv="clr-namespace:WalletWasabi.Fluent.Converters" Background="{StaticResource SystemBaseLowColor}">
+ xmlns:conv="clr-namespace:WalletWasabi.Fluent.Converters"
+ xmlns:b="clr-namespace:WalletWasabi.Fluent.Behaviors"
+ Background="{StaticResource SystemBaseLowColor}">
<Design.DataContext>
<searchBar:SearchBarDesignViewModel />
</DockPanel>
<i:Interaction.Behaviors>
- <EventTriggerBehavior EventName="PointerPressed">
+ <b:RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.PointerPressedEvent}">
<ia:InvokeCommandAction Command="{Binding Command}" />
- </EventTriggerBehavior>
- <EventTriggerBehavior EventName="PointerReleased">
+ </b:RoutedEventTriggerBehavior>
+ <b:RoutedEventTriggerBehavior RoutedEvent="{x:Static InputElement.PointerReleasedEvent}">
<ChangePropertyAction TargetObject="{Binding #UserControl}" PropertyName="IsVisible"
Value="False" />
<ChangePropertyAction TargetObject="{Binding #ListBox}" PropertyName="SelectedItem"
Value="{x:Null}" />
- </EventTriggerBehavior>
+ </b:RoutedEventTriggerBehavior>
</i:Interaction.Behaviors>
</Panel>

Using RoutedEventTriggerBehavior, ie. no reflection