【问题标题】:BindingList<> ListChanged eventBindingList<> ListChanged 事件
【发布时间】:2010-11-23 23:33:32
【问题描述】:

我有一个类的 BindingList 设置为 BindingSource 的 DataSource 属性,而后者又设置为 DataGridView 的 DataSource 属性。

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

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

2。 我很难理解的是,我应该在哪里最好地拦截 ListChanged 事件以尝试将事物编组到 UI 线程上?我猜我需要以某种方式引用 UI 线程来帮助做到这一点?

我已经阅读了很多关于此的帖子/文章,但我很苦恼,因为我不完全了解这里的工作机制。

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

(我使用的是 .NET 2.0)

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 我理解它,但它仍然很神奇,并且可以做每个人都想做的事情。通过数据结构更新控件,无需担心调用。
    • @bulltorious 你可以提供赏金:)
    【解决方案2】:
    1. 这种观点很公平。在幕后,CurrencyManager 和 Binding 等其他对象可确保在底层数据源更改时更新控件。

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

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

    该按钮启动另一个线程,该线程模拟获取新项目以包含在 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; }
        }
    

    【讨论】:

    • 阿尔弗雷德,谢谢你的清晰例子。但是,假设我有一个在另一个线程上创建的类,它创建并添加到该线程的列表中。要将数据绑定到网格,我需要对 UI 线程的引用,是吗?我不知道在这里做什么是最好的。也许通过类构造函数传入对表单的引用,并以这种方式做某事?
    • 1.是的。 2. 通过 Form 的 构造函数或其他成员将对象的引用传递给表单。
    • 安迪,对象通常与线程无关,控件是一个例外。它是在线程上执行的代码。
    猜你喜欢
    • 1970-01-01
    • 2012-10-28
    • 2014-11-15
    • 2010-11-12
    • 2020-01-16
    • 2016-12-28
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    相关资源
    最近更新 更多