UI Safe BindingSource

It caught me off guard that the BindingSource is not really UI thread safe.  The situation we had was a model running on a background thread that was updating a dataset.  My view contained a grid, that was bound to a BindingSource which was bound to a table in that dataset.    The result was cross-threading exceptions being thrown as soon as we opened our view.  The thread that updates the dataset is the same thread that fires off the events that tell the BindingSource the underlying data has changed, which in turn tells the UI piece that data has changed, all the while still on that background thread.

My first pass at fixing the issue was to override OnListChanged in the BindingSource and marshal the thread back on to the UI thread.  This seemed to solve the problem until we bound to the entire dataset instead of just a table.  Internally the BindingSource creates a BindingSource for every list inside the collection of lists.  In this case, for every table in the dataset. 

Thanks to Reflector and the ability to debug into the source code, I found a way to get around the creation of more BindingSources, and instead created my own ObjectsharpBindingSource internally. 

This solution is probably only happy path, but that is the only path my code was following so that’s the only code I looked into.

Comments?  Questions?  Concerns?  Don’t like my code?  Better idea? Let me know, feedback is always welcome.

The idea behind UIThreadMarshal can be found at this great article by

    public class ObjectsharpBindingSource : BindingSource
{
private delegate void OnListChangedDelegate(ListChangedEventArgs e);
private Dictionary<string, ObjectsharpBindingSource> relatedBindingSources;

public ObjectsharpBindingSource() : base()
{
relatedBindingSources = new Dictionary<string, ObjectsharpBindingSource>();
}

public ObjectsharpBindingSource(object dataSource, string dataMember)
: base(dataSource, dataMember)
{
}

public override CurrencyManager GetRelatedCurrencyManager(string dataMember)
{
CurrencyManager result = null;
if (string.IsNullOrEmpty(dataMember))
{
//If you call the CurrencyManager property you end up in a recursive loop
Type t = this.GetType().BaseType;
result = t.InvokeMember("currencyManager", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, this, null) as CurrencyManager;
}
else if (dataMember.IndexOf(".") != -1)
{
//dot notation is not supported by the BindingSource
result = null;
}
else if (relatedBindingSources.ContainsKey(dataMember))
{
result = relatedBindingSources[dataMember].CurrencyManager;
}
else
{
ObjectsharpBindingSource bindingSource = new ObjectsharpBindingSource(this, dataMember);
this.relatedBindingSources.Add(dataMember, bindingSource);
result = bindingSource.CurrencyManager;
}
return result;
}

protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs e)
{
if (UIThreadMarshal.InvokeRequired)
{
OnListChangedDelegate changeDelegate = new OnListChangedDelegate(base.OnListChanged);
UIThreadMarshal.Invoke(new MethodInvoker( delegate { OnListChanged(e); } ));
}
else
{
base.OnListChanged(e);
}
}
}