瀏覽代碼

Fixed RadioGroup

Tig 10 月之前
父節點
當前提交
66edb36cc7

+ 4 - 0
Terminal.Gui/View/View.Keyboard.cs

@@ -11,6 +11,10 @@ public partial class View // Keyboard APIs
     private void SetupKeyboard ()
     {
         KeyBindings = new (this);
+        KeyBindings.Add (Key.Space, Command.Select);
+        KeyBindings.Add (Key.Enter, Command.Accept);
+
+        // Note, setting HotKey will bind HotKey to Command.HotKey
         HotKeySpecifier = (Rune)'_';
         TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
     }

+ 33 - 45
Terminal.Gui/View/View.cs

@@ -136,20 +136,39 @@ public partial class View : Responder, ISupportInitializeNotification
     {
         SetupAdornments ();
 
+        AddCommand (
+                    Command.Select,
+                    () =>
+                    {
+                        SetFocus ();
+
+                        return RaiseSelectEvent ();
+                    });
+
+        AddCommand (
+                    Command.HotKey,
+                    () =>
+                    {
+                        SetFocus ();
+
+                        return RaiseHotKeyCommandEvent ();
+                    });
+
+        AddCommand (
+                    Command.Accept,
+                    () =>
+                    {
+                        SetFocus ();
+
+                        return RaiseAcceptEvent ();
+                    });
+
         SetupKeyboard ();
 
         //SetupMouse ();
 
         SetupText ();
 
-        // By default, the Select command does nothing
-        AddCommand (Command.Select, RaiseSelectEvent);
-
-        // By default, the HotKey command sets the focus
-        AddCommand (Command.HotKey, RaiseHotKeyEvent);
-
-        // By default, the Accept command sets the focus
-        AddCommand (Command.Accept, RaiseAcceptEvent);
     }
 
     /// <summary>
@@ -285,19 +304,9 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     Called when the <see cref="Command.Accept"/> command is received. Set <see cref="HandledEventArgs.Handled"/> to
     ///     <see langword="true"/> to stop processing.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         The base implementation calls <see cref="SetFocus"/>.
-    ///     </para>
-    /// </remarks>
     /// <param name="args"></param>
     /// <returns><see langword="true"/> to stop processing.</returns>
-    protected virtual bool OnAccept (HandledEventArgs args)
-    {
-        SetFocus ();
-
-        return false;
-    }
+    protected virtual bool OnAccept (HandledEventArgs args) { return false; }
 
     /// <summary>
     ///     Cancelable event raised when the <see cref="Command.Accept"/> command is invoked. Set
@@ -335,19 +344,9 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     Called when the <see cref="Command.Select"/> command is received. Set <see cref="HandledEventArgs.Handled"/> to
     ///     <see langword="true"/> to stop processing.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         The base implementation calls <see cref="SetFocus"/>.
-    ///     </para>
-    /// </remarks>
     /// <param name="args"></param>
     /// <returns><see langword="true"/> to stop processing.</returns>
-    protected virtual bool OnSelect (HandledEventArgs args)
-    {
-        SetFocus ();
-
-        return false;
-    }
+    protected virtual bool OnSelect (HandledEventArgs args) { return false; }
 
     /// <summary>
     ///     Cancelable event raised when the <see cref="Command.Select"/> command is invoked. Set
@@ -356,22 +355,21 @@ public partial class View : Responder, ISupportInitializeNotification
     /// </summary>
     public event EventHandler<HandledEventArgs>? Select;
 
-
     /// <summary>
     ///     Called when the <see cref="Command.HotKey"/> command is invoked. Raises <see cref="HotKey"/>
     ///     event.
     /// </summary>
     /// <returns>
-    ///     If <see langword="true"/> the event was canceled. If <see langword="false"/> the event was raised but not canceled.
+    ///     If <see langword="true"/> the event was handled. If <see langword="false"/> the event was raised but not handled.
     ///     If <see langword="null"/> no event was raised.
     /// </returns>
-    protected bool? RaiseHotKeyEvent ()
+    protected bool? RaiseHotKeyCommandEvent ()
     {
         HandledEventArgs args = new ();
 
         // Best practice is to invoke the virtual method first.
         // This allows derived classes to handle the event and potentially cancel it.
-        if (OnHotKey (args) || args.Handled)
+        if (OnHotKeyCommand (args) || args.Handled)
         {
             return true;
         }
@@ -386,19 +384,9 @@ public partial class View : Responder, ISupportInitializeNotification
     ///     Called when the <see cref="Command.HotKey"/> command is received. Set <see cref="HandledEventArgs.Handled"/> to
     ///     <see langword="true"/> to stop processing.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         The base implementation calls <see cref="SetFocus"/>.
-    ///     </para>
-    /// </remarks>
     /// <param name="args"></param>
     /// <returns><see langword="true"/> to stop processing.</returns>
-    protected virtual bool OnHotKey (HandledEventArgs args)
-    {
-        SetFocus ();
-
-        return false;
-    }
+    protected virtual bool OnHotKeyCommand (HandledEventArgs args) { return false; }
 
     /// <summary>
     ///     Cancelable event raised when the <see cref="Command.HotKey"/> command is invoked. Set

+ 2 - 0
Terminal.Gui/Views/Button.cs

@@ -89,7 +89,9 @@ public class Button : View, IDesignable
                         return false;
                     });
 
+        KeyBindings.Remove (Key.Space);
         KeyBindings.Add (Key.Space, Command.HotKey);
+        KeyBindings.Remove (Key.Enter);
         KeyBindings.Add (Key.Enter, Command.HotKey);
 
         TitleChanged += Button_TitleChanged;

