BindingList<>的ListChanged事件

19

我有一个绑定到BindingSource的BindingList<>列表,而BindingSource又绑定到DataGridView的DataSource属性上。

1. 我了解到,对列表中的任何添加操作都会触发ListChanged事件,然后通过BindingSource传播到DataGridView,后者将更新自身以显示更改。这是因为事件已经自动连接了。(是吗?)

当所有工作都在UI线程上完成时,这一切都很好,但当该列表被非UI线程创建和更改时,最终会出现跨线程异常,当网格更新时会发生。我可以理解为什么会发生这种情况,但不知道如何解决...

2. 我很难理解的是,在哪里能够最好地截取ListChanged事件,以尝试将其调度到UI线程上?我猜想我需要某种方式引用UI线程来帮助执行此操作?

我已经阅读了许多关于此事的文章/帖子,但由于我不完全了解这里所涉及的机制,因此感到困难。

一旦它们在列表中,我将永远不会更改任何项目,只会添加它们,并最初清除列表。

(我正在使用.NET 2.0)

2个回答

29

你可以扩展BindingList,使用ISynchronizeInvoke(由System.Windows.Forms.Control实现)来将事件调用封送到UI线程。

然后你只需要使用新的列表类型,一切都会被排序。

public partial class Form1 : System.Windows.Forms.Form {

    SyncList<object> _List; 
    public Form1() {
        InitializeComponent();
        _List = new SyncList<object>(this);
    }
}

public class SyncList<T> : System.ComponentModel.BindingList<T> {

    private System.ComponentModel.ISynchronizeInvoke _SyncObject;
    private System.Action<System.ComponentModel.ListChangedEventArgs> _FireEventAction;

    public SyncList() : this(null) {
    }

    public SyncList(System.ComponentModel.ISynchronizeInvoke syncObject) {

        _SyncObject = syncObject;
        _FireEventAction = FireEvent;
    }

    protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs args) {
        if(_SyncObject == null) {
            FireEvent(args);
        }
        else {
            _SyncObject.Invoke(_FireEventAction, new object[] {args});
        }
    }

    private void FireEvent(System.ComponentModel.ListChangedEventArgs args) {
        base.OnListChanged(args);
    }
}

我理解它,但它仍然是神奇的,并且可以做每个人想要做的事情。通过数据结构更新控件而不必担心调用。 - Beached
@bulltorious 你可以提供赏金 :) - Thomas Ayoub

2
  1. 那个观点算是公正的。在底层,像 CurrencyManager 和 Binding 这样的其他对象确保当基础数据源更改时更新控件。

  2. 向数据绑定的 BindingList 添加项目会触发一系列事件,最终尝试更新 DataGridView。由于只能从 UI 线程更新 UI,因此应通过 Control.Invoke 从 UI 线程添加项目到 BindingList。

我创建了一个快速示例,创建了一个带有 DataGridView、BindingSource 和按钮的表单。

该按钮启动另一个线程,模拟获取新项目以包含在 BindingList 中。

包含本身是通过 Control.Invoke 在 UI 线程中完成的。


    public partial class BindingListChangedForm : Form {
        BindingList<Person> people = new BindingList<Person>();
        Action<Person> personAdder;

        public BindingListChangedForm() {
            InitializeComponent();
            this.dataGridView1.AutoGenerateColumns = true;
            this.bindingSource1.DataSource = this.people;
            this.personAdder = this.PersonAdder;
        }

        private void button1_Click(object sender, EventArgs e) {
            Thread t = new Thread(this.GotANewPersononBackgroundThread);
            t.Start();
        }

        // runs on the background thread.
        private void GotANewPersononBackgroundThread() {
            Person person = new Person { Id = 1, Name = "Foo" };

            //Invokes the delegate on the UI thread.
            this.Invoke(this.personAdder, person);
        }

        //Called on the UI thread.
        void PersonAdder(Person person) {
            this.people.Add(person);
        }
    }

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

Alfred,非常感谢您提供的清晰例子。但是,假设我有一个在另一个线程上创建并添加到此线程列表中的类。为了将其数据绑定到网格中,我需要引用UI线程,对吗?我不确定该怎么做最好。也许可以通过类构造函数传递表单引用,并以这种方式执行一些操作? - Andy
1
  1. 是的。
  2. 通过 Form 的构造函数或其他成员将您的对象的引用传递给表单。
- Alfred Myers
Andy,对象通常与线程无关,但控件是一个例外。它是在线程上执行的代码。 - H H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接