WPF routed event
What’s routed event? It is an amazing thing, after have read Dependency Property code, I can found that the design thought of routed event is as much same as dependency property.
o Use of Routed event
Why need routed event?When a user presses the left mouse button
with the mouse pointer over a standard Button, however, they are really interacting with
its ButtonChrome or TextBlock visual child. Because the event travels up the visual tree,
the Button eventually sees the event and can handle it.
1. Register your routed event
public static readonly RoutedEvent MyButtonTagEvent = EventManager.RegisterRoutedEvent("MyButtonTag", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(UserControlButton));
parmaters:
name
The name of the routed event. The name must be unique within the owner type and cannot be null, or an empty string.
routingStrategy
The routing strategy of the event as a value of the enumeration.
handlerType
The type of the event handler. This must be a delegate type and cannot be null.
ownerType
The owner class type of the routed event. This cannot be null.
Explain routing strategy specifically:
Tunneling—The event is first raised on the root, then on each element down the
tree until the source element is reached (or until a handler halts the tunneling by
marking the event as handled).
Bubbling—The event is first raised on the source element, then on each element up
the tree until the root is reached (or until a handler halts the bubbling by marking
the event as handled).
Direct—The event is only raised on the source element. This is the same behavior
as a plain .NET event, except that such events can still participate in mechanisms
specific to routed events such as event triggers.
Preview event is tunnel event; handling a Preview event on any element other than the element that raised it (the element that is reported as the source in the event data) has the effect of not providing an element the opportunity to handle the event that it originated.
For input events specifically, Preview events also share event data instances with the equivalent bubbling event. If you use a Preview event class handler to mark the input event handled, the bubbling input event class handler will not be invoked
2. Provide CLR accessors for the event. Use AddHandler and removeHandler method to add or remove event handler.
public event RoutedEventHandler MyButtonTag
{
add { AddHandler(MyButtonTagEvent, value); }
remove { RemoveHandler(MyButtonTagEvent, value); }
}
Compare to normal event:
public event EventHandler NewEvent
{
add{NewEvent += value ;}
remove {NewEvent -= value ;}
}
3. Set when to raise the event.
protected override void OnClick()
{
//raises the event
RaiseEvent(new RoutedEventArgs(UserControlButton.MyButtonTagEvent, this));
}
4. Write event handler code:
protected void MyButtonClick(object source,RoutedEventArgs e)
{
MessageBox.Show("MyButtonClick");
}
5. Add event handler to event
<ab:UserControlButton x:Name=="button2" MinWidth="75" Margin="10" MyButtonTag=" MyButtonClick">OK</ab:UserControlButton>
Corresponding Use Code:
public Window1()
{
button2.MyButtonTag += new RoutedEventHandler(MyButtonClick);
}
o Register a routed event(EventManager)
In static GlobalEventManager Class’s register method, first create a new RoutedEvent Class instance , then use a list to store routed event information when you register a routed event. This list is static, so store all events globally.
internal static class GlobalEventManager
{
………….
private static DTypeMap _dTypedRoutedEventList = new DTypeMap(10);
……………
}
From the code, we can see the index of _dTypedRoutedEventList is owner type which is a parameter in register method. The list element value is a FrugalObjectList<RoutedEvent>, stores all routed event for one owner type.
internal static void AddOwner(RoutedEvent routedEvent, Type ownerType)
{
…………..
FrugalObjectList<RoutedEvent> list2;
DependencyObjectType type = DependencyObjectType.FromSystemTypeInternal(ownerType);
object obj3 = _dTypedRoutedEventList[type];
if (obj3 == null) // if have no already
{
list2 = new FrugalObjectList<RoutedEvent>(1);
_dTypedRoutedEventList[type] = list2;
}
else
{
list2 = (FrugalObjectList<RoutedEvent>) obj3;
}
if (!list2.Contains(routedEvent))
{
list2.Add(routedEvent); // add into registed routed event
}
……………….
}
_dTypedRoutedEventList[ownerType] = Routed Event list of this type.
Like Dependency property ,there is FromName method to use _dTypedRoutedEventList for getting routed event through owner type. This method is used when parsing the xaml like dependency property do.
o AddHandler and RemoveHandler (UIElement ,ContentElment)
UIElement is the base class for all visual objects with support for routed events,
command binding, layout, and focus. So, AddHandler method and RemoveHandler method are in this class, used to add event handler or remove an event Handler for a routed event. Then let’s see what it has done in these two methods.
public class UIElement : Visual, IAnimatable, IInputElement
{
…………..
public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);
…………
}
参数:
handledEventsToo:(default=false)
Declares whether the handler should be invoked in cases where the GTMT#routed event has already been marked as handled. true denotes that the handler should be invoked even for cases where the GTMT#routed event is marked handled in its arguments object. false denotes that handler should not be invoked if the GTMT#routed event is already marked handled. The default is false. Asking to handle already-handled routed events is not common.
routedEvent
Identifier object for the GTMT#routed event being handled.
handler
A reference to the handler implementation.
AddHandler sequence diagram:
The UIElement uses a EventHandlerStore class instance to store the handlers of each routed event. EventHandlerStore is collection data structure, I won't explain it specifically. We can see it as an array. Each elment in array is an event hander list which is belongs to each event. EventHandlerStore[event.globalIndex] = list of handlers for this event.
Notice UIElement use a UncommonFiled<EventHandlerStore> to operate EventHandlerStore. I don’t want to explain it specifically either.
internal static readonly UncommonField<System.Windows.EventHandlersStore> EventHandlersStoreField = new UncommonField<System.Windows.EventHandlersStore>();
internal System.Windows.EventHandlersStore EventHandlersStore
{
[FriendAccessAllowed]
get
{
if (!this.ReadFlag(CoreFlags.ExistsEventHandlersStore))
{
return null;
}
return EventHandlersStoreField.GetValue(this);
}
}
EventHandlerStore will store itself into _effectiveValue[] of current UIElement instance ,and use EventHandlerStore._GlobalIndex as index:
internal void EnsureEventHandlersStore()
{
EventHandlersStoreField.SetValue(this, new System.Windows.EventHandlersStore());
}
public void SetValue(DependencyObject instance, T value)
{
EntryIndex entry = instance.LookupEntry(this._globalIndex);
if (!object.ReferenceEquals(value, this._defaultValue))
{
instance.SetEffectiveValue(entry, null, this._globalIndex, null, value, BaseValueSourceInternal.Local);
this._hasBeenSet = true;
}
else
{
instance.UnsetEffectiveValue(entry, null, null);
}
}
EventHandlerStore .AddRoutedEventHandler method code like:
internal class EventHandlersStore
{
public EventHandlersStore()
{
this._entries = new FrugalMap();
}
public void AddRoutedEventHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo)
{
RoutedEventHandlerInfo info = new RoutedEventHandlerInfo(handler, handledEventsToo);
FrugalObjectList<RoutedEventHandlerInfo> list = this[routedEvent];
if (list == null)
{
this._entries[routedEvent.GlobalIndex] = list = new FrugalObjectList<RoutedEventHandlerInfo>(1);
}
list.Add(info);
}
}
Notice the data struct (_entries) is not the data structure in GlobalEventManager(_dTypedRoutedEventList) when registration.
We can see it create a RoutedEventHandlerInfo class instance to wrapper the handler which client want to add, then add it a RoutedEventHandlerInfo list , then add this list to array , index is also the GlobalIndex which is belongs to one routed event uniquely.
So, in store, we maintain a list of handler for each routed event. Notice that this store is instance member, not static, so each control of WPF will maintain their own store which has its own routed event handler.
UIElement.RemoveHandler method is similar to AddHandler method, it will delete the handler from the corresponding routed event handler list in its EventHandlerStore.
internal class EventHandlersStore
{
public void RemoveRoutedEventHandler(RoutedEvent routedEvent, Delegate handler)
{
FrugalObjectList<RoutedEventHandlerInfo> list = this[routedEvent];
for (int i = 0; i < list.Count; i++)
{
RoutedEventHandlerInfo info = list[i];
if (info.Handler == handler)
{
list.RemoveAt(i);
return;
}
}
o Register ClassHandler
When we use AddHandler to add event handler, we call this handler is instance handler, because it is stored in instance member _effectiveValue[] of one UIElement instance.
A class handler is like a static handler that exists for all instances of the class.
EventManager.RegisterClassHandler(typeof(UserControlButton), MyButtonTagEvent, new RoutedEventHandler(MyButtonClassHandler));
EventManager.RegisterClassHandler (Type, RoutedEvent, Delegate, Boolean)
Parameters:
handledEventsToo
true to have this class handler be invoked even if arguments of the routed event have been marked as handled. false to retain the default behavior of not invoking the handler on any marked-handled event.
routedEvent
The routed event identifier of the event to handle.
classType
The type of the class that is declaring class handling.
handler
Reference to the class handler implementation.
If you implement a class handler that has the behavior of marking the event as handled. It will block all instance handlers except instance handler’s handledEventsToo parameter is true.
Many of the WPF base element events provide class handling virtual methods. By overriding these methods in classes that inherit the base classes, you can implement class handling without calling RegisterClassHandler in static constructors. These class handling methods typically exist for input events and have names that start with "On" and end with the name of the event being class handled.
Confusions, I have made some experiments, and two confusions:
1. Msdn says “The class handler is static, but an access instances through the sender parameter and/or the event data. Class handlers are invoked before instance handlers.”, but I try the method can not be static, code like:
public class UserControlButton : Button
{
public static readonly RoutedEvent MyButtonTagEvent;
public UserControlButton()
{
EventManager.RegisterClassHandler(typeof(UserControlButton), MyButtonTagEvent, new RoutedEventHandler(MyButtonClassHandler),true)
}
protected void MyButtonClassHandler(object source, RoutedEventArgs e)
{
MessageBox.Show("MyButtonTag class handler ");
if (source is Control)
{
Control tmp = source as Control;
tmp.Background = Brushes.Green;
}
e.Handled = true;}
}
Solution : if we registerClasshandler in instance method, it will add class handler when call this instance method, and the handler can also be instance. WPF seems always registerClassHandler in static constructor method, so handler must be static, and the handler will be added when this class loaded.
2. We can register ClassHandler in one class for another class’s event, something like attached event:
public class UserControlLabel : Label
{
public UserControlLabel()
{
EventManager.RegisterClassHandler(typeof(UserControlButton), UserControlButton.MyButtonTagEvent, new RoutedEventHandler(MyButtonClick), true);
}
protected void MyButtonClick(object source, RoutedEventArgs e)
{
this.Background = Brushes.Green;
MessageBox.Show("Label ButtonTagEvent"); }
}
The classType parameter should be UserControlButton, if is UserControlLabel , the handler will not be triggered. But when I get to window, it works fine:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(Window1), UserControlButton.UserControlButton.MyButtonTagEvent, new RoutedEventHandler(MyButtonClick),true);
}
protected void MyButtonClick(object source,RoutedEventArgs e)
{
MessageBox.Show("Window_MyButtonClick");
this.Title = "Window_MyButton Clicked";
e.Handled = true;
}
}
Now, let’s dig into code, find what RegisterClassHandler do.
Instance handler has an EventHandlerStore, it stores in instance member _effecitiveValue[], Classhandler has a ClassHandlerStore, it stores in static map _dTypedClassListeners of GlobalEventManager. It is static, so global.
_dTypedClassListeners[ownertype] = this type’s ClassHandlerStore.
ClassHandlerStore._eventhandlerslist[event]=list of handers of this event. The code is below:
internal static class GlobalEventManager
{
………….
private static DTypeMap _dTypedClassListeners = new DTypeMap(100);……………
}
internal class ClassHandlersStore
{
internal RoutedEventHandlerInfoList AddToExistingHandlers(int index, Delegate handler, bool handledEventsToo)
{
RoutedEventHandlerInfo info = new RoutedEventHandlerInfo(handler, handledEventsToo);
RoutedEventHandlerInfoList handlers = this._eventHandlersList.List[index].Handlers;
if ((handlers == null) || !this._eventHandlersList.List[index].HasSelfHandlers)
{
handlers = new RoutedEventHandlerInfoList();
handlers.Handlers = new RoutedEventHandlerInfo[] { info };
handlers.Next = this._eventHandlersList.List[index].Handlers;
this._eventHandlersList.List[index].Handlers = handlers;
this._eventHandlersList.List[index].HasSelfHandlers = true;
return handlers;
}
int length = handlers.Handlers.Length;
RoutedEventHandlerInfo[] destinationArray = new RoutedEventHandlerInfo[length + 1];
Array.Copy(handlers.Handlers, 0, destinationArray, 0, length);
destinationArray[length] = info;
handlers.Handlers = destinationArray;
return handlers;
}
}
The last step 1.1.4:ClassHandlerStore.UpdateSubClasshandler (), it will add the handler to all subclass of ownertype’s event handlers list.
WPF Library use class handler example:
public class Control : FrameworkElement
{
static Control()
{ EventManager.RegisterClassHandler(typeof(Control), UIElement.MouseRightButtonDownEvent, new MouseButtonEventHandler(Control.HandleDoubleClick), true);
}
private static void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
}
}
My example, we can block all control’s button down event except set handledEventsToo istrue, like:
public partial class Window1 : Window
{
public Window1()
{ EventManager.RegisterClassHandler(typeof(Control), UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(UIElementMouseButton), true);
}
private static void UIElementMouseButton(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("UIElementMouseButtonDown");
e.Handled = true;
}
}
o Let’s RaiseEvent() (UIElement , ContentElement)
RaiseEvent(new RoutedEventArgs(UserControlButton.MyButtonTagEvent, this));
This method is also belongs to UIElement,the sequence diagram is :
EventRouteFactory has a stack that contains EventRoute, EventRoute is a runtime data structure and is an event information wrapper (its class diagram see appendix1), it contains a specifically runtime event’s information, which contains routed event, its source list, its handlers which should be triggered. We know for a routed event, it handlers or sources are different depending on this event’s route strategy at runtime, so this is what “1.1.2.1: static BuildRouteHelper (this,EventRoute,args)” do .
From the upper picture, You can see some virtual methods that we can extend in BuildRouteHelper method.
Public class UIElement
{
internal virtual object AdjustEventSource(RoutedEventArgs args)
{
return null;
}
}
EventRoute.Add(source) will add source to source list _sourceItemList of eventRoute:
public sealed class EventRoute
{
private FrugalStructList<SourceItem> _sourceItemList;
internal void AddSource(object source)
{
int count = this._routeItemList.Count;
this._sourceItemList.Add(new SourceItem(count, source));
}
}
This source list is used for event route, see the invokeHandlers section.
Public class UIElement
{
internal virtual bool BuildRouteCore(EventRoute route, RoutedEventArgs args)
{
return false;
}
}
In Element.GetUIParent(bool) method, there are also one virtual GetUIParentCore() method:
Public class UIElement
{
internal DependencyObject GetUIParent(bool continuePastVisualTree)
{
return UIElementHelper.GetUIParent(this, continuePastVisualTree);
}
}
Public class Ms.Internal.UIElementHelper
{
internal static DependencyObject GetUIParent(DependencyObject child, bool continuePastVisualTree)
{
DependencyObject containingUIElement = null;
DependencyObject o = null;
if (child is Visual)
{
o = ((Visual) child).InternalVisualParent;
}
else
{
o = ((Visual3D) child).InternalVisualParent;
}
containingUIElement = InputElement.GetContainingUIElement(o);
if ((containingUIElement == null) && continuePastVisualTree)
{
UIElement element = child as UIElement;
if (element != null)
{
return (InputElement.GetContainingInputElement(element.GetUIParentCore()) as DependencyObject);
}
UIElement3D elementd = child as UIElement3D;
if (elementd != null)
{
containingUIElement = InputElement.GetContainingInputElement(elementd.GetUIParentCore()) as DependencyObject;
}
}
return containingUIElement;
}
protected internal virtual DependencyObject GetUIParentCore()
{
return null;
}
UIElement::AddToEventRoute(EventRoute,RoutedEventArgs)adds handlers to the specified EventRoute for the current UIElement event handler collection.
Event handlers are from two places: the first is class handlers, which are stored in classhandlerStore of _dTypedClassListeners ;the second place is event handlers ,which are stored in EvetHandlerStore of _effectiveValue[].
Public class UIElement
{
public void AddToEventRoute(EventRoute route, RoutedEventArgs e)
{
//add class handlers
for (RoutedEventHandlerInfoList list = GlobalEventManager.GetDTypedClassListeners(base.DependencyObjectType, e.RoutedEvent); list != null; list = list.Next)
{
for (int i = 0; i < list.Handlers.Length; i++)
{
route.Add(this, list.Handlers[i].Handler, list.Handlers[i].InvokeHandledEventsToo);
}
}
//add event handlers
FrugalObjectList<RoutedEventHandlerInfo> list2 = null;
System.Windows.EventHandlersStore eventHandlersStore = this.EventHandlersStore;
if (eventHandlersStore != null)
{
list2 = eventHandlersStore[e.RoutedEvent];
if (list2 != null)
{
for (int j = 0; j < list2.Count; j++)
{
RoutedEventHandlerInfo info2 = list2[j];
RoutedEventHandlerInfo info = list2[j];
route.Add(this, info2.Handler, info.InvokeHandledEventsToo);
}
}
}
this.AddToEventRouteCore(route, e);
}
}
In EventRoute, use a _routeItemList to store the event handlers and target.
public sealed class EventRoute
{
public void Add(object target, Delegate handler, bool handledEventsToo)
{
RouteItem item = new RouteItem(target, new RoutedEventHandlerInfo(handler, handledEventsToo));
this._routeItemList.Add(item);
}
}
The AddToEventRouteCore method is virtual, we can extend it:
Public class UIElement
{
internal virtual void AddToEventRouteCore(EventRoute route, RoutedEventArgs args)
{
}
}
1.1.3:EventRoute.InvokeHandlers(this,args) ,this method really invokes handlers.
internal void InvokeHandlers(object source, RoutedEventArgs args)
{
this.InvokeHandlersImpl(source, args, false);
}
The InvokeHandlersImpl method program diagram is like below:
GetTunnelSource and GetBubbleSource methods will try to find current event source’s tunnel Source or BubbleSource from _sourceItemList of EventRoute, then to invoke their event handlers . This _sourceItemList is updated in BuildRouteHelper method which we have said before. The two methods code , I put them into Appendix 2.
this._routeItemList[k].InvokeHandler(args),Invoke Handlers have the procedure like below:
internal struct RouteItem
{
internal void InvokeHandler(RoutedEventArgs routedEventArgs)
{
this._routedEventHandlerInfo.InvokeHandler(this._target, routedEventArgs);
}
}
public struct RoutedEventHandlerInfo
{
internal void InvokeHandler(object target, RoutedEventArgs routedEventArgs)
{
if (!routedEventArgs.Handled || this._handledEventsToo)
{
if (this._handler is RoutedEventHandler) //routedEventHandler
{
((RoutedEventHandler) this._handler)(target, routedEventArgs);
}
Else //other my defined event handler
{
routedEventArgs.InvokeHandler(this._handler, target);
}
}
}
}
public class RoutedEventArgs : EventArgs
{
internal void InvokeHandler(Delegate handler, object target)
{
this.InvokingHandler = true;
try
{
this.InvokeEventHandler(handler, target);
}
finally
{
this.InvokingHandler = false;
}
}
}
The InvokeEventHandler method is virtual ,we can extend it, it provides a way to invoke event handlers in a type-specific way, which can increase efficiency over the base implementation.
Parameter:
genericTarget
The target on which the provided handler should be invoked.
genericHandler
The generic handler / delegate implementation to be invoked.
protected virtual void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{ this.InvokingHandler = true;
try
{
if (genericHandler is RoutedEventHandler)
{
((RoutedEventHandler) genericHandler)(genericTarget, this);
}
else
{
genericHandler.DynamicInvoke(new object[] { genericTarget, this });
}
}
finally
{
this.InvokingHandler = false;
}
}
}
o Attached Event
Every routed event can be used as attached event, use of attached event:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfRoutedEventApp.Window1"
ListBox.SelectionChanged="ListBox_SelectionChanged ">
Corresponding Code:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
AddHandler(ListBox.SelectionChangedEvent, new SelectionChangedEventHandler (ListBox_SelectionChanged), false);
}
In “AddHandler” section before, we get to know it use EventhandlerStore which is saved in _effectiveValue[] to store event handlers. EventHandlerStore[event.globalIndex] = list of handlers for this event. So,for attached event, its handlers are also stored in it:
Window1 instance’s EventHandlerStore[ListBox.SelectionChangedEvent.globalIndex] = list of handlers. For ListBox.SelectionChangedEvent.globalIndex , it is the same for any elements. And in RaiseEvent and building eventRoute, this globalIndex can be used to find all handlers from any elment that will process this ListBox event. Specifically, you need to see the “RaiseEvent” section.
o Other topics:
- Property Change Events
- Weak Event Patterns.
(Strong reference makes ordinarily attaching an event handler for a listener (Window1) causes the listener to have an object lifetime that influenced by the object lifetime for the source (ListBox.selectionChangeEvent) (unless the event handler is explicitly removed).)
WeakEventManager, in WPF, it has LostFocusEventManager, DataChangeEventManager, PropertyEventChangeManager, etc. inherited from weak event manager.
Appendix1:
Appendix2:
EventRoute.GetTunnelSource and EventRoute.GetBubbleSource methods Code:
private object GetBubbleSource(int index, out int endIndex)
{
if (this._sourceItemList.Count == 0)
{
endIndex = this._routeItemList.Count;
return null;
}
SourceItem item7 = this._sourceItemList[0];
if (index < item7.StartIndex)
{
SourceItem item6 = this._sourceItemList[0];
endIndex = item6.StartIndex;
return null;
}
for (int i = 0; i < (this._sourceItemList.Count - 1); i++)
{
SourceItem item5 = this._sourceItemList[i];
if (index >= item5.StartIndex)
{
SourceItem item4 = this._sourceItemList[i + 1];
if (index < item4.StartIndex)
{
SourceItem item3 = this._sourceItemList[i + 1];
endIndex = item3.StartIndex;
SourceItem item2 = this._sourceItemList[i];
return item2.Source;
}
}
}
endIndex = this._routeItemList.Count;
SourceItem item = this._sourceItemList[this._sourceItemList.Count - 1];
return item.Source;
}
private object GetTunnelSource(int index, out int startIndex)
{
if (this._sourceItemList.Count == 0)
{
startIndex = 0;
return null;
}
SourceItem item7 = this._sourceItemList[0];
if (index < item7.StartIndex)
{
startIndex = 0;
return null;
}
for (int i = 0; i < (this._sourceItemList.Count - 1); i++)
{
SourceItem item6 = this._sourceItemList[i];
if (index >= item6.StartIndex)
{
SourceItem item5 = this._sourceItemList[i + 1];
if (index < item5.StartIndex)
{
SourceItem item4 = this._sourceItemList[i];
startIndex = item4.StartIndex;
SourceItem item3 = this._sourceItemList[i];
return item3.Source;
}
}
}
SourceItem item2 = this._sourceItemList[this._sourceItemList.Count - 1];
startIndex = item2.StartIndex;
SourceItem item = this._sourceItemList[this._sourceItemList.Count - 1];
return item.Source;
}