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:
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 enfalse
. Los controles en mis vistas, que están enlazados a esta propiedad, están inmediatamente atenuados, que es lo que yo esperaría.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 comoINotifyPropertyChanged
para objetosICommand
, me veo forzado a llamar aCommandManager.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 deICommand
para cambiar visualmente a un estado deshabilitado que en los controles de UI que tienen su propiedadIsEnabled
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!
- Implemente el
Command
para mantener / almacenar en cachéCanExecuteState
en una variable booleana. - Implemente el método
RaiseCanExecuteChanged
para recalcularCanExecuteState
y si realmente cambió para generar el eventoCanExecuteChanged
. - Implemente el método
CanExecute
para simplemente devolverCanExecuteState
. - Cuando se invoca el método
InvalidateRequerySuggested
, los suscriptores delCommand
solo leerán la variableCanExecute
invocando el métodoCanExecute
y verificando si cambió o no. Eso es casi cero por encima. Todos losCommands
se desactivarán / habilitarán casi al mismo tiempo. - Todo el trabajo se realizará en el método
RaiseCanExecuteChanged
que se llamará solo una vez para unCommand
y solo para un conjunto limitado deCommands
.
¿Intenta escribir su propio enlace que llame a su RaiseCanExecuteChanged () dentro de los conversos? es más fácil
Solo para aclarar:
- Desea
CanExecute
una actualización deCanExecute
cuando laCommand property changed
- Cree su propia clase de enlace que detecte cambios en la
Command property
y luego llame aRaiseCanExecuteChanged()
- 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));
}
}