【问题标题】:Process.Start() an executable and return a DialogResult to the callerProcess.Start() 一个可执行文件并将 DialogResult 返回给调用者
【发布时间】:2021-08-17 22:21:06
【问题描述】:

当我想显示消息时,我正在从我的主程序(调用程序)调用消息显示子程序(EXE)。我无法将调用的 exe 对话框结果传递给调用者。

        Dim psiProcessInfo As New ProcessStartInfo
        With psiProcessInfo
            .FileName = "DisplayMessage"
            .Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
        End With
        Process.Start(psiProcessInfo)

上面我显示调用部分。

Private Sub dlgDisplayMessage_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Input Parameter Accepted
        strInputMessage = Command()
        
        ' Composite Parameter Seperator
        Dim strParaSeperator As String = "$"
        Dim strCompersitePara As String = Microsoft.VisualBasic.Interaction.Command
        ' Parameter Split & Assign To Array
        Dim arParameter() As String = strCompersitePara.Split(strParaSeperator.ToCharArray)
        
        With pbPictureBox
            Select Case lblMessageType.Text
                Case Is = "C" ' Critical
                    .Image = My.Resources.Critical
                Case Is = "E" ' Exclamation
                    .Image = My.Resources.Exclamation
                Case Is = "Q" ' Question
                    .Image = My.Resources.Question
            End Select
            .Visible = True
        End With

        With txtMessageBody
            .Multiline = True
            .Size = New Size(386, 215)
            .Location = New Point(24, 53)
            .ScrollBars = ScrollBars.Vertical
            .TextAlign = HorizontalAlignment.Center
            .Text = vbCrLf & _
            lblMessageLine01.Text.Trim & _
            vbCrLf & vbCrLf & _
            lblMessageLine02.Text.Trim & _
            vbCrLf & vbCrLf & _
            lblMessageLine03.Text.Trim
            .Visible = True
        End With
        With cmdCancel
            .Focus()
        End With
End Sub



Private Sub cmdYes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdYes.Click
    
        Me.DialogResult = System.Windows.Forms.DialogResult.Yes
    
End Sub

Private Sub cmdCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCancel.Click
    Try
        Me.DialogResult = Windows.Forms.DialogResult.No
    
End Sub

显示我在上面显示的消息对话框编码。我想知道如何让DialogResult.OKDialogResult.No 调用exe。

编辑

根据 Jimi,我更改了调用程序代码。但它仍然没有返回任何值。

Dim p As New Process()
    p.StartInfo.UseShellExecute = False
    p.StartInfo.ErrorDialog = True
    p.StartInfo.RedirectStandardOutput = True
    p.StartInfo.UseShellExecute = False
    p.StartInfo.Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
    p.StartInfo.FileName = "DisplayMessage"
    p.Start()

    Dim output As String = p.StandardOutput.ReadToEnd()
    p.WaitForExit()

    MessageBox.Show(output)

