none
unity生成的EXE程序嵌入WPF中后,如何在点击WPF窗口之后再重获unity的焦点 RRS feed

  • 问题

  • 如题,做了一个unity项目生成EXE后,使用 Win32Api.SetParent()将其嵌入到一个WPF控件,也可以正常运行。

    如下图所示:

    但是当我点击WPF界面后,Unity界面将会失去焦点。也就是Unity本身的点击和鼠标类事件都没有反应了。

    请问有什么办法解决吗?

     public class AppContainer : ContentControl
        {
            private WindowsFormsHost _winFormHost;
            private System.Windows.Forms.Panel _hostPanel;
            private readonly ManualResetEvent _eventDone = new ManualResetEvent(false);
            private Process _process = null;
            internal IntPtr _embededWindowHandle;
    
            public AppContainer()
            {
                var res = new ResourceDictionary
                {
                    Source = new Uri("pack://application:,,,/PowerPixel;component/Embed/AppContainer.xaml")
                };
                Application.Current.Resources.MergedDictionaries.Add(res);
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                _winFormHost = GetTemplateChild("PART_Host") as WindowsFormsHost;
                if (_winFormHost != null)
                {
                    _hostPanel = new System.Windows.Forms.Panel();
                    _winFormHost.Child = _hostPanel;
                }
            }
    
            public bool StartAndEmbedProcess(string processPath)
            {
                if (null != _process)
                    return true;
    
                var isStartAndEmbedSuccess = false;
                _eventDone.Reset();
    
                // Start the process 
                _process = Process.Start(processPath);
                if (_process == null)
                {
                    return false;
                }
    
                // Wait for process to be created and enter idle condition 
                _process.WaitForInputIdle();
    
                // Get the main handle
                var thread = new Thread(() =>
                {
                    while (true)
                    {
                        if (_process.MainWindowHandle != (IntPtr)0)
                        {
                            _eventDone.Set();
                            break;
                        }
                        Thread.Sleep(10);
                    }
                });
                thread.Start();
    
                //嵌入进程
                if (_eventDone.WaitOne(10000))
                {
                    isStartAndEmbedSuccess = EmbedApp(_process);
                    if (!isStartAndEmbedSuccess)
                    {
                        CloseApp(_process);
                    }
                }
                return isStartAndEmbedSuccess;
            }
    
            public bool EmbedExistProcess(Process process)
            {
                _process = process;
                return EmbedApp(process);
            }
    
            /// <summary>
            /// 将外进程嵌入到当前程序
            /// </summary>
            /// <param name="process"></param>
            private bool EmbedApp(Process process)
            {
                //是否嵌入成功标志,用作返回值
                var isEmbedSuccess = false;
                //外进程句柄
                var processHwnd = process.MainWindowHandle;
                //容器句柄
                var panelHwnd = _hostPanel.Handle;
    
                if (processHwnd != (IntPtr)0 && panelHwnd != (IntPtr)0)
                {
                    //把本窗口句柄与目标窗口句柄关联起来
                    var setTime = 0;
                    while (!isEmbedSuccess && setTime < 50)
                    {
                        // Put it into this form
                        isEmbedSuccess = Win32Api.SetParent(processHwnd, panelHwnd) != 0;
                        Thread.Sleep(10);
                        setTime++;
                    }
    
                    // Remove border and whatnot
                    Win32Api.SetWindowLong(processHwnd, Win32Api.GWL_STYLE, Win32Api.WS_CHILDWINDOW | Win32Api.WS_CLIPSIBLINGS | Win32Api.WS_CLIPCHILDREN | Win32Api.WS_VISIBLE);
    
                    // Move the window to overlay it on this window
                    Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
                }
    
                if (isEmbedSuccess)
                {
                    _embededWindowHandle = _process.MainWindowHandle;
                }
    
                return isEmbedSuccess;
            }
    
            protected override void OnRender(DrawingContext drawingContext)
            {
                if (_process != null)
                {
                    Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
                }
                base.OnRender(drawingContext);
            }
    
            protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
            {
                InvalidateVisual();
                base.OnRenderSizeChanged(sizeInfo);
            }
    
            /// <summary>
            /// 关闭进程
            /// </summary>
            /// <param name="process"></param>
            private void CloseApp(Process process)
            {
                if (process != null && !process.HasExited)
                {
                    if (process.CloseMainWindow())
                        process.Close();
                    //process.Kill();//这种情况下unity不会触发destroy事件或者applicationquit事件
                }
            }
    
            public void CloseProcess()
            {
                CloseApp(_process);
                _process = null;
            }
        }

    <Style TargetType="{x:Type local:AppContainer}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:AppContainer}">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid Background="LightGray">
                                <WindowsFormsHost x:Name="PART_Host"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>


    • 已编辑 heater 2019年11月29日 4:28
    2019年11月28日 13:11

答案

  • 可以重写Resize方法,在里面修改窗体的大小位置然后激活

    • 已标记为答案 heater 2020年1月19日 9:28
    2020年1月19日 9:28
  • 启动时有一个闪亮的解决方法:

    start进程的时候采用unity命令行参数,Arguments = "-popupwindow";去掉窗体的边框,不使用代码中的SetWindowLong()

    • 已标记为答案 heater 2020年1月19日 9:31
    2020年1月19日 9:31

