|
2 | 2 |
|
3 | 3 | Terminal.Gui exposes and uses events in many places. This deep dive covers the patterns used, where they are used, and notes any exceptions.
|
4 | 4 |
|
| 5 | +## See Also |
| 6 | + |
| 7 | +* [Cancellable Work Pattern](cancellable_work_pattern.md) |
| 8 | +* [Command Deep Dive](command.md) |
| 9 | + |
5 | 10 | ## Tenets for Terminal.Gui Events (Unless you know better ones...)
|
6 | 11 |
|
7 | 12 | Tenets higher in the list have precedence over tenets lower in the list.
|
@@ -29,98 +34,24 @@ Tenets higher in the list have precedence over tenets lower in the list.
|
29 | 34 |
|
30 | 35 | TG follows the *naming* advice provided in [.NET Naming Guidelines - Names of Events](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members?redirectedfrom=MSDN#names-of-events).
|
31 | 36 |
|
32 |
| -## `EventHandler` style event best-practices |
| 37 | +## Common Event Patterns |
| 38 | + |
| 39 | +### OnEvent/Event |
| 40 | + |
| 41 | +The primary pattern for events is the `OnEvent/Event` idiom. |
33 | 42 |
|
34 | 43 | * Implement a helper method for raising the event: `RaisexxxEvent`.
|
35 | 44 | * If the event is cancelable, the return type should be either `bool` or `bool?`.
|
36 | 45 | * Can be `private`, `internal`, or `public` depending on the situation. `internal` should only be used to enable unit tests.
|
37 |
| -* Raising an event involves FIRST calling the `protected virtual` method, THEN invoking the `EventHandler. |
38 |
| - |
39 |
| -## `Action<T>` style callback best-practices |
40 |
| - |
41 |
| -- tbd |
42 |
| - |
43 |
| -## `INotifyPropertyChanged` style notification best practices |
44 |
| - |
45 |
| -- tbd |
46 |
| - |
47 |
| -## Common Patterns |
48 |
| - |
49 |
| -The primary pattern for events is the `event/EventHandler` idiom. We use the `Action<T>` idiom sparingly. We support `INotifyPropertyChanged` in cases where data binding is relevant. |
50 |
| - |
51 |
| - |
52 |
| - |
53 |
| -## Cancellable Event Pattern |
54 |
| - |
55 |
| -A cancellable event is really two events and some activity that takes place between those events. The "pre-event" happens before the activity. The activity then takes place (or not). If the activity takes place, then the "post-event" is typically raised. So, to be precise, no event is being cancelled even though we say we have a cancellable event. Rather, the activity that takes place between the two events is what is cancelled — and likely prevented from starting at all. |
56 |
| - |
57 |
| -### **Before** - If any pre-conditions are met raise the "pre-event", typically named in the form of "xxxChanging". e.g. |
58 |
| - |
59 |
| - - A `protected virtual` method is called. This method is named `OnxxxChanging` and the base implementation simply does `return false`. |
60 |
| - - If the `OnxxxChanging` method returns `true` it means a derived class canceled the event. Processing should stop. |
61 |
| - - Otherwise, the `xxxChanging` event is invoked via `xxxChanging?.Invoke(args)`. If `args.Cancel/Handled == true` it means a subscriber has cancelled the event. Processing should stop. |
62 |
| - |
63 |
| - |
64 |
| -### **During** - Do work. |
65 |
| - |
66 |
| -### **After** - Raise the "post-event", typically named in the form of "xxxChanged" |
67 |
| - |
68 |
| - - A `protected virtual` method is called. This method is named `OnxxxChanged` has a return type of `void`. |
69 |
| - - The `xxxChanged` event is invoked via `xxxChanging?.Invoke(args)`. |
70 |
| - |
71 |
| -The `OrientationHelper` class supporting `IOrientation` and a `View` having an `Orientation` property illustrates the preferred TG pattern for cancelable events. |
72 |
| - |
73 |
| -```cs |
74 |
| - /// <summary> |
75 |
| - /// Gets or sets the orientation of the View. |
76 |
| - /// </summary> |
77 |
| - public Orientation Orientation |
78 |
| - { |
79 |
| - get => _orientation; |
80 |
| - set |
81 |
| - { |
82 |
| - if (_orientation == value) |
83 |
| - { |
84 |
| - return; |
85 |
| - } |
86 |
| - |
87 |
| - // Best practice is to call the virtual method first. |
88 |
| - // This allows derived classes to handle the event and potentially cancel it. |
89 |
| - if (_owner?.OnOrientationChanging (value, _orientation) ?? false) |
90 |
| - { |
91 |
| - return; |
92 |
| - } |
93 |
| - |
94 |
| - // If the event is not canceled by the virtual method, raise the event to notify any external subscribers. |
95 |
| - CancelEventArgs<Orientation> args = new (in _orientation, ref value); |
96 |
| - OrientationChanging?.Invoke (_owner, args); |
97 |
| - |
98 |
| - if (args.Cancel) |
99 |
| - { |
100 |
| - return; |
101 |
| - } |
102 |
| - |
103 |
| - // If the event is not canceled, update the value. |
104 |
| - Orientation old = _orientation; |
| 46 | +* Raising an event involves FIRST calling the `protected virtual` method, THEN invoking |
| 47 | +the `EventHandler`. |
105 | 48 |
|
106 |
| - if (_orientation != value) |
107 |
| - { |
108 |
| - _orientation = value; |
| 49 | +### Action |
109 | 50 |
|
110 |
| - if (_owner is { }) |
111 |
| - { |
112 |
| - _owner.Orientation = value; |
113 |
| - } |
114 |
| - } |
| 51 | +We use the `Action<T>` idiom sparingly. |
115 | 52 |
|
116 |
| - // Best practice is to call the virtual method first, then raise the event. |
117 |
| - _owner?.OnOrientationChanged (_orientation); |
118 |
| - OrientationChanged?.Invoke (_owner, new (in _orientation)); |
119 |
| - } |
120 |
| - } |
121 |
| -``` |
| 53 | +### INotifyPropertyChanged |
122 | 54 |
|
123 |
| - ## `bool` or `bool?` |
| 55 | +We support `INotifyPropertyChanged` in cases where data binding is relevant. |
124 | 56 |
|
125 |
| - |
126 | 57 |
|
0 commit comments