我正在开发一个 WPF 应用程序,它集成了 CefSharp 来呈现 Web 内容。最初,我们使用 CefSharp.Wpf,一切都按预期工作,包括使用
InputLanguageManager
检测键盘布局更改的能力。但是,由于 CefSharp.Wpf
的性能问题,我们决定切换到 CefSharp.Wpf.Hwnd,这显着提高了性能。
不幸的是,切换到
CefSharp.Wpf.Hwnd
后,我们遇到了一个问题:当焦点位于浏览器窗口(Hwnd 渲染区域)内时,InputLanguageManager
停止检测键盘布局变化。当浏览器区域失去焦点时(例如,当我们单击应用程序中的其他位置或另一个窗口(例如 Excel 或记事本)时),InputLanguageManager
会再次开始正常工作。
使用
InputLanguageManager
:
CefSharp.Wpf
完美配合,但一旦浏览器区域处于焦点状态,就停止使用 CefSharp.Wpf.Hwnd
检测布局更改。实现自定义
GetKeyboardLayout
方法:
GetForegroundWindow
、GetWindowThreadProcessId
和 GetKeyboardLayout
)。这是我们方法的简化版本:
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
private void DetectKeyboardLayout()
{
IntPtr foregroundWindow = GetForegroundWindow();
uint processId;
uint threadId = GetWindowThreadProcessId(foregroundWindow, out processId);
IntPtr layout = GetKeyboardLayout(threadId);
Console.WriteLine($"Current keyboard layout: {layout}");
}
使用原生 Hooks (
SetWindowsHookEx
):
SetWindowsHookEx
使用全局钩子来拦截键盘事件并检测布局更改。这是我们使用的基本模板:
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private LowLevelKeyboardProc _proc = HookCallback;
private IntPtr _hookID = IntPtr.Zero;
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
// Detect keyboard layout change here...
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private void SetHook()
{
_hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, IntPtr.Zero, 0);
}
当焦点位于 CefSharp.Wpf.Hwnd 内部时,
InputLanguageManager
和我们的自定义 GetKeyboardLayout
方法或本机键盘挂钩都不会检测到键盘布局更改。
当焦点移出
CefSharp.Wpf.Hwnd
区域(移至另一个 WPF 元素或外部应用程序)时,所有方法都能正常工作。
当焦点位于
CefSharp.Wpf.Hwnd
渲染区域内时,如何检测 WPF 应用程序中的键盘布局更改(例如,在英语和俄语之间切换)?有没有办法在使用 InputLanguageManager
时恢复 GetKeyboardLayout
、我们自定义 CefSharp.Wpf.Hwnd
方法或本机钩子的功能?
任何建议或解决方法将不胜感激!
我正在回答我自己的问题,因为用户@amaitland在评论中提供了解决方案,但没有创建正式的答案。这是有效的解决方案:
要解决
CefSharp.Wpf.Hwnd
和键盘布局更改的问题,您需要禁用多线程消息循环并使用 Cef.DoMessageLoopWork()
在 UI 线程上手动调用 DispatcherTimer
。
解决方案:
1。禁用多线程消息循环:
var settings = new CefSettings();
settings.MultiThreadedMessageLoop = false;
Cef.Initialize(settings);
2。设置定时器来呼叫
Cef.DoMessageLoopWork()
:
private DispatcherTimer timer;
private void StartTimer()
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000 / 30); // 30 FPS
timer.Tick += UiThreadTimerTick;
timer.Start();
}
private void StopTimer()
{
timer.Tick -= UiThreadTimerTick;
timer.Stop();
}
private void UiThreadTimerTick(object sender, EventArgs e)
{
Cef.DoMessageLoopWork();
}
这解决了我在专注于
CefSharp.Wpf.Hwnd
浏览器窗口时检测键盘布局更改的问题。