全部回复

  • 并且这种失焦的现象不是必现的,还不确定是什么原因造成的失焦
    2019年11月29日 2:25
  • 有可能是消息列队的问题。

    如果可以使用winform的窗口经行嵌套,看看是否这种现象会出现。

    如果不会复现,可以在wpf中手动编写消息循环(虽然wpf底层也是消息循环,手动确保传播)

    如果也会复现,这个就只能慢慢调了,使用SetParent时会有很多奇怪的问题。

    不过鼠标和事件没有反应多半是消息列队了

    2019年11月29日 2:45
  • 你好 我是使用的Winform窗口。我待会儿重新编辑一下贴上代码。

    消息列队我还没有研究过。但是我嵌入的Unity中的事件响应也和WPF中的消息列队有关系吗

    2019年11月29日 4:25
  • 更新一下:

    问题的原因应该不是失焦,因为我的unity中有一个捕获鼠标坐标的事件是可以正常工作的。

    但是unity中的鼠标点击事件不能正常运行。

    怀疑是我点击Unity窗口的时候window将这个事件传递给了我的WPF窗口,并且点击的时候确实WPF有高亮。所以我的unity点击事件就没有响应。

    但是还不知道怎么解决这一个问题

    2019年11月29日 7:55
  • 你使用完全的winform的项目,不用host的来装载。 使用setparent麻烦事真的不少
    2019年11月29日 10:00
  • 现在来说应该不可能改成Winform了。。。

    这些麻烦事有办法解决吗?


    2019年11月30日 6:00
  • 我的意思是用winform测试,主要是测试完全的消息循环。

    不是改成winform。

    因为不确定到底是哪个环节的缺失了,Setparent真的很麻烦


    • 已编辑 ARM830 2019年11月30日 7:28
    2019年11月30日 7:28
  • 使用Winform配合SetParent(),键盘和鼠标滑轮会失效,鼠标左右键正常
    2019年12月2日 4:07
  • 使用WinForm配合unity传参指令嵌入的时候,刚启动的时候键盘和鼠标滑轮会失效,但是Rezise父窗体后键盘和鼠标滑轮正常
    2019年12月2日 4:13
  • WPF中调整父窗口之后untiy也会正常吗?

    如果是 那就好办

    如果不是,尝试编写消息循环后调整WPF窗口大小后再试试。

    什么功能非得要untiy和wpf?

    如果可以试试这篇博文中的方法

    https://www.cnblogs.com/wangboke/p/5329483.html


    • 已编辑 ARM830 2019年12月2日 10:30
    2019年12月2日 9:57
  • 是的,使用_process.StartInfo.Arguments = "-parentHWND " + _hostPanel.Handle.ToInt32() + " " + Environment.CommandLine传参作为父窗体,然后EnumChildWindows()枚举的方法嵌入。

    刚启动的时候,unity的鼠标左键和右键点击事件是正常的,键盘和鼠标换轮失效。

    手动改变父窗体的大小后,键盘和鼠标滑轮就正常了。这里试过使用代码模拟改变父窗体的大小,但是不起作用。

    用unity做的3D显示,WPF项目老早就搭好了,然后想嵌到一起看起来好看一些(这样就不会有多个窗口弹出来,,,就为了这个目的,也没有说非得要。。。。)

    https://www.cnblogs.com/wangboke/p/5329483.html这个博客我之前看过,但是有看过下面一个博客后就没有试了。

    https://blog.csdn.net/llhswwha/article/details/79373245。------一开始(两年前了,还是使用4.6版本的时候)是用WebPlayer打包的,需要在电脑上安装WebPlayer播放器,WinForm中引入WebPlayer的控件,加载Unity打包生成的WebPlayer文件。WebPlayer第一次运行要下载文件的,后面就不需要了...具体碰到的一些问题以前还有记录下来,那时是第一次用博客记录问题的(UnityWebPlayer使用1,2,3),主要是要整合好多人的资料加上一些实验才能解决问题。

       后来(5.0版本开始吧),Unity对WebPlayer要渐渐不支持了,而且随着WebPlayer播放器的升级,要联网的问题处理不了了,改成Standalone打包,嵌入exe程序到WinForm中的方式,用Windows的API

    2019年12月3日 1:59
  • 如果c# 代码改变窗体大小是window.height这样?

    尝试使用win32api? 比如说movewindow。

    2019年12月3日 2:32
  • 都试过了 还是不行
    2019年12月3日 7:11
  • 必须只有手动托才好使?

    发送消息呢?

    或者SetWindowPos

    2019年12月4日 2:32
  • 好像都没有用 我太气了
    2019年12月4日 9:22
  • 可以重写Resize方法,在里面修改窗体的大小位置然后激活

    • 已标记为答案 heater 2020年1月19日 9:28
    2020年1月19日 9:28
  • 启动时有一个闪亮的解决方法:

    start进程的时候采用unity命令行参数,Arguments = "-popupwindow";去掉窗体的边框,不使用代码中的SetWindowLong()

    • 已标记为答案 heater 2020年1月19日 9:31
    2020年1月19日 9:31