views:

345

answers:

4

How can I call a method on a form from a method called from an external class from a backgroundWorker? I believe that delegates are somehow the answer to this question, but after spending time reading, I still am confused by this problem.

This is in Visual Studio 2008, the backgroundWorker is run from the form and calls ExternalClass.Method. The form is in namespace ProgramName and the ExternalClass is using ProgramName. When i declare public delegate MyDelegate in the namespace ProgramName in the file of my windows.form I can create an instance of MyDelegate and call it in a method of my form (but this does not help me), but if I try to create an instance of MyDelegate and call it from a method of my external class I cannot access the method of the windows.form, even though it is public.

thanks

yes, I want to pass progress reports (int percent, string status) back from ExternalClass.Method. Can you explain a bit more about that CSharpAtl (or anyone)?

A: 

Note that your question (afaik) is not just about the backgroundwiorker but just as much about how to break a circular reference between classes. This is a standard problem with a standard solution.

You can pass a delegate (referring to a Form-method) around just as any object so also to a Backgroundworker. And the Bgw can pass it to the external method. A delegate includes a reference to the object (in this case the Form).

Note that since you are on another thread you will need to use Control.Invoke inside the delegate, or use the Bgw ReportProgress event.

public partial class Form1 : Form
{
    private void ReportProgresshandler(int percent, string state)
    {
        backgroundWorker1.ReportProgress(percent);  // also does the Invoke
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        var ex = new ExampleClass();
        ex.Dowork(ReportProgresshandler);    
    }
}

and something like

class ExampleClass
{
    delegate void ReportDelegate(int percent, string status);

    public void Dowork(ReportDelegate report)
    {
        report(0, "starting");
    }

}
Henk Holterman
Just to help Clarify, you'll need to use the *Form*'s Invoke() method, not Delegate.Invoke().
STW
Yoooder: good point, absolutely. I sidestepped it using ReportProgress.
Henk Holterman
A: 

I'm not sure what the trouble is. And also you can use a delegate, but don't need one.

using System.Windows.Forms;
using System.ComponentModel;

public partial class ExampleForm : Form
{

    public ExampleForm()
    {
        InitializeComponent();

        var worker = new BackgroundWorker();
        worker.DoWork += new DoWorkEventHandler(doWork);
        worker.RunWorkerAsync(this);
    }

    void doWork(object sender, DoWorkEventArgs e)
    {
        ExampleForm f = e.Argument as ExampleForm;
        f.Hello();
    }

    private void Hello()
    {

    }

}
Cheeso
+1  A: 

The main thing to realize is that you actually have two levels of synchronization going on here: between the Form and the BackgroundWorker, and between the BackgroundWorker and the ExternalClass object.

The Form is asynchronously invoking BackgroundWorker.DoWork(), which is running in another thread. Any updates to the Form should come through Form.Invoke() (which fires an arbitrary delegate in the Form's thread) or, better yet, through the BackgroundWorker.ProgressChanged event (which fires a specific event in the Form's thread).

So what you want to do is proxy the status updates from the ExternalClass method back to the BackgroundWorker, which will in turn push them on to the Form. One way I've done this in the past is to use a callback delegate:

public delegate void ProgressCallback(double percentCompleted, string status);

And have my expensive worker method take the callback as an argument:

public void ExpensiveMethod(ProgressCallback callback) {
    while(doingThings) {
        if(callback != null) callback(percentDone, statusString);
    }
}

Then in your BackgroundWorker class, define a method that matches your callback delegate, and have it call BackgroundWorker.ReportProgress() to trigger the BackgroundWorker.ProgressChanged event, which can in turn update your Form's state.

Update: this is basically the same as the solution Henk Holterman suggested in his new edit.

Daniel Pryden