diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs
index 79152e20d2..a159b19026 100644
--- a/src/Avalonia.Input/KeyboardDevice.cs
+++ b/src/Avalonia.Input/KeyboardDevice.cs
@@ -217,12 +217,31 @@ namespace Avalonia.Input
{
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;
}
diff --git a/src/Avalonia.Visuals/Media/PolyLineSegment.cs b/src/Avalonia.Visuals/Media/PolyLineSegment.cs
new file mode 100644
index 0000000000..55bfb33041
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/PolyLineSegment.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using Avalonia.Collections;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Represents a set of line segments defined by a points collection with each Point specifying the end point of a line segment.
+ ///
+ public sealed class PolyLineSegment : PathSegment
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PointsProperty
+ = AvaloniaProperty.Register(nameof(Points));
+
+ ///
+ /// Gets or sets the points.
+ ///
+ ///
+ /// The points.
+ ///
+ public AvaloniaList Points
+ {
+ get => GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PolyLineSegment()
+ {
+ Points = new Points();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The points.
+ public PolyLineSegment(IEnumerable points) : this()
+ {
+ Points.AddRange(points);
+ }
+
+ protected internal override void ApplyTo(StreamGeometryContext ctx)
+ {
+ var points = Points;
+ if (points.Count > 0)
+ {
+ for (int i = 0; i < points.Count; i++)
+ {
+ ctx.LineTo(points[i]);
+ }
+ }
+ }
+
+ public override string ToString()
+ => Points.Count >= 1 ? "L " + string.Join(" ", Points) : "";
+ }
+}
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
index df0a077c7f..7730cee78c 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
@@ -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;
@@ -86,5 +90,45 @@ namespace Avalonia.Input.UnitTests
focused.Verify(x => x.RaiseEvent(It.IsAny()));
}
+
+ [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();
+ }
}
}