example commandmanager wpf icommand

wpf - commandmanager - icommand mvvm



CommandManager.InvalidateRequerySuggested() no es lo suficientemente rápido. ¿Que puedo hacer? (7)

Version corta

Las llamadas a CommandManager.InvalidateRequerySuggested() tardan más en surtir efecto de lo que me gustaría (1-2 segundos de retraso antes de que los controles de UI se deshabiliten).

Versión larga

Tengo un sistema en el que envío tareas a un procesador de tareas basado en el hilo de fondo. Este envío ocurre en el subproceso UI de WPF.

Cuando ocurre este envío, el objeto que gestiona mi hilo de fondo hace dos cosas:

  1. Se plantea un evento "ocupado" (todavía en el hilo de la interfaz de usuario) al que responden varios modelos de vista; cuando reciben este evento, establecen una bandera IsEnabled en sí mismos en false . Los controles en mis vistas, que están enlazados a esta propiedad, están inmediatamente atenuados, que es lo que yo esperaría.

  2. Informa a mis objetos WPF ICommand que no se les debe permitir ejecutar (otra vez, aún en el hilo de UI). Como no hay nada como INotifyPropertyChanged para objetos ICommand , me veo forzado a llamar a CommandManager.InvalidateRequerySuggested() para obligar a WPF a reconsiderar todos mis objetos de comando '' CanExecute states (sí, de hecho, necesito hacer esto: de lo contrario, ninguno de estos controles quedar deshabilitado) . Sin embargo, a diferencia del ítem 1, mis botones / elementos de menú / etc tardan mucho más tiempo en usar objetos de ICommand para cambiar visualmente a un estado deshabilitado que en los controles de UI que tienen su propiedad IsEnabled configurada manualmente.

El problema es que, desde el punto de vista de UX, esto se ve horrible ; la mitad de mis controles están atenuados de inmediato (porque su propiedad IsEnabled está configurada en falso), y luego 1-2 segundos después, la otra mitad de mis controles lo sigue (porque sus métodos CanExecute finalmente se vuelven a evaluar).

Entonces, parte 1 de mi pregunta:
Por más tonto que CommandManager.InvalidateRequerySuggested() preguntar, ¿hay alguna manera de hacer que CommandManager.InvalidateRequerySuggested() haga su trabajo más rápido? Sospecho que no hay.

Bastante, parte 2 de mi pregunta:
¿Cómo puedo solucionar esto? Prefiero que todos mis controles se deshabiliten al mismo tiempo. Simplemente se ve poco profesional y extraño de lo contrario. ¿Algunas ideas? :-)


¿Hay alguna manera de hacer que CommandManager.InvalidateRequerySuggested () haga su trabajo más rápido?

Sí, hay una manera de hacerlo funcionar más rápido!

  1. Implemente el Command para mantener / almacenar en caché CanExecuteState en una variable booleana.
  2. Implemente el método RaiseCanExecuteChanged para recalcular CanExecuteState y si realmente cambió para generar el evento CanExecuteChanged .
  3. Implemente el método CanExecute para simplemente devolver CanExecuteState .
  4. Cuando se invoca el método InvalidateRequerySuggested , los suscriptores del Command solo leerán la variable CanExecute invocando el método CanExecute y verificando si cambió o no. Eso es casi cero por encima. Todos los Commands se desactivarán / habilitarán casi al mismo tiempo.
  5. Todo el trabajo se realizará en el método RaiseCanExecuteChanged que se llamará solo una vez para un Command y solo para un conjunto limitado de Commands .

¿Intenta escribir su propio enlace que llame a su RaiseCanExecuteChanged () dentro de los conversos? es más fácil


Solo para aclarar:

  1. Desea CanExecute una actualización de CanExecute cuando la Command property changed
  2. Cree su propia clase de enlace que detecte cambios en la Command property y luego llame a RaiseCanExecuteChanged()
  3. Use este enlace en CommandParameter

Trabajó para mi.


