Selaa lähdekoodia

Merge branch 'v2_develop' into v2_3521-DimAuto-Equality

Tig 11 kuukautta sitten
vanhempi
commit
29dc789d45
45 muutettua tiedostoa jossa 1259 lisäystä ja 681 poistoa
  1. 22 0
      NativeAot/NativeAot.csproj
  2. 113 0
      NativeAot/Program.cs
  3. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml
  4. 18 0
      NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml
  5. 5 0
      NativeAot/Publish_linux-x64_Debug.sh
  6. 5 0
      NativeAot/Publish_linux-x64_Release.sh
  7. 5 0
      NativeAot/Publish_osx-x64_Debug.sh
  8. 5 0
      NativeAot/Publish_osx-x64_Release.sh
  9. 10 0
      NativeAot/README.md
  10. 0 5
      Terminal.Gui/Application/Application.Keyboard.cs
  11. 6 6
      Terminal.Gui/Configuration/SourceGenerationContext.cs
  12. 16 1
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  13. 2 1
      Terminal.Gui/Drawing/Alignment.cs
  14. 2 1
      Terminal.Gui/Drawing/AlignmentModes.cs
  15. 1 1
      Terminal.Gui/Drawing/Color.Formatting.cs
  16. 3 0
      Terminal.Gui/Drawing/LineStyle.cs
  17. 21 4
      Terminal.Gui/Input/Key.cs
  18. 2 2
      Terminal.Gui/Input/ShortcutHelper.cs
  19. 1 0
      Terminal.Gui/Resources/config.json
  20. 4 1
      Terminal.Gui/View/Adornment/ShadowStyle.cs
  21. 0 4
      Terminal.Gui/Views/Button.cs
  22. 18 15
      Terminal.Gui/Views/Dialog.cs
  23. 1 4
      Terminal.Gui/Views/FrameView.cs
  24. 23 14
      Terminal.Gui/Views/Menu/Menu.cs
  25. 27 24
      Terminal.Gui/Views/Menu/MenuBar.cs
  26. 74 5
      Terminal.Gui/Views/Menu/MenuBarItem.cs
  27. 170 76
      Terminal.Gui/Views/Menu/MenuItem.cs
  28. 1 16
      Terminal.Gui/Views/MessageBox.cs
  29. 1 4
      Terminal.Gui/Views/Window.cs
  30. 6 0
      Terminal.sln
  31. 1 1
      UICatalog/Scenarios/ContextMenus.cs
  32. 1 0
      UICatalog/Scenarios/Dialogs.cs
  33. 310 348
      UICatalog/Scenarios/DynamicMenuBar.cs
  34. 10 81
      UICatalog/Scenarios/DynamicStatusBar.cs
  35. 1 1
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  36. 11 11
      UICatalog/UICatalog.cs
  37. 40 40
      UnitTests/Configuration/ConfigurationMangerTests.cs
  38. 108 0
      UnitTests/Configuration/SerializableConfigurationPropertyTests.cs
  39. 9 4
      UnitTests/Configuration/ThemeScopeTests.cs
  40. 3 0
      UnitTests/Configuration/ThemeTests.cs
  41. 17 1
      UnitTests/Input/KeyTests.cs
  42. 72 0
      UnitTests/TestHelpers.cs
  43. 91 6
      UnitTests/Views/MenuBarTests.cs
  44. 2 2
      UnitTests/Views/MenuTests.cs
  45. 3 2
      UnitTests/Views/NumericUpDownTests.cs

+ 22 - 0
NativeAot/NativeAot.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <PublishAot>true</PublishAot>
+    <InvariantGlobalization>false</InvariantGlobalization>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+    <TrimmerRootAssembly Include="Terminal.Gui" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <PackageReference Include="Terminal.Gui" Version="[2.0.0-pre.1788,3)" />
+    <TrimmerRootAssembly Include="Terminal.Gui" />
+  </ItemGroup>
+
+</Project>

+ 113 - 0
NativeAot/Program.cs

@@ -0,0 +1,113 @@
+// This is a test application for a native Aot file.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using Terminal.Gui;
+
+namespace NativeAot;
+
+public static class Program
+{
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
+    private static void Main (string [] args)
+    {
+        Application.Init ();
+
+        #region The code in this region is not intended for use in a native Aot self-contained. It's just here to make sure there is no functionality break with localization in Terminal.Gui using self-contained
+
+        if (Equals(Thread.CurrentThread.CurrentUICulture, CultureInfo.InvariantCulture) && Application.SupportedCultures.Count == 0)
+        {
+            // Only happens if the project has <InvariantGlobalization>true</InvariantGlobalization>
+            Debug.Assert (Application.SupportedCultures.Count == 0);
+        }
+        else
+        {
+            Debug.Assert (Application.SupportedCultures.Count > 0);
+            Debug.Assert (Equals (CultureInfo.CurrentCulture, Thread.CurrentThread.CurrentUICulture));
+        }
+
+        #endregion
+
+        ExampleWindow app = new ();
+        Application.Run (app);
+
+        // Dispose the app object before shutdown
+        app.Dispose ();
+
+        // Before the application exits, reset Terminal.Gui for clean shutdown
+        Application.Shutdown ();
+
+        // To see this output on the screen it must be done after shutdown,
+        // which restores the previous screen.
+        Console.WriteLine ($@"Username: {ExampleWindow.UserName}");
+    }
+}
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window
+{
+    public static string? UserName;
+
+    public ExampleWindow ()
+    {
+        Title = $"Example App ({Application.QuitKey} to quit)";
+
+        // Create input components and labels
+        var usernameLabel = new Label { Text = "Username:" };
+
+        var userNameText = new TextField
+        {
+            // Position text field adjacent to the label
+            X = Pos.Right (usernameLabel) + 1,
+
+            // Fill remaining horizontal space
+            Width = Dim.Fill ()
+        };
+
+        var passwordLabel = new Label
+        {
+            Text = "Password:", X = Pos.Left (usernameLabel), Y = Pos.Bottom (usernameLabel) + 1
+        };
+
+        var passwordText = new TextField
+        {
+            Secret = true,
+
+            // align with the text box above
+            X = Pos.Left (userNameText),
+            Y = Pos.Top (passwordLabel),
+            Width = Dim.Fill ()
+        };
+
+        // Create login button
+        var btnLogin = new Button
+        {
+            Text = "Login",
+            Y = Pos.Bottom (passwordLabel) + 1,
+
+            // center the login button horizontally
+            X = Pos.Center (),
+            IsDefault = true
+        };
+
+        // When login button is clicked display a message popup
+        btnLogin.Accept += (s, e) =>
+        {
+            if (userNameText.Text == "admin" && passwordText.Text == "password")
+            {
+                MessageBox.Query ("Logging In", "Login Successful", "Ok");
+                UserName = userNameText.Text;
+                Application.RequestStop ();
+            }
+            else
+            {
+                MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+            }
+        };
+
+        // Add the views to the Window
+        Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
+    }
+}

+ 18 - 0
NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Debug.pubxml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Debug</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Debug\net8.0\publish\win-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>false</PublishSingleFile>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 18 - 0
NativeAot/Properties/PublishProfiles/FolderProfile_net8.0_win-x64_Release.pubxml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Release\net8.0\publish\win-x64\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+    <_TargetId>Folder</_TargetId>
+    <TargetFramework>net8.0</TargetFramework>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
+    <SelfContained>true</SelfContained>
+    <PublishSingleFile>false</PublishSingleFile>
+    <PublishReadyToRun>false</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 5 - 0
NativeAot/Publish_linux-x64_Debug.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Debug -r linux-x64 --self-contained

+ 5 - 0
NativeAot/Publish_linux-x64_Release.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Release -r linux-x64 --self-contained

+ 5 - 0
NativeAot/Publish_osx-x64_Debug.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Debug -r osx-x64 --self-contained

+ 5 - 0
NativeAot/Publish_osx-x64_Release.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+dotnet clean
+dotnet build
+dotnet publish -c Release -r osx-x64 --self-contained

+ 10 - 0
NativeAot/README.md

@@ -0,0 +1,10 @@
+# Terminal.Gui C# SelfContained
+
+This project aims to test the `Terminal.Gui` library to create a simple `native AOT` `self-container` GUI application in C#, ensuring that all its features are available.
+
+With `Debug` the `.csproj` is used and with `Release` the latest `nuget package` is used, either in `Solution Configurations` or in `Profile Publish` or in the `Publish_linux-x64` or in the `Publish_osx-x64` files.
+Unlike self-contained single-file publishing, native AOT publishing must be generated on the same platform as the target execution version. Therefore, if the target execution is Linux, then the publishing must be generated on a Linux operating system. Attempting to generate on Windows for the Linux target will throw an exception.
+
+To publish the `native AOT` file in `Debug` or `Release` mode, it is not necessary to select it in the `Solution Configurations`, just choose the `Debug` or `Release` configuration in the `Publish Profile` or the `*.sh` files.
+
+When executing the file directly from the `native AOT` file and needing to debug it, it will be necessary to attach it to the debugger, just like any other standalone application and selecting `Native Code`.

+ 0 - 5
Terminal.Gui/Application/Application.Keyboard.cs

