views:

50

answers:

3

To be honest, I'm a little bit lame when it comes to threading ;) So I'm asking for a little help.

Let's assume, we have some kind of control on which we can draw some charts. There's also a method that draws a chart on this control. The problem is that the charting method has access only to one of the fields of the control and we need to refresh the control when the chart is ready.

So let's assume our control looks like that:

class ChartingControl : System.Windows.Forms.Control
{
    public Canvas canvas;
    public void Refresh();
    /*
     ... other fields/methods
    */
}

where Canvas is a class used to draw the image (something like Graphics).
The charting method has access only to the canvas object (we cannot change it), so it looks like that:

public static void DrawChart(canvas)
{ /* draw */ } 

This method can be called from a separate thread, background worker etc... And I need to synchronize it with the main thread and call Refresh() when the chart is ready.

Right now when the chart is ready, I set a flag on canvas object

public static void DrawChart(canvas)
{ /* draw */ 
  canvas.Tag = true; // chart is ready
}

And I have a background worker running inside of the charting control and listening if the canvas.Tag field has changed and if so, it calls Refresh()

But it seems that my method is a little bit rought, easy-to-fail etc... Is there any better method to improve it?

Limitations:
- we cannot modify Canvas class. The only thing we can use is the Tag field (of type object
- we can modify ChartingControl class and drawing method.
- There can be many charting controls
- We have no control over how DrawChart is called. It can be called in a separate thread, or not. It is called elsewhere. All we can do is to create the control and the DrawChart method and try to comunicate them somehow

Solution
OK, I solved it this way: in ChartingControl I created a ManualResetEvent manualReset and a background worker.

Bacground worker waits for the manualReset:

    void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
            manualReset.WaitOne(); // Wait for a chart to be ready
    }

and in the end calls Reset() method

void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        Refresh();
    }

I pass the manualReset object inside canvas.Tag and inside drawing method, when the chart is ready I call manualReset.Set(); to pass the signal that chart is ready.

A: 

Supposing you deal with synchronization of access to the Canvas object, I will just recommend that you do
myChartingControl.Invoke(new Action(myChartingControl.Refresh));
from another thread that draws the chart

ULysses
From the thread that draws the chart I have no access to the charting control :/
Gacek
but you somehow pass there a reference to the Canvas, right? This means that you can pass a reference to the control, and take canvas from there
ULysses
I create a library that is used elsewhere. The control contains Canvas object and in the application where it is used, a new control is created and then `DrawChart(chartingControl.canvas)` is called (directly or in sub-thread)
Gacek
so you should define an interface, IChartingControl. This interface should contain one method `GetCanvas()`. Whoever creates a control with a canvas and wants to use your DrawChart method should implement this interface by simply returning a reference to it's Canvas instance.The DrawChart will have footprint like `DrawChart(IChartingControl ctrl)`. This will give you a reference to the control and a method to get the Canvas instance to draw on: `Canvas cnv = ctrl.GetCanvas(); Control ctl = ctrl as Control;`
ULysses
A: 

BackgroundWorker will help you

  void StartDrawChart (){

        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        bw.RunWorkerAsync()
  }


   void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   {
        this.Refresh();
   }

   void bw_DoWork(object sender, DoWorkEventArgs e)
   {
        DrawChart(this.canvas);
   }
Orsol
Yes, this would be the solution if I would have the control over how the `DrawChart` method is called. But it is used elsewhere and I have no control over it. I will update my question.
Gacek
A: 

OK, I solved it this way: in ChartingControl I created a ManualResetEvent manualReset and a background worker.

Bacground worker waits for the manualReset:

    void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
            manualReset.WaitOne(); // Wait for a chart to be ready
    }

and in the end calls Reset() method

void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        Refresh();
    }

I pass the manualReset object inside canvas.Tag and inside drawing method, when the chart is ready I call manualReset.Set(); to pass the signal that chart is ready.

Gacek
You made the right choice IMO - can you update your OP rather than post an answer to your own post though - makes it easier to see the problem and solution in one place
zebrabox