【问题标题】:Extracting keyboard layouts from windows从窗口中提取键盘布局
【发布时间】:2010-10-14 06:53:10
【问题描述】:

好的,这是一个有点奇怪的问题。

我们有一个触摸屏应用程序(即没有键盘)。当用户需要输入文本时,应用程序会显示虚拟键盘 - WinForms 中手动内置。

为每种新语言手工制作这些东西是猴子的工作。我认为 Windows 必须将此键盘布局信息隐藏在某个 dll 中的某个位置。有没有办法从窗口中获取这些信息?

欢迎其他想法(我认为至少从 xml 文件生成东西肯定比在 VS 中手动生成要好)。

(注意:说了这么多,我注意到有一个日文键盘、状态机等等……所以 XML 可能不够用)

更新:关于这个主题的不错的系列(我相信)here

【问题讨论】:

  • 这是一个非常酷的问题......将布局移植到其他环境(Linux)也可能是可能的,但这当然可能侵犯版权。
  • 使用内置的windows平板键盘不是更方便吗? (显然需要在操作系统中安装正确的扩展,但现在似乎只需将 wacom 插入其中就足够了,所以可能有更多方法?)。
  • 考虑在下面接受我的回答?谢谢。

标签: c# winforms keyboard-layout


【解决方案1】:

Microsoft Keyboard Layout Creator 可以加载系统键盘并将其导出为.klc files。由于它是用 .NET 编写的,因此您可以使用 Reflector 来查看它是如何做到的,并使用反射来驱动它。这是使用以下 C# 代码创建的zip file of .klc files for the 187 keyboards in Windows 8。请注意,我最初是为 Windows XP 编写的,现在使用 Windows 8 和屏幕键盘,它真的很慢并且似乎使任务栏崩溃:/ 但是,它确实有效:)

using System;
using System.Collections;
using System.IO;
using System.Reflection;

class KeyboardExtractor {

    static Object InvokeNonPublicStaticMethod(Type t, String name,
            Object[] args)
    {
        return t.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)
            .Invoke(null, args);
    }

    static void InvokeNonPublicInstanceMethod(Object o, String name,
            Object[] args)
    {
        o.GetType().GetMethod(name, BindingFlags.Instance |
                BindingFlags.NonPublic) .Invoke(o, args);
    }

    static Object GetNonPublicProperty(Object o, String propertyName) {
        return o.GetType().GetField(propertyName,
                BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(o);
    }

    static void SetNonPublicField(Object o, String propertyName, Object v) {
        o.GetType().GetField(propertyName,
                BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue(o, v);
    }

    [STAThread] public static void Main() {
        System.Console.WriteLine("Keyboard Extractor...");

        KeyboardExtractor ke = new KeyboardExtractor();
        ke.extractAll();

        System.Console.WriteLine("Done.");
    }

    Assembly msklcAssembly;
    Type utilitiesType;
    Type keyboardType;
    String baseDirectory;

    public KeyboardExtractor() {
        msklcAssembly = Assembly.LoadFile("C:\\Program Files\\Microsoft Keyboard Layout Creator 1.4\\MSKLC.exe");
        utilitiesType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Utilities");
        keyboardType = msklcAssembly.GetType("Microsoft.Globalization.Tools.KeyboardLayoutCreator.Keyboard");

        baseDirectory = Directory.GetCurrentDirectory();
    }

    public void extractAll() {

        DateTime startTime = DateTime.UtcNow;

        SortedList keyboards = (SortedList)InvokeNonPublicStaticMethod(
                utilitiesType, "KeyboardsOnMachine", new Object[] {false});

        DateTime loopStartTime = DateTime.UtcNow;

        int i = 0;
        foreach (DictionaryEntry e in keyboards) {
            i += 1;
            Object k = e.Value;

            String name = (String)GetNonPublicProperty(k, "m_stLayoutName");
            String layoutHexString = ((UInt32)GetNonPublicProperty(k, "m_hkl"))
                .ToString("X");

            TimeSpan elapsed = DateTime.UtcNow - loopStartTime;
            Double ticksRemaining = ((Double)elapsed.Ticks * keyboards.Count)
                        / i - elapsed.Ticks;
            TimeSpan remaining = new TimeSpan((Int64)ticksRemaining);
            String msgTimeRemaining = "";
            if (i > 1) {
                // Trim milliseconds
                remaining = new TimeSpan(remaining.Hours, remaining.Minutes,
                        remaining.Seconds);
                msgTimeRemaining = String.Format(", about {0} remaining",
                        remaining);
            }
            System.Console.WriteLine(
                    "Saving {0} {1}, keyboard {2} of {3}{4}",
                    layoutHexString, name, i, keyboards.Count,
                    msgTimeRemaining);

            SaveKeyboard(name, layoutHexString);

        }

        System.Console.WriteLine("{0} elapsed", DateTime.UtcNow - startTime);

    }

    private void SaveKeyboard(String name, String layoutHexString) {
        Object k = keyboardType.GetConstructors(
                BindingFlags.Instance | BindingFlags.NonPublic)[0]
            .Invoke(new Object[] {
                        new String[] {"", layoutHexString},
                    false});

        SetNonPublicField(k, "m_fSeenOrHeardAboutPropertiesDialog", true);
        SetNonPublicField(k, "m_stKeyboardTextFileName",
                String.Format("{0}\\{1} {2}.klc",
                    baseDirectory, layoutHexString, name));
        InvokeNonPublicInstanceMethod(k, "mnuFileSave_Click",
                new Object[] {new Object(), new EventArgs()});

        ((IDisposable)k).Dispose();
    }

}

基本上,它获取系统上所有键盘的列表,然后对每个键盘,将其加载到 MSKLC 中,设置“另存为”文件名,判断是否已经配置了自定义键盘属性,然后模拟一个点击文件 -> 保存菜单项。

【讨论】:

  • -1 因为无法再找到压缩包。再次访问时我会更新。
  • 太棒了!除非编辑答案,否则我无法更改投票。添加一个点或空格或其他东西,我会改变投票。
  • @Kent 既然你知道你不能收回它,也许你将来会更明智地投反对票
  • @andrew:好吧,如果你改变了答案,我不会投反对票。 :-) 本站视个人主观意见而定。我的意见是该网站应该尽可能自给自足。我通过投票来表达我的意见。对我来说,指向某人域中 tmp 文件夹的死链接可以作为一个示例,说明何时最好在答案中包含更多信息或将文件托管在更可靠的来源。其他人可能不同意,并按他们认为合适的方式投票。这当然不是私人的!
  • @Kent 好的,现在更自给自足了。
【解决方案2】:

为什么不使用屏幕键盘 (osk.exe)?看起来你重新发明了轮子。而且不是最简单的!

【讨论】:

  • 一个外观问题,我担心。请记住,这是一个 pre-wpf 项目,他们手工重新编码了所有控件,以使它们更漂亮。
  • 触摸屏也有点小。
【解决方案3】:

我知道这些 DLL 文件的路径在哪里:

在您的注册表中,您会看到:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts

每个分支都有一些值,例如"Layout File"="KBDSP.dll"。根目录是

C:\Windows\System32

C:\Windows\SystemWOW64

这些是所有键盘布局文件所在的位置。例如,KBDUS.dll 表示“美国键盘”。

我尝试将 DLL 文件替换为我由 MSKLC 制作的自定义 DLL,我发现它会在“语言”-“输入法”-“预览”中自动加载布局映射图像:

所以我们知道映射存在于 DLL 中。

