views:

247

answers:

3

I have a

 BindingList<T>

which is bound to a datagridview. One property in my class takes long to calculate, so I threaded the action. After the calculation I raise the OnPropertyChanged() event to notify the grid that the value is ready.

At least, that's the theory. But since the OnPropertyChanged Method is called from a differend thread I get some weired exceptions in the OnRowPrePaint method of the grid.

Can anybody tell me how I fore the OnPropertyChanged event to be excecuted in the main thread? I can not use Form.Invoke, since the class MyClass is not aware that it runs in a Winforms application.

public class MyClass : INotifyPropertyChanged
{
    public int FastMember {get;set;}

    private int? slowMember;
    public SlowMember
    {
        get
        {
            if (slowMember.HasValue)
               return slowMember.Value;
            else
            {
               Thread t = new Thread(getSlowMember);
               t.Start();
               return -1;
            }

        }
    }

   private void getSlowMember()
   {
       Thread.Sleep(1000);
       slowMember = 5;
       OnPropertyChanged("SlowMember");
   }

   public event PropertyChangedEventHandler PropertyChanged;
   private void OnPropertyChanged(string propertyName)
   {
        PropertyChangingEventHandler eh = PropertyChanging;
        if (eh != null)
        {
            eh(this, e);
        }
   }

}
+4  A: 

By design, a control can only be updated by the thread it was created in. This is why you are getting exceptions.

Consider using a BackgroundWorker and only update the member after the long lasting operation has completed by subscribing an eventhandler to RunWorkerCompleted.

Yannick M.
Works like a charm. Until now I did not know about BackgroundWorker.That makes this task so easy, thx a lot.
SchlaWiener
+1  A: 

Consideration 1:
Take a look at UIThreadMarshal class and its usage in this article:
UI Thread Marshaling in the Model Layer
You can change the class from static to instance and inject it into your object. So your object will not know about Form class. It will know only about UIThreadMarshal class.

Consideration 2:
I don't think returning -1 from your property is good idea. It looks like a bad design to me.

Consideration 3:
Maybe your class shouldn't use antoher thread. Maybe it's consumer classes who should decide how to call your property: directly or in a separate thread. In this case maybe you need to provide additional property, such as IsSlowMemberInitialized.

nightcoder
To 1: Thanks for the link. The BackgroundWorker solved my problem in this case but I bet my shorts I will need this in the near future.To 2: You're right, especially beacause SlowMember can be -1. Was just for testingTo 3: Not possible, beacause the DataGridView queries the value (and gets -1 for the first time, than I update the value and use the INotifyPropertyChanged interface to inform the datagridview of the changed property, which has to happen in the main thread. (Ok I could use a Timer and check for IsSlowMemberInitialized = true but that's ugly.Anyway thx a lot.
SchlaWiener
If you use DataGridView, then maybe you need to use BindingSource. In the link I gave you, there is implementation of a BindingSource which supports binding from different threads. You can work on that code to make it better suitable for your needs.
nightcoder
+1  A: 

Here's something I wrote a while ago; it should work reasonably well, but note the cost of lots of updates...

using System.ComponentModel;
using System.Threading;
public class ThreadedBindingList<T> : BindingList<T> { 
    SynchronizationContext ctx = SynchronizationContext.Current; 
    protected override void OnAddingNew(AddingNewEventArgs e) { 
        if (ctx == null) { BaseAddingNew(e); } 
        else { ctx.Send(delegate { BaseAddingNew(e); }, null); } 
    } 
    protected override void OnListChanged(ListChangedEventArgs e)  { 
        if (ctx == null) { BaseListChanged(e); } 
        else  { ctx.Send(delegate { BaseListChanged(e); }, null); } 
    } 
    void BaseListChanged(ListChangedEventArgs e)  { base.OnListChanged(e); } 
    void BaseAddingNew(AddingNewEventArgs e) { base.OnAddingNew(e); } 
}
Marc Gravell
Interesting implementation Marc, but as this allows for bad design I reckon it should only be used in certain scenario's where you actually need the control to update while the action is processing.
Yannick M.