Sugiero que investigue ReactiveUI y específicamente en la implementación de ICommand que proporciona, ReactiveCommand . Utiliza un enfoque diferente de DelegateCommand / RelayCommand que se implementan con delegados para CanExecute que deben evaluarse activamente. El valor de ReactiveCommand para CanExecute se empuja usando IObservables.


Tomas tiene una buena solución, pero por favor tenga en cuenta que hay un error grave en el sentido de que CanExecute no siempre se activará cuando esté vinculado a un Botón debido a esto:

// Call the handlers that we snapshotted for (int i = 0; i < count; i++) { EventHandler handler = callees[i]; handler(null, EventArgs.Empty); }

El parámetro ''nulo'' pasado en causa problemas con el CanExecuteChangedEventManager (utilizado por la clase de botón de WPF para escuchar los cambios en cualquier comando vinculado a él). Específicamente, CanExecuteChangedEventManager mantiene una colección de eventos débiles que deben invocarse para determinar si el comando Can-Execute () pero esta colección está codificada por el ''emisor''.

La solución es simple y funciona para mí: cambie la firma a

internal static void CallWeakReferenceHandlers(ICommand sender, List<WeakReference> handlers) { .... handler(sender, EventArgs.Empty); }

Perdón por no haberlo descrito demasiado bien: ¡con un poco de prisa por alcanzar a mi desarrollador ahora después de tomarme unas horas para resolverlo!


CommandManager.InvalidateRequerySuggested() intenta validar todos los comandos, lo cual es totalmente ineficaz (y en su caso, lento) - en cada cambio, usted está pidiendo a cada comando que vuelva a CanExecute() su CanExecute() !

Necesitará el comando para saber de qué objetos y propiedades depende CanExecute , y sugerir solo cuando cambien. De esta forma, si cambia una propiedad de un objeto, solo los comandos que dependen de él cambiarán su estado.

Así es como resolví el problema, pero al principio, un adelanto:

// in ViewModel''s constructor - add a code to public ICommand: this.DoStuffWithParameterCommand = new DelegateCommand<object>( parameter => { //do work with parameter (remember to check against null) }, parameter => { //can this command execute? return true or false } ) .ListenOn(whichObject, n => n.ObjectProperty /*type safe!*/, this.Dispatcher /*we need to pass UI dispatcher here*/) .ListenOn(anotherObject, n => n.AnotherObjectProperty, this.Dispatcher); // chain calling!

El comando está escuchando en los eventos NotifyPropertyChanged del objeto que afecta si se puede ejecutar e invoca el control solo cuando se necesita una nueva consulta.

Ahora, un montón de código (parte de nuestro marco interno) para hacer esto:

Yo uso DelegateCommand de Prism, que se ve así:

