Crad's .NET Blog

L'UGIblog di Marco De Sanctis
posts - 190, comments - 457, trackbacks - 70

Collection Master/Detail con ITypedList

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:

Errore ITypedList

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.

toysBindingSource

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

Print | posted on martedì 28 marzo 2006 17:32 | Filed Under [ .Net 2.0 ]

Powered by:
Powered By Subtext Powered By ASP.NET