3

I have a program that has Classes

  • GUI
  • Upload
  • and a buffer between the 2 classes - ie used to communicate between the 2 classes .

The Upload class uses Process to run an command line FTP app. I want to return what output produced by the FTP app to be displayed in a textbox in the GUI.
I have tried using the following code that has been truncated.

Upload Class (beginProcess() is a method used to start the Thread (not shown here)):

public delegate void WputOutputHandler(object sender, DataReceivedEventArgs e);
class Upload
{
    private WputOutputHandler wputOutput;

    beginProcess()
    {
        Process pr = new Process();                                                 
        pr.StartInfo.FileName = @"wput.exe";                                        
        pr.StartInfo.RedirectStandardOutput = true;
        pr.StartInfo.UseShellExecute = false;
        pr.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        pr.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived);
        pr.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceived);
        pr.Start();                                                                 
        pr.BeginOutputReadLine();
        pr.WaitForExit();
    }


    public void OnDataReceived(object sender, DataReceivedEventArgs e)
    {
        if(wputOutput != null)
        {
            wputOutput(sender, e);
        }
    }


    public event WputOutputHandler WputOutput
    {
        add
        {
            wputOutput += value;
        }
        remove
        {
            wputOutput -= value;
        }
    }
}

Buffer Class:

public void EventSubscriber()
{
    uploadSession.WputOutput += Main.writeToTextBoxEvent;
}

Main Class:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
    if(this.textBox1.InvokeRequired)
    {
        MethodInvoker what now?
    }
    else
    {
        textBox1.Text = e.Data;
    }
}

As you can see, when it come to the Main method's writeToTextBoxEvent, I've ran out of ideas. I'm not sure whether doing a UI update using a custom event is even the best way to do it. If someone could point me in the right direction I would be most grateful.

nf313743
  • 4,129
  • 8
  • 48
  • 63
  • Take a look at http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c?rq=1 too – LCJ Apr 02 '14 at 18:58

5 Answers5

3

How about this:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
    if(this.textBox1.InvokeRequired)
    {
        // In .Net 2.0
        this.textBox1.BeginInvoke(new MethodInvoker(() => writeToTextBoxEvent(sender, e)));

        // In .Net 3.5 (above is also possible, but looks nicer)
        this.textBox1.BeginInvoke(new Action(() => writeToTextBoxEvent(sender, e)));
    }
    else
    {
        textBox1.Text = e.Data;
    }
}

The advantage for this method against Richard solution is you don't need to write the executing code twice (within the BeginInvoke() and again in the else path).

Update

If you're on .Net 3.5 make it as an extension method:

public static class ControlExtensions
{
    public static void SafeInvoke(this Control control, Action action)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    public static void SafeBeginInvoke(this Control control, Action action)
    {
        if (control.InvokeRequired)
        {
            control.BeginInvoke(action);
        }
        else
        {
            action();
        }
    }
}

And use it that way:

public void writeToTextBoxEvent(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
    // Write it as a single line
    this.textBox1.SafeBeginInvoke(new Action(() => textBox1.Text = e.Data));

    this.textBox1.SafeBeginInvoke(new Action(() =>
        {
            //Write it with multiple lines
            textBox1.Text = e.Data;
        }));
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type – nf313743 Oct 15 '10 at 12:45
  • @johnnyturbo3 see my update to my answer: type inference fails in this case, just create an `Action` instance and pass that. – Richard Oct 15 '10 at 12:58
2
if(this.textBox1.InvokeRequired)
{
   Action a = () => { textBox1.Text = e.Data; };
  textBox1.Invoke(a);
}

I.e. use a delegate (which can be a closure to capture local variables) with the Invoke method. This will dispatch the delegate to be executed on the textbox's thread. Invoke will return when the delegate has completed execution.

There is also an asynchronous version when you don't need to wait (BeginInvoke).

EDIT: Forgot that because Invoke takes a Delegate type inference fails, so use local. NB you can of course create the delegate at an earlier point and use it in both branches to avoid duplicate code.

Richard
  • 106,783
  • 21
  • 203
  • 265
2

Here is a popular extension mechanism:

public static void InvokeIfRequired(this System.Windows.Forms.Control c,
                                    Action action) {
    if (c.InvokeRequired) {
        c.Invoke((Action)(() => action()));
    }
    else {
        action();
    }
}

Usage:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) {
    this.textBox1.InvokeIfRequired(() => { textBox1.Text = e.Data; }
}
Nayan
  • 3,092
  • 25
  • 34
  • 1
    Tsk, tsk, attribution required here. http://stackoverflow.com/questions/2367718/c-automating-the-invokerequired-code-pattern/2367763#2367763 – Hans Passant Oct 15 '10 at 13:53
  • Thanks Hans! I understand, but I'm not sure if that source is the original one. There are hundreds of similar example. :) But yes, that answer is pretty same. – Nayan Oct 15 '10 at 14:27
0

If you want a responsive UI, you need a worker thread and the main thread to update the UI.

I think you need a worker thread to look for data being received and then from your worker thread use a delegate to write data to your TextBox.

Something like :

public delegate void textBoxWriteDelegate(string msg);

private void textBoxWrite(string sMess) {
  textBox.AppendText(sMess);
}

And from your worker thread :

Invoke(new textBoxWriteDelegate(textBoxWrite), new object [] { "Launching ftp cmd line .... \n" });

Sorry it's net 1.1 ;) There's a better way to write delegates in 2.0 and higher...

oleveau
  • 200
  • 1
  • 13
0

I created a little helper for that:

class LayoutsEngine 
{
    internal static void ThreadSafeDoer(Form Form, Delegate Whattodo)
    {
        if (!Form.IsDisposed)
        {
            if (Form.InvokeRequired)
            {
                Form.Invoke(Whattodo);
            }
            else
            {
                Whattodo.DynamicInvoke();
            }
        }
    }
}

and am using it like this:

    LayoutsEngine.ThreadSafeDoer(this, new MethodInvoker(delegate()
    {
         txtWhatever.Text=whatever();
         //  and any other GUI meddling
    }));

Also, if anyone has a way to reduce MethodInvoker() here, please comment.

Daniel Mošmondor
  • 19,718
  • 12
  • 58
  • 99