+ 0 - 3
Terminal.Gui/Views/CheckBox.cs

@@ -24,9 +24,6 @@ public class CheckBox : View
         AddCommand (Command.Accept, AdvanceCheckState);
         AddCommand (Command.HotKey, AdvanceCheckState);
 
-        // Default keybindings for this view
-        KeyBindings.Add (Key.Space, Command.Accept);
-
         TitleChanged += Checkbox_TitleChanged;
 
         HighlightStyle = DefaultHighlightStyle;

+ 0 - 1
Terminal.Gui/Views/ComboBox.cs

@@ -90,7 +90,6 @@ public class ComboBox : View, IDesignable
         AddCommand (Command.UnixEmulation, () => UnixEmulation ());
 
         // Default keybindings for this view
-        KeyBindings.Add (Key.Enter, Command.Accept);
         KeyBindings.Add (Key.F4, Command.Toggle);
         KeyBindings.Add (Key.CursorDown, Command.Down);
         KeyBindings.Add (Key.CursorUp, Command.Up);

+ 0 - 1
Terminal.Gui/Views/HexView.cs

@@ -80,7 +80,6 @@ public class HexView : View
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.CursorDown, Command.Down);
         KeyBindings.Add (Key.CursorUp, Command.Up);
-        KeyBindings.Add (Key.Enter, Command.Accept);
 
         KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
         KeyBindings.Add (Key.PageUp, Command.PageUp);

+ 0 - 3
Terminal.Gui/Views/Label.cs

@@ -19,9 +19,6 @@ public class Label : View
         // Things this view knows how to do
         AddCommand (Command.HotKey, FocusNext);
 
-        // Default key bindings for this view
-        KeyBindings.Add (Key.Space, Command.Accept);
-
         TitleChanged += Label_TitleChanged;
         MouseClick += Label_MouseClick;
     }

+ 4 - 4
Terminal.Gui/Views/ListView.cs

@@ -180,8 +180,8 @@ public class ListView : View, IDesignable
 
         KeyBindings.Add (Key.End, Command.End);
 
-        KeyBindings.Add (Key.Space, Command.Select);
-
+        // BUGBUG: This should just be Command.Accept
+        KeyBindings.Remove (Key.Enter);
         KeyBindings.Add (Key.Enter, Command.Open);
     }
 
@@ -476,9 +476,9 @@ public class ListView : View, IDesignable
 
         _selected = Viewport.Y + me.Position.Y;
 
-        if (!MarkUnmarkSelectedItem())
+        if (MarkUnmarkSelectedItem())
         {
-            return true;
+           // return true;
         }
 
         OnSelectedChanged ();

+ 0 - 1
Terminal.Gui/Views/Menu/Menu.cs

@@ -195,7 +195,6 @@ internal sealed class Menu : View
         KeyBindings.Add (Key.CursorLeft, Command.Left);
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.Esc, Command.Cancel);
-        KeyBindings.Add (Key.Enter, Command.Accept);
     }
 
     private void AddKeyBindingsHotKey (MenuBarItem? menuBarItem)

+ 0 - 1
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -145,7 +145,6 @@ public class MenuBar : View, IDesignable
         KeyBindings.Add (Key.CursorRight, Command.Right);
         KeyBindings.Add (Key.Esc, Command.Cancel);
         KeyBindings.Add (Key.CursorDown, Command.Accept);
-        KeyBindings.Add (Key.Enter, Command.Accept);
 
         KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
         KeyBindings.Add (Key, keyBinding);

+ 59 - 39
Terminal.Gui/Views/RadioGroup.cs

@@ -3,13 +3,6 @@
 /// <summary>Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.</summary>
 public class RadioGroup : View, IDesignable, IOrientation
 {
-    private int _cursor;
-    private List<(int pos, int length)> _horizontal;
-    private int _horizontalSpace = 2;
-    private List<string> _radioLabels = [];
-    private int _selected;
-    private readonly OrientationHelper _orientationHelper;
-
     /// <summary>
     ///     Initializes a new instance of the <see cref="RadioGroup"/> class.
     /// </summary>
@@ -42,6 +35,7 @@ public class RadioGroup : View, IDesignable, IOrientation
                         {
                             return false;
                         }
+
                         return MoveDownRight ();
                     }
                    );
@@ -75,11 +69,12 @@ public class RadioGroup : View, IDesignable, IOrientation
                         return true;
                     }
                    );
+
         AddCommand (
                     Command.Select,
                     () =>
                     {
-                        if (SelectedItem == _cursor)
+                        if (SelectedItem == Cursor)
                         {
                             if (!MoveDownRight ())
                             {
@@ -87,19 +82,21 @@ public class RadioGroup : View, IDesignable, IOrientation
                             }
                         }
 
-                        SelectedItem = _cursor;
+                        SelectedItem = Cursor;
 
                         return true;
                     });
+
         AddCommand (
                     Command.Accept,
                     () =>
                     {
-                        SelectedItem = _cursor;
+                        SelectedItem = Cursor;
 
                         return RaiseAcceptEvent () is false;
                     }
                    );
