Una collezione avanzata per il data binding di WPF

di Cristian Civera, in Windows Presentation Foundation,

Il motore di data binding di Windows Presentation Foundation per poter essere sfruttato appieno, necessita di collezioni di dati che implementino l'interfaccia INotifyCollectionChanged. In questo modo, aggiungendo o togliendo elementi dalla lista, il motore si accorge degli elementi cambiati e cambia di conseguenza l'interfaccia.

Nello script #25, si è visto come implementare questa interfaccia e come la classe ObservableCollection effettua già questo lavoro. Essa però non dispone di alcune funzionalità utili in casi reali come la possibilità di inibire gli eventi per una serie di operazioni sulla collezione. Ad ogni metodo di Add, Remove, Clear infatti, viene scatenato l'evento sulle proprietà Count, Item[] e la rispettiva NotifyCollectionChangedAction. Questo significa che se dobbiamo inserire parecchi elementi, ad ogni modifica della lista, scattano delle operazioni sui controlli di lista associati.

E' possibile creare però una propria classe con un metodo StartDeferRefresh per aprire una finestra in cui manipolare la lista come si vuole, senza scatenare eventi, per poi ripristinare l'operazione quando si ha terminato.
Si definisce quindi una propria classe che sovrascriva i metodi OnPropertyChanged e OnCollectionChanged in modo che in funzione di una campo privato, questi lancino o meno gli eventi.

public class ObservableCollectionEx<T> : ObservableCollection<T>
{
  private bool _deferRefresh;

  protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
  {
  if (!_deferRefresh)
    base.OnPropertyChanged(e);
  }

  protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  {
  if (!_deferRefresh)
    base.OnCollectionChanged(e);
  }

  public bool DeferRefresh
  {
  get { return _deferRefresh; }
  private set
  {
    _deferRefresh = value;
  }
  }

Il metodo StartDeferRefresh non fa altro che impostare il campo privato e restituire un oggetto IDisposable. Questo permette di ripristinare la generazione degli eventi usando il costrutto try/finally o la using.

public IDisposable StartDeferRefresh(bool raiseEvents)
{
  this.DeferRefresh = true;

  return new Disp(this, raiseEvents);
}

La classe interna DeferDispose riceve i riferimenti alla collezione stessa e un parametro per indicare se scatenare al termine gli eventi, dando per scontato che la lista sia cambiata.

protected class Disp : IDisposable
{
  private ObservableCollectionEx<T> parent;
  private readonly bool raiseEvents;

  public Disp(ObservableCollectionEx<T> parent, bool raiseEvents)
  {
    this.parent = parent;
    this.raiseEvents = raiseEvents;
  }

  void IDisposable.Dispose()
  {
    this.parent.DeferRefresh = false;

    // Genero gli eventi, se richiesto
    if (raiseEvents)
    {
      parent.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
      parent.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
      parent.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
  }
}

A questo punto l'utilizzo è piuttosto rapido perché non si deve far altro che chiamare lo StartDeferRefresh con il costrutto using e manipolare la lista.

ObservableCollectionEx<String> values = new ObservableCollectionEx<String>();

using (values.StartDeferRefresh(true))
{
  values.Add("item 1");
  values.Add("item 1");
}

Questa tecnica infine può essere sfruttata anche per invocare gli eventi sul thread principale, per risolvere la problematica descritta nello script #25, andando ad invocare il Dispose sul thread UI, anche se la manipolazione della lista viene fatta in un thread differente.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi