项目背景:公司需要为一个Python应用做一个前端界面,这个Python应用没有界面,只会弹出一个窗体,窗体里面播放视频,非常简陋,我考虑到WPF做界面比较美观,所以选择用C#启动这个Python程序后,把他弹出的窗口嵌入到WPF的界面的一个Panel当中,实现起来还是很简单的,只要知道这个窗体的名称即可。

以下为代码,SetWindow类为窗体设置类,对窗体的设置都在其中完成。

    public static class SetWindow
    {
        public static IntPtr intPtr;         //第三方应用窗口的句柄
        
        /// <summary>
        /// 调整第三方应用窗体大小
        /// </summary>
        public static void ResizeWindow()
        {
            ShowWindow(intPtr, 0);  //先将窗口隐藏
            ShowWindow(intPtr, 3);  //再将窗口最大化,可以让第三方窗口自适应容器的大小
        }

        /// <summary>
        /// 循环查找第三方窗体
        /// </summary>
        /// <returns></returns>
        public static bool FindWindow(string formName)
        {
            for (int i = 0; i < 100; i++)
            {
                //按照窗口标题查找Python窗口
                IntPtr vHandle = FindWindow(null, formName);     
                if (vHandle == IntPtr.Zero)
                {
                    Thread.Sleep(100);  //每100ms查找一次,直到找到,最多查找10s
                    continue;
                }
                else      //找到返回True
                {
                    intPtr = vHandle;
                    return true;
                }
            }
            intPtr = IntPtr.Zero;
            return false;
        }


        /// <summary>
        /// 将第三方窗体嵌入到容器内
        /// </summary>
        /// <param name="hWndNewParent">父容器句柄</param>
        /// <param name="windowName">窗体名</param>
        public static void SetParent(IntPtr hWndNewParent,string windowName)
        {
            ShowWindow(intPtr , 0);                 //先将窗体隐藏,防止出现闪烁
            SetParent(intPtr , hWndNewParent);      //将第三方窗体嵌入父容器                    
            Thread.Sleep(100);                      //略加延时
            ShowWindow(intPtr , 3);                 //让第三方窗体在容器中最大化显示
            RemoveWindowTitle(intPtr );             // 去除窗体标题
        }


        /// <summary>
        /// 去除窗体标题
        /// </summary>
        /// <param name="vHandle">窗口句柄</param>
        public static void RemoveWindowTitle(IntPtr vHandle)
        {
            long style = GetWindowLong(vHandle, -16);                   
            style &= ~0x00C00000;
            SetWindowLong(vHandle, -16, style);
        }


        #region API 需要using System.Runtime.InteropServices;

        [DllImport("user32.dll ", EntryPoint = "SetParent")]
        private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);   //将外部窗体嵌入程序

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string lpszClass, string lpszWindow);      //按照窗体类名或窗体标题查找窗体

        [DllImport("user32.dll", EntryPoint = "ShowWindow", CharSet = CharSet.Auto)]
        private static extern int ShowWindow(IntPtr hwnd, int nCmdShow);                  //设置窗体属性

        [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
        public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong);

        [DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)]
        public static extern long GetWindowLong(IntPtr hWnd, int nIndex);

        #endregion 
}

前端界面调用代码如下,启动第三方应用我是用的Process,代码就不再写了,这里着重介绍窗体的设置。

//先启动第三方应用

//开线程来查找窗体,不然UI线程会卡死

Task.Run(() => {
   //PCR TempControl是要查找的窗体名称,自行替换
   if (SetWindow.FindWindow("PCR TempControl"))
   {
        //这里是WPF的写法,Winform把this.Dispatcher.Invoke改为this.Invoke即可
        this.Dispatcher.Invoke(new Action(() =>
        {
          SetWindow.SetParent(panel1.Handle, "PCR TempControl");  //设置父容器
        }));
   }
   else
   {
         MessageBox.Show("未能查找到窗体");
   }
});

在此就完成了第三方窗体的查找,嵌入,以及显示功能,如果你的Panel大小不会跟随窗体变化的话,那么就不要再用下面的代码了,虽然我已尽力优化但下面的代码还是不可避免会出现少量闪烁。

如果你的前端窗体是可以调整大小的,并且Panel大小也会随之变化的话,你会发现在每次Panel大小改变的时候,你嵌入的第三方窗体不会随着变大变小,如果你拉大窗体,你的Panel变大了,第三方窗体大小却没变,Panel里面就会空出来一块,非常难看,于是就有了下面的代码来实现父容器大小变化时,第三方窗体的自适应

DispatcherTimer timer2 = new DispatcherTimer();     //WPF的定时器,Winform用Timer

//在窗体构造函数里把定时器绑定好,时间间隔设置为0.1秒
public MainWindow()
{
    InitializeComponent();
    timer2.Tick += new EventHandler(timer2_Tick);  //绑定事件
    timer2.Interval = TimeSpan.FromSeconds(0.1);
}

void timer2_Tick(object sender, EventArgs e)
{
    //第三方窗体句柄不为空
    if (SetWindow.intPtr!= IntPtr.Zero)
      {
         Thread t = new Thread(SetWindow.ResizeWindow);
         t.Start();  //开线程刷新第三方窗体大小
         Thread.Sleep(50); //略加延时
         timer2.Stop();  //停止定时器
      }
}

private void MainWindow1_SizeChanged(object sender, SizeChangedEventArgs e)
{
    timer2.Start();   //在每次窗体大小改变时,开启定时器调整第三方窗体大小
}

上面是WPF的代码,Winform的话直接用工具箱里的Timer,把事件代码复制过去就行。

经过我的测试,使用定时器会减少闪烁的出现,并且也不会出现卡顿,下图为我将第三方的程序嵌入窗体的成果,用的是Winform。

 将Steam嵌入Panel容器

可以看到第三方的窗体完美的嵌入到了Panel里面,大小也变成了相应的大小,拉大窗体,Panel大小变化第三方窗体也会跟着适应大小,效果非常NICE!

 可能会遇到的问题:

1.有的窗体没有标题,那怎么知道那个窗体的名称?

答:在电脑屏幕最下方任务栏那里会有小图标,鼠标移上去会显示这个窗体的名字。

2.WPF没有Panel怎么办?

答:用WindowsFormsHost控件,Panel放在这个控件里。

3.除了Panel,能不能放其他容器里?

答:理论上可以,不过我并没有进行测试。

4.如何退出嵌入,使窗体回归自由状态?

答:将parent设置为空句柄即可,可以在SetWindow类里加上下面这个方法,在前端直接用SetWindow.ExitParent()调用就可以了。这里我用了两次ShowWindow是为了取消最大化(因为之前在Panel里面我们设置成了最大化,如果要恢复成原来的大小就要用ShowWindow)。

在恢复窗口自由之后,如果前面设置了Timer的话,由于Timer还在运行,在Panel大小改变之后会触发最大化,所以在取消嵌入后,还需要将Timer与事件解绑一下。

        public static void ExitParent()
        {
            ShowWindow(intPtr, 0);
            SetParent(IntPtr.Zero);
            ShowWindow(intPtr,9);
        }

我写了一版Winform的源码示例,大家可以去自行下载。

C#Winform实现窗体内嵌入第三方应用窗体源码,可自适应容器大小-C#文档类资源-CSDN下载Winform实现窗体内嵌入第三方应用窗体,只需要知道窗体名称,即可将第三方应用的窗体嵌入到自己的程更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/XX_YZDY/85762239

代码虽然简单,但功能实现的很好,大家还有什么其他问题,可以在评论区留言交流

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