+
         AddCommand (
                     Command.HotKey,
                     ctx =>
@@ -132,24 +129,26 @@ public class RadioGroup : View, IDesignable, IOrientation
 
     private void SetupKeyBindings ()
     {
-        KeyBindings.Clear ();
-
         // Default keybindings for this view
         if (Orientation == Orientation.Vertical)
         {
+            KeyBindings.Remove (Key.CursorUp);
             KeyBindings.Add (Key.CursorUp, Command.Up);
+            KeyBindings.Remove (Key.CursorDown);
             KeyBindings.Add (Key.CursorDown, Command.Down);
         }
         else
         {
+            KeyBindings.Remove (Key.CursorLeft);
             KeyBindings.Add (Key.CursorLeft, Command.Up);
+            KeyBindings.Remove (Key.CursorRight);
             KeyBindings.Add (Key.CursorRight, Command.Down);
         }
 
+        KeyBindings.Remove (Key.Home);
         KeyBindings.Add (Key.Home, Command.Start);
+        KeyBindings.Remove (Key.End);
         KeyBindings.Add (Key.End, Command.End);
-        KeyBindings.Add (Key.Enter, Command.Accept);
-        KeyBindings.Add (Key.Space, Command.Select);
     }
 
     private void RadioGroup_MouseClick (object sender, MouseEventEventArgs e)
@@ -173,7 +172,7 @@ public class RadioGroup : View, IDesignable, IOrientation
 
             if (c > -1)
             {
-                _cursor = SelectedItem = c;
+                Cursor = SelectedItem = c;
                 SetNeedsDisplay ();
             }
         }
@@ -181,6 +180,9 @@ public class RadioGroup : View, IDesignable, IOrientation
         e.Handled = true;
     }
 
+    private List<(int pos, int length)> _horizontal;
+    private int _horizontalSpace = 2;
+
     /// <summary>
     ///     Gets or sets the horizontal space for this <see cref="RadioGroup"/> if the <see cref="Orientation"/> is
     ///     <see cref="Orientation.Horizontal"/>
@@ -199,6 +201,8 @@ public class RadioGroup : View, IDesignable, IOrientation
         }
     }
 
+    private List<string> _radioLabels = [];
+
     /// <summary>
     ///     The radio labels to display. A key binding will be added for each radio enabling the user to select
     ///     and/or focus the radio label using the keyboard. See <see cref="View.HotKey"/> for details on how HotKeys work.
@@ -236,6 +240,8 @@ public class RadioGroup : View, IDesignable, IOrientation
         }
     }
 
+    private int _selected;
+
     /// <summary>The currently selected item from the list of radio labels</summary>
     /// <value>The selected.</value>
     public int SelectedItem
@@ -244,7 +250,7 @@ public class RadioGroup : View, IDesignable, IOrientation
         set
         {
             OnSelectedItemChanged (value, SelectedItem);
-            _cursor = Math.Max (_selected, 0);
+            Cursor = Math.Max (_selected, 0);
             SetNeedsDisplay ();
         }
     }
@@ -283,19 +289,19 @@ public class RadioGroup : View, IDesignable, IOrientation
                 {
                     Rune rune = rlRunes [j];
 
-                    if (j == hotPos && i == _cursor)
+                    if (j == hotPos && i == Cursor)
                     {
                         Application.Driver?.SetAttribute (
-                                                         HasFocus
-                                                             ? ColorScheme.HotFocus
-                                                             : GetHotNormalColor ()
-                                                        );
+                                                          HasFocus
+                                                              ? ColorScheme.HotFocus
+                                                              : GetHotNormalColor ()
+                                                         );
                     }
-                    else if (j == hotPos && i != _cursor)
+                    else if (j == hotPos && i != Cursor)
                     {
                         Application.Driver?.SetAttribute (GetHotNormalColor ());
                     }
-                    else if (HasFocus && i == _cursor)
+                    else if (HasFocus && i == Cursor)
                     {
                         Application.Driver?.SetAttribute (GetFocusColor ());
                     }
@@ -305,15 +311,15 @@ public class RadioGroup : View, IDesignable, IOrientation
                         j++;
                         rune = rlRunes [j];
 
-                        if (i == _cursor)
+                        if (i == Cursor)
                         {
                             Application.Driver?.SetAttribute (
-                                                             HasFocus
-                                                                 ? ColorScheme.HotFocus
-                                                                 : GetHotNormalColor ()
-                                                            );
+                                                              HasFocus
+                                                                  ? ColorScheme.HotFocus
+                                                                  : GetHotNormalColor ()
+                                                             );
                         }
-                        else if (i != _cursor)
+                        else if (i != Cursor)
                         {
                             Application.Driver?.SetAttribute (GetHotNormalColor ());
                         }
@@ -325,11 +331,13 @@ public class RadioGroup : View, IDesignable, IOrientation
             }
             else
             {
-                DrawHotString (rl, HasFocus && i == _cursor);
+                DrawHotString (rl, HasFocus && i == Cursor);
             }
         }
     }
 
+    #region IOrientation
+
     /// <summary>
     ///     Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
     ///     <see cref="Orientation.Vertical"/>.
@@ -340,7 +348,7 @@ public class RadioGroup : View, IDesignable, IOrientation
         set => _orientationHelper.Orientation = value;
     }
 
-    #region IOrientation
+    private readonly OrientationHelper _orientationHelper;
 
     /// <inheritdoc/>
     public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
@@ -373,6 +381,17 @@ public class RadioGroup : View, IDesignable, IOrientation
         SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem));
     }
 
