“IMonitor” guarda giù tu che io sono stufo di guardare su :-)
Cosa è? la prima parta direi che non necessità di spiegazioni, se usate Wpf e avete mai sentito parlare di M-V-VM siete sicuramente incappati in una della migliaia di implementazioni del concetto di DelegateCommand / RelayCommand / OgnunoLoChiamaComeVuoleMaSempreQuelloè… cioè un meccanismo per costruire un ICommand, componente vitale e fantastico nel mondo Wpf, utilizzando i delegati.var command = DelegateCommand.Create() .OnCanExecute( o => true ) .OnExecute( o => { } ) .TriggerUsing( PropertyChangedObserver .Monitor( this ) .HandleChangesOf( vm => vm.IsValid ) );
La necessità però in M-V-VM è spesso quella di poter rivalutare lo stato di CanExecute() di un command a “richiesta” e spesso si arriva a soluzioni di questo genere, perlomeno io ho sempre fatto così:
che sinceramente sono di una bruttura mai vista :-) oltre al fatto che se volete triggherare per motivi diversi dal “Property Changed” dovete spargere altre brutture.protected override void OnPropertyChanged( PropertyChangedEventArgs e ) { base.OnPropertyChanged( e ); switch( e.PropertyName ) { case "IsValid": this.ReevaluateCommands(); break; } }
Guarda giù tu…
Questo è il classico esempio in cui un piccolo refactoring introducendo un piccolo componente fa la differenza:
Definiamo che cosa ci serve e come lo vogliamo usare:public interface IMonitor { event EventHandler Changed; void StopMonitoring(); } public interface IMonitor: IMonitor { T Source { get; } }
Ho rimosso di tutto e di più, come la RAI :-), e a questo punto proviamo a realizzarne uno di Observer/Monitor.public class DelegateCommand : ICommand {
… omissis …
private EventHandler triggerHanlder = null; public DelegateCommand TriggerUsing( IMonitor source ) { source.Changed += this.triggerHanlder; return this; } public DelegateCommand RemoveTrigger( IMonitor source ) { source.Changed -= this.triggerHanlder; return this; } }
PropertyChangedObserver
Che altro non fa che tenere sotto controllo una istanza di INotifyPropertyChanged e osservare se viene scatenato l’evento PropertyChanged per una delle proprietà che ci interessano.public static class PropertyChangedObserver { public static PropertyChangedMonitorMonitor ( T source ) where T : class, INotifyPropertyChanged { return new PropertyChangedMonitor ( source ); } } public class PropertyChangedMonitor : INotifyPropertyChanged, IMonitor where T : class, INotifyPropertyChanged { public event EventHandler Changed; public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( PropertyChangedEventArgs args ) { if( this.PropertyChanged != null ) { this.PropertyChanged( this, args ); } if( this.Changed != null ) { this.Changed( this, EventArgs.Empty ); } } IDictionary<String, Action String>> propertiesToWatch = new Dictionary<String, Action String>>(); PropertyChangedEventHandler handler = null; public PropertyChangedMonitor( T source ) { Ensure.That( source ).Named( "source" ).IsNotNull(); handler = ( s, e ) => { Action String> callback; if( propertiesToWatch.TryGetValue( e.PropertyName, out callback ) ) { if( callback != null ) { callback( source, e.PropertyName ); } } if( propertiesToWatch.ContainsKey( e.PropertyName ) ) { this.OnPropertyChanged( e ); } }; this.Source = source; this.Source.PropertyChanged += handler; } public T Source { get; private set; } public PropertyChangedMonitor HandleChangesOf ( Expression<Func > property ) { Ensure.That( property ).Named( "property" ).IsNotNull(); this.HandleChangesOf( property, null ); return this; } public PropertyChangedMonitor HandleChangesOf ( Expression<Func > property, Action String> callback ) { Ensure.That( property ).Named( "property" ).IsNotNull(); var propertyName = property.GetMemberName(); if( propertiesToWatch.ContainsKey( propertyName ) ) { propertiesToWatch[ propertyName ] = callback; } else { propertiesToWatch.Add( propertyName, callback ); } return this; } public PropertyChangedMonitor DismissHandlingOf ( Expression<Func > property ) { var propertyName = property.GetMemberName(); if( propertiesToWatch.ContainsKey( propertyName ) ) { propertiesToWatch.Remove( propertyName ); } return this; } public void StopMonitoring() { this.Source.PropertyChanged -= handler; this.Source = null; } }
Con poco codice e decisamente pochi minuti (direi non più di mezz’ora tra pensare la cosa, realizzarla e affinarla) abbiamo incapsulato della logica in un componente riutilizzabile ma soprattutto abbiamo delegato al “coso” giusto il suo compito.
Naturalmente i “trigger” sono impilabili e quindi questo funziona come ci si aspetta:
Dove il MementoObserver è un IMonitor per un “memento service” che è un’altra storia, stay tuned :-)var command = DelegateCommand.Create() .OnCanExecute( o => true ) .OnExecute( o => { } ) .TriggerUsing( PropertyChangedObserver .Monitor( this ) .HandleChangesOf( vm => vm.IsValid ) .HandleChangesOf( vm => vm.DataSource ) ) .TriggerUsing( MementoObserver.Monitor( this.Memento ) );
Cosa manca?
probabilmente la gestione degli eventi tramite “weak reference” ma è un’ottimizazzione prematura…
.m