WPF MVVM Command CanExecute,仅在焦点更改时重新评估
Posted
技术标签:
【中文标题】WPF MVVM Command CanExecute,仅在焦点更改时重新评估【英文标题】:WPF MVVM Command CanExecute, reevaluates only at focus change 【发布时间】:2017-04-07 20:07:46 【问题描述】:在 WPF 中重构 MVVM 项目,我试图摆脱 MVVM 模式用户之间似乎常见的问题。
我有视图,谁的 DataContext 是 MyViewModel。这是一个按钮,绑定了一个实现 Execute 和 CanExecute 的命令。
XAML:
<Button Command="Binding ConnectCommand"/>
MyViewModel 公开 ConnectCommand:
public ICommand ConnectCommand
get return new DelegateCommand(() => Connect(), () => IsConnectEnabled);
(最后是我使用的 DelegateCommand 的定义)
MyViewModel 还公开了属性 IsConnectEnabled,用于命令的 CanExecute 部分:
public bool IsConnectEnabled
get
return (isDisconnected && null!=selectedDevice && 0<selectedDevice.Length);
MyViewModel 类实现 INotifyPropertyChanged 接口
public class MyViewModel : INotifyPropertyChanged
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
#endregion
#region rest of the class
命令的CanExecute 部分仅根据应用程序中焦点的变化进行评估(即,无论我做什么点击)。我知道 UpdateSourceTrigger 默认设置为 PropertyChanged,因此我目前的解决方案是在以下几个地方手动引发 PropertyChanged 事件代码。但我想做得更好,并在 IsConnectEnabled 的值发生变化时自动完成此活动。
WPF 和 MVVM 模式是否为这个问题提供了解决方案?
为了完整起见,请遵循我正在使用的完整 ICommand 实现,DelegateCommand:
/// <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
/// <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;
#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);
【问题讨论】:
WPF 还是 Silverlight? @heltonbiker WPF。谢谢,我现在在描述中说得更清楚了。 "WPF 和 MVVM 模式是否为这个问题提供了解决方案?"没有。 【参考方案1】:这是一个古老的错误行为,至少在我的 WPF 经验中。
我发现 RelayCommand (GalaSoft.MvvmLight.CommandWpf.RelayCommand
) 的 MvvmLight 实现似乎可以解决这个问题。
请注意,他们甚至创建了一个 WPF 特定实现 (!),apparently 旨在纠正这种怪异现象。
【讨论】:
以上是关于WPF MVVM Command CanExecute,仅在焦点更改时重新评估的主要内容,如果未能解决你的问题,请参考以下文章
WPF MVVM DataGrid Button Command绑定
使用 WPF + MVVM 中的 Command + CommandParameter 删除列表视图中的行
WPF MVVM,Prism,Command Binding
wpf中mvvm的Command绑定后,如何在点击按钮的时候在viewmodel里面异步执行方法。