+    /// <summary>
+    ///     Gets or sets the <see cref="RadioLabels"/> index for the cursor. The cursor may or may not be the selected
+    ///     RadioItem.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Maps to either the X or Y position within <see cref="View.Viewport"/> depending on <see cref="Orientation"/>.
+    ///     </para>
+    /// </remarks>
+    public int Cursor { get; set; }
+
     /// <inheritdoc/>
     public override Point? PositionCursor ()
     {
@@ -382,13 +401,13 @@ public class RadioGroup : View, IDesignable, IOrientation
         switch (Orientation)
         {
             case Orientation.Vertical:
-                y = _cursor;
+                y = Cursor;
 
                 break;
             case Orientation.Horizontal:
                 if (_horizontal.Count > 0)
                 {
-                    x = _horizontal [_cursor].pos;
+                    x = _horizontal [Cursor].pos;
                 }
 
                 break;
@@ -411,9 +430,9 @@ public class RadioGroup : View, IDesignable, IOrientation
 
     private bool MoveDownRight ()
     {
-        if (_cursor + 1 < _radioLabels.Count)
+        if (Cursor + 1 < _radioLabels.Count)
         {
-            _cursor++;
+            Cursor++;
             SetNeedsDisplay ();
 
             return true;
@@ -423,18 +442,19 @@ public class RadioGroup : View, IDesignable, IOrientation
         return false;
     }
 
-    private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
-    private void MoveHome () { _cursor = 0; }
+    private void MoveEnd () { Cursor = Math.Max (_radioLabels.Count - 1, 0); }
+    private void MoveHome () { Cursor = 0; }
 
     private bool MoveUpLeft ()
     {
-        if (_cursor > 0)
+        if (Cursor > 0)
         {
-            _cursor--;
+            Cursor--;
             SetNeedsDisplay ();
 
             return true;
         }
+
         // Moving past should move focus to next view, not wrap
         return false;
     }

+ 0 - 2
Terminal.Gui/Views/Shortcut.cs

@@ -65,8 +65,6 @@ public class Shortcut : View, IOrientation, IDesignable
         AddCommand (Command.HotKey, ctx => OnAccept (ctx));
         AddCommand (Command.Accept, ctx => OnAccept (ctx));
         AddCommand (Command.Select, ctx => OnSelect (ctx));
-        KeyBindings.Add (KeyCode.Enter, Command.Accept);
-        KeyBindings.Add (KeyCode.Space, Command.Select);
 
         TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set
 

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

@@ -283,8 +283,8 @@ public class TableView : View
         KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
 
         KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
+        KeyBindings.Remove (CellActivationKey);
         KeyBindings.Add (CellActivationKey, Command.Accept);
-        KeyBindings.Add (Key.Space, Command.Select);
     }
 
     // TODO: Update to use Key instead of KeyCode

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

@@ -409,7 +409,6 @@ public class TextField : View
         ContextMenu.KeyChanged += ContextMenu_KeyChanged;
 
         KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.Context);
-        KeyBindings.Add (Key.Enter, Command.Accept);
     }
 
 

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

@@ -2275,7 +2275,9 @@ public class TextView : View
                         return true;
                     }
                    );
-        AddCommand (Command.NewLine, () => ProcessReturn ());
+
+        // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
+        AddCommand (Command.Accept, () => ProcessReturn ());
 
         AddCommand (
                     Command.End,
@@ -2479,8 +2481,6 @@ public class TextView : View
         KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards); // kill-word-forwards
         KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards); // kill-word-backwards
 
-        // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
-        KeyBindings.Add (Key.Enter, Command.NewLine);
         KeyBindings.Add (Key.End.WithCtrl, Command.End);
         KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
         KeyBindings.Add (Key.Home.WithCtrl, Command.Start);

+ 3 - 1
Terminal.Gui/Views/TreeView/TreeView.cs

@@ -296,6 +296,8 @@ public class TreeView<T> : View, ITreeView where T : class
         KeyBindings.Add (Key.Home, Command.Start);
         KeyBindings.Add (Key.End, Command.End);
         KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
+
+        KeyBindings.Remove (ObjectActivationKey);
         KeyBindings.Add (ObjectActivationKey, Command.Select);
     }
 
@@ -1227,7 +1229,7 @@ public class TreeView<T> : View, ITreeView where T : class
             {
                 Move (0, idx - ScrollOffsetVertical);
 
-                return MultiSelect ? new (0, idx - ScrollOffsetVertical) : null ;
+                return MultiSelect ? new (0, idx - ScrollOffsetVertical) : null;
             }
         }
         return base.PositionCursor ();

+ 0 - 2
Terminal.Gui/Views/Window.cs

@@ -31,8 +31,6 @@ public class Window : Toplevel
         ColorScheme = Colors.ColorSchemes ["Base"]; // TODO: make this a theme property
         BorderStyle = DefaultBorderStyle;
         ShadowStyle = DefaultShadow;
-
-        KeyBindings.Add (Key.Enter, Command.Accept);
     }
 
     // TODO: enable this

+ 0 - 1
UICatalog/Scenarios/CharacterMap.cs

@@ -449,7 +449,6 @@ internal class CharMap : View
                     }
                    );
 
-        KeyBindings.Add (Key.Enter, Command.Accept);
         KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
         KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
         KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft);

+ 2 - 2
UICatalog/Scenarios/Generic.cs

@@ -17,8 +17,8 @@ public sealed class MyScenario : Scenario
             Title = GetQuitKeyAndName (),
         };
 
-        var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" };
-        button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "Ok");
+        var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "_Press me!" };
+        button.Accept += (s, e) => MessageBox.ErrorQuery ("Error", "You pressed the button!", "_Ok");
         appWindow.Add (button);
 
         // Run - Start the application.

+ 42 - 0
UnitTests/View/HotKeyTests.cs

@@ -341,4 +341,46 @@ public class HotKeyTests
         Assert.Equal ("", view.Title);
         Assert.Equal (KeyCode.Null, view.HotKey);
     }
