You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 18:31:02