Framework / DOM / DOM Basics / Events
In This Topic
    Events
    In This Topic
     Events Overview
    In the DOM events are represented by instances of the NEvent class. It represents a contract between the node that raises the event, and the event subscribers. This means that you need to specify an instance of the NEvent class when you both raise an event and subscribe for an event. Each event is associated with a specific type of event arguments. All types of event arguments must derive from the NEventArgs class. The actual raising of an event is achieved by first creating a new instance of the specific event arguments, and then calling the event arguments Raise() method. Parties interested in a specific event of a specific node, need to create an event handler and register it in the node, which they want to observe for that specific event. If the event occurs, the event handler Invoke method is called.
     Creating and Raising Events

    New events are created by the NEvent.Create static methods that are described in the following table:

    Method Description

    NEvent Create(string name, NSchema ownerSchema, Type eventArgsType)

    Creates a new direct event with the specified name, owner schema and type of event arguments.

    NEvent Create(string name, NSchema ownerSchema, Type eventArgsType, bool canBubble, bool canSink)

    Creates a new event with the specified name, owner schema and type of event arguments, that can optionally bubble and/or sink (see Events Routing below)

    NEvent Create(string name, NSchema ownerSchema, Type eventArgsType, NEvent category)

    Creates a new event with the specified name and owner schema, that belongs to the specified event category (see Event Categories below)

    The following code sample creates a simple node that declares a direct event, to which users can subscribe and also defines a simple method that raises the event.

    My First Event
    Copy Code
    public class MyNode : NNode
    {
     public MyNode()
     {
     }
     static MyNode()
     {
      MyNodeSchema = NSchema.Create(typeof(MyNode), NNode.NNodeSchema);
      DoSomethingEvent = NEvent.Create("DoSomething", MyNodeSchema, typeof(NEventArgs));
     }
     // occurs when you call the RaiseDoSomething() method.
     public event Function<NEventArgs> DoSomething
     {
      add
      {
       AddEventHandler(DoSomethingEvent, new NEventHandler<NEventArgs>(value));
      }
      remove
      {
       RemoveEventHandler(DoSomethingEvent, new NEventHandler<NEventArgs>(value));
      }
     }
     public void RaiseDoSomething()
     {
      NEventArgs args = new NEventArgs(DoSomethingEvent, this);
      args.Raise();
     }
     
     public static readonly NSchema MyNodeSchema;
     public static readonly NEvent DoSomethingEvent;
    }
    ...
    void TestEventsMethod()
    {
     MyNode myNode = new MyNode();
     // subscribe for DoSomething
     myNode.DoSomething += new Function<NEventArgs>(OnMyNodeDoSomething);
     // raise do something -> will call the OnMyNodeDoSomething method.
     myNode.RaiseDoSomething();
     // unsubscribe from DoSomething
     myNode.DoSomething -= new Function<NEventArgs>(OnMyNodeDoSomething);
     // raise do something -> will do nothing, since there are no DoSomething Event Subscribers
     myNode.RaiseDoSomething();
    }
    void OnMyNodeDoSomething(NEventArgs args)
    {
     Console.WriteLine("DoSomething");
    }
    ...
    
     What are Event Handlers

    In the DOM event handlers are represented by the INEventHandler interface that has only one method - void Invoke(NEventArgs args). This means that in NOV event handlers are actually objects that implement the INEventHandler interface rather than delegates.

    Using the delegate syntax for event handlers is however possible with the help of the NEventHandler<TArgs> generic class (implements INEventHandler). Its constructor accepts a Function<TArgs> delegate, that it subsequently calls when its Invoke method is called. If you take a closer look at the My First Event sample, you can see that we have created instances of the NEventHandler<TArgs> generic class to both add and remove an event handler that is a delegate to the OnMyNodeDoSomething method.

     Instance Event Handlers

    The event handlers attached to a specific node instance are called instance handlers, because they will get invoked only for the specific node instance to which they are added. In the My First Event example we have created an instance handler for the DoSomething Event. The instance handlers for each node instance are managed by the following set of NNode methods:

    Method Description

    void AddEventHandler(NEvent domEvent, Delegate eventHandler)

    Adds an event handler, which is invoked during the AtTarget/Bubbling phase of the specified event. The delegate must have a Function<TArgs> signature, where TArgs needs to be assignable from domEvent.ArgsType. Internally creates a NEventHandler<TArgs> instance where TArgs is domEvent.ArgsType.

    void AddEventHandler(NEvent domEvent, Delegate eventHandler, bool sinking)

    Adds an event handler, which is invoked during the Sinking phase (if sinking is true) or AtTarget/Bubbling phase (if sinking is false) of the specified event. The delegate must have a Function<TArgs> signature, where TArgs needs to be assignable from domEvent.ArgsType. Internally creates a NEventHandler<TArgs> instance where TArgs is domEvent.ArgsType. 

    void AddEventHandler(NEvent domEvent, NEventHandler eventHandler)

    Adds an event handler, which is invoked during the AtTarget/Bubbling phase of the specified event.

    void AddEventHandler(NEvent domEvent, NEventHandler eventHandler, bool sinking)

    Adds an event handler, which is invoked during the Sinking phase (if sinking is true) or AtTarget/Bubbling phase (if sinking is false) of the specified event.

    void RemoveEventHandler(NEvent domEvent, Delegate eventHandler)

    Removes the specified event handler from the list of AtTarget/Bubbling phase handlers of the specified event. The delegate must have a Function<TArgs> signature, where TArgs needs to be assignable from domEvent.ArgsType. Internally creates a NEventHandler<TArgs> instance where TArgs is domEvent.ArgsType.

    void RemoveEventHandler(NEvent domEvent, Delegate eventHandler, bool sinking)

    Removes the specified event handler from the list of Sinking phase handler (if sinking is true) or AtTarget/Bubbling phase handlers (if sinking is false) of the specified event. The delegate must have a Function<TArgs> signature, where TArgs needs to be assignable from domEvent.ArgsType. Internally creates a NEventHandler<TArgs> instance where TArgs is domEvent.ArgsType.

    void RemoveEventHandler(NEvent domEvent, NEventHandler eventHandler)

    Removes the specified event handler from the list of AtTarget/Bubbling phase handlers for the specified event.

    void RemoveEventHandler(NEvent domEvent, NEventHandler eventHandler, bool sinking)

    Removes the specified event handler from the list of Sinking phase handlers (if sinking is true) or AtTarget/Bubbling phase handlers (if sinking is false) of the specified event.

    void RemoveAllEventHandlers(bool sinking)

    Removes all handlers for the Sinking phase (if sinking is true) or AtTarget/Bubbling phase (if sinking is false).

    void RemoveAllEventHandlers()

    Removes all handlers for the Sinking and AtTarget/Bubbling phases (clears all instance handlers of the node).

    In most cases you will add/remove handlers for the AtTarget/Bubbling phase of events. See Events Routing for more information about event phases.

     Static Event Handlers

    There are cases when a certain type of node always needs to handle a specific event. Consider a scrollbar element that always needs to handle the clicks of its arrow buttons to increment/decrement its value. One way to achieve this is to always add instance handlers for the arrow buttons click events whenever a new scrollbar is created. This approach is however very inefficient. A more elegant solution to this problem is to use static event handlers.

    Static event handlers are created during the node schema initialization and are always invoked for all node instances of the schema. Static event handlers have the meaning of saying: "always do that, whenever this event occurs for this type of node".

    The following code sample demonstrates static events.

    My First Static Event Handler
    Copy Code
    public class MyNode : NNode
    {
     public MyNode()
     {
     }
     static MyNode()
     {
      MyNodeSchema = NSchema.Create(typeof(MyNode), NNode.NNodeSchema);
      // create some event
      DoSomethingEvent = NEvent.Create("DoSomething", MyNodeSchema, typeof(NEventArgs));
      // create a static event handler
      MyNodeSchema.AddEventHandler(DoSomethingEvent, new NEventHandler<NEventArgs>(new Function<NEventArgs>(OnMyNodesDoSomethingHandler)));
     }
     public static void OnMyNodesDoSomethingHandler(NEventArgs args)
     {
      Console.WriteLine("Always Called when DoSomethingEvent is raised for instances of MyNode");
     }
     
     public void RaiseDoSomething()
     {
      NEventArgs args = new NEventArgs(DoSomethingEvent, this);
      args.Raise();
     }
     
     public static readonly NSchema MyNodeSchema;
     public static readonly NEvent DoSomethingEvent;
    }
    ...
    void TestStaticEventHandlersMethod()
    {
     MyNode myNode = new MyNode();
     // raise do something -> will call the OnMyNodesDoSomethingHandler method. 
     myNode.RaiseDoSomething();
    }
    

    The following table lists the NSchema methods that help you manage static event handlers

    Method Description

    void AddEventHandler(NEvent domEvent, NEventHandler eventHandler)

    Adds an event handler, which is invoked during the AtTarget/Bubbling phase for all nodes that are instances of the schema.

    void AddEventHandler(NEvent domEvent, NEventHandler eventHandler, bool sinking)

    Adds an event handler, which is invoked during the Sinking phase (if sinking is true) or the AtTarget/Bubbling phase (if sinking is false) for all nodes that are instances of the schema.

     

     Events Routing

    Events routing refers to the built-in ability of DOM events to invoke event handlers attached to nodes that are ancestors of the target node. Each time you raise an event you create an instance of the NEventArgs class (or NEventArgs derived class), by passing a reference to the node for which the event will be raised. This node is the event target node. Whether the event args will be dispatched to any ancestors of the target node and in what order, depends on whether the event was created with the optional bubbling and/or sinking routing ability.

    To better illustrate bubbling and sinking consider a hierarchy of nodes, drawn as an inverted tree below the sea surface:

    figure 1. Event Routing, represented as an inverted tree sunken beneath the sea surface.

    Now lets suppose that node H raises the X event, which was created with the ability to sink and bubble. Lets also suppose that we have added event handlers for both the AtTarget/Bubbling and Sinking phases to nodes A, C, F and H (i.e. to the target node and its ancestors). The event handlers will be called in this order:

    1. A Sinking Handler
    2. C Sinking Handler
    3. F Sinking Handler
    4. H AtTarget/Bubbling Handler
    5. F AtTarget/Bubbling Handler
    6. C AtTarget/Bubbling Handler
    7. A AtTarget/Bubbling Handler

    During the sinking phase, the event travels from the root to the target, and in the context of figure 1., we can say that it is sinking (descends from the sea surface). During the bubbling phase, the event travels from the root to the target, and in the context of figure 1., we can say that it is bubbling (ascends towards the sea surface).

    The bubbling phase of routed events is called in the same way in HTML, WPF/Silverlight and other APIs we examined. The sinking phase however is by our opinion not well named in both HTML and WPF/Silverlight. In HTML it is called canceling phase, while in WPF/Silverlight it is called tunneling/preview phase - both do not clearly match well as a counterpart for bubbling. So although no other API uses the term sinking as the opposite phase of bubbling, we decided that it is time to finally give this phase a proper name.

    The EventPhase property of the NEventArgs passed to all event handler indicates the phase at which the event currently is. The CurrentTargetNode property of the NEventArgs returns the node at which the event currently is. Through the entire event lifecycle the TargetNode property returns that target node, for which the event was raised.

    Lets inspect the values of these event args properties inside some of the event handlers of our fictional tree:

    When the A Sinking Handler is called:

    EventPhase = ENEventPhase.Sinking
    CurrentTargetNode = node A
    TargetNode = node H

    When the H AtTarget/Bubbling Handler is called:

    EventPhase = ENEventPhase.AtTarget
    CurrentTargetNode = node H
    TargetNode = node H

    When the C AtTarget/Bubbling Handler is called:

    EventPhase = ENEventPhase.Bubbling
    CurrentTargetNode = node C
    TargetNode = node H

    Both the bubbling and the sinking phases are optional for each event that you declare. If an event declared with the ability to bubble or sink it is said to be a routed event, because there is a certain path of nodes (also called event route) whose event handlers for the event will be called (depending on the event phase). If an event is declared without the ability to bubble or sink it is said to be a direct event, because only the target node AtTarget/Bubbling event handlers will be called.

    The event route for routed events usually goes to the root node, however NOV is different than any other environment in the fact that it actually gives events the freedom to choose an event route - it is just that the default route is from the node to its root. Some routed events however use a shorter route - for example the user input In and Out events - see User Input for more info.

     Event Categories

    Events can also be hierarchically organized. This means that each event, when created can have an associated parent event, which is automatically triggered when any of its descendant events is raised. The parent event is called category event. The purpose of event categorization is to allow the developer to subscribe for a category (group) of events with a single event handler.

    For example - suppose that you want to handle all changes made with a certain node (all property and children changes). Since the property change events created by each property, belong to the NEvent.ChangedCategoryEvent and since the NNode.ChildInsertedEvent and NNode.ChildRemovedEvent belong to the NEvent.ChildrenChangedCategoryEvent, which on its turn belongs to the NEvent.ChangedCategoryEvent, you can achieve this by simply subscribing for the NEvent.ChangedCategoryEvent. This is by default done by the Changed event of each node instance, like shown in the following sample:

    Subscribing for Category Events
    Copy Code
    ...
    MyNode node = new MyNode();
    node.Changed += new Function<NEventArgs>(node_Changed);
    ...
    
    void node_Changed(NEventArgs args)
    {
     if (args is NValueChangeEventArgs)
     {
      NValueChangeEventArgs valueArg = (NValueChangeEventArgs)args;
      Console.WriteLine("Property Changed: " + valueArg.Property.Name);
     }
     else if (args is NInsertChildEventArgs)
     {
      Console.WriteLine("Child inserted");
     }
     else if (args is NRemoveChildEventArgs)
     {
      Console.WriteLine("Child Removed");
     }
    }
    

    Events that belong to a certain category event obey to the following rules:

    1. The event uses the routing strategy of its category event.
    2. The event needs to have type arguments that are equal or derived from the category event type arguments.

    The following table summarizes the predefined event categories used in the DOM:

    Category Event Parent Category Event Routing Strategy Description
    NEvent.ChangingCategoryEvent direct Occurs when a node property is about to change, or when the children of the node are about to change (i.e. a child is about to be inserted or removed). For elements also occurs when the expression associated with a property is about to change.
    NEvent.ChildrenChangingCategoryEvent NEvent.ChangingCategoryEvent direct Occurs when the children of the node are about to change (i.e. a child is about to be inserted or removed).
    NEvent.ChangedCategoryEvent direct Occurs when a node property has changed, or when the children of the node have changed (i.e. a child has been inserted or removed). For elements also occurs when the expression associated with a property has changed.
    NEvent.ChildrenChangedCategoryEvent NEvent.ChangedCategoryEvent direct Occurs when the children of the node have changed (i.e. a child has been inserted or removed).
    NEvent.ContextChangedCategoryEvent direct Occurs when the node parent has changed (i.e. the node has been reparented). In the case of document nodes also occurs when the node is registered/unregistered from a document.
    NEvent.UICategoryEvent sinking and bubbling Serves as category event for all UI events that need to sink and bubble.