Question Display fluid standard output from console process in WinForm?

Buho1

Member
Joined
Mar 23, 2006
Messages
12
Location
Maryland, USA
Programming Experience
10+
Hi. I've been searching around the web and here for several days and haven't found a solution to my problem. I'm a veteran ASP.NET developer but I'm new to WinForms and multithreading. I'm using VS 2010 and VB.NET 4.0.

I have a console application that takes minutes to hours to run. It spits out to the standard output what it's doing. My client now wants a GUI for this app (command line parameters and XML scare them) and the quickest solution is to write a WinForm that launches the console app.

I've successfully written this application. Here's the core code I have at this point:
    Private bcProcess As New Process

    Private Sub btnProcess_Click() Handles btnProcess.Click
        runBCMaker()
    End Sub

    Private Sub runBCMaker()
        Try
            openOutputWindow()
            bcProcess.StartInfo.FileName = defaultBCMakerFilename
            bcProcess.StartInfo.Arguments = makeCmdArg()
            bcProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden 'Output will be shown in a WinForm
            bcProcess.StartInfo.CreateNoWindow = True 'Line above didn't work, try this.
            bcProcess.StartInfo.UseShellExecute = False
            bcProcess.StartInfo.RedirectStandardOutput = True
            bcProcess.Start()
            outputWindow.Output = bcProcess.StandardOutput.ReadToEnd()
            bcProcess.WaitForExit()
            bcProcess.Close()
            lblProcess.Text = "Process finished."
        Catch ex As Exception
            lblProcess.Text = "BCMaker.exe crashed!"
        Finally
            bcProcess.Dispose()
        End Try
    End Sub
This will open another WinForm that I've made, basically a big Textbox with a public property Output that I can assign the StandardOutput.ReadToEnd() to. The WinForm will freeze for the minutes that my program (BCMaker.exe) is running and then come back, with the output in the Output window. Fine. I met the requirements of my project. Except this freeze will likely aggravate my client, especially when they can't see any progress.

Now, I've learned that WaitForExit() is a synchronous operation, so if I want to use a BackgroundWorker and use this snippet, that's a bad idea.

I've experimented with BackgroundWorker but results haven't been much better. The main form and the output window are responsive, but the output window shows no output until the process completes, where it shows everything.

Here's what I've got with BackgroundProcess:

    Private bcProcess As New Process
    Private WithEvents BackgroundWorker1 As New BackgroundWorker

    Private Sub btnProcess_Click() Handles btnProcess.Click
        openOutputWindow()
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub runBCMaker()
        Try
            bcProcess.StartInfo.FileName = defaultBCMakerFilename
            bcProcess.StartInfo.Arguments = makeCmdArg()
            bcProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden 'Output will be shown in a WinForm
            bcProcess.StartInfo.CreateNoWindow = True 'Line above didn't work, try this.
            bcProcess.StartInfo.UseShellExecute = False
            bcProcess.StartInfo.RedirectStandardOutput = True
            bcProcess.Start()
            outputWindow.Output = bcProcess.StandardOutput.ReadToEnd()
            bcProcess.Close()
        Catch ex As Exception
            lblProcess.Text = "BCMaker.exe crashed!"
        Finally
            bcProcess.Dispose()
        End Try
    End Sub

    Private Sub runBCMaker_Completed(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
        If e.Cancelled Then
            lblProcess.Text = "Canceled!"
        ElseIf e.Error IsNot Nothing Then
            lblProcess.Text = "Error: " + e.Error.Message
        Else
            lblProcess.Text = "Done!"
        End If
    End Sub
BackgroundWorker.ProgressChanged event is no good, since there's nothing discernible my console app is reporting; I'm redirecting the StandardOutput stream. Maybe if ProgressChangedEventArgs could contain a data payload I could send the standard output of the console through periodically (like every 100ms), but I don't see a way to send any data payload this way.

Any ideas on how to display a fluid StandardOutput?
 
After some more searching, I found the solution.

1. Indeed, BackgroundWorker is my friend. It keeps the UI responsive.

2. The missing piece was the BeginOutputReadLine event.

Added to my 2nd block of code is this sub:

    Private Sub runBCMaker_OutputReceived(ByVal sendingProcess As Object, outLine As DataReceivedEventArgs)
        If Not String.IsNullOrEmpty(outLine.Data) Then
            outputWindow.Output += outLine.Data + vbCrLf
        End If
    End Sub
and two extra lines in my runBCMaker() sub:
AddHandler bcProcess.OutputDataReceived, AddressOf runBCMaker_OutputReceived
bcProcess.Start()
bcProcess.BeginOutputReadLine()
 
Back
Top