@@ -9,7 +9,6 @@ public static partial class Application // Keyboard handling
 
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key NextTabKey
     {
         get => _nextTabKey;
@@ -27,7 +26,6 @@ public static partial class Application // Keyboard handling
 
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key PrevTabKey
     {
         get => _prevTabKey;
@@ -45,7 +43,6 @@ public static partial class Application // Keyboard handling
 
     /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key NextTabGroupKey
     {
         get => _nextTabGroupKey;
@@ -63,7 +60,6 @@ public static partial class Application // Keyboard handling
 
     /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key PrevTabGroupKey
     {
         get => _prevTabGroupKey;
@@ -81,7 +77,6 @@ public static partial class Application // Keyboard handling
 
     /// <summary>Gets or sets the key to quit the application.</summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-    [JsonConverter (typeof (KeyJsonConverter))]
     public static Key QuitKey
     {
         get => _quitKey;

+ 6 - 6
Terminal.Gui/Configuration/SourceGenerationContext.cs

@@ -7,17 +7,17 @@ namespace Terminal.Gui;
 /// </summary>
 [JsonSerializable (typeof (Attribute))]
 [JsonSerializable (typeof (Color))]
-[JsonSerializable (typeof (ThemeScope))]
-[JsonSerializable (typeof (ColorScheme))]
-[JsonSerializable (typeof (SettingsScope))]
 [JsonSerializable (typeof (AppScope))]
+[JsonSerializable (typeof (SettingsScope))]
 [JsonSerializable (typeof (Key))]
 [JsonSerializable (typeof (GlyphDefinitions))]
-[JsonSerializable (typeof (ConfigProperty))]
+[JsonSerializable (typeof (Alignment))]
+[JsonSerializable (typeof (AlignmentModes))]
+[JsonSerializable (typeof (LineStyle))]
 [JsonSerializable (typeof (ShadowStyle))]
-[JsonSerializable (typeof (string))]
-[JsonSerializable (typeof (bool))]
 [JsonSerializable (typeof (bool?))]
 [JsonSerializable (typeof (Dictionary<ColorName, string>))]
+[JsonSerializable (typeof (Dictionary<string, ThemeScope>))]
+[JsonSerializable (typeof (Dictionary<string, ColorScheme>))]
 internal partial class SourceGenerationContext : JsonSerializerContext
 { }

+ 16 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -2343,7 +2343,22 @@ internal class WindowsClipboard : ClipboardBase
 {
     private const uint CF_UNICODE_TEXT = 13;
 
-    public override bool IsSupported { get; } = IsClipboardFormatAvailable (CF_UNICODE_TEXT);
+    public override bool IsSupported { get; } = CheckClipboardIsAvailable ();
+
+    private static bool CheckClipboardIsAvailable ()
+    {
+        // Attempt to open the clipboard
+        if (OpenClipboard (nint.Zero))
+        {
+            // Clipboard is available
+            // Close the clipboard after use
+            CloseClipboard ();
+
+            return true;
+        }
+        // Clipboard is not available
+        return false;
+    }
 
     protected override string GetClipboardDataImpl ()
     {

+ 2 - 1
Terminal.Gui/Drawing/Alignment.cs

@@ -1,10 +1,11 @@
-
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
 /// <summary>
 ///     Determines the position of items when arranged in a container.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
 public enum Alignment
 {
     /// <summary>

+ 2 - 1
Terminal.Gui/Drawing/AlignmentModes.cs

@@ -1,10 +1,11 @@
-
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
 /// <summary>
 ///     Determines alignment modes for <see cref="Alignment"/>.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
 [Flags]
 public enum AlignmentModes
 {

+ 1 - 1
Terminal.Gui/Drawing/Color.Formatting.cs

@@ -284,7 +284,7 @@ public readonly partial record struct Color
                                                                                               ),
 
                    // Any string too short to possibly be any supported format.
-                   { Length: > 0 and < 4 } => throw new ColorParseException (
+                   { Length: > 0 and < 3 } => throw new ColorParseException (
                                                                              in text,
                                                                              "Text was too short to be any possible supported format.",
                                                                              in text

+ 3 - 0
Terminal.Gui/Drawing/LineStyle.cs

@@ -1,7 +1,10 @@
 #nullable enable
+using System.Text.Json.Serialization;
+
 namespace Terminal.Gui;
 
 /// <summary>Defines the style of lines for a <see cref="LineCanvas"/>.</summary>
+[JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
 public enum LineStyle
 {
     /// <summary>No border is drawn.</summary>

+ 21 - 4
Terminal.Gui/Input/Key.cs

@@ -1,5 +1,6 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
@@ -448,9 +449,9 @@ public class Key : EventArgs, IEquatable<Key>
 
     #region String conversion
 
-    /// <summary>Pretty prints the KeyEvent</summary>
+    /// <summary>Pretty prints the Key.</summary>
     /// <returns></returns>
-    public override string ToString () { return ToString (KeyCode, (Rune)'+'); }
+    public override string ToString () { return ToString (KeyCode, Separator); }
 
     private static string GetKeyString (KeyCode key)
     {
@@ -483,7 +484,7 @@ public class Key : EventArgs, IEquatable<Key>
     ///     The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
     ///     name will be returned.
     /// </returns>
-    public static string ToString (KeyCode key) { return ToString (key, (Rune)'+'); }
+    public static string ToString (KeyCode key) { return ToString (key, Separator); }
 
     /// <summary>Formats a <see cref="KeyCode"/> as a string.</summary>
     /// <param name="key">The key to format.</param>
@@ -584,7 +585,7 @@ public class Key : EventArgs, IEquatable<Key>
         key = null;
 
         // Split the string into parts
-        string [] parts = text.Split ('+', '-');
+        string [] parts = text.Split ('+', '-', (char)Separator.Value);
 
         if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
         {
@@ -971,4 +972,20 @@ public class Key : EventArgs, IEquatable<Key>
     public static Key F24 => new (KeyCode.F24);
 
     #endregion
+
+    private static Rune _separator = new ('+');
+
+    /// <summary>Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static Rune Separator
+    {
+        get => _separator;
+        set
+        {
+            if (_separator != value)
+            {
+                _separator = value == default (Rune) ? new ('+') : value;
+            }
+        }
+    }
 }

+ 2 - 2
Terminal.Gui/Input/ShortcutHelper.cs

@@ -23,7 +23,7 @@ public class ShortcutHelper
     }
 
     /// <summary>The keystroke combination used in the <see cref="Shortcut"/> as string.</summary>
-    public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter);
+    public virtual string ShortcutTag => Key.ToString (shortcut, Key.Separator);
 
     /// <summary>Lookup for a <see cref="KeyCode"/> on range of keys.</summary>
     /// <param name="key">The source key.</param>
@@ -59,7 +59,7 @@ public class ShortcutHelper
         //var hasCtrl = false;
         if (delimiter == default (Rune))
         {
-            delimiter = MenuBar.ShortcutDelimiter;
+            delimiter = Key.Separator;
         }
 
         string [] keys = sCut.Split (delimiter.ToString ());

+ 1 - 0
Terminal.Gui/Resources/config.json

@@ -22,6 +22,7 @@
   "Application.NextTabGroupKey": "F6",
   "Application.PrevTabGroupKey": "Shift+F6",
   "Application.QuitKey": "Esc",
+  "Key.Separator": "+",
 
   "Theme": "Default",
   "Themes": [

+ 4 - 1
Terminal.Gui/View/Adornment/ShadowStyle.cs

@@ -1,8 +1,11 @@
-namespace Terminal.Gui;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Defines the style of shadow to be drawn on the right and bottom sides of the <see cref="View"/>.
 /// </summary>
+[JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
 public enum ShadowStyle
 {
     /// <summary>

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

@@ -5,8 +5,6 @@
 //   Miguel de Icaza ([email protected])
 //
 
-using System.Text.Json.Serialization;
-
 namespace Terminal.Gui;
 
 /// <summary>Button is a <see cref="View"/> that provides an item that invokes raises the <see cref="View.Accept"/> event.</summary>
@@ -39,8 +37,6 @@ public class Button : View, IDesignable
     /// Gets or sets whether <see cref="Button"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
-
     public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
 
     /// <summary>Initializes a new instance of <see cref="Button"/>.</summary>

+ 18 - 15
Terminal.Gui/Views/Dialog.cs

@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains
@@ -19,13 +17,11 @@ public class Dialog : Window
     /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
     public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End; // Default is set in config.json
 
-    /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
+    /// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<AlignmentModes>))]
     public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
 
     /// <summary>
@@ -47,7 +43,6 @@ public class Dialog : Window
     /// Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<ShadowStyle>))]
     public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None; // Default is set in config.json
 
     /// <summary>
@@ -56,7 +51,6 @@ public class Dialog : Window
     /// </summary>
 
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json
 
     private readonly List<Button> _buttons = new ();
@@ -99,6 +93,22 @@ public class Dialog : Window
         KeyBindings.Add (Key.Esc, Command.QuitToplevel);
     }
 
+    // BUGBUG: We override GetNormal/FocusColor because "Dialog" ColorScheme is goofy.
+    // BUGBUG: By defn, a Dialog is Modal, and thus HasFocus is always true. OnDrawContent
+    // BUGBUG: Calls these methods.
+    // TODO: Fix this in https://github.com/gui-cs/Terminal.Gui/issues/2381
+    /// <inheritdoc />
+    public override Attribute GetNormalColor ()
+    {
+        return ColorScheme.Normal;
+    }
+
+    /// <inheritdoc />
+    public override Attribute GetFocusColor ()
+    {
+        return ColorScheme.Normal;
+    }
+
     private bool _canceled;
 
     /// <summary>Gets a value indicating whether the <see cref="Dialog"/> was canceled.</summary>
@@ -172,12 +182,5 @@ public class Dialog : Window
 
         _buttons.Add (button);
         Add (button);
-
-        SetNeedsDisplay ();
-
-        if (IsInitialized)
-        {
-            LayoutSubviews ();
-        }
     }
 }

+ 1 - 4
Terminal.Gui/Views/FrameView.cs

@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     The FrameView is a container View with a border around it. 
@@ -38,6 +36,5 @@ public class FrameView : View
     ///     <see cref="FrameView"/>s.
     /// </remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
 }

+ 23 - 14
Terminal.Gui/Views/Menu/Menu.cs

@@ -120,7 +120,7 @@ internal sealed class Menu : View
                     }
                    );
 
-        AddKeyBindings (_barItems);
+        AddKeyBindingsHotKey (_barItems);
     }
 
     public Menu ()
@@ -179,7 +179,7 @@ internal sealed class Menu : View
         KeyBindings.Add (Key.Enter, Command.Accept);
     }
 
-    private void AddKeyBindings (MenuBarItem menuBarItem)
+    private void AddKeyBindingsHotKey (MenuBarItem menuBarItem)
     {
         if (menuBarItem is null || menuBarItem.Children is null)
         {
@@ -190,23 +190,30 @@ internal sealed class Menu : View
         {
             KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem);
 
-            if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null)
+            if (menuItem.HotKey != Key.Empty)
             {
-                KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value);
-                KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding);
-                KeyBindings.Remove ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask);
-                KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding);
+                KeyBindings.Remove (menuItem.HotKey);
+                KeyBindings.Add (menuItem.HotKey, keyBinding);
+                KeyBindings.Remove (menuItem.HotKey.WithAlt);
+                KeyBindings.Add (menuItem.HotKey.WithAlt, keyBinding);
             }
+        }
+    }
+
+    private void RemoveKeyBindingsHotKey (MenuBarItem menuBarItem)
+    {
+        if (menuBarItem is null || menuBarItem.Children is null)
+        {
+            return;
+        }
 
-            if (menuItem.Shortcut != KeyCode.Null)
+        foreach (MenuItem menuItem in menuBarItem.Children.Where (m => m is { }))
+        {
+            if (menuItem.HotKey != Key.Empty)
             {
-                keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
-                KeyBindings.Remove (menuItem.Shortcut);
-                KeyBindings.Add (menuItem.Shortcut, keyBinding);
+                KeyBindings.Remove (menuItem.HotKey);
+                KeyBindings.Remove (menuItem.HotKey.WithAlt);
             }
-
-            MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
-            AddKeyBindings (subMenu);
         }
     }
 
@@ -910,6 +917,8 @@ internal sealed class Menu : View
 
     protected override void Dispose (bool disposing)
     {
+        RemoveKeyBindingsHotKey (_barItems);
+
         if (Application.Current is { })
         {
             Application.Current.DrawContentComplete -= Current_DrawContentComplete;

+ 27 - 24
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -66,6 +66,8 @@ public class MenuBar : View, IDesignable
     /// <summary>Initializes a new instance of the <see cref="MenuBar"/>.</summary>
     public MenuBar ()
     {
+        MenuItem._menuBar = this;
+
         TabStop = TabBehavior.NoStop;
         X = 0;
         Y = 0;
@@ -122,7 +124,7 @@ public class MenuBar : View, IDesignable
                         return true;
                     }
                    );
-        AddCommand (Command.ToggleExpandCollapse, ctx => Select ((int)ctx.KeyBinding?.Context!));
+        AddCommand (Command.ToggleExpandCollapse, ctx => Select (Menus.IndexOf (ctx.KeyBinding?.Context)));
         AddCommand (Command.Select, ctx => Run ((ctx.KeyBinding?.Context as MenuItem)?.Action));
 
         // Default key bindings for this view
@@ -172,19 +174,23 @@ public class MenuBar : View, IDesignable
             {
                 MenuBarItem menuBarItem = Menus [i];
 
-                if (menuBarItem?.HotKey != default (Rune))
+                if (menuBarItem?.HotKey != Key.Empty)
                 {
-                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, i);
-                    KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value, keyBinding);
-                    keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, i);
-                    KeyBindings.Add ((KeyCode)menuBarItem.HotKey.Value | KeyCode.AltMask, keyBinding);
+                    KeyBindings.Remove (menuBarItem!.HotKey);
+                    KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, menuBarItem);
+                    KeyBindings.Add (menuBarItem!.HotKey, keyBinding);
+                    KeyBindings.Remove (menuBarItem.HotKey.WithAlt);
+                    keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuBarItem);
+                    KeyBindings.Add (menuBarItem.HotKey.WithAlt, keyBinding);
                 }
 