+
+
+    [Fact]
+    public void HotKey_Raises_HotKeyCommand ()
+    {
+        var hotKeyRaised = false;
+        var acceptRaised = false;
+        var selectRaised = false;
+        Application.Top = new Toplevel ();
+        var view = new View
+        {
+            CanFocus = true,
+            HotKeySpecifier = new Rune ('_'),
+            Title = "_Test"
+        };
+        Application.Top.Add (view);
+        view.HotKeyCommand += (s, e) => hotKeyRaised = true;
+        view.Accept += (s, e) => acceptRaised = true;
+        view.Select += (s, e) => selectRaised = true;
+
+        Assert.Equal (KeyCode.T, view.HotKey);
+        Assert.False (Application.OnKeyDown (Key.T)); // wasn't handled
+        Assert.True (hotKeyRaised);
+        Assert.False (acceptRaised);
+        Assert.False (selectRaised);
+
+        hotKeyRaised = false;
+        Assert.False (Application.OnKeyDown (Key.T.WithAlt));
+        Assert.True (hotKeyRaised);
+        Assert.False (acceptRaised);
+        Assert.False (selectRaised);
+
+        hotKeyRaised = false;
+        view.HotKey = KeyCode.E;
+        Assert.False (Application.OnKeyDown (Key.E.WithAlt));
+        Assert.True (hotKeyRaised);
+        Assert.False (acceptRaised);
+        Assert.False (selectRaised);
+
+        Application.Top.Dispose ();
+        Application.ResetState (true);
+    }
 }

+ 175 - 0
UnitTests/View/ViewCommandTests.cs

@@ -0,0 +1,175 @@
+using System.ComponentModel;
+using System.Text;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+public class ViewCommandTests (ITestOutputHelper output)
+{
+    // OnAccept/Accept tests
+    [Fact]
+    public void Accept_Command_Raises ()
+    {
+        var view = new ViewEventTester ();
+        Assert.False (view.HasFocus);
+
+        Assert.False (view.InvokeCommand (Command.Accept)); // false means it was not handled
+
+        Assert.Equal (1, view.OnAcceptCount);
+
+        Assert.Equal (1, view.AcceptCount);
+
+        Assert.True (view.HasFocus);
+    }
+
+    [Fact]
+    public void Accept_Command_Handle_OnAccept_NoEvent ()
+    {
+        var view = new ViewEventTester ();
+        Assert.False (view.HasFocus);
+
+        view.HandleOnAccept = true;
+        Assert.True (view.InvokeCommand (Command.Accept));
+
+        Assert.Equal (1, view.OnAcceptCount);
+
+        Assert.Equal (0, view.AcceptCount);
+    }
+
+
+
+
+    [Fact]
+    public void Accept_Handle_Event_OnAccept_Returns_True ()
+    {
+        var view = new View ();
+        var acceptInvoked = false;
+
+        view.Accept += ViewOnAccept;
+
+        bool? ret = view.InvokeCommand (Command.Accept);
+        Assert.True (ret);
+        Assert.True (acceptInvoked);
+
+        return;
+
+        void ViewOnAccept (object sender, HandledEventArgs e)
+        {
+            acceptInvoked = true;
+            e.Handled = true;
+        }
+    }
+
+    [Fact]
+    public void Accept_Command_Invokes_Accept_Event ()
+    {
+        var view = new View ();
+        var accepted = false;
+
+        view.Accept += ViewOnAccept;
+
+        view.InvokeCommand (Command.Accept);
+        Assert.True (accepted);
+
+        return;
+
+        void ViewOnAccept (object sender, HandledEventArgs e) { accepted = true; }
+    }
+
+    [Fact]
+    public void HotKey_Command_SetsFocus ()
+    {
+        var view = new View ();
+
+        view.CanFocus = true;
+        Assert.False (view.HasFocus);
+        view.InvokeCommand (Command.HotKey);
+        Assert.True (view.HasFocus);
+    }
+
+    public class ViewEventTester : View
+    {
+        public ViewEventTester ()
+        {
+            CanFocus = true;
+
+            Accept += (s, a) =>
+                      {
+                          a.Handled = HandleAccept;
+                          AcceptCount++;
+                      };
+
+            HotKeyCommand += (s, a) =>
+                             {
+                                 a.Handled = HandleHotKeyCommand;
+                                 HotKeyCommandCount++;
+                             };
+
+
+            Select += (s, a) =>
+                             {
+                                 a.Handled = HandleSelect;
+                                 SelectCount++;
+                             };
+        }
+
+        public int OnAcceptCount { get; set; }
+        public int AcceptCount { get; set; }
+        public bool HandleOnAccept { get; set; }
+
+        /// <inheritdoc />
+        protected override bool OnAccept (HandledEventArgs args)
+        {
+            OnAcceptCount++;
+
+            if (!HandleOnAccept)
+            {
+                return base.OnAccept (args);
+            }
+
+            return HandleOnAccept;
+        }
+
+        public bool HandleAccept { get; set; }
+
+        public int OnHotKeyCommandCount { get; set; }
+        public int HotKeyCommandCount { get; set; }
+        public bool HandleOnHotKeyCommand { get; set; }
+
+        /// <inheritdoc />
+        protected override bool OnHotKeyCommand (HandledEventArgs args)
+        {
+            OnHotKeyCommandCount++;
+            if (!HandleOnHotKeyCommand)
+            {
+                return base.OnHotKeyCommand (args);
+            }
+
+
+            return HandleOnHotKeyCommand;
+        }
+
+        public bool HandleHotKeyCommand { get; set; }
+
+
+        public int OnSelectCount { get; set; }
+        public int SelectCount { get; set; }
+        public bool HandleOnSelect { get; set; }
+
+        /// <inheritdoc />
+        protected override bool OnSelect (HandledEventArgs args)
+        {
+            OnSelectCount++;
+
+            if (!HandleOnSelect)
+            {
+                return base.OnSelect (args);
+            }
+
+            return HandleOnSelect;
+        }
+
+        public bool HandleSelect { get; set; }
+
+    }
+}

