通过单独的任务更新 BindingSource 中的元素
Update elements in BindingSource via separate task
我有一个 class,比如 Person,有一个 Id 和一个名字。 class 正确实现了 INotifyPropertyChanged
补充:有人问class人
我真正的问题是一个更复杂的 class,我已将它简化为一个相当简单的 POCO 以确定它不是因为我的 class。
原来:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
}
对于更新,它需要实施 INofityChanged。完整代码在本题末尾
Whosebug: How to properly implement INotifyPropertyChanged
- 我有一个System.Windows.Forms.Form
- 此表单有一个 BindingSource。
- 绑定源的数据源属性设置为我的class人
- 我有一个绑定到 BindingSource 的 DataGridView
- 我已经向绑定源添加了几个 Person 实例
- 已添加的人已正确显示。
- 如果我以编程方式更改绑定源中的 Person,更改后的值会正确显示。
到目前为止一切顺利。如果在单独的线程中更改 Person,则会出现问题。
我经常收到带有消息
的 InvalidOperationException
BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
我想这与更新是在可等待的异步任务中完成这一事实有关。我知道在更新用户界面项之前,您应该检查是否 InvokeRequired 并采取相应措施。
private void OnGuiItemChanged()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
}
else
{
... // update Gui Item
}
}
但是,当使用绑定源时,更改是在绑定源内部处理的。所以我无法检查 InvokeRequired
那么如何在非UI线程中更新也存储在绑定源中的项目?
根据要求:实施 class 人和我的表格的一些代码
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));
}
}
}
形式的一些代码:
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)
{
}
}
正如您提到的,更改是在 BindingSource
class 内部处理的,所以我看到的最简单的方法是将其替换为以下内容
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);
}
}
只需确保它是在 UI 线程上创建的。
我有一个 class,比如 Person,有一个 Id 和一个名字。 class 正确实现了 INotifyPropertyChanged
补充:有人问class人
我真正的问题是一个更复杂的 class,我已将它简化为一个相当简单的 POCO 以确定它不是因为我的 class。
原来:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
}
对于更新,它需要实施 INofityChanged。完整代码在本题末尾
Whosebug: How to properly implement INotifyPropertyChanged
- 我有一个System.Windows.Forms.Form
- 此表单有一个 BindingSource。
- 绑定源的数据源属性设置为我的class人
- 我有一个绑定到 BindingSource 的 DataGridView
- 我已经向绑定源添加了几个 Person 实例
- 已添加的人已正确显示。
- 如果我以编程方式更改绑定源中的 Person,更改后的值会正确显示。
到目前为止一切顺利。如果在单独的线程中更改 Person,则会出现问题。
我经常收到带有消息
的 InvalidOperationExceptionBindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
我想这与更新是在可等待的异步任务中完成这一事实有关。我知道在更新用户界面项之前,您应该检查是否 InvokeRequired 并采取相应措施。
private void OnGuiItemChanged()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
}
else
{
... // update Gui Item
}
}
但是,当使用绑定源时,更改是在绑定源内部处理的。所以我无法检查 InvokeRequired
那么如何在非UI线程中更新也存储在绑定源中的项目?
根据要求:实施 class 人和我的表格的一些代码
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));
}
}
}
形式的一些代码:
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)
{
}
}
正如您提到的,更改是在 BindingSource
class 内部处理的,所以我看到的最简单的方法是将其替换为以下内容
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);
}
}
只需确保它是在 UI 线程上创建的。