/// <summary> /// This class allows delegating the commanding logic to methods passed as parameters, /// and enables a View to bind commands to objects that are not part of the element tree. /// </summary> public class DelegateCommand : ICommand { #region Constructors /// <summary> /// Constructor /// </summary> public DelegateCommand(Action executeMethod) : this(executeMethod, null, false) { } /// <summary> /// Constructor /// </summary> public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod) : this(executeMethod, canExecuteMethod, false) { } /// <summary> /// Constructor /// </summary> public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled) { if (executeMethod == null) { throw new ArgumentNullException("executeMethod"); } _executeMethod = executeMethod; _canExecuteMethod = canExecuteMethod; _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; this.RaiseCanExecuteChanged(); } #endregion #region Public Methods /// <summary> /// Method to determine if the command can be executed /// </summary> public bool CanExecute() { if (_canExecuteMethod != null) { return _canExecuteMethod(); } return true; } /// <summary> /// Execution of the command /// </summary> public void Execute() { if (_executeMethod != null) { _executeMethod(); } } /// <summary> /// Property to enable or disable CommandManager''s automatic requery on this command /// </summary> public bool IsAutomaticRequeryDisabled { get { return _isAutomaticRequeryDisabled; } set { if (_isAutomaticRequeryDisabled != value) { if (value) { CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); } else { CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); } _isAutomaticRequeryDisabled = value; } } } /// <summary> /// Raises the CanExecuteChaged event /// </summary> public void RaiseCanExecuteChanged() { OnCanExecuteChanged(); } /// <summary> /// Protected virtual method to raise CanExecuteChanged event /// </summary> protected virtual void OnCanExecuteChanged() { CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); } #endregion #region ICommand Members /// <summary> /// ICommand.CanExecuteChanged implementation /// </summary> public event EventHandler CanExecuteChanged { add { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested += value; } CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); } remove { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested -= value; } CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); } } bool ICommand.CanExecute(object parameter) { return CanExecute(); } void ICommand.Execute(object parameter) { Execute(); } #endregion #region Data private readonly Action _executeMethod = null; private readonly Func<bool> _canExecuteMethod = null; private bool _isAutomaticRequeryDisabled = false; private List<WeakReference> _canExecuteChangedHandlers; #endregion } /// <summary> /// This class allows delegating the commanding logic to methods passed as parameters, /// and enables a View to bind commands to objects that are not part of the element tree. /// </summary> /// <typeparam name="T">Type of the parameter passed to the delegates</typeparam> public class DelegateCommand<T> : ICommand { #region Constructors /// <summary> /// Constructor /// </summary> public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null, false) { } /// <summary> /// Constructor /// </summary> public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) : this(executeMethod, canExecuteMethod, false) { } /// <summary> /// Constructor /// </summary> public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled) { if (executeMethod == null) { throw new ArgumentNullException("executeMethod"); } _executeMethod = executeMethod; _canExecuteMethod = canExecuteMethod; _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; } #endregion #region Public Methods /// <summary> /// Method to determine if the command can be executed /// </summary> public bool CanExecute(T parameter) { if (_canExecuteMethod != null) { return _canExecuteMethod(parameter); } return true; } /// <summary> /// Execution of the command /// </summary> public void Execute(T parameter) { if (_executeMethod != null) { _executeMethod(parameter); } } /// <summary> /// Raises the CanExecuteChaged event /// </summary> public void RaiseCanExecuteChanged() { OnCanExecuteChanged(); } /// <summary> /// Protected virtual method to raise CanExecuteChanged event /// </summary> protected virtual void OnCanExecuteChanged() { CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); } /// <summary> /// Property to enable or disable CommandManager''s automatic requery on this command /// </summary> public bool IsAutomaticRequeryDisabled { get { return _isAutomaticRequeryDisabled; } set { if (_isAutomaticRequeryDisabled != value) { if (value) { CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); } else { CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); } _isAutomaticRequeryDisabled = value; } } } #endregion #region ICommand Members /// <summary> /// ICommand.CanExecuteChanged implementation /// </summary> public event EventHandler CanExecuteChanged { add { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested += value; } CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); } remove { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested -= value; } CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); } } bool ICommand.CanExecute(object parameter) { // if T is of value type and the parameter is not // set yet, then return false if CanExecute delegate // exists, else return true if (parameter == null && typeof(T).IsValueType) { return (_canExecuteMethod == null); } return CanExecute((T)parameter); } void ICommand.Execute(object parameter) { Execute((T)parameter); } #endregion #region Data private readonly Action<T> _executeMethod = null; private readonly Func<T, bool> _canExecuteMethod = null; private bool _isAutomaticRequeryDisabled = false; private List<WeakReference> _canExecuteChangedHandlers; #endregion } /// <summary> /// This class contains methods for the CommandManager that help avoid memory leaks by /// using weak references. /// </summary> internal class CommandManagerHelper { internal static void CallWeakReferenceHandlers(List<WeakReference> handlers) { if (handlers != null) { // Take a snapshot of the handlers before we call out to them since the handlers // could cause the array to me modified while we are reading it. EventHandler[] callees = new EventHandler[handlers.Count]; int count = 0; for (int i = handlers.Count - 1; i >= 0; i--) { WeakReference reference = handlers[i]; EventHandler handler = reference.Target as EventHandler; if (handler == null) { // Clean up old handlers that have been collected handlers.RemoveAt(i); } else { callees[count] = handler; count++; } } // Call the handlers that we snapshotted for (int i = 0; i < count; i++) { EventHandler handler = callees[i]; handler(null, EventArgs.Empty); } } } internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers) { if (handlers != null) { foreach (WeakReference handlerRef in handlers) { EventHandler handler = handlerRef.Target as EventHandler; if (handler != null) { CommandManager.RequerySuggested += handler; } } } } internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers) { if (handlers != null) { foreach (WeakReference handlerRef in handlers) { EventHandler handler = handlerRef.Target as EventHandler; if (handler != null) { CommandManager.RequerySuggested -= handler; } } } } internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler) { AddWeakReferenceHandler(ref handlers, handler, -1); } internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize) { if (handlers == null) { handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>()); } handlers.Add(new WeakReference(handler)); } internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler) { if (handlers != null) { for (int i = handlers.Count - 1; i >= 0; i--) { WeakReference reference = handlers[i]; EventHandler existingHandler = reference.Target as EventHandler; if ((existingHandler == null) || (existingHandler == handler)) { // Clean up old handlers that have been collected // in addition to the handler that is to be removed. handlers.RemoveAt(i); } } } } }