+ 10 - 75
UnitTests/View/ViewTests.cs

@@ -160,7 +160,7 @@ public class ViewTests (ITestOutputHelper output)
         if (label)
         {
             Assert.False (v.CanFocus);
-            Assert.Equal (new  (0, 0, text.Length, 1), v.Frame);
+            Assert.Equal (new (0, 0, text.Length, 1), v.Frame);
         }
         else
         {
@@ -469,7 +469,7 @@ At 0,0
             X = 0, // don't overcomplicate unit tests
             Y = 1,
             Height = Dim.Auto (DimAutoStyle.Text),
-            Width = Dim.Auto(DimAutoStyle.Text),
+            Width = Dim.Auto (DimAutoStyle.Text),
             Text = "Press me!"
         };
 
@@ -783,7 +783,7 @@ At 0,0
         r.Dispose ();
 
         // Empty Rect
-        r = new() { Frame = Rectangle.Empty };
+        r = new () { Frame = Rectangle.Empty };
         Assert.NotNull (r);
         Assert.Equal ($"View(){r.Viewport}", r.ToString ());
         Assert.False (r.CanFocus);
@@ -807,7 +807,7 @@ At 0,0
         r.Dispose ();
 
         // Rect with values
-        r = new() { Frame = new (1, 2, 3, 4) };
+        r = new () { Frame = new (1, 2, 3, 4) };
         Assert.NotNull (r);
         Assert.Equal ($"View(){r.Frame}", r.ToString ());
         Assert.False (r.CanFocus);
@@ -831,7 +831,7 @@ At 0,0
         r.Dispose ();
 
         // Initializes a view with a vertical direction
-        r = new()
+        r = new ()
         {
             Text = "Vertical View",
             TextDirection = TextDirection.TopBottom_LeftRight,
@@ -870,11 +870,11 @@ At 0,0
     {
         var r = new View ();
 
-        Assert.False (r.OnKeyDown (new() { KeyCode = KeyCode.Null }));
+        Assert.False (r.OnKeyDown (new () { KeyCode = KeyCode.Null }));
 
         //Assert.False (r.OnKeyDown (new KeyEventArgs () { Key = Key.Unknown }));
-        Assert.False (r.OnKeyUp (new() { KeyCode = KeyCode.Null }));
-        Assert.False (r.NewMouseEvent (new() { Flags = MouseFlags.AllEvents }));
+        Assert.False (r.OnKeyUp (new () { KeyCode = KeyCode.Null }));
+        Assert.False (r.NewMouseEvent (new () { Flags = MouseFlags.AllEvents }));
 
         r.Dispose ();
 
@@ -960,7 +960,7 @@ At 0,0
         view.Dispose ();
 
         // Object Initializer
-        view = new() { X = 1, Y = 2, Text = "" };
+        view = new () { X = 1, Y = 2, Text = "" };
         Assert.Equal (1, view.X);
         Assert.Equal (2, view.Y);
         Assert.Equal (0, view.Width);
@@ -975,7 +975,7 @@ At 0,0
         view.Y = 2;
         view.Width = 3;
         view.Height = 4;
-        super = new() { Frame = new (0, 0, 10, 10) };
+        super = new () { Frame = new (0, 0, 10, 10) };
         super.Add (view);
         super.BeginInit ();
         super.EndInit ();
@@ -1153,69 +1153,4 @@ At 0,0
             return true;
         }
     }
-
-    // OnAccept/Accept tests
-    [Fact]
-    public void OnAccept_Fires_Accept ()
-    {
-        var view = new View ();
-        var accepted = false;
-
-        view.Accept += ViewOnAccept;
-
-        view.InvokeCommand (Command.Accept);
-        Assert.True (accepted);
-
-        return;
-
-        void ViewOnAccept (object sender, HandledEventArgs e) { accepted = true; }
-    }
-
-    [Fact]
-    public void Accept_Cancel_Event_OnAccept_Returns_True ()
-    {
-        var view = new View ();
-        var acceptInvoked = false;
-
-        view.Accept += ViewOnAccept;
-
-        bool? ret = view.InvokeCommand (Command.Accept);
-        Assert.True (ret);
-        Assert.True (acceptInvoked);
-
-        return;
-
-        void ViewOnAccept (object sender, HandledEventArgs e)
-        {
-            acceptInvoked = true;
-            e.Handled = true;
-        }
-    }
-
-    [Fact]
-    public void Accept_Command_Invokes_Accept_Event ()
-    {
-        var view = new View ();
-        var accepted = false;
-
-        view.Accept += ViewOnAccept;
-
-        view.InvokeCommand (Command.Accept);
-        Assert.True (accepted);
-
-        return;
-
-        void ViewOnAccept (object sender, HandledEventArgs e) { accepted = true; }
-    }
-
-    [Fact]
-    public void HotKey_Command_SetsFocus ()
-    {
-        var view = new View ();
-
-        view.CanFocus = true;
-        Assert.False (view.HasFocus);
-        view.InvokeCommand (Command.HotKey);
-        Assert.True (view.HasFocus);
-    }
 }

+ 14 - 14
UnitTests/Views/ButtonTests.cs

