【问题标题】:Is it possible to create a keyboard layout that is identical to the keyboard used?是否可以创建与使用的键盘相同的键盘布局?
【发布时间】:2019-09-30 11:13:33
【问题描述】:

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

例如这样的:

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

【问题讨论】:

  • 看起来像是一个不错的附加组件;)
  • 您只需使用 Google 图片即可获取国际键盘布局的图片。例如,尝试使用谷歌搜索 French keyboard layout
  • 是的,但我需要使用预定义的样式从头开始绘制它。另外,如果我使用布局,那么我需要存储所有图像,对吗?我需要在运行时动态地执行此操作(启动自定义对话框时)。
  • @Oded:你想这样做是为了大家的利益吗? :O
  • @Joan Venge - 我正在考虑一个 VS 插件来映射 VS 键盘快捷键,但由于有组合的,我看不出如何创建有用的映射。跨度>

标签: c# .net globalization regional


【解决方案1】:

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

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

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

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

以下代码摘自我编写的一个更大的键盘处理库(我一直打算将其开源,但没有时间)。该方法初始化一个数组 (this._virtualKeyScanCodes),该数组由给定输入语言环境的 VK 代码索引(存储在声明为 System.Windows.Forms.InputLanguagethis._inputLanguage 中。您可以使用该数组来确定对应于VK 代码通过检查例如this._virtualKeyScanCodes[VK_NUMPAD0] - 如果扫描代码为零,则该 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,与您的类似。

【讨论】:

  • +1 这比我们在 cmets 中讨论的我的回答要好 :)
【解决方案2】:

没有包含键盘布局的内置 .NET 类。键盘布局是操作系统的功能,通常是 Windows。当 .NET 介入时,按下的键已经从硬件事件转换为软件事件。如果您想看到这一点,请找到 2 个键盘布局,其中一个键在它们之间移动。在Key_Down 事件上设置一个带有事件处理程序的虚拟应用程序,然后注意事件参数是相同的;如果您按下 - 键,则无论 - 键位于何处,您都按下了 - 键。

【讨论】:

  • 谢谢,这是有道理的。但是他们如何为说俄语,中文键盘做这些?就像什么键返回 Keys.A 等。还有一些应用程序是如何做到这一点的,它们能够重新创建您在屏幕上使用的键盘布局。 Windows OSD 键盘能做到这一点吗?
  • 他们可能正在使用windows OSD;因为这是一个操作系统实用程序,它实际上是布局感知的;见microsoft.com/windowsxp/using/accessibility/osklayout.mspx
  • 谢谢,但他们如何使用它?它不是另一个应用程序,看起来好像没有 API 可以连接,对吧?
  • 嗯,它是一个 Microsoft 应用程序,这意味着它可能可以通过 Win32 或(更有可能)在注册表中乱搞,设置一些值,然后使用 Process.Start() 来控制“显示”它。我自己没用过;您可能想尝试将其提升为自己的问题。
  • 谢谢,但这是我无法接受的骇人听闻的方式。我的解决方案使用所有具有自定义样式和逻辑的 WPF,因此入侵 OSD 应用程序看起来很不合适。
猜你喜欢
  • 2020-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多