的BindingList<> ListChanged事件

时间:2009-08-29 10:55:22

标签: c# .net winforms data-binding datagridview

我有一个BindingList<>一个类的设置为BindingSource的DataSource属性,该属性又设置为DataGridView的DataSource属性。

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

当在UI线程上完成所有工作时,这一切都很好,但是当从非UI线程创建和更改列表时,最终会在更新网格时发生跨线程异常。我能理解为什么会这样,但是没有办法解决它......

2。 我很难理解的是,我应该在哪里最好拦截ListChanged事件来尝试将内容编组到UI线程中?我猜我需要以某种方式引用UI线程来帮助实现这个目标吗?

我已经阅读了很多这方面的帖子/文章,但我很难挣扎,因为我不完全理解这里的工作机制。

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

(我使用的是.NET 2.0)

2 个答案:

答案 0 :(得分:28)

您可以扩展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);
    }
}

答案 1 :(得分:2)

  1. 这种观点足够公平。在封面下,其他对象(如CurrencyManager和Binding)确保在基础数据源发生更改时更新控件。

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

  3. 我组装了一个快速示例,使用DataGridView,BindingSource和Button创建一个Form。

    该按钮旋转另一个线程,该线程模拟获取包含在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; }
        }