I have a class, say Person, with an Id and a name. This class properly implements INotifyPropertyChanged
Addition: some people asked for class Person.
My real problem is a more elaborate class, I've simplified it to a fairly simple POCO to be certain it was not because of my class.
Originally:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
}
For updates it needed to implement INofityChanged. The full code is at the end of this question
StackOverflow: How to properly implement INotifyPropertyChanged
So far so good. Problems arise if the Person is changed in a separate thread.
I regularly get the an InvalidOperationException with the message
BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
I guess this has something to do with the fact that the update is done in a an awaitable async Task. I know that before updating a user interface item you should check if InvokeRequired and act accordingly.
private void OnGuiItemChanged()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
}
else
{
... // update Gui Item
}
}
However, when using a binding source the changes are handled inside the bindingsource. So I can't check for InvokeRequired
So how to update items that are also stored in a binding source in a non-UI thread?
By request: implementation of class Person and some code of my form
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id = 0;
private string name = null;
public int Id
{
get { return this.id; }
set { this.SetField(ref this.id, value); }
}
public string Name
{
get { return this.name; }
set { this.SetField(ref this.name, value); }
}
protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
RaiseEventPropertyChanged(propertyName);
}
}
private void RaiseEventPropertyChanged(string propertyName)
{
var tmpEvent = this.PropertyChanged;
if (tmpEvent != null)
{
tmpEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some code of the form:
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 10; ++i)
{
var person = new Person()
{
Id = i,
Name = "William " + i.ToString(),
};
this.bindingSource1.Add(person);
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
this.cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
Task.Run(() => ChangePersonsAsync(this.cancellationTokenSource.Token));
}
private async Task ChangePersonsAsync(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
foreach (var p in this.bindingSource1)
{
Person person = (Person)p;
person.Id = -person.Id;
}
await Task.Delay(TimeSpan.FromSeconds(0.01), token);
}
}
catch (TaskCanceledException)
{
}
}
As you mentioned, the changes are handled inside the BindingSource
class, so the easiest way I see is to replace it with the following
public class SyncBindingSource : BindingSource
{
private SynchronizationContext syncContext;
public SyncBindingSource()
{
syncContext = SynchronizationContext.Current;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (syncContext != null)
syncContext.Send(_ => base.OnListChanged(e), null);
else
base.OnListChanged(e);
}
}
Just make sure it's created on the UI thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With