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