According to this MSDN article (among others),
Class handlers are invoked before any instance listener handlers that are attached to an instance of that class, whenever a routed event reaches an element instance in its route.
I'm quite new to RoutedEvent
s so there is a chance that I have a mistake in my code, but it seems as though a class handler attached to a RoutedEvent
that is declared as RoutingStrategy.Tunnel
does not always fire before the instance handlers attached to the same event.
In my example below, I have created a TouchButton
control class with a tunneling RoutedEvent
and a bubbling RoutedEvent
. I have registered class handlers for each. I then created an instance of the class in a window and handle each event in the code behind. I attached the same handler for the tunneling event on both the class element and the Grid
that contains it. All four handlers display their name in a MessageBox
so you can clearly see the order of execution.
This means that if I call e.Handled = true;
in the class PreviewTouch
event handler, I can stop execution from reaching all of the other event handlers except for the one attached to the Grid
element. Is this supposed to be like this, or have I made a mistake somewhere? Otherwise, how can I stop execution from reaching every instance event handler?
Here is the class:
public class TouchButton : Button
{
static TouchButton()
{
EventManager.RegisterClassHandler(typeof(TouchButton), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
EventManager.RegisterClassHandler(typeof(TouchButton), TouchEvent,
new RoutedEventHandler(TouchButton_Touch), true);
}
private static void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_PreviewTouch");
}
private static void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show("Class TouchButton_Touch");
}
public static RoutedEvent TouchEvent = EventManager.RegisterRoutedEvent("Touch",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
public event RoutedEventHandler Touch
{
add { AddHandler(TouchEvent, value); }
remove { RemoveHandler(TouchEvent, value); }
}
public static RoutedEvent PreviewTouchEvent = EventManager.RegisterRoutedEvent(
"PreviewTouch", RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof(TouchButton));
public event RoutedEventHandler PreviewTouch
{
add { AddHandler(PreviewTouchEvent, value); }
remove { RemoveHandler(PreviewTouchEvent, value); }
}
protected override void OnClick()
{
RaiseTouchEvent();
}
private void RaiseTouchEvent()
{
RoutedEventArgs touchEventArgs = new RoutedEventArgs(PreviewTouchEvent);
RaiseEvent(touchEventArgs);
if (!touchEventArgs.Handled) RaiseEvent(new RoutedEventArgs(TouchEvent));
}
}
Here are the instance handlers in the window code behind:
private void TouchButton_PreviewTouch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance PreviewTouch",
((FrameworkElement)sender).Name));
}
private void TouchButton_Touch(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Format("{0} Instance Touch",
((FrameworkElement)sender).Name));
}
Here is the control XAML:
<Grid Name="Grid" Controls:TouchButton.PreviewTouch="TouchButton_PreviewTouch">
<Controls:TouchButton x:Name="TouchButton" Width="200" Height="45" FontSize="24"
Content="Touch me" Touch="TouchButton_Touch" PreviewTouch="TouchButton_PreviewTouch" />
</Grid>
I do understand that the tunneling event is handled by the Grid
element before 'tunneling' down to the TouchButton
element, but I thought that the class handlers were always supposed to fire before the instance handlers. If not, how can I achieve this?
UPDATE >>>
Thanks to @sanguine's answer, I managed to find a way to stop all instance handlers from handling the event. If instead of replacing the declared class handling type of TouchButton
with Grid
as sanguine suggested, I replace it with FrameworkElement
, then it will catch all FrameworkElement
-derived controls.
EventManager.RegisterClassHandler(typeof(FrameworkElement), PreviewTouchEvent,
new RoutedEventHandler(TouchButton_PreviewTouch), true);
MSDN article means - When a traversing event finds an element(in tree) which has provision of both Class and instance handler then it invokes class handler before the instance handler. Therefore in this case when event is fired and tunneled from out to in, it encounters grid but the Grid class does not have any Class handler so it merely calls the instance handler used by the "Grid" instance. If this line is added in toggle button-
EventManager.RegisterClassHandler(typeof(Grid), PreviewTouchEvent, new RoutedEventHandler(TouchButton_PreviewTouch), true);
then before Grid's instance handler, the corresponding Class handler will be called.