如何在C#程序的面板中运行另一个应用程序?

81

我一直在阅读如何从C#程序内部触发应用程序(Process.Start())的相关内容,但我没有找到有关如何让这个新应用程序在我的C#程序的面板中运行的任何信息。例如,我想通过点击一个按钮来打开notepad.exe,它将在我的应用程序内部运行,而不是在外部运行。

8个回答

113

使用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应用程序


19
非常有帮助!链接没有显示这些函数的dll导入,所以我想在这里分享它们。[DllImport("user32.dll")] static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] static extern bool MoveWindow(IntPtr Handle, int x, int y, int w, int h, bool repaint); - hrh
当我在IE或Chrome中使用它时,浏览器的顶部部分显示为黑色。有没有办法将其更改为透明,以便显示我的主窗口颜色? - user21479
参考的文章/代码对我不起作用(也许是因为我正在使用WPF?)。必须使用此链接:https://dev59.com/Lm025IYBdhLWcg3wtofX。另外,对于PInvoke导入,http://pinvoke.net总是很有用的。 - dudeNumber4
2
是的,经验丰富的Windows程序员使用WaitForInputIdle。Windows程序员应该对使用Sleep的代码进行两次或更多次的检查。 - Sam Hobbs
这个解决方案对于记事本exe运行良好,但是当我尝试使用Excel exe时,Excel会在面板外打开。如何限制它? - Kiran Desai
显示剩余2条评论

19

我不确定目前是否仍建议使用“对象链接和嵌入”框架,该框架允许您直接将某些对象/控件嵌入到应用程序中。这可能仅适用于某些应用程序,但我不确定记事本是否属于其中之一。对于像记事本这样的简单事物,您可能更容易使用由您使用的媒介提供的文本框控件(例如WinForms)。

以下是可用于入门的OLE信息链接:

http://en.wikipedia.org/wiki/Object_Linking_and_Embedding


7

另一个有趣的解决方案,可以使用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的步骤可以消除烦人的延迟。

enter image description here


不要睡眠等待在实时平台上无法保证预期时间执行的事件。 - Lodewijk

6
  • 在答案中添加一些解决方案...

这段代码帮助我将一些可执行文件嵌入到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);
     }

3
我注意到所有之前的答案都使用旧的Win32用户库函数来完成这个任务。我认为在大多数情况下,这将起作用,但随着时间的推移,可靠性会降低。

现在,由于我没有做过这件事,所以我不能告诉您它的工作效果如何,但我知道一个当前的Windows技术可能是更好的解决方案:桌面窗口管理器API

DWM是让您可以使用任务栏和任务切换器UI看到应用程序实时缩略图预览的相同技术。我相信它与远程终端服务密切相关。

当您强制将应用程序设置为不是桌面窗口的父窗口的子窗口时,可能会出现一个可能的问题,即一些应用程序开发人员会对设备上下文(DC)、指针(鼠标)位置、屏幕宽度等进行假设,这可能导致在“嵌入”主窗口时出现不稳定或有问题的行为。

我怀疑您可以通过依赖DWM来帮助您管理必要的转换,从而使应用程序的窗口能够可靠地呈现并在另一个应用程序的容器窗口中进行交互,从而在很大程度上消除这些问题。

文档假设使用C++编程,但我找到了一个人声称已经制作了一个开源的C#封装库:https://bytes.com/topic/c-sharp/answers/823547-desktop-window-manager-wrapper。这篇文章比较旧,而且源代码不在像GitHub、Bitbucket或SourceForge这样的大型代码库中,所以我不知道它有多新。

1

如果另一个应用程序可以附加到Win32窗口句柄,那么我知道这是可能的。例如,我们有一个单独的C#应用程序,它在其中一个窗口中托管DirectX应用程序。我不熟悉如何实现这一点的确切细节,但我认为只需将您的面板的win32 Handle传递给其他应用程序即可让该应用程序附加其DirectX表面。


0
如果你想在你的应用程序中运行记事本,最好使用文本编辑器组件。显然,WinForms自带一个基本的文本框,但我怀疑更高级的组件可以在互联网上找到,提供记事本功能(或更好)。

1
记事本只是一个测试用例! - Fernando Vieira

-3
简短回答:

不行

稍微详细的回答:

只有当其他应用程序被设计为允许这样做时,您才能通过提供组件来将其添加到自己的应用程序中。


5
即使简短的回答完全错误,您也愿意吗?;-) 简短的回答是“是”。 - Chris Rogers
在现实世界中,我们经常不得不适应一些并非为此而设计的东西。 - Vinicius Gonçalves

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接