diff --git a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandBehaviorBase.cs b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandBehaviorBase.cs new file mode 100644 index 00000000..9a69ce22 --- /dev/null +++ b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandBehaviorBase.cs @@ -0,0 +1,103 @@ +using System.Windows.Input; +using Avalonia.Controls; +using Avalonia.Threading; +using Avalonia.VisualTree; + +namespace Avalonia.Xaml.Interactions.Custom; + +/// +/// +/// +public abstract class ExecuteCommandBehaviorBase : AttachedToVisualTreeBehavior +{ + /// + /// + /// + public static readonly StyledProperty CommandProperty = + AvaloniaProperty.Register(nameof(Command)); + + /// + /// + /// + public static readonly StyledProperty CommandParameterProperty = + AvaloniaProperty.Register(nameof(CommandParameter)); + + /// + /// + /// + public static readonly StyledProperty FocusTopLevelProperty = + AvaloniaProperty.Register(nameof(FocusTopLevel)); + + /// + /// + /// + public static readonly StyledProperty FocusControlProperty = + AvaloniaProperty.Register(nameof(CommandParameter)); + + /// + /// + /// + public ICommand? Command + { + get => GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + + /// + /// + /// + public object? CommandParameter + { + get => GetValue(CommandParameterProperty); + set => SetValue(CommandParameterProperty, value); + } + + /// + /// + /// + public bool FocusTopLevel + { + get => GetValue(FocusTopLevelProperty); + set => SetValue(FocusTopLevelProperty, value); + } + + /// + /// + /// + [ResolveByName] + public Control? FocusControl + { + get => GetValue(FocusControlProperty); + set => SetValue(FocusControlProperty, value); + } + + /// + /// + /// + /// + 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; + } +} diff --git a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnActivatedBehavior.cs b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnActivatedBehavior.cs index 79a70015..39d16361 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnActivatedBehavior.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnActivatedBehavior.cs @@ -1,8 +1,6 @@ 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; @@ -10,40 +8,25 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// /// -public class ExecuteCommandOnActivatedBehavior : DisposingBehavior +public class ExecuteCommandOnActivatedBehavior : ExecuteCommandBehaviorBase { - public static readonly StyledProperty CommandProperty = - AvaloniaProperty.Register(nameof(Command)); - - /// - /// - /// - public ICommand? Command - { - get => GetValue(CommandProperty); - set => SetValue(CommandProperty, value); - } - /// /// /// - /// - protected override void OnAttached(CompositeDisposable disposables) - { - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) - { - var mainWindow = lifetime.MainWindow; + /// + 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>(_ => + var dispose = Observable + .FromEventPattern(mainWindow, nameof(mainWindow.Activated)) + .Subscribe(new AnonymousObserver>(e => { - if (Command is { } cmd && cmd.CanExecute(default)) - { - cmd.Execute(default); - } + ExecuteCommand(); })); - disposables.Add(dispose); - } - } + disposable.Add(dispose); + } + } } diff --git a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnDoubleTappedBehavior.cs b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnDoubleTappedBehavior.cs index 9a02e56e..406120b0 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnDoubleTappedBehavior.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnDoubleTappedBehavior.cs @@ -1,6 +1,4 @@ using System.Reactive.Disposables; -using System.Windows.Input; -using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -9,41 +7,37 @@ namespace Avalonia.Xaml.Interactions.Custom; /// /// /// -public class ExecuteCommandOnDoubleTappedBehavior : DisposingBehavior +public class ExecuteCommandOnDoubleTappedBehavior : ExecuteCommandBehaviorBase { /// /// /// - public static readonly StyledProperty CommandProperty = - AvaloniaProperty.Register(nameof(Command)); - - /// - /// - /// - public ICommand? Command + /// + 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); + } } - /// - /// - /// - /// - protected override void OnAttached(CompositeDisposable disposables) + private void AssociatedObject_DoubleTapped(object? sender, RoutedEventArgs e) { - var disposable = Gestures.DoubleTappedEvent.AddClassHandler( - (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; + } } } + diff --git a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnKeyDownBehavior.cs b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnKeyDownBehavior.cs index a9d6eb78..fba4686a 100644 --- a/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnKeyDownBehavior.cs +++ b/src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandOnKeyDownBehavior.cs @@ -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; /// /// /// -public class ExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavior +public class ExecuteCommandOnKeyDownBehavior : ExecuteCommandBehaviorBase { /// /// @@ -21,27 +18,8 @@ public class ExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavior /// /// - public static readonly StyledProperty IsEnabledProperty = - AvaloniaProperty.Register(nameof(IsEnabled), true); - - /// - /// - /// - public static readonly StyledProperty CommandProperty = - AvaloniaProperty.Register(nameof(Command)); - - /// - /// - /// - public static readonly StyledProperty CommandParameterProperty = - AvaloniaProperty.Register(nameof(CommandParameter)); - - /// - /// - /// - public static readonly StyledProperty EventRoutingStrategyProperty = - AvaloniaProperty.Register(nameof(EventRoutingStrategy), - RoutingStrategies.Bubble); + public static readonly StyledProperty GestureProperty = + AvaloniaProperty.Register(nameof(Gesture)); /// /// @@ -55,74 +33,48 @@ public Key? Key /// /// /// - public bool IsEnabled + public KeyGesture? Gesture { - get => GetValue(IsEnabledProperty); - set => SetValue(IsEnabledProperty, value); + get => GetValue(GestureProperty); + set => SetValue(GestureProperty, value); } /// /// /// - public ICommand Command + /// + protected override void OnAttachedToVisualTree(CompositeDisposable disposable) { - get => GetValue(CommandProperty); - set => SetValue(CommandProperty, value); - } + var dispose = AssociatedObject? + .AddDisposableHandler( + InputElement.KeyDownEvent, + OnKeyDown, + RoutingStrategies.Bubble); - /// - /// - /// - public object CommandParameter - { - get => GetValue(CommandParameterProperty); - set => SetValue(CommandParameterProperty, value); + if (dispose is not null) + { + disposable.Add(dispose); + } } - /// - /// - /// - 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); - /// - /// - /// - /// - 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; } } }