-                if (menuBarItem?.Shortcut != KeyCode.Null)
+                if (menuBarItem?.ShortcutKey != Key.Empty)
                 {
                     // Technically this will never run because MenuBarItems don't have shortcuts
-                    KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, i);
-                    KeyBindings.Add (menuBarItem.Shortcut, keyBinding);
+                    // unless the IsTopLevel is true
+                    KeyBindings.Remove (menuBarItem.ShortcutKey);
+                    KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuBarItem);
+                    KeyBindings.Add (menuBarItem.ShortcutKey, keyBinding);
                 }
 
                 menuBarItem?.AddShortcutKeyBindings (this);
@@ -1255,21 +1261,6 @@ public class MenuBar : View, IDesignable
         }
     }
 
-    private static Rune _shortcutDelimiter = new ('+');
-
-    /// <summary>Sets or gets the shortcut delimiter separator. The default is "+".</summary>
-    public static Rune ShortcutDelimiter
-    {
-        get => _shortcutDelimiter;
-        set
-        {
-            if (_shortcutDelimiter != value)
-            {
-                _shortcutDelimiter = value == default (Rune) ? new ('+') : value;
-            }
-        }
-    }
-
     /// <summary>The specifier character for the hot keys.</summary>
     public new static Rune HotKeySpecifier => (Rune)'_';
 
@@ -1321,6 +1312,10 @@ public class MenuBar : View, IDesignable
         {
             OpenMenu ();
         }
+        else if (Menus [index].IsTopLevel)
+        {
+            Run (Menus [index].Action);
+        }
         else
         {
             Activate (index);
@@ -1766,4 +1761,12 @@ public class MenuBar : View, IDesignable
         ];
         return true;
     }
+
+    /// <inheritdoc />
+    protected override void Dispose (bool disposing)
+    {
+        MenuItem._menuBar = null;
+
+        base.Dispose (disposing);
+    }
 }

+ 74 - 5
Terminal.Gui/Views/Menu/MenuBarItem.cs

@@ -2,7 +2,7 @@ namespace Terminal.Gui;
 
 /// <summary>
 ///     <see cref="MenuBarItem"/> is a menu item on  <see cref="MenuBar"/>. MenuBarItems do not support
-///     <see cref="MenuItem.Shortcut"/>.
+///     <see cref="MenuItem.ShortcutKey"/>.
 /// </summary>
 public class MenuBarItem : MenuItem
 {
@@ -100,11 +100,9 @@ public class MenuBarItem : MenuItem
         {
             // For MenuBar only add shortcuts for submenus
 
-            if (menuItem.Shortcut != KeyCode.Null)
+            if (menuItem.ShortcutKey != Key.Empty)
             {
-                KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
-                menuBar.KeyBindings.Remove (menuItem.Shortcut);
-                menuBar.KeyBindings.Add (menuItem.Shortcut, keyBinding);
+                menuItem.UpdateShortcutKeyBinding (Key.Empty);
             }
 
             SubMenu (menuItem)?.AddShortcutKeyBindings (menuBar);
@@ -176,4 +174,75 @@ public class MenuBarItem : MenuItem
         title ??= string.Empty;
         Title = title;
     }
+
+    /// <summary>
+    /// Add a <see cref="MenuBarItem"/> dynamically into the <see cref="MenuBar"/><c>.Menus</c>.
+    /// </summary>
+    /// <param name="menuItem"></param>
+    public void AddMenuBarItem (MenuItem menuItem = null)
+    {
+        if (menuItem is null)
+        {
+            MenuBarItem [] menus = _menuBar.Menus;
+            Array.Resize (ref menus, menus.Length + 1);
+            menus [^1] = this;
+            _menuBar.Menus = menus;
+        }
+        else
+        {
+            MenuItem [] childrens = Children ?? [];
+            Array.Resize (ref childrens, childrens.Length + 1);
+            childrens [^1] = menuItem;
+            Children = childrens;
+        }
+    }
+
+    /// <inheritdoc />
+    public override void RemoveMenuItem ()
+    {
+        if (Children is { })
+        {
+            foreach (MenuItem menuItem in Children)
+            {
+                if (menuItem.ShortcutKey != Key.Empty)
+                {
+                    // Remove an existent ShortcutKey
+                    _menuBar?.KeyBindings.Remove (menuItem.ShortcutKey);
+                }
+            }
+        }
+
+        if (ShortcutKey != Key.Empty)
+        {
+            // Remove an existent ShortcutKey
+            _menuBar?.KeyBindings.Remove (ShortcutKey);
+        }
+
+        var index = _menuBar!.Menus.IndexOf (this);
+        if (index > -1)
+        {
+            if (_menuBar!.Menus [index].HotKey != Key.Empty)
+            {
+                // Remove an existent HotKey
+                _menuBar?.KeyBindings.Remove (HotKey.WithAlt);
+            }
+
+            _menuBar!.Menus [index] = null;
+        }
+
+        var i = 0;
+
+        foreach (MenuBarItem m in _menuBar.Menus)
+        {
+            if (m != null)
+            {
+                _menuBar.Menus [i] = m;
+                i++;
+            }
+        }
+
+        MenuBarItem [] menus = _menuBar.Menus;
+        Array.Resize (ref menus, menus.Length - 1);
+        _menuBar.Menus = menus;
+    }
 }

+ 170 - 76
Terminal.Gui/Views/Menu/MenuItem.cs

