diff --git a/Terminal.Gui/Input/Bindings.cs b/Terminal.Gui/Input/Bindings.cs
new file mode 100644
index 000000000..6894ebfd4
--- /dev/null
+++ b/Terminal.Gui/Input/Bindings.cs
@@ -0,0 +1,150 @@
+#nullable enable
+using System;
+
+namespace Terminal.Gui;
+
+///
+/// Abstract base class for and .
+///
+/// The type of the event (e.g. or ).
+/// The binding type (e.g. ).
+public abstract class Bindings where TBinding : IInputBinding, new() where TEvent : notnull
+{
+ ///
+ /// The bindings.
+ ///
+ protected readonly Dictionary _bindings;
+
+ private readonly Func _constructBinding;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ protected Bindings (Func constructBinding, IEqualityComparer equalityComparer)
+ {
+ _constructBinding = constructBinding;
+ _bindings = new (equalityComparer);
+ }
+
+ /// Adds a bound to to the collection.
+ ///
+ ///
+ public void Add (TEvent eventArgs, TBinding binding)
+ {
+ if (TryGet (eventArgs, out TBinding _))
+ {
+ throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
+ }
+
+ // IMPORTANT: Add a COPY of the mouseEventArgs. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy
+ // IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus
+ // IMPORTANT: Apply will update the Dictionary with the new mouseEventArgs, but the old mouseEventArgs will still be in the dictionary.
+ // IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details.
+ _bindings.Add (eventArgs, binding);
+ }
+
+
+ /// Gets the commands bound with the specified .
+ ///
+ /// The args to check.
+ ///
+ /// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are
+ /// found; otherwise, null. This parameter is passed uninitialized.
+ ///
+ /// if the mouse flags are bound; otherwise .
+ public bool TryGet (TEvent eventArgs, out TBinding? binding)
+ {
+ return _bindings.TryGetValue (eventArgs, out binding);
+ }
+
+
+ ///
+ /// Adds a new mouse flag combination that will trigger the commands in .
+ ///
+ /// If the key is already bound to a different array of s it will be rebound
+ /// .
+ ///
+ ///
+ ///
+ /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
+ /// focus to another view and perform multiple commands there).
+ ///
+ /// The mouse flags to check.
+ ///
+ /// The command to invoked on the when is received. When
+ /// multiple commands are provided,they will be applied in sequence. The bound event
+ /// will be
+ /// consumed if any took effect.
+ ///
+ public void Add (TEvent eventArgs, params Command [] commands)
+ {
+ if (commands.Length == 0)
+ {
+ throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+ }
+
+ if (TryGet (eventArgs, out var binding))
+ {
+ throw new InvalidOperationException (@$"A binding for {eventArgs} exists ({binding}).");
+ }
+
+ Add (eventArgs, _constructBinding(commands,eventArgs));
+ }
+
+ ///
+ /// Gets the bindings.
+ ///
+ ///
+ public IEnumerable> GetBindings ()
+ {
+ return _bindings;
+ }
+
+ /// Removes all objects from the collection.
+ public void Clear () { _bindings.Clear (); }
+
+ ///
+ /// Removes all bindings that trigger the given command set. Views can have multiple different events bound to
+ /// the same command sets and this method will clear all of them.
+ ///
+ ///
+ public void Clear (params Command [] command)
+ {
+ KeyValuePair [] kvps = _bindings
+ .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
+ .ToArray ();
+
+ foreach (KeyValuePair kvp in kvps)
+ {
+ Remove (kvp.Key);
+ }
+ }
+
+ /// Gets the for the specified .
+ ///
+ ///
+ public TBinding? Get (TEvent eventArgs)
+ {
+ if (TryGet (eventArgs, out var binding))
+ {
+ return binding;
+ }
+
+ throw new InvalidOperationException ($"{eventArgs} is not bound.");
+ }
+
+
+ /// Removes a from the collection.
+ ///
+ public void Remove (TEvent mouseEventArgs)
+ {
+ if (!TryGet (mouseEventArgs, out var _))
+ {
+ return;
+ }
+
+ _bindings.Remove (mouseEventArgs);
+ }
+}
diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs
index 61c21f4e6..e4fdfba68 100644
--- a/Terminal.Gui/Input/CommandContext.cs
+++ b/Terminal.Gui/Input/CommandContext.cs
@@ -7,14 +7,14 @@ namespace Terminal.Gui;
///
/// .
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-public record struct CommandContext : ICommandContext
+public record struct CommandContext : ICommandContext
{
///
/// Initializes a new instance with the specified ,
///
///
///
- public CommandContext (Command command, TBindingType? binding)
+ public CommandContext (Command command, TBinding? binding)
{
Command = command;
Binding = binding;
@@ -26,5 +26,5 @@ public record struct CommandContext : ICommandContext
///
/// The keyboard or mouse minding that was used to invoke the , if any.
///
- public TBindingType? Binding { get; set; }
+ public TBinding? Binding { get; set; }
}
\ No newline at end of file
diff --git a/Terminal.Gui/Input/Mouse/IInputBinding.cs b/Terminal.Gui/Input/IInputBinding.cs
similarity index 100%
rename from Terminal.Gui/Input/Mouse/IInputBinding.cs
rename to Terminal.Gui/Input/IInputBinding.cs
diff --git a/Terminal.Gui/Input/Keyboard/Key.cs b/Terminal.Gui/Input/Keyboard/Key.cs
index 84f42ee3f..3fece5c1a 100644
--- a/Terminal.Gui/Input/Keyboard/Key.cs
+++ b/Terminal.Gui/Input/Keyboard/Key.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -69,6 +70,7 @@ namespace Terminal.Gui;
///
///
///
+[DefaultValue(KeyCode.Null)]
public class Key : EventArgs, IEquatable
{
/// Constructs a new
diff --git a/Terminal.Gui/Input/Keyboard/KeyBindings.cs b/Terminal.Gui/Input/Keyboard/KeyBindings.cs
index ed3585c57..7b68e1b43 100644
--- a/Terminal.Gui/Input/Keyboard/KeyBindings.cs
+++ b/Terminal.Gui/Input/Keyboard/KeyBindings.cs
@@ -7,14 +7,15 @@ namespace Terminal.Gui;
///
///
///
-public class KeyBindings : Bindings
+public class KeyBindings : Bindings
{
/// Initializes a new instance bound to .
- public KeyBindings (View? target) :base(
- (commands,key)=> new KeyBinding (commands),
- new KeyEqualityComparer ()) { Target = target; }
-
-
+ public KeyBindings (View? target) : base (
+ (commands, key) => new (commands),
+ new KeyEqualityComparer ())
+ {
+ Target = target;
+ }
///
///
@@ -29,11 +30,14 @@ public class KeyBindings : Bindings
///
///
/// The key to check.
- /// The View the commands will be invoked on. If , the key will be bound to .
+ ///
+ /// The View the commands will be invoked on. If , the key will be bound to
+ /// .
+ ///
///
/// The command to invoked on the when is pressed. When
/// multiple commands are provided,they will be applied in sequence. The bound strike will be
- /// consumed if any took effect.
+ /// consumed if any took effect.
///
public void Add (Key key, View? target, params Command [] commands)
{
@@ -45,10 +49,7 @@ public class KeyBindings : Bindings
/// Gets the bindings.
///
///
- public IEnumerable> GetBindings ()
- {
- return _bindings;
- }
+ public IEnumerable> GetBindings () { return _bindings; }
///
/// Gets the keys that are bound.
@@ -179,10 +180,10 @@ public class KeyBindings : Bindings
if (newKey == Key.Empty)
{
Remove (oldKey);
+
return;
}
-
if (TryGet (oldKey, out KeyBinding binding))
{
Remove (oldKey);
diff --git a/Terminal.Gui/Input/Mouse/MouseBindings.cs b/Terminal.Gui/Input/Mouse/MouseBindings.cs
index 46ca72726..5300fccfd 100644
--- a/Terminal.Gui/Input/Mouse/MouseBindings.cs
+++ b/Terminal.Gui/Input/Mouse/MouseBindings.cs
@@ -1,162 +1,20 @@
#nullable enable
-using System.Collections;
-using System.Collections.Generic;
-
namespace Terminal.Gui;
-public abstract class Bindings where TBind : IInputBinding, new()
-{
- protected readonly Dictionary _bindings;
- private readonly Func _constructBinding;
-
- protected Bindings (Func constructBinding, IEqualityComparer equalityComparer)
- {
- _constructBinding = constructBinding;
- _bindings = new (equalityComparer);
- }
-
- /// Adds a to the collection.
- ///
- ///
- public void Add (TKey mouseEventArgs, TBind binding)
- {
- if (TryGet (mouseEventArgs, out TBind _))
- {
- throw new InvalidOperationException (@$"A binding for {mouseEventArgs} exists ({binding}).");
- }
-
- // IMPORTANT: Add a COPY of the mouseEventArgs. This is needed because ConfigurationManager.Apply uses DeepMemberWiseCopy
- // IMPORTANT: update the memory referenced by the key, and Dictionary uses caching for performance, and thus
- // IMPORTANT: Apply will update the Dictionary with the new mouseEventArgs, but the old mouseEventArgs will still be in the dictionary.
- // IMPORTANT: See the ConfigurationManager.Illustrate_DeepMemberWiseCopy_Breaks_Dictionary test for details.
- _bindings.Add (mouseEventArgs, binding);
- }
-
-
- /// Gets the commands bound with the specified .
- ///
- /// The key to check.
- ///
- /// When this method returns, contains the commands bound with the specified mouse flags, if the mouse flags are
- /// found; otherwise, null. This parameter is passed uninitialized.
- ///
- /// if the mouse flags are bound; otherwise .
- public bool TryGet (TKey mouseEventArgs, out TBind? binding)
- {
- return _bindings.TryGetValue (mouseEventArgs, out binding);
- }
-
-
- ///
- /// Adds a new mouse flag combination that will trigger the commands in .
- ///
- /// If the key is already bound to a different array of s it will be rebound
- /// .
- ///
- ///
- ///
- /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
- /// focus to another view and perform multiple commands there).
- ///
- /// The mouse flags to check.
- ///
- /// The command to invoked on the when is received. When
- /// multiple commands are provided,they will be applied in sequence. The bound event
- /// will be
- /// consumed if any took effect.
- ///
- public void Add (TKey mouseFlags, params Command [] commands)
- {
- if (EqualityComparer.Default.Equals (mouseFlags, default))
- {
- throw new ArgumentException (@"Invalid MouseFlag", nameof (mouseFlags));
- }
-
- if (commands.Length == 0)
- {
- throw new ArgumentException (@"At least one command must be specified", nameof (commands));
- }
-
- if (TryGet (mouseFlags, out var binding))
- {
- throw new InvalidOperationException (@$"A binding for {mouseFlags} exists ({binding}).");
- }
-
- Add (mouseFlags, _constructBinding(commands,mouseFlags));
- }
-
- ///
- /// Gets the bindings.
- ///
- ///
- public IEnumerable> GetBindings ()
- {
- return _bindings;
- }
-
- /// Removes all objects from the collection.
- public void Clear () { _bindings.Clear (); }
-
- ///
- /// Removes all bindings that trigger the given command set. Views can have multiple different events bound to
- /// the same command sets and this method will clear all of them.
- ///
- ///
- public void Clear (params Command [] command)
- {
- KeyValuePair [] kvps = _bindings
- .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
- .ToArray ();
-
- foreach (KeyValuePair kvp in kvps)
- {
- Remove (kvp.Key);
- }
- }
-
- /// Gets the for the specified combination of .
- ///
- ///
- public TBind? Get (TKey mouseEventArgs)
- {
- if (TryGet (mouseEventArgs, out var binding))
- {
- return binding;
- }
-
- throw new InvalidOperationException ($"{mouseEventArgs} is not bound.");
- }
-
-
- /// Removes a from the collection.
- ///
- public void Remove (TKey mouseEventArgs)
- {
- if (!TryGet (mouseEventArgs, out var _))
- {
- return;
- }
-
- _bindings.Remove (mouseEventArgs);
- }
-}
-
///
/// Provides a collection of objects bound to a combination of .
///
///
///
-public class MouseBindings : Bindings
+public class MouseBindings : Bindings
{
///
- /// Initializes a new instance. This constructor is used when the are not bound to a
- /// . This is used for Application.MouseBindings and unit tests.
+ /// Initializes a new instance.
///
- public MouseBindings ():base(
- (commands, flags)=> new MouseBinding (commands, flags),
- EqualityComparer.Default) { }
-
-
+ public MouseBindings () : base (
+ (commands, flags) => new (commands, flags),
+ EqualityComparer.Default)
+ { }
///
/// Gets combination of bound to the set of commands specified by
@@ -209,7 +67,6 @@ public class MouseBindings : Bindings
return _bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
}
-
/// Replaces the commands already bound to a combination of .
///
///
@@ -245,7 +102,6 @@ public class MouseBindings : Bindings
throw new ArgumentException (@"Invalid MouseFlag", nameof (newMouseFlags));
}
-
if (TryGet (oldMouseFlags, out MouseBinding binding))
{
Remove (oldMouseFlags);
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index 32b51cc5f..623ccc4b8 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -59,6 +59,7 @@ public class RadioGroup : View, IDesignable, IOrientation
AddCommand (Command.HotKey,
ctx =>
{
+ // If the command did not come from a keyboard event, ignore it
if (ctx is not CommandContext keyCommandContext)
{
return false;
@@ -66,10 +67,9 @@ public class RadioGroup : View, IDesignable, IOrientation
var item = keyCommandContext.Binding.Data as int?;
-
if (HasFocus)
{
- if (keyCommandContext is { Binding : { } } && (keyCommandContext.Binding.Target != this || HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift))
+ if (keyCommandContext is { Binding : { } } && (item is null || HotKey == keyCommandContext.Binding.Key?.NoAlt.NoCtrl.NoShift))
{
// It's this.HotKey OR Another View (Label?) forwarded the hotkey command to us - Act just like `Space` (Select)
return InvokeCommand (Command.Select);
diff --git a/UnitTests/Input/Keyboard/KeyBindingsTests.cs b/UnitTests/Input/Keyboard/KeyBindingsTests.cs
index 8e39cb96d..70be3dc11 100644
--- a/UnitTests/Input/Keyboard/KeyBindingsTests.cs
+++ b/UnitTests/Input/Keyboard/KeyBindingsTests.cs
@@ -27,13 +27,13 @@ public class KeyBindingsTests ()
Assert.Contains (Command.Left, resultCommands);
}
- [Fact]
- public void Add_Invalid_Key_Throws ()
- {
- var keyBindings = new KeyBindings (new View ());
- List commands = new ();
- Assert.Throws (() => keyBindings.Add (Key.Empty, Command.Accept));
- }
+ //[Fact]
+ //public void Add_Invalid_Key_Throws ()
+ //{
+ // var keyBindings = new KeyBindings (new View ());
+ // List commands = new ();
+ // Assert.Throws (() => keyBindings.Add (Key.Empty, Command.Accept));
+ //}
[Fact]
public void Add_Multiple_Commands_Adds ()
diff --git a/UnitTests/Input/Mouse/MouseBindingsTests.cs b/UnitTests/Input/Mouse/MouseBindingsTests.cs
index 9db8583ea..d8efd8f6a 100644
--- a/UnitTests/Input/Mouse/MouseBindingsTests.cs
+++ b/UnitTests/Input/Mouse/MouseBindingsTests.cs
@@ -23,13 +23,13 @@ public class MouseBindingsTests
Assert.Contains (Command.Left, resultCommands);
}
- [Fact]
- public void Add_Invalid_Flag_Throws ()
- {
- var mouseBindings = new MouseBindings ();
- List commands = new ();
- Assert.Throws (() => mouseBindings.Add (MouseFlags.None, Command.Accept));
- }
+ //[Fact]
+ //public void Add_Invalid_Flag_Throws ()
+ //{
+ // var mouseBindings = new MouseBindings ();
+ // List commands = new ();
+ // Assert.Throws (() => mouseBindings.Add (MouseFlags.None, Command.Accept));
+ //}
[Fact]
public void Add_Multiple_Commands_Adds ()