Routed Event
Purpose
The reason for creating a dedicated RoutedEvent in Avalonia is that, standard CLR event can't propagate event upward(bubble) or downward(tunnel)
RoutedEvent Definition
Similar to AvaloniaProperty (or specifically StyledProperty), Avalonia has registry for all RoutedEvent, and all RoutedEvent should be registered as static readonly field for specific owner type. Avalonia has dedicated Interactive.AddHandler and Interactive.RemoveHandler wrappers for adding and removing handlers, which are operations applied on Interactive._eventHandlers
public partial class MainWindow : Window {
public static readonly RoutedEvent<RoutedEventArgs> FooEvent =
RoutedEvent.Register<MainWindow, RoutedEventArgs>(nameof(ValueChanged), RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> ValueChanged {
add => AddHandler(FooEvent, value);
remove => RemoveHandler(FooEvent, value);
}
protected virtual void OnFoo() {
// RoutedEventArgs requires event definition
RoutedEventArgs args = new RoutedEventArgs(FooEvent);
RaiseEvent(args);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
Routing Modes
Bubble: propagate upwardTunnel: propagate downwardDirect: do not propagate(for owner control only)
RoutedEvent Identity & Storage
Each Interactive has dedicated dictionary for storing events by RoutedEvent definition, this is much simpler than how AvaloniaObject manage its StyledProperty since events doesn't have a complex system to manage the priority or something else.
public class Interactive : Layoutable {
/** ... **/
private Dictionary<RoutedEvent, List<EventSubscription>>? _eventHandlers;
/** ... **/
}2
3
4
5
Static RoutedEvent definitions are stored in RoutedEventRegistry.Instance
// query all RoutedEvent from owner type
_ = RoutedEventRegistry.Instance.GetRegistered<MainWindow>();2
RoutedEventArgs
Handled: indicating whether the event has been handled by any control along the visual tree.RoutedEvent: used as an index to trigger the event in controls along the visual tree.
public class RoutedEventArgs : EventArgs {
/// <summary>
/// Gets or sets a value indicating whether the routed event has already been handled.
/// </summary>
/// <remarks>
/// Once handled, a routed event should be ignored.
/// </remarks>
public bool Handled { get; set; }
/// <summary>
/// Gets or sets the routed event associated with these event args.
/// </summary>
public RoutedEvent? RoutedEvent { get; set; }
/// <summary>
/// Gets or sets the routing strategy (direct, bubbling, or tunneling) of the routed event.
/// </summary>
public RoutingStrategies Route { get; set; }
/// <summary>
/// Gets or sets the source object that raised the routed event.
/// </summary>
public object? Source { get; set; }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class Handler
Class handlers are event handlers for RoutedEvent with highest priority, which means class handlers will always be invoked before any other handlers. It is class-specific, usually registered in a static constructor using RoutedEvent.AddClassHandler<TTarget>. InputElement class has registered most of default class handlers such as InputElement.OnPointerPressed(PointerPressedEventArgs e), developers can override such empty methods to manage the default behaviour of an RoutedEvent.
public class InputElement : Interactive, IInputElement {
/** ... **/
static InputElement() {
/** ... **/
KeyDownEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyDown(e));
KeyUpEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyUp(e));
TextInputEvent.AddClassHandler<InputElement>((x, e) => x.OnTextInput(e));
PointerEnteredEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerEnteredCore(e));
PointerExitedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerExitedCore(e));
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e));
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
/** ... **/
}
public event EventHandler<PointerPressedEventArgs>? PointerPressed {
add { AddHandler(PointerPressedEvent, value); }
remove { RemoveHandler(PointerPressedEvent, value); }
}
public static readonly RoutedEvent<PointerPressedEventArgs> PointerPressedEvent =
RoutedEvent.Register<InputElement, PointerPressedEventArgs>(
nameof(PointerPressed),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
// default empty class handler to be overridden
protected virtual void OnPointerPressed(PointerPressedEventArgs e) { }
/** ... **/
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Attached Event
Similar to AttachedProperty, attached event are external definition for a TOwner(typically a static class) However, there's no dedicated type for attached event, attached events are still presented as RoutedEvent.
public static class Gestures {
/** ... **/
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>(
"Tapped",
RoutingStrategies.Bubble,
typeof(Gestures));
/** ... **/
}2
3
4
5
6
7
8