@@ -6,31 +6,25 @@ namespace Terminal.Gui;
 /// </summary>
 public class MenuItem
 {
-    private readonly ShortcutHelper _shortcutHelper;
-    private bool _allowNullChecked;
-    private MenuItemCheckStyle _checkType;
+    internal static MenuBar _menuBar;
 
-    private string _title;
-
-    // TODO: Update to use Key instead of KeyCode
     /// <summary>Initializes a new instance of <see cref="MenuItem"/></summary>
-    public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
+    public MenuItem (Key shortcutKey = null) : this ("", "", null, null, null, shortcutKey) { }
 
-    // TODO: Update to use Key instead of KeyCode
     /// <summary>Initializes a new instance of <see cref="MenuItem"/>.</summary>
     /// <param name="title">Title for the menu item.</param>
     /// <param name="help">Help text to display.</param>
     /// <param name="action">Action to invoke when the menu item is activated.</param>
     /// <param name="canExecute">Function to determine if the action can currently be executed.</param>
     /// <param name="parent">The <see cref="Parent"/> of this menu item.</param>
-    /// <param name="shortcut">The <see cref="Shortcut"/> keystroke combination.</param>
+    /// <param name="shortcutKey">The <see cref="ShortcutKey"/> keystroke combination.</param>
     public MenuItem (
         string title,
         string help,
         Action action,
         Func<bool> canExecute = null,
         MenuItem parent = null,
-        KeyCode shortcut = KeyCode.Null
+        Key shortcutKey = null
     )
     {
         Title = title ?? "";
@@ -38,14 +32,20 @@ public class MenuItem
         Action = action;
         CanExecute = canExecute;
         Parent = parent;
-        _shortcutHelper = new ();
 
-        if (shortcut != KeyCode.Null)
+        if (Parent is { } && Parent.ShortcutKey != Key.Empty)
         {
-            Shortcut = shortcut;
+            Parent.ShortcutKey = Key.Empty;
         }
+        // Setter will ensure Key.Empty if it's null
+        ShortcutKey = shortcutKey;
     }
 
+    private bool _allowNullChecked;
+    private MenuItemCheckStyle _checkType;
+
+    private string _title;
+
     /// <summary>Gets or sets the action to be invoked when the menu item is triggered.</summary>
     /// <value>Method to invoke.</value>
     public Action Action { get; set; }
@@ -104,6 +104,12 @@ public class MenuItem
     /// <value>The help text.</value>
     public string Help { get; set; }
 
+    /// <summary>
+    ///     Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
+    ///     <see cref="CanExecute"/>.
+    /// </summary>
+    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
+
     /// <summary>Gets the parent for this <see cref="MenuItem"/>.</summary>
     /// <value>The parent.</value>
     public MenuItem Parent { get; set; }
@@ -125,46 +131,6 @@ public class MenuItem
         }
     }
 
-    /// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
-    internal bool IsFromSubMenu => Parent != null;
-
-    internal int TitleLength => GetMenuBarItemLength (Title);
-
-    // 
-    // ┌─────────────────────────────┐
-    // │ Quit  Quit UI Catalog  Ctrl+Q │
-    // └─────────────────────────────┘
-    // ┌─────────────────┐
-    // │ ◌ TopLevel Alt+T │
-    // └─────────────────┘
-    // TODO: Replace the `2` literals with named constants 
-    internal int Width => 1
-                          + // space before Title
-                          TitleLength
-                          + 2
-                          + // space after Title - BUGBUG: This should be 1 
-                          (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
-                               ? 2
-                               : 0)
-                          + // check glyph + space 
-                          (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
-                          + // Two spaces before Help
-                          (ShortcutTag.GetColumns () > 0
-                               ? 2 + ShortcutTag.GetColumns ()
-                               : 0); // Pad two spaces before shortcut tag (which are also aligned right)
-
-    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
-    internal bool GetMenuBarItem () { return IsFromSubMenu; }
-
-    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
-    internal MenuItem GetMenuItem () { return this; }
-
-    /// <summary>
-    ///     Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around
-    ///     <see cref="CanExecute"/>.
-    /// </summary>
-    public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
-
     /// <summary>
     ///     Toggle the <see cref="Checked"/> between three states if <see cref="AllowNullChecked"/> is
     ///     <see langword="true"/> or between two states if <see cref="AllowNullChecked"/> is <see langword="false"/>.
@@ -193,6 +159,40 @@ public class MenuItem
         }
     }
 
+    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
+    internal bool GetMenuBarItem () { return IsFromSubMenu; }
+
+    /// <summary>Merely a debugging aid to see the interaction with main.</summary>
+    internal MenuItem GetMenuItem () { return this; }
+
+    /// <summary>Gets if this <see cref="MenuItem"/> is from a sub-menu.</summary>
+    internal bool IsFromSubMenu => Parent != null;
+
+    internal int TitleLength => GetMenuBarItemLength (Title);
+
+    // 
+    // ┌─────────────────────────────┐
+    // │ Quit  Quit UI Catalog  Ctrl+Q │
+    // └─────────────────────────────┘
+    // ┌─────────────────┐
+    // │ ◌ TopLevel Alt+T │
+    // └─────────────────┘
+    // TODO: Replace the `2` literals with named constants
+    internal int Width => 1
+                          + // space before Title
+                          TitleLength
+                          + 2
+                          + // space after Title - BUGBUG: This should be 1
+                          (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
+                               ? 2
+                               : 0)
+                          + // check glyph + space 
+                          (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
+                          + // Two spaces before Help
+                          (ShortcutTag.GetColumns () > 0
+                               ? 2 + ShortcutTag.GetColumns ()
+                               : 0); // Pad two spaces before shortcut tag (which are also aligned right)
+
     private static int GetMenuBarItemLength (string title)
     {
         return title.EnumerateRunes ()
@@ -202,21 +202,32 @@ public class MenuItem
 
     #region Keyboard Handling
 
-    // TODO: Update to use Key instead of Rune
+    private Key _hotKey = Key.Empty;
+
     /// <summary>
     ///     The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the
     ///     <see cref="Title"/> of a MenuItem with an underscore ('_').
     ///     <para>
     ///         Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is
-    ///         not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
+    ///         not active. Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
     ///     </para>
     ///     <para>
     ///         For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
     ///         File menu. Pressing the N key will then activate the New MenuItem.
     ///     </para>
-    ///     <para>See also <see cref="Shortcut"/> which enable global key-bindings to menu items.</para>
+    ///     <para>See also <see cref="ShortcutKey"/> which enable global key-bindings to menu items.</para>
     /// </summary>
-    public Rune HotKey { get; set; }
+    public Key HotKey
+    {
+        get => _hotKey;
+        private set
+        {
+            var oldKey = _hotKey ?? Key.Empty;
+            _hotKey = value ?? Key.Empty;
+            UpdateHotKeyBinding (oldKey);
+        }
+    }
+
     private void GetHotKey ()
     {
         var nextIsHot = false;
@@ -227,47 +238,130 @@ public class MenuItem
             {
                 nextIsHot = true;
             }
-            else
+            else if (nextIsHot)
             {
-                if (nextIsHot)
-                {
-                    HotKey = (Rune)char.ToUpper (x);
+                    HotKey = char.ToLower (x);
 
-                    break;
-                }
-
-                nextIsHot = false;
-                HotKey = default (Rune);
+                    return;
             }
         }
+
+        HotKey = Key.Empty;
     }
 
-    // TODO: Update to use Key instead of KeyCode
+    private Key _shortcutKey = Key.Empty;
+
     /// <summary>
     ///     Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
     ///     <see cref="View"/> that is the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this
     ///     <see cref="MenuItem"/>.
     ///     <para>
-    ///         The <see cref="KeyCode"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
+    ///         The <see cref="Key"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and
     ///         <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
     ///     </para>
     /// </summary>
-    public KeyCode Shortcut
+    public Key ShortcutKey
     {
-        get => _shortcutHelper.Shortcut;
+        get => _shortcutKey;
         set
         {
-            if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
+            var oldKey = _shortcutKey ?? Key.Empty;
+            _shortcutKey = value ?? Key.Empty;
+            UpdateShortcutKeyBinding (oldKey);
+        }
+    }
+
+    /// <summary>Gets the text describing the keystroke combination defined by <see cref="ShortcutKey"/>.</summary>
+    public string ShortcutTag => ShortcutKey != Key.Empty ? ShortcutKey.ToString () : string.Empty;
+
+    private void UpdateHotKeyBinding (Key oldKey)
+    {
+        if (_menuBar is null || _menuBar?.IsInitialized == false)
+        {
+            return;
+        }
+
+        if (oldKey != Key.Empty)
+        {
+            var index = _menuBar.Menus?.IndexOf (this);
+
+            if (index > -1)
+            {
+                _menuBar.KeyBindings.Remove (oldKey.WithAlt);
+            }
+        }
+
+        if (HotKey != Key.Empty)
+        {
+            var index = _menuBar.Menus?.IndexOf (this);
+
+            if (index > -1)
             {
-                _shortcutHelper.Shortcut = value;
+                _menuBar.KeyBindings.Remove (HotKey.WithAlt);
+                KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, this);
+                _menuBar.KeyBindings.Add (HotKey.WithAlt, keyBinding);
             }
         }
     }
 
-    /// <summary>Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.</summary>
-    public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
-                                     ? string.Empty
-                                     : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
+    internal void UpdateShortcutKeyBinding (Key oldKey)
+    {
+        if (_menuBar is null)
+        {
+            return;
+        }
+
+        if (oldKey != Key.Empty)
+        {
+            _menuBar.KeyBindings.Remove (oldKey);
+        }
+
+        if (ShortcutKey != Key.Empty)
+        {
+            KeyBinding keyBinding = new ([Command.Select], KeyBindingScope.HotKey, this);
+            // Remove an existent ShortcutKey
+            _menuBar?.KeyBindings.Remove (ShortcutKey);
+            _menuBar?.KeyBindings.Add (ShortcutKey, keyBinding);
+        }
+    }
 
     #endregion Keyboard Handling
+
+    /// <summary>
+    /// Removes a <see cref="MenuItem"/> dynamically from the <see cref="Parent"/>.
+    /// </summary>
+    public virtual void RemoveMenuItem ()
+    {
+        if (Parent is { })
+        {
+            MenuItem [] childrens = ((MenuBarItem)Parent).Children;
+            var i = 0;
+
+            foreach (MenuItem c in childrens)
+            {
+                if (c != this)
+                {
+                    childrens [i] = c;
+                    i++;
+                }
+            }
+
+            Array.Resize (ref childrens, childrens.Length - 1);
+
+            if (childrens.Length == 0)
+            {
+                ((MenuBarItem)Parent).Children = null;
+            }
+            else
+            {
+                ((MenuBarItem)Parent).Children = childrens;
+            }
+        }
+
+        if (ShortcutKey != Key.Empty)
+        {
+            // Remove an existent ShortcutKey
+            _menuBar?.KeyBindings.Remove (ShortcutKey);
+        }
+    }
 }

+ 1 - 16
Terminal.Gui/Views/MessageBox.cs

