Continuiamo con la nostra serie introduttiva su Model View ViewModel.
L’ultima volta, troppo tempo fa, abbiamo parlato del primo approccio a Model View ViewModel. Andiamo avanti e prima di addentrarci nelle vere problematiche legate all’implementazione di questo pattern vediamo di capire come gestire un modello che sia un filo più complesso di quello dell’ultima volta:
image
la solita solfa, un bel master-detail “tipicamente tipico”, prossimamente faremo anche un esempio N-N e non sempre e solo 1-N; fissiamo degli obiettivi per questo giro:
  1. essere in grado di visualizzare la lista degli indirizzi;
  2. essere in grado di modificare un indirizzo;
  3. essere in grado di eliminare un indirizzo;
i due punti mettono immediatamente in evidenza un paio di cose interessanti:
  • abbiamo la necessità di capire come visualizzare una lista di elementi in una view;
  • abbiamo la necessità di capire come tenere traccia dell’elemento selezionato nella lista degli indirizzi;
  • abbiamo la necessità di capire come visualizzare i dati dell’elemento selezionato;
  • abbiamo la necessità di capire come gestire un’azione, la cancellazione in questo caso;
DataBinding: “I Love You” ;-)
i primi due punti sono un “problema” che deleghiamo volentieri al motore di data binding di wpf, ma prima di vedere come preoccupiamoci di aggiornare il nostro ViewModel al fine di soddisfare i nuovi requisiti:
image
Refactoring
La prima cosa che notiamo è un massiccio refactoring dall’ultima volta:
11b96bde-e2da-437e-94e5-2dc714af0c22
il MainViewModel innanzitutto comincia a fare il MainViewModel e non rappresenta più una Person, ruolo che non gli si addiceva, abbiamo introdotto un AbstractViewModel che faccia da classe base per tutti e abbiamo introdotto i ViewModel sia per Person che per Address. In perfetta sintonia con il concetto di Separation of Concern il modello è completamente mediato dai ViewModel e non è mai esposto direttamente alla View. In questo esempio la gerarchia dei ViewModel è speculare alla gerarchia del modello ma non deve essere necessariamente così: il modello ha lo scopo di modellare il nostro mondo, la realtà che vogliamo rappresentare, mentre il ViewModel ha lo scopo di adattare il modello ad uno specifico Use Case.
Questa nuova struttura ci permette di soddisfare il primo requisito.
“List” Data Binding
Aggiorniamo la nostra UI:
image
con questo semplice xaml:
<ListView Grid.ColumnSpan="2" 
          ItemsSource="{Binding Path=Person.Addresses}"
          Grid.Row="4" HorizontalAlignment="Stretch" Margin="12" 
          VerticalAlignment="Stretch">
    <ListView.View>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource streetAddressConverter}}" Header="Street" />
            <GridViewColumn Header="City" DisplayMemberBinding="{Binding Path=City}"  />
        <GridView>
    <ListView.View>
<ListView>
Abbiamo aggiunto alla nostra View una ListView, nulla di trascendentale, ci sono però un paio di cose degne di nota:
  • per agganciare la lista degli indirizzi alla ListView mattiamo in binding la collection Addresses esposta da PersonViewModel con la proprietà ItemsSource:
    • se andiamo a spulciare notiamo che Addresses è una ObservableCollection, una ObservableCollection è una collection apposita per Wpf che ha la capacità di notificare alla UI le variazioni interne alla collection, una sorta di INotifyPropertyChanged per le liste;
  • per “realizzare” le colonne ci limitiamo a mettere in binding una GridViewColumn con il dato che ci interessa, anche qui una cosa nuova degna di nota: in uno dei casi utilizziamo un converter. Avete inoltre notato che non stiamo specificando esplicitamente un Path verso una proprietà questo significa che il motore di binding deve usare tutta l’entità e non una specifica proprietà, quindi in questo caso la colonna sarà in binding con l’intera istanza dell’AddressViewModel.
Binding Converter
Un converter è una qualsiasi classe che implementa l’interfaccia IValueConverter:
public class StreetAddressConverter : IValueConverter
{
    public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
    {
        if( value != null )
        {
            var address = ( AddressViewModel )value;
            return String.Format( "{0}, {1}", address.Street, address.Number );
        }

        return null;
    }

    public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {
        throw new NotImplementedException();
    }
}
un converter si inserisce nella binding pipeline e ha la possibilità di manipolare il contenuto del binding secondo questo schema:
image
Per completare il tutto quello che ci resta da fare è modificare il nostro MainViewModel così:
public class MainViewModel : AbstractViewModel
{
    public MainViewModel()
    {
        var person = new Model.Person( new DateTime( 1973, 1, 10 ) )
        {
            FirstName = "Mauro",
            LastName = "Servienti"
        };

        person.Addresses.Add
        (
            new Model.Address( person )
            {
                Street = "v. Nonzo",
                Number = "12/B",
                City = "Città del Mondo",
                ZipCode = "10101",
                Province = "SW",
                Country = "Italy"
            }
        );

        person.Addresses.Add
        (
            new Model.Address( person )
            {
                Street = "v. Schiaffino",
                Number = "11",
                City = "Milano",
                ZipCode = "20100",
                Province = "MI",
                Country = "Italy"
            }
        );

        this.Person = new PersonViewModel( person );
    }

    private PersonViewModel _person = null;
    public PersonViewModel Person
    {
        get { return this._person; }
        set
        {
            if( value != this.Person )
            {
                this._person = value;
                this.OnPropertyChanged( () => this.Person );
            }
        }
    }
}
Qui facciamo una cosa sola: abbiamo cablato, per ora, la creazione del modello per tenere bassa la complessità.
Creiamo l’istanza di Person la popoliamo con le proprietà del caso e con un paio di indirizzi giusto per avere dei dati da visualizzare, infine creaimo il ViewModel per mediare il rapporto tra la Person e la View.

I ViewModel che wrappano le nostre entità non fanno nulla di trascendentale, l’unica cosa degna di nota è il costruttore del PersonViewModel che a sua volta crea la lista degli indirizzi:
public PersonViewModel( Model.Person person )
{
    this.person = person;

    this.FirstName = this.person.FirstName;
    this.LastName = this.person.LastName;
    this.BornDate = this.person.BornDate;
    this.Age = this.EvalAge( this.BornDate );

    this.Addresses = this.person.Addresses
        .Aggregate( new ObservableCollection<AddressViewModel>(), ( list, obj ) =>
        {
            var avm = new AddressViewModel( obj );
            list.Add( avm );
            return list;
        } );
}
Per ora direi che ci possiamo fermare qui, abbiamo messo un bel po’ di carne al fuoco, al prossimo giro vediamo di completare i requisiti che ci siamo dati ad inizio post:
  1. essere in grado di visualizzare la lista degli indirizzi;
  2. essere in grado di modificare un indirizzo;
  3. essere in grado di eliminare un indirizzo;
Sorgenti aggiornati: Mvvm.Application-1.1.zip
.m