// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Interactivity { using System; using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using Perspex.Layout; using Perspex.VisualTree; /// /// Base class for objects that raise routed events. /// public class Interactive : Layoutable, IInteractive { private Dictionary> eventHandlers = new Dictionary>(); /// /// Gets the interactive parent of the object for bubbling and tunnelling events. /// IInteractive IInteractive.InteractiveParent { get { return ((IVisual)this).VisualParent as IInteractive; } } /// /// Adds a handler for the specified routed event. /// /// 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 IDisposable AddHandler( RoutedEvent routedEvent, Delegate handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) { Contract.Requires(routedEvent != null); Contract.Requires(handler != null); List subscriptions; if (!this.eventHandlers.TryGetValue(routedEvent, out subscriptions)) { subscriptions = new List(); this.eventHandlers.Add(routedEvent, subscriptions); } var sub = new EventSubscription { Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, }; subscriptions.Add(sub); return Disposable.Create(() => subscriptions.Remove(sub)); } /// /// Adds a handler for the specified routed event. /// /// The type of the event's args. /// 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 IDisposable AddHandler( RoutedEvent routedEvent, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TEventArgs : RoutedEventArgs { return this.AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo); } /// /// Removes a handler for the specified routed event. /// /// The routed event. /// The handler. public void RemoveHandler(RoutedEvent routedEvent, Delegate handler) { Contract.Requires(routedEvent != null); Contract.Requires(handler != null); List subscriptions; if (this.eventHandlers.TryGetValue(routedEvent, out subscriptions)) { subscriptions.RemoveAll(x => x.Handler == handler); } } /// /// Removes a handler for the specified routed event. /// /// The type of the event's args. /// The routed event. /// The handler. public void RemoveHandler(RoutedEvent routedEvent, EventHandler handler) where TEventArgs : RoutedEventArgs { this.RemoveHandler(routedEvent, (Delegate)handler); } /// /// Raises a routed event. /// /// The event args. public void RaiseEvent(RoutedEventArgs e) { Contract.Requires(e != null); e.Source = e.Source ?? this; e.OriginalSource = e.OriginalSource ?? this; if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct) { e.Route = RoutingStrategies.Direct; this.RaiseEventImpl(e); } if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Tunnel) != 0) { this.TunnelEvent(e); } if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Bubble) != 0) { this.BubbleEvent(e); } } /// /// Bubbles an event. /// /// The event args. private void BubbleEvent(RoutedEventArgs e) { Contract.Requires(e != null); e.Route = RoutingStrategies.Bubble; foreach (var target in this.GetBubbleEventRoute()) { ((Interactive)target).RaiseEventImpl(e); } } /// /// Tunnels an event. /// /// The event args. private void TunnelEvent(RoutedEventArgs e) { Contract.Requires(e != null); e.Route = RoutingStrategies.Tunnel; foreach (var target in this.GetTunnelEventRoute()) { ((Interactive)target).RaiseEventImpl(e); } } /// /// Carries out the actual invocation of an event on this object. /// /// The event args. private void RaiseEventImpl(RoutedEventArgs e) { Contract.Requires(e != null); e.RoutedEvent.InvokeClassHandlers(this, e); List subscriptions; if (this.eventHandlers.TryGetValue(e.RoutedEvent, out subscriptions)) { foreach (var sub in subscriptions.ToList()) { bool correctRoute = (e.Route & sub.Routes) != 0; bool notFinished = !e.Handled || sub.AlsoIfHandled; if (correctRoute && notFinished) { sub.Handler.DynamicInvoke(this, e); } } } } } }