我一直在阅读有关如何从 C# 程序 (Process.Start()) 内部触发应用程序的大量信息,但我无法找到有关如何在我的 C# 程序面板中运行此新应用程序的任何信息. 例如,我想要一个按钮单击以在我的应用程序内打开 notepad.exe,而不是在外部打开。
8 回答
使用 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 而不是休眠。所以代码会是这样的:
Process p = Process.Start("notepad.exe");
p.WaitForInputIdle();
SetParent(p.MainWindowHandle, panel1.Handle);
代码项目有一篇关于整个过程的好文章:在 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;
}
ProcessWindowStyle.Normal的步骤 ProcessWindowStyle.Minimized
消除了烦人的延迟。
- 在答案中添加一些解决方案..**
这段代码帮助我将一些可执行文件停靠在 Windows 窗体中。像记事本、Excel、word、Acrobat 阅读器等等...
但它不适用于某些应用程序。有时当您启动某个应用程序的进程时......等待空闲时间......并尝试获取它的 mainWindowHandle......直到主窗口句柄变为空......
所以我做了一个技巧来解决这个问题
如果您将主窗口句柄设为空...然后在系统上搜索所有正在运行的进程并找到您的进程...然后获取进程的主窗口并将设置面板作为其父级。
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);
}
我注意到所有先前的答案都使用较旧的 Win32 用户库函数来完成此操作。我认为这在大多数情况下都会起作用,但随着时间的推移会变得不那么可靠。
现在,还没有这样做,我无法告诉您它的效果如何,但我知道当前的 Windows 技术可能是一个更好的解决方案:桌面 Windows 管理器 API。
DWM 是同一种技术,可让您使用任务栏和任务切换器 UI 查看应用程序的实时缩略图预览。我相信它与远程终端服务密切相关。
我认为当您强制应用程序成为不是桌面窗口的父窗口的子窗口时可能会发生的一个问题是,一些应用程序开发人员会对设备上下文 (DC)、指针(鼠标)位置做出假设,屏幕宽度等,当它“嵌入”到主窗口时可能会导致不稳定或有问题的行为。
我怀疑您可以通过依赖 DWM 来帮助您管理必要的转换,从而在另一个应用程序的容器窗口中可靠地呈现应用程序的窗口并与之交互,从而在很大程度上消除这些问题。
该文档假定 C++ 编程,但我发现一个人制作了他声称的开源 C# 包装器库:https ://bytes.com/topic/c-sharp/answers/823547-desktop-window-manager-wrapper . 帖子很旧,源不在 GitHub、bitbucket 或 sourceforge 之类的大型存储库上,所以我不知道它的最新情况。
如果你想在你的应用程序中运行记事本,你可能会更好地使用文本编辑器组件。WinForms 显然有一个基本的文本框,但我怀疑可以在互联网上找到提供记事本功能(或更好)的更高级的组件。
我知道如果其他应用程序可以将自己附加到 win32 窗口句柄,这是可能的。例如,我们有一个单独的 C# 应用程序,它在它的一个窗口中托管一个 DirectX 应用程序。我不熟悉如何实现的确切细节,但我认为只需将Handle
面板的 win32 传递给其他应用程序就足以让该应用程序附加其 DirectX 表面。
不
简短的答案:只有当其他应用程序被设计为允许它时,才通过提供组件供您添加到您自己的应用程序中。