项目背景:公司需要为一个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的源码示例,大家可以去自行下载。
代码虽然简单,但功能实现的很好,大家还有什么其他问题,可以在评论区留言交流