C# WPF应用嵌入Unity后的输入焦点与跨组件输入捕获问题
问题梳理
- 将Unity进程嵌入WPF的
syncfusion:DockPanel(基于EmbeddedWindow示例)后,初始Unity获焦时WPF无法捕获全局快捷键(如Ctrl+S) - 点击WPF其他控件后,Unity无法重新获取完整焦点,仅左右鼠标点击可用,其余键鼠输入失效
- Unity区域内的鼠标滚轮事件无法被WPF的
PreviewMouseWheel捕获,无法通过双向NamedPipe传递给Unity - 尝试过
ActivateUnityWindow、RegisterRawInputDevices、SetParent、SetForegroundWindow等方案及管理员运行,均未解决;临时方案需点击Unity区域才能恢复部分输入,无法满足双向输入需求
解决方案建议
1. 全局键盘钩子捕获WPF专属快捷键
通过Windows API的全局键盘钩子直接拦截需要WPF处理的全局按键,无需依赖WPF控件焦点:
private static IntPtr _hookId = IntPtr.Zero; private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); private static HookProc _hookCallback = HookCallback; private static IntPtr SetHook(HookProc proc) { using (var curProcess = Process.GetCurrentProcess()) using (var curModule = curProcess.MainModule) { return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } } private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) { var keyData = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); // 捕获Ctrl+S快捷键 if (keyData.vkCode == (int)Keys.S && (GetKeyState((int)Keys.ControlKey) & 0x8000) != 0) { Application.Current.Dispatcher.Invoke(() => { // 执行WPF侧的保存逻辑 YourSaveMethod(); }); // 可选:阻止事件传递到Unity,避免重复处理 return (IntPtr)1; } } return CallNextHookEx(_hookId, nCode, wParam, lParam); } // 需引入的Windows API声明 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); private const int WH_KEYBOARD_LL = 13; private const int WM_KEYDOWN = 0x0100; [StructLayout(LayoutKind.Sequential)] private struct KBDLLHOOKSTRUCT { public int vkCode; public int scanCode; public int flags; public int time; public IntPtr dwExtraInfo; }
2. 自定义容器控件接管输入路由
继承syncfusion:DockPanel实现自定义容器,重写输入事件直接处理Unity区域的滚轮输入,并同步焦点状态:
public class UnityHostDockPanel : DockPanel { private IntPtr _unityWindowHandle; private INamedPipeClient _pipeClient; // 你的NamedPipe客户端实例 public void Initialize(IntPtr unityHandle, INamedPipeClient pipeClient) { _unityWindowHandle = unityHandle; _pipeClient = pipeClient; } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { base.OnPreviewMouseDown(e); // 激活Unity窗口确保输入能被Unity接收 SetForegroundWindow(_unityWindowHandle); // 保持容器的WPF焦点,确保后续输入事件能被捕获 Focus(); } protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) { base.OnPreviewMouseWheel(e); // 直接通过NamedPipe发送滚轮数据到Unity _pipeClient.SendMessage($"MouseWheel:{e.Delta}"); e.Handled = true; // 阻止事件继续传递,避免干扰 } // Windows API声明 [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); }
3. 调整Unity窗口属性避免焦点抢占
启动Unity进程时,设置WS_EX_NOACTIVATE扩展样式,让Unity窗口不会自动抢占全局焦点,输入优先由WPF捕获后转发:
var startInfo = new ProcessStartInfo("YourUnityBuild.exe") { CreateNoWindow = false, UseShellExecute = false, WorkingDirectory = Path.GetDirectoryName("YourUnityBuild.exe") }; var unityProcess = Process.Start(startInfo); unityProcess.WaitForInputIdle(); var unityHandle = unityProcess.MainWindowHandle; // 设置窗口扩展样式,禁止自动激活 const int GWL_EXSTYLE = -20; const int WS_EX_NOACTIVATE = 0x08000000; var exStyle = GetWindowLongPtr(unityHandle, GWL_EXSTYLE); SetWindowLongPtr(unityHandle, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE); // 将Unity窗口嵌入到自定义容器 SetParent(unityHandle, _unityHostDockPanel.Handle); // Windows API声明 [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")] private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
4. 双向焦点同步维护
维护一个全局焦点状态变量,在WPF控件获焦时记录当前控件;当需要Unity接收输入时,通过SendInput模拟输入事件发送给Unity,同时保持WPF的输入路由正常,避免焦点锁死。
内容的提问来源于stack exchange,提问作者Vincent Leduc
相关产品推荐
相关产品推荐

