Un bel po’ di carne al fuoco già nel titolo, quindi preparatevi ;-)
Scenario
Diciamo che il vostro cliente è un giornale, e che su quel giornale ci finiscano casualmente delle pubblicità, e che sempre casualmente voi stiate realizzando per quel giornale un gestionale ad uso interno :-). Tralasciando tutto il processo diciamo che avete in mano una cosa che potremmo chiamare “Impegnato” che è una lista di potenziali pubblicità che dovrebbero finire su una certa uscita, potenziali e dovrebbero perchè per tutta una serie di buoni motivi la redazione/direzione/grafica potrebbero dedicere di apportare modifiche dell’ultima ora che portano al taglio di uno spazio o ad una variazione rispetto a quanto pattuito con il cliente.
Quello che succede è che dopo ogni uscita ci sono un uomo e una donna che spulciano a mano il giornale e confrontano quello che è stato pubblicato con la lista dell’impegnato che gli produco io, questo confronto produce come output un set di modifiche/conferme stile Mastermind:
  • Ok: giusta al posto giusto;
  • Pubblicata ma sbagliata;
    • posto sbagliato;
    • dimensioni sbagliate
    • colore – B/N
    • etc…
  • Non pubblicata:
    • Colpa nostra: perchè?;
      • --> ripianificazione
    • Richiesta del cliente: perchè?;
      • --> ripianificazione
  • Sospesa:
    • Scelta nostra: perchè?
    • Richiesta del cliente: perchè?
  • etc…
Queste variazioni hanno la necessità di essere storicizzate e produrre quindi una “reservation history” consultabile che permetta di ricostruire la sequenza di avvenimenti che hanno portato alla pubblicazione di una pubblicità; è evidente che in una situazione di questo genere una Reservation è qualcosa di immutabile del tipo:
public interface IAdvertisementReservation
{
    AdvertisementType Type { get; }
    AdvertisementPosition Position { get; }
    AdvertisementColor Color { get; }
    Double ModulesQuantity { get; }
    IPublication PublishQueue { get; }
    AdvertisementReservationStatus Status { get; }
}
Quello di cui abbiamo bisogno ora è un sistema per poter editare lo stato di quella cosa immutabile… che in realtà comporta la creazione di un “figlio” in una catena tipo linked-list. E’ evidente che i concetti di “figlio” e “linked-list” non hanno nulla a che spartire con il modello e sono degli ottimi candidati per diventare parte integrante di un ViewModel atto a produrre una cosa del tipo:
UI Mock-up 001
Figoso questo tool per fare UI Mock-up si chiama: CartaPenna+Fantasia :-)
Adesso abbiamo però una “magagna”: immutabilità;
La sorgente dati di quella cosa è una IEnumerable, non è una collection perchè l’impegnato è scolpito nella pietra.
Questo scenario è un fantastico candidato per un ViewModel(Of ViewModel):
  • ReservationManagerViewModel:
    • IEnumerable:
      • IAdversisementReservation
      • EditStatus
Abbiamo quindi la lista di reservation di partenza, la mappiamo su un view model per la gestione, questo view model farà da adapter a adatterà ogni item con un apposito view model che espone l’elemento originale e una classe (un piccolo DTO) che deriva da EditStatus (che è abstract): OkStatus, RelocateStatus, SuspendStatus etc…
Sfruttando la potenza di Wpf e grazie a DataTemplate e Trigger sulla base del tipo di EditStatus visualizzo un template ad-hoc per l’editing dello stato di quella Reservation, il tutto in un bellissimo e semplicissimo ItemsControl.
Prima di continuare un paio di considerazioni:
  1. Ultimamente ho detto che il rischio dei DTO è l’abuso di DTO, ergo passate la vita a scrivere DTO (onore al capo che ha coniato questa frase): questo è uno scenario in cui il gioco vale la candela tutta la vita;
  2. Sempre ultimamente ho detto che “rimappareIList su IList> costa un sacco e ha anche qualche magagna: come al solito non viviamo di dogmi e scenari abbastanza complessi, nel loro insieme, meritano soluzioni flessibili e sensate, anche se a prima vista costose;
  3. Wpf I love you: se penso di dover realizzare la stessa cosa (la UI per l’edit) con Windows Forms potrei dire che sarebbe decisamente più facile imparare un dialetto cinese e poi pinagere in quel dialetto… oppure passare l’esistenza con la peggiore delle suocere :-), in casi come questo Wpf dimostra tutta la sua potenza e dimostra anche che la curva di apprendimento merita di essere affrontata perchè superato lo scoglio iniziale fate cose che con Windows Form/GDI avrebbero un costo pressochè inavvicinabile… cosa ci fate ancora li con una tecnologia legacy!?!?!?! ;-)
