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 7042ba7af49..1deae00b596 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
@@ -1,230 +1,321 @@
-// 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.
-using System.ComponentModel;
-using System.Globalization;
+using System.ComponentModel; // for TypeConverter
+using System.Globalization; // for CultureInfo
namespace System.Windows.Input
{
///
- /// Key Converter class for converting between a string and the Type of a Key
+ /// Converter class for converting between a and .
///
- ///
public class KeyConverter : TypeConverter
{
- ///
- /// CanConvertFrom()
- ///
- ///
- ///
- ///
- ///
+ ///
+ /// Used to check whether we can convert a into a .
+ ///
+ ///ITypeDescriptorContext
+ ///type to convert from
+ /// if the given can be converted, otherwise.
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
- if (sourceType == typeof(string))
- {
- return true;
- }
- else
- {
- return false;
- }
+ // We can only handle string
+ return sourceType == typeof(string);
}
///
- /// TypeConverter method override.
+ /// Used to check whether we can convert specified value to .
///
/// ITypeDescriptorContext
/// Type to convert to
- /// true if conversion is possible
+ /// if conversion to is possible, otherwise.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
- // We can convert to a string.
- // 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)
- {
- Key key = (Key)context.Instance;
- return ((int)key >= (int)Key.None && (int)key <= (int)Key.DeadCharProcessed);
- }
- }
- return false;
+ // We can convert to a string
+ if (destinationType != typeof(string))
+ return false;
+
+ // When invoked by the serialization engine we can convert to string only for known type
+ if (context is null || context.Instance is null)
+ return false;
+
+ return IsDefinedKey((Key)context.Instance);
}
///
- /// ConvertFrom()
+ /// Converts of type to its representation.
///
- ///
- ///
- ///
- ///
- ///
+ /// Parser Context
+ /// Culture Info
+ /// Key String
+ /// A representing the specified by .
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object source)
{
- if (source is string)
- {
- string fullName = ((string)source).Trim();
- object key = GetKey(fullName, CultureInfo.InvariantCulture);
- if (key != null)
- {
- return ((Key)key);
- }
- else
- {
- throw new NotSupportedException(SR.Format(SR.Unsupported_Key, fullName));
- }
- }
- throw GetConvertFromException(source);
+ if (source is not string stringSource)
+ throw GetConvertFromException(source);
+
+ ReadOnlySpan fullName = stringSource.AsSpan().Trim();
+ return GetKeyFromString(fullName);
}
///
- /// ConvertTo()
+ /// Converts a of type to its representation.
///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// Serialization Context
+ /// Culture Info
+ /// Key value
+ /// Type to Convert
+ /// A representing the specified by .
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
ArgumentNullException.ThrowIfNull(destinationType);
- if (destinationType == typeof(string) && value != null)
+ if (value is null || destinationType != typeof(string))
+ throw GetConvertToException(value, destinationType);
+
+ Key key = (Key)value;
+ return key switch
{
- Key key = (Key)value;
- if (key == Key.None)
- {
- return String.Empty;
- }
-
- if (key >= Key.D0 && key <= Key.D9)
- {
- return Char.ToString((char)(int)(key - Key.D0 + '0'));
- }
-
- if (key >= Key.A && key <= Key.Z)
- {
- return Char.ToString((char)(int)(key - Key.A + 'A'));
- }
-
- String strKey = MatchKey(key, culture);
- if (strKey != null)
- {
- return strKey;
- }
- }
- throw GetConvertToException(value, destinationType);
+ Key.None => string.Empty,
+ // This is a fast path for common keys before resort to Enum.ToString()
+ >= Key.D0 and <= Key.D9 => char.ToString((char)(key - Key.D0 + '0')),
+ >= Key.A and <= Key.Z => char.ToString((char)(key - Key.A + 'A')),
+ // We format some keys differently than defined in the enum
+ Key.Back => "Backspace",
+ Key.LineFeed => "Clear",
+ Key.Escape => "Esc",
+ // We will add some heavily used interned strings (F10-F12)
+ Key.F10 => "F10",
+ Key.F11 => "F11",
+ Key.F12 => "F12",
+ // Last resort, use Enum.ToString() if the range is defined
+ _ when IsDefinedKey(key) => key.ToString(),
+ // Everything else failed, we throw an exception
+ _ => throw GetConvertToException(value, destinationType)
+ };
}
- private object GetKey(string keyToken, CultureInfo culture)
+ ///
+ /// 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)
{
- if (keyToken.Length == 0)
- {
+ // If the token is empty, we presume "None" as our value but it is a success
+ if (keyToken.IsEmpty)
return Key.None;
+
+ // In case we're dealing with a lowercase character, we uppercase it
+ char firstChar = keyToken[0];
+ if (firstChar >= 'a' && firstChar <= 'z')
+ firstChar ^= (char)0x20;
+
+ // If this is a single-character we're dealing with, match digits/letters
+ if (keyToken.Length == 1 && char.IsLetterOrDigit(firstChar))
+ {
+ // Match an ASCII digit or an ASCII letter (lower/uppercase)
+ if (char.IsAsciiDigit(firstChar)) // 0 - 9
+ return Key.D0 + firstChar - '0';
+ else if (char.IsAsciiLetterUpper(firstChar)) // A - Z
+ return Key.A + firstChar - 'A';
+ else
+ throw new ArgumentException(SR.Format(SR.CannotConvertStringToType, keyToken.ToString(), typeof(Key)));
}
- else
+
+ // It is a special key or an invalid one, we're gonna find out
+ switch (keyToken.Length)
{
- keyToken = keyToken.ToUpper(culture);
- if (keyToken.Length == 1 && Char.IsLetterOrDigit(keyToken[0]))
- {
- if (Char.IsDigit(keyToken[0]) && (keyToken[0] >= '0' && keyToken[0] <= '9'))
+ case 2:
+ // Special path for F1-F9 (switch would take 600 B in code size for no benefit)
+ char secondChar = keyToken[1];
+ if (firstChar == 'F' && (secondChar > '0' && secondChar <= '9'))
+ return Key.F1 + secondChar - '1';
+ // We've got one more special case for Key.Back/Backspace -> "BS"
+ if (firstChar == 'B' && (secondChar is 'S' or 's'))
+ return Key.Back;
+ break;
+ case 3:
+ switch (firstChar)
{
- return ((int)(Key)(Key.D0 + keyToken[0] - '0'));
+ case 'A':
+ if (keyToken.Equals("Alt", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftAlt;
+ break;
+ case 'D':
+ if (keyToken.Equals("Del", StringComparison.OrdinalIgnoreCase))
+ return Key.Delete;
+ break;
+ case 'E':
+ if (keyToken.Equals("Esc", StringComparison.OrdinalIgnoreCase))
+ return Key.Escape;
+ break;
+ case 'F':
+ if (keyToken.Equals("F10", StringComparison.OrdinalIgnoreCase))
+ return Key.F10;
+ if (keyToken.Equals("F11", StringComparison.OrdinalIgnoreCase))
+ return Key.F11;
+ if (keyToken.Equals("F12", StringComparison.OrdinalIgnoreCase))
+ return Key.F12;
+ break;
+ case 'I':
+ if (keyToken.Equals("INS", StringComparison.OrdinalIgnoreCase))
+ return Key.Insert;
+ break;
+ case 'P':
+ if (keyToken.Equals("Pa1", StringComparison.OrdinalIgnoreCase))
+ return Key.Pa1;
+ break;
+ case 'W':
+ if (keyToken.Equals("Win", StringComparison.OrdinalIgnoreCase))
+ return Key.LWin;
+ break;
}
- else if (Char.IsLetter(keyToken[0]) && (keyToken[0] >= 'A' && keyToken[0] <= 'Z'))
+ break;
+ case 4:
+ switch (firstChar)
{
- return ((int)(Key)(Key.A + keyToken[0] - 'A'));
+ case 'A':
+ if (keyToken.Equals("Apps", StringComparison.OrdinalIgnoreCase))
+ return Key.Apps;
+ if (keyToken.Equals("Attn", StringComparison.OrdinalIgnoreCase))
+ return Key.Attn;
+ break;
+ case 'B':
+ if (keyToken.Equals("BKSP", StringComparison.OrdinalIgnoreCase))
+ return Key.Back;
+ break;
+ case 'C':
+ if (keyToken.Equals("Ctrl", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftCtrl;
+ break;
+ case 'P':
+ if (keyToken.Equals("PGDN", StringComparison.OrdinalIgnoreCase))
+ return Key.PageDown;
+ if (keyToken.Equals("PGUP", StringComparison.OrdinalIgnoreCase))
+ return Key.PageUp;
+ if (keyToken.Equals("Pipe", StringComparison.OrdinalIgnoreCase))
+ return Key.OemPipe;
+ if (keyToken.Equals("Play", StringComparison.OrdinalIgnoreCase))
+ return Key.Play;
+ if (keyToken.Equals("Plus", StringComparison.OrdinalIgnoreCase))
+ return Key.OemPlus;
+ break;
+ case 'Z':
+ if (keyToken.Equals("Zoom", StringComparison.OrdinalIgnoreCase))
+ return Key.Zoom;
+ break;
}
- else
+ break;
+ case 5:
+ switch (firstChar)
{
- throw new ArgumentException(SR.Format(SR.CannotConvertStringToType, keyToken, typeof(Key)));
+ case 'B':
+ if (keyToken.Equals("Break", StringComparison.OrdinalIgnoreCase))
+ return Key.Cancel;
+ break;
+ case 'C':
+ if (keyToken.Equals("Comma", StringComparison.OrdinalIgnoreCase))
+ return Key.OemComma;
+ if (keyToken.Equals("CrSel", StringComparison.OrdinalIgnoreCase))
+ return Key.CrSel;
+ break;
+ case 'E':
+ if (keyToken.Equals("Enter", StringComparison.OrdinalIgnoreCase))
+ return Key.Return;
+ if (keyToken.Equals("ExSel", StringComparison.OrdinalIgnoreCase))
+ return Key.ExSel;
+ break;
+ case 'M':
+ if (keyToken.Equals("Minus", StringComparison.OrdinalIgnoreCase))
+ return Key.OemMinus;
+ break;
+ case 'P':
+ if (keyToken.Equals("PRTSC", StringComparison.OrdinalIgnoreCase))
+ return Key.PrintScreen;
+ break;
+ case 'S':
+ if (keyToken.Equals("Shift", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftShift;
+ break;
+ case 'T':
+ if (keyToken.Equals("Tilde", StringComparison.OrdinalIgnoreCase))
+ return Key.OemTilde;
+ break;
}
- }
- else
- {
- Key keyFound = (Key)(-1);
- switch (keyToken)
- {
- case "ENTER": keyFound = Key.Return; break;
- case "ESC": keyFound = Key.Escape; break;
- case "PGUP": keyFound = Key.PageUp; break;
- case "PGDN": keyFound = Key.PageDown; break;
- case "PRTSC": keyFound = Key.PrintScreen; break;
- case "INS": keyFound = Key.Insert; break;
- case "DEL": keyFound = Key.Delete; break;
- case "WINDOWS": keyFound = Key.LWin; break;
- case "WIN": keyFound = Key.LWin; break;
- case "LEFTWINDOWS": keyFound = Key.LWin; break;
- case "RIGHTWINDOWS": keyFound = Key.RWin; break;
- case "APPS": keyFound = Key.Apps; break;
- case "APPLICATION": keyFound = Key.Apps; break;
- case "BREAK": keyFound = Key.Cancel; break;
- case "BACKSPACE": keyFound = Key.Back; break;
- case "BKSP": keyFound = Key.Back; break;
- case "BS": keyFound = Key.Back; break;
- case "SHIFT": keyFound = Key.LeftShift; break;
- case "LEFTSHIFT": keyFound = Key.LeftShift; break;
- case "RIGHTSHIFT": keyFound = Key.RightShift; break;
- case "CONTROL": keyFound = Key.LeftCtrl; break;
- case "CTRL": keyFound = Key.LeftCtrl; break;
- case "LEFTCTRL": keyFound = Key.LeftCtrl; break;
- case "RIGHTCTRL": keyFound = Key.RightCtrl; break;
- case "ALT": keyFound = Key.LeftAlt; break;
- case "LEFTALT": keyFound = Key.LeftAlt; break;
- case "RIGHTALT": keyFound = Key.RightAlt; break;
- case "SEMICOLON": keyFound = Key.OemSemicolon; break;
- case "PLUS": keyFound = Key.OemPlus; break;
- case "COMMA": keyFound = Key.OemComma; break;
- case "MINUS": keyFound = Key.OemMinus; break;
- case "PERIOD": keyFound = Key.OemPeriod; break;
- case "QUESTION": keyFound = Key.OemQuestion; break;
- case "TILDE": keyFound = Key.OemTilde; break;
- case "OPENBRACKETS": keyFound = Key.OemOpenBrackets; break;
- case "PIPE": keyFound = Key.OemPipe; break;
- case "CLOSEBRACKETS": keyFound = Key.OemCloseBrackets; break;
- case "QUOTES": keyFound = Key.OemQuotes; break;
- case "BACKSLASH": keyFound = Key.OemBackslash; break;
- case "FINISH": keyFound = Key.OemFinish; break;
- case "ATTN": keyFound = Key.Attn; break;
- case "CRSEL": keyFound = Key.CrSel; break;
- case "EXSEL": keyFound = Key.ExSel; break;
- case "ERASEEOF": keyFound = Key.EraseEof; break;
- case "PLAY": keyFound = Key.Play; break;
- case "ZOOM": keyFound = Key.Zoom; break;
- case "PA1": keyFound = Key.Pa1; break;
- default: keyFound = Enum.Parse(keyToken, true); break;
- }
-
- if ((int)keyFound != -1)
- {
- return keyFound;
- }
- return null;
- }
+ break;
+ case 6:
+ if (keyToken.Equals("Finish", StringComparison.OrdinalIgnoreCase))
+ return Key.OemFinish;
+ if (keyToken.Equals("Period", StringComparison.OrdinalIgnoreCase))
+ return Key.OemPeriod;
+ if (keyToken.Equals("Quotes", StringComparison.OrdinalIgnoreCase))
+ return Key.OemQuotes;
+ break;
+ case 7:
+ if (keyToken.Equals("Control", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftCtrl;
+ if (keyToken.Equals("LeftAlt", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftAlt;
+ if (keyToken.Equals("Windows", StringComparison.OrdinalIgnoreCase))
+ return Key.LWin;
+ break;
+ case 8:
+ if (keyToken.Equals("EraseEof", StringComparison.OrdinalIgnoreCase))
+ return Key.EraseEof;
+ if (keyToken.Equals("LeftCtrl", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftCtrl;
+ if (keyToken.Equals("Question", StringComparison.OrdinalIgnoreCase))
+ return Key.OemQuestion;
+ if (keyToken.Equals("RightAlt", StringComparison.OrdinalIgnoreCase))
+ return Key.RightAlt;
+ break;
+ case 9:
+ if (keyToken.Equals("Backslash", StringComparison.OrdinalIgnoreCase))
+ return Key.OemBackslash;
+ if (keyToken.Equals("Backspace", StringComparison.OrdinalIgnoreCase))
+ return Key.Back;
+ if (keyToken.Equals("LeftShift", StringComparison.OrdinalIgnoreCase))
+ return Key.LeftShift;
+ if (keyToken.Equals("RightCtrl", StringComparison.OrdinalIgnoreCase))
+ return Key.RightCtrl;
+ if (keyToken.Equals("Semicolon", StringComparison.OrdinalIgnoreCase))
+ return Key.OemSemicolon;
+ break;
+ case 10:
+ if (keyToken.Equals("RightShift", StringComparison.OrdinalIgnoreCase))
+ return Key.RightShift;
+ break;
+ case 11:
+ if (keyToken.Equals("Application", StringComparison.OrdinalIgnoreCase))
+ return Key.Apps;
+ if (keyToken.Equals("LeftWindows", StringComparison.OrdinalIgnoreCase))
+ return Key.LWin;
+ break;
+ case 12:
+ if (keyToken.Equals("OpenBrackets", StringComparison.OrdinalIgnoreCase))
+ return Key.OemOpenBrackets;
+ if (keyToken.Equals("RightWindows", StringComparison.OrdinalIgnoreCase))
+ return Key.RWin;
+ break;
+ case 13:
+ if (keyToken.Equals("CloseBrackets", StringComparison.OrdinalIgnoreCase))
+ return Key.OemCloseBrackets;
+ break;
}
+
+ return Enum.Parse(keyToken, true);
}
- private static string MatchKey(Key key, CultureInfo culture)
+ ///
+ /// Helper function similar to , just lighter and faster.
+ ///
+ /// The value to test against.
+ /// if falls in enumeration range, otherwise.
+ private static bool IsDefinedKey(Key key)
{
- if (key == Key.None)
- return String.Empty;
- else
- {
- switch (key)
- {
- case Key.Back: return "Backspace";
- case Key.LineFeed: return "Clear";
- case Key.Escape: return "Esc";
- }
- }
- if ((int)key >= (int)Key.None && (int)key <= (int)Key.DeadCharProcessed)
- return key.ToString();
- else
- return null;
+ return key >= Key.None && key <= Key.DeadCharProcessed;
}
}
}