Luego escribí un método de extensión ListenOn , que ''vincula'' el comando a una propiedad e invoca su RaiseCanExecuteChanged :

public static class DelegateCommandExtensions { /// <summary> /// Makes DelegateCommnand listen on PropertyChanged events of some object, /// so that DelegateCommnand can update its IsEnabled property. /// </summary> public static DelegateCommand ListenOn<ObservedType, PropertyType> (this DelegateCommand delegateCommand, ObservedType observedObject, Expression<Func<ObservedType, PropertyType>> propertyExpression, Dispatcher dispatcher) where ObservedType : INotifyPropertyChanged { //string propertyName = observedObject.GetPropertyName(propertyExpression); string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression); observedObject.PropertyChanged += (sender, e) => { if (e.PropertyName == propertyName) { if (dispatcher != null) { ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged); } else { delegateCommand.RaiseCanExecuteChanged(); } } }; return delegateCommand; //chain calling } /// <summary> /// Makes DelegateCommnand listen on PropertyChanged events of some object, /// so that DelegateCommnand can update its IsEnabled property. /// </summary> public static DelegateCommand<T> ListenOn<T, ObservedType, PropertyType> (this DelegateCommand<T> delegateCommand, ObservedType observedObject, Expression<Func<ObservedType, PropertyType>> propertyExpression, Dispatcher dispatcher) where ObservedType : INotifyPropertyChanged { //string propertyName = observedObject.GetPropertyName(propertyExpression); string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression); observedObject.PropertyChanged += (object sender, PropertyChangedEventArgs e) => { if (e.PropertyName == propertyName) { if (dispatcher != null) { ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged); } else { delegateCommand.RaiseCanExecuteChanged(); } } }; return delegateCommand; //chain calling } }

Luego necesita la siguiente extensión para NotifyPropertyChanged

/// <summary> /// <see cref="http://dotnet.dzone.com/news/silverlightwpf-implementing"/> /// </summary> public static class NotifyPropertyChangedBaseExtensions { /// <summary> /// Raises PropertyChanged event. /// To use: call the extension method with this: this.OnPropertyChanged(n => n.Title); /// </summary> /// <typeparam name="T">Property owner</typeparam> /// <typeparam name="TProperty">Type of property</typeparam> /// <param name="observableBase"></param> /// <param name="expression">Property expression like ''n => n.Property''</param> public static void OnPropertyChanged<T, TProperty>(this T observableBase, Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChangedWithRaise { observableBase.OnPropertyChanged(GetPropertyName<T, TProperty>(expression)); } public static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChanged { if (expression == null) throw new ArgumentNullException("expression"); var lambda = expression as LambdaExpression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else { memberExpression = lambda.Body as MemberExpression; } if (memberExpression == null) throw new ArgumentException("Please provide a lambda expression like ''n => n.PropertyName''"); MemberInfo memberInfo = memberExpression.Member; if (String.IsNullOrEmpty(memberInfo.Name)) throw new ArgumentException("''expression'' did not provide a property name."); return memberInfo.Name; } }

donde INotifyPropertyChangedWithRaise es esto (establece la interfaz estándar para generar eventos NotifyPropertyChanged):

public interface INotifyPropertyChangedWithRaise : INotifyPropertyChanged { void OnPropertyChanged(string propertyName); }

La última pieza del rompecabezas es esta:

public class ThreadTools { public static void RunInDispatcher(Dispatcher dispatcher, Action action) { RunInDispatcher(dispatcher, DispatcherPriority.Normal, action); } public static void RunInDispatcher(Dispatcher dispatcher, DispatcherPriority priority, Action action) { if (action == null) { return; } if (dispatcher.CheckAccess()) { // we are already on thread associated with the dispatcher -> just call action try { action(); } catch (Exception ex) { //Log error here! } } else { // we are on different thread, invoke action on dispatcher''s thread dispatcher.BeginInvoke( priority, (Action)( () => { try { action(); } catch (Exception ex) { //Log error here! } }) ); } } }


Esta solución es una versión reducida de la solución propuesta por Tomáš Kafka (gracias a Tomás por describir su solución en detalle) en este hilo.

En la solución de Tomas tenía 1) DelegateCommand 2) CommandManagerHelper 3) DelegateCommandExtensions 4) NotifyPropertyChangedBaseExtensions 5) INotifyPropertyChangedWithRaise 6) ThreadTools

