Eccoci di nuovo a parlare di Model View ViewModel, finalmente Smile. Per ora ci siamo limitati ad una serie di post introduttivi all’argomento:
Vi consiglio anche di dare un’occhiata a tutti i post dell’amico Alessandro che sta facendo un ottimo lavoro in versione VB.Net.
Con questo post direi che possiamo cominciare ad introdurre una sorta di “Adavnced-M-V-VM”, quello che vorrei fare è, sempre sfruttando l’applicazione di esempio che abbiamo portato avanti sino ad ora, introdurre tutti quei concetti che sono fondamentali in un’applicazione degna del suo nome, concetti che se calati in un mondo basato su M-V-VM tendono a risultare più ostici del dovuto.
Navigazione
Uno degli argomenti forse più ostici è come gestire la navigazione con M-V-VM, la domanda tipica è:
Come faccio dal ViewModel ad aprire una nuova finestra?
La risposta è complessa e articolata e come al solito non c’è una verità assoluta, quindi quello che vediamo in questo post è una possibile soluzione al problema.
Prima di affrontare però il problema della navigazione dobbiamo affrontarne uno più a “basso livello”.
Comunicazione
Diciamo che abbiamo due ViewModel ospitati in due Window (aka View) diverse, ViewModel che non si conoscono ma che hanno bisogno di scambiare informazioni tra loro. Non conoscendosi non possiamo applicare il classico pattern basato su eventi ma dobbiamo trovare un’alternativa.
image
quello che vogliamo ottenere è che le due finestre, dell’esempio qui sopra, possano comunicare: è una banale “chat” in-process, totalmente inutile se non al fine di spiegare il concetto di brokering Smile:
image
gli attori fondamentali in questo scambio di messaggi sono:
  • i messaggi, ovviamente Smile: un messaggio non è altro che una semplice classe il cui scopo è trasportare informazioni, nel nostro esempio il “messaggio di testo”;
  • il message broker: il postino, il cui scopo è quello di trasportare e consegnare il messaggio;
in questo gioco le due View/ViewModel non si conoscono tra loro ma conoscono il messaggio (la lingua con cui parlare tra loro) e il postino. Ovviamente il postino dovrà essere uno ed uno solo Smile, un Singleton.
public partial class App : Application
{
 protected override void OnStartup( StartupEventArgs e )
 {
  base.OnStartup( e );
 
  this.ShutdownMode = System.Windows.ShutdownMode.OnLastWindowClose;
 
  var broker = new MessageBroker( new WpfDispatcher( this.Dispatcher ) );
 
  var w1 = new ChatWindow()
  {
   DataContext = new ChatViewModel( broker )
  };
 
  var w2 = new ChatWindow()
  {
   DataContext = new ChatViewModel( broker )
  };
 
  w1.Show();
  w2.Show();
 }
}
Nell’esempio, che potete scaricare, altro non facciamo che, all’avvio dell’applicazione:
  1. creare un nuovo postino, usando l’implementazione presente in Radical;
  2. creare due istanze delle View, con i relativi ViewModel, che simulano la nostra “local chat”;
In un’implementazione reale il tutto sarebbe delegato ad un motore di Invertion of Control ma non è oggetto di questo post quindi niente “castelli” inutili.
Come si può notare i ViewModel dipendono dal MessageBroker, il postino, e lo sfruttano per la comunicazione:
readonly IMessageBroker broker;
 
public ChatViewModel( IMessageBroker broker )
{
 this.broker = broker;
 this.Messages = new ObservableCollection<String>();
 
 this.Speak = DelegateCommand.Create()
  .OnCanExecute( o => ... )
  .AddMonitor( ... )
  .OnExecute( o => ... );
 
 this.broker.Subscribe<TextMessage>( this, msg =>
 {
  if( msg.Sender != this )
  {
   this.Messages.Insert( 0, msg.Text );
  }
 } );
}
Ogni ViewModel, in questo caso nel costruttore ma poco importa dove, chiede al postino (Subscribe) di essere avvisato se qualcuno spedisce (Dispatch) un determinato messaggio e aggancia, via lambda, un handler per il messaggio, con l’accortezza di evitare di reagire ai messaggi mandati eventualmente da se stesso;
Inviare un messaggio è altrettanto banale:
this.Speak = DelegateCommand.Create()
 .OnCanExecute( o => ... )
 .AddMonitor( ... )
 .OnExecute( o =>
 {
  var msg = new TextMessage( this ) { Text = this.Text };
  this.broker.Dispatch( msg );
 } );
ci limitaimo a creare un’ìstanza del messaggio che vogliamo spedire e la passiamo al broker, sarà onere del broker sapere se ci sono subscriber per quello specifico messaggio e nel caso notificarli.
Navigazione, reload.
Torniamo al nostro problema iniziale, vista l’implementazione del sistema di comunicazione cosa ci vieta di realizzare questo set di messaggi?:
  • DiplayXxxxViewModel: ad esempio “DisplayChatSettings” delgando all’handler, che nel nostro esempio triviale potrebbe essere nel file App.cs, l’onere di risolvere il problema;
  • ViewModelLoading, ViewModelLoaded, ViewModelUnloaded: “spediti” da ogni ViewModel al fine di notificare al mondo il loro ciclo di vita e permettere ad altri viewmodel di reagire ove necessario: nel nostro esempio all’avvio dei “ChatSettings” potremmo desiderare che le View della chat si disabilitino Winking smile
.m