`

wpf - RoutedEvent and EventManager.RegisterClassHandler

    博客分类:
  • WPF
wpf 
阅读更多

Updated: there was a mistake in my impl, I referenced the code in PresentationCore and modified my code based on the findings.

 

 

 

Routed Event is a special Event in WPF and Silverlight where the Event can traval up/down the tree, and you can have a handler to the parent on the visual tree to handle the event coming from this chidlren.

 

There are two phases of the Event, they are called the RoutingStrategy

 

 

  • Tunnel Phase / Or you can call it the capturing phase
  • Bubble Phase 

 

For more information about phase, please see the information RoutingStrategy.

 

 

First let's see the How to Register Routing Event and How we use use them 

 

Routing Event

Register and Setup the Event

 

To Register the routing event, you may call the EventManager.RegisterRoutedEvent; Below shows the examle. 

 

By convention, you should give the RoutedEvent static readonly field a name ended with the suffix Event.

 

 

 

 public class MyButtonSimple : Button
 {
    // namespace of RoutedEvent is System.Windows.
    public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

}

 

 

You may provide add-and-remove common language runtiem (CLR() event accessor; so that in CODE you can add/remove event handlers

 

 

    // CLR accessors
    // this is necessary if you want to manipulate the Event from the code
    public event RoutedEventHandler Tap
    {
      add { AddHandler(TapEvent, value); }
      remove { RemoveHandler(TapEvent, value); }
    }
 

 

You may add a method to raise the event, this can be used for example, exposing to the client...

 

 

    // and methods that raises teh TapEvent
    // this is to allow cilent to raise the events
    void RaiseTapEvent()
    {
      RaiseEvent(new RoutedEventArgs(MyButtonSimple.TapEvent));
      // you can try the overload to provide  a default Sender 
      //RaiseEvent(new RoutedEventArgs(MyButtonSimple.TapEvent, this));
    }
 

We may raise the event through, e.g. OnClick event (ihow to raise the event and how the event responds to the changes to depends on your needs)

 

 

 

    protected override void OnClick()
    {
      RaiseTapEvent();
      // suppress the base's Click Event
      //base.OnClik();
    }
 

 

Then comes how you use it ; 

 

Use the RoutedEvent 

Instance listeners are particular class instances/elements where one or more handlers have been attached for that routed event by a call to AddHandler. Existing WPF routed events make calls to AddHandler as part of the common language runtime (CLR) event wrapper add{} and remove{} implementations of the event, which is also how the simple XAML mechanism of attaching event handlers via an attribute syntax is enabled. Therefore even the simple XAML usage ultimately equates to an AddHandler call.

Instance Handlers

You can use the event handler by providing an handler to the instance of MyButtonSimple in Xaml, or in CLR. Or you can register an handler to the event in the parent node, which is capable of handling any events that is coming from MyButtonSimple instance, also in Xaml or in CLR.

 

 

To register an handler through an instance method/object is called the Routed Event's Instance Handlers.

 

 

 

Below shows you how to use  Instance Handlers to reponse to the custom Routed Event.

 

 

<Window x:Class="RoutedEventsTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:custom="clr-namespace:RoutedEventsTest"
        >
    <Window.Resources>
        <Style TargetType="{x:Type custom:MyButtonSimple}">
            <Setter Property="Height" Value="20"/>
            <Setter Property="Width" Value="250"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="Background" Value="#808080"/>
        </Style>
    </Window.Resources>
    <StackPanel
        Background="LightGray"
        custom:MyButtonSimple.Tap="MainWindowTapHandler"
        >
        <custom:MyButtonSimple x:Name="mybtnSimple" Tap="TapHandler">Click to Tap Custom event</custom:MyButtonSimple>
        <custom:MyButtonSimpleDerived x:Name="mybtnSimpleDerived">Click to tap Custom Event on Derived Class</custom:MyButtonSimpleDerived>
    </StackPanel>
</Window>
 

 

and below is the code behind code, where the TapHandler and MainWindowTapHandler are defined.

 

 

namespace RoutedEventsTest
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void TapHandler(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("Instance Handler at child node");
      // if you set the Handler to true, then the Bubbling will stop
      //e.Handled = true;
    }

    private void MainWindowTapHandler(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("Instance Handler at the parent node");
    }
  }
}
 

 

Class Handlers

Class listeners exist because types have called a particular EventManager API ,RegisterClassHandler, in their static constructor, or have overridden a class handler virtual method from an element base class

 

Class Handlers respond before instance handlers

On each given element node in an event route, class listeners have the opportunity to respond to the routed event before any instance listener on the element can.

 

class handlers are sometimes used to suppress routed events that a particular control class implementation does not wish to propagate further, or to provide special handling of that routed event that is a feature of the class. For instance, a class might raise its own class-specific event that contains more specifics about what some user input condition means in the context of that particular class. ...

 

To register a Class Handler, you can do is to call EventManager.RegisterClassHandler in its static constructor. 

 

 

    static MyButtonSimple()
    {
      EventManager.RegisterClassHandler(typeof(MyButtonSimple), TapEvent, new RoutedEventHandler(ClassOnTapEvent));
    }

    internal static void ClassOnTapEvent(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("Class handler fire ealier than any instance handler");
    }
 

 

Class Handler Virtuals 

Let's first see what is the class handler virtuals...

 

Some elements, particularly the base elements such as UIElement, expose empty "On*Event" and "OnPreview*Event" virtual methods that correspond to their list of public routed events. These virtual methods can be overridden to implement a class handler for that routed event. The base element classes register these virtual methods as their class handler for each such routed event using RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) as described earlier. The On*Event virtual methods make it much simpler to implement class handling for the relevant routed events, without requiring special initialization in static constructors for each type. 

 

Below I will show you to implement the Class Handler Virtuals

 

First you might call EventManager.ReisterClassHandler on the Constructor; and you may provide an empty OnXXX handler with protected virtua lpattern

 

 

    public MyButtonSimple()
    {
      EventManager.RegisterClassHandler(typeof(MyButtonSimple), TapEvent, new RoutedEventHandler(OnTapEvent));
    }

    protected virtual void OnTapEvent(object sender, RoutedEventArgs e)
    { 
      // this is empty to allow dervied class to override it 
      Console.WriteLine("Class Handler Virtuals makes it much simpler to implement class handling for the relevant routed events, without requiring special initialization in static constructor for each type");
    }
 

Then you can create a derived class, which override the OnTap methods.

 

 

  public class MyButtonSimpleDerived : MyButtonSimple
  {
    protected override void OnTap(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("You can override the base's OnXXXEvent");
      // Calling the base implementation is strongly recommended because the virtual method is on the base class. 
      //base.OnTapEvent(sender, e);
    }
  }

 

 

And last you add the Derived class to xam or in CLR. 

 

 

One tip on how to implement the Class Handlers Virtuals:

 

Calling the base implementation is strongly recommended because the virtual method is on the base class. The standard protected virtual pattern of calling the base implementations from each virtual essentially replaces and parallels a similar mechanism that is native to routed event class handling, whereby class handlers for all classes in a class hierarchy are called on any given instance, starting with the most-derived class' handler and continuing to the base class handler. You should only omit the base implementation call if your class has a deliberate requirement to change the base class handling logic. Whether you call the base implementation before or after your overriding code will depend on the nature of your implementation.

 

 

Order of the Class Handlers and Instance Handlers.

 

 

the order would be (giving that the routing event strategy is Bubbling)

 

 

 

  1. Class Handler
  2. Class handlers virtual
  3. Instance Handler where the Tap event is used (Child)
  4. Instance handler at the parent node (parent)

 

 

Give the code in this example, you will see the following output if you click on the mybuttonSimple

 

 

Class handler fire ealier than any instance handler

Class Handler Virtuals makes it much simpler to implement class handling for the relevant routed events, without requiring special initialization in static constructor for each type

You can override the base's OnXXXEvent

Instance Handler at child node

Instance Handler at the parent node

 

 

Do you see the problem?

 

The System.Window.UIElement impl on Class handler virtuals

 

the problem is that both the derived OnTap and the Base's OnTap are called. 

 

 

The reason is when mybuttonSimple create, MyButtonSimple.OnTap is added  as a class handler.  and when myButtonSimpleDerived is created, the MyButtonSimpleDerived is added as a class handler, and you end up have both the base and derived classes OnXXX method called. 

 

Below is the revised code after reading the Presenation's code. 

 

 

 

 

    static MyButtonSimple()
    {
      EventManager.RegisterClassHandler(typeof(MyButtonSimple), TapEvent, new RoutedEventHandler(OnTapThunk));
    }

    internal static void OnTapThunk(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("Class handler fire ealier than any instance handler");
      MyButtonSimple buttonSimple = sender as MyButtonSimple;
      if (buttonSimple != null)
      {
        buttonSimple.OnTap(sender, e);
      }
    }
 

 

 

Now, the full code of MyButtonSimple and MyButtonSimpleDerived is as follow.

 

 

namespace RoutedEventsTest
{
  public class MyButtonSimple : Button
  {
    // namespace of RoutedEvent is System.Windows.

    public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

    // CLR accessors
    // this is necessary if you want to manipulate the Event from the code
    public event RoutedEventHandler Tap
    {
      add { AddHandler(TapEvent, value); }
      remove { RemoveHandler(TapEvent, value); }
    }

    // and methods that raises teh TapEvent
    // this is to allow cilent to raise the events
    void RaiseTapEvent()
    {
      RaiseEvent(new RoutedEventArgs(MyButtonSimple.TapEvent));
      // you can try the overload to provide  a default Sender 
      //RaiseEvent(new RoutedEventArgs(MyButtonSimple.TapEvent, this));
    }

    protected override void OnClick()
    {
      RaiseTapEvent();
      // suppress the base's Click Event
      //base.OnClik();
    }

    static MyButtonSimple()
    {
      EventManager.RegisterClassHandler(typeof(MyButtonSimple), TapEvent, new RoutedEventHandler(OnTapThunk));
    }

    internal static void OnTapThunk(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("Class handler fire ealier than any instance handler");
      MyButtonSimple buttonSimple = sender as MyButtonSimple;
      if (buttonSimple != null)
      {
        buttonSimple.OnTap(sender, e);
      }
    }

    protected virtual void OnTap(object sender, RoutedEventArgs e)
    { 
      // this is empty to allow dervied class to override it 
      Console.WriteLine("Class Handler Virtuals makes it much simpler to implement class handling for the relevant routed events, without requiring special initialization in static constructor for each type");
    }

  }

  public class MyButtonSimpleDerived : MyButtonSimple
  {
    protected override void OnTap(object sender, RoutedEventArgs e)
    {
      Console.WriteLine("You can override the base's OnXXXEvent");
      // Calling the base implementation is strongly recommended because the virtual method is on the base class. 
      //base.OnTapEvent(sender, e);
    }
  }
}
 

 

 

References:

 

You may find this Karl's blog very intereseting.  - WPF Samples Series - EventManger RegisterClassHandler 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics