我一直在阅读如何从C#程序内部触发应用程序(Process.Start())的相关内容,但我没有找到有关如何让这个新应用程序在我的C#程序的面板中运行的任何信息。例如,我想通过点击一个按钮来打开notepad.exe,它将在我的应用程序内部运行,而不是在外部运行。
使用win32 API可以实现“吞噬”另一个应用程序。基本上,您可以获取该应用程序的顶部窗口,并将其父级设置为要放置它的面板的句柄。如果不想要MDI样式效果,则还必须调整窗口样式以使其最大化并删除标题栏。
这里是一些简单的示例代码,其中我有一个带有按钮和面板的表单:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Process p = Process.Start("notepad.exe");
Thread.Sleep(500); // Allow the process to open it's window
SetParent(p.MainWindowHandle, panel1.Handle);
}
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
}
}
我刚看到另一个例子,他们调用了WaitForInputIdle而不是sleep。所以代码会像这样:
Process p = Process.Start("notepad.exe");
p.WaitForInputIdle();
SetParent(p.MainWindowHandle, panel1.Handle);
The Code Project有一篇很好的文章介绍了整个过程:在WinForm项目中托管EXE应用程序
我不确定目前是否仍建议使用“对象链接和嵌入”框架,该框架允许您直接将某些对象/控件嵌入到应用程序中。这可能仅适用于某些应用程序,但我不确定记事本是否属于其中之一。对于像记事本这样的简单事物,您可能更容易使用由您使用的媒介提供的文本框控件(例如WinForms)。
以下是可用于入门的OLE信息链接:
另一个有趣的解决方案,可以使用WinForm容器启动外部应用程序,如下所示:
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
private void Form1_Load(object sender, EventArgs e)
{
ProcessStartInfo psi = new ProcessStartInfo("notepad.exe");
psi.WindowStyle = ProcessWindowStyle.Minimized;
Process p = Process.Start(psi);
Thread.Sleep(500);
SetParent(p.MainWindowHandle, panel1.Handle);
CenterToScreen();
psi.WindowStyle = ProcessWindowStyle.Normal;
}
这段代码帮助我将一些可执行文件嵌入到Windows表单中,例如记事本、Excel、Word、Acrobat Reader等等...
但是对于某些应用程序它无法正常工作。有时候当您启动某个应用程序的进程时......等待空闲时间...然后尝试获取其主窗口句柄......直到主窗口句柄变为 null.....
所以我做了一个技巧来解决这个问题
如果您获取的主窗口句柄为 null......那么搜索系统上运行的所有进程并找到您的进程...然后获取该进程的主要句柄,并将面板设置为其父级。
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "xxxxxxxxxxxx.exe";
info.Arguments = "yyyyyyyyyy";
info.UseShellExecute = true;
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Maximized;
info.RedirectStandardInput = false;
info.RedirectStandardOutput = false;
info.RedirectStandardError = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForInputIdle();
Thread.Sleep(3000);
Process[] p1 ;
if(p.MainWindowHandle == null)
{
List<String> arrString = new List<String>();
foreach (Process p1 in Process.GetProcesses())
{
// Console.WriteLine(p1.MainWindowHandle);
arrString.Add(Convert.ToString(p1.ProcessName));
}
p1 = Process.GetProcessesByName("xxxxxxxxxxxx");
//p.WaitForInputIdle();
Thread.Sleep(5000);
SetParent(p1[0].MainWindowHandle, this.panel2.Handle);
}
else
{
SetParent(p.MainWindowHandle, this.panel2.Handle);
}
现在,由于我没有做过这件事,所以我不能告诉您它的工作效果如何,但我知道一个当前的Windows技术可能是更好的解决方案:桌面窗口管理器API。
DWM是让您可以使用任务栏和任务切换器UI看到应用程序实时缩略图预览的相同技术。我相信它与远程终端服务密切相关。
当您强制将应用程序设置为不是桌面窗口的父窗口的子窗口时,可能会出现一个可能的问题,即一些应用程序开发人员会对设备上下文(DC)、指针(鼠标)位置、屏幕宽度等进行假设,这可能导致在“嵌入”主窗口时出现不稳定或有问题的行为。
我怀疑您可以通过依赖DWM来帮助您管理必要的转换,从而使应用程序的窗口能够可靠地呈现并在另一个应用程序的容器窗口中进行交互,从而在很大程度上消除这些问题。
文档假设使用C++编程,但我找到了一个人声称已经制作了一个开源的C#封装库:https://bytes.com/topic/c-sharp/answers/823547-desktop-window-manager-wrapper。这篇文章比较旧,而且源代码不在像GitHub、Bitbucket或SourceForge这样的大型代码库中,所以我不知道它有多新。如果另一个应用程序可以附加到Win32窗口句柄,那么我知道这是可能的。例如,我们有一个单独的C#应用程序,它在其中一个窗口中托管DirectX应用程序。我不熟悉如何实现这一点的确切细节,但我认为只需将您的面板的win32 Handle
传递给其他应用程序即可让该应用程序附加其DirectX表面。
不行
稍微详细的回答:只有当其他应用程序被设计为允许这样做时,您才能通过提供组件来将其添加到自己的应用程序中。