How do you correctly update a databound datagridview from a background thread
Asked Answered
T

3

8

I have a custom object that implements INotifyPropertyChanged. I have a collection of these objects where the collection is based on BindingList I have created a binding source for the collection, and set the datasources of the bindingsource and datagridview.

Everything works great, except I need to update properties on the custom object from background threads. when I do so, I get the following error :

BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refere back to BindingSource

I found the following post that seems to have my exact problem (and solution?) but I cannot quite figure it out.

http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/3566f7c7-eb47-422e-ab09-9549a18da360/

I created and initialized the oper variables per the post in my business object, and then I put the two event functions into my collection class. This compiled correctly, but hangs without exception when run.

I have seen many posts saying to use Invoke/Begin Invoke, but I am not calling any functions on the UI, just updating business objects, so I am not sure where I would put the invoke calls.

One restriction : I want the business object to remain unaware of who is displaying it (as there are multiple consumers) so sending in GUI references into the business object so that I am later able to call invoke using those references is not an option.

Typeface answered 18/1, 2009 at 19:39 Comment(0)
T
14

I found this class in a forum that works. Just use this instead of BindingList

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ComponentModel;
using System.Threading;

namespace Utility
{
    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);
            }
        }
        void BaseAddingNew(AddingNewEventArgs e)
        {
            base.OnAddingNew(e);
        }
        protected override void OnListChanged(ListChangedEventArgs e)
        {
           // SynchronizationContext ctx = SynchronizationContext.Current;
            if (ctx == null)
            {
                BaseListChanged(e);
            }
            else
            {
                ctx.Send(delegate
                {
                    BaseListChanged(e);
                }, null);
            }
        }
        void BaseListChanged(ListChangedEventArgs e)
        {
            base.OnListChanged(e);
        }
    } 
}
Typeface answered 18/1, 2009 at 19:52 Comment(1)
@Marc Gravell [I know the post is old but ..When Instantiating from another List ThreadedBindingList(my Ilist) - there is no method for this. Shouldn't the method be there in order for this to be 'complete' or would there be a threading issue ?Trichosis
S
1

Since I took the time to format the sample for my needs I might as well post it here as a readable reference. Nothing changed except formatting.

using System.ComponentModel; 
using System.Threading;

namespace Utility 
{
  public class ThreadedBindingList : BindingList 
  {
    SynchronizationContext ctx = SynchronizationContext.Current;
    protected override void OnAddingNew(AddingNewEventArgs e)
    {
      if (ctx == null)
      {
        BaseAddingNew(e);
      }
      else
      {
        ctx.Send(delegate { BaseAddingNew(e); }, null);
      }
    }

    void BaseAddingNew(AddingNewEventArgs e)
    {
      base.OnAddingNew(e);
    }

    protected override void OnListChanged(ListChangedEventArgs e)
    {
      // SynchronizationContext ctx = SynchronizationContext.Current;
      if (ctx == null)
      {
        BaseListChanged(e);
      }
      else
      {
        ctx.Send(delegate { BaseListChanged(e); }, null);
      }
    }

    void BaseListChanged(ListChangedEventArgs e)
    {
      base.OnListChanged(e);
    }
  }
}
Succoth answered 9/4, 2009 at 9:40 Comment(0)
C
0

Not quite thread safe, but this small change to the above answers could have a big impact if your background thread is modifying object properties faster than they can be displayed;

protected override void OnListChanged(ListChangedEventArgs e)
{
  // SynchronizationContext ctx = SynchronizationContext.Current;
  if (ctx == null)
  {
    BaseListChanged(e);
  }
  else if(e.ListChangedType == ListChangedType.ItemChanged)
  {
    ctx.Post(delegate { BaseListChanged(e); }, null);
  }
  else
  {
    ctx.Send(delegate { BaseListChanged(e); }, null);
  }
}

Welcome any suggestions for reducing the number of posted calls if the same object has been modified more than once, and making sure any later send call will block until all posted calls have been processed.

Cupping answered 14/10, 2014 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.