Scenario: abbiamo una nostra bella custom collection,
magari che implementa IBindingList, ma non siamo perfettamente soddisfatti di
come il FX ne gestisca il binding: vorremmo infatti scegliere a priori, ad esempio, l'ordine con cui le proprietà vengono
visualizzate quando si effettua un data binding con una DataGridView, o vorremmo poter popolare la nostra custom
collection di Person anche con tipi derivati da Person (normalmente
la DataGridView se la prende parecchio se ci proviamo ).
La soluzione è implementare ITypedList e io, mesi
fa, l'avevo fatto seguendo i dettami di questo articolo su MSDN
Magazine. E' andata sempre bene, finché oggi, facendo alcuni test sulle
WindowsForms, ed in particolare provando ad implementare una semplice UI
Master-Detail con BindingSource, non mi sono trovato davanti una schermata del
genere:
Prima di procedere, è necessaria una piccola premessa;
l'interfaccia ITypedList è composta da due metodi, ossia:
public interface ITypedList
{
PropertyDescriptorCollection GetItemProperties(
PropertyDescriptor[] listAccessors);
string GetItemName(
PropertyDescriptor[] listAccessors);
}
Il problema della soluzione proposta su MSDN Magazine è che essa non
prende in considerazione il parametro listAccessors, che invece è utilizzato,
ad esempio, da BindingSource tutte le volte che, assegnati DataSource e
DataMember, quest'ultimo è un tipo complesso che implementa a sua volta
ITypedList.
Supponiamo di avere la classe Baby che espone una collection di Toy:
public class Baby
{
// ... more code here ...
public ToyCollection Toys
{
get { return toys; }
}
}
Mi piacerebbe poter visualizzare, per il bambino corrente, la griglia con
l'elenco di tutti i suoi giocattoli, quindi dopo aver creato quello per il
Master, creo un bel BindingSource anche per la proprietà Toy, collegato al
precedente.
Cosa accade a questo punto quando la DataGridView chiede a toysBindingSource
di fornire informazioni circa il tipo di dati che espone? Accade che
toysBindingSource non richiama il metodo GetItemProperties di
ToysCollection come ci si potrebbe aspettare, bensì richiama quello di
BabyCollection, inserendo in listAccessors un PropertyDescriptor che punta alla
proprietà Toys: chiede in pratica alla collection dei bambini di
fornire ulteriori informazioni sul tipo restituito dalla collection dei
giocattoli.
Bella grana! Noi non abbiamo il minimo riferimento per risalire a tale
informazione da BabyCollection! Ho trovato in giro per la rete le soluzioni più
disparate, ad esempio decorare ogni proprietà collection con un
attributo che indica il tipo restituito, ma mi sembrava un'informazione
ridondante e non mi piaceva granché. Allora me ne sono inventata una io :
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
PropertyDescriptorCollection result = null;
if (listAccessors == null || listAccessors.Length == 0)
{
result = TypeDescriptor.GetProperties(typeof(T));
}
else
{
PropertyDescriptor childProperty =
listAccessor[listAccessors.Length -1];
if (typeof(ITypedList)
.IsAssignableFrom(childProperty.PropertyType))
{
Assembly asm = childProperty.PropertyType.Assembly;
object childItem =
asm.CreateInstance(childProperty.PropertyType.FullName);
if (childItem != null)
{
ITypedList typedList = childItem as ITypedList;
if (typedList != null)
result = typedList.GetItemProperties(null);
IDisposable disposable = childItem as IDisposable;
if (disposable != null)
disposable.Dispose();
}
}
}
return result;
}
Sembra complesso, ma in realtà il codice è molto semplice: nel caso
listAccessors sia vuoto, restituisco tutti i tipi, come avveniva in precedenza
(ho trascurato per maggiore chiarezza di implementare l'ordinamento o una
selezione delle proprietà). Se invece viene richiesta, tramite
listAccessor, la descrizione di una particolare proprietà,
tramite reflection richiamo il GetItemProperties della collection che questa
proprietà espone.
Quanto sopra è passibile di molte migliorie: ad es. se la proprietà in
oggetto non è ITypedList, il mio metodo ritorna null, ma nel mio caso specifico questo
non è un problema, perché tutte le collection che uso implementano
quest'interfaccia. Inoltre supporta un nesting di soli due livelli, dato che non
propago listAccessors nella chiamata alla collection figlia. Francamente non ho
la necessità di gestire contemporaneamente relazioni
mastro/dettaglio più profonde, quindi non me ne sono preoccupato. Però penso che
sia comunque un buon punto di partenza e spero che possa essere utile a
qualcuno!
powered by IMHO 1.3