CommandBinding: MarkupExtension
Possiamo però partire da questo post, come suggerito da Giuseppe, e wrappare la classe Binding in una nostra classe Binding esponendo le stesse identitche proprietà che saranno dei meri proxy verso la classe Binding reale; facendo infine semplicemente questo:
Nel momento in cui la nostra markup extension viene effettivamente invocata l’unica cosa che possiamo fare è passare la palla alla vera markup extension con cui Wpf è in grado di interagire.public override object ProvideValue( IServiceProvider provider ) { return binding.ProvideValue( provider ); }
Ma non è ancora tutto perso… se usiamo M-V-VM siamo probabilmente molto avvezzi al concetto di DelegateCommand/RelayCommand e alla possibilità di scrivere una cosa di questo genere:
con lo scopo di creare un ICommand a cui sia implicitamente associata la combinazione Ctrl+S peccato che quella cosa non funzioni perchè nessuno (aka la classe Binding) si preoccupa di eseguire la registrazione degli Input Bindings…var command = DelegateCommand.Create() .AddKeyGesture( System.Windows.Input.Key.S, System.Windows.Input.ModifierKeys.Control ) .OnExecute( o => { /* execution logic */ } );
Sfruttando quindi la nostra bella markup extension perchè non realizzare qualcosa del genere:
Con TryGetTargetItems definita così:public class CommandBinding : BindingDecoratorBase { public override object ProvideValue( IServiceProvider provider ) { var b = base.ProvideValue( provider ); this.OnProvideValue( provider, b ); return b; } protected virtual void OnProvideValue( IServiceProvider provider, Object value ) { FrameworkElement fe; DependencyProperty dp; if( this.TryGetTargetItems<FrameworkElement>( provider, out fe, out dp ) ) { RoutedEventHandler reh = null; reh = ( s, e ) => { fe.Loaded -= reh; this.OnTargetLoaded( fe, dp ); }; fe.Loaded += reh; } else { FrameworkContentElement fce; if( this.TryGetTargetItems<FrameworkContentElement>( provider, out fce, out dp ) ) { RoutedEventHandler reh = null; reh = ( s, e ) => { fce.Loaded -= reh; this.OnTargetLoaded( fce, dp ); }; fce.Loaded += reh; } } }
Abbiamo la necessità di sapere quando il target del Binding è “Loaded”, tracciamo sia FrameworkElement (eg Button) che FrameworkContentElement (eg Hyperlink) perchè purtroppo (protected virtual bool TryGetTargetItems( IServiceProvider provider, out T target, out DependencyProperty dp ) where T : DependencyObject { target = null; dp = null; if( provider == null ) return false; var service = provider.GetService( typeof( IProvideValueTarget ) ) as IProvideValueTarget; if( service == null ) return false; target = service.TargetObject as T; dp = service.TargetProperty as DependencyProperty; return target != null && dp != null; }
protected virtual void OnTargetLoaded( DependencyObject target, DependencyProperty targetProperty ) { var source = target as ICommandSource; var command = this.GetCommand( target, targetProperty ); this.SetInputBindings( target, source, command ); }A questo punto quando il target è Loaded ci limitiamo ad estrarre dalla DepenedencyProperty (omesso per brevità) il valore dell’ICommand e non facciamo altro che impostare gli imput bindings sul root element, anche qui la funzione ricorsiva che va alla ricerca del root element è omessa:
Togo, adesso lo possiamo usare così:protected virtual void SetInputBindings( DependencyObject target, ICommandSource source, IBindableCommand command) { if( source != null && command != null && command.InputBindings != null ) { var rootElement = this.GetRootElement( target as FrameworkElement ); foreach( InputBinding ib in command.InputBindings ) { if( ib.CommandParameter != source.CommandParameter ) { ib.CommandParameter = source.CommandParameter; } rootElement.InputBindings.Add( ib ); } } }
Ottenendo gratis la registrazione degli input bindings.<Button Height="23" Command="{cmd:CommandBinding Path=ExecuteSampleCommand}" />
Ma non è finita… Stay tuned!
.m