调用CommandManager.InvalidateRequerySuggested()
需要更长时间才能生效(在禁用UI控件之前1-2秒延迟)。
我有一个系统,我将任务提交给基于后台线程的任务处理器。此提交发生在WPF UI线程上。
当提交此提交时,管理我的后台线程的对象会做两件事:
它引发了几个视图模型响应的“忙”事件(仍在UI线程上);当他们收到此活动时,他们会在IsEnabled
上为自己设置false
标记。我的视图中的控件(数据绑定到此属性)会立即显示为灰色,这正是我所期望的。
它通知我的WPF ICommand
对象不允许执行它们(同样,仍然在UI线程上)。因为INotifyPropertyChanged
对象没有ICommand
,所以我被迫调用CommandManager.InvalidateRequerySuggested()
强制WPF重新考虑我的所有命令对象'CanExecute
状态(是的,我实际上需要这样做:否则,这些控件都不会被禁用)。但是,与第1项不同,使用ICommand
个对象的按钮/菜单项/等在视觉上更改为禁用状态所需的时间要比使用{{1}的UI控件的时间长得多。 }属性手动设置。
问题是,从用户体验的角度来看,这看起来糟糕;我的一半控件立即显示为灰色(因为他们的IsEnabled
属性设置为false),然后整整1-2秒后,我的另一半控件也跟随(因为他们的IsEnabled
方法终于重新评估了。)
因此,我的问题的第1部分:
听起来很愚蠢,有没有办法让CanExecute
更快地完成工作?我怀疑没有。
很公平,我的问题的第2部分:
我该如何解决这个问题?我希望我的所有控件都可以同时禁用。它只是看起来不专业而且很尴尬。有任何想法吗? : - )
答案 0 :(得分:58)
CommandManager.InvalidateRequerySuggested()
尝试验证所有命令,这些命令完全无效(在您的情况下很慢) - 每次更改时,您都要求每个命令重新检查其CanExecute()
!
您需要使用该命令来了解哪些对象和属性是CanExecute
依赖的,并且仅在更改时建议重新查询。这样,如果更改对象的属性,则只有依赖于它的命令才会更改其状态。
这就是我解决问题的方法,但首先是一个预告片:
// 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!
该命令正在侦听来自对象的NotifyPropertyChanged
事件,这些事件会影响它是否可以执行,并且仅在需要重新查询时调用该检查。
现在,很多代码(我们的内部框架的一部分)要做到这一点:
我使用Prism的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
{
#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);
}
}
}
}
}
然后我编写了一个ListenOn
扩展方法,将命令“绑定”到一个属性,然后调用它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
}
}
然后,您需要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;
}
}
这里INotifyPropertyChangedWithRaise
(它建立了用于引发NotifyPropertyChanged事件的标准接口):
public interface INotifyPropertyChangedWithRaise : INotifyPropertyChanged
{
void OnPropertyChanged(string propertyName);
}
最后一块拼图是:
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!
}
})
);
}
}
}
答案 1 :(得分:8)
此解决方案是TomášKafka提出的解决方案的简化版本(感谢Tomas详细描述了他的解决方案)。
在Tomas的解决方案中,他有 1)DelegateCommand 2)CommandManagerHelper 3)DelegateCommandExtensions 4)NotifyPropertyChangedBaseExtensions 5)INotifyPropertyChangedWithRaise 6)ThreadTools
此解决方案 1)DelegateCommand 2)Delegate Command本身中的DelegateCommandExtensions方法和NotifyPropertyChangedBaseExtensions方法。
注意由于我们的wpf应用程序遵循MVVM模式,我们处理在UI线程中执行的viewmodel级别的命令,我们不需要获取UI disptacher的引用。
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;
}
}}
解决方案说明:
通常,当我们将UI元素(Button)绑定到ICommand实现时,WPF Button会在ICommand实现中注册事件“CanExecuteChanged”。如果“CanExecuteChanged”的Icommand实现挂钩到CommandManager的RequesySuggest事件(请阅读本文{ {3}})然后当CommandManager检测到可能改变命令执行能力的条件时(像Focus转换和某些键盘事件一样),会发生CommandManager的RequerySuggested事件,这将导致调用Button'e委托,因为我们在我们的DelegateCommand中执行“CanExecuteChanged”时,将buttos的delgate挂钩到CommandManager的RequerySuggested。
但问题是ComandManager无法始终检测到更改。因此,当我们的命令实现(DelegateCommand)检测到存在更改时,它会引发“CanExecuteChanged”的解决方案。通常,当我们在viewmodel中声明ICommand的CanExecute的delagate时,我们绑定到viewmodel中声明的属性,我们的ICommand实现可以侦听“ propertychanged“这些属性上的事件。这就是DelegateCommand的“ListenForNotificationFrom”方法。如果客户端代码没有注册特定的属性更改,则默认情况下,DelegateCommand将侦听视图模型上声明和定义命令的任何属性更改。
DelegateCommand中的“ControlEvent”,它是存储Button的EventHandler列表 “CanExecuteChange EventHandler”被声明为弱引用以避免内存泄漏。
ViewModel将如何使用此DelegateCommand 有两种方法可以使用它。 (第二种用法更具体到您希望Command侦听的属性。
delegateCommand = new DelegateCommand
{
ExecuteDelegate = Search,
CanExecuteDelegate = (r) => !IsBusy
};
anotherDelegateCommand = new DelegateCommand
{
ExecuteDelegate = SearchOne,
CanExecuteDelegate = (r) => !IsBusyOne
}.ListenOn(this, n => n.IsBusyOne);
详细的ViewModel
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));
}
}
答案 2 :(得分:1)
Tomas有一个很好的解决方案,但是请注意,由于这个原因,CanExecute在绑定到Button时不会总是触发,这是一个严重的错误:
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
传入的'null'参数会导致CanExecuteChangedEventManager出现问题(WPF Button类用于侦听绑定到它的任何Command的更改)。具体来说,CanExecuteChangedEventManager维护一组需要调用的弱事件,以确定命令Can-Execute(),但此集合是否由'sender'键入。
修复很简单,对我有用 - 将签名更改为
internal static void CallWeakReferenceHandlers(ICommand sender, List<WeakReference> handlers)
{
....
handler(sender, EventArgs.Empty);
}
对不起,我没有把它描述得太好 - 在花了几个小时来解决这个问题之后,现在有点急于赶上我的开发!
答案 3 :(得分:1)
我建议调查ReactiveUI,特别是它提供的ICommand实现,ReactiveCommand。它使用与DelegateCommand / RelayCommand不同的方法,这些方法是使用CanExecute的代理实现的,必须对其进行主动评估。使用IObservables推送ReactiveCommand的CanExecute值。
答案 4 :(得分:1)
有没有办法让CommandManager.InvalidateRequerySuggested()更快地完成工作?
是的,有办法让它更快地运作!
Command
以将/ CanExecuteState
保留在布尔变量中。RaiseCanExecuteChanged
方法重新计算CanExecuteState
以及是否真的改为提升CanExecuteChanged
事件。CanExecute
方法,只需返回CanExecuteState
。InvalidateRequerySuggested
方法Command
订阅者只会通过调用CanExecuteState
方法读取CanExecute
变量,并检查其是否发生了变化。这几乎是零开销。几乎同时禁用/启用所有Commands
。RaiseCanExecuteChanged
方法中完成,该方法仅针对Command
调用一次,且仅针对有限的Commands
调用。答案 5 :(得分:0)
尝试编写自己的绑定,在转换中调用RaiseCanExecuteChanged()?它更容易
答案 6 :(得分:0)
只是为了澄清:
CanExecute
Command property changed
的更新
Command property
中的更改,然后调用RaiseCanExecuteChanged()
CommandParameter
为我工作。