c# - 是否可以创建与所用键盘相同的键盘布局?

标签 c# .net globalization regional

如果我需要为用户生成一个看起来像他/她的键盘的自定义键盘布局,我该怎么做?

例如这样的事情:

enter image description here

法语、瑞典语、英语、加拿大语等会有不同的布局,对吧。这是大量工作还是只是使用某种内置的 .NET 区域类的问题?

最佳答案

按键会生成硬件事件,向 Windows 操作系统报告“扫描代码”。然后根据扫描码以及其他键盘状态因素(Caps Lock 状态、Shift/Alt/Ctrl 键状态,以及任何未决的死键击)。转换后的 VK 值由 KeyDown 事件等报告。

从扫描码到 VK 码的转换取决于当前的输入语言环境——简单地说,输入语言环境定义了扫描码和虚拟键码之间的映射。参见 the MSDN documentation有关键盘输入的完整说明。

通过反转这个查找过程,可以确定每个虚拟键码对应的扫描码(当然,由于shift/ctrl/alt状态等,相同的扫描码会映射到多个VK码)。 Win32 API 提供 MapVirtualKeyEx函数来执行此映射,通过使用 MAPVK_VK_TO_VSC_EX 选项。您可以使用它来确定哪个扫描代码生成特定的 VK 代码。

不幸的是,这是您可以通过编程实现的最大程度 - 无法确定键盘的物理布局或每个扫描代码的按键位置。然而,大多数物理键盘的接线方式相同,因此(例如)左上键在大多数物理键盘设计上将具有相同的扫描码。您可以使用此假定约定来推断与扫描代码对应的物理位置,具体取决于基本物理键盘布局(101 键、102 键等)。不能保证,但这是一个非常安全的猜测。

以下代码摘 self 编写的一个更大的键盘处理库(我一直打算将其开源但没有时间)。该方法初始化一个数组 (this._virtualKeyScanCodes),该数组由给定输入语言环境的 VK 代码索引(存储在 this._inputLanguage 中,声明为 System .Windows.Forms.InputLanguage。您可以使用该数组通过检查例如 this._virtualKeyScanCodes[VK_NUMPAD0] 来确定对应于 VK 代码的扫描代码 - 如果扫描代码为零, 那么该 VK 在当前输入区域的键盘上不可用;如果它非零,则它是您可以从中推断出物理键的扫描码。

不幸的是,当您进入死键领域时,事情会比这稍微复杂一些(例如,产生重音字符的多个键组合)。这太复杂了,现在无法进入,但 Michael S. Kaplan 写了一个详细的系列 blog posts如果你想进一步探索这个。祝你好运!

private void Initialize()
{
    this._virtualKeyScanCodes = new uint[MaxVirtualKeys];

    // Scroll through the Scan Code (SC) values and get the Virtual Key (VK)
    // values in it. Then, store the SC in each valid VK so it can act as both a 
    // flag that the VK is valid, and it can store the SC value.
    for (uint scanCode = 0x01; scanCode <= 0xff; scanCode++)
    {
        uint virtualKeyCode = NativeMethods.MapVirtualKeyEx(
            scanCode, 
            NativeMethods.MAPVK_VSC_TO_VK, 
            this._inputLanguage.Handle);
        if (virtualKeyCode != 0)
        {
            this._virtualKeyScanCodes[virtualKeyCode] = scanCode;
        }
    }

    // Add the special keys that do not get added from the code above
    for (KeysEx ke = KeysEx.VK_NUMPAD0; ke <= KeysEx.VK_NUMPAD9; ke++)
    {
        this._virtualKeyScanCodes[(uint)ke] = NativeMethods.MapVirtualKeyEx(
            (uint)ke, 
            NativeMethods.MAPVK_VK_TO_VSC, 
            this._inputLanguage.Handle);
    }

    this._virtualKeyScanCodes[(uint)KeysEx.VK_DECIMAL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_DECIMAL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_DIVIDE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_DIVIDE, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_CANCEL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_CANCEL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);

    this._virtualKeyScanCodes[(uint)KeysEx.VK_LSHIFT] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LSHIFT, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RSHIFT] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RSHIFT, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LCONTROL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LCONTROL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RCONTROL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RCONTROL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LMENU] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LMENU, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RMENU] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RMENU, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LWIN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LWIN, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RWIN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RWIN, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_PAUSE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_PAUSE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_UP] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_UP, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_DOWN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_DOWN, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_MUTE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_MUTE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);

    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_NEXT_TRACK] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_NEXT_TRACK, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_PREV_TRACK] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_PREV_TRACK, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_PLAY_PAUSE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_PLAY_PAUSE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_STOP] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_STOP, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);

    this._stateController = new KeyboardStateController();
    this._baseVirtualKeyTable = new VirtualKeyTable(this);
}

编辑:另见 this question这与你的相似。

关于c# - 是否可以创建与所用键盘相同的键盘布局?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4822261/

相关文章:

c# - 设计基于组件的 asp.net 应用程序的最佳实践

c# - 标记枚举混淆 C#

c# - 列出已应用属性的对象属性

c# - 如何告诉应用程序从我的自定义 app.config 文件而不是默认文件中读取 <runtime>

c# - 使用动态值而不是固定值的范围属性

c# - asp.net 上的全局化问题 : it's not updating accentuated words

c# - 动态创建的 PDF 不在 64 位 IE9 窗口中嵌入/呈现内联

c# - 枚举 DataTable,过滤项目,然后还原为 DataTable

c# - 为什么我不能在设计时在 XAML 中使用 CultureInfo.CurrentCulture?

c# - 从工作线程更改主线程的 CultureInfo