【讨论】:

    【解决方案4】:

    众所周知,MSKLC 无法忠实地导入和重现 Windows 提供的所有 .DLL 文件的键盘布局,尤其是 Windows 8 及更高版本中的那些。如果您无法从中提取任何有意义或有用的信息,那么知道这些文件在哪里也没有任何好处。 Michael Kaplan 在他的博客(他是 MSKLC 的开发人员)上记录了这一点,我看到你已经链接到上面。

    当 MSKLC 遇到任何它不理解的内容时,该部分将被删除。 使用 MSKLC 提取布局适用于大多数键盘,但也有少数——即 Cherokee 键盘和日韩键盘(仅举几例,我不确定还有多少)——提取的布局不会准确或完全反映键盘的实际使用和功能。 Cherokee 键盘具有 MSKLC 不支持的链接死键。远东键盘具有 MSKLC 不知道的修饰键——这意味着缺少整个层/转换状态!

    Michael Kaplan 提供了一些代码并解开了 MSLKC 的一些秘密以及可用于绕过其中一些限制的随附软件,但它需要大量的手工操作 - 正是您想要做的避免!此外,Michael 的目标是创建具有 MSKLC 无法创建或理解的功能但在 Windows 中可以工作的键盘(这与 OP 试图完成的相反)。

    我确信我的解决方案来得太晚,无法用于 OP,但也许它在未来对处于类似情况的人会有所帮助。这就是我发布这篇文章的希望和原因。

    到目前为止,我所做的只是解释其他答案是不够的。即使是最好的键盘也不会也不可能完全准确地再现所有 Windows 的本机键盘并将它们呈现为 KLC 源文件。这真的很不幸,这当然不是作者的错,因为那是一段非常聪明的代码/脚本!值得庆幸的是,脚本和源文件(其链接可能有效也可能无效)对大多数 Windows 键盘以及 MSKLC 创建的任何自定义键盘都很有用且有效。

    具有 MSKLC 不支持的高级功能的键盘是由 Windows DDK 创建的,但这些功能并未正式记录。虽然人们可以通过研究 MSKLC 提供的源文件来了解它们的潜力。

    遗憾的是,我能提供的唯一解决方案是第三方付费软件,名为KbdEdit。我相信它是目前唯一真正能够忠实地解码和重新创建任何 Windows 提供的键盘的可用解决方案——尽管有一些高级功能甚至无法重现(例如执行特殊母语的组合键/热键)功能;例如:Ctrl+CapsLock 激活 KanaLock(日本修改器层)。KbdEdit 确实忠实地再现了 MSKLC 剥离的修改器层,如果您不支持,它只是不支持激活该转换状态的这种替代方法有一个带有假名锁定键的日文键盘。不过,它允许您将键盘上的键转换为假名键(可能是 Scroll Lock?)。

    幸运的是,这些不受支持的功能甚至都不适用于屏幕键盘。

    KbdEdit 是一个非常强大且令人惊叹的工具,我为此付出的每一分钱都物有所值! (对于几乎任何其他付费软件,我都不会这么说……) 尽管 KbdEdit 是第 3 方软件,但它只需要创建键盘,而不是使用它们。它创建的所有键盘都可以在没有安装 KbdEdit 的任何 Windows 系统上本地工作。 它支持多达 15 种修饰状态和三个附加修饰键,其中一个是可切换的,类似于 CapsLock。它还支持链接死键,以及重新映射大多数键盘上的任何键。

    【讨论】:

      【解决方案5】:

      请检查以下 Windows API

       [DllImport("user32.dll")]
       private static extern long LoadKeyboardLayout(string pwszKLID, uint Flags);
      

      查看MSDN here

      【讨论】:

      • 嗯,这只是在当前进程/线程中加载一个键盘布局,它不允许我在视觉上重现它(即,什么键在什么位置)。
      • 虽然您可能在这里有所了解,但显然 MapVirtualKey 可以将扫描码(硬件)映射到 VirtualKey,而 GetKeyNameText 可以将扫描码转换为字符串...
      猜你喜欢
      • 1970-01-01
      • 2021-06-23
      • 1970-01-01
      • 2013-07-19
      • 1970-01-01
      • 2019-01-27
      • 2019-12-09
      • 2013-02-22
      • 2019-05-28
      相关资源
      最近更新 更多