如何将外部 CLI window 嵌入到 Windows 表单的面板中?

How to embed a external CLI window into a Panel in Windows Forms?

在 C# 或 VB.NET 中,在 Windows 表单下,我想知道如何将外部命令行界面 (CLI) window 嵌入面板或其他类型的主机 window,我可以在其中呈现外部 CLI 的内容 window 在我的表单中。

请注意,我不会假装自己重定向和打印 StdOut 流。我只想将 window 嵌入到我的表单中,随它去吧。

我已经按照建议尝试了 SetParent 功能,但它似乎不适用于非图形用户界面 windows,因为在设置父级 window 时, CLI window 从屏幕上消失并且未在父级(面板)中呈现 window。

这似乎可以按照建议在 WPF 中完成 here,但我不知道如何在 Windows Forms 下完成。

我正在寻找可以像这样使用的解决方法:

using (var p = new Process()) {
        p.StartInfo.FileName = @".\cli-process.exe";
        p.StartInfo.Arguments = @"...";
        p.StartInfo.CreateNoWindow = false;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
        p.Start();

        Thread.Sleep(2000);
        EmbedToWindow(p.MainWindowHandle, this.Panel1.Handle);
        p.WaitForExit(TimeOut.Infinite);
    }
}

我刚刚在 VB.NET 中编写了一个简单的帮助程序 class,它将帮助我轻松设置和释放父级 window。

至少就我在所需场景中测试而言,它似乎按预期工作。

感谢@Jimi、@RbMm 和@Remy Lebeau 的帮助以及我需要知道的提示,以便弄清楚如何执行此操作。

但是,由于缺少类型,我在这里分享的内容无法使用,但您可以理解。抱歉,但它需要超过此 post 的最大字符限制,如果我是唯一有兴趣完成此任务的人,那么复制和粘贴并调整内容以在此处显示它们可能需要付出太多努力。 .

但是如果您想弄清楚如何使这个 class 起作用,那么只需添加我在缺少的 NativeMethods class 定义中使用的缺少的 P/Invoke 成员,并替换缺少的 WindowInfo 类型以调用 GetWindowInfo (and the WINDOWINFO struct) or else GetClientRect + GetWindowLongPtr functions, and replace missing SafeWindowHandle type for IntPtr or for a custom class derived from SafeHandleZeroOrMinusOneIsInvalid.

用法示例:

Dim process As Process = Process.GetProcessesByName("name").Single()
Dim windowParenting As New WindowParenting(process.MainWindowHandle)

windowParenting.SetParent(Me.Panel1, 
                          fitToParentBounds:=True, 
                          removeBorder:=True, 
                          removeCaption:=True, 
                          resizable:=False)

Thread.Sleep(5000)
windowParenting.ReleaseParent(throwOnInvalidSourceWindowHandle:=True)

' Or...
' windowParenting.Dispose() ' It calls ReleaseParent.

WindowParenting Class:

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Implements a mechanism to set and release the parent window for a specific source window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Public Class WindowParenting : Implements IDisposable

