namespace Terminal.Gui.App; #nullable enable /// /// Provides helper methods for executing property change workflows in the Cancellable Work Pattern (CWP). /// /// /// /// Used for workflows where a property value is modified, such as in or /// , allowing pre- and post-change events to customize or cancel the change. /// /// /// /// public static class CWPPropertyHelper { /// /// Executes a CWP workflow for a property change, with pre- and post-change events. /// /// /// The type of the property value, which may be a nullable reference type (e.g., /// ?). /// /// /// Reference to the current property value, which may be null for nullable types. If the change is not cancelled, this /// will be set to . /// /// The proposed new property value, which may be null for nullable types. /// The virtual method invoked before the change, returning true to cancel. /// The pre-change event raised to allow modification or cancellation. /// The action that performs the actual work of setting the property (e.g., updating backing field, calling related methods). /// The virtual method invoked after the change. /// The post-change event raised to notify of the completed change. /// /// The final value after the workflow, reflecting any modifications, which may be null for /// nullable types. /// /// True if the property was changed, false if cancelled. /// /// Thrown if is null for non-nullable reference types after the /// workflow. /// /// /// /// string? current = _schemeName; /// string? proposed = "Base"; /// Func<ValueChangingEventArgs<string?>, bool> onChanging = OnSchemeNameChanging; /// EventHandler<ValueChangingEventArgs<string?>>? changingEvent = SchemeNameChanging; /// Action<string?> doWork = value => _schemeName = value; /// Action<ValueChangedEventArgs<string?>>? onChanged = OnSchemeNameChanged; /// EventHandler<ValueChangedEventArgs<string?>>? changedEvent = SchemeNameChanged; /// bool changed = CWPPropertyHelper.ChangeProperty( /// current, proposed, onChanging, changingEvent, doWork, onChanged, changedEvent, out string? final); /// /// public static bool ChangeProperty ( ref T currentValue, T newValue, Func, bool>? onChanging, EventHandler>? changingEvent, Action doWork, Action>? onChanged, EventHandler>? changedEvent, out T finalValue ) { if (EqualityComparer.Default.Equals (currentValue, newValue)) { finalValue = currentValue; return false; } ValueChangingEventArgs args = new (currentValue, newValue); if (onChanging is { }) { bool cancelled = onChanging (args) || args.Handled; if (cancelled) { finalValue = currentValue; return false; } } changingEvent?.Invoke (null, args); if (args.Handled) { finalValue = currentValue; return false; } // Validate NewValue for non-nullable reference types if (args.NewValue is null && !typeof (T).IsValueType && !Nullable.GetUnderlyingType (typeof (T))?.IsValueType == true) { throw new InvalidOperationException ("NewValue cannot be null for non-nullable reference types."); } finalValue = args.NewValue; // Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events doWork (finalValue); ValueChangedEventArgs changedArgs = new (currentValue, finalValue); currentValue = finalValue; onChanged?.Invoke (changedArgs); changedEvent?.Invoke (null, changedArgs); return true; } }