Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash when KeyBindings change while they are being handled #5055

Merged
merged 8 commits into from
Jun 14, 2021
23 changes: 21 additions & 2 deletions src/Avalonia.Input/KeyboardDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,31 @@ public void ProcessRawEvent(RawInputEventArgs e)
{
var bindings = (currentHandler as IInputElement)?.KeyBindings;
if (bindings != null)
{
KeyBinding[]? bindingsCopy = null;

// Create a copy of the KeyBindings list if there's a binding which matches the event.
// If we don't do this the foreach loop will throw an InvalidOperationException when the KeyBindings list is changed.
// This can happen when a new view is loaded which adds its own KeyBindings to the handler.
foreach (var binding in bindings)
{
if (ev.Handled)
if (binding.Gesture?.Matches(ev) == true)
{
bindingsCopy = bindings.ToArray();
break;
binding.TryHandle(ev);
}
}

if (bindingsCopy is object)
{
foreach (var binding in bindingsCopy)
{
if (ev.Handled)
break;
binding.TryHandle(ev);
}
}
}
currentHandler = currentHandler.VisualParent;
}

Expand Down
46 changes: 45 additions & 1 deletion tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using Avalonia.Input.Raw;
using System;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.UnitTests;
using Moq;
using Xunit;

Expand Down Expand Up @@ -86,5 +90,45 @@ public void TextInput_Should_Be_Sent_To_Focused_Element()

focused.Verify(x => x.RaiseEvent(It.IsAny<TextInputEventArgs>()));
}

[Fact]
public void Can_Change_KeyBindings_In_Keybinding_Event_Handler()
{
var target = new KeyboardDevice();
var button = new Button();
var root = new TestRoot(button);
var raised = 0;

button.KeyBindings.Add(new KeyBinding
{
Gesture = new KeyGesture(Key.O, KeyModifiers.Control),
Command = new DelegateCommand(() =>
{
button.KeyBindings.Clear();
++raised;
}),
});

target.SetFocusedElement(button, NavigationMethod.Pointer, 0);
target.ProcessRawEvent(
new RawKeyEventArgs(
target,
0,
root,
RawKeyEventType.KeyDown,
Key.O,
RawInputModifiers.Control));

Assert.Equal(1, raised);
}

private class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action) => _action = action;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action();
}
}
}