Skip to content
This repository has been archived by the owner on Nov 1, 2024. It is now read-only.

Commit

Permalink
Add ExecuteCommandBehaviorBase
Browse files Browse the repository at this point in the history
  • Loading branch information
wieslawsoltes committed Apr 24, 2024
1 parent dc236fb commit 886d60b
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 133 deletions.
103 changes: 103 additions & 0 deletions src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandBehaviorBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace Avalonia.Xaml.Interactions.Custom;

/// <summary>
///
/// </summary>
public abstract class ExecuteCommandBehaviorBase : AttachedToVisualTreeBehavior<Control>
{
/// <summary>
///
/// </summary>
public static readonly StyledProperty<ICommand?> CommandProperty =
AvaloniaProperty.Register<ExecuteCommandBehaviorBase, ICommand?>(nameof(Command));

/// <summary>
///
/// </summary>
public static readonly StyledProperty<object?> CommandParameterProperty =
AvaloniaProperty.Register<ExecuteCommandBehaviorBase, object?>(nameof(CommandParameter));

/// <summary>
///
/// </summary>
public static readonly StyledProperty<bool> FocusTopLevelProperty =
AvaloniaProperty.Register<ExecuteCommandBehaviorBase, bool>(nameof(FocusTopLevel));

/// <summary>
///
/// </summary>
public static readonly StyledProperty<Control?> FocusControlProperty =
AvaloniaProperty.Register<ExecuteCommandBehaviorBase, Control?>(nameof(CommandParameter));

/// <summary>
///
/// </summary>
public ICommand? Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}

/// <summary>
///
/// </summary>
public object? CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}

/// <summary>
///
/// </summary>
public bool FocusTopLevel
{
get => GetValue(FocusTopLevelProperty);
set => SetValue(FocusTopLevelProperty, value);
}

/// <summary>
///
/// </summary>
[ResolveByName]
public Control? FocusControl
{
get => GetValue(FocusControlProperty);
set => SetValue(FocusControlProperty, value);
}

