【问题标题】:Update elements in BindingSource via separate task通过单独的任务更新 BindingSource 中的元素
【发布时间】:2015-10-01 10:43:01
【问题描述】:

我有一个类,比如 Person,有一个 Id 和一个名字。此类正确实现 INotifyPropertyChanged

补充:有人要求Person类。

我真正的问题是一个更复杂的课程,我已经将它简化为一个相当简单的 POCO,以确保它不是因为我的课程。

原来:

public class Person
{
    public int Id {get; set;}
    public string Name {get; set;}
}

对于需要实施 INofityChanged 的​​更新。完整代码在本题末尾

StackOverflow: How to properly implement INotifyPropertyChanged

  • 我有一个 System.Windows.Forms.Form
  • 此表单有一个 BindingSource。
  • 绑定源的 DataSource 属性设置为我的类 Person
  • 我有一个绑定到 BindingSource 的 DataGridView
  • 我已将几个 Person 实例添加到绑定源
  • 已正确显示添加的人员。
  • 如果我以编程方式更改绑定源中的 Person,更改后的值会正确显示。

到目前为止一切顺利。如果 Person 在单独的线程中更改,则会出现问题。

我经常收到带有消息的 InvalidOperationException

BindingSource 不能是它自己的数据源。不要将 DataSource 和 DataMember 属性设置为引用回 BindingSource 的值。

我想这与更新是在一个可等待的异步任务中完成的事实有关。我知道在更新用户界面项之前,您应该检查是否 InvokeRequired 并采取相应措施。

private void OnGuiItemChanged()
{
    if (this.InvokeRequired)
    {
       this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
    }
    else
    {
        ... // update Gui Item
    }
}

但是,当使用绑定源时,更改是在绑定源内部处理的。所以我无法检查 InvokeRequired

那么如何在非 UI 线程中更新也存储在绑定源中的项目?

按要求:Person 类的实现和我的表单的一些代码

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)
    {
    }
}

【问题讨论】:

标签: c# multithreading bindingsource


【解决方案1】:

正如您所提到的,更改是在 BindingSource 类中处理的,所以我看到的最简单的方法是将其替换为以下内容

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 线程上创建的。

【讨论】:

  • 这成功了!在设计器中,我将绑定源替换为您的绑定源。该异常不再发生。谢谢伊万!我唯一需要改变的是构造函数。设计者想要一个带有 IContainer 的构造函数,所以当表单被 Disposed 时,它可以被 Disposed。
  • 很好的答案,但我有一点建议要改变 ArgumentOutOfRangeException... 将 OnListChanged 事件中的整个代码放入 lock(this)
  • 救命稻草。简短而甜蜜。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-06
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 2015-03-02
  • 2021-01-07
相关资源
最近更新 更多