33

I am trying to map a virtual keycode to a char.

My code uses ProcessCmdKey to listen to WM_KEYDOWN which gives me access to the key pressed. For example, when I press single quote I get a key of 222 which I want to have it mapped to keychar 39 which represents... you guessed it... single quote.

My dev context is: - .net Framework 2.0 - UserControl placed in a lot of places

Do you know the answer to the question?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Horas
  • 1,781
  • 3
  • 16
  • 11

7 Answers7

47

Isn't that what the System.Windows.Form.KeysConverter class is for?

KeysConverter kc = new KeysConverter();
string keyChar = kc.ConvertToString(keyData);
Powerlord
  • 87,612
  • 17
  • 125
  • 175
  • Forgot to mention the context of my question: - .net Framework 2.0 - UserControl So the answer to your answer is no. – Horas Nov 26 '08 at 14:52
  • 3
    Just noticed I never replied to that... [KeysConverter exists in .NET 2.0](http://msdn.microsoft.com/en-us/library/system.windows.forms.keysconverter%28v=vs.80%29.aspx). – Powerlord Oct 16 '14 at 15:20
  • 3
    Hm, but that works only partially. As an example where it won't work: `(new KeysConverter()).ConvertToString(Keys.OemQuestion)` *(**Spoiler:** it would return `OemQuestion` instead of `?`)*. Besides, it isn't clear how to handle *Shift* key. Is there a working solution? – Hi-Angel Jun 11 '15 at 10:47
  • 1
    @Hi-Angel Make sure you're matching the correct KeysConverter to the correct Keys enum. [System.Windows.Forms.KeyConverter](https://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.converttostring.aspx) is for WinForms, [System.Windows.Input.KeyConverter](https://msdn.microsoft.com/en-us/library/system.windows.input.keyconverter.aspx) is for WPF. – Powerlord Jun 11 '15 at 13:57
  • 1
    As for modifier keys, the Keys enum actually consists of flags, so D would actually be `Keys.Shift | Keys.D` – Powerlord Jun 11 '15 at 13:59
  • Real answer (at least for me and I guess for some more), is [further below](http://stackoverflow.com/a/38787314/568266). – Matthias Jan 22 '17 at 02:01
  • 1
    Nah, this is just the same with the Keys.ToString() – 123iamking May 28 '17 at 01:17
28

Yes, I did use the MapVirtualKey method. But I was expecting more details on how to use it: what DllImport directive to use, what enum is specific for mapping to characters, etc.

I don't like these answers where you google for like 5 seconds and then just suggest a solution: the real challenge is to put all the pieces together and not have to waste your time with tons of sample-less MSDN pages or other coding forums in order to get your answer. No offense plinth, but your answer (even good) was worhtless since I had this answer even before posting my question on the forum!

So there you go, I am going to post what I was looking for - an out-of-the-box C# solution:

1- Place this directive inside your class:

[DllImport("user32.dll")]
static extern int MapVirtualKey(uint uCode, uint uMapType);

2- Retrieve your char like this:

  protected override bool ProcessCmdKey(ref Message msg, Keys keyData)      
  {
     const int WM_KEYDOWN = 0x100;

     if (msg.Msg == WM_KEYDOWN)
     {            
        // 2 is used to translate into an unshifted character value 
        int nonVirtualKey = MapVirtualKey((uint)keyData, 2);

        char mappedChar = Convert.ToChar(nonVirtualKey);
     }

     return base.ProcessCmdKey(ref msg, keyData);
  }

Thanks for caring... and enjoy!

Jerther
  • 5,558
  • 8
  • 40
  • 59
Horas
  • 1,781
  • 3
  • 16
  • 11
  • Thanks for following up. If you had already looked at MapVirtualKey() perhaps you should've included that in your question (ie, "I've looked at MapVirtualKey() but I don't know how to call it from C#). For your future needs, you might find http://www.pinvoke.net useful. – plinth Nov 26 '08 at 18:57
  • 1
    And in the case of MapVirtualKey, here's the entry from pinvoke.net: http://www.pinvoke.net/default.aspx/user32/MapVirtualKey.html – plinth Nov 26 '08 at 18:58
  • 1
    Minor correction: MapVirtualKey actually returns a uint, not an int. But thank you for that bit of code, it was exactly what I needed for my own project. – Nick Spreitzer Apr 12 '10 at 03:47
  • Very helpful Q&A. I was searching for this. Thank you. – Mojtaba Rezaeian Jan 22 '16 at 12:50
  • THIS should be the top-voted answer. The currently top-voted doesn't for for chars that aren't alphanumeric. – Mickael Bergeron Néron Oct 11 '18 at 14:10
25

I was looking for something similar but I needed the character mapped to the current keyboard layout. Since none of the above answers fulfilled my requirements, here is what I came up with.


        public string KeyCodeToUnicode(Keys key)
        {
            byte[] keyboardState = new byte[255];
            bool keyboardStateStatus = GetKeyboardState(keyboardState);

            if (!keyboardStateStatus)
            {
                return "";
            }

            uint virtualKeyCode = (uint)key;
            uint scanCode = MapVirtualKey(virtualKeyCode, 0);
            IntPtr inputLocaleIdentifier = GetKeyboardLayout(0);

            StringBuilder result = new StringBuilder();
            ToUnicodeEx(virtualKeyCode, scanCode, keyboardState, result, (int)5, (uint)0, inputLocaleIdentifier);

            return result.ToString();
        }

        [DllImport("user32.dll")]
        static extern bool GetKeyboardState(byte[] lpKeyState);

        [DllImport("user32.dll")]
        static extern uint MapVirtualKey(uint uCode, uint uMapType);

        [DllImport("user32.dll")]
        static extern IntPtr GetKeyboardLayout(uint idThread);

        [DllImport("user32.dll")]
        static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);

Ivan Petrov
  • 628
  • 6
  • 12
  • 3
    Excellent. It works perfectly with non English characters. I use it from KeyDown event and call it with e.Keycode or e.KeyData and both works. Thanks – Čikić Nenad Aug 19 '16 at 07:41
  • 3
    The hero we need, but not the hero we deserve! – Georgi-it Sep 15 '16 at 10:19
  • This is great. Works as expected. On the other side, such an obvious task require a lot of code. Well done. – Wojciech Jakubas Mar 06 '18 at 10:24
  • I noticed unexpected results when holding a modifier-key while calling the function and started wondering... Why get the current keyboard state? Works as expected whithout the part related to current keyboard state - and I don't see why the current state would be relevant anyway... am I missing something? – Mikk3lRo Apr 25 '18 at 05:50
  • 1
    Looks like this was lifted (without attribution) from PInvoke.net (https://www.pinvoke.net/default.aspx/user32.tounicodeex) – edtheprogrammerguy Jun 11 '18 at 17:04
  • @edtheprogrammerguy given the "quality" articles http://www.pinvoke.net/default.aspx/user32.hello%20from%20spws and http://www.pinvoke.net/default.aspx/user32.how%20to%20suck%20cock%20on%20today on pinvoke.net, it's probably safe to say the lifting was done the other way around. – Pebermynte Lars Jul 17 '19 at 19:43
  • So which is safer for Me to *Lift* into my own code (with attribution, of course): `byte[] keyboardState = new byte[255];` from this answer or `byte[] bKeyState = new byte[256];` from pinvoke.net? – gridtrak Aug 20 '21 at 20:23
  • `new byte[255]` or `new byte[256]' ? See [nf-winuser-tounicodeex on learn.microsoft.com](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-tounicodeex) for the answer. – gridtrak Aug 20 '21 at 20:44
  • Yes, as @gridtrak has figured it out, the [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) article shows that codes go from 0x01 to 0xFE or from 1 to 255. – Ivan Petrov Aug 24 '21 at 06:27
7

After reading and testing some of the answers provided, I thought I'd suggest an alternative.

As mentioned by MM, System.Windows.KeysConverter does not provide a character representation of the key but rather the enum's name, e.g. "Enter" instead of '\n'.

The MapVirtualKey method suggested by Horas, in answer to his own question, is a good starting point, but still does not support either capslock, or characters entered with the shift key, e.g. '!', '$' and '>'.

An alternative to the MapVirtualKey method, that I am using, is an extension method for the Keys class:

public static char ToChar(this Keys key)
{
    char c = '\0';
    if((key >= Keys.A) && (key <= Keys.Z))
    {
        c = (char)((int)'a' + (int)(key - Keys.A));
    }

    else if((key >= Keys.D0) && (key <= Keys.D9))
    {
        c = (char)((int)'0' + (int)(key - Keys.D0));
    }

    return c;
}

The method shown above will provide support for alphanumeric characters. Support for additional characters could be implemented with either a switch statement or lookup table.

Community
  • 1
  • 1
SongWithoutWords
  • 471
  • 5
  • 12
  • Does this use `System.Windows.Forms` or `System.Windows.Input`? `Key` is an enumeration in `System.Windows.Input`, but is not used here, and it is unclear where `Keys` comes from. –  Feb 07 '17 at 23:46
  • 'Keys' is part of 'System.Windows.Forms' – Geovani Martinez Jan 26 '19 at 20:36
6

I've just written an improvement of Ivan Petrov answer to display a string representation of pressed key combinations in WPF, see my code below:

public static string GetKeyString(Key key, ModifierKeys modifiers)
{
    string result = "";
    if (key != Key.None)
    {
        // Setup modifiers
        if (modifiers.HasFlag(ModifierKeys.Control))
            result += "Ctrl + ";
        if (modifiers.HasFlag(ModifierKeys.Alt))
            result += "Alt + ";
        if (modifiers.HasFlag(ModifierKeys.Shift))
            result += "Shift + ";
        // Get string representation
        string keyStr = key.ToString();
        int keyInt = (int)key;
        // Numeric keys are returned without the 'D'
        if (key >= Key.D0 && key <= Key.D9)
            keyStr = char.ToString((char)(key - Key.D0 + '0'));
        // Char keys are returned directly
        else if (key >= Key.A && key <= Key.Z)
            keyStr = char.ToString((char)(key - Key.A + 'A'));
        // If the key is a keypad operation (Add, Multiply, ...) or an 'Oem' key, P/Invoke
        else if ((keyInt >= 84 && keyInt <= 89) || keyInt >= 140)
            keyStr = KeyCodeToUnicode(key);
        result += keyStr;
    }
    return result;
}

private static string KeyCodeToUnicode(Key key)
{
    byte[] keyboardState = new byte[255];
    bool keyboardStateStatus = GetKeyboardState(keyboardState);

    if (!keyboardStateStatus)
    {
        return "";
    }

    uint virtualKeyCode = (uint)KeyInterop.VirtualKeyFromKey(key);
    uint scanCode = MapVirtualKey(virtualKeyCode, 0);
    IntPtr inputLocaleIdentifier = GetKeyboardLayout(0);

    StringBuilder result = new StringBuilder();
    ToUnicodeEx(virtualKeyCode, scanCode, new byte[255], result, (int)5, (uint)0, inputLocaleIdentifier);

    return result.ToString();
}

[DllImport("user32.dll")]
static extern bool GetKeyboardState(byte[] lpKeyState);

[DllImport("user32.dll")]
static extern uint MapVirtualKey(uint uCode, uint uMapType);

[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);

[DllImport("user32.dll")]
static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
edupeux
  • 81
  • 1
  • 5
  • Hmm, this works, but if a non-alphanumeric keys is pressed, the return result is an empty string. Similarly, if the user presses `space` or `TAB`, the result will be *invisible*, making the user think that no result was returned. Is there a list somewhere of non-alphanumeric key codes and their corresponding names so that we can return the name of the key pressed? – pookie Nov 30 '18 at 13:33
  • Maybe put more conditions in the `GetKeyString` method. – edupeux Dec 01 '18 at 14:15
1

KeysConverter gets the key name not the keys "text" ex: "Num2" instead of "2" MapVirtualKey will work for english but for non-english chars documentation states using MapVirtualKeyEx but that requires a locale Identifier that identifier is loaded by LoadKeyBoardLayout which requires a culture id constant but then after finding the correct id values it didn't work as i tried it so finally i dumped the whole thing

mm.
  • 21
  • 1
  • MapVirtualKey works also for non-english layouts. it uses the actual keyboard-layout. – X181 Nov 04 '13 at 12:54
-1

If you are using WPF (I didn't try it with WinForms), there is a very simple solution that returns directly the pressed char, the TextInput Event of Window.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        TextInput += MainWindow_TextInput;
    }

    private void MainWindow_TextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        txtInput.Text += e.Text;
    }
}
Francesco Bonizzi
  • 5,142
  • 6
  • 49
  • 88