Ver código fonte

Fixed.
Added event.md

Tig 9 meses atrás
pai
commit
6df071fbe1

+ 36 - 30
Terminal.Gui/View/View.Keyboard.cs

@@ -1,5 +1,6 @@
 #nullable enable
 #nullable enable
 using System.Diagnostics;
 using System.Diagnostics;
+using Microsoft.CodeAnalysis.FlowAnalysis;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -292,18 +293,21 @@ public partial class View // Keyboard APIs
 
 
         // During (this is what can be cancelled)
         // During (this is what can be cancelled)
 
 
-        if (RaiseInvokingKeyBindingsAndInvokeCommands (key) is not false || key.Handled)
+        // TODO: NewKeyDownEvent returns bool. It should be bool? so state of RaiseInvokingKeyBindingsAndInvokeCommands can be reflected up stack
+
+
+        if (RaiseInvokingKeyBindingsAndInvokeCommands (key) is true || key.Handled)
         {
         {
             return true;
             return true;
         }
         }
 
 
+        // After
+        // TODO: Is ProcessKeyDown really the right name?
         if (RaiseProcessKeyDown (key) || key.Handled)
         if (RaiseProcessKeyDown (key) || key.Handled)
         {
         {
             return true;
             return true;
         }
         }
 
 
-        // After
-
         return key.Handled;
         return key.Handled;
 
 
         bool RaiseKeyDown (Key key)
         bool RaiseKeyDown (Key key)
@@ -322,14 +326,13 @@ public partial class View // Keyboard APIs
 
 
         bool RaiseProcessKeyDown (Key key)
         bool RaiseProcessKeyDown (Key key)
         {
         {
-            // BUGBUG: The proper pattern is for the v-method (OnProcessKeyDown) to be called first, then the event
-            ProcessKeyDown?.Invoke (this, key);
-
-            if (!key.Handled && OnProcessKeyDown (key))
+            if (OnProcessKeyDown (key) || key.Handled)
             {
             {
                 return true;
                 return true;
             }
             }
 
 
+            ProcessKeyDown?.Invoke (this, key);
+
             return false;
             return false;
         }
         }
     }
     }
@@ -512,17 +515,22 @@ public partial class View // Keyboard APIs
     /// </summary>
     /// </summary>
     /// <param name="key"></param>
     /// <param name="key"></param>
     /// <param name="scope"></param>
     /// <param name="scope"></param>
-    /// <returns></returns>
+    /// <returns>
+    ///     <see langword="null"/> if no command was invoked or there was no matching key binding; input processing should continue.
+    ///     <see langword="false"/> if a command was invoked and was not handled (or cancelled); input processing should
+    ///     continue.
+    ///     <see langword="true"/> if <see cref="InvokingKeyBindings"/> was handled or a command was invoked and handled (or cancelled); input processing should stop.
+    /// </returns>
     internal bool? RaiseInvokingKeyBindingsAndInvokeCommands (Key key)
     internal bool? RaiseInvokingKeyBindingsAndInvokeCommands (Key key)
     {
     {
-        // Before
-        // fire event only if there's a hotkey binding for the key
-        if (!KeyBindings.TryGet (key, KeyBindingScope.Focused | KeyBindingScope.HotKey, out KeyBinding kb))
-        {
-            return null;
-        }
+        //// Before
+        //// fire event only if there's a hotkey binding for the key
+        //if (!KeyBindings.TryGet (key, KeyBindingScope.Focused | KeyBindingScope.HotKey, out KeyBinding kb))
+        //{
+        //    return null;
+        //}
 
 
-        KeyBindingScope scope = kb.Scope;
+        KeyBindingScope scope = KeyBindingScope.Focused | KeyBindingScope.HotKey;
 
 
         // During
         // During
         // BUGBUG: The proper pattern is for the v-method (OnInvokingKeyBindings) to be called first, then the event
         // BUGBUG: The proper pattern is for the v-method (OnInvokingKeyBindings) to be called first, then the event
@@ -533,14 +541,13 @@ public partial class View // Keyboard APIs
             return true;
             return true;
         }
         }
 
 
