views:

234

answers:

1

I'm having problems with the redirected output from a console application being presented in a Winforms textbox in real-time. The messages are being produced line by line however as soon as interaction with the Form is called for, nothing appears to be displayed.

Following the many examples on both Stackoverflow and other forums, I can't seem to get the redirected output from the process to display in the textbox on the form until the process completes.

By adding debug lines to the 'stringWriter_StringWritten' method and writing the redirected messages to the debug window I can see the messages arriving during the running of the process but these messages will not appear on the form's textbox until the process completes.

Grateful for any advice on this.

Here's an extract of the code

public partial class RunExternalProcess : Form
{
    private static int numberOutputLines = 0;
    private static MyStringWriter stringWriter;

    public RunExternalProcess()
    {
        InitializeComponent();

        // Create the output message writter
        RunExternalProcess.stringWriter = new MyStringWriter();
        stringWriter.StringWritten += new EventHandler(stringWriter_StringWritten);

        System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();

        startInfo.FileName = "myCommandLineApp.exe";

        startInfo.UseShellExecute = false;
        startInfo.RedirectStandardOutput = true;
        startInfo.CreateNoWindow = true;
        startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;

        using (var pProcess = new System.Diagnostics.Process())
        {
            pProcess.StartInfo = startInfo;
            pProcess.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(RunExternalProcess.Process_OutputDataReceived);
            pProcess.EnableRaisingEvents = true;

            try
            {
                pProcess.Start();

                pProcess.BeginOutputReadLine();
                pProcess.BeginErrorReadLine();

                pProcess.WaitForExit();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            finally
            {
                pProcess.OutputDataReceived -= new System.Diagnostics.DataReceivedEventHandler(RunExternalProcess.Process_OutputDataReceived);
            }
        }
    }

    private static void Process_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
    {
        if (!string.IsNullOrEmpty(e.Data))
        {
            RunExternalProcess.OutputMessage(e.Data);
        }
    }

    private static void OutputMessage(string message)
    {
        RunExternalProcess.stringWriter.WriteLine("[" + RunExternalProcess.numberOutputLines++.ToString() + "] - " + message);
    }

    private void stringWriter_StringWritten(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(((MyStringWriter)sender).GetStringBuilder().ToString());

        SetProgressTextBox(((MyStringWriter)sender).GetStringBuilder().ToString());
    }

    private delegate void SetProgressTextBoxCallback(string text);

    private void SetProgressTextBox(string text)
    {
        if (this.ProgressTextBox.InvokeRequired)
        {
            SetProgressTextBoxCallback callback = new SetProgressTextBoxCallback(SetProgressTextBox);
            this.BeginInvoke(callback, new object[] { text });
        }
        else
        {
            this.ProgressTextBox.Text = text;
            this.ProgressTextBox.Select(this.ProgressTextBox.Text.Length, 0);
            this.ProgressTextBox.ScrollToCaret();
        }
    }
}

public class MyStringWriter : System.IO.StringWriter
{
    // Define the event.
    public event EventHandler StringWritten;

    public MyStringWriter()
        : base()
    {
    }

    public MyStringWriter(StringBuilder sb)
        : base(sb)
    {
    }

    public MyStringWriter(StringBuilder sb, IFormatProvider formatProvider)
        : base(sb, formatProvider)
    {
    }

    public MyStringWriter(IFormatProvider formatProvider)
        : base(formatProvider)
    {
    }

    protected virtual void OnStringWritten()
    {
        if (StringWritten != null)
        {
            StringWritten(this, EventArgs.Empty);
        }
    }

    public override void Write(char value)
    {
        base.Write(value);
        this.OnStringWritten();
    }

    public override void Write(char[] buffer, int index, int count)
    {
        base.Write(buffer, index, count);
        this.OnStringWritten();
    }

    public override void Write(string value)
    {
        base.Write(value);
        this.OnStringWritten();
    }
}
+1  A: 
   pProcess.WaitForExit();

You are hanging the UI thread with that. It won't get around to doing its normal duties, like paint the text box, until the process has exited. Similarly, the BeginInvoke() delegate target cannot run until the UI thread goes idle again. They'll stack up in the invoke queue, in extreme cases you'll run out of memory. Even after the process exits, the UI thread will be unresponsive for some time while it works on emptying the queue.

You don't need it here. If necessary, use the Process.Exited event instead.

Hans Passant
Many thanks that was it. Unfortunately now the Exited event handler isn't firing when the process exits, so some more debugging to do.
Jonathan Websdale
Got that as well, it's down to the 'using' statement so my Process was being cleaned up and hence the exited event wasn't fired now that the WaitForExit() call wasn't in the 'using' block.
Jonathan Websdale