views:

111

answers:

5

I have a MainWindow (form1) and a class called Module

In Module there is a method to create a new Backgroundworker and change a label in MainWindow. I have tried creating a public or internal method in the MainWindow code and calling it in the Module class but it doesn't seem to work.

Can anyone help me figure this out, it's just something which is stopping me from continuing development.

Sorry if I didn't make things clear, if you need something cleared up let me know.

Module.cs

public class Module
{
    protected System.Diagnostics.PerformanceCounter cpuCounter;
    BackgroundWorker cpuUThread;
    private delegate void UIDelegate();
    MainWindow mn;

    public void runCPUUsage()
    {

        cpuUThread = new BackgroundWorker();
        cpuUThread.DoWork += new DoWorkEventHandler(cpuUThread_DoWork);
        cpuUThread.WorkerSupportsCancellation = true;
        cpuUThread.RunWorkerAsync();
        mn = new MainWindow();
    }

    void cpuUThread_DoWork(object sender, DoWorkEventArgs e)
    {
        cpuCounter = new System.Diagnostics.PerformanceCounter();
        cpuCounter.CategoryName = "Processor";
        cpuCounter.CounterName = "% Processor Time";
        cpuCounter.InstanceName = "_Total";

        try
        {
            mn.changeCPUULabel(getCurrentCpuUsage().ToString());
        }
        catch (Exception ex)
        {

        }
    }

    public double getCurrentCpuUsage()
    {
        return Math.Round(cpuCounter.NextValue(), 0);
    }

    public void disposeCpuUsage()
    {
        cpuUThread.CancelAsync();
        cpuUThread.Dispose();
    }
}

MainWindow - Contains a label (labelCPUU)

internal void changeCPUULabel(string val)
    {
        Dispatcher.Invoke(new UIDelegate(delegate
            {
                this.labelCPUU.Content = val;
            }));
    }
public double getCurrentCpuUsage()
    {
       return mod.getCurrentCpuUsage();
    }
void activateCPUU(){ mod.runCPUUsage(); }
+5  A: 

Are you trying to change the label from a different thread than the GUI thread? You can’t do that. You can, however, call Invoke on any control, and it will be executed when the GUI thread gets around to it (which is, of course, immediate if the GUI thread is idle):

// Instead of:
myMainForm.MyLabel.Text = "New Text";

// Write:
myMainForm.Invoke(new Action(() => { myMainForm.MyLabel.Text = "New Text"; }));
Timwi
I am using a delegate for invoking from my thread.
Sandeep Bansal
[So?](http://meta.stackoverflow.com/questions/700/)
Timwi
It doesn't seem to be changing anything, look at my code above.
Sandeep Bansal
+5  A: 

If you have to update the UI with the background worker, use the ReportProgress method. That will fire an event that you should handle. Implement your UI update logic there.

Pierre 303
+1 - OP will also need to specify ReportProgress = true; when the BackgroundWorker is instantiated.
Joel Etherton
cpuUThread.WorkerReportsProgress = true; No change
Sandeep Bansal
It's not enought, you have to create an event handler for the event ReportProgress, and move the UI update logic there.
Pierre 303
A: 

If you're updating the UI in another thread (not the main UI thread), you need to use Control.Invoke.

Jerome
I am using Dispatcher.Invoke already
Sandeep Bansal
A: 

Have you tried putting a debug point in mainwindow on the method it calls, and on the line of code in backgroundworker that makes the call to see if it is even been made? If it hits backgrounderworker but not mainwindow, it maybe that backgroundworker is referencing its own version of mainwindow not the one you are expecting?

Another idea, but not a strong point for me, but he had an issue at work with GUI threads. Im wondering if your background worker is on a different thread to the GUI adn this is causing issues. The GUI should always be on its own thread, sence code in a GUI can lock up the GUI. If you need to interact between different thread you need to look up invoking.

JonWillis
Ah, so invoke was the cause. Heh im surprised i remembered that.
JonWillis
I am invoking the content from the thread to the gui.
Sandeep Bansal
A: 

It's a good idea not to couple the form to tightly with the module. One way to do that is to define an interface that the form implements, and have the module accept such an interface as a parameter. I'll exemplify with some code. Let's start with the interface:

public interface IDataReceiver
{
    void SetData(string data);
}

In my example I use a string for the data, but that can be whatever type you need to use. Then we implement this interface in the form:

public partial class Form1 : Form, IDataReceiver
{


    private void Button_Click(object sender, EventArgs e)
    {
        // create a module intstance, passing this form as parameter
        var module = new SomeModule(this);
        module.DoSomeWork();
    }

    public void SetData(string data)
    {
        // use the data that is received
        txtSomeTextBox.Text = data;
    }

    // the rest of the form code left out to keep it short
}

Finally the module with the BackgroundWorker:

public class SomeModule
{
    private BackgroundWorker _worker = new BackgroundWorker();
    private IDataReceiver _receiver;
    public SomeModule(IDataReceiver receiver)
    {
        _worker.DoWork += new DoWorkEventHandler(Worker_DoWork);
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker_RunWorkerCompleted);
        _worker.ProgressChanged += new ProgressChangedEventHandler(Worker_ProgressChanged);
        _worker.WorkerReportsProgress = true;
        _receiver = receiver;
    }

    void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        _receiver.SetData(e.UserState.ToString());
    }

    public void DoSomeWork()
    {
        // start the worker
        _worker.RunWorkerAsync();
    }

    private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // call method to pass data to receiver
        _receiver.SetData(e.Result.ToString());
    }

    private void Worker_DoWork(object sender, DoWorkEventArgs e)
    {
        // do some work here
        // assign the resulting data to e.Result
        for (int i = 0; i < 10; i++)
        {
            _worker.ReportProgress(0, "some data " + i);
            Thread.Sleep(250);
        }
        e.Result = "Finished";
    }
}

This way the module is in no way depending on what your form class looks like (it is even unaware that it is talking to a form). In my sample I call _receiver.SetData from the RunWorkerCompleted event handler, but it could just as well be done from a ReportProgress event handler.

Also note how the form is the "driving force" here. The module does not create the form, or take any initiatives of any kind. It is simply used by the form.

Fredrik Mörk
I have implemented parts of your code in and it seems to work well, but when putting e.Result = "data" into a while(true) loop it doesn't change the data, I need the data updated constantly.
Sandeep Bansal
@Sandeep: as Fredrik stated, you can use ReportProgress to report progress as the worker executes. Your original code has a `MainWindow` which creates a `Module`, which creates *another* `MainWindow`. Your code *is* updating the label, but it's updating the label of a *different* `MainWindow` - not the one that's displayed on screen. The tight coupling and poor design is the root of your problem.
Kent Boogaart
@Sandeep: see updated code sample (only `SomeModule` is changed). Specifically, I added `_worker.WorkerReportsProgress = true;` in the constructor, an event handler for the `ProgressChanged` event and a loop calling `ReportProgress`.
Fredrik Mörk
Works Great Fredrik, can't thankyou enough. @Kent thanks for the info on the poor design, I am working on remodelling my code etc.
Sandeep Bansal