-        // TODO: NewKeyDownEvent returns bool. It should be bool? so state of InvokeCommand can be reflected up stac
-        bool? handled = OnInvokingKeyBindings (key, scope);
-
-        if (handled is { } && (bool)handled)
+        if (OnInvokingKeyBindings (key, scope))
         {
         {
             return true;
             return true;
         }
         }
 
 
+        bool? handled;
+
         // After
         // After
 
 
         // * If no key binding was found, `InvokeKeyBindings` returns `null`.
         // * If no key binding was found, `InvokeKeyBindings` returns `null`.
@@ -636,7 +643,7 @@ public partial class View // Keyboard APIs
                     return true;
                     return true;
                 }
                 }
 
 
-                bool? subViewHandled = subview.OnInvokingKeyBindings (keyEvent, scope);
+                bool? subViewHandled = subview.RaiseInvokingKeyBindingsAndInvokeCommands (keyEvent);
 
 
                 if (subViewHandled is { })
                 if (subViewHandled is { })
                 {
                 {
@@ -694,26 +701,25 @@ public partial class View // Keyboard APIs
 
 
 
 
     /// <summary>
     /// <summary>
-    ///     Low-level API called when a user presses a key; invokes any key bindings set on the view. This is called
-    ///     during <see cref="NewKeyDownEvent"/> after <see cref="OnKeyDown"/> has returned.
+    ///     Called when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
+    ///     stop the key from being processed by other views.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
-    ///     <para>Fires the <see cref="InvokingKeyBindings"/> event.</para>
     ///     <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
     ///     <para>See <see href="../docs/keyboard.md">for an overview of Terminal.Gui keyboard APIs.</see></para>
     /// </remarks>
     /// </remarks>
     /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
     /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
     /// <param name="scope">The scope.</param>
     /// <param name="scope">The scope.</param>
     /// <returns>
     /// <returns>
-    ///     <see langword="null"/> if no event was raised; input proessing should continue.
-    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should
+    ///     <see langword="false"/> if the event was raised and was not handled (or cancelled); input processing should
     ///     continue.
     ///     continue.
-    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+    ///     <see langword="true"/> if the event was raised and handled (or cancelled); input processing should stop.
     /// </returns>
     /// </returns>
-    protected virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
+    protected virtual bool OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
     {
     {
         return false;
         return false;
     }
     }
 
 
+    // TODO: This does not carry KeyBindingScope, but OnInvokingKeyBindings does
     /// <summary>
     /// <summary>
     ///     Raised when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
     ///     Raised when a key is pressed that may be mapped to a key binding. Set <see cref="Key.Handled"/> to true to
     ///     stop the key from being processed by other views.
     ///     stop the key from being processed by other views.
@@ -727,10 +733,10 @@ public partial class View // Keyboard APIs
     /// <param name="key">The key event passed.</param>
     /// <param name="key">The key event passed.</param>
     /// <param name="scope">The scope.</param>
     /// <param name="scope">The scope.</param>
     /// <returns>
     /// <returns>
-    ///     <see langword="null"/> if no command was invoked; input proessing should continue.
-    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input proessing
+    ///     <see langword="null"/> if no command was invoked; input processing should continue.
+    ///     <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input processing
     ///     should continue.
     ///     should continue.
-    ///     <see langword="true"/> if at least one command was invoked and handled (or cancelled); input proessing should stop.
+    ///     <see langword="true"/> if at least one command was invoked and handled (or cancelled); input processing should stop.
     /// </returns>
     /// </returns>
     protected bool? InvokeCommands (Key key, KeyBindingScope scope)
     protected bool? InvokeCommands (Key key, KeyBindingScope scope)
     {
     {

+ 4 - 12
Terminal.Gui/Views/Menu/Menu.cs

@@ -305,19 +305,11 @@ internal sealed class Menu : View
         return true;
         return true;
     }
     }
 
 
-    /// <inheritdoc/>
-    protected override bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
+    /// <inheritdoc />
+    protected override bool OnProcessKeyDown (Key keyEvent)
     {
     {
-        bool? handled = base.OnInvokingKeyBindings (keyEvent, scope);
-
-        if (handled is { } && (bool)handled)
-        {
-            return true;
-        }
-
-        // TODO: Determine if there's a cleaner way to handle this.
-        // This supports the case where the menu bar is a context menu
-        return _host.RaiseInvokingKeyBindingsAndInvokeCommands (keyEvent);
+        // We didn't handle the key, pass it on to host
+        return _host.RaiseInvokingKeyBindingsAndInvokeCommands (keyEvent) == true;
     }
     }
 
 
     private void Current_TerminalResized (object? sender, SizeChangedEventArgs e)
     private void Current_TerminalResized (object? sender, SizeChangedEventArgs e)

+ 1 - 1
Terminal.Gui/Views/TextField.cs

@@ -1014,7 +1014,7 @@ public class TextField : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope)
+    protected override bool OnInvokingKeyBindings (Key a, KeyBindingScope scope)
     {
     {
         // Give autocomplete first opportunity to respond to key presses
         // Give autocomplete first opportunity to respond to key presses
         if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a))
         if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a))