@@ -252,16 +252,16 @@ public class ButtonTests (ITestOutputHelper output)
         btn.Accept += (s, e) => clicked = true;
 
         Assert.Equal (KeyCode.T, btn.HotKey);
-        Assert.True (btn.NewKeyDownEvent (Key.T));
+        Assert.False (btn.NewKeyDownEvent (Key.T)); // Button processes, but does not handle
         Assert.True (clicked);
 
         clicked = false;
-        Assert.True (btn.NewKeyDownEvent (Key.T.WithAlt));
+        Assert.False (btn.NewKeyDownEvent (Key.T.WithAlt)); // Button processes, but does not handle
         Assert.True (clicked);
 
         clicked = false;
         btn.HotKey = KeyCode.E;
-        Assert.True (btn.NewKeyDownEvent (Key.E.WithAlt));
+        Assert.False (btn.NewKeyDownEvent (Key.E.WithAlt)); // Button processes, but does not handle
         Assert.True (clicked);
     }
 
@@ -421,56 +421,56 @@ public class ButtonTests (ITestOutputHelper output)
 
         // Hot key. Both alone and with alt
         Assert.Equal (KeyCode.T, btn.HotKey);
-        Assert.True (btn.NewKeyDownEvent (Key.T));
+        Assert.False (btn.NewKeyDownEvent (Key.T)); // Button processes, but does not handle
         Assert.True (clicked);
         clicked = false;
 
-        Assert.True (btn.NewKeyDownEvent (Key.T.WithAlt));
+        Assert.False (btn.NewKeyDownEvent (Key.T.WithAlt));
         Assert.True (clicked);
         clicked = false;
 
-        Assert.True (btn.NewKeyDownEvent (btn.HotKey));
+        Assert.False (btn.NewKeyDownEvent (btn.HotKey));
         Assert.True (clicked);
         clicked = false;
-        Assert.True (btn.NewKeyDownEvent (btn.HotKey));
+        Assert.False (btn.NewKeyDownEvent (btn.HotKey));
         Assert.True (clicked);
         clicked = false;
 
         // IsDefault = false
         // Space and Enter should work
         Assert.False (btn.IsDefault);
-        Assert.True (btn.NewKeyDownEvent (Key.Enter));
+        Assert.False (btn.NewKeyDownEvent (Key.Enter));
         Assert.True (clicked);
         clicked = false;
 
         // IsDefault = true
         // Space and Enter should work
         btn.IsDefault = true;
-        Assert.True (btn.NewKeyDownEvent (Key.Enter));
+        Assert.False (btn.NewKeyDownEvent (Key.Enter));
         Assert.True (clicked);
         clicked = false;
 
         // Toplevel does not handle Enter, so it should get passed on to button
-        Assert.True (Application.Top.NewKeyDownEvent (Key.Enter));
+        Assert.False (Application.Top.NewKeyDownEvent (Key.Enter));
         Assert.True (clicked);
         clicked = false;
 
         // Direct
-        Assert.True (btn.NewKeyDownEvent (Key.Enter));
+        Assert.False (btn.NewKeyDownEvent (Key.Enter));
         Assert.True (clicked);
         clicked = false;
 
-        Assert.True (btn.NewKeyDownEvent (Key.Space));
+        Assert.False (btn.NewKeyDownEvent (Key.Space));
         Assert.True (clicked);
         clicked = false;
 
-        Assert.True (btn.NewKeyDownEvent (new ((KeyCode)'T')));
+        Assert.False (btn.NewKeyDownEvent (new ((KeyCode)'T')));
         Assert.True (clicked);
         clicked = false;
 
         // Change hotkey:
         btn.Text = "Te_st";
-        Assert.True (btn.NewKeyDownEvent (btn.HotKey));
+        Assert.False (btn.NewKeyDownEvent (btn.HotKey));
         Assert.True (clicked);
         clicked = false;
 

+ 2 - 2
UnitTests/Views/DatePickerTests.cs

@@ -82,7 +82,7 @@ public class DatePickerTests
         Assert.Equal (datePicker.Subviews.First (v => v.Id == "_nextMonthButton"), datePicker.Focused);
 
         // Change month to December
-        Assert.True (Application.OnKeyDown (Key.Enter));
+        Assert.False (Application.OnKeyDown (Key.Enter));
         Assert.Equal (12, datePicker.Date.Month);
 
         // Next month button is disabled, so focus advanced to edit field
@@ -111,7 +111,7 @@ public class DatePickerTests
         Assert.Equal (datePicker.Subviews.First (v => v.Id == "_previousMonthButton"), datePicker.Focused);
 
         // Change month to January 
-        Assert.True (datePicker.NewKeyDownEvent (Key.Enter));
+        Assert.False (datePicker.NewKeyDownEvent (Key.Enter));
         Assert.Equal (1, datePicker.Date.Month);
 
         // Next prev button is disabled, so focus advanced to edit button

+ 37 - 6
UnitTests/Views/RadioGroupTests.cs

@@ -55,7 +55,7 @@ public class RadioGroupTests (ITestOutputHelper output)
         rg.SetFocus ();
 
         Assert.Equal (-1, rg.SelectedItem);
-        Assert.True (Application.OnKeyDown (Key.Space));
+        Application.OnKeyDown (Key.Space);
         Assert.Equal (0, rg.SelectedItem);
 
         Application.Top.Dispose ();
@@ -84,23 +84,54 @@ public class RadioGroupTests (ITestOutputHelper output)
         Application.Top.Add (rg);
         rg.SetFocus ();
         Assert.Equal (Orientation.Vertical, rg.Orientation);
+
+        // By default the first item is selected
         Assert.Equal (0, rg.SelectedItem);
