2009-08-29 768 views
19

Tengo un BindingList <> de una clase establecida en la propiedad DataSource de un BindingSource, que a su vez se establece en la propiedad DataSource de un DataGridView.BindingList <> evento ListChanged

1. Tengo entendido que cualquier adición a la lista activará un evento ListChanged que se propagará a través de BindingSource y luego a DataGridView, que se actualizará para mostrar el cambio. Esto sucederá porque los eventos se han conectado automáticamente. (¿Sí?)

Esto está bien y bien cuando todo el trabajo se realiza en el subproceso de interfaz de usuario, pero cuando la lista se crea y cambia de un subproceso que no es UI, finalmente se produce una excepción cruzada cuando la cuadrícula esta actualizado Puedo entender por qué sucede esto, pero no cómo solucionarlo ...

2. Lo que estoy teniendo dificultades para comprender, es dónde debería interceptar mejor el evento ListChanged para intentar organizar las cosas en el hilo de la interfaz de usuario ? Supongo que necesito una referencia a la secuencia de comandos de interfaz de usuario de alguna manera para ayudar a hacer esto?

He leído muchas publicaciones/artículos sobre esto, pero estoy luchando porque no entiendo completamente los mecanismos en el trabajo aquí.

Nunca volveré a cambiar ningún elemento una vez que esté en la lista, solo se agregarán e inicialmente se borrará la lista.

(estoy usando .NET 2,0)

Respuesta

27

Puede extender BindingList utilizar un ISynchronizeInvoke (implementado por System.Windows.Forms.Control) de formar las invokations de eventos en la rosca de interfaz de usuario.

Luego todo lo que tiene que hacer es usar el nuevo tipo de lista y todo está ordenado.

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); 
    } 
} 
+3

IMPRESIONANTE. Si pudiera votar esto un millón de veces, lo haría. – bulltorious

+0

Lo entiendo, pero igual es mágico y hace lo que todos quieren hacer. Actualice los controles a través de la estructura de datos y no tenga que preocuparse por la invocación. – Beached

+0

@bulltorious podrías ofrecer una recompensa :) –

2
  1. Ese punto de vista es bastante justo. Debajo de las cubiertas, otros objetos como CurrencyManager y Binding aseguran que los controles se actualicen cuando la fuente de datos subyacente cambie.

  2. Agregar un elemento a un enlace de datos vinculados BindingList desencadena una serie de eventos que terminan tratando de actualizar el DataGridView. Dado que la interfaz de usuario solo se puede actualizar desde el subproceso de interfaz de usuario, debe agregar elementos a BindingList desde el subproceso de interfaz de usuario a través de Control.Invoke.

He montado una muestra rápida creando un formulario con un DataGridView, un BindingSource y un botón.

El botón hace girar otro hilo que simula obtener un nuevo elemento para incluirlo en BindingList.

La inclusión en sí misma se realiza de nuevo en el subproceso de interfaz de usuario a través de Control.Invocar.


    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; } 
    } 
+0

Alfred, gracias por su claro ejemplo. Pero, supongamos que tengo una clase que se crea en otro hilo que crea y agrega a una lista en este hilo. Para que los datos se vinculen a una cuadrícula, ¿necesito una referencia al hilo de la interfaz de usuario, sí? No estoy seguro de qué hacer aquí para lo mejor. Pase una referencia al formulario a través del constructor de la clase, tal vez, y haga algo de esta manera? – Andy

+1

1. Sí. 2. Pase una referencia de su objeto al formulario a través del ** constructor del formulario ** u otro miembro. –

+0

Andy, los objetos normalmente no tienen ninguna relación con un subproceso, los controles son una excepción.Es código que se ejecuta en un hilo. –

Cuestiones relacionadas