Untergeordnete Seiten
  • UI event system

  Wiki Navigation

    Loading...


 Recently Updated


 Latest Releases

 MediaPortal 1.32
            Releasenews | Download
 MediaPortal 2.5
            Releasenews | Download


Table of Contents
WIP! the UI event system is work in progress! Currently it is available in the GIT branch FEAT_EventManager only.

 

The UI event system is similar to the WPF Routed Event system. Yu can read more about the WPF Routed Events here:

http://msdn.microsoft.com/en-us/library/ms742806(v=vs.110).aspx

This page gives an overview of the MP UI event system an the differences and limitations compared to WPF.

What are Routed Events

At first a routed event is an event like any standard .NET event. But unlike normal events, routed events are not only raised on one element, but on all elements up the visual tree of the UI. Events can bubble up the tree from the original source to the UI root, or they can be tunneled from the UI root to the original source.

For more information about bubbling an tunneling see below.

Bubbling and Tunneled events

Routed events can be invoked as bubbled, tunneled or direct events.

Bubbled events are invoked 1st on the original source and bubble up the visual tree to the root element.

Tunneled events are invoked 1st on the root element and are then tunneled down the visual tree to the original source.

Direct events are invoked on the original source only.

Many events are available in a bubbling and a tunneled version.
MouseDown for example is a bubbled event, which is invoked for the hovered element 1st, and the bubbles up the visual tree to the root element.
PreviewMouseDown is the associated tunneled event, which is invoked 1st on the root element, and then upwards the visual tree to the hovered element.
The tunneled (preview) event is always invoked 1st. Also the tunneled and bubbled event share the same event arguments. By this if the preview event is handled (see below), the bubbling event is handled too.
Often there are also class handlers like OnPreviewMouseDown() and OnMouseDown(). The class handler for an event is always called before the event is invoked and receives the same RoutedEventArgs. By this handling an event in the class handler is the same as handling it in the event handler.

RoutedEventArgs

RoutedEventArgs is a specialized EventArgs class for routed events. It has to be used as base class for all routed events.

The RoutedEventArgs adds 3 properties:

  • OriginalSource:
    The original source is the elements on which the RaiseEvent method was called.
  • Source:
    Source is the element to which the event handler was added that is currently executed. This is usually the same as the sender object of the event handler.
  • Handled:
    Handled is a flag which indicates if the event is already handled by a previous invoked event handler. Setting Handled to true will prevent other event handler from being invoked, as long as the handler was not registered with the handledEventsToo flag.

Specialized event args can add any data needed for handling these events.

The concept of Handled events

Routed events can be marked as handled. Handled events stops invoking other subscribers when bubbling or tunneling up and down the visual tree. In fact this is not fully true. Any subscriber can set a flag to also get invoked on handled events.

To mark an event as handled, the Handled property of the RoutedEventArgs that is passed to every handler has to bet to true.

private void MyEventHandler(object sender, RoutedEvntArgs e)
{
  e.Handled = true;
}

As soon as an event is marked as handled, no remaining event handlers are invoked. As always there is an exception to this rule. If the handler was registered with the flag handledEventsToo set to true, it gets called if the Handled is set to true already.

Adding event handlers

Event handler can be added in several ways.

Using standard .NET event syntax

If a standard .NET event was added to the class for the routed event, it can be used like any other event:

element.MouseDown += MyMouseDownHandler;

...

private void MyMouseDownHandler(object sender, MouseButtonEventArgs e)
{
   ...
}

Using this syntax does not allow to set the handledEventsToo flag to true.

To remove the handler the -= operator can be used.

Using the UIElement.AddHandler method

Using the AddHandler method is the same as using the default C# syntax, but allows to use some options.

element.AddHandler(UIElement.MouseDownEvent, MyMouseDownHandler);

The 1st parameter defines the event, the 2nd one is the handler.
By this you can also add an handler for an event that is defined in the type of this element. This allows for example to handle a ItemsControl.SelectionChangedEvent by one of it's VisualParents.

There is also an overload of AddHandler() which allows to spcify the handledEventsToo flag as 3rd parameter.

AddHandler also has overloads to use an ICommandStencil instead of an delegate as event handler.

To remove an handler UIElement.RemoveHandler() can be used.

Adding event handler in XAML code

Event handler can be attached to any element directly in XAML code.

