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

Commit

Permalink
Merge branch 'master' into release/11.1.0-beta1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
wieslawsoltes committed Apr 24, 2024
2 parents d6d69eb + 752fbbf commit fe2c4d1
Show file tree
Hide file tree
Showing 36 changed files with 1,526 additions and 236 deletions.
2 changes: 1 addition & 1 deletion samples/BehaviorsTestApplication/Views/ItemView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Panel Background="Transparent">
<i:Interaction.Behaviors>
<iac:ShowOnDoubleTappedBehavior TargetControl="TextBoxEdit" />
<iac:ShowOnKeyDownTappedBehavior TargetControl="TextBoxEdit" />
<iac:ShowOnKeyDownBehavior TargetControl="TextBoxEdit" Key="F2" />
</i:Interaction.Behaviors>
<TextBox x:Name="TextBoxEdit"
IsVisible="False"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ namespace Avalonia.Xaml.Interactions.Custom;
/// </summary>
public class ButtonExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavior<Button>
{
/// <summary>
///
/// </summary>
public static readonly StyledProperty<bool> IsEnabledProperty =
AvaloniaProperty.Register<ButtonExecuteCommandOnKeyDownBehavior, bool>(nameof(IsEnabled));

/// <summary>
///
/// </summary>
Expand All @@ -20,8 +26,17 @@ public class ButtonExecuteCommandOnKeyDownBehavior : AttachedToVisualTreeBehavio
/// <summary>
///
/// </summary>
public static readonly StyledProperty<bool> IsEnabledProperty =
AvaloniaProperty.Register<ButtonExecuteCommandOnKeyDownBehavior, bool>(nameof(IsEnabled));
public static readonly StyledProperty<KeyGesture?> GestureProperty =
AvaloniaProperty.Register<ButtonExecuteCommandOnKeyDownBehavior, KeyGesture?>(nameof(Gesture));

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

/// <summary>
///
Expand All @@ -35,10 +50,10 @@ 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>
Expand All @@ -47,13 +62,7 @@ public bool IsEnabled
/// <param name="disposables"></param>
protected override void OnAttachedToVisualTree(CompositeDisposable disposables)
{
var button = AssociatedObject;
if (button is null)
{
return;
}

if (button.GetVisualRoot() is InputElement inputRoot)
if (AssociatedObject?.GetVisualRoot() is InputElement inputRoot)
{
var disposable = inputRoot.AddDisposableHandler(InputElement.KeyDownEvent, RootDefaultKeyDown);
disposables.Add(disposable);
Expand All @@ -62,19 +71,38 @@ protected override void OnAttachedToVisualTree(CompositeDisposable disposables)

private void RootDefaultKeyDown(object? sender, KeyEventArgs e)
{
var button = AssociatedObject;
if (button is null)
var haveKey = Key is not null && e.Key == Key;
var haveGesture = Gesture is not null && Gesture.Matches(e);

if (!haveKey && !haveGesture)
{
return;
}

if (Key is { } && e.Key == Key && button.IsVisible && button.IsEnabled && IsEnabled)
if (AssociatedObject is { } button)
{
ExecuteCommand(button);
}
}

private bool ExecuteCommand(Button button)
{
if (!IsEnabled)
{
return false;
}

if (button is not { IsVisible: true, IsEnabled: true })
{
if (!e.Handled && button.Command?.CanExecute(button.CommandParameter) == true)
{
button.Command.Execute(button.CommandParameter);
e.Handled = true;
}
return false;
}

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

button.Command.Execute(button.CommandParameter);
return true;
}
}
123 changes: 123 additions & 0 deletions src/Avalonia.Xaml.Interactions.Custom/ExecuteCommandBehaviorBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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<bool> IsEnabledProperty =
AvaloniaProperty.Register<ExecuteCommandBehaviorBase, bool>(nameof(IsEnabled), true);

/// <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 bool IsEnabled
{
get => GetValue(IsEnabledProperty);
set => SetValue(IsEnabledProperty, value);
}

/// <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 (!IsEnabled)
{
return false;
}

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,35 @@
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>>(_ =>
{
if (Command is { } cmd && cmd.CanExecute(default))
if (mainWindow is not null)
{
var dispose = Observable
.FromEventPattern(mainWindow, nameof(mainWindow.Activated))
.Subscribe(new AnonymousObserver<EventPattern<object>>(e =>
{
cmd.Execute(default);
}
}));
disposables.Add(dispose);
}
}
ExecuteCommand();
}));
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,36 @@ 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;
}
}
}
Loading

0 comments on commit fe2c4d1

Please sign in to comment.