5

在 C# Windows.Forms 项目中,我有一个不提供 KeyPressed 事件的控件(它是一个 COM 控件 - ESRI 映射)。

它仅提供包含KeyEventArgs结构的 KeyUp 和 KeyDown 事件。

如何将 KeyEventArgs 中的信息转换为可显示的 Unicode 字符,同时考虑当前活动的键盘布局等?

4

3 回答 3

11

诀窍是使用一组 user32.dll 函数:GetWindowThreadProcessIdGetKeyboardLayoutGetKeyboardStateToUnicodeEx

  1. 使用GetWindowThreadProcessId函数和您的控制句柄来获得相关的本机线程 ID。
  2. 将该线程 id 传递给GetKeyboardLayout以获取当前的键盘布局。
  3. 调用GetKeyboardState获取当前键盘状态。这有助于下一个方法根据修饰符状态决定生成哪个字符。
  4. 最后,使用所需的虚拟键码和扫描码(这两个可以相同)、当前键盘状态、作为字符串持有者(保存结果)的字符串构建器、无标志 (0) 和当前的键盘布局指针。

如果结果不为零,则只返回第一个返回的字符。

  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();
        }
    }
于 2008-12-17T16:04:36.590 回答
8

Itai Bar-Haim 的解决方案很好,但有死键的问题。

第一次使用死键的扫描码调用 CodeToString(int scanCode) 时,ToUnicodeEx() 的返回码为 -1,并且您的方法返回正确的值。

但是在下一次调用时,ToUnicodeEx() 将返回 2。结果字符串将包含预期结果之前的死键,然后是内存垃圾数据。

例如,我用 ^(死键)然后用 $ 调用你的方法。第一次调用返回 ^(不错),但第二次调用返回 ^$azertyui。

你必须做两件事来解决这个问题:

1) 您必须使用 ToUnicodeEx() 返回码绝对值来设置 StringBuilder 的长度。因此,您可以避免在字符之后获取垃圾数据。

2) 当 ToUnicodeEx() 返回码为 -1 时,您必须将 StringBuilder 的第一个字符作为方法的结果。然后您必须从键盘状态中删除死键:使用空格键的扫描码调用 ToUnicodeEx()。

您可能遇到的另一个问题:如果在调用 ToUnicodeEx() 之前按下死键的用户修改了键盘状态,则返回的字符串将包含预期值之前的死键字符。我发现的一种解决方法是在实际调用之前使用空格键的扫描码调用 ToUnicodeEx()。

希望这有帮助!

于 2009-05-06T14:20:14.137 回答
0

查看KeysConverter及其 ConvertToString 方法。请记住,并非所有 KeyDown 都映射到 KeyPress。

于 2008-12-16T19:28:48.300 回答