M-V-VM: comunicazione e navigazione
-
M-V-VM: The beginning
-
M-V-VM: master-details
-
ViewModel != DTO
-
M-V-VM: master-details (2): let’s move on…
-
M-V-VM: Commanding
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.
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 :
gli attori fondamentali in questo scambio di messaggi sono:
- i messaggi, ovviamente : 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;
Nell’esempio, che potete scaricare, altro non facciamo che, all’avvio dell’applicazione: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(); } }
- creare un nuovo postino, usando l’implementazione presente in Radical;
- creare due istanze delle View, con i relativi ViewModel, che simulano la nostra “local chat”;
Come si può notare i ViewModel dipendono dal MessageBroker, il postino, e lo sfruttano per la comunicazione:
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;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 ); } } ); }
Inviare un messaggio è altrettanto banale:
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.this.Speak = DelegateCommand.Create() .OnCanExecute( o => ... ) .AddMonitor( ... ) .OnExecute( o => { var msg = new TextMessage( this ) { Text = this.Text }; this.broker.Dispatch( msg ); } );
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