.netvb.netwinformsprocessdialogresult

Process.Start() an executable and return a DialogResult to the caller


When I want to display message I'm calling Message Display sub program(EXE) from my main Program (calling program). I cannot get called exe dialog result to caller.

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

above I display calling section.

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

Display message dialog coding I display above. I want to know how I get DialogResult.OK or DialogResult.No to calling exe.

Edited

According to the Jimi I change my caller program code. But still it didnt return any value.

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)

Solution

  • A few suggestions about the way the dlgDisplayMessage Form handles the CommandLine arguments:

    Of course you could also use some form of Interprocess Communications, but this a different scenario than what described in the 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
    

    The application that starts this executable can get the outcome of the Dialog in different ways: reading the StandardOutput, StandardError or ExitCode of the Process it launched. Or all of them, of course.

    I assume the application that creates this Dialog is under your control (you made it).

    In any case, the Process StartInfo must set RedirectStandardOutput to True, optionally RedirectStandardError to True and UseShellExecute to False (starting with the System Shell doesn't allow redirections here)

    You can then:

    This procedure is synchronous (blocking). There's a good chance you don't want this when the Process is started and its results waited in a GUI, since it will also block the User Interface.
    Of course you could run a Task or start a Thread, then marshal back the results to the UI Thread.

    You can also use the asynchronous (event-driven) version, subscribing to the OutputDataReceived, ErrorDataReceived and Exited events.
    To enable the Exited event, you need to set Process.EnableRaisingEvents to True.
    Also setting the Process.SynchronizingObject to the instance of a Control class (usually a Form, but any ISynchronizeInvoke object would do) that will handle the events. This because the Process' events are raised in ThreadPool Threads. Setting a UI element as the SynchronizingObject, causes the events to raise in the same Thread where object specified was created (the UI Thread, here).

    This can be somewhat obnoxious in existing contexts, because you have to add the event handlers to a Form class, remember to remove them, dispose of the Process asynchronously etc.

    So here's a helper class that transforms the event-driven procedure in an awaitable Task that can be executed from any async method. E.g., it can be called from the Click event handler of a Button, adding the Async keyword to the method.
    It can be modified and used to test different scenarios and methods to start a Process and get its results in a separate environment.

    It uses a TaskCompletionSource + Task.WhenAny().
    The Exited event causes the TaskCompletionSource to set its result.

    The helper class returns the contents of the Process' StandardOutput, StandardError and ExitCode value translated to a DialogResult value.
    It can set a Timeout, to stop waiting for the Process to return a result, if specified.

    Sample calling procedure:

    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
    

    If the Interpolated Strings feature is not available, use String.Format() instead:

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

    DialogResponseHelper class:

    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