diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index ea06bdb394..07e42c64e4 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -476,7 +476,7 @@ namespace Avalonia.Controls { _dropDownButton.Click += DropDownButton_Click; _buttonPointerPressedSubscription = - _dropDownButton.AddHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true); + _dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true); } if (_textBox != null) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 9d471a0fc0..d1f54da23a 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.VisualTree; @@ -265,7 +266,7 @@ namespace Avalonia.Controls var toplevel = this.GetVisualRoot() as TopLevel; if (toplevel != null) { - _subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) => + _subscriptionsOnOpen = toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) => { //eat wheel scroll event outside dropdown popup while it's open if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 9dd481cf40..1a12b53b9d 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -279,7 +279,7 @@ namespace Avalonia.Controls.Primitives } } - DeferCleanup(topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel)); + DeferCleanup(topLevel.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel)); DeferCleanup(InputManager.Instance?.Process.Subscribe(ListenForNonClientClick)); diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 0464047273..b3a8f4745e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -22,7 +22,7 @@ namespace Avalonia.Diagnostics } } - return root.AddHandler( + return root.AddDisposableHandler( InputElement.KeyDownEvent, PreviewKeyDown, RoutingStrategies.Tunnel); diff --git a/src/Avalonia.Interactivity/IInteractive.cs b/src/Avalonia.Interactivity/IInteractive.cs index 33baa9453a..51aee78988 100644 --- a/src/Avalonia.Interactivity/IInteractive.cs +++ b/src/Avalonia.Interactivity/IInteractive.cs @@ -23,7 +23,7 @@ namespace Avalonia.Interactivity /// The routing strategies to listen to. /// Whether handled events should also be listened for. /// A disposable that terminates the event subscription. - IDisposable AddHandler( + void AddHandler( RoutedEvent routedEvent, Delegate handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, @@ -38,7 +38,7 @@ namespace Avalonia.Interactivity /// The routing strategies to listen to. /// Whether handled events should also be listened for. /// A disposable that terminates the event subscription. - IDisposable AddHandler( + void AddHandler( RoutedEvent routedEvent, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 9493d86885..321ecbc516 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -27,8 +27,7 @@ namespace Avalonia.Interactivity /// The handler. /// The routing strategies to listen to. /// Whether handled events should also be listened for. - /// A disposable that terminates the event subscription. - public IDisposable AddHandler( + public void AddHandler( RoutedEvent routedEvent, Delegate handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, @@ -38,7 +37,8 @@ namespace Avalonia.Interactivity handler = handler ?? throw new ArgumentNullException(nameof(handler)); var subscription = new EventSubscription(handler, routes, handledEventsToo); - return AddEventSubscription(routedEvent, subscription); + + AddEventSubscription(routedEvent, subscription); } /// @@ -49,8 +49,7 @@ namespace Avalonia.Interactivity /// The handler. /// The routing strategies to listen to. /// Whether handled events should also be listened for. - /// A disposable that terminates the event subscription. - public IDisposable AddHandler( + public void AddHandler( RoutedEvent routedEvent, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, @@ -69,7 +68,7 @@ namespace Avalonia.Interactivity var subscription = new EventSubscription(handler, routes, handledEventsToo, (baseHandler, sender, args) => InvokeAdapter(baseHandler, sender, args)); - return AddEventSubscription(routedEvent, subscription); + AddEventSubscription(routedEvent, subscription); } /// @@ -188,7 +187,7 @@ namespace Avalonia.Interactivity return result; } - private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) + private void AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) { _eventHandlers ??= new Dictionary>(); @@ -199,8 +198,6 @@ namespace Avalonia.Interactivity } subscriptions.Add(subscription); - - return new UnsubscribeDisposable(subscriptions, subscription); } private readonly struct EventSubscription @@ -225,22 +222,5 @@ namespace Avalonia.Interactivity public bool HandledEventsToo { get; } } - - private sealed class UnsubscribeDisposable : IDisposable - { - private readonly List _subscriptions; - private readonly EventSubscription _subscription; - - public UnsubscribeDisposable(List subscriptions, EventSubscription subscription) - { - _subscriptions = subscriptions; - _subscription = subscription; - } - - public void Dispose() - { - _subscriptions.Remove(_subscription); - } - } } } diff --git a/src/Avalonia.Interactivity/InteractiveExtensions.cs b/src/Avalonia.Interactivity/InteractiveExtensions.cs index 414c408080..e6c93e26b2 100644 --- a/src/Avalonia.Interactivity/InteractiveExtensions.cs +++ b/src/Avalonia.Interactivity/InteractiveExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Disposables; using System.Reactive.Linq; namespace Avalonia.Interactivity @@ -11,6 +12,28 @@ namespace Avalonia.Interactivity /// public static class InteractiveExtensions { + /// + /// Adds a handler for the specified routed event and returns a disposable that can terminate the event subscription. + /// + /// The type of the event's args. + /// Target for adding given event handler. + /// The routed event. + /// The handler. + /// The routing strategies to listen to. + /// Whether handled events should also be listened for. + /// A disposable that terminates the event subscription. + public static IDisposable AddDisposableHandler(this IInteractive o, RoutedEvent routedEvent, + EventHandler handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false) where TEventArgs : RoutedEventArgs + { + o.AddHandler(routedEvent, handler, routes, handledEventsToo); + + return Disposable.Create( + (instance: o, handler, routedEvent), + state => state.instance.RemoveHandler(state.routedEvent, state.handler)); + } + /// /// Gets an observable for a . /// @@ -31,7 +54,7 @@ namespace Avalonia.Interactivity o = o ?? throw new ArgumentNullException(nameof(o)); routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent)); - return Observable.Create(x => o.AddHandler( + return Observable.Create(x => o.AddDisposableHandler( routedEvent, (_, e) => x.OnNext(e), routes,