#Region " Properties"

    ''' <summary>
    ''' A safe handle to the source window.
    ''' </summary>
    Public ReadOnly Property WindowHandle As SafeWindowHandle

    ''' <summary>
    ''' Gets a <see cref="DevCase.Core.IPC.WindowInfo"/> object for the source window.
    ''' </summary>
    Public ReadOnly Property WindowInfo As WindowInfo
        Get
            Return Me.GetWindowInfo()
        End Get
    End Property

    ''' <summary>
    ''' Gets a value that determine whether the source window has a parent window.
    ''' </summary>
    Public ReadOnly Property HasParent As Boolean
        Get
            Try
                Return Me.WindowInfo.ParentWindow IsNot Nothing
            Catch ex As Exception
                Return False
            End Try
        End Get
    End Property

#End Region

#Region " Private Fields"

    ''' <summary>
    ''' Keeps track of the current source window bounds when making a call to <see cref="WindowParenting.SetParent"/> method.
    ''' </summary>
    Private lastBounds As Rectangle = Rectangle.Empty

    ''' <summary>
    ''' Keeps track of the current source <see cref="WindowStyles"/> when making a call to <see cref="WindowParenting.SetParent"/> method.
    ''' </summary>
    Private lastWindowStyle As WindowStyles = WindowStyles.None

#End Region

#Region " Constructors "

    ''' <summary>
    ''' Initializes a new instance of the <see cref="WindowParenting"/> class.
    ''' </summary>
    ''' <param name="hwnd">
    ''' A handle to the source window.
    ''' </param>
    <DebuggerStepThrough>
    Public Sub New(hwnd As IntPtr)
        Me.New(New SafeWindowHandle(hwnd))
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="WindowParenting"/> class.
    ''' </summary>
    ''' <param name="hwnd">
    ''' A handle to the source window.
    ''' </param>
    <DebuggerStepThrough>
    Public Sub New(hwnd As SafeHandle)
        Me.WindowHandle = hwnd

        If Me.WindowHandle.IsInvalid Then
            Throw New Exception("Invalid window handle.")
        End If
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="WindowParenting"/> class.
    ''' </summary>
    ''' <param name="window">
    ''' AThe source window.
    ''' </param>
    <DebuggerStepThrough>
    Public Sub New(window As NativeWindow)
        Me.New(New SafeWindowHandle(window.Handle))
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="WindowParenting"/> class.
    ''' </summary>
    ''' <param name="window">
    ''' The source window.
    ''' </param>
    <DebuggerStepThrough>
    Public Sub New(window As IWin32Window)
        Me.New(New SafeWindowHandle(window.Handle))
    End Sub

#End Region

#Region " Public Methods "

    ''' <summary>
    ''' Sets a new parent window for the source window.
    ''' </summary>
    ''' <param name="parentWindow">
    ''' The parent window.
    ''' </param>
    ''' 
    ''' <param name="fitToParentBounds">
    ''' If set to <see langword="True"/>, fits to size of the source window to the parent window bounds.
    ''' </param>
    ''' 
    ''' <param name="removeBorder">
    ''' If set to <see langword="True"/>, removes the border from the source window.
    ''' </param>
    ''' 
    ''' <param name="removeCaption">
    ''' If set to <see langword="True"/>, removes the caption from the source window.
    ''' </param>
    ''' 
    ''' <param name="resizable">
    ''' If set to <see langword="False"/>, remove sthe size frame from the source window.
    ''' </param>
    ''' <exception cref="InvalidOperationException">
    ''' Source window already has a parent window.
    ''' </exception>
    <DebuggerStepThrough>
    Public Overridable Sub SetParent(parentWindow As IWin32Window, fitToParentBounds As Boolean,
                                     removeBorder As Boolean, removeCaption As Boolean,
                                     resizable As Boolean)

        Dim curentWindowInfo As WindowInfo = Me.GetWindowInfo()
        If Me.lastBounds = Rectangle.Empty Then
            Me.lastBounds = curentWindowInfo.Bounds
        End If
        If Me.lastWindowStyle = WindowStyles.None Then
            Me.lastWindowStyle = curentWindowInfo.WindowStyle
        End If

        Dim newStyle As WindowStyles = (Me.lastWindowStyle And Not WindowStyles.SysMenu)
        If removeBorder Then
            newStyle = (newStyle And Not WindowStyles.Border)
        End If
        If removeCaption Then
            newStyle = (newStyle And Not WindowStyles.Caption)
        End If
        If Not resizable Then
            newStyle = (newStyle And Not WindowStyles.SizeFrame)
        End If

        Dim parentWindowHandle As New SafeWindowHandle(parentWindow.Handle)

        NativeMethods.SetParent(Me.WindowHandle, parentWindowHandle)
        Me.SetSourceWindowStyle(newStyle)

        Dim parentClientRect As Rectangle
        If fitToParentBounds Then
            NativeMethods.GetClientRect(parentWindowHandle, parentClientRect)
        End If
        NativeMethods.SetWindowPos(Me.WindowHandle, IntPtr.Zero, 0, 0,
                                   If(fitToParentBounds, parentClientRect.Width, 0),
                                   If(fitToParentBounds, parentClientRect.Height, 0),
                                   SetWindowPosFlags.AsyncWindowPos Or
                                   SetWindowPosFlags.ShowWindow Or
                                   If(fitToParentBounds, SetWindowPosFlags.None, SetWindowPosFlags.IgnoreResize))

    End Sub

    Public Overridable Sub SetParent(parentWindow As NativeWindow, fitToParentBounds As Boolean,
                                     removeBorder As Boolean, removeCaption As Boolean,
                                     resizable As Boolean)

        Me.SetParent(DirectCast(parentWindow, IWin32Window), fitToParentBounds, removeBorder, removeCaption, resizable)

    End Sub

    Public Overridable Sub SetParent(parentWindow As Control, fitToParentBounds As Boolean,
                                     removeBorder As Boolean, removeCaption As Boolean,
                                     resizable As Boolean)

        Me.SetParent(DirectCast(parentWindow, IWin32Window), fitToParentBounds, removeBorder, removeCaption, resizable)

    End Sub

    ''' <summary>
    ''' Release the source window from its current parent window.
    ''' </summary>
    ''' <param name="throwOnInvalidSourceWindowHandle">
    ''' If set to <see langword="True"/>, throws an <see cref="NullReferenceException"/> 
    ''' if the source window handle specified in <see cref="WindowParenting.WindowHandle"/> is invalid.
    ''' <para></para>
    ''' This can be useful if you need to detect whether the source window has been destroyed.
    ''' </param>
    <DebuggerStepThrough>
    Public Overridable Sub ReleaseParent(Optional throwOnInvalidSourceWindowHandle As Boolean = False)

        Dim isInvalid As Boolean = Me.WindowHandle.IsInvalid OrElse
                                   Me.WindowHandle.IsClosed OrElse
                                   Not NativeMethods.IsWindow(Me.WindowHandle)

        If isInvalid AndAlso throwOnInvalidSourceWindowHandle Then
            Throw New NullReferenceException("Invalid source window handle.")

        ElseIf Not isInvalid Then
            If Not Me.HasParent Then
                Throw New InvalidOperationException("Source window has not a parent window.")
            End If

            NativeMethods.SetParent(Me.WindowHandle, IntPtr.Zero)

            If Me.lastWindowStyle <> WindowStyles.None Then
                Me.SetSourceWindowStyle(Me.lastWindowStyle)
                Me.lastWindowStyle = WindowStyles.None
            End If

            If Me.lastBounds <> Rectangle.Empty Then
                NativeMethods.SetWindowPos(Me.WindowHandle, IntPtr.Zero,
                                       Me.lastBounds.X, Me.lastBounds.Y,
                                       Me.lastBounds.Width, Me.lastBounds.Height,
                                       SetWindowPosFlags.AsyncWindowPos)
                Me.lastBounds = Rectangle.Empty
            End If

        End If

    End Sub

#End Region

#Region " Private Methods "

    ''' <summary>
    ''' Returns a <see cref="DevCase.Core.IPC.WindowInfo"/> object for the source window.
    ''' </summary>
    <DebuggerStepThrough>
    Private Function GetWindowInfo() As WindowInfo
        Return New WindowInfo(Me.WindowHandle)
    End Function

    ''' <summary>
    ''' Sets the <see cref="WindowStyles"/> for the source window.
    ''' </summary>
    <DebuggerStepThrough>
    Private Sub SetSourceWindowStyle(style As WindowStyles)

        If Environment.Is64BitProcess Then
            NativeMethods.SetWindowLongPtr(Me.WindowHandle, WindowLongValues.WindowStyle, style)
        Else
            NativeMethods.SetWindowLong(Me.WindowHandle, WindowLongValues.WindowStyle, style)
        End If

    End Sub

#End Region

#Region " IDisposable Implementation "

    ''' <summary>
    ''' Flag to detect redundant calls.
    ''' </summary>
    Private disposedValue As Boolean

    ''' <summary>
    ''' Releases all the resources used by this instance.
    ''' </summary>
    ''' <param name="disposing">
    ''' <see langword="True"/> to release both managed and unmanaged resources; 
    ''' <see langword="False"/> to release only unmanaged resources.
    ''' </param>
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue AndAlso disposing Then
            Try
                Me.ReleaseParent()
            Catch ex As Exception
            End Try
            Me.WindowHandle?.Close()
        End If
        Me.disposedValue = True
    End Sub

    ''' <summary>
    ''' Releases all the resources used by this instance.
    ''' </summary>
    Public Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(True)
    End Sub

#End Region

End Class

P/invokes 缺少一点 win32 error-handling。也许其他方面可以改进。