|
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