DataBinding di specifici oggetti in collezioni

Il DataBinding in WPF è sicuramente un elemento essenziale ed un primo cittadino ma supponiamo di avere una collezione generica come ObservableCollection<T>, dove T è una nostra classe, per esempio la classica classe Person contenente le proprietà Nome, Cognome e Descrizione: come facciamo a visualizzare tramite XAML nella nostra finestra la descrizione di Mario Rossi?


Diagramma della classe Person

Questo scenario non è direttamente supportato in quanto la sintassi di Binding permette di specificare proprietà, sottoproprietà ed indicizzatori ma non selezionare oggetti in collezioni dato il valore di alcune loro proprietà.

NON è possibile per esempio scrivere, supponendo come contesto dei dati la collezione (DataContext=Collezione), codice XAML di questo tipo

<TextBlock Text="{Binding Path='Name=[Mario],SurName=[Rossi],Path=[Description]'}" />

per selezionare la descrizione dell'oggetto della collezione che ha come valore della proprietà Name "Mario" e come valore della proprietà SurName il valore "Rossi".

Vediamo come risolvere questo problema:

Per semplicità ci limiteremo a supportare la selezione della proprietà ToString che nel caso della classe Person restituirà esattamente "Name Surname".

Potremmo cioè poter scrivere

<TextBlock Text="{Binding Path='Mario Rossi.Description'}" />
 

ma nulla vieta di estendere questo esempio per supportare una sintassi "XPath o Linq Like".

Ricapitolando il DataBinding viene effettuato su Proprietà, dobbiamo perciò far credere al motore di DataBinding di WPF che la nostra collezione espone una proprietà "Nome Cognome" che restituisce il nostro oggetto Person selezionato.

Questo problema è risolvibile estendendo tramite l'ereditarietà la classe ObservableCollection<T> e implementando l'interfaccia ICustomTypeDescriptor che per l'appunto permette di fornire informazioni dinamiche di tipo personalizzato come ad esempio le proprietà che espone una classe.

Se la nostra classe non espone questa interfaccia il motore di DataBinding utilizzerà la Reflection per capire quali proprietà sono disponibili.

NOTA BENE:
La Reflection è relativamente lenta e sebbene possano essere utilizzati internamente dei meccanismi di caching, per velocizzare questa operazione sono nati progetti come HyperDescriptor.

L'interfaccia ICustomTypeDescriptor non è tra le più snelle del Framework ed infatti contiene 12 metodi ed è in genere preferibile derivare dalla classe CustomTypeDescriptor ed effettuare solamente gli override di nostro interesse ma purtroppo la nostra ereditarietà è già stata "bruciata" con ObservableCollection<T> e la nostra scelta deve ricadere obbligatoriamente sull'interfaccia.

Anche se a prima vista questa interfaccia potrebbe intimorire a noi interessa veramente solo il metodo GetProperties, possiamo quindi limitarci nell'implementazione degli altri metodi a ritornare i valori di default.

NOTA BENE: Per migliorare le prestazioni possiamo implementare un sistema di caching

Il metodo GetProperties dell'interfaccia ICustomTypeDescriptor serve proprio a ritornare al Framework una collezione di proprietà, basterà quindi eseguire un ciclo su tutti gli elementi della collezione e ritornare per ogni elemento una proprietà chiamata con il metodo ToString dell'oggetto e che contenga come valore l'oggetto stesso.


Esempio di implementazione di GetProperties

Le proprietà sono rappresentate dalla classe astratta PropertyDescriptor, creiamo dunque una classe generica GenericPropertyDescriptor<T> derivante da tale classe con un semplice costruttore che accetta in ingresso un oggetto T e che tramite gli override ad GetValue ed SetValue restituisce ed imposta il valore dell'oggetto. Per impostare il nome della proprietà la classe dovrà chiamare il costruttore della classe base passandogli in questo caso la stringa ritornata dal metodo ToString dell'oggetto.

NOTA BENE: In uno scenario più flessibile probabilmente vorremmo poter passare una funzione agente sull'oggetto per determinare il nome della proprietà
ATTENZIONE: Il nome della proprietà non dovrebbe contenere caratteri come il punto che sono interpretati come sottoproprietà dal motore di DataBinding di WPF

La classe astratta PropertyDescriptor definisce anche altre proprietà e metodi astratti che possiamo però semplicemente implementare con valori di default visto che l'importante è che la proprietà restituisca, imposti e si chiami in un certo modo. Rimando all'analisi del codice allegato per i dettagli.


Diagramma della classe GenericPropertyDescriptor<T>

Il gioco è quasi ultimato, creiamo ora per provare le nostre classi una Window con impostata essa stessa come DataContext e con esposta tramite una proprietà la nostra ObservablePropertyCollection<Person> popolata con alcune persone di esempio.

Nelllo XAML corrispettivo aggiungiamo uno StackPanel che visualizzi tramite TextBlock le varie proprietà dell'elemento Mario Rossi

E il gioco è fatto. Lo XAML risulta immediatamente intuitivo anche se a Design-Time purtroppo non visualizzerà alcun risultato.

Nel codice allegato è presente inoltre un pulsante che dimostra come la soluzione proposta, a differenza di soluzioni a base di Converter, supporta la notifica delle singole proprietà dell'elemento selezionato.

Codice Allegato

Missione completata

Print | posted on sabato 22 gennaio 2011 01:47

Comments on this post

# re: DataBinding di specifici oggetti in collezioni

Requesting Gravatar...
Avevo valutato attentamente anche la soluzione con Converter ma purtroppo non era possibile supportare la notifica delle singole proprietà dell'elemento selezionato come citato a fine articolo. Per portarti un esempio se si crea un Converter che accetta come parametro "Mario Rossi.Description" il binding rimane sulla collezione e di conseguenza se la proprietà Description cambia il valore non viene aggiornato. Questo problema può essere risolvibile impostando come DataContext del controllo "Mario Rossi" e nel binding un semplice path="Description" ma in tal modo se un oggetto deve visualizzare sia "Mario Rossi" che "Marco Sempronio" in due proprietà diverse iniziano i problemi in quanto il DataContext è unico. La soluzione dell'articolo di contro non è al momento portabile su Silverlight (neanche in versione Windows Phone)
Left by Leonardo on gen 25, 2012 4:59

Your comment:

 (will show your gravatar)
 
Please add 8 and 3 and type the answer here: