正确检测键盘布局
Properly detect keyboard layout
我有一个 winforms 应用程序,我需要获取用户当前的键盘布局。为此,我使用 System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName
.
只要用户将表单作为活动表单 window,它就可以正常工作,一旦他关注其他内容并更改语言,前者 属性 将不会 return 正确的值, 它将 return 表单仍处于活动状态时最后使用的语言 window。
有没有一种方法可以获取用户键盘布局的名称,即使他没有关注表单,对可以使用的内容没有限制。
您可能已经知道,System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName 属性 returns 当前线程的键盘布局,无论您选择何种布局,执行线程都将保持不变除非你 select window 并更改 window 的键盘输入布局。
也就是说,您实际上是想检查当前的键盘布局文化,并能够知道它何时发生变化。前一段时间我有类似的要求,我想出了以下代码,对我很有帮助:
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
};
这里有两点需要注意。 Windows 中的键盘 selection 是在每个线程的基础上完成的。这允许用户为任何给定的应用程序 select 不同的键盘语言环境,Windows 将遵守该设置,同时让其他应用程序保持独立。
用户通过在 Windows 任务栏中启用语言栏(对于已安装的键盘)来执行此操作。如果他们 select 使用不同的键盘而另一个应用程序具有焦点,则 selection 仅适用于该应用程序。此外,如果您的应用程序没有焦点,它也无能为力。通过以这种方式使用语言栏,用户明确表示他们打算让 selected 键盘仅应用于活动应用程序。您的应用程序无法找到它,因为从 Windows' 的角度来看,它是您应用程序业务的 none。
现在,如果您想知道用户是否更改了整个系统的键盘(使用控制面板小程序),这是可行的。如果您的应用程序没有焦点,则无法捕获通知消息。您的表单仍会将当前语言视为它开始使用的语言。但是,系统范围的更改确实将 InputLanguage.DefaultInputLanguage.
更改为新的 selected 键盘。因此,重写 OnActivated
处理程序并检查默认语言的值,而不是当前语言(每个线程)的值。
我有一个 winforms 应用程序,我需要获取用户当前的键盘布局。为此,我使用 System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName
.
只要用户将表单作为活动表单 window,它就可以正常工作,一旦他关注其他内容并更改语言,前者 属性 将不会 return 正确的值, 它将 return 表单仍处于活动状态时最后使用的语言 window。
有没有一种方法可以获取用户键盘布局的名称,即使他没有关注表单,对可以使用的内容没有限制。
您可能已经知道,System.Windows.Forms.InputLanguage.CurrentInputLanguage.LayoutName 属性 returns 当前线程的键盘布局,无论您选择何种布局,执行线程都将保持不变除非你 select window 并更改 window 的键盘输入布局。
也就是说,您实际上是想检查当前的键盘布局文化,并能够知道它何时发生变化。前一段时间我有类似的要求,我想出了以下代码,对我很有帮助:
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
};
这里有两点需要注意。 Windows 中的键盘 selection 是在每个线程的基础上完成的。这允许用户为任何给定的应用程序 select 不同的键盘语言环境,Windows 将遵守该设置,同时让其他应用程序保持独立。
用户通过在 Windows 任务栏中启用语言栏(对于已安装的键盘)来执行此操作。如果他们 select 使用不同的键盘而另一个应用程序具有焦点,则 selection 仅适用于该应用程序。此外,如果您的应用程序没有焦点,它也无能为力。通过以这种方式使用语言栏,用户明确表示他们打算让 selected 键盘仅应用于活动应用程序。您的应用程序无法找到它,因为从 Windows' 的角度来看,它是您应用程序业务的 none。
现在,如果您想知道用户是否更改了整个系统的键盘(使用控制面板小程序),这是可行的。如果您的应用程序没有焦点,则无法捕获通知消息。您的表单仍会将当前语言视为它开始使用的语言。但是,系统范围的更改确实将 InputLanguage.DefaultInputLanguage.
更改为新的 selected 键盘。因此,重写 OnActivated
处理程序并检查默认语言的值,而不是当前语言(每个线程)的值。