<StackPanel MouseDown="{CommandStencil Source={StaticResource Model}, Path=MyMouseDownHandler}">
   ...
</StackPanel>

Because there is no code behind class in MPF like it is in WPF, it is not possible to put a simple method name as event handler. Instead a command stencil is used, which can be target any object that can be defined by the Source attribute. If no Source attribute is defined, the DataContext is used.

The event handler in the sample above is not only called for the stack panel itself, but also for all child elements of the stack panel, as long as these does not set the Handled flag.

It is not possible to set the handledEventsToo flag by adding the handler like this.

Adding event handler in XAML code using a style

Event handlers can be added to elements using a style.

<Style x:Key="MyStyle">
  <EventSetter Event="UIElement.MouseDown" Handler="{CommandStencil Source={StaticResource Model}, Path=MyMouseDownHandler}" HandledEventsToo="true"/>
</Style>
...
<StackPanel Style="{StaticResource MyStyle}">
  ..
</StackPanel>

When using a style to add a event handler, a fully qualified name for the event (including the type) must be used.

Using an EventSetter also allows to the the HandledEventsToo flag to true.

Using routed events as Trigger in XAML

Routed events can be used as trigger.

<Style x:Key="MyStyle">
  <Style.Triggers>
    <EventTrigger RoutedEvent="UIElement.MouseDown">
      <TriggerCommand Command="{Command Source={StaticResource Model}, Path=MouseDownCommand}"/>
    </EventTrigger>
  <Style.Triggers>
</Style>

The difference to using an simple EventSetter is that an EventTrigger can be used to trigger storyboards or invoke methods with any signature.

Using class handlers

A class handler is a method of a class, that is always invoked on any instance of the class, without adding an handler to them.

To add a class handler call the EventManager.RegisterCalssHandler method.

public class MyElement : UIElement
{
  static MyElement()
  {
    EventManager.RegisterClassHandler(typeof(MyElement), UIElement.MouseDown, new MouseButtonEventHandler(OnMyMouseDownHandler), false);
  }
  
  private OnMyMouseDownHandler(object sender, MouseButtonEventArgs args)
  {
    ...
  }
}

The RegisterCalssHandler  method takes the class type for which the event is registered, the routed event identifier, the handler delegate and the handledEventsToo flag.

Adding new routed events to elements

New routed events can be added to any class based on UIElement.

A routed event is defined by the static routed event identifier and a standard .NET event event declaration, which wraps the Add/RemoveHandler methods.

You can use any of the existing RoutedEventArgs classes for event args or define a custom one (see below).

Optionally a class handler can be defined for the event (see above).

public class MyElement : UIElement
{
  public static readonly RoutedEvent MySelectionChangedEvent = EventManager.RegisterRoutedEvent(
    "SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(MyElement));
    
  public event SelectionChangedEventHandler MySelectionChanged
  {
    add { AddHandler(MySelectionChangedEvent, value); }
    remove { RemoveHandler(MySelectionChangedEvent, value); }
  }
}

There are a couple of naming conventions when defining new events:

  • The event name is <Name>
  • The RoutedEvent identifier is named <Name>Event
  • The wrapping standard event is called <Name>

Defining custom routed event args

If an event needs custom data, you can create a custom event args type. This event must be based on RoutedEventArgs or one if the specialized classes. Also a event handler delegate should be defined.

Even that using RoutedEventArgs is the only requirement typical custom event args should look like this:

public class MyEventArgs : RoutedEventArgs
{
  public MyEventArgs(int myData)
  {
    Mydata = myData;
  }
  
  public int MyData { get; private set;
  
  protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
  {
    var handler = genericHandler as MyEventHandler;
    if (handler != null)
    {
      handler(genericTarget, this);
    }
    else
    {
      base.InvokeEventHandler(genericHandler, genericTarget);
    }
  }
}

public delegate void MyEventHandler(object sender, MyEventArgs e);

Overriding the InvokeEventHandlerMethod() is not needed, but it saves a DynamicInvoke on the delegate.

Raising routed events

Raising an routed event is quite simple.

  1. Create the event args.
  2. Set the routed event identifier.
  3. Call the RaiseEvent method on the original source element.
var args = new MyEventArgs(1);
args.RoutedEvent = MyElement.MyEvent;
element.RaiseEvent(args);

   

 

This page has no comments.