+ 1 - 1
Terminal.Gui/Views/TextView.cs

@@ -3664,7 +3664,7 @@ public class TextView : View
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    protected override bool? OnInvokingKeyBindings (Key a, KeyBindingScope scope)
+    protected override bool OnInvokingKeyBindings (Key a, KeyBindingScope scope)
     {
     {
         if (!a.IsValid)
         if (!a.IsValid)
         {
         {

+ 36 - 40
UnitTests/View/KeyboardEventTests.cs → UnitTests/View/Keyboard/KeyboardEventTests.cs

@@ -12,7 +12,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     /// </summary>
     /// </summary>
     [Theory]
     [Theory]
     [MemberData (nameof (AllViewTypes))]
     [MemberData (nameof (AllViewTypes))]
-    public void AllViews_KeyDown_All_EventsFire (Type viewType)
+    public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType)
     {
     {
         var view = CreateInstanceIfNotGeneric (viewType);
         var view = CreateInstanceIfNotGeneric (viewType);
 
 
@@ -49,7 +49,8 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
                                    keyDownProcessed = true;
                                    keyDownProcessed = true;
                                };
                                };
 
 
-        Assert.True (view.NewKeyDownEvent (Key.A)); // this will be true because the ProcessKeyDown event handled it
+        // Key.Empty is invalid, but it's used here to test that the event is fired
+        Assert.True (view.NewKeyDownEvent (Key.Empty)); // this will be true because the ProcessKeyDown event handled it
         Assert.True (keyDown);
         Assert.True (keyDown);
         Assert.True (invokingKeyBindings);
         Assert.True (invokingKeyBindings);
         Assert.True (keyDownProcessed);
         Assert.True (keyDownProcessed);
@@ -62,7 +63,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     /// </summary>
     /// </summary>
     [Theory]
     [Theory]
     [MemberData (nameof (AllViewTypes))]
     [MemberData (nameof (AllViewTypes))]
-    public void AllViews_KeyUp_All_EventsFire (Type viewType)
+    public void AllViews_NewKeyUpEvent_All_EventsFire (Type viewType)
     {
     {
         var view = CreateInstanceIfNotGeneric (viewType);
         var view = CreateInstanceIfNotGeneric (viewType);
 
 
@@ -92,13 +93,13 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     [InlineData (true, false, false)]
     [InlineData (true, false, false)]
     [InlineData (true, true, false)]
     [InlineData (true, true, false)]
     [InlineData (true, true, true)]
     [InlineData (true, true, true)]
-    public void Events_Are_Called_With_Only_Key_Modifiers (bool shift, bool alt, bool control)
+    public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool shift, bool alt, bool control)
     {
     {
         var keyDown = false;
         var keyDown = false;
         var keyPressed = false;
         var keyPressed = false;
         var keyUp = false;
         var keyUp = false;
 
 
-        var view = new OnKeyTestView ();
+        var view = new OnNewKeyTestView ();
         view.CancelVirtualMethods = false;
         view.CancelVirtualMethods = false;
 
 
         view.KeyDown += (s, e) =>
         view.KeyDown += (s, e) =>
@@ -139,7 +140,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
                              );
                              );
         Assert.True (keyPressed);
         Assert.True (keyPressed);
         Assert.True (view.OnKeyDownCalled);
         Assert.True (view.OnKeyDownCalled);
-        Assert.True (view.OnKeyPressedContinued);
+        Assert.True (view.OnProcessKeyDownCalled);
 
 
         view.NewKeyUpEvent (
         view.NewKeyUpEvent (
                             new (
                             new (
@@ -154,7 +155,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     }
     }
 
 
     [Fact]
     [Fact]
-    public void InvokingKeyBindings_Handled_Cancels ()
+    public void NewKeyDownEvent_InvokingKeyBindings_Handled_Cancels ()
     {
     {
         var view = new View ();
         var view = new View ();
         var keyPressInvoked = false;
         var keyPressInvoked = false;
@@ -201,13 +202,13 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     }
     }
 
 
     [Fact]
     [Fact]
-    public void InvokingKeyBindings_Handled_True_Stops_Processing ()
+    public void NewKeyDownEvent_InvokingKeyBindings_Handled_True_Stops_Processing ()
     {
     {
         var keyDown = false;
         var keyDown = false;
         var invokingKeyBindings = false;
         var invokingKeyBindings = false;
         var keyPressed = false;
         var keyPressed = false;
 
 
-        var view = new OnKeyTestView ();
+        var view = new OnNewKeyTestView ();
         Assert.True (view.CanFocus);
         Assert.True (view.CanFocus);
         view.CancelVirtualMethods = false;
         view.CancelVirtualMethods = false;
 
 
@@ -233,7 +234,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
                                {
                                {
                                    Assert.Equal (KeyCode.A, e.KeyCode);
                                    Assert.Equal (KeyCode.A, e.KeyCode);
                                    Assert.False (keyPressed);
                                    Assert.False (keyPressed);
-                                   Assert.False (view.OnKeyPressedContinued);
+                                   Assert.False (view.OnProcessKeyDownCalled);
                                    e.Handled = true;
                                    e.Handled = true;
                                    keyPressed = true;
                                    keyPressed = true;
                                };
                                };
@@ -245,17 +246,17 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
 
 
         Assert.True (view.OnKeyDownCalled);
         Assert.True (view.OnKeyDownCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
-        Assert.False (view.OnKeyPressedContinued);
+        Assert.False (view.OnProcessKeyDownCalled);
     }
     }
 
 
     [Fact]
     [Fact]
-    public void KeyDown_Handled_True_Stops_Processing ()
+    public void NewKeyDownEvent_Handled_True_Stops_Processing ()
     {
     {
         var keyDown = false;
         var keyDown = false;
         var invokingKeyBindings = false;
         var invokingKeyBindings = false;
         var keyPressed = false;
         var keyPressed = false;
 
 
-        var view = new OnKeyTestView ();
+        var view = new OnNewKeyTestView ();
         Assert.True (view.CanFocus);
         Assert.True (view.CanFocus);
         view.CancelVirtualMethods = false;
         view.CancelVirtualMethods = false;
 
 
@@ -281,7 +282,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
                                {
                                {
                                    Assert.Equal (KeyCode.A, e.KeyCode);
                                    Assert.Equal (KeyCode.A, e.KeyCode);
                                    Assert.False (keyPressed);
                                    Assert.False (keyPressed);
-                                   Assert.False (view.OnKeyPressedContinued);
+                                   Assert.False (view.OnProcessKeyDownCalled);
                                    e.Handled = true;
                                    e.Handled = true;
                                    keyPressed = true;
                                    keyPressed = true;
                                };
                                };
@@ -293,11 +294,11 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
 
 
         Assert.True (view.OnKeyDownCalled);
         Assert.True (view.OnKeyDownCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
-        Assert.False (view.OnKeyPressedContinued);
+        Assert.False (view.OnProcessKeyDownCalled);
     }
     }
 
 
     [Fact]
     [Fact]
-    public void KeyPress_Handled_Cancels ()
+    public void NewKeyDownEvent_KeyDown_Handled_Stops_Processing ()
     {
     {
         var view = new View ();
         var view = new View ();
         var invokingKeyBindingsInvoked = false;
         var invokingKeyBindingsInvoked = false;
@@ -338,13 +339,13 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     }
     }
 
 
     [Fact]
     [Fact]
-    public void KeyPressed_Handled_True_Stops_Processing ()
+    public void NewKeyDownEvent_ProcessKeyDown_Handled_Stops_Processing ()
     {
     {
         var keyDown = false;
         var keyDown = false;
         var invokingKeyBindings = false;
         var invokingKeyBindings = false;
-        var keyPressed = false;
+        var processKeyDown = false;
 
 
-        var view = new OnKeyTestView ();
+        var view = new OnNewKeyTestView ();
         Assert.True (view.CanFocus);
         Assert.True (view.CanFocus);
         view.CancelVirtualMethods = false;
         view.CancelVirtualMethods = false;
 
 
@@ -360,7 +361,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
         view.InvokingKeyBindings += (s, e) =>
         view.InvokingKeyBindings += (s, e) =>
                                     {
                                     {
                                         Assert.Equal (KeyCode.A, e.KeyCode);
                                         Assert.Equal (KeyCode.A, e.KeyCode);
-                                        Assert.False (keyPressed);
+                                        Assert.False (processKeyDown);
                                         Assert.False (view.OnInvokingKeyBindingsCalled);
                                         Assert.False (view.OnInvokingKeyBindingsCalled);
                                         e.Handled = false;
                                         e.Handled = false;
                                         invokingKeyBindings = true;
                                         invokingKeyBindings = true;
@@ -369,28 +370,28 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
         view.ProcessKeyDown += (s, e) =>
         view.ProcessKeyDown += (s, e) =>
                                {
                                {
                                    Assert.Equal (KeyCode.A, e.KeyCode);
                                    Assert.Equal (KeyCode.A, e.KeyCode);
-                                   Assert.False (keyPressed);
-                                   Assert.False (view.OnKeyPressedContinued);
+                                   Assert.False (processKeyDown);
+                                   Assert.True (view.OnProcessKeyDownCalled);
                                    e.Handled = true;
                                    e.Handled = true;
-                                   keyPressed = true;
+                                   processKeyDown = true;
                                };
                                };
 
 
         view.NewKeyDownEvent (Key.A);
         view.NewKeyDownEvent (Key.A);
         Assert.True (keyDown);
         Assert.True (keyDown);
         Assert.True (invokingKeyBindings);
         Assert.True (invokingKeyBindings);
-        Assert.True (keyPressed);
+        Assert.True (processKeyDown);
 
 
         Assert.True (view.OnKeyDownCalled);
         Assert.True (view.OnKeyDownCalled);
         Assert.True (view.OnInvokingKeyBindingsCalled);
         Assert.True (view.OnInvokingKeyBindingsCalled);
-        Assert.False (view.OnKeyPressedContinued);
+        Assert.True (view.OnProcessKeyDownCalled);
     }
     }
 
 
     [Fact]
     [Fact]
-    public void KeyUp_Handled_True_Stops_Processing ()
+    public void NewKeyUpEvent_KeyUp_Handled_True_Stops_Processing ()
     {
     {
         var keyUp = false;
         var keyUp = false;
 
 
-        var view = new OnKeyTestView ();
+        var view = new OnNewKeyTestView ();
         Assert.True (view.CanFocus);
         Assert.True (view.CanFocus);
         view.CancelVirtualMethods = false;
         view.CancelVirtualMethods = false;
 
 
@@ -398,7 +399,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
                       {
                       {
                           Assert.Equal (KeyCode.A, e.KeyCode);
                           Assert.Equal (KeyCode.A, e.KeyCode);
                           Assert.False (keyUp);
                           Assert.False (keyUp);
-                          Assert.False (view.OnKeyPressedContinued);
+                          Assert.False (view.OnProcessKeyDownCalled);
                           e.Handled = true;
                           e.Handled = true;
                           keyUp = true;
                           keyUp = true;
                       };
                       };
@@ -409,14 +410,14 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
         Assert.True (view.OnKeyUpCalled);
         Assert.True (view.OnKeyUpCalled);
         Assert.False (view.OnKeyDownCalled);
         Assert.False (view.OnKeyDownCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
         Assert.False (view.OnInvokingKeyBindingsCalled);
-        Assert.False (view.OnKeyPressedContinued);
+        Assert.False (view.OnProcessKeyDownCalled);
     }
     }
 
 
     [Theory]
     [Theory]
     [InlineData (null, null)]
     [InlineData (null, null)]
     [InlineData (true, true)]
     [InlineData (true, true)]
     [InlineData (false, false)]
     [InlineData (false, false)]
-    public void OnInvokingKeyBindings_Returns_Nullable_Properly (bool? toReturn, bool? expected)
+    public void RaiseInvokingKeyBindingsAndInvokeCommands_Returns_Nullable_Properly (bool? toReturn, bool? expected)
     {
     {
         var view = new KeyBindingsTestView ();
         var view = new KeyBindingsTestView ();
         view.CommandReturns = toReturn;
         view.CommandReturns = toReturn;
@@ -439,17 +440,17 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     }
     }
 
 
     /// <summary>A view that overrides the OnKey* methods so we can test that they are called.</summary>
     /// <summary>A view that overrides the OnKey* methods so we can test that they are called.</summary>
-    public class OnKeyTestView : View
+    public class OnNewKeyTestView : View
     {
     {
-        public OnKeyTestView () { CanFocus = true; }
+        public OnNewKeyTestView () { CanFocus = true; }
         public bool CancelVirtualMethods { set; private get; }
         public bool CancelVirtualMethods { set; private get; }
         public bool OnInvokingKeyBindingsCalled { get; set; }
         public bool OnInvokingKeyBindingsCalled { get; set; }
         public bool OnKeyDownCalled { get; set; }
         public bool OnKeyDownCalled { get; set; }
-        public bool OnKeyPressedContinued { get; set; }
+        public bool OnProcessKeyDownCalled { get; set; }
         public bool OnKeyUpCalled { get; set; }
         public bool OnKeyUpCalled { get; set; }
         public override string Text { get; set; }
         public override string Text { get; set; }
 
 
-        protected override bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
+        protected override bool OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
         {
         {
 
 
             OnInvokingKeyBindingsCalled = true;
             OnInvokingKeyBindingsCalled = true;
@@ -473,12 +474,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
 
 
         protected override bool OnProcessKeyDown (Key keyEvent)
         protected override bool OnProcessKeyDown (Key keyEvent)
         {
         {
-            if (base.OnProcessKeyDown (keyEvent))
-            {
-                return true;
-            }
-
-            OnKeyPressedContinued = true;
+            OnProcessKeyDownCalled = true;
 
 
             return CancelVirtualMethods;
             return CancelVirtualMethods;
         }
         }

+ 1 - 1
UnitTests/Views/MenuBarTests.cs

@@ -1384,7 +1384,7 @@ wo
 
 
         foreach (Key key in keys)
         foreach (Key key in keys)
         {
         {
-            top.NewKeyDownEvent (new (key));
+            top.NewKeyDownEvent (key);
             Application.MainLoop.RunIteration ();
             Application.MainLoop.RunIteration ();
         }
         }
 
 

+ 126 - 0
docfx/docs/events.md

@@ -0,0 +1,126 @@
+# Terminal.Gui Event Deep Dive
+
+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 for Terminal.Gui Events (Unless you know better ones...)
+
+Tenets higher in the list have precedence over tenets lower in the list.
+
+* **UI Interaction and Live Data Are Different Beasts** - TG distinguishes between events used for human interaction and events for live data. We don't believe in a one-size-fits-all eventing model. For UI interactions we use `EventHandler`. For data binding we think `INotifyPropertyChanged` is groovy. For some callbacks we use `Action<T>`.
+
+## Lexicon and Taxonomy
+
+* *Action*
+* *Event*
+* *Command*
+* *Invoke*
+* *Raise*
+* *Listen*
+* *Handle/Handling/Handled* - Applies to scenarios where an event can either be handled by an event listener (or override) vs not handled. Events that originate from a user action like mouse moves and key presses are examples. 
+* *Cancel/Cancelling/Cancelled* - Applies to scenarios where something can be cancelled. Changing the `Orientation` of a `Slider` is cancelable.
+
+## Useful External Documentation
+
+* [.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)
+* [.NET Design for Extensibility - Events and Callbacks](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/events-and-callbacks)
+* [C# Event Implementation Fundamentals, Best Practices and Conventions](https://www.codeproject.com/Articles/20550/C-Event-Implementation-Fundamentals-Best-Practices)
+
+## Naming
+
+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).
+
+## `EventHandler` style event best-practices
+
+* Implement a helper method for raising the event: `RaisexxxEvent`.
+  * If the event is cancelable, the return type should be either `bool` or `bool?`.
+  * Can be `private`, `internal`, or `public` depending on the situation. `internal` should only be used to enable unit tests.
+* Raising an event involves FIRST calling the `protected virtual` method, THEN invoking the `EventHandler.
+
+## `Action<T>` style callback best-practices
+
+- tbd
+
+## `INotifyPropertyChanged` style notification best practices
+
+- tbd
+
+## Common Patterns
+
+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.
+
+
+
+## Cancellable Event Pattern
+
+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.
+
+### **Before** - If any pre-conditions are met raise the "pre-event", typically named in the form of "xxxChanging". e.g.
+
+  - A `protected virtual` method is called. This method is named `OnxxxChanging` and the base implementation simply does `return false`.
+  - If the `OnxxxChanging` method returns `true` it means a derived class canceled the event. Processing should stop.
+  - 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.
+
+
+### **During** - Do work.
+
+### **After** - Raise the "post-event", typically named in the form of "xxxChanged"
+
+  - A `protected virtual` method is called. This method is named `OnxxxChanged` has a return type of `void`.
+  - The `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.
+
+```cs
+    /// <summary>
+    ///     Gets or sets the orientation of the View.
+    /// </summary>
+    public Orientation Orientation
+    {
+        get => _orientation;
+        set
+        {
+            if (_orientation == value)
+            {
+                return;
+            }
+
+            // Best practice is to invoke 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 invoke the virtual method first.
+            _owner?.OnOrientationChanged (_orientation);
+
+            // Even though Changed is not cancelable, it is still a good practice to raise the event after.
+            OrientationChanged?.Invoke (_owner, new (in _orientation));
+        }
+    }
+```
+
+ 
+