@@ -1,7 +1,4 @@
-using System.Diagnostics;
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     MessageBox displays a modal message to the user, with a title, a message and a series of options that the user
@@ -32,13 +29,11 @@ public static class MessageBox
     ///     <see cref="ConfigurationManager"/>.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json
 
     /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
     /// <remarks>This property can be set in a Theme.</remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<Alignment>))]
     public static Alignment DefaultButtonAlignment { get; set; } = Alignment.Center; // Default is set in config.json
 
     /// <summary>
@@ -405,11 +400,6 @@ public static class MessageBox
         d.TextFormatter.WordWrap = wrapMessage;
         d.TextFormatter.MultiLine = !wrapMessage;
 
-        d.ColorScheme = new ColorScheme (d.ColorScheme)
-        {
-            Focus = d.ColorScheme.Normal
-        };
-
         // Setup actions
         Clicked = -1;
 
@@ -423,11 +413,6 @@ public static class MessageBox
                              Clicked = buttonId;
                              Application.RequestStop ();
                          };
-
-            if (b.IsDefault)
-            {
-                b.SetFocus ();
-            }
         }
 
         // Run the modal; do not shut down the mainloop driver when done

+ 1 - 4
Terminal.Gui/Views/Window.cs

@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
 ///     A <see cref="Toplevel"/> <see cref="View"/> with <see cref="View.BorderStyle"/> set to
@@ -75,6 +73,5 @@ public class Window : Toplevel
     ///     s.
     /// </remarks>
     [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-    [JsonConverter (typeof (JsonStringEnumConverter<LineStyle>))]
     public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
 }

+ 6 - 0
Terminal.sln

@@ -46,6 +46,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C7A51224-5
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfContained", "SelfContained\SelfContained.csproj", "{524DEA78-7E7C-474D-B42D-52ED4C04FF14}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "NativeAot\NativeAot.csproj", "{E6D716C6-AC94-4150-B10A-44AE13F79344}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -80,6 +82,10 @@ Global
 		{524DEA78-7E7C-474D-B42D-52ED4C04FF14}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{524DEA78-7E7C-474D-B42D-52ED4C04FF14}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{524DEA78-7E7C-474D-B42D-52ED4C04FF14}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 1
UICatalog/Scenarios/ContextMenus.cs

@@ -179,7 +179,7 @@ public class ContextMenus : Scenario
                                                                        "This would open setup dialog",
                                                                        "Ok"
                                                                       ),
-                                                           shortcut: KeyCode.T
+                                                           shortcutKey: KeyCode.T
                                                                      | KeyCode
                                                                          .CtrlMask
                                                           ),

+ 1 - 0
UICatalog/Scenarios/Dialogs.cs

@@ -263,6 +263,7 @@ public class Dialogs : Scenario
             dialog = new ()
             {
                 Title = titleEdit.Text,
+                Text = "Dialog Text",
                 ButtonAlignment = (Alignment)Enum.Parse (typeof (Alignment), alignmentRadioGroup.RadioLabels [alignmentRadioGroup.SelectedItem]),
 
                 Buttons = buttons.ToArray ()

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 310 - 348
UICatalog/Scenarios/DynamicMenuBar.cs


+ 10 - 81
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -15,7 +15,7 @@ public class DynamicStatusBar : Scenario
 {
     public override void Main ()
     {
-
+        Application.Init ();
         Application.Run<DynamicStatusBarSample> ().Dispose ();
         Application.Shutdown ();
     }
@@ -125,58 +125,9 @@ public class DynamicStatusBar : Scenario
 
             TextShortcut.KeyDown += (s, e) =>
                                     {
-                                        if (!ProcessKey (e))
-                                        {
-                                            return;
-                                        }
+                                        TextShortcut.Text = e.ToString ();
 
-                                        if (CheckShortcut (e.KeyCode, true))
-                                        {
-                                            e.Handled = true;
-                                        }
                                     };
-
-            bool ProcessKey (Key ev)
-            {
-                switch (ev.KeyCode)
-                {
-                    case KeyCode.CursorUp:
-                    case KeyCode.CursorDown:
-                    case KeyCode.Tab:
-                    case KeyCode.Tab | KeyCode.ShiftMask:
-                        return false;
-                }
-
-                return true;
-            }
-
-            bool CheckShortcut (KeyCode k, bool pre)
-            {
-                Shortcut m = _statusItem != null ? _statusItem : new Shortcut (k, "", null);
-
-                if (pre && !ShortcutHelper.PreShortcutValidation (k))
-                {
-                    TextShortcut.Text = "";
-
-                    return false;
-                }
-
-                if (!pre)
-                {
-                    return true;
-                }
-
-                TextShortcut.Text = k.ToString ();
-                return true;
-            }
-
-            TextShortcut.KeyUp += (s, e) =>
-                                  {
-                                      if (CheckShortcut (e.KeyCode, true))
-                                      {
-                                          e.Handled = true;
-                                      }
-                                  };
             Add (TextShortcut);
 
             var _btnShortcut = new Button
@@ -210,7 +161,7 @@ public class DynamicStatusBar : Scenario
                                   ? GetTargetAction (statusItem.Action)
                                   : string.Empty;
 
-            TextShortcut.Text = statusItem.CommandView.Text;
+            TextShortcut.Text = statusItem.Key;
         }
 
         public DynamicStatusItem EnterStatusItem ()
@@ -238,13 +189,6 @@ public class DynamicStatusBar : Scenario
                                   }
                                   else
                                   {
-                                      if (!string.IsNullOrEmpty (TextShortcut.Text))
-                                      {
-                                          TextTitle.Text = DynamicStatusBarSample.SetTitleText (
-                                                                                                TextTitle.Text,
-                                                                                                TextShortcut.Text
-                                                                                               );
-                                      }
 
                                       valid = true;
                                       Application.RequestStop ();
@@ -433,10 +377,6 @@ public class DynamicStatusBar : Scenario
                                   }
                                   else if (_currentEditStatusItem != null)
                                   {
-                                      _frmStatusBarDetails.TextTitle.Text = SetTitleText (
-                                                                                          _frmStatusBarDetails.TextTitle.Text,
-                                                                                          _frmStatusBarDetails.TextShortcut.Text
-                                                                                         );
 
                                       var statusItem = new DynamicStatusItem
                                       {
@@ -487,6 +427,7 @@ public class DynamicStatusBar : Scenario
                                       if (statusItem != null)
                                       {
                                           _statusBar.RemoveShortcut (_currentSelectedStatusBar);
+                                          statusItem.Dispose ();
                                           DataContext.Items.RemoveAt (_lstItems.SelectedItem);
 
                                           if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1)
@@ -526,6 +467,7 @@ public class DynamicStatusBar : Scenario
                                                }
 
                                                Remove (_statusBar);
+                                               _statusBar.Dispose ();
                                                _statusBar = null;
                                                DataContext.Items = [];
                                                _currentStatusItem = null;
@@ -588,7 +530,7 @@ public class DynamicStatusBar : Scenario
 
             Shortcut CreateNewStatusBar (DynamicStatusItem item)
             {
-                var newStatusItem = new Shortcut (Key.Empty, item.Title, null);
+                var newStatusItem = new Shortcut (item.Shortcut, item.Title, _frmStatusBarDetails.CreateAction (item));
 
                 return newStatusItem;
             }
@@ -599,8 +541,9 @@ public class DynamicStatusBar : Scenario
                 int index
             )
             {
-                _currentEditStatusItem = CreateNewStatusBar (statusItem);
-                //_statusBar.Items [index] = _currentEditStatusItem;
+                _statusBar.Subviews [index].Title = statusItem.Title;
+                ((Shortcut)_statusBar.Subviews [index]).Action = _frmStatusBarDetails.CreateAction (statusItem);
+                ((Shortcut)_statusBar.Subviews [index]).Key = statusItem.Shortcut;
 
                 if (DataContext.Items.Count == 0)
                 {
@@ -624,23 +567,9 @@ public class DynamicStatusBar : Scenario
 
         public DynamicStatusItemModel DataContext { get; set; }
 
-        public static string SetTitleText (string title, string shortcut)
-        {
-            string txt = title;
-            string [] split = title.Split ('~');
 
-            if (split.Length > 1)
-            {
-                txt = split [2].Trim ();
-            }
 
-            if (string.IsNullOrEmpty (shortcut) || shortcut == "Null")
-            {
-                return txt;
-            }
 
-            return $"~{shortcut}~ {txt}";
-        }
     }
 
     public class DynamicStatusItem
@@ -662,7 +591,7 @@ public class DynamicStatusBar : Scenario
 
         public Shortcut Shortcut { get; set; }
         public string Title { get; set; }
-        public override string ToString () { return $"{Title}, {Shortcut}"; }
+        public override string ToString () { return $"{Title}, {Shortcut.Key}"; }
     }
 
     public class DynamicStatusItemModel : INotifyPropertyChanged

+ 1 - 1
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -119,7 +119,7 @@ public class VkeyPacketSimulator : Scenario
                                                                                         "Keys",
                                                                                         $"'{Key.ToString (
                                                                                                           e.KeyCode,
-                                                                                                          MenuBar.ShortcutDelimiter
+                                                                                                          Key.Separator
                                                                                                          )}' pressed!",
                                                                                         "Ok"
                                                                                        )

+ 11 - 11
UICatalog/UICatalog.cs

@@ -676,7 +676,7 @@ public class UICatalogApp
 
             ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
 
-            MenuBar!.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
+            MenuBar!.Menus [0].Children [0].ShortcutKey = Application.QuitKey;
 
             if (StatusBar is { })
             {
@@ -700,8 +700,8 @@ public class UICatalogApp
             {
                 var item = new MenuItem
                 {
-                    Title = $"_{theme.Key}",
-                    Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++))
+                    Title = theme.Key == "Dark" ? $"{theme.Key.Substring (0, 3)}_{theme.Key.Substring (3, 1)}" : $"_{theme.Key}",
+                    ShortcutKey = new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++))
                         .WithCtrl
                 };
                 item.CheckType |= MenuItemCheckStyle.Checked;
