diff --git a/.claude/rules/event-patterns.md b/.claude/rules/event-patterns.md index 6d66dc17dd..bff0fb6d42 100644 --- a/.claude/rules/event-patterns.md +++ b/.claude/rules/event-patterns.md @@ -1,5 +1,26 @@ # Event Patterns +## When to Use `-ing` vs `-ed` Events + +Terminal.Gui exposes paired events — `Accepting`/`Accepted`, `Activating`/`Activated`, `ValueChanging`/`ValueChanged`, etc. + +**Rule:** Use `-ed` (past-tense) for side-effects. Use `-ing` (present-progressive) only when you need to inspect or cancel the in-flight operation. + +```csharp +// ✅ Correct — fire-and-forget side-effect +button.Accepted += (_, _) => DoTheThing (); + +// ✅ Correct — actually cancels +button.Accepting += (_, e) => { if (!CanProceed ()) e.Handled = true; }; + +// ❌ Wrong — handler ignores EventArgs; use Accepted instead +button.Accepting += (_, _) => DoTheThing (); +``` + +If the handler body doesn't reference `e` at all (or ignores `e.Handled`, `e.Cancel`, and the candidate value), it belongs on the `-ed` event. + +The `-ing` event runs synchronously in the middle of the dispatch path; subscribing when you don't need to cancel adds unnecessary overhead and misleads readers. + ## Lambda Parameters **Replace unused parameters with discards `_`:** diff --git a/Terminal.Gui/ViewBase/View.Command.cs b/Terminal.Gui/ViewBase/View.Command.cs index d1cde141e8..ba8fd0e307 100644 --- a/Terminal.Gui/ViewBase/View.Command.cs +++ b/Terminal.Gui/ViewBase/View.Command.cs @@ -499,6 +499,16 @@ private void SetupCommands () /// /// See for more information. /// + /// + /// When to use: Subscribe to only when you need to inspect or cancel the + /// in-flight Accept operation (e.g., set e.Handled = true to prevent the accept). For simple side-effects + /// that don't need to cancel, subscribe to instead — it is lighter-weight and communicates + /// intent more clearly. + /// + /// + /// Rule of thumb: If your handler doesn't read or set anything on + /// (no e.Handled, no inspection of context), use . + /// /// public event EventHandler? Accepting; @@ -543,6 +553,19 @@ protected virtual void OnAccepted (ICommandContext? ctx) { } /// /// See for more information. /// + /// + /// When to use: Subscribe to for fire-and-forget side-effects — things that + /// happen after the accept has completed and cannot be cancelled. This is the right choice for the vast + /// majority of button-click–style handlers. + /// + /// + /// Example: + /// + /// button.Accepted += (_, _) => DoTheThing (); // correct — side-effect only + /// button.Accepting += (_, e) => { if (!CanProceed ()) e.Handled = true; }; // correct — cancels + /// button.Accepting += (_, _) => DoTheThing (); // wrong — use Accepted instead + /// + /// /// public event EventHandler? Accepted; @@ -843,6 +866,18 @@ private void BubbleActivatedUp (ICommandContext? ctx, bool compositeOnly = false /// Set CommandEventArgs.Handled to to indicate the event was handled and processing should /// stop. /// + /// + /// + /// When to use: Subscribe to only when you need to inspect or cancel the + /// in-flight Activate operation (e.g., set e.Handled = true to prevent the state change). For simple + /// side-effects that don't need to cancel, subscribe to instead — it is lighter-weight and + /// communicates intent more clearly. + /// + /// + /// Rule of thumb: If your handler doesn't read or set anything on + /// (no e.Handled, no inspection of context), use . + /// + /// public event EventHandler? Activating; /// @@ -913,6 +948,16 @@ protected virtual void OnActivated (ICommandContext? ctx) { } /// Event raised when the user has performed an action (e.g. ) causing the /// View to change state or preparing it for interaction. /// + /// + /// + /// Unlike , this event cannot be cancelled. It is raised after the View has activated. + /// + /// + /// When to use: Subscribe to for fire-and-forget side-effects — things + /// that happen after the activation has completed and cannot be cancelled. This is the right choice for + /// the vast majority of state-change–reaction handlers. + /// + /// public event EventHandler>? Activated; #endregion Activate diff --git a/docfx/docs/events.md b/docfx/docs/events.md index 9e5c7bf023..4f54b42fc1 100644 --- a/docfx/docs/events.md +++ b/docfx/docs/events.md @@ -42,6 +42,37 @@ Use this decision tree to choose the right pattern: | Simple notification (no cancel) | `EventHandler` | [Recipe 3](#recipe-3-simple-notification) | | Property notification (MVVM) | `INotifyPropertyChanged` | [Recipe 4](#recipe-4-mvvm-property-notification) | +## When to Use `-ing` vs `-ed` Events + +Terminal.Gui exposes paired events on many surfaces — `Accepting`/`Accepted`, `Activating`/`Activated`, `ValueChanging`/`ValueChanged`, etc. Use this rule to choose: + +> **Use `-ed` (past-tense) events for side-effects. Use `-ing` (present-progressive) events only when you actually need to inspect or cancel the in-flight operation.** + +If your handler doesn't read or set anything on the `EventArgs` (no `e.Handled`, no `e.Cancel`, no inspection of the candidate value), you want the `-ed` event. The `-ing` event runs synchronously in the middle of the dispatch path and is heavier for both the framework and the reader of your code. + +### Concrete Examples + +```csharp +// ✅ Correct — fire-and-forget side-effect belongs on the -ed event +button.Accepted += (_, _) => DoTheThing (); + +// ✅ Correct — actually needs to cancel, so -ing is right +button.Accepting += (_, e) => { if (!CanProceed ()) e.Handled = true; }; + +// ❌ Wrong — handler ignores EventArgs; should use Accepted +button.Accepting += (_, _) => DoTheThing (); +``` + +The same rule applies to every other paired event in the framework: + +| Use `-ed` (side-effect) | Use `-ing` (inspect / cancel) | +|-------------------------|-------------------------------| +| `Accepted` | `Accepting` | +| `Activated` | `Activating` | +| `ValueChanged` | `ValueChanging` | +| `TextChanged` | `TextChanging` | +| `TitleChanged` | `TitleChanging` | + ## See Also * [Cancellable Work Pattern](cancellable-work-pattern.md) - Conceptual overview