C# 如何将虚拟键码转换为字符?

33
我正在尝试将虚拟键码映射到字符。 我的代码使用ProcessCmdKey监听WM_KEYDOWN事件,这使我可以访问按下的键。例如,当我按单引号键时,我得到一个222的键码,我希望将其映射到代表单引号的键字符39。 我的开发环境是: - .net Framework 2.0 - 在很多地方放置的用户控件 你知道答案吗?

1
MapVirtualKey()是什么? - plinth
7个回答

47

3
刚刚注意到我从未回复过那个......KeysConverter 存在于 .NET 2.0 - Powerlord
3
哦,但这只部分有效。一个不能工作的例子是: (new KeysConverter()).ConvertToString(Keys.OemQuestion)剧透:它会返回OemQuestion而不是?)。此外,如何处理Shift键也不清楚。有有效的解决方案吗? - Hi-Angel
1
@Hi-Angel 确保将正确的KeysConverter与正确的Keys枚举匹配。 System.Windows.Forms.KeyConverter 适用于WinForms,System.Windows.Input.KeyConverter 适用于WPF。 - Powerlord
1
关于修改键,Keys枚举实际上由标志组成,因此D实际上是Keys.Shift | Keys.D - Powerlord
1
不,这只是与Keys.ToString()相同。 - 123iamking
显示剩余2条评论

29

是的,我确实使用了MapVirtualKey方法。但我期望能得到更多使用该方法的细节:应该使用什么DllImport指令,哪些enum具体用于映射字符等。

我不喜欢那些只是在谷歌上搜索几秒钟然后建议解决方案的答案:真正的挑战在于把所有的东西放在一起,而不必浪费时间去查找没有示例的MSDN页面或其他编码论坛以获得答案。没有冒犯plinth的意思,但你的答案(即使很好)也是无用的,因为在发布我的问题之前我已经有了这个答案!

所以,在这里,我要发表我正在寻找的——一个开箱即用的C#解决方案:

1- 将此指令放入您的类内:

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

按照以下方式检索您的char:

  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);
  }

感谢关注……请享受!


感谢您的跟进。如果您已经查看了MapVirtualKey(),也许您应该在问题中包含它(例如,“我已经查看了MapVirtualKey(),但不知道如何从C#中调用它”。)。对于您未来的需求,您可能会发现http://www.pinvoke.net有用。 - plinth
1
而在MapVirtualKey的情况下,这是来自pinvoke.net的条目:http://www.pinvoke.net/default.aspx/user32/MapVirtualKey.html - plinth
1
小修正:MapVirtualKey实际上返回的是一个无符号整数(uint),而不是有符号整数(int)。但是非常感谢你提供的那段代码,正好满足了我自己的项目需求。 - Nick Spreitzer
非常有帮助的问答。我一直在寻找这个。谢谢。 - Mojtaba Rezaeian
这应该是获得最多赞的答案。当前获得最多赞的回答对于非字母数字字符无效。 - Mickael Bergeron Néron

25

我正在寻找相似的东西,但是我需要将字符映射到当前键盘布局。由于上面的答案都没有满足我的要求,所以我想出了以下解决方案。


        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);


3
非常好。它能完美地处理非英文字符。我在KeyDown事件中使用它,并使用e.Keycode或e.KeyData进行调用,两者都有效。谢谢。 - Čikić Nenad
3
我们需要这位英雄,但我们不配拥有他! - Georgi-it
1
看起来这是从 PInvoke.net(https://www.pinvoke.net/default.aspx/user32.tounicodeex)无授权转载的。 - edtheprogrammerguy
那么,哪个更安全,我可以将其提取到我的代码中(当然要进行归属):来自此答案的 byte[] keyboardState = new byte[255]; 还是来自 pinvoke.net 的 byte[] bKeyState = new byte[256]; - gridtrak
是的,正如@gridtrak所发现的那样,虚拟键代码文章显示代码从0x01到0xFE或从1到255。 - Ivan Petrov
显示剩余4条评论

7
阅读并测试了提供的一些答案后,我想提供一种替代方案。
MM所述,System.Windows.KeysConverter不提供键的字符表示,而是枚举名称,例如“Enter”而不是'\n'。 Horas在回答自己的问题时建议使用的MapVirtualKey方法是一个很好的起点,但仍不支持大写锁定或使用Shift键输入的字符,例如'!', '$'和'>'。
我使用的MapVirtualKey方法的替代方法是Keys类的扩展方法:
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;
}

上述方法将为字母数字字符提供支持。可以使用switch语句或查找表实现对其他字符的支持。

这个程序使用 System.Windows.Forms 还是 System.Windows.InputKeySystem.Windows.Input 中的一个枚举,但在这里没有被使用,而且不清楚 Keys 是从哪里来的。 - user1618054
"Keys" 是 "System.Windows.Forms" 的一部分。 - Geovani Martinez

6

我刚刚改进了Ivan Petrov的回答,以在WPF中显示按键组合的字符串表示形式,请参见下面的代码:

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);

嗯,这个可以用,但如果按下非字母数字键,则返回结果为空字符串。同样地,如果用户按下 spaceTAB 键,结果将是不可见的,使用户认为没有返回结果。是否有一个非字母数字键代码及其相应名称的列表,以便我们可以返回所按键的名称? - pookie
也许在 GetKeyString 方法中加入更多的条件。 - edupeux

1

KeysConverter 可以获取键名而不是键的“文本” 例如:“Num2”而不是“2” MapVirtualKey 对于英语可以工作,但对于非英语字符,文档指出使用 MapVirtualKeyEx,但这需要区域标识符 该标识符由 LoadKeyBoardLayout 加载,它需要一个文化 ID 常量 但是在找到正确的 ID 值之后,它没有起作用,所以最终我放弃了整个事情


MapVirtualKey 也适用于非英语布局,它使用实际的键盘布局。 - X181

-1
如果您正在使用WPF(我没有尝试过WinForms),那么有一个非常简单的解决方案,可以直接返回按下的字符,即窗口的TextInput事件
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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接