diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGesture.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGesture.cs
index f3a4bf70072..b90f78d2658 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGesture.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGesture.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -287,9 +287,9 @@ internal static KeyGesture CreateFromResourceStrings(string keyGestureToken, str
{
// combine the gesture and the display string, producing a string
// that the type converter will recognize
- if (!String.IsNullOrEmpty(keyDisplayString))
+ if (!string.IsNullOrEmpty(keyDisplayString))
{
- keyGestureToken += KeyGestureConverter.DISPLAYSTRING_SEPARATOR + keyDisplayString;
+ keyGestureToken += DisplayStringSeparator + keyDisplayString;
}
return _keyGestureConverter.ConvertFromInvariantString(keyGestureToken) as KeyGesture;
@@ -307,6 +307,7 @@ internal static KeyGesture CreateFromResourceStrings(string keyGestureToken, str
private Key _key = Key.None;
private string _displayString;
private const char MULTIPLEGESTURE_DELIMITER = ';';
+ private const char DisplayStringSeparator = ',';
private static TypeConverter _keyGestureConverter = new KeyGestureConverter();
//private static bool _classRegistered = false;
#endregion Private Fields
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGestureConverter.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGestureConverter.cs
index a092fbb637a..99124293916 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGestureConverter.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/Command/KeyGestureConverter.cs
@@ -1,187 +1,166 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-//
-//
-//
-// Description: KeyGestureConverter - Converts a KeyGesture string
-// to the *Type* that the string represents
-//
+
using System.ComponentModel; // for TypeConverter
using System.Globalization; // for CultureInfo
namespace System.Windows.Input
{
///
- /// KeyGesture - Converter class for converting between a string and the Type of a KeyGesture
+ /// Converter class for converting between a and .
///
public class KeyGestureConverter : TypeConverter
{
- private const char MODIFIERS_DELIMITER = '+' ;
- internal const char DISPLAYSTRING_SEPARATOR = ',' ;
-
- ///
- ///CanConvertFrom()
- ///
- ///ITypeDescriptorContext
- ///type to convert from
- ///true if the given type can be converted, false otherwise
+ ///
+ /// To aid with conversion from to .
+ ///
+ private static readonly KeyConverter s_keyConverter = new();
+
+ ///
+ /// Returns whether or not this class can convert from a given .
+ ///
+ /// The for this call.
+ /// The being queried for support.
+ ///
+ /// if the given can be converted, otherwise.
+ ///
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
// We can only handle string.
- if (sourceType == typeof(string))
- {
- return true;
- }
- else
- {
- return false;
- }
+ return sourceType == typeof(string);
}
-
- ///
- ///TypeConverter method override.
- ///
- ///ITypeDescriptorContext
- ///Type to convert to
- ///true if conversion is possible
+ ///
+ /// Returns whether or not this class can convert to a given .
+ ///
+ /// The for this call.
+ /// The being queried for support.
+ ///
+ /// if this class can convert to , otherwise.
+ ///
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
// We can convert to an InstanceDescriptor or to a string.
- if (destinationType == typeof(string))
- {
- // When invoked by the serialization engine we can convert to string only for known type
- if (context != null && context.Instance != null)
- {
- KeyGesture keyGesture = context.Instance as KeyGesture;
- if (keyGesture != null)
- {
- return (ModifierKeysConverter.IsDefinedModifierKeys(keyGesture.Modifiers)
- && IsDefinedKey(keyGesture.Key));
- }
- }
- }
- return false;
+ if (destinationType != typeof(string))
+ return false;
+
+ // When invoked by the serialization engine we can convert to string only for known type
+ if (context?.Instance is not KeyGesture keyGesture)
+ return false;
+
+ return ModifierKeysConverter.IsDefinedModifierKeys(keyGesture.Modifiers) && IsDefinedKey(keyGesture.Key);
}
///
- /// ConvertFrom()
+ /// Converts of type to its represensation.
///
- ///
- ///
- ///
- ///
+ /// This parameter is ignored during the call.
+ /// This parameter is ignored during the call.
+ /// The object to convert to a .
+ ///
+ /// A new instance of class representing the data contained in .
+ ///
+ /// Thrown in case the was not a .
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object source)
{
- if (source != null && source is string)
+ if (source is not string sourceString)
+ throw GetConvertFromException(source);
+
+ ReadOnlySpan trimmedSource = sourceString.AsSpan().Trim();
+ if (trimmedSource.IsEmpty)
+ return new KeyGesture(Key.None);
+
+ ReadOnlySpan keyToken;
+ ReadOnlySpan modifiersToken;
+ string displayString;
+
+ // Break apart display string
+ int index = trimmedSource.IndexOf(',');
+ if (index >= 0)
{
- string fullName = ((string)source).Trim();
- if (fullName.Length == 0)
- return new KeyGesture(Key.None);
-
- string keyToken;
- string modifiersToken;
- string displayString;
-
- // break apart display string
- int index = fullName.IndexOf(DISPLAYSTRING_SEPARATOR);
- if (index >= 0)
- {
- displayString = fullName.Substring(index + 1).Trim();
- fullName = fullName.Substring(0, index).Trim();
- }
- else
- {
- displayString = String.Empty;
- }
-
- // break apart key and modifiers
- index = fullName.LastIndexOf(MODIFIERS_DELIMITER);
- if (index >= 0)
- { // modifiers exists
- modifiersToken = fullName.Substring(0, index);
- keyToken = fullName.Substring(index + 1);
- }
- else
- {
- modifiersToken = String.Empty;
- keyToken = fullName;
- }
-
- ModifierKeys modifiers = ModifierKeys.None;
- object resultkey = keyConverter.ConvertFrom(context, culture, keyToken);
- if (resultkey != null)
- {
- object temp = modifierKeysConverter.ConvertFrom(context, culture, modifiersToken);
- if (temp != null)
- {
- modifiers = (ModifierKeys)temp;
- }
- return new KeyGesture((Key)resultkey, modifiers, displayString);
- }
+ displayString = trimmedSource.Slice(index + 1).Trim().ToString();
+ trimmedSource = trimmedSource.Slice(0, index).Trim();
}
- throw GetConvertFromException(source);
+ else
+ {
+ displayString = string.Empty;
+ }
+
+ // Break apart key and modifiers
+ index = trimmedSource.LastIndexOf('+');
+ if (index >= 0)
+ {
+ modifiersToken = trimmedSource.Slice(0, index);
+ keyToken = trimmedSource.Slice(index + 1).Trim();
+ }
+ else
+ {
+ modifiersToken = ReadOnlySpan.Empty;
+ keyToken = trimmedSource;
+ }
+
+ return new KeyGesture(KeyConverter.GetKeyFromString(keyToken), ModifierKeysConverter.ConvertFromImpl(modifiersToken), displayString);
}
///
- /// ConvertTo()
+ /// Attempt to convert a class to the .
///
- ///
- ///
- ///
- ///
- ///
+ /// This parameter is ignored during the call.
+ /// This parameter is ignored during the call.
+ /// The object to convert to a .
+ /// The to convert to.
+ ///
+ /// The formatted to its representation.
+ ///
+ /// Thrown in case was .
+ ///
+ /// Thrown in case the was not a
+ /// or was not a .
+ ///
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
ArgumentNullException.ThrowIfNull(destinationType);
- if (destinationType == typeof(string))
- {
- if (value != null)
- {
- KeyGesture keyGesture = value as KeyGesture;
- if (keyGesture != null)
- {
- if (keyGesture.Key == Key.None)
- return String.Empty;
-
- string strBinding = "" ;
- string strKey = (string)keyConverter.ConvertTo(context, culture, keyGesture.Key, destinationType) as string;
- if (strKey != String.Empty)
- {
- strBinding += modifierKeysConverter.ConvertTo(context, culture, keyGesture.Modifiers, destinationType) as string;
- if (strBinding != String.Empty)
- {
- strBinding += MODIFIERS_DELIMITER;
- }
- strBinding += strKey;
-
- if (!String.IsNullOrEmpty(keyGesture.DisplayString))
- {
- strBinding += DISPLAYSTRING_SEPARATOR + keyGesture.DisplayString;
- }
- }
- return strBinding;
- }
- }
- else
- {
- return String.Empty;
- }
- }
- throw GetConvertToException(value,destinationType);
+ if (destinationType != typeof(string))
+ throw GetConvertToException(value, destinationType);
+
+ // Following checks are here to match the previous behavior
+ if (value is null)
+ return string.Empty;
+
+ if (value is not KeyGesture keyGesture)
+ throw GetConvertToException(value, destinationType);
+
+ // If the key is None, nothing else matters
+ if (keyGesture.Key is Key.None)
+ return string.Empty;
+
+ // You will only get string.Empty from KeyConverter for Key.None and we've checked that above
+ string strKey = (string)s_keyConverter.ConvertTo(context, culture, keyGesture.Key, destinationType);
+
+ // No modifiers, just binding (possibly with with display string) -> "F5,Refresh"
+ if (keyGesture.Modifiers is ModifierKeys.None)
+ return string.IsNullOrEmpty(keyGesture.DisplayString) ? strKey : $"{strKey},{keyGesture.DisplayString}";
+
+ ReadOnlySpan modifierSpan = ModifierKeysConverter.ConvertMultipleModifiers(keyGesture.Modifiers, stackalloc char[22]);
+
+ // Append display string if there's any, like "Ctrl+A,Description"
+ if (!string.IsNullOrEmpty(keyGesture.DisplayString))
+ return string.Create(CultureInfo.InvariantCulture, stackalloc char[168], $"{modifierSpan}+{strKey},{keyGesture.DisplayString}");
+
+ // We just put together modifiers and key, like "Ctrl+A"
+ return string.Create(CultureInfo.InvariantCulture, stackalloc char[50], $"{modifierSpan}+{strKey}");
}
- // Check for Valid enum, as any int can be casted to the enum.
+ ///
+ /// Helper function similar to , just lighter and faster.
+ ///
+ /// The value to test against.
+ /// if falls in enumeration range, otherwise.
internal static bool IsDefinedKey(Key key)
{
- return (key >= Key.None && key <= Key.OemClear);
+ return key >= Key.None && key <= Key.OemClear;
}
-
- private static KeyConverter keyConverter = new KeyConverter();
- private static ModifierKeysConverter modifierKeysConverter = new ModifierKeysConverter();
}
}
diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/KeyConverter.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/KeyConverter.cs
index 1deae00b596..c26971f9607 100644
--- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/KeyConverter.cs
+++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/KeyConverter.cs
@@ -8,12 +8,12 @@
namespace System.Windows.Input
{
///
- /// Converter class for converting between a and .
+ /// Converter class for converting between a and .
///
public class KeyConverter : TypeConverter
{
///
- /// Used to check whether we can convert a into a .
+ /// Used to check whether we can convert a into a .
///
///ITypeDescriptorContext
///type to convert from
@@ -25,11 +25,11 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT
}
///
- /// Used to check whether we can convert specified value to .
+ /// Used to check whether we can convert specified value to .
///
/// ITypeDescriptorContext
/// Type to convert to
- /// if conversion to is possible, otherwise.
+ /// if conversion to is possible, otherwise.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
// We can convert to a string
@@ -44,12 +44,12 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
}
///
- /// Converts of type to its representation.
+ /// Converts of type to its representation.
///
/// Parser Context
/// Culture Info
/// Key String
- /// A representing the specified by .
+ /// A representing the specified by .
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object source)
{
if (source is not string stringSource)
@@ -60,13 +60,13 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c
}
///
- /// Converts a of type to its representation.
+ /// Converts a of type to its representation.
///
/// Serialization Context
/// Culture Info
/// Key value
/// Type to Convert
- /// A representing the specified by .
+ /// A representing the specified by .
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
ArgumentNullException.ThrowIfNull(destinationType);
@@ -100,8 +100,10 @@ _ when IsDefinedKey(key) => key.ToString(),
/// Helper function that performs the conversion of to the enum.
///
/// The string to convert from.
- /// A value corresponding to the specified string, if was empty.
- private static Key GetKeyFromString(ReadOnlySpan keyToken)
+ ///
+ /// A value corresponding to the specified string, if was empty.
+ ///
+ internal static Key GetKeyFromString(ReadOnlySpan keyToken)
{
// If the token is empty, we presume "None" as our value but it is a success
if (keyToken.IsEmpty)
diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/ModifierKeysConverter.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/ModifierKeysConverter.cs
index 3d2789703f0..28b18c0ed01 100644
--- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/ModifierKeysConverter.cs
+++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/ModifierKeysConverter.cs
@@ -1,4 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -58,6 +58,18 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c
ReadOnlySpan modifiersToken = stringSource.AsSpan().Trim();
+ return ConvertFromImpl(modifiersToken);
+ }
+
+ ///
+ /// Converts to its represensation.
+ ///
+ ///
+ /// is expected to have separated with '+', with any amount of whitespace.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static ModifierKeys ConvertFromImpl(ReadOnlySpan modifiersToken)
+ {
// Empty token means there were no modifiers, exit early
if (modifiersToken.IsEmpty)
return ModifierKeys.None;
@@ -123,10 +135,32 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul
};
}
- private static string ConvertMultipleModifiers(ModifierKeys modifiers)
+ ///
+ /// This is a proxy function for
+ /// so we do not initialize a stack-allocated buffer when we do not need to.
+ ///
+ /// The modifiers bits to format.
+ /// Formatted in [modifier_1]+[modifier_2] format.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static string ConvertMultipleModifiers(ModifierKeys modifiers)
+ {
+ return new string(ConvertMultipleModifiers(modifiers, stackalloc char[22]));
+ }
+
+ ///
+ /// Format multiple into -provided buffer.
+ ///
+ /// The modifiers bits to format.
+ /// The buffer to write formatted modifiers into.
+ /// Formatted in [modifier_1]+[modifier_2] format.
+ ///
+ /// Make sure the is at least 22 chars long, otherwise conversion might fail.
+ ///
+ internal static ReadOnlySpan ConvertMultipleModifiers(ModifierKeys modifiers, Span modifierSpan)
{
// Ctrl+Alt+Windows+Shift is the maximum char length, though the composition of such value is improbable
- Span modifierSpan = stackalloc char[22];
+ Debug.Assert(modifierSpan.Length > 21);
+
int totalLength = 0;
if (modifiers.HasFlag(ModifierKeys.Control))
@@ -141,14 +175,14 @@ private static string ConvertMultipleModifiers(ModifierKeys modifiers)
if (modifiers.HasFlag(ModifierKeys.Shift))
AppendWithDelimiter("Shift", ref totalLength, ref modifierSpan);
- //Helper function to concatenate modifiers
+ // Helper function to concatenate modifiers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void AppendWithDelimiter(string literal, ref int totalLength, ref Span modifierSpan)
{
// If this is not the first modifier in the span, we prepend a delimiter (e.g. Ctrl -> Ctrl+Alt)
if (totalLength > 0)
{
- "+".CopyTo(modifierSpan.Slice(totalLength));
+ modifierSpan[totalLength] = '+';
totalLength++;
}
@@ -156,7 +190,7 @@ static void AppendWithDelimiter(string literal, ref int totalLength, ref Span