Architettura Model-View-ViewModel in un'applicazione WPF
di Cristian Civera, in Windows Presentation Foundatio, 24 febbraio 2009
Weak event pattern
Nel diagramma iniziale si può vedere che la classe MainWindowViewModel dispone di una proprietà TotalReadThreads per restituire quanti thread sono già stati letti. Per saperlo occorre contare quanti thread hanno la proprietà IsRead impostata true e occorre inoltre che TotalReadThreads venga ricalcolata ogni qual volta che cambia IsRead di ogni thread.
E' necessario intercettare l'evento PropertyChanged del ThreadPostViewModel ed inoltre intercettare l'evento CollectionChanged della proprietà MainWindowViewModel.Threads per togliere e aggiungere l'handler su PropertyChanged per tutti i thread che vengo aggiunti o tolti. Questo di fatto non è un problema, poiché gli eventi di .NET esistono dalla versione 1.0 e sono di facile utilizzo. Purtroppo però sono fonte di possibili memory leak perché gli eventi si basano sui delegate, cioè puntatori a funzione che stanno su un oggetto. Quando si intercetta un evento non si fa altro che dare il delegate all'oggetto detentore dell'evento che lo mantiene fino a quando non si smette di intercettare l'evento. Se questa operazione non viene fatta l'oggetto intercettato non può mai essere distrutto dal garbage collector, rimanendo sempre in vita fino a quando anche l'oggetto che lo intercetta non muore.
Sebbene in alcuni casi possa bastare scrivere il codice in modo corretto, mettendo e togliendo l'hander ad un evento, non è sempre possibile determinare quando togliere l'handler. Con WPF è presente perciò la classe WeakEventManager e relative specializzazioni che permettono di mettersi in ascolto su un evento di un'istanza di una classe usando una WeakReference, cioè con un riferimento debole all'oggetto intercettato così da permette al garbage collector di eliminare l'oggetto intercettato se, escluse le WeakReference, non ha più riferimenti in altre classi.
Vi sono varie implementazioni, specifiche per una tipologia di oggetti o interfacce aventi certi eventi, che presentano solitamente due metodi statici: AddListener e RemoveListener. Ecco le più principali:
- CollectionChangedEventManager: permette di intercettare l'evento di INotifyCollectionChanged;
- CurrentChangedEventManager: permette di intercettare il cambio di selezione tramite l'interfaccia ICollectionView;
- CurrentChangingEventManager: permette di intercettare prima che avvenga il cambio di selezione dell'interfaccia ICollectionView;
- PropertyChangedEventManager: permette di intercettare l'evento di IPropertyChanged;
Il metodo AddListener, come previsto dal pattern, solitamente accetta l'oggetto da intercettare e l'istanza dell'oggetto che implementa IWeakEventListener per ricevere le notifiche.
Ecco quindi come la proprietà Threads viene inizializzata per conoscere quando un elemento viene aggiunto o tolto alla lista mediante l'evento CollectionChanged dell'interfaccia INotifyCollectionChanged implementata da ObservableCollection.
public ThreadPostViewModelCollection Threads { get { if (_threads == null) { _threads = new ObservableCollection<ThreadPostViewModel>(); CollectionChangedEventManager.AddListener(_threads, this); } return _threads; } }
L'interfaccia IWeakEventListener che il MainWindowViewModel implementa, prevede una sola funzione dove viene passato il tipo di gestore degli eventi, il sender dell'evento e l'argomento dell'evento. Al fine di conoscere quando ricontare il numero di thread letti occorre perciò che a sua volta la funzione intercetti l'evento PropertyChanged, eventualmente usando sempre il weak event pattern. Nell'esempio seguente viene usato sempre lo stesso listener per l'evento, perciò viene fatta discriminanza con l'ausilio del parametro sender.
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { // Evento scatenato da INotifyCollectionChanged if (sender == this.Threads) { NotifyCollectionChangedEventArgs ce = (NotifyCollectionChangedEventArgs)e; switch (ce.Action) { // Mi metto in ascolto sul cambio di proprietà IsRead case NotifyCollectionChangedAction.Add: foreach (object item in ce.NewItems) PropertyChangedEventManager.AddListener((INotifyPropertyChanged)item, this, "IsRead"); break; // Non occorre più che mi metta in ascolto sul cambio di proprietà IsRead case NotifyCollectionChangedAction.Remove: foreach (object item in ce.OldItems) PropertyChangedEventManager.RemoveListener((INotifyPropertyChanged)item, this, "IsRead"); break; } // L'evento è stato gestito return true; } // Evento PropertyChanged invocato sul Thread else if (sender is ThreadPostViewModel) { // Notifico che la proprietà è cambiata // così verrà re interrogata e calcolata OnPropertyChanged("TotalReadThreads"); return true; } return false; }
Si noti che la funzione deve ritornare true o false per indicare se l'evento è di sua competenza ed è stato processato. Se tutti i listener restituiscono false viene sollevata un eccezione perché viene considerata un'anomalia il fatto che l'evento non è stato processato da nessuno.
La proprietà TotalReadThreads è piuttosto semplice:
public int TotalReadThreads { get { return this.Threads.Count(t => t.IsRead); } }
Attenzione: Questo articolo contiene un allegato
Contenuti dell'articolo
- Pagina 1
- Pagina 2
- Pagina 3
- Pagina 4
- Pagina 5
- Pagina 6
- Pagina 7
Sullo stesso argomento
-
.NET Framework 4.0 Beta 1: Entity Framework
-
Real Code Day 4.0: Agenda (quasi) completata!
-
#143 - Forzare la modalità di rendering software nelle applicazioni WPF
-
#149 - Personalizzare le istanze di un servizio WCF
-
.NET Framework 4.0 beta 1: Windows Communication Foundation
-
Disponibile il download di Silverlight 3.0 beta1!
-
Visual Studio 2010 e .NET Framework 4.0: beta 2 e data di release RTM
-
Scopri i nostri nuovi libri su ASP.NET 4.0, C# 4 e Visual Basic 2010: in offerta lancio al 20% di sconto!
-
#115 - Creare file ZIP con System.IO.Packaging
-
Disponibile la beta 1 del .NET Framework 4.0 e di Visual Studio 2010
-
Finalmente disponibile ASP.NET MVC 2 in RTM
-
#169 - Sfruttare msbuild per differenziare il config

















Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.