Introduzione
Ne avevo parlato un po'
di tempo fa qua sul mio blog. Se nel nostro domain model abbiamo creato una
custom collection e poi vogliamo bindarla su una
DataGridView, tale collection deve implementare l'interfaccia ITypedList. Questa
interfaccia è di per sè abbastanza semplice: richiede l'implementazione di due
soli metodi pubblici (GetItemProperties e GetListName). In pratica, il
primo metodo ritorna un oggetto PropertyDescriptorCollection che rappresenta
l'elenco delle proprietà che vogliamo bindare come colonne sulla DataGridView. Il secondo metodo ritorna una banale
stringa con il nome della lista.
Facciamo un esempio pratico. Il class designer vale in questo caso molto più
di mille parole:
Come è facilmente intuibile, la classe HockeyPlayer rappresenta un giocatore
di hockey (nome, numero di maglia, peso, altezza ed elenco di falli subiti
durante una partita). La classe Fault è il singolo fallo (tipo di fallo e quando
è avvenuto). Poi esiste la classe FaultsCollection che non fa altro che
ereditare direttamente da BindingList<Fault>. Se vogliamo
bindare questa collection ad una DataGridView, dobbiamo utilizzare l'interfaccia
ITypedList per intercettare il binding e specificare quali proprietà
vogliamo ed in quale ordine. In questo caso è piuttosto semplice, perchè le
proprietà sono solamente due, ma il problema c'è comunque e volevo
risolverlo in un modo più elegante. Mi sono posto innanzitutto le seguenti
domande:
- Non è assolutamente corretto che io come sviluppatore debba decidere
durante lo sviluppo quali siano le colonne da far vedere, ed in quale ordine.
L'utente potrebbe voler personalizzare la vista, potrebbe riordinare le
colonne
- Non è assolutamente corretto che un business object debba sapere come
mostrarsi. Mi piacerebbe quindi che ad ogni binding di FaultsCollection, tale
classe richieda una sorta di Default View ad un'altra classe gemella, che
invece implementa tutta la logica necessaria
- Mi piacerebbe che le informazioni sul binding vengano persistite in
qualche modo, ad esempio su file. In questo modo, io decido in fase di
sviluppo la vista standard, ma se poi l'utente me la cambia (re-ordering delle
colonne sulla DataGridView) io devo accorgermene, salvare il tutto e
riprendere successivamente
Tutte queste domande mi hanno portato a creare una piccola soluzione che
voglio proporre sul mio blog.
Come ho risolto il mio piccolo problema
Innanzitutto,
l'abbiamo detto prima. La classe FaultsCollection deve implementare
ITypedList.
using System;
using System.ComponentModel;
namespace DataBindingSample
{
public class FaultsCollection : BindingList<Fault>, ITypedList
{
#region ITypedList
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
// ritorno un
oggetto
PropertyDescriptorCollection
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
// ritorno
"FaultsCollection"
}
#endregion
}
}
Quando viene chiamato uno dei due metodi previsti dall'interfaccia,
non faccio altro che delegare un'altra classe allo scopo. Questa altra classe si
chiama FaultsCollectionDefaultView e dispone di due metodi statici che non fanno
altro che mappare GetItemProperties e GetListName. Il codice è riportato qui
sotto.
public static class FaultCollectionDefaultView
{
public static PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
Type tp = typeof(Fault);
PropertyDescriptorCollection m_OriginalList = TypeDescriptor.GetProperties(tp);
PropertyDescriptorCollection m_SortedList = m_OriginalList.Sort(new string[] { "Motivo" });
m_SortedList.RemoveAt(2);
return (m_SortedList);
}
public static string GetListName(PropertyDescriptor[] listAccessors)
{
return "FaultsCollection";
}
}
Il metodo GetItemProperties ottiene innanzitutto l'elenco delle
property esposte dal singolo oggetto Fault: tale elenco viene gestito da
m_OriginalList. Poi viene creato l'elenco vero e proprio che verrà utilizzato
per il binding, usando solo la proprietà Motivo e rimuovendo la proprietà ID che
non voglio mostrare mai all'utente finale.
In questo modo, non sarà direttamente la classe FaultsCollection a contenere
queste codice, ma una classe gemella che invece contiene tutta la logica che
serve per descrivere come una certa custom collection deve mostrarsi su
una DataGridView.
Prossimo passo? Persistere su un file!
Il passo
successivo dovrebbe migliorare in prestazioni ed in user-experience. L'idea è
quella di andare a leggere su un file XML l'elenco delle properties da bindare,
un elenco che potrebbe quindi anche esprimere direttamente l'ordine esatto di
default. Invece di usare il metodo statico GetProperties di TypeDescriptor per ottenere un elenco preliminare dal
quale poi togliere quello che non mi serve e riordinare, potrei direttamente
andare a leggere su file le informazioni di binding.
In questo modo, inoltre, se l'utente finale riordina le colonne sulla
DataGridView, non farei altro che persistere le nuove informazioni, per
mantenerle tra una sessione di lavoro e l'altra. Non è solo una soluzione
tecnica, ma anche una soluzione di design: non mi sembrava corretto che un
oggetto venisse farcito di troppe responsabilità, delegando invece ad altri
oggetti i relativi compiti.