Routed Events
Most events in Avalonia are implemented as Routed Events. Routed events are events that are raised on the whole tree rather than just the control that raised the event.
What Is a Routed Event
A typical Avalonia application contains many elements. Whether created in code or declared in XAML, these elements exist in an element tree relationship to each other. The event route can travel in one of two directions depending on the event definition, but generally the route travels from the source element and then "bubbles" upward through the element tree until it reaches the element tree root (typically a page or a window). This bubbling concept might be familiar to you if you have worked with the HTML DOM previously.
Top-level Scenarios for Routed Events
The following is a brief summary of the scenarios that motivated the routed event concept, and why a typical CLR event was not adequate for these scenarios:
Control composition and encapsulation: Various controls in Avalonia have a rich content model. For example, you can place an image inside of a Button
, which effectively extends the visual tree of the button. However, the added image must not break the hit-testing behavior that causes a button to respond to a Click
of its content, even if the user clicks on pixels that are technically part of the image.
Singular handler attachment points: In Windows Forms, you would have to attach the same handler multiple times to process events that could be raised from multiple elements. Routed events enable you to attach that handler only once, as was shown in the previous example, and use handler logic to determine where the event came from if necessary. For instance, this might be the handler for the previously shown XAML:
private void CommonClickHandler(object sender, RoutedEventArgs e)
{
var source = e.Source as Control;
switch (source.Name)
{
case "YesButton":
// do something here ...
break;
case "NoButton":
// do something ...
break;
case "CancelButton":
// do something ...
break;
}
e.Handled=true;
}
Class handling: Routed events permit a static handler that is defined by the class. This class handler has the opportunity to handle an event before any attached instance handlers can.
Referencing an event without reflection: Certain code and markup techniques require a way to identify a specific event. A routed event creates a RoutedEvent
field as an identifier, which provides a robust event identification technique that does not require static or run-time reflection.
How Routed Events Are Implemented
A routed event is a CLR event that is backed by an instance of the RoutedEvent
class and registered with the Avalonia event system. RoutedEvent
instance obtained from registration is typically retained as a public
static
readonly
field member of the class that registers and thus "owns" the routed event. The connection to the identically named CLR event (which is sometimes termed the "wrapper" event) is accomplished by overriding the add
and remove
implementations for the CLR event. Ordinarily, the add
and remove
are left as an implicit default that uses the appropriate language-specific event syntax for adding and removing handlers of that event. The routed event backing and connection mechanism is conceptually similar to how an avalonia property is a CLR property that is backed by the AvaloniaProperty
class and registered with the Avalonia property system.
The following example shows the declaration for a custom Tap
routed event, including the registration and exposure of the RoutedEvent
identifier field and the add
and remove
implementations for the Tap
CLR event.
public class SampleControl: Control
{
public static readonly RoutedEvent<RoutedEventArgs> TapEvent =
RoutedEvent.Register<SampleControl, RoutedEventArgs>(nameof(Tap), RoutingStrategies.Bubble);
// Provide CLR accessors for the event
public event EventHandler<RoutedEventArgs> Tap
{
add => AddHandler(TapEvent, value);
remove => RemoveHandler(TapEvent, value);
}
}
Routed Event Handlers and XAML
To add a handler for an event using XAML, you declare the event name as an attribute on the element that is an event listener. The value of the attribute is the name of your implemented handler method, which must exist in the class of the code-behind file.
<Button Click="b1SetColor">button</Button>
The XAML syntax for adding standard CLR event handlers is the same for adding routed event handlers, because you are really adding handlers to the CLR event wrapper, which has a routed event implementation underneath.
Routing Strategies
Routed events use one of three routing strategies:
- Bubbling: Event handlers on the event source are invoked. The routed event then routes to successive parent elements until reaching the element tree root. Most routed events use the bubbling routing strategy. Bubbling routed events are generally used to report input or state changes from distinct controls or other UI elements.
- Direct: Only the source element itself is given the opportunity to invoke handlers in response. This is analogous to the "routing" that Windows Forms uses for events. However, unlike a standard CLR event, direct routed events support class handling (class handling is explained in an upcoming section).
- Tunneling: Initially, event handlers at the element tree root are invoked. The routed event then travels a route through successive child elements along the route, towards the node element that is the routed event source (the element that raised the routed event). Tunneling routed events are often used or handled as part of the compositing for a control, such that events from composite parts can be deliberately suppressed or replaced by events that are specific to the complete control. Input events provided in Avalonia often raise both tunneling and bubbling events.
Why Use Routed Events?
As an application developer, you do not always need to know or care that the event you are handling is implemented as a routed event. Routed events have special behavior, but that behavior is largely invisible if you are handling an event on the element where it is raised.
Where routed events become powerful is if you use any of the suggested scenarios: defining common handlers at a common root, compositing your own control, or defining your own custom control class.
Routed event listeners and routed event sources do not need to share a common event in their hierarchy. Any control can be an event listener for any routed event. Therefore, you can use the full set of routed events available throughout the working API set as a conceptual "interface" whereby disparate elements in the application can exchange event information. This "interface" concept for routed events is particularly applicable for input events.
Routed events can also be used to communicate through the element tree, because the event data for the event is perpetuated to each element in the route. One element could change something in the event data, and that change would be available to the next element in the route.
Other than the routing aspect, there are two other reasons that any given Avalonia event might be implemented as a routed event instead of a standard CLR event. If you are implementing your own events, you might also consider these principles:
- Certain styling and templating features require the referenced event to be a routed event. This is the event identifier scenario mentioned earlier.
- Routed events support a class handling mechanism whereby the class can specify static methods that have the opportunity to handle routed events before any registered instance handlers can access them. This is very useful in control design, because your class can enforce event-driven class behaviors that cannot be accidentally suppressed by handling an event on an instance.
Each of the above considerations is discussed in a separate section of this topic.
Adding and Implementing an Event Handler for a Routed Event
To add an event handler in XAML, you simply add the event name to an element as an attribute and set the attribute value as the name of the event handler that implements an appropriate delegate, as in the following example.
<Button Click="b1SetColor">button</Button>
b1SetColor
is the name of the implemented handler that contains the code that handles the Click
event. b1SetColor
must have the same signature as the RoutedEventHandler<RoutedEventArgs>
delegate, which is the event handler delegate for the Click
event. The first parameter of all routed event handler delegates specifies the element to which the event handler is added, and the second parameter specifies the data for the event.
void b1SetColor(object sender, RoutedEventArgs args)
{
//logic to handle the Click event
}