Question

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 RoutedEvents 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.

  1. Grid Instance PreviewTouch
  2. Class TouchButton_PreviewTouch
  3. TouchButton Instance PreviewTouch
  4. Class TouchButton_Touch
  5. TouchButton Instance Touch

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);
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top