/// <summary>
///
/// </summary>
/// <returns></returns>
protected bool ExecuteCommand()
{
if (AssociatedObject is not { IsVisible: true, IsEnabled: true })
{
return false;
}

if (Command?.CanExecute(CommandParameter) != true)
{
return false;
}

if (FocusTopLevel)
{
Dispatcher.UIThread.Post(() => (AssociatedObject?.GetVisualRoot() as TopLevel)?.Focus());
}

if (FocusControl is { } focusControl)
{
Dispatcher.UIThread.Post(() => focusControl.Focus());
}

Command.Execute(CommandParameter);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,32 @@
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;

namespace Avalonia.Xaml.Interactions.Custom;

/// <summary>
///
/// </summary>
public class ExecuteCommandOnActivatedBehavior : DisposingBehavior<Control>
public class ExecuteCommandOnActivatedBehavior : ExecuteCommandBehaviorBase
{
public static readonly StyledProperty<ICommand?> CommandProperty =
AvaloniaProperty.Register<ExecuteCommandOnActivatedBehavior, ICommand?>(nameof(Command));

/// <summary>
///
/// </summary>
public ICommand? Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}

/// <summary>
///
/// </summary>
/// <param name="disposables"></param>
protected override void OnAttached(CompositeDisposable disposables)
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
var mainWindow = lifetime.MainWindow;
/// <param name="disposable"></param>
protected override void OnAttachedToVisualTree(CompositeDisposable disposable)
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
var mainWindow = lifetime.MainWindow;

var dispose = Observable
.FromEventPattern(mainWindow, nameof(mainWindow.Activated))
.Subscribe(new AnonymousObserver<EventPattern<object>>(_ =>
var dispose = Observable
.FromEventPattern(mainWindow, nameof(mainWindow.Activated))
.Subscribe(new AnonymousObserver<EventPattern<object>>(e =>
{
if (Command is { } cmd && cmd.CanExecute(default))
{
cmd.Execute(default);
}
ExecuteCommand();
}));
disposables.Add(dispose);
}
}
disposable.Add(dispose);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Reactive.Disposables;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;

Expand All @@ -9,41 +7,37 @@ namespace Avalonia.Xaml.Interactions.Custom;
/// <summary>
///
/// </summary>
public class ExecuteCommandOnDoubleTappedBehavior : DisposingBehavior<Control>
public class ExecuteCommandOnDoubleTappedBehavior : ExecuteCommandBehaviorBase
{
/// <summary>
///
/// </summary>
public static readonly StyledProperty<ICommand?> CommandProperty =
AvaloniaProperty.Register<ExecuteCommandOnDoubleTappedBehavior, ICommand?>(nameof(Command));

/// <summary>
///
/// </summary>
public ICommand? Command
/// <param name="disposable"></param>
protected override void OnAttachedToVisualTree(CompositeDisposable disposable)
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
var dispose = AssociatedObject?
.AddDisposableHandler(
Gestures.DoubleTappedEvent,
AssociatedObject_DoubleTapped,
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);

if (dispose is not null)
{
disposable.Add(dispose);
}
}

/// <summary>
///
/// </summary>
/// <param name="disposables"></param>
protected override void OnAttached(CompositeDisposable disposables)
private void AssociatedObject_DoubleTapped(object? sender, RoutedEventArgs e)
{
var disposable = Gestures.DoubleTappedEvent.AddClassHandler<InputElement>(
(x, _) =>
{
if (Equals(x, AssociatedObject))
{
if (Command is { } cmd && cmd.CanExecute(default))
{
cmd.Execute(default);
}
}
},
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
disposables.Add(disposable);
if (e.Handled)
{
return;
}

if (ExecuteCommand())
{
e.Handled = true;
}
}
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
using System.Reactive.Disposables;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;

namespace Avalonia.Xaml.Interactions.Custom;

/// <summary>
///
/// </summary>
public class ExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavior<Control>
public class ExecuteCommandOnKeyDownBehavior : ExecuteCommandBehaviorBase
{
/// <summary>
///
Expand All @@ -21,27 +18,8 @@ public class ExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavior<Cont
/// <summary>
///
/// </summary>
public static readonly StyledProperty<bool> IsEnabledProperty =
AvaloniaProperty.Register<ExecuteCommandOnKeyDownBehavior, bool>(nameof(IsEnabled), true);

/// <summary>
///
/// </summary>
public static readonly StyledProperty<ICommand> CommandProperty =
AvaloniaProperty.Register<ExecuteCommandOnKeyDownBehavior, ICommand>(nameof(Command));

/// <summary>
///
/// </summary>
public static readonly StyledProperty<object> CommandParameterProperty =
AvaloniaProperty.Register<ExecuteCommandOnKeyDownBehavior, object>(nameof(CommandParameter));

/// <summary>
///
/// </summary>
public static readonly StyledProperty<RoutingStrategies> EventRoutingStrategyProperty =
AvaloniaProperty.Register<ExecuteCommandOnKeyDownBehavior, RoutingStrategies>(nameof(EventRoutingStrategy),
RoutingStrategies.Bubble);
public static readonly StyledProperty<KeyGesture?> GestureProperty =
AvaloniaProperty.Register<ExecuteCommandOnKeyDownBehavior, KeyGesture?>(nameof(Gesture));

/// <summary>
///
Expand All @@ -55,74 +33,48 @@ public Key? Key
/// <summary>
///
/// </summary>
public bool IsEnabled
public KeyGesture? Gesture
{
get => GetValue(IsEnabledProperty);
set => SetValue(IsEnabledProperty, value);
get => GetValue(GestureProperty);
set => SetValue(GestureProperty, value);
}

/// <summary>
///
/// </summary>
public ICommand Command
/// <param name="disposable"></param>
protected override void OnAttachedToVisualTree(CompositeDisposable disposable)
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
var dispose = AssociatedObject?
.AddDisposableHandler(
InputElement.KeyDownEvent,
OnKeyDown,
RoutingStrategies.Bubble);

/// <summary>
///
/// </summary>
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
if (dispose is not null)
{
disposable.Add(dispose);
}
}

/// <summary>
///
/// </summary>
public RoutingStrategies EventRoutingStrategy
private void OnKeyDown(object? sender, KeyEventArgs e)
{
get => GetValue(EventRoutingStrategyProperty);
set => SetValue(EventRoutingStrategyProperty, value);
}
var haveKey = Key is not null && e.Key == Key;
var haveGesture = Gesture is not null && Gesture.Matches(e);

/// <summary>
///
/// </summary>
/// <param name="disposables"></param>
protected override void OnAttachedToVisualTree(CompositeDisposable disposables)
{
var control = AssociatedObject;
if (control is null)
if (!haveKey && !haveGesture)
{
return;
}

if (control.GetVisualRoot() is InputElement inputRoot)
{
var disposable =
inputRoot.AddDisposableHandler(InputElement.KeyDownEvent, RootDefaultKeyDown, EventRoutingStrategy);
disposables.Add(disposable);
}
}

private void RootDefaultKeyDown(object? sender, KeyEventArgs e)
{
var control = AssociatedObject;
if (control is null)
if (e.Handled)
{
return;
}

if (Key is { } && e.Key == Key && control.IsVisible && control.IsEnabled && IsEnabled)
if (ExecuteCommand())
{
if (!e.Handled && Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
e.Handled = true;
}
e.Handled = true;
}
}
}

0 comments on commit 886d60b

Please sign in to comment.