views:

1705

answers:

3

In a C# Windows.Forms project I have a control that does not supply the KeyPressed event (It’s a COM control – ESRI map).

It only supplies the KeyUp and KeyDown events, containing the KeyEventArgs structure.

How can I convert the information in KeyEventArgs to a displayable Unicode character, taking the current active keyboard layout into account, etc.?

A: 

Look at KeysConverter and its ConvertToString method. Remember that not all KeyDown map to a KeyPress.

Martin Plante
+3  A: 

The trick is to use a set of user32.dll functions: GetWindowThreadProcessId, GetKeyboardLayout, GetKeyboardState and ToUnicodeEx.

  1. Use the GetWindowThreadProcessId function with your control handle to achieve the relevant native thread id.
  2. Pass that thread id to GetKeyboardLayout to get the current keyboard layout.
  3. Call GetKeyboardState to get the current keyboard state. This helps the next method to decide which character to generate according to modifiers state.
  4. Finally, call the ToUnicodeEx function with the wanted virtual key code and scan code (those two can be the same), the current keyboard state, a string builder as a string holder (to hold the result), no flags (0), and the current keyboard layout pointer.

If the result is not zero, just return the first returned character.

  public class KeyboardHelper
    {
        [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        private static extern int ToUnicodeEx(
            uint wVirtKey,
            uint wScanCode,
            Keys[] lpKeyState,
            StringBuilder pwszBuff,
            int cchBuff,
            uint wFlags,
            IntPtr dwhkl);

        [DllImport("user32.dll", ExactSpelling = true)]
        internal static extern IntPtr GetKeyboardLayout(uint threadId);

        [DllImport("user32.dll", ExactSpelling = true)]
        internal static extern bool GetKeyboardState(Keys[] keyStates);

        [DllImport("user32.dll", ExactSpelling = true)]
        internal static extern uint GetWindowThreadProcessId(IntPtr hwindow, out uint processId);

        public static string CodeToString(int scanCode)
        {
            uint procId;
            uint thread = GetWindowThreadProcessId(Process.GetCurrentProcess().MainWindowHandle, out procId);
            IntPtr hkl = GetKeyboardLayout(thread);

            if (hkl == IntPtr.Zero)
            {
                Console.WriteLine("Sorry, that keyboard does not seem to be valid.");
                return string.Empty;
            }

            Keys[] keyStates = new Keys[256];
            if (!GetKeyboardState(keyStates))
                return string.Empty;

            StringBuilder sb = new StringBuilder(10);
            int rc = ToUnicodeEx((uint)scanCode, (uint)scanCode, keyStates, sb, sb.Capacity, 0, hkl);
            return sb.ToString();
        }
    }
Itai Bar-Haim
so now you are answering your questions....
Asher
A: 

Hi!

Itai Bar-Haim's solution is nice but has a problem with dead keys.

The first time you call CodeToString(int scanCode) with the scan code of a dead key, the return code of ToUnicodeEx() is -1 and your method returns the right value.

But on the next call, ToUnicodeEx() will return 2. The result string will contain the dead key before the expected result, followed by memory junk data.

For instance, I call your method with ^ (dead key) and then with $. The first call returns ^ (nice), but the second call returns ^$azertyui.

You must do two things to fix that :

1) You must use the ToUnicodeEx() return code absolute value to set the length of your StringBuilder. So you avoid getting junk data after the characters.

2) When ToUnicodeEx() return code is -1, you must take the first char of your StringBuilder as a result for your method. And then you must remove the dead key from the keyboard state : recall ToUnicodeEx() with the scancode of the Space key.

Another problem you could have : if the keyboard state has been modified by a user who hit a dead key before you call ToUnicodeEx(), the returned string will contains the dead key character before the expected value. A workaround I found is to call ToUnicodeEx() with the scancode of the Space key before the actual call.

Hope this help!

Stephane