MVVM e Windows Phone – Model e ViewModel (2° parte)
Posted by qmatteoq in Windows Phone Tutorials on Monday 30 July 2012 at 10:00 AM
Dopo aver visto le basi teoriche vediamo come iniziare a strutturare un progetto in modo da sfruttare il pattern MVVM, utilizzando come riferimento lo stesso descritto nel post precedente: un’applicazione per consumare un feed RSS.
Nota bene! La procedura di cui parlerò in questo e nei prossimi post vuole essere semplicemente una descrizione dell’approccio da me utilizzato e con cui sono abituato a lavorare. Non è l’unico approccio esistente e non è detto che sia il migliore.
Il primo passo è quello di creare un progetto Windows Phone “vuoto”, utilizzando il template Windows Phone Application di Visual Studio. Dopodichè andiamo a creare alcune cartelle per inserire le nostre classi secondo la struttura del pattern: è mia abitudine creare una cartella Views (per i file XAML), ViewModels (per i view models), Services (per i servizi che si occupano di gestire i dati) e Entities (per le classi che rappresentano le entità con cui lavoreremo).
Il secondo passo è quello di installare MVVM Light, il toolkit di Laurent Bugnion che offre una serie di helper e utility che semplificheranno notevolmente il nostro lavoro. Il modo più semplice per farlo è quello di utilizzare NuGet: troverete due versioni della libreria, una tradizionale e una contrassegnata dall’etichetta Libraries only. Vi consiglio di installare la seconda, che si limita ad aggiungere una reference alle DLL necessarie.
Realizziamo il servizio
Il primo passo è realizzare il model, ovvero i servizi che si occuperanno di recuperare il feed RSS (quindi un file XML “piatto”) e di tradurlo in oggetti .NET, che saremo in grado di manipolare. Il servizio non è nient’altro che una classe, che conterrà uno o più metodi per effettuare operazioni sull’RSS. Prima però abbiamo bisogno di realizzare una classe in grado di mappare ogni singola notizia contenuta nel nostro feed RSS.
public class FeedItem
{
public string Title { get; set; }
public string Description { get; set; }
public Uri Url { get; set; }
}
Ovviamente si tratta di una versione molto semplificata: in un’applicazione reale potrebbero essere molte di più le informazioni da memorizzare. Una volta definita l’entità dobbiamo realizzare un servizio in grado di tradurre l’RSS in una collezione di oggetti di tipo FeedItem. Con il vecchio approccio il codice per recuperare le notizie sarebbe stato inserito direttamente nel code behind e richiamato in fase di caricamento della pagina (ad esempio, nel costruttore della classe MainPage). Con l’approccio MVVM, invece, andiamo a realizzare una classe ad hoc per effettuare queste operazione che, nell’esempio, viene chiamata FeedService e inclusa nella cartella Services.
La definizine del servizio, nel nostro esempio, è molto semplice e si limita a contenere un unico metodo che, sfruttando LINQ to XML, è in grado di elaborare l’RSS scaricato:
public class FeedService
{
public void GetNews()
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (obj, args) =>
{
XDocument doc = XDocument.Parse(args.Result);
var result = doc.Descendants("rss").Descendants("channel").Elements("item").Select(x => new FeedItem
{
Title = x.Element("title").Value,
Description = x.Element("description").Value,
}).ToList();
OnGetNewsCompleted(new GetNewsCompletedEventArgs(result));
};
client.DownloadStringAsync(new Uri("http://feeds.feedburner.com/qmatteoq"));
}
public event EventHandler<GetNewsCompletedEventArgs> GetNewsCompleted;
public void OnGetNewsCompleted(GetNewsCompletedEventArgs e)
{
EventHandler<GetNewsCompletedEventArgs> handler = GetNewsCompleted;
if (handler != null) handler(this, e);
}
}
Dato che per scaricare il feed RSS da Internet utilizziamo la classe WebClient, che è asincrona, utilizziamo il meccanismo degli event handler per gestire l’operazione: lo sviluppatore invocherà il metodo GetNews e si sottoscriverà all’evento GetNewsCompleted, che sarà scatenato nel momento in cui il download del file XML è terminato e, grazie a LINQ to XML, lo abbiamo convertito in oggetti di tipo FeedItem.
Il codice appena presentato dovrebbe essere semplice da capire: una volta scaricato il file RSS (nel momento in cui, perciò, l’evento DownloadStringCompleted viene scatenato) tramite LINQ to XML andiamo a recuperare i nodi title e description di ogni nodo item, che rappresenta la singola notizia. Una volta che abbiamo completate questa operazione, tramite l’event invocator OnGetNewsCompleted, andiamo a scatenare l’evento GetNewsCompleted, in modo che il client sia in grado, tramite i parametri di ritorno, di recupare le notizie sotto forma di collezione IEnumerable<FeedItem>.
Come vedete, il servizio non restituisce un tipo di collezione specifico, ma una generica IEnumerable: sarà compito del view model elaborare questa collezione nel modo più consono affinchè possa essere presentato dalla view.
Realizziamo il view model
Ora possiamo passare a realizzare il view model che, come anticipato nel post precedente, fa da tramite tra il model e la view: si occupa di prendere i dati "grezzi” (nel nostro caso, la collezione di oggetti di tipo FeedItem) e di elaborarli affinchè possano essere presentati dalla View.
Con l’approccio tradizionale, ovvero nel code behind, dopo aver recuperato l’elenco delle notizie lo avremmo, ad esempio, assegnato alla proprietà ItemsSource di una ListBox, come nell’esempio:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
IEnumerable<FeedItem> feedItems = GetNews();
NewsList.ItemsSource = feedItems;
}
}
L’approccio basato su MVVM richiede però che il collegamento tra proprietà e controlli venga gestito tramite il binding: questo perchè nel view model non abbiamo accesso diretto ai controlli come avviene nel code behind; per questo motivo dobbiamo costruire le nostre proprietà supportando il meccanismo previsto dall’interfaccia INotifyPropertyChanged, affinchè ogni variazione venga automaticamente gestita dalla view. A questo scopo ci viene in aiuto MVVM Light, offrendoci una classe base da cui far ereditare i nostri view model, chiamata ViewModelBase. La caratteristica di questa classe che andremo ad usare con maggiore frequenza è l’evento RaisePropertyChanged, da scatenare ogni qualvolta il valore della proprietà è cambiato.
Il modo in cui andremo a scrivere le proprietà sarà perciò leggermente diverso da quello a cui siamo abituati. Ipotizziamo di voler definire una proprietà per memorizzare la lista di notizie da mostrare nella view. In altre situazioni l’avremmo semplicemente dichiarata nel seguente modo:
public ObservableCollection<FeedItem> FeedItems { get; set; }
Per poter supportare pienamente il binding, invece, dobbiamo dichiararla nel seguente modo:
private ObservableCollection<FeedItem> _feedItems;
public ObservableCollection<FeedItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems != value)
{
_feedItems = value;
RaisePropertyChanged(() => FeedItems);
}
}
}
Il risultato sarà esattamente lo stesso: la differenza è che, ogni qualvolta il valore della proprietà FeedItems cambia, lo notifichiamo a tutti controlli in binding con la stessa scatenando l’evento RaisePropertyChanged. Quello che ci resta da fare in questo semplice view model è, in fase di inizializzazione, collegarci al servizio realizzato in precedenza per ottenere le notizie pubblicate nel feed RSS. Ecco come appare, perciò, il view model nella sua interezza:
public class MainViewModel: ViewModelBase
{
private ObservableCollection<FeedItem> _feedItems;
public ObservableCollection<FeedItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems != value)
{
_feedItems = value;
RaisePropertyChanged(() => FeedItems);
}
}
}
public MainViewModel()
{
FeedService feedService = new FeedService();
feedService.GetNewsCompleted += (obj, args) =>
{
FeedItems = args.FeedItems.ToObservableCollection();
};
feedService.GetNews();
}
}
Nel costruttore non facciamo altro che inizializzare il nostro servizio e chiamare il metodo GetNews: nell’event handler che gestisce l’evento GetNewsCompleted andiamo a convertire il risultato del servizio (una collezione di tipo IEnumerable<FeedItem>) in una ObservableCollection<FeedItem>, che è un tipo di collezione molto più adatta nello scenario tipico di un view model in quanto i controlli in binding con tale collezione saranno aggiornati in automatico ad ogni cambiamento (aggiunta o rimozione di un elemento, modifica dell’ordine, ecc.). E’ importante sottolineare che ToObservableCollection() è un extension method scritto ad hoc, non fa parte del framework .NET: potrete trovarlo il codice nel progetto di esempio che pubblicherò alla fine di questi post dedicati a MVVM.
Nella prossima puntata
In questo post abbiamo realizzato i primi due “pezzi” della nostra applicazione. Al momento, però, non siamo entrati particolarmente nel vivo di MVVM: ci siamo limitati a realizzare due classi con pochi punti in comune e non abbiamo ancora avuto modo di vedere i frutti del nostro lavoro grazie ad un’interfaccia grafica ad hoc. Nel prossimo post vedremo invece come collegare la view al view model e come far si che la UI sia in grado di mostare le proprietà che abbiano definito nel view model.

Recent Comments