Ma… è l’unica soluzione?
Qualcuno ricorderà che ho detto una cosa del tipo: un’implementazione custom di IBindingList risolve molte delle problematiche che altrimenti ci porterebbero a “rimappare”…
Cerchiamo di spiegare il concetto senza perderci nei meandri del framework e dando contemporaneamente senso al titolo del post.
Un salto nel passato
torniamo per un attimo all’anno del signore 2000, se qualcuno ricorda fummo folgorati dalla possibilità di scrivere una cosa del tipo:
DataTable dt = …
myDataGrid.DataSource = dt;

e veder magicamente comparire nella giglia le colonne automaticamente mappate sulle colonne della DataTable. La “causa prima” del tutto è un interfaccia che si chiama ITypedList il cui scopo è quello di descrivere come è strutturata la lista… e voi direte ok, ma quella cosa funziona anche se come DataSource ci piazziamo una MyCustomClass[], si è vero ma fa un uso pazzesco di reflection ;-). Quello che succede è più o meno questo:
  • la DataSource è una DataTable:
    • rimappa la DataSource su DataTable.DefaultView e ricomincia… la DataView è ITypedList;
  • La DataSource è ITypedList:
    • Chiama GetItemProperties() e qui viene il bello…;
  • altrimenti se la DataSource è ICollection/Array cerca di capire quale sia il tipo degli item, analizzalo via reflection e costruisci le colonne;
Prima di tornare a ITypedList.GetItemProperties() vediamo di definire un po’ meglio la “big picture”: l’interfaccia IBindingList ha un evento ListChanged che serve per notificare al sottoscrittore che qualcosa nella “lista” (in realtà IBindingList è molto più di una lista) è cambiato, negli arguments dell’evento troviamo una cosa decisamente interessante, troviamo un’enumerazione che ci dice che tipo di cambiamento c’è stato nella lista, guarda caso ci sono un alcuni valori decisamente interessanti:
ListChangedType.PropertyDescriptorAdded;
ListChangedType.PropertyDescriptorDeleted;
ListChangedType.PropertyDescriptorChanged;

IBindingList prevede la possibilità di notifcare al mondo che la struttura dei dati, non solo i dati, è cambiata… la cosa interessante è se la lista in questione oltre ad essere IBindingList è anche ITypedList… la vedete anche voi la big picture?
Torniamo per un momento alla DataGrid del framework 1.0, lo stesso discorso vale la GridView del framework 2.0, e purtroppo per nessun controllo Wpf, ma non è un problema.
La griglia scopre che la DataSource è ITypedList, e se è IBindingList si aggancia l’evento ListChanged, a questo punto chiama GetItemProperties() e ottiene una collection di PropertyDescriptor che sono delle classi nate con lo scopo di descrivere un’altra classe (sono una cosa concettualmente molto diversa da System.Reflection.PropertyInfo, anche se superficialmente condividono molto), implementando noi ITypedList risulta adesso evidente che possiamo ingannare il chiamante e fargli credere che eistano proprietà che in realtà non ci sono :-)… se il tipo di cambiamento notificato con l’evento ListChanged riguarda la struttura la griglia chiamerà nuovamente GetItemProperties() per capire cosa è cambiato e adegurasi di conseguenza. Creare una colonna calcolata a runtime a questo punto potrebbe essere un gioco da ragazzi ;-)
Questo però ci porta ad un altro problema, o meglio ci porta a farci una domanda, ma quando in Wpf scriviamo una cosa del genere:
<TextBlock Text="{Binding Path=PropertyName}" />
cosa succede dietro le quinte?
A prima vista potremmo essere portati a pensare che li dietro ci sia un bel po’ di reflection, e ci potrebbe anche essere se passiamo come data context di quel TextBlock una entità veramente POCO, se però facciamo un po’ di ricerche potremmo scoprire che esiste un’altra interfaccia decisamente interessante:
ICustomTypeDescriptor
Quello che succede è che se la classe che diventa il DataContext di quella TextBlock implementa ICustomTypeDescriptor l’infrastruttura di binding non fa più nessuna assunzione e al momento del binding chiama GetProperties() e va li alla ricerca della proprietà “PropertyName”
Ricapitoliamo…
… che è il caso, e facciamolo con un esempio, partiamo dalla fine:
var query = new ReservationsByPublicationQuery( this.CurrentPublication );
var source = this.DataContext.GetByQuery<IAdvertisementReservation>( query );