@@ -735,6 +735,7 @@ public class UICatalogApp
                                    ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
                                    Application.Top!.SetNeedsDisplay ();
                                };
+                item.ShortcutKey = ((Key)sc.Key [0].ToString ().ToLower ()).WithCtrl;
                 schemeMenuItems.Add (item);
             }
 
@@ -796,7 +797,7 @@ public class UICatalogApp
             {
                 var item = new MenuItem
                 {
-                    Title = GetDiagnosticsTitle (diag), Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt
+                    Title = GetDiagnosticsTitle (diag), ShortcutKey = new Key (index.ToString () [0]).WithAlt
                 };
                 index++;
                 item.CheckType |= MenuItemCheckStyle.Checked;
@@ -951,9 +952,8 @@ public class UICatalogApp
             List<MenuItem> menuItems = new ();
             MiIsMenuBorderDisabled = new () { Title = "Disable Menu _Border" };
 
-            MiIsMenuBorderDisabled.Shortcut =
-                (KeyCode)new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt
-                                                                                       .WithCtrl.NoShift;
+            MiIsMenuBorderDisabled.ShortcutKey =
+                new Key (MiIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl.NoShift;
             MiIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked;
 
             MiIsMenuBorderDisabled.Action += () =>
@@ -974,8 +974,8 @@ public class UICatalogApp
             List<MenuItem> menuItems = new ();
             MiIsMouseDisabled = new () { Title = "_Disable Mouse" };
 
-            MiIsMouseDisabled.Shortcut =
-                (KeyCode)new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift;
+            MiIsMouseDisabled.ShortcutKey =
+                new Key (MiIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl.NoShift;
             MiIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
 
             MiIsMouseDisabled.Action += () =>
@@ -994,7 +994,7 @@ public class UICatalogApp
             List<MenuItem> menuItems = new ();
             MiUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" };
 
-            MiUseSubMenusSingleFrame.Shortcut = KeyCode.CtrlMask
+            MiUseSubMenusSingleFrame.ShortcutKey = KeyCode.CtrlMask
                                                 | KeyCode.AltMask
                                                 | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [
                                                  0];
@@ -1017,7 +1017,7 @@ public class UICatalogApp
             MiForce16Colors = new ()
             {
                 Title = "Force _16 Colors",
-                Shortcut = (KeyCode)Key.F6,
+                ShortcutKey = Key.F6,
                 Checked = Application.Force16Colors,
                 CanExecute = () => Application.Driver?.SupportsTrueColor ?? false
             };

+ 40 - 40
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -38,7 +38,7 @@ public class ConfigurationManagerTests
         }
 
         // act
-        Settings ["Application.QuitKey"].PropertyValue = Key.Q;
+        Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
         Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
         Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
 
@@ -130,7 +130,7 @@ public class ConfigurationManagerTests
             { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) }
         };
         dictCopy = (Dictionary<string, Attribute>)DeepMemberWiseCopy (dictSrc, dictDest);
-        Assert.Equal (2, dictCopy.Count);
+        Assert.Equal (2, dictCopy!.Count);
         Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
         Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
 
@@ -141,7 +141,7 @@ public class ConfigurationManagerTests
         };
         dictSrc = new Dictionary<string, Attribute> { { "Disabled", new Attribute (Color.White) } };
         dictCopy = (Dictionary<string, Attribute>)DeepMemberWiseCopy (dictSrc, dictDest);
-        Assert.Equal (2, dictCopy.Count);
+        Assert.Equal (2, dictCopy!.Count);
         Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
         Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
     }
@@ -151,7 +151,7 @@ public class ConfigurationManagerTests
     {
         Reset ();
 
-        Settings ["Application.QuitKey"].PropertyValue = Key.Q;
+        Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
         Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
         Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
 
@@ -163,16 +163,16 @@ public class ConfigurationManagerTests
             fired = true;
 
             // assert
-            Assert.Equal (Key.Esc, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+            Assert.Equal (Key.Esc, (((Key)Settings! ["Application.QuitKey"].PropertyValue)!).KeyCode);
 
             Assert.Equal (
                           KeyCode.F6,
-                          ((Key)Settings ["Application.NextTabGroupKey"].PropertyValue).KeyCode
+                          (((Key)Settings ["Application.NextTabGroupKey"].PropertyValue)!).KeyCode
                          );
 
             Assert.Equal (
                           KeyCode.F6 | KeyCode.ShiftMask,
-                          ((Key)Settings ["Application.PrevTabGroupKey"].PropertyValue).KeyCode
+                          (((Key)Settings ["Application.PrevTabGroupKey"].PropertyValue)!).KeyCode
                          );
         }
 
@@ -228,7 +228,7 @@ public class ConfigurationManagerTests
 
         // arrange
         Reset ();
-        Settings ["Application.QuitKey"].PropertyValue = Key.Q;
+        Settings! ["Application.QuitKey"].PropertyValue = Key.Q;
         Settings ["Application.NextTabGroupKey"].PropertyValue = Key.F;
         Settings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
         Settings.Apply ();
@@ -242,7 +242,7 @@ public class ConfigurationManagerTests
         Reset ();
 
         // assert
-        Assert.NotEmpty (Themes);
+        Assert.NotEmpty (Themes!);
         Assert.Equal ("Default", Themes.Theme);
         Assert.Equal (Key.Esc, Application.QuitKey);
         Assert.Equal (Key.F6, Application.NextTabGroupKey);
@@ -274,7 +274,7 @@ public class ConfigurationManagerTests
     {
         Locations = ConfigLocations.DefaultOnly;
         Reset ();
-        Assert.NotEmpty (Themes);
+        Assert.NotEmpty (Themes!);
         Assert.Equal ("Default", Themes.Theme);
     }
 
@@ -367,7 +367,7 @@ public class ConfigurationManagerTests
         // Serialize to a JSON string
         string json = ToJson ();
 
-        // Write the JSON string to the file 
+        // Write the JSON string to the file
         File.WriteAllText ("config.json", json);
     }
 
@@ -377,23 +377,23 @@ public class ConfigurationManagerTests
         Locations = ConfigLocations.All;
         Reset ();
 
-        Assert.NotEmpty (Settings);
+        Assert.NotEmpty (Settings!);
 
         // test that all ConfigProperties have our attribute
         Assert.All (
                     Settings,
                     item => Assert.NotEmpty (
-                                             item.Value.PropertyInfo.CustomAttributes.Where (
-                                                                                             a => a.AttributeType == typeof (SerializableConfigurationProperty)
-                                                                                            )
+                                             item.Value.PropertyInfo!.CustomAttributes.Where (
+                                                                                              a => a.AttributeType == typeof (SerializableConfigurationProperty)
+                                                                                             )
                                             )
                    );
 
         Assert.Empty (
                       Settings.Where (
-                                      cp => cp.Value.PropertyInfo.GetCustomAttribute (
-                                                                                      typeof (SerializableConfigurationProperty)
-                                                                                     )
+                                      cp => cp.Value.PropertyInfo!.GetCustomAttribute (
+                                                                                       typeof (SerializableConfigurationProperty)
+                                                                                      )
                                             == null
                                      )
                      );
@@ -401,12 +401,12 @@ public class ConfigurationManagerTests
         // Application is a static class
         PropertyInfo pi = typeof (Application).GetProperty ("QuitKey");
         Assert.Equal (pi, Settings ["Application.QuitKey"].PropertyInfo);
-        
 
         // FrameView is not a static class and DefaultBorderStyle is Scope.Scheme
         pi = typeof (FrameView).GetProperty ("DefaultBorderStyle");
         Assert.False (Settings.ContainsKey ("FrameView.DefaultBorderStyle"));
-        Assert.True (Themes ["Default"].ContainsKey ("FrameView.DefaultBorderStyle"));
+        Assert.True (Themes! ["Default"].ContainsKey ("FrameView.DefaultBorderStyle"));
+        Assert.Equal (pi, Themes! ["Default"] ["FrameView.DefaultBorderStyle"].PropertyInfo);
     }
 
     [Fact]
@@ -414,31 +414,31 @@ public class ConfigurationManagerTests
     {
         // Color.ColorSchemes is serialized as "ColorSchemes", not "Colors.ColorSchemes"
         PropertyInfo pi = typeof (Colors).GetProperty ("ColorSchemes");
-        var scp = (SerializableConfigurationProperty)pi.GetCustomAttribute (typeof (SerializableConfigurationProperty));
-        Assert.True (scp.Scope == typeof (ThemeScope));
+        var scp = (SerializableConfigurationProperty)pi!.GetCustomAttribute (typeof (SerializableConfigurationProperty));
+        Assert.True (scp!.Scope == typeof (ThemeScope));
         Assert.True (scp.OmitClassName);
 
         Reset ();
-        Assert.Equal (pi, Themes ["Default"] ["ColorSchemes"].PropertyInfo);
+        Assert.Equal (pi, Themes! ["Default"] ["ColorSchemes"].PropertyInfo);
     }
 
     [Fact]
     [AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
     public void TestConfigurationManagerInitDriver ()
     {
-        Assert.Equal ("Default", Themes.Theme);
+        Assert.Equal ("Default", Themes!.Theme);
 
-        Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
+        Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"]!.Normal.Foreground);
         Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
 
         // Change Base
         Stream json = ToStream ();
 
-        Settings.Update (json, "TestConfigurationManagerInitDriver");
+        Settings!.Update (json, "TestConfigurationManagerInitDriver");
 
         Dictionary<string, ColorScheme> colorSchemes =
             (Dictionary<string, ColorScheme>)Themes [Themes.Theme] ["ColorSchemes"].PropertyValue;
-        Assert.Equal (Colors.ColorSchemes ["Base"], colorSchemes ["Base"]);
+        Assert.Equal (Colors.ColorSchemes ["Base"], colorSchemes! ["Base"]);
         Assert.Equal (Colors.ColorSchemes ["TopLevel"], colorSchemes ["TopLevel"]);
         Assert.Equal (Colors.ColorSchemes ["Error"], colorSchemes ["Error"]);
         Assert.Equal (Colors.ColorSchemes ["Dialog"], colorSchemes ["Dialog"]);
@@ -489,7 +489,7 @@ public class ConfigurationManagerTests
 				}
 			}";
 
-        Settings.Update (json, "test");
+        Settings!.Update (json, "test");
 
         // AbNormal is not a ColorScheme attribute
         json = @"
@@ -514,7 +514,7 @@ public class ConfigurationManagerTests
 
         Settings.Update (json, "test");
 
-        // Modify hotNormal background only 
+        // Modify hotNormal background only
         json = @"
 			{
 				""Themes"" :  [ 
@@ -572,7 +572,7 @@ public class ConfigurationManagerTests
 				]
 			}";
 
-        var jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+        var jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
         Assert.Equal ("Unexpected color name: brown.", jsonException.Message);
 
         // AbNormal is not a ColorScheme attribute
@@ -596,10 +596,10 @@ public class ConfigurationManagerTests
 				]
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+        jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
         Assert.Equal ("Unrecognized ColorScheme Attribute name: AbNormal.", jsonException.Message);
 
-        // Modify hotNormal background only 
+        // Modify hotNormal background only
         json = @"
 			{
 				""Themes"" : [ 
@@ -619,7 +619,7 @@ public class ConfigurationManagerTests
 				]
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+        jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
         Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message);
 
         // Unknown property
@@ -628,7 +628,7 @@ public class ConfigurationManagerTests
 				""Unknown"" : ""Not known""
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+        jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
         Assert.StartsWith ("Unknown property", jsonException.Message);
 
         Assert.Equal (0, _jsonErrors.Length);
@@ -644,7 +644,7 @@ public class ConfigurationManagerTests
         GetHardCodedDefaults ();
         Stream stream = ToStream ();
 
-        Settings.Update (stream, "TestConfigurationManagerToJson");
+        Settings!.Update (stream, "TestConfigurationManagerToJson");
     }
 
     [Fact]
@@ -790,19 +790,19 @@ public class ConfigurationManagerTests
         Reset ();
         ThrowOnJsonErrors = true;
 
-        Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
+        Settings!.Update (json, "TestConfigurationManagerUpdateFromJson");
 
         Assert.Equal (KeyCode.Esc, Application.QuitKey.KeyCode);
-        Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+        Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)Settings ["Application.QuitKey"].PropertyValue)!.KeyCode);
 
-        Assert.Equal ("Default", Themes.Theme);
+        Assert.Equal ("Default", Themes!.Theme);
 
-        Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
+        Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"]!.Normal.Foreground);
         Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
 
         Dictionary<string, ColorScheme> colorSchemes =
             (Dictionary<string, ColorScheme>)Themes.First ().Value ["ColorSchemes"].PropertyValue;
-        Assert.Equal (new Color (Color.White), colorSchemes ["Base"].Normal.Foreground);
+        Assert.Equal (new Color (Color.White), colorSchemes! ["Base"].Normal.Foreground);
         Assert.Equal (new Color (Color.Blue), colorSchemes ["Base"].Normal.Background);
 
         // Now re-apply

+ 108 - 0
UnitTests/Configuration/SerializableConfigurationPropertyTests.cs

@@ -0,0 +1,108 @@
+#nullable enable
+
+using System.Reflection;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Terminal.Gui.ConfigurationTests;
+
+public class SerializableConfigurationPropertyTests
+{
+    [Fact]
+    public void Test_SerializableConfigurationProperty_Types_Added_To_JsonSerializerContext ()
+    {
+        // The assembly containing the types to inspect
+        var assembly = Assembly.GetAssembly (typeof (SourceGenerationContext));
+
+        // Get all types from the assembly
+        var types = assembly!.GetTypes ();
+
+        // Find all properties with the SerializableConfigurationProperty attribute
+        var properties = new List<PropertyInfo> ();
+        foreach (var type in types)
+        {
+            properties.AddRange (type.GetProperties ().Where (p =>
+                p.GetCustomAttributes (typeof (SerializableConfigurationProperty), false).Any ()));
+        }
+
+        // Get the types of the properties
+        var propertyTypes = properties.Select (p => p.PropertyType).Distinct ();
+
+        // Get the types registered in the JsonSerializerContext derived class
+        var contextType = typeof (SourceGenerationContext);
+        var contextTypes = GetRegisteredTypes (contextType);
+
+        // Ensure all property types are included in the JsonSerializerContext derived class
+        IEnumerable<Type> collection = contextTypes as Type [] ?? contextTypes.ToArray ();
+
+        foreach (var type in propertyTypes)
+        {
+            Assert.Contains (type, collection);
+        }
+
+        // Ensure no property has the generic JsonStringEnumConverter<>
+        EnsureNoSpecifiedConverters (properties, new [] { typeof (JsonStringEnumConverter<>) });
+        // Ensure no property has the type RuneJsonConverter
+        EnsureNoSpecifiedConverters (properties, new [] { typeof (RuneJsonConverter) });
+        // Ensure no property has the type KeyJsonConverter
+        EnsureNoSpecifiedConverters (properties, new [] { typeof (KeyJsonConverter) });
+
+        // Find all classes with the JsonConverter attribute of type ScopeJsonConverter<>
+        var classesWithScopeJsonConverter = types.Where (t =>
+            t.GetCustomAttributes (typeof (JsonConverterAttribute), false)
+            .Any (attr => ((JsonConverterAttribute)attr).ConverterType!.IsGenericType &&
+                         ((JsonConverterAttribute)attr).ConverterType!.GetGenericTypeDefinition () == typeof (ScopeJsonConverter<>)));
+
+        // Ensure all these classes are included in the JsonSerializerContext derived class
+        foreach (var type in classesWithScopeJsonConverter)
+        {
+            Assert.Contains (type, collection);
+        }
+    }
+
+    private static IEnumerable<Type> GetRegisteredTypes (Type contextType)
+    {
+        // Use reflection to find which types are registered in the JsonSerializerContext
+        var registeredTypes = new List<Type> ();
+
+        var properties = contextType.GetProperties (BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
+        foreach (var property in properties)
+        {
+            if (property.PropertyType.IsGenericType &&
+                property.PropertyType.GetGenericTypeDefinition () == typeof (JsonTypeInfo<>))
+            {
+                registeredTypes.Add (property.PropertyType.GetGenericArguments () [0]);
+            }
+        }
+
+        return registeredTypes.Distinct ();
+    }
+
+    private static void EnsureNoSpecifiedConverters (List<PropertyInfo> properties, IEnumerable<Type> converterTypes)
+    {
+        // Ensure no property has any of the specified converter types
+        foreach (var property in properties)
+        {
+            var jsonConverterAttributes = property.GetCustomAttributes (typeof (JsonConverterAttribute), false)
+                                                  .Cast<JsonConverterAttribute> ();
+
+            foreach (var attribute in jsonConverterAttributes)
+            {
+                foreach (var converterType in converterTypes)
+                {
+                    if (attribute.ConverterType!.IsGenericType &&
+                        attribute.ConverterType.GetGenericTypeDefinition () == converterType)
+                    {
+                        Assert.Fail ($"Property '{property.Name}' should not use the converter '{converterType.Name}'.");
+                    }
+
+                    if (!attribute.ConverterType!.IsGenericType &&
+                        attribute.ConverterType == converterType)
+                    {
+                        Assert.Fail ($"Property '{property.Name}' should not use the converter '{converterType.Name}'.");
+                    }
+                }
+            }
+        }
+    }
+}

+ 9 - 4
UnitTests/Configuration/ThemeScopeTests.cs

@@ -29,13 +29,18 @@ public class ThemeScopeTests
     {
         Reset ();
         Assert.NotEmpty (Themes);
-        Assert.Equal (Alignment.End, Dialog.DefaultButtonAlignment);
+        Alignment savedValue = Dialog.DefaultButtonAlignment;
+        Alignment newValue = Alignment.Center != savedValue ? Alignment.Center : Alignment.Start;
 
-        Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = Alignment.Center;
+        Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = newValue;
 
         ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
-        Assert.Equal (Alignment.Center, Dialog.DefaultButtonAlignment);
-        Reset ();
+        Assert.Equal (newValue, Dialog.DefaultButtonAlignment);
+
+        // Replace with the savedValue to avoid failures on other unit tests that rely on the default value
+        Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = savedValue;
+        ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
+        Assert.Equal (savedValue, Dialog.DefaultButtonAlignment);
     }
 
     [Fact]

+ 3 - 0
UnitTests/Configuration/ThemeTests.cs

@@ -76,6 +76,9 @@ public class ThemeTests
     [Fact]
     public void TestSerialize_RoundTrip ()
     {
+        // This is needed to test only this alone
+        Reset ();
+
         var theme = new ThemeScope ();
         theme ["Dialog.DefaultButtonAlignment"].PropertyValue = Alignment.End;
 

+ 17 - 1
UnitTests/Input/KeyTests.cs

@@ -533,7 +533,7 @@ public class KeyTests
         var b = Key.A;
         Assert.True (a.Equals (b));
     }
-    
+
     [Fact]
     public void Equals_Handled_Changed_ShouldReturnTrue_WhenEqual ()
     {
@@ -552,4 +552,20 @@ public class KeyTests
         var b = Key.A;
         Assert.False (a.Equals (b));
     }
+
+    [Fact]
+    public void Set_Key_Separator_With_Rune_Default_Ensure_Using_The_Default_Plus ()
+    {
+        Key key = new (Key.A.WithCtrl);
+        Assert.Equal ((Rune)'+', Key.Separator);
+        Assert.Equal ("Ctrl+A", key.ToString ());
+
+        Key.Separator = new ('-');
+        Assert.Equal ((Rune)'-', Key.Separator);
+        Assert.Equal ("Ctrl-A", key.ToString ());
+
+        Key.Separator = new ();
+        Assert.Equal ((Rune)'+', Key.Separator);
+        Assert.Equal ("Ctrl+A", key.ToString ());
+    }
 }

