正确检测键盘布局

4
我有一个WinForms应用程序,需要获取用户当前的键盘布局。为了实现这一点,我使用System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName
只要用户将窗体作为活动窗口,这个方法就可以正常工作。但是一旦用户将焦点放在其他地方并更改语言,该属性将不会返回正确的值,它将返回窗体仍然是活动窗口时上次使用的语言。
是否有一种方法可以获取用户的键盘布局名称,即使他没有将窗体聚焦,并且没有任何限制可供使用。

您预测用户在 Windows 运行时会更换键盘吗? - user585968
@MickyD 他可能不会,但我需要知道他是否这样做。 - Deadzone
这个.NET类使用Windows API GetKeyboardLayout。正如您所读到的,键盘布局可以由线程定义。该.NET类为当前线程读取它,因此您看到的可能是正确的(只要您不缓存该值)。或者您正在寻找其他信息? - Simon Mourier
2个回答

7
你可能已经知道,System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName属性返回当前线程的键盘布局。无论你选择什么布局,它都会保持在执行线程中,除非你选择该窗口并更改该窗口的键盘输入布局。
也就是说,你实际上想要做的是检查当前的键盘布局语言,并能够知道它何时发生了变化。我之前有类似的需求,以下代码对我很有帮助:
public delegate void KeyboardLayoutChanged(int oldCultureInfo, int newCultureInfo);

class KeyboardLayoutWatcher : IDisposable
{
    private readonly Timer _timer;
    private int _currentLayout = 1033;


    public KeyboardLayoutChanged KeyboardLayoutChanged;

    public KeyboardLayoutWatcher()
    {
        _timer = new Timer(new TimerCallback(CheckKeyboardLayout), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
    }

    [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
    [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hwnd, IntPtr proccess);
    [DllImport("user32.dll")] static extern IntPtr GetKeyboardLayout(uint thread);
    public int GetCurrentKeyboardLayout()
    {
        try
        {
            IntPtr foregroundWindow = GetForegroundWindow();
            uint foregroundProcess = GetWindowThreadProcessId(foregroundWindow, IntPtr.Zero);
            int keyboardLayout = GetKeyboardLayout(foregroundProcess).ToInt32() & 0xFFFF;

            if (keyboardLayout == 0)
            {
                // something has gone wrong - just assume English
                keyboardLayout = 1033;
            }
            return keyboardLayout;
        }
        catch (Exception ex)
        {
            // if something goes wrong - just assume English
            return 1033;
        }
    }

    private void CheckKeyboardLayout(object sender)
    {
        var layout = GetCurrentKeyboardLayout();
        if (_currentLayout != layout && KeyboardLayoutChanged != null)
        {
            KeyboardLayoutChanged(_currentLayout, layout);
            _currentLayout = layout;
        }

    }

    private void ReleaseUnmanagedResources()
    {
        _timer.Dispose();
    }

    public void Dispose()
    {
        ReleaseUnmanagedResources();
        GC.SuppressFinalize(this);
    }

    ~KeyboardLayoutWatcher()
    {
        ReleaseUnmanagedResources();
    }
}

并像这样使用:

        new KeyboardLayoutWatcher().KeyboardLayoutChanged += (o, n) =>
        {
            this.CurrentLayoutLabel.Text = $"{o} -> {n}"; // old and new KB layout
        };

谢谢您的回答,您能提供一个键盘布局ID的文档/描述链接吗? - Deadzone
1
@Deadzone:这里有一个列表,作为一个起点:http://www.pinvoke.net/default.aspx/user32/GetKeyboardLayout.html - deblocker

1
这里需要注意几点。在Windows中,键盘选择是基于每个线程进行的。这使得用户可以为任何应用程序选择不同的键盘区域设置,而Windows会尊重这一选择,同时保持其他应用程序不变。
用户通过在Windows任务栏中启用语言栏(对于已安装的键盘)来实现此操作。如果他们在另一个应用程序具有焦点时选择了不同的键盘,则该选择仅适用于该应用程序。此外,如果您的应用程序没有焦点,则无法对其进行任何处理。通过以这种方式使用语言栏,用户明确表示其意图仅将所选键盘应用于活动应用程序。从Windows的角度来看,您的应用程序无法找出它,因为这与您的应用程序无关。
现在,如果您想知道用户是否更改了整个系统的键盘设置(使用控制面板小程序),那是可行的。如果您的应用程序没有焦点,则无法捕获通知消息。您的表单仍将认为当前语言是它启动的语言。但是,全系统更改会更改 InputLanguage.DefaultInputLanguage 为新选择的键盘。因此,重写 OnActivated 处理程序并检查默认语言的值,而不是当前语言的值(即每个线程)。

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