var view = new EntityView<IAdvertisementReservation>( source.ToArray() );
view.AutoGenerateProperties = false;
view.AddPropertyMapping( "EditStatus", typeof( EditStatus ), obj => { var status = ( EditStatus )obj.GetCustomValue( "EditStatus" ); if( status == null ) { status = EditStatus.Undefined; obj.SetCustomValue( "EditStatus", status ); } return status; } ); view.AddPropertyMapping( "Confirm", typeof( ICommand ), obj => { var cmd = obj.GetCustomValue( "Confirm" ) as ICommand; if( cmd == null ) { cmd = DelegateCommand.Create() .OnExecute( o => { var val = ( EditStatus )obj.GetCustomValue( "EditStatus" ); if( val == EditStatus.Undefined ) { obj.SetCustomValue( "EditStatus", EditStatus.Ok ); } else { obj.SetCustomValue( "EditStatus", EditStatus.Undefined ); } } ); obj.SetCustomValue( "Confirm", cmd ); } return cmd; } );
Con calma e un pezzetto al volta:
var query = new ReservationsByPublicationQuery( this.CurrentPublication );
var source = this.DataContext.GetByQuery<IAdvertisementReservation>( query );
Nulla di trasendentale utilizziamo una variazione sul tema “Specification Pattern”, che mixato con un Builder è una vera manna, e recuperiamo dal nostro DataContext i dati che vogliamo manipolare, poi creiamo una vista sui dati, una EntityView è una implementazione custom di IBindingList, IBindingListView, ITypedList e un po’ di altre cose “marginali”…
var view = new EntityView<IAdvertisementReservation>( source.ToArray() );
view.AutoGenerateProperties = false;
Una view si comporta esattamente come una View di Sql Server ergo è un qualcosa che può esporre una vista sui dati manipolabile, è possibile fare:
  • Filtri;
  • Sort;
  • Aggiunta, rimozione di elementi;
  • etc.. etc..
senza alterare la data source, con la dichiarazione di cui sopra creiamo una EntityView “statica” a cui non possono essere aggiunti o rimossi elementi. Infine diciamo all view di non generare in automatico le “proprietà”, un’eventuale chiamata a GetItemProperties() a questo punto ritornerebbe una collection di PropertyDescriptor vuota.
Infine creiamo delle proprietà calcolate:
view.AddPropertyMapping( "EditStatus", typeof( EditStatus ), obj =>
{
    var status = ( EditStatus )obj.GetCustomValue( "EditStatus" );
    if( status == null )
    {
        status = EditStatus.Undefined;
        obj.SetCustomValue( "EditStatus", status );
    }

    return status;
} );
questo stralcio di codice crea una proprietà calcolata di tipo EditStatus (una mia classe, un banale DTO) il cui nome è EditStatus, non c’è nessun vincolo sul nome, e che infine utilizza un delagato per fare il “get”, producendo quindi una proprietà in sola lettura. Questo altro non fa che creare una classe custom che implementa ICustomTypeDescriptor e che quindi viene ben digerita dal motore di data binding e cosa molto interessante non fa il benchè minimo uso di reflection. L’altra cosa interessante è che la classe custom implementa anche INotifyPropertyChanged facendo si che questo:
var itemView = view.ElementAt( 0 );
view.SetCustomPropertyValue( "EditStatus", itemView, EditStatus.Ok );
generi alla fine della fiera una evento ListChanged notificando al motore di data binding che un certo elemento nella lista (IBindingList) è cambiato e facendo si che il motore di binding venga stimolato ad andare a rileggersi il valore della proprietà cambiata, ed essendo il DataContext di tipo ICustomTypeDescriptor, alla fine venga da noi facendo si che la nostra proprietà calcolata prenda vita, toghissimo!
facendo un esempio semplice semplice questa è una proprietà calcolata in lettura/scrittura:
view.AddPropertyMapping( "Name", typeof( String ), obj =>
{
    return obj.GetCustomValue( "Name" ) as String;
}, ( obj, value ) => 
{
    obj.SetCustomValue( "Name", value );
} );
Nel primo esempio invece la seconda proprietà calcolata è di tipo ICommand dimostrando che ci si può mettere qualsiasi cosa e si può arrivare ad avere uno xaml di questo tipo:
<ItemsControl ItemsSource="{Binding Path=Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=EditStatus}" />
                <Button Command="{Binding Path=Confirm}" Content="Invoke Command" />
            StackPanel>
        DataTemplate>
    ItemsControl.ItemTemplate>    
ItemsControl>
che funziona alla perfezione nonostante le proprietà EditStatus e Confirm non esistano proprio.
Concluedendo… che è il caso :-)
  • “Wrappare” ha molto senso se dovete montare molta logica intorno all’item/entity che state manipolando;
  • Se avete a disposizione una implementazione di IBindingList iniettarci la logica per gestire le proprietà calcolate è abbastanza banale, è tutto il resto di IBindingList che non lo è per niente… e per cose semplici vale molto la pena di wrappare con delle proprietà “calcolate” come nell’esempio ottenendo lo stesso identico risultato, anche in termini prestazionali, ma scrivendo molto meno codice. E’ evidente che al complicarsi della logica l’ago della bilancia si sposta verso un vero e proprio “ViewModelWrapper”, lo dimostra la proprietà calcolata che espone il command, è fattibile ma comincia ad essere troppo complesso e anche poco leggibile quindi probabilmente poco manutenibile;
Se voleste approfondire quello che c’è dietro e quindi:
Non avete che da chiedere ;-) e sarò ben felice, con i miei tempi, di intasare il muro di UGI.
.m