2009-08-29 12 views
19

Ho un BindingList <> di una classe impostata sulla proprietà DataSource di BindingSource, che è a sua volta impostata sulla proprietà DataSource di un DataGridView.BindingList <> ListChanged event

1. È a mia conoscenza che qualsiasi aggiunta all'elenco genererà un evento ListChanged che si propagherà attraverso BindingSource e quindi su DataGridView, che si aggiorna automaticamente per visualizzare la modifica. Questo accadrà perché gli eventi sono stati automaticamente collegati. (Sì?)

Questo va tutto bene quando tutto il lavoro viene eseguito sul thread dell'interfaccia utente, ma quando l'elenco viene creato e modificato da un thread non dell'interfaccia utente, in definitiva si verifica un'eccezione cross-thread quando la griglia è aggiornato. Posso capire perché succede, ma non so come risolverlo ...

2. 2. Quello che sto avendo difficoltà a capire, è dove dovrei intercettare al meglio l'evento ListChanged per provare a fare il marshalling sul thread dell'interfaccia utente ? Sto indovinando che ho bisogno di un riferimento al thread dell'interfaccia utente in qualche modo per aiutare a fare questo?

Ho letto molti post/articoli su questo, ma sto facendo fatica perché non comprendo appieno i meccanismi al lavoro qui.

Non cambierò mai alcun elemento una volta che sono nella lista, aggiungendoli solo e cancellando inizialmente l'elenco.

(sto usando .NET 2,0)

risposta

27

È possibile estendere BindingList utilizzare un ISynchronizeInvoke (implementato da System.Windows.Forms.Control) per schierare le invocazioni di eventi sul thread UI.

Quindi tutto quello che devi fare è usare il nuovo tipo di lista e tutto è ordinato.

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

FANTASTICO. Se potessi revocare questo milione di volte, lo farei. – bulltorious

+0

Lo capisco, ma comunque è magico e fa quello che tutti vogliono fare. Aggiorna i controlli tramite la struttura dati e non devi preoccuparti di invocare. – Beached

+0

@bulltorious potresti offrire una taglia :) –

2
  1. Questo punto di vista è abbastanza giusto. Sotto le copertine, altri oggetti come CurrencyManager e Binding assicurano che i controlli vengano aggiornati quando cambia l'origine dati sottostante.

  2. Aggiunta di un elemento a un collegamento dati BindingList attiva una serie di eventi che provano ad aggiornare DataGridView. Poiché l'interfaccia utente può essere aggiornata solo dal thread dell'interfaccia utente, è necessario aggiungere elementi a BindingList dal thread dell'interfaccia utente tramite Control.Invoke.

Ho assemblato un esempio rapido creando un modulo con un DataGridView, un BindingSource e un pulsante.

Il pulsante attiva un altro thread che simula il recupero di un nuovo elemento da includere in BindingList.

L'inclusione stessa viene ripristinata nel thread dell'interfaccia utente tramite Control.Invoke.


    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, grazie per il tuo chiaro esempio. Supponiamo però che abbia una classe creata su un altro thread che crea e aggiunge a un elenco su questo thread. Per i dati collegarli a una griglia ho bisogno di un riferimento al thread dell'interfaccia utente sì? Non sono sicuro di cosa fare qui per il meglio. Passa un riferimento al modulo tramite il costruttore della classe, forse, e fai qualcosa in questo modo? – Andy

+1

1. Sì. 2. Passa un riferimento dell'oggetto al modulo tramite il costruttore ** del modulo ** o un altro membro. –

+0

Andy, gli oggetti normalmente non hanno alcuna relazione con una discussione, i controlli sono un'eccezione.È un codice che viene eseguito su un thread. –

Problemi correlati