views:

117

answers:

5

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.

+2  A: 
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
+1  A: 

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
Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type
johnnyturbo3
@johnnyturbo3 see my update to my answer: type inference fails in this case, just create an `Action` instance and pass that.
Richard
upvote for extension method here!
Daniel Mošmondor
Brilliant. Shame I don't really understand it yet. Thanks for your help!
johnnyturbo3
A: 

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...

olifozzy
A: 

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
Nope, not possible. You can only replace it in 3.5 by the nicer one `Action`.
Oliver
I was hoping for some => magic.
Daniel Mošmondor
+1  A: 

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
Tsk, tsk, attribution required here. http://stackoverflow.com/questions/2367718/c-automating-the-invokerequired-code-pattern/2367763#2367763
Hans Passant
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