diff --git a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
index 66f1e8cc26..730ca2bd6e 100644
--- a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
+++ b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
@@ -1,6 +1,8 @@
netstandard2.0
+ Enable
+ CS8600;CS8602;CS8603
@@ -9,6 +11,4 @@
-
-
-
+
\ No newline at end of file
diff --git a/src/Avalonia.Interactivity/EventRoute.cs b/src/Avalonia.Interactivity/EventRoute.cs
new file mode 100644
index 0000000000..85ba33d7ba
--- /dev/null
+++ b/src/Avalonia.Interactivity/EventRoute.cs
@@ -0,0 +1,200 @@
+using System;
+using Avalonia.Collections.Pooled;
+
+namespace Avalonia.Interactivity
+{
+ ///
+ /// Holds the route for a routed event and supports raising an event on that route.
+ ///
+ public class EventRoute : IDisposable
+ {
+ private readonly RoutedEvent _event;
+ private PooledList? _route;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The routed event to be raised.
+ public EventRoute(RoutedEvent e)
+ {
+ e = e ?? throw new ArgumentNullException(nameof(e));
+
+ _event = e;
+ _route = null;
+ }
+
+ ///
+ /// Gets a value indicating whether the route has any handlers.
+ ///
+ public bool HasHandlers => _route?.Count > 0;
+
+ ///
+ /// Adds a handler to the route.
+ ///
+ /// The target on which the event should be raised.
+ /// The handler for the event.
+ /// The routing strategies to listen to.
+ ///
+ /// If true the handler will be raised even when the routed event is marked as handled.
+ ///
+ ///
+ /// An optional adapter which if supplied, will be called with
+ /// and the parameters for the event. This adapter can be used to avoid calling
+ /// `DynamicInvoke` on the handler.
+ ///
+ public void Add(
+ IInteractive target,
+ Delegate handler,
+ RoutingStrategies routes,
+ bool handledEventsToo = false,
+ Action? adapter = null)
+ {
+ target = target ?? throw new ArgumentNullException(nameof(target));
+ handler = handler ?? throw new ArgumentNullException(nameof(handler));
+
+ _route ??= new PooledList(16);
+ _route.Add(new RouteItem(target, handler, adapter, routes, handledEventsToo));
+ }
+
+ ///
+ /// Adds a class handler to the route.
+ ///
+ /// The target on which the event should be raised.
+ public void AddClassHandler(IInteractive target)
+ {
+ target = target ?? throw new ArgumentNullException(nameof(target));
+
+ _route ??= new PooledList(16);
+ _route.Add(new RouteItem(target, null, null, 0, false));
+ }
+
+ ///
+ /// Raises an event along the route.
+ ///
+ /// The event source.
+ /// The event args.
+ public void RaiseEvent(IInteractive source, RoutedEventArgs e)
+ {
+ source = source ?? throw new ArgumentNullException(nameof(source));
+ e = e ?? throw new ArgumentNullException(nameof(e));
+
+ e.Source = source;
+
+ if (_event.RoutingStrategies == RoutingStrategies.Direct)
+ {
+ e.Route = RoutingStrategies.Direct;
+ RaiseEventImpl(e);
+ _event.InvokeRouteFinished(e);
+ }
+ else
+ {
+ if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel))
+ {
+ e.Route = RoutingStrategies.Tunnel;
+ RaiseEventImpl(e);
+ _event.InvokeRouteFinished(e);
+ }
+
+ if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble))
+ {
+ e.Route = RoutingStrategies.Bubble;
+ RaiseEventImpl(e);
+ _event.InvokeRouteFinished(e);
+ }
+ }
+ }
+
+ ///
+ /// Disposes of the event route.
+ ///
+ public void Dispose()
+ {
+ _route?.Dispose();
+ _route = null;
+ }
+
+ private void RaiseEventImpl(RoutedEventArgs e)
+ {
+ if (_route is null)
+ {
+ return;
+ }
+
+ if (e.Source is null)
+ {
+ throw new ArgumentException("Event source may not be null", nameof(e));
+ }
+
+ IInteractive? lastTarget = null;
+ var start = 0;
+ var end = _route.Count;
+ var step = 1;
+
+ if (e.Route == RoutingStrategies.Tunnel)
+ {
+ start = end - 1;
+ step = end = -1;
+ }
+
+ for (var i = start; i != end; i += step)
+ {
+ var entry = _route[i];
+
+ // If we've got to a new control then call any RoutedEvent.Raised listeners.
+ if (entry.Target != lastTarget)
+ {
+ if (!e.Handled)
+ {
+ _event.InvokeRaised(entry.Target, e);
+ }
+
+ // If this is a direct event and we've already raised events then we're finished.
+ if (e.Route == RoutingStrategies.Direct && lastTarget is object)
+ {
+ return;
+ }
+
+ lastTarget = entry.Target;
+ }
+
+ // Raise the event handler.
+ if (entry.Handler is object &&
+ entry.Routes.HasFlagCustom(e.Route) &&
+ (!e.Handled || entry.HandledEventsToo))
+ {
+ if (entry.Adapter is object)
+ {
+ entry.Adapter(entry.Handler, entry.Target, e);
+ }
+ else
+ {
+ entry.Handler.DynamicInvoke(entry.Target, e);
+ }
+ }
+ }
+ }
+
+ private readonly struct RouteItem
+ {
+ public RouteItem(
+ IInteractive target,
+ Delegate? handler,
+ Action? adapter,
+ RoutingStrategies routes,
+ bool handledEventsToo)
+ {
+ Target = target;
+ Handler = handler;
+ Adapter = adapter;
+ Routes = routes;
+ HandledEventsToo = handledEventsToo;
+ }
+
+ public IInteractive Target { get; }
+ public Delegate? Handler { get; }
+ public Action? Adapter { get; }
+ public RoutingStrategies Routes { get; }
+ public bool HandledEventsToo { get; }
+ }
+ }
+}
diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs
deleted file mode 100644
index e8fb1bfaf1..0000000000
--- a/src/Avalonia.Interactivity/EventSubscription.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Interactivity
-{
- internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args);
-
- internal class EventSubscription
- {
- public HandlerInvokeSignature InvokeAdapter { get; set; }
-
- public Delegate Handler { get; set; }
-
- public RoutingStrategies Routes { get; set; }
-
- public bool AlsoIfHandled { get; set; }
- }
-}
diff --git a/src/Avalonia.Interactivity/IInteractive.cs b/src/Avalonia.Interactivity/IInteractive.cs
index 47046b58e2..33baa9453a 100644
--- a/src/Avalonia.Interactivity/IInteractive.cs
+++ b/src/Avalonia.Interactivity/IInteractive.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Interactivity
///
/// Gets the interactive parent of the object for bubbling and tunneling events.
///
- IInteractive InteractiveParent { get; }
+ IInteractive? InteractiveParent { get; }
///
/// Adds a handler for the specified routed event.
@@ -60,6 +60,13 @@ namespace Avalonia.Interactivity
void RemoveHandler(RoutedEvent routedEvent, EventHandler handler)
where TEventArgs : RoutedEventArgs;
+ ///
+ /// Adds the object's handlers for a routed event to an event route.
+ ///
+ /// The event.
+ /// The event route.
+ void AddToEventRoute(RoutedEvent routedEvent, EventRoute route);
+
///
/// Raises a routed event.
///
diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs
index 27ece25183..9493d86885 100644
--- a/src/Avalonia.Interactivity/Interactive.cs
+++ b/src/Avalonia.Interactivity/Interactive.cs
@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
using Avalonia.Layout;
using Avalonia.VisualTree;
@@ -15,16 +13,12 @@ namespace Avalonia.Interactivity
///
public class Interactive : Layoutable, IInteractive
{
- private Dictionary> _eventHandlers;
-
- private static readonly Dictionary s_invokeHandlerCache = new Dictionary();
+ private Dictionary>? _eventHandlers;
///
/// Gets the interactive parent of the object for bubbling and tunneling events.
///
- IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive;
-
- private Dictionary> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary>());
+ IInteractive? IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive;
///
/// Adds a handler for the specified routed event.
@@ -40,16 +34,10 @@ namespace Avalonia.Interactivity
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false)
{
- Contract.Requires(routedEvent != null);
- Contract.Requires(handler != null);
-
- var subscription = new EventSubscription
- {
- Handler = handler,
- Routes = routes,
- AlsoIfHandled = handledEventsToo,
- };
+ routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
+ handler = handler ?? throw new ArgumentNullException(nameof(handler));
+ var subscription = new EventSubscription(handler, routes, handledEventsToo);
return AddEventSubscription(routedEvent, subscription);
}
@@ -68,35 +56,18 @@ namespace Avalonia.Interactivity
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
{
- Contract.Requires(routedEvent != null);
- Contract.Requires(handler != null);
-
- // EventHandler delegate is not covariant, this forces us to create small wrapper
- // that will cast our type erased instance and invoke it.
- Type eventArgsType = routedEvent.EventArgsType;
+ routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
+ handler = handler ?? throw new ArgumentNullException(nameof(handler));
- if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter))
+ static void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args)
{
- void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args)
- {
- var typedHandler = (EventHandler)baseHandler;
- var typedArgs = (TEventArgs)args;
+ var typedHandler = (EventHandler)baseHandler;
+ var typedArgs = (TEventArgs)args;
- typedHandler(sender, typedArgs);
- }
-
- invokeAdapter = InvokeAdapter;
-
- s_invokeHandlerCache.Add(eventArgsType, invokeAdapter);
+ typedHandler(sender, typedArgs);
}
- var subscription = new EventSubscription
- {
- InvokeAdapter = invokeAdapter,
- Handler = handler,
- Routes = routes,
- AlsoIfHandled = handledEventsToo,
- };
+ var subscription = new EventSubscription(handler, routes, handledEventsToo, (baseHandler, sender, args) => InvokeAdapter(baseHandler, sender, args));
return AddEventSubscription(routedEvent, subscription);
}
@@ -108,14 +79,19 @@ namespace Avalonia.Interactivity
/// The handler.
public void RemoveHandler(RoutedEvent routedEvent, Delegate handler)
{
- Contract.Requires(routedEvent != null);
- Contract.Requires(handler != null);
-
- List subscriptions = null;
+ routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
+ handler = handler ?? throw new ArgumentNullException(nameof(handler));
- if (_eventHandlers?.TryGetValue(routedEvent, out subscriptions) == true)
+ if (_eventHandlers is object &&
+ _eventHandlers.TryGetValue(routedEvent, out var subscriptions))
{
- subscriptions.RemoveAll(x => x.Handler == handler);
+ for (var i = subscriptions.Count - 1; i >= 0; i--)
+ {
+ if (subscriptions[i].Handler == handler)
+ {
+ subscriptions.RemoveAt(i);
+ }
+ }
}
}
@@ -137,112 +113,117 @@ namespace Avalonia.Interactivity
/// The event args.
public void RaiseEvent(RoutedEventArgs e)
{
- Contract.Requires(e != null);
-
- e.Source = e.Source ?? this;
-
- if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct)
- {
- e.Route = RoutingStrategies.Direct;
- RaiseEventImpl(e);
- e.RoutedEvent.InvokeRouteFinished(e);
- }
+ e = e ?? throw new ArgumentNullException(nameof(e));
- if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Tunnel) != 0)
+ if (e.RoutedEvent == null)
{
- TunnelEvent(e);
- e.RoutedEvent.InvokeRouteFinished(e);
+ throw new ArgumentException("Cannot raise an event whose RoutedEvent is null.");
}
- if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Bubble) != 0)
- {
- BubbleEvent(e);
- e.RoutedEvent.InvokeRouteFinished(e);
- }
+ using var route = BuildEventRoute(e.RoutedEvent);
+ route.RaiseEvent(this, e);
}
- ///
- /// Bubbles an event.
- ///
- /// The event args.
- private void BubbleEvent(RoutedEventArgs e)
+ void IInteractive.AddToEventRoute(RoutedEvent routedEvent, EventRoute route)
{
- Contract.Requires(e != null);
+ routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
+ route = route ?? throw new ArgumentNullException(nameof(route));
- e.Route = RoutingStrategies.Bubble;
-
- var traverser = HierarchyTraverser.Create(e);
-
- traverser.Traverse(this);
- }
-
- ///
- /// Tunnels an event.
- ///
- /// The event args.
- private void TunnelEvent(RoutedEventArgs e)
- {
- Contract.Requires(e != null);
-
- e.Route = RoutingStrategies.Tunnel;
-
- var traverser = HierarchyTraverser.Create(e);
-
- traverser.Traverse(this);
+ if (_eventHandlers != null &&
+ _eventHandlers.TryGetValue(routedEvent, out var subscriptions))
+ {
+ foreach (var sub in subscriptions)
+ {
+ route.Add(this, sub.Handler, sub.Routes, sub.HandledEventsToo, sub.InvokeAdapter);
+ }
+ }
}
///
- /// Carries out the actual invocation of an event on this object.
+ /// Builds an event route for a routed event.
///
- /// The event args.
- private void RaiseEventImpl(RoutedEventArgs e)
+ /// The routed event.
+ /// An describing the route.
+ ///
+ /// Usually, calling is sufficent to raise a routed
+ /// event, however there are situations in which the construction of the event args is expensive
+ /// and should be avoided if there are no handlers for an event. In these cases you can call
+ /// this method to build the event route and check the
+ /// property to see if there are any handlers registered on the route. If there are, call
+ /// to raise the event.
+ ///
+ protected EventRoute BuildEventRoute(RoutedEvent e)
{
- Contract.Requires(e != null);
-
- e.RoutedEvent.InvokeRaised(this, e);
+ e = e ?? throw new ArgumentNullException(nameof(e));
- List subscriptions = null;
+ var result = new EventRoute(e);
+ var hasClassHandlers = e.HasRaisedSubscriptions;
- if (_eventHandlers?.TryGetValue(e.RoutedEvent, out subscriptions) == true)
+ if (e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble) ||
+ e.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel))
{
- foreach (var sub in subscriptions.ToList())
- {
- bool correctRoute = (e.Route & sub.Routes) != 0;
- bool notFinished = !e.Handled || sub.AlsoIfHandled;
+ IInteractive? element = this;
- if (correctRoute && notFinished)
+ while (element != null)
+ {
+ if (hasClassHandlers)
{
- if (sub.InvokeAdapter != null)
- {
- sub.InvokeAdapter(sub.Handler, this, e);
- }
- else
- {
- sub.Handler.DynamicInvoke(this, e);
- }
+ result.AddClassHandler(element);
}
+
+ element.AddToEventRoute(e, result);
+ element = element.InteractiveParent;
}
}
+ else
+ {
+ if (hasClassHandlers)
+ {
+ result.AddClassHandler(this);
+ }
+
+ ((IInteractive)this).AddToEventRoute(e, result);
+ }
+
+ return result;
}
- private List GetEventSubscriptions(RoutedEvent routedEvent)
+ private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
{
- if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions))
+ _eventHandlers ??= new Dictionary>();
+
+ if (!_eventHandlers.TryGetValue(routedEvent, out var subscriptions))
{
subscriptions = new List();
- EventHandlers.Add(routedEvent, subscriptions);
+ _eventHandlers.Add(routedEvent, subscriptions);
}
- return subscriptions;
+ subscriptions.Add(subscription);
+
+ return new UnsubscribeDisposable(subscriptions, subscription);
}
- private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
+ private readonly struct EventSubscription
{
- List subscriptions = GetEventSubscriptions(routedEvent);
+ public EventSubscription(
+ Delegate handler,
+ RoutingStrategies routes,
+ bool handledEventsToo,
+ Action? invokeAdapter = null)
+ {
+ Handler = handler;
+ Routes = routes;
+ HandledEventsToo = handledEventsToo;
+ InvokeAdapter = invokeAdapter;
+ }
- subscriptions.Add(subscription);
+ public Action? InvokeAdapter { get; }
- return new UnsubscribeDisposable(subscriptions, subscription);
+ public Delegate Handler { get; }
+
+ public RoutingStrategies Routes { get; }
+
+ public bool HandledEventsToo { get; }
}
private sealed class UnsubscribeDisposable : IDisposable
@@ -261,67 +242,5 @@ namespace Avalonia.Interactivity
_subscriptions.Remove(_subscription);
}
}
-
- private interface ITraverse
- {
- void Execute(IInteractive target, RoutedEventArgs e);
- }
-
- private struct NopTraverse : ITraverse
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Execute(IInteractive target, RoutedEventArgs e)
- {
- }
- }
-
- private struct RaiseEventTraverse : ITraverse
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Execute(IInteractive target, RoutedEventArgs e)
- {
- ((Interactive)target).RaiseEventImpl(e);
- }
- }
-
- ///
- /// Traverses interactive hierarchy allowing for raising events.
- ///
- /// Called before parent is traversed.
- /// Called after parent has been traversed.
- private struct HierarchyTraverser
- where TPreTraverse : struct, ITraverse
- where TPostTraverse : struct, ITraverse
- {
- private TPreTraverse _preTraverse;
- private TPostTraverse _postTraverse;
- private readonly RoutedEventArgs _args;
-
- private HierarchyTraverser(TPreTraverse preTraverse, TPostTraverse postTraverse, RoutedEventArgs args)
- {
- _preTraverse = preTraverse;
- _postTraverse = postTraverse;
- _args = args;
- }
-
- public static HierarchyTraverser Create(RoutedEventArgs args)
- {
- return new HierarchyTraverser(new TPreTraverse(), new TPostTraverse(), args);
- }
-
- public void Traverse(IInteractive target)
- {
- _preTraverse.Execute(target, _args);
-
- IInteractive parent = target.InteractiveParent;
-
- if (parent != null)
- {
- Traverse(parent);
- }
-
- _postTraverse.Execute(target, _args);
- }
- }
}
}
diff --git a/src/Avalonia.Interactivity/InteractiveExtensions.cs b/src/Avalonia.Interactivity/InteractiveExtensions.cs
index 07e4029240..414c408080 100644
--- a/src/Avalonia.Interactivity/InteractiveExtensions.cs
+++ b/src/Avalonia.Interactivity/InteractiveExtensions.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reactive.Linq;
namespace Avalonia.Interactivity
@@ -30,6 +28,9 @@ namespace Avalonia.Interactivity
bool handledEventsToo = false)
where TEventArgs : RoutedEventArgs
{
+ o = o ?? throw new ArgumentNullException(nameof(o));
+ routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
+
return Observable.Create(x => o.AddHandler(
routedEvent,
(_, e) => x.OnNext(e),
diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs
index 55d9e61d87..e515efd3b4 100644
--- a/src/Avalonia.Interactivity/RoutedEvent.cs
+++ b/src/Avalonia.Interactivity/RoutedEvent.cs
@@ -25,10 +25,14 @@ namespace Avalonia.Interactivity
Type eventArgsType,
Type ownerType)
{
- Contract.Requires(name != null);
- Contract.Requires(eventArgsType != null);
- Contract.Requires(ownerType != null);
- Contract.Requires(typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType));
+ name = name ?? throw new ArgumentNullException(nameof(name));
+ eventArgsType = eventArgsType ?? throw new ArgumentNullException(nameof(name));
+ ownerType = ownerType ?? throw new ArgumentNullException(nameof(name));
+
+ if (!typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType))
+ {
+ throw new InvalidCastException("eventArgsType must be derived from RoutedEventArgs.");
+ }
EventArgsType = eventArgsType;
Name = name;
@@ -44,6 +48,8 @@ namespace Avalonia.Interactivity
public RoutingStrategies RoutingStrategies { get; }
+ public bool HasRaisedSubscriptions => _raised.HasObservers;
+
public IObservable<(object, RoutedEventArgs)> Raised => _raised;
public IObservable RouteFinished => _routeFinished;
@@ -52,7 +58,7 @@ namespace Avalonia.Interactivity
RoutingStrategies routingStrategy)
where TEventArgs : RoutedEventArgs
{
- Contract.Requires(name != null);
+ name = name ?? throw new ArgumentNullException(nameof(name));
var routedEvent = new RoutedEvent(name, routingStrategy, typeof(TOwner));
RoutedEventRegistry.Instance.Register(typeof(TOwner), routedEvent);
@@ -65,7 +71,7 @@ namespace Avalonia.Interactivity
Type ownerType)
where TEventArgs : RoutedEventArgs
{
- Contract.Requires(name != null);
+ name = name ?? throw new ArgumentNullException(nameof(name));
var routedEvent = new RoutedEvent(name, routingStrategy, ownerType);
RoutedEventRegistry.Instance.Register(ownerType, routedEvent);
@@ -108,8 +114,6 @@ namespace Avalonia.Interactivity
public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType)
: base(name, routingStrategies, typeof(TEventArgs), ownerType)
{
- Contract.Requires(name != null);
- Contract.Requires(ownerType != null);
}
[Obsolete("Use overload taking Action.")]
diff --git a/src/Avalonia.Interactivity/RoutedEventArgs.cs b/src/Avalonia.Interactivity/RoutedEventArgs.cs
index 05bbf7b6a3..e00393322d 100644
--- a/src/Avalonia.Interactivity/RoutedEventArgs.cs
+++ b/src/Avalonia.Interactivity/RoutedEventArgs.cs
@@ -11,12 +11,12 @@ namespace Avalonia.Interactivity
{
}
- public RoutedEventArgs(RoutedEvent routedEvent)
+ public RoutedEventArgs(RoutedEvent? routedEvent)
{
RoutedEvent = routedEvent;
}
- public RoutedEventArgs(RoutedEvent routedEvent, IInteractive source)
+ public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
{
RoutedEvent = routedEvent;
Source = source;
@@ -24,10 +24,10 @@ namespace Avalonia.Interactivity
public bool Handled { get; set; }
- public RoutedEvent RoutedEvent { get; set; }
+ public RoutedEvent? RoutedEvent { get; set; }
public RoutingStrategies Route { get; set; }
- public IInteractive Source { get; set; }
+ public IInteractive? Source { get; set; }
}
}
diff --git a/src/Avalonia.Interactivity/RoutedEventRegistry.cs b/src/Avalonia.Interactivity/RoutedEventRegistry.cs
index 34c970a806..0111b115e6 100644
--- a/src/Avalonia.Interactivity/RoutedEventRegistry.cs
+++ b/src/Avalonia.Interactivity/RoutedEventRegistry.cs
@@ -32,8 +32,8 @@ namespace Avalonia.Interactivity
///
public void Register(Type type, RoutedEvent @event)
{
- Contract.Requires(type != null);
- Contract.Requires(@event != null);
+ type = type ?? throw new ArgumentNullException(nameof(type));
+ @event = @event ?? throw new ArgumentNullException(nameof(@event));
if (!_registeredRoutedEvents.TryGetValue(type, out var list))
{
@@ -66,7 +66,7 @@ namespace Avalonia.Interactivity
/// All routed events registered with the provided type.
public IReadOnlyList GetRegistered(Type type)
{
- Contract.Requires(type != null);
+ type = type ?? throw new ArgumentNullException(nameof(type));
if (_registeredRoutedEvents.TryGetValue(type, out var events))
{
diff --git a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs
index 414e67bb94..ef3770d1d9 100644
--- a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs
+++ b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Xunit;
@@ -358,6 +357,29 @@ namespace Avalonia.Interactivity.UnitTests
Assert.Equal(1, called);
}
+ [Fact]
+ public void Removing_Control_In_Handler_Should_Not_Stop_Event()
+ {
+ // Issue #3176
+ var ev = new RoutedEvent("test", RoutingStrategies.Bubble, typeof(RoutedEventArgs), typeof(TestInteractive));
+ var invoked = new List();
+ EventHandler handler = (s, e) => invoked.Add(((TestInteractive)s).Name);
+ var parent = CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel);
+ var target = (IInteractive)parent.GetVisualChildren().Single();
+
+ EventHandler removeHandler = (s, e) =>
+ {
+ parent.Children = Array.Empty();
+ };
+
+ target.AddHandler(ev, removeHandler);
+
+ var args = new RoutedEventArgs(ev, target);
+ target.RaiseEvent(args);
+
+ Assert.Equal(new[] { "3", "2b", "1" }, invoked);
+ }
+
private TestInteractive CreateTree(
RoutedEvent ev,
EventHandler handler,
@@ -414,6 +436,7 @@ namespace Avalonia.Interactivity.UnitTests
set
{
+ VisualChildren.Clear();
VisualChildren.AddRange(value.Cast());
}
}
diff --git a/tests/Avalonia.UnitTests/MouseTestHelper.cs b/tests/Avalonia.UnitTests/MouseTestHelper.cs
index f6454a9cd2..bf75b40a72 100644
--- a/tests/Avalonia.UnitTests/MouseTestHelper.cs
+++ b/tests/Avalonia.UnitTests/MouseTestHelper.cs
@@ -56,7 +56,7 @@ namespace Avalonia.UnitTests
{
_pressedButton = mouseButton;
_pointer.Capture((IInputElement)target);
- target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, Timestamp(), props,
+ source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, Timestamp(), props,
GetModifiers(modifiers), clickCount));
}
}