【问题讨论】:

    标签: .net vb.net winforms process dialogresult


    【解决方案1】:

    关于dlgDisplayMessage 表单处理命令行参数的方式的一些建议:

    • 您应该使用Environment.GetCommandLineArgs() 来获取传递给命令行的值数组。这些值应以空格分隔。
      第一项始终表示可执行路径。推荐,它是一种 .Net 方法,更容易翻译成另一种 .Net 语言。
      也可以使用My.Application.CommandLineArgs,区别在于第一项是命令行的第一个参数,而不是可执行路径。 避免Interaction.Command()

    • 我认为那些lblMessageLine01等部分实际上是一些Label的内容。在这种情况下,您当然应该使用lblMessageLine01.Text 属性。您可以使用内插字符串将这些值添加到命令行。由于这些标签可能包含由空格分隔的多个单词,因此您需要将这些值括在双引号中。例如:

      Dim commandLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}"""  
      
    • 要从 Dialog 中返回一个值,使用 Process 类,您有几个选择:

    当然,您也可以使用某种形式的 Interprocess Communications,但这与 OP 中描述的情况不同。


    Imports System.IO
    
    Public Class dlgDisplayMessage
        ' For testing. Replace the Bitmaps with anything else
        Private images As New Dictionary(Of String, Bitmap) From {
            {"C", SystemIcons.Warning.ToBitmap()},
            {"E", SystemIcons.Exclamation.ToBitmap()},
            {"Q", SystemIcons.Question.ToBitmap()}
        }
    
        Protected Overrides Sub OnLoad(e As EventArgs)
            ' The first item is always the executable path
            Dim arParameter = Environment.GetCommandLineArgs()
    
            If arParameter.Count > 1 AndAlso images.ContainsKey(arParameter(1)) Then
                pbPictureBox.Image = images(arParameter(1))
            End If
    
            If arParameter.Count > 2 Then
                txtMessageBody.Visible = True
                txtMessageBody.AppendText(String.Join(Environment.NewLine, arParameter(3).Split("$"c)))
            End If
            MyBase.OnLoad(e)
        End Sub
    
        Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
            Console.WriteLine("Some data to return")
            Console.Out.WriteLine("More data")
            Environment.Exit(DialogResult.OK)
        End Sub
    
        Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
            Using sw = New StreamWriter(Console.OpenStandardOutput())
                sw.AutoFlush = True
                sw.WriteLine("Other data to return")
            End Using
            Environment.Exit(DialogResult.Cancel)
        End Sub
    
        Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
            Console.Error.WriteLine("Some errors to report")
            Environment.Exit(DialogResult.Abort)
        End Sub
    End Class
    

    启动这个可执行文件的应用程序可以通过不同的方式获得对话框的结果:读取它启动的进程的 StandardOutput、StandardError 或 ExitCode。当然,或者全部。

    我假设创建这个对话框的应用程序在你的控制之下(你做了它)。

    在任何情况下,进程StartInfo 必须将RedirectStandardOutput 设置为True,可选地将RedirectStandardError 设置为TrueUseShellExecute 设置为False(从系统外壳开始,此处不允许重定向)

    然后你可以:

    • 开始进程

    • 读取标准输出和标准错误

    • 使用Process.WaitForExit()同步等待进程退出

    • 阅读进程ExitCode,例如:

      [Process].Start()
      Dim sOut = [Process].StandardOutput.ReadToEnd()
      Dim sErr = [Process].StandardError.ReadToEnd()
      
      [Process].WaitForExit()
      Dim exitCode = [Process]?.ExitCode
      [Process]?.Dispose()
      

    此过程是同步的(阻塞)。当进程启动并在 GUI 中等待其结果时,您很有可能不希望这样做,因为它也会阻塞用户界面。
    当然,您可以运行一个任务或启动一个线程,然后将结果编组回 UI 线程。

    您还可以使用异步(事件驱动)版本,订阅OutputDataReceivedErrorDataReceivedExited 事件。
    要启用Exited 事件,您需要将Process.EnableRaisingEvents 设置为True
    还将Process.SynchronizingObject 设置为将处理事件的Control 类的实例(通常是Form,但任何ISynchronizeInvoke 对象都可以)。这是因为 Process 的事件是在 ThreadPool 线程中引发的。将 UI 元素设置为 SynchronizingObject,会导致事件在创建指定对象的同一线程中引发(此处为 UI 线程)。

    这在现有上下文中可能有点令人讨厌,因为您必须将事件处理程序添加到 Form 类中,记得删除它们,处理进程 asynchronously 等。

    所以这里有一个帮助类,它将事件驱动的过程转换为可从任何异步方法执行的等待任务。例如,可以从 Button 的 Click 事件处理程序调用它,将 Async 关键字添加到方法中。
    它可以被修改并用于测试不同的场景和方法来启动一个进程并在一个单独的环境中得到它的结果。

    它使用TaskCompletionSource + Task.WhenAny()
    Exited 事件导致 TaskCompletionSource 设置其结果。

    帮助类返回 Process 的 StandardOutput、StandardError 和 ExitCode 值转换为 DialogResult 值的内容。
    它可以设置一个超时,停止等待进程返回结果,如果指定的话。

    调用程序示例

    Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
        Dim exePath = "[The Executable Path]"
        Dim cmdLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}${lblMessageLine03.Text}"""
    
        Dim dlgResponse = New DialogResponseHelper(exePath, cmdLine)
        ' This call returns when the Dialog is closed or a Timeout occurs
        Dim exitCode = Await dlgResponse.Start()
    
        ' Read the StandardOutput results
        If dlgResponse.DialogData.Count > 0 Then
            For Each dataItem In dlgResponse.DialogData
                Console.WriteLine(dataItem)
            Next
        End If
    
        ' See whether errors are signaled
        Console.WriteLine(dlgResponse.DialogErrors.Count)
    
        If dlgResponse.ExitCode = DialogResult.OK Then
            ' Do something
        End If
    End Sub
    

    如果Interpolated Strings 功能不可用,请改用String.Format()

    Dim dataOut = {lblMessageLine01.Text, lblMessageLine02.Text, lblMessageLine03.Text}
    Dim cmdLine = String.Format("FormName C ""{0}${1}${2}""", dataOut)
    

    DialogResponseHelper类:

    Imports System.Collections.Concurrent
    Imports System.Collections.Generic 
    Imports System.Diagnostics
    Imports System.IO
    Imports System.Linq
    Imports System.Threading
    Imports System.Threading.Tasks
    Imports System.Windows.Forms
    
    Public Class DialogResponseHelper
        Private exitedTcs As TaskCompletionSource(Of Integer) = Nothing
        Private dlgData As New ConcurrentBag(Of String)()
        Private dlgErrors As New ConcurrentBag(Of String)()
        Private mExitCode As DialogResult = DialogResult.None
        Private proc As Process = Nothing
    
        Public Sub New(exePath As String, cmdLine As String, Optional timeout As Integer = Timeout.Infinite)
            ExecutablePath = exePath
            CommandLine = cmdLine
            WaitTimeout = timeout
        End Sub
    
        Public ReadOnly Property ExecutablePath As String
        Public ReadOnly Property CommandLine As String
        Public ReadOnly Property WaitTimeout As Integer
        Public ReadOnly Property ExitCode As DialogResult
            Get
                Return mExitCode
            End Get
        End Property
    
        Public ReadOnly Property DialogData As List(Of String)
            Get
                Return dlgData.ToList()
            End Get
        End Property
        Public ReadOnly Property DialogErrors As List(Of String)
            Get
                Return dlgErrors.ToList()
            End Get
        End Property
    
        Public Async Function Start() As Task(Of Integer)
            exitedTcs = New TaskCompletionSource(Of Integer)()
    
            proc = New Process()
            Dim psi = New ProcessStartInfo(ExecutablePath, CommandLine) With {
                .RedirectStandardError = True,
                .RedirectStandardOutput = True,
                .UseShellExecute = False,
                .WorkingDirectory = Path.GetDirectoryName(ExecutablePath)
            }
    
            proc.StartInfo = psi
            AddHandler proc.OutputDataReceived, AddressOf DataReceived
            AddHandler proc.ErrorDataReceived, AddressOf ErrorReceived
            AddHandler proc.Exited, AddressOf ProcessExited
            proc.EnableRaisingEvents = True
    
            proc.Start()
            proc.BeginErrorReadLine()
            proc.BeginOutputReadLine()
    
            Await Task.WhenAny(exitedTcs.Task, Task.Delay(WaitTimeout))
    
            If proc IsNot Nothing Then
                RemoveHandler proc.Exited, AddressOf ProcessExited
                RemoveHandler proc.ErrorDataReceived, AddressOf ErrorReceived
                RemoveHandler proc.OutputDataReceived, AddressOf DataReceived
                proc.Dispose()
                proc = Nothing
            End If
            Return exitedTcs.Task.Result
        End Function
    
        Private Sub DataReceived(sender As Object, e As DataReceivedEventArgs)
            If String.IsNullOrEmpty(e.Data) Then Return
            dlgData.Add(e.Data)
        End Sub
    
        Private Sub ErrorReceived(sender As Object, e As DataReceivedEventArgs)
            If String.IsNullOrEmpty(e.Data) Then Return
            dlgErrors.Add(e.Data)
        End Sub
    
        Private Sub ProcessExited(sender As Object, e As EventArgs)
            Dim exitId = (proc?.ExitCode).Value
            mExitCode = CType(exitId, DialogResult)
            exitedTcs.TrySetResult(exitId)
        End Sub
    End Class
    

    【讨论】:

    • 非常感谢吉米。它的工作。我使用 Environment.Exit(DialogResult.Abort) & Dim exitCode = [Process]?.ExitCode 从对话框返回结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-07
    • 1970-01-01
    • 2014-07-03
    • 2011-05-07
    • 2013-07-19
    • 2017-06-11
    相关资源
    最近更新 更多