+
+        // Test up/down without Select
         Assert.False (Application.OnKeyDown (Key.CursorUp)); // Should not change (should focus prev view if there was one, which there isn't)
         Assert.Equal (0, rg.SelectedItem);
+        Assert.Equal (0, rg.Cursor);
         Assert.True (Application.OnKeyDown (Key.CursorDown));
+        Assert.Equal (0, rg.SelectedItem); // Cursor changed, but selection didnt
+        Assert.Equal (1, rg.Cursor);
+        Assert.False (Application.OnKeyDown (Key.CursorDown)); // Should not change (should focus next view if there was one, which there isn't)
+        Assert.Equal (0, rg.SelectedItem);
+        Assert.Equal (1, rg.Cursor);
+
+        // Now test Select (Space) when Cursor != SelectedItem
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
-        Assert.False (Application.OnKeyDown (Key.CursorDown)); // Should not change (should focus prev view if there was one, which there isn't)
+        Assert.Equal (1, rg.Cursor);
+
+        // Now test Select (Space) when Cursor == SelectedItem - Should cycle
+        Assert.True (Application.OnKeyDown (Key.Space));
+        Assert.Equal (0, rg.SelectedItem);
+        Assert.Equal (0, rg.Cursor);
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
-        Assert.True (Application.OnKeyDown (Key.Home));
+        Assert.Equal (1, rg.Cursor);
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (0, rg.SelectedItem);
-        Assert.True (Application.OnKeyDown (Key.End));
+        Assert.Equal (0, rg.Cursor);
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
+        Assert.Equal (1, rg.Cursor);
+
+        Assert.True (Application.OnKeyDown (Key.Home));
+        Assert.Equal (1, rg.SelectedItem);
+        Assert.Equal (0, rg.Cursor);
+        Assert.True (Application.OnKeyDown (Key.Space));
+        Assert.Equal (0, rg.SelectedItem);
+        Assert.Equal (0, rg.Cursor);
+
+        Assert.True (Application.OnKeyDown (Key.End));
+        Assert.Equal (0, rg.SelectedItem);
+        Assert.Equal (1, rg.Cursor);
         Assert.True (Application.OnKeyDown (Key.Space));
         Assert.Equal (1, rg.SelectedItem);
+        Assert.Equal (1, rg.Cursor);
+
         Application.ResetState (ignoreDisposed: true);
     }
 
@@ -184,7 +215,7 @@ public class RadioGroupTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void HotKey_For_Item_SetsFocus ()
+    public void HotKey_For_Item_Does_Not_SetFocus ()
     {
         var superView = new View
         {
@@ -200,7 +231,7 @@ public class RadioGroupTests (ITestOutputHelper output)
         group.NewKeyDownEvent (Key.R);
 
         Assert.Equal (1, group.SelectedItem);
-        Assert.True (group.HasFocus);
+        Assert.False (group.HasFocus);
     }
 
     [Fact]

+ 3 - 1
docfx/docs/migratingfromv1.md

@@ -247,6 +247,7 @@ In v2, the API is (NOT YET IMPLEMENTED) simplified. A view simply reports the st
 See [navigation.md](navigation.md) for more details.
 See also [Keyboard](keyboard.md) where HotKey is covered more deeply...
 
+* In v1, `View.CanFocus` was `true` by default. In v2, it is `false`. Any `View` subclass that wants to be focusable must set `CanFocus = true`.
 * In v1 it was not possible to remove focus from a view. `HasFocus` as a get-only property. In v2, `view.HasFocus` can be set as well. Setting to `true` is equivalent to calling `view.SetFocus`. Setting to `false` is equivalent to calling `view.SuperView.AdvanceFocus` (which might not actually cause `view` to stop having focus). 
 * In v1, calling `super.Add (view)` where `view.CanFocus == true` caused all views up the hierarchy (all SuperViews) to get `CanFocus` set to `true` as well. In v2, developers need to explicitly set `CanFocus` for any view in the view-hierarchy where focus is desired. This simplifies the implementation and removes confusing automatic behavior. 
 * In v1, if `view.CanFocus == true`, `Add` would automatically set `TabStop`. In v2, the automatic setting of `TabStop` in `Add` is retained because it is not overly complex to do so and is a nice convenience for developers to not have to set both `Tabstop` and `CanFocus`. Note v2 does NOT automatically change `CanFocus` if `TabStop` is changed.
@@ -262,8 +263,9 @@ See also [Keyboard](keyboard.md) where HotKey is covered more deeply...
 
 ### How to Fix (Focus API)
 
+* Set @Terminal.Gui.View.CanFocus to `true` for any View sub-class that wants to be focusable.
 * Use @Terminal.Gui.Application.Navigation.GetFocused to get the most focused view in the application.
-*  Use @Terminal.Gui.Application.Navigation.AdvanceFocus to cause focus to change.
+* Use @Terminal.Gui.Application.Navigation.AdvanceFocus to cause focus to change.
 
 ### Keyboard Navigation
 

+ 1 - 1
docfx/docs/navigation.md

@@ -652,7 +652,7 @@ Like `Checkbox` the right thing to do is for Hotkey to NOT set focus. Why? If th
 
 ### `HasFocus`
 
-* `Enter` - `Command.Accept` -> Advances state to selected RadioItem and Raises `Accept` 
+* `Enter` - `Command.Accept` -> Raises `Accept` 
 * `Space` - `Command.Select` -> Advances state
 * `Title.Hotkey` - `Command.Hotkey` -> does nothing
 * `RadioItem.Hotkey` - `Command.Select` -> Advance State to RadioItem with hotkey.