Esta solución tiene 1) Método DelegateCommand 2) Método DelegateCommandExtensions y Método NotifyPropertyChangedBaseExtensions en el comando Delegar.

Nota Dado que nuestra aplicación wpf sigue el patrón MVVM y manejamos comandos en el nivel del modelo de vista que se ejecuta en el subproceso UI, no necesitamos obtener la referencia al disptacher de UI.

using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; using System.Windows.Input; namespace ExampleForDelegateCommand { public class DelegateCommand : ICommand { public Predicate<object> CanExecuteDelegate { get; set; } private List<INotifyPropertyChanged> propertiesToListenTo; private List<WeakReference> ControlEvent; public DelegateCommand() { ControlEvent= new List<WeakReference>(); } public List<INotifyPropertyChanged> PropertiesToListenTo { get { return propertiesToListenTo; } set { propertiesToListenTo = value; } } private Action<object> executeDelegate; public Action<object> ExecuteDelegate { get { return executeDelegate; } set { executeDelegate = value; ListenForNotificationFrom((INotifyPropertyChanged)executeDelegate.Target); } } public static ICommand Create(Action<object> exec) { return new SimpleCommand { ExecuteDelegate = exec }; } #region ICommand Members public bool CanExecute(object parameter) { if (CanExecuteDelegate != null) return CanExecuteDelegate(parameter); return true; // if there is no can execute default to true } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; ControlEvent.Add(new WeakReference(value)); } remove { CommandManager.RequerySuggested -= value; ControlEvent.Remove(ControlEvent.Find(r => ((EventHandler) r.Target) == value)); } } public void Execute(object parameter) { if (ExecuteDelegate != null) ExecuteDelegate(parameter); } #endregion public void RaiseCanExecuteChanged() { if (ControlEvent != null && ControlEvent.Count > 0) { ControlEvent.ForEach(ce => { if(ce.Target!=null) ((EventHandler) (ce.Target)).Invoke(null, EventArgs.Empty); }); } } public DelegateCommand ListenOn<TObservedType, TPropertyType>(TObservedType viewModel, Expression<Func<TObservedType, TPropertyType>> propertyExpression) where TObservedType : INotifyPropertyChanged { string propertyName = GetPropertyName(propertyExpression); viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) => { if (e.PropertyName == propertyName) RaiseCanExecuteChanged(); }); return this; } public void ListenForNotificationFrom<TObservedType>(TObservedType viewModel) where TObservedType : INotifyPropertyChanged { viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) => { RaiseCanExecuteChanged(); }); } private string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChanged { var lambda = expression as LambdaExpression; MemberInfo memberInfo = GetmemberExpression(lambda).Member; return memberInfo.Name; } private MemberExpression GetmemberExpression(LambdaExpression lambda) { MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = lambda.Body as UnaryExpression; memberExpression = unaryExpression.Operand as MemberExpression; } else memberExpression = lambda.Body as MemberExpression; return memberExpression; } }}

Explicación de la solución:

Normalmente, cuando vinculamos un elemento UI (Button) a la implementación de ICommand, el botón WPF se registra para un evento "CanExecuteChanged" en la implementación de ICommand. Si su implementación de Icommand para "CanExecuteChanged" enlaza al evento RequesySuggest de CommandManager (lea este artículo http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/ ) cuando CommandManager detecta condiciones que pueden cambiar la capacidad de ejecución de un comando (cambios como cambios de foco y algunos eventos de teclado), se produce el evento RequerySuggested de CommandManager que a su vez hará que se llame al delegado de Button''e ya que enganchamos el delgate de buttos a RequerySugger de CommandManager en la implementación de "CanExecuteChanged" en nuestro DelegateCommand.

Pero el problema es que ComandManager no siempre puede detectar los cambios. De ahí la solución para generar "CanExecuteChanged" cuando nuestra implementación de comando (DelegateCommand) detecta que hay un cambio. Normalmente, cuando declaramos el delagate para CanExecute de ICommand en nuestro viewmodel, nos vinculamos a propiedades declaradas en nuestro viewmodel y nuestra implementación ICommand puede escuchar ". propiedad cambiada "eventos en estas propiedades. Eso es lo que hace el método "ListenForNotificationFrom" de DelegateCommand. En caso de que el código del cliente no se registre para cambios de propiedad específicos, DelegateCommand de forma predeterminada escucha cualquier cambio de propiedad en el modelo de vista donde se declara y define el comando.

"ControlEvent" en DelegateCommand que es una lista de EventHandler que almacena el "CanExecuteChange EventHandler" del botón se declara como referencia débil para evitar pérdidas de memoria.

¿Cómo usará ViewModel este DelegateCommand? Hay 2 maneras de usarlo. (el segundo uso es más específico para las propiedades que desea que el comando escuche).

delegateCommand = new DelegateCommand { ExecuteDelegate = Search, CanExecuteDelegate = (r) => !IsBusy }; anotherDelegateCommand = new DelegateCommand { ExecuteDelegate = SearchOne, CanExecuteDelegate = (r) => !IsBusyOne }.ListenOn(this, n => n.IsBusyOne);

Un ViewModel detallado

public class ExampleViewModel { public SearchViewModelBase() { delegateCommand = new DelegateCommand { ExecuteDelegate = Search, CanExecuteDelegate = (r) => !IsBusy }; anotherDelegateCommand = new DelegateCommand { ExecuteDelegate = SearchOne, CanExecuteDelegate = (r) => !IsBusyOne }.ListenOn(this, n => n.IsBusyOne); } private bool isBusy; public virtual bool IsBusy { get { return isBusy; } set { if (isBusy == value) return; isBusy = value; NotifyPropertyChanged(MethodBase.GetCurrentMethod()); } } private bool isBusyOne; public virtual bool IsBusyOne { get { return isBusyOne; } set { if (isBusyOne == value) return; isBusyOne = value; NotifyPropertyChanged(MethodBase.GetCurrentMethod()); } } private void Search(object obj) { IsBusy = true; new SearchService().Search(Callback); } public void Callback(ServiceResponse response) { IsBusy = false; } private void Search(object obj) { IsBusyOne = true; new SearchService().Search(CallbackOne); } public void CallbackOne(ServiceResponse response) { IsBusyOne = false; } private void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } private void NotifyPropertyChanged(MethodBase methodBase) { string methodName = methodBase.Name; if (!methodName.StartsWith("set_")) { var ex = new ArgumentException("MethodBase must refer to a Property Setter method."); throw ex; } NotifyPropertyChanged(methodName.Substring(4)); }

}