Terminal.Gui exposes and uses events in many places. This deep dive covers the patterns used, where they are used, and notes any exceptions.
Tenets higher in the list have precedence over tenets lower in the list.
EventHandler
. For data binding we think INotifyPropertyChanged
is groovy. For some callbacks we use Action<T>
.Orientation
of a Slider
is cancelable.TG follows the naming advice provided in .NET Naming Guidelines - Names of Events.
EventHandler
style event best-practicesRaisexxxEvent
.
bool
or bool?
.private
, internal
, or public
depending on the situation. internal
should only be used to enable unit tests.protected virtual
method, THEN invoking the `EventHandler.Action<T>
style callback best-practicesINotifyPropertyChanged
style notification best practicesThe 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.
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.
protected virtual
method is called. This method is named OnxxxChanging
and the base implementation simply does return false
.OnxxxChanging
method returns true
it means a derived class canceled the event. Processing should stop.xxxChanging
event is invoked via xxxChanging?.Invoke(args)
. If args.Cancel/Handled == true
it means a subscriber has cancelled the event. Processing should stop.protected virtual
method is called. This method is named OnxxxChanged
has a return type of void
.xxxChanged
event is invoked via xxxChanging?.Invoke(args)
.The OrientationHelper
class supporting IOrientation
and a View
having an Orientation
property illustrates the preferred TG pattern for cancelable events.
/// <summary>
/// Gets or sets the orientation of the View.
/// </summary>
public Orientation Orientation
{
get => _orientation;
set
{
if (_orientation == value)
{
return;
}
// Best practice is to call the virtual method first.
// This allows derived classes to handle the event and potentially cancel it.
if (_owner?.OnOrientationChanging (value, _orientation) ?? false)
{
return;
}
// If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
CancelEventArgs<Orientation> args = new (in _orientation, ref value);
OrientationChanging?.Invoke (_owner, args);
if (args.Cancel)
{
return;
}
// If the event is not canceled, update the value.
Orientation old = _orientation;
if (_orientation != value)
{
_orientation = value;
if (_owner is { })
{
_owner.Orientation = value;
}
}
// Best practice is to call the virtual method first, then raise the event.
_owner?.OnOrientationChanged (_orientation);
OrientationChanged?.Invoke (_owner, new (in _orientation));
}
}
## bool
or bool?