I am working on a WPF application that integrates CefSharp to render web content. Initially, we used CefSharp.Wpf, and everything worked as expected, including the ability to detect keyboard layout changes using InputLanguageManager
. However, due to performance issues with CefSharp.Wpf
, we decided to switch to CefSharp.Wpf.Hwnd, which significantly improved performance.
Unfortunately, after switching to CefSharp.Wpf.Hwnd
, we encountered a problem: InputLanguageManager
stopped detecting keyboard layout changes when the focus is inside the browser window (the Hwnd-rendered area). When the browser area loses focus (e.g., when we click elsewhere in the application or in another window, like Excel or Notepad), InputLanguageManager
starts working correctly again.
Using InputLanguageManager
:
CefSharp.Wpf
, but stopped detecting layout changes with CefSharp.Wpf.Hwnd
once the browser area is in focus.Implementing a Custom GetKeyboardLayout
Method:
GetForegroundWindow
, GetWindowThreadProcessId
, and GetKeyboardLayout
).Here’s a simplified version of our method:
[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}");
}
Using Native Hooks (SetWindowsHookEx
):
SetWindowsHookEx
to intercept keyboard events and detect layout changes.Here’s a basic template we used:
[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);
}
When focus is inside CefSharp.Wpf.Hwnd, neither InputLanguageManager
nor our custom GetKeyboardLayout
method or native keyboard hooks detect keyboard layout changes.
All approaches work correctly when the focus moves outside the CefSharp.Wpf.Hwnd
area, either to another WPF element or an external application.
How can I detect keyboard layout changes (e.g., switching between English and Russian) in my WPF application when the focus is inside the CefSharp.Wpf.Hwnd
-rendered area? Is there a way to restore the functionality of InputLanguageManager
, our custom GetKeyboardLayout
method, or native hooks when using CefSharp.Wpf.Hwnd
?
Any suggestions or workarounds would be greatly appreciated!
I'm answering my own question since the user @amaitland provided a solution in the comments but didn't create a formal answer. Here's the solution that worked:
To fix the issue with CefSharp.Wpf.Hwnd
and keyboard layout changes, you need to disable the multi-threaded message loop and manually call Cef.DoMessageLoopWork()
on the UI thread using a DispatcherTimer
.
Solution:
1. Disable Multi-Threaded Message Loop:
var settings = new CefSettings();
settings.MultiThreadedMessageLoop = false;
Cef.Initialize(settings);
2. Set up a Timer to Call 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();
}
This resolved my problem of detecting keyboard layout changes while focused on the CefSharp.Wpf.Hwnd
browser window.