+ 72 - 0
UnitTests/TestHelpers.cs

@@ -86,6 +86,11 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
             }
 #endif
             ConfigurationManager.Reset ();
+
+            if (CM.Locations != CM.ConfigLocations.None)
+            {
+                SetCurrentConfig (_savedValues);
+            }
         }
     }
 
@@ -110,10 +115,77 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
             }
 #endif
             Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
+
+            if (CM.Locations != CM.ConfigLocations.None)
+            {
+                _savedValues = GetCurrentConfig ();
+            }
         }
     }
 
     private bool AutoInit { get; }
+
+    private List<object> _savedValues;
+
+    private List<object> GetCurrentConfig ()
+    {
+        CM.Reset ();
+
+        List<object> savedValues =
+        [
+            Dialog.DefaultButtonAlignment,
+            Dialog.DefaultButtonAlignmentModes,
+            MessageBox.DefaultBorderStyle
+        ];
+        CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = Alignment.End;
+        CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignmentModes"].PropertyValue = AlignmentModes.AddSpaceBetweenItems;
+        CM.Themes! ["Default"] ["MessageBox.DefaultBorderStyle"].PropertyValue = LineStyle.Double;
+        ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
+
+        return savedValues;
+    }
+
+    private void SetCurrentConfig (List<object> values)
+    {
+        CM.Reset ();
+        bool needApply = false;
+
+        foreach (object value in values)
+        {
+            switch (value)
+            {
+                case Alignment alignment:
+                    if ((Alignment)CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue! != alignment)
+                    {
+                        needApply = true;
+                        CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = alignment;
+                    }
+
+                    break;
+                case AlignmentModes alignmentModes:
+                    if ((AlignmentModes)CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignmentModes"].PropertyValue! != alignmentModes)
+                    {
+                        needApply = true;
+                        CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignmentModes"].PropertyValue = alignmentModes;
+                    }
+
+                    break;
+                case LineStyle lineStyle:
+                    if ((LineStyle)CM.Themes! ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue! != lineStyle)
+                    {
+                        needApply = true;
+                        CM.Themes! ["Default"] ["MessageBox.DefaultBorderStyle"].PropertyValue = lineStyle;
+                    }
+
+                    break;
+            }
+        }
+
+        if (needApply)
+        {
+            ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
+        }
+    }
 }
 
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]

+ 91 - 6
UnitTests/Views/MenuBarTests.cs

@@ -1,4 +1,5 @@
-using Xunit.Abstractions;
+using System.Text;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.ViewsTests;
 
@@ -217,7 +218,7 @@ public class MenuBarTests (ITestOutputHelper output)
         Assert.Null (menuBarItem.Action);
         Assert.Null (menuBarItem.CanExecute);
         Assert.Null (menuBarItem.Parent);
-        Assert.Equal (KeyCode.Null, menuBarItem.Shortcut);
+        Assert.Equal (Key.Empty, menuBarItem.ShortcutKey);
     }
 
     [Fact]
@@ -1229,7 +1230,7 @@ wo
                 )]
     [InlineData ("Closed", "None", "About", KeyCode.F9, KeyCode.CursorRight, KeyCode.CursorRight, KeyCode.Enter)]
 
-    // Hotkeys
+    //// Hotkeys
     [InlineData ("_File", "_New", "", KeyCode.AltMask | KeyCode.F)]
     [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.ShiftMask | KeyCode.F)]
     [InlineData ("Closed", "None", "", KeyCode.AltMask | KeyCode.F, KeyCode.Esc)]
@@ -1245,9 +1246,10 @@ wo
     [InlineData ("_Edit", "_1st", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3)]
     [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D1)]
     [InlineData ("Closed", "None", "1", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.Enter)]
-    [InlineData ("_Edit", "_3rd Level", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)]
+    [InlineData ("Closed", "None", "2", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D2)]
+    [InlineData ("_Edit", "_5th", "", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D3, KeyCode.D4)]
     [InlineData ("Closed", "None", "5", KeyCode.AltMask | KeyCode.E, KeyCode.F, KeyCode.D4, KeyCode.D5)]
-    [InlineData ("_About", "_About", "", KeyCode.AltMask | KeyCode.A)]
+    [InlineData ("Closed", "None", "About", KeyCode.AltMask | KeyCode.A)]
     public void KeyBindings_Navigation_Commands (
         string expectedBarTitle,
         string expectedItemTitle,
@@ -1283,7 +1285,7 @@ wo
         top.Add (menu);
         Application.Begin (top);
 
-        foreach (KeyCode key in keys)
+        foreach (Key key in keys)
         {
             top.NewKeyDownEvent (new (key));
             Application.MainLoop.RunIteration ();
@@ -3725,4 +3727,87 @@ Edit
         Assert.True (btnClicked);
         top.Dispose ();
     }
+
+    [Fact]
+    public void Update_ShortcutKey_KeyBindings_Old_ShortcutKey_Is_Removed ()
+    {
+        var menuBar = new MenuBar ()
+        {
+            Menus =
+            [
+                new MenuBarItem (
+                                 "_File",
+                                 new MenuItem []
+                                 {
+                                     new MenuItem ("New", "Create New", null, null, null, Key.A.WithCtrl)
+                                 }
+                                )
+            ]
+        };
+
+        Assert.Contains (Key.A.WithCtrl, menuBar.KeyBindings.Bindings);
+
+        menuBar.Menus [0].Children [0].ShortcutKey = Key.B.WithCtrl;
+
+        Assert.DoesNotContain (Key.A.WithCtrl, menuBar.KeyBindings.Bindings);
+        Assert.Contains (Key.B.WithCtrl, menuBar.KeyBindings.Bindings);
+    }
+
+    [Fact]
+    public void SetMenus_With_Same_HotKey_Does_Not_Throws ()
+    {
+        var mb = new MenuBar ();
+
+        var i1 = new MenuBarItem ("_heey", "fff", () => { }, () => true);
+
+        mb.Menus = new MenuBarItem [] { i1 };
+        mb.Menus = new MenuBarItem [] { i1 };
+
+        Assert.Equal (Key.H, mb.Menus [0].HotKey);
+    }
+
+    [Fact]
+    [AutoInitShutdown]
+    public void AddMenuBarItem_RemoveMenuItem_Dynamically ()
+    {
+        var menuBar = new MenuBar ();
+        var menuBarItem = new MenuBarItem { Title = "_New" };
+        var action = "";
+        var menuItem = new MenuItem { Title = "_Item", Action = () => action = "I", Parent = menuBarItem };
+        Assert.Equal ("n", menuBarItem.HotKey);
+        Assert.Equal ("i", menuItem.HotKey);
+        Assert.Empty (menuBar.Menus);
+        menuBarItem.AddMenuBarItem (menuItem);
+        menuBar.Menus = [menuBarItem];
+        Assert.Single (menuBar.Menus);
+        Assert.Single (menuBar.Menus [0].Children);
+        Assert.Contains (Key.N.WithAlt, menuBar.KeyBindings.Bindings);
+        Assert.DoesNotContain (Key.I, menuBar.KeyBindings.Bindings);
+
+        var top = new Toplevel ();
+        top.Add (menuBar);
+        Application.Begin (top);
+
+        top.NewKeyDownEvent (Key.N.WithAlt);
+        Application.MainLoop.RunIteration ();
+        Assert.True (menuBar.IsMenuOpen);
+        Assert.Equal ("", action);
+
+        top.NewKeyDownEvent (Key.I);
+        Application.MainLoop.RunIteration ();
+        Assert.False (menuBar.IsMenuOpen);
+        Assert.Equal ("I", action);
+
+        menuItem.RemoveMenuItem ();
+        Assert.Single (menuBar.Menus);
+        Assert.Equal (null, menuBar.Menus [0].Children);
+        Assert.Contains (Key.N.WithAlt, menuBar.KeyBindings.Bindings);
+        Assert.DoesNotContain (Key.I, menuBar.KeyBindings.Bindings);
+
+        menuBarItem.RemoveMenuItem ();
+        Assert.Empty (menuBar.Menus);
+        Assert.DoesNotContain (Key.N.WithAlt, menuBar.KeyBindings.Bindings);
+
+        top.Dispose ();
+    }
 }

+ 2 - 2
UnitTests/Views/MenuTests.cs

@@ -31,7 +31,7 @@ public class MenuTests
         Assert.Null (menuItem.Action);
         Assert.Null (menuItem.CanExecute);
         Assert.Null (menuItem.Parent);
-        Assert.Equal (KeyCode.Null, menuItem.Shortcut);
+        Assert.Equal (Key.Empty, menuItem.ShortcutKey);
 
         menuItem = new MenuItem ("Test", "Help", Run, () => { return true; }, new MenuItem (), KeyCode.F1);
         Assert.Equal ("Test", menuItem.Title);
@@ -39,7 +39,7 @@ public class MenuTests
         Assert.Equal (Run, menuItem.Action);
         Assert.NotNull (menuItem.CanExecute);
         Assert.NotNull (menuItem.Parent);
-        Assert.Equal (KeyCode.F1, menuItem.Shortcut);
+        Assert.Equal (KeyCode.F1, menuItem.ShortcutKey);
 
         void Run () { }
     }

+ 3 - 2
UnitTests/Views/NumericUpDownTests.cs

@@ -98,7 +98,8 @@ public class NumericUpDownTests (ITestOutputHelper _output)
     [Fact]
     public void WhenCreatedWithInvalidTypeObject_ShouldNotThrowInvalidOperationException ()
     {
-        NumericUpDown<object> numericUpDown = new ();
+        Exception exception = Record.Exception (() => new NumericUpDown<object> ());
+        Assert.Null (exception);
     }
 
     [Fact]
@@ -217,7 +218,7 @@ public class NumericUpDownTests (ITestOutputHelper _output)
     }
 
     [Fact]
-    public void KeDown_CursorUp_Increments ()
+    public void KeyDown_CursorUp_Increments ()
     {
         NumericUpDown<int> numericUpDown = new ();
 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä