33

精简版

调用CommandManager.InvalidateRequerySuggested()需要比我想要的更长的时间才能生效(在 UI 控件被禁用之前有 1-2 秒的延迟)。

长版

我有一个系统,我将任务提交到基于后台线程的任务处理器。此提交发生在 WPF UI 线程上。

当这个提交发生时,管理我的后台线程的对象会做两件事:

  1. 它引发了多个视图模型响应的“忙碌”事件(仍在 UI 线程上);当他们收到这个事件时,他们将IsEnabled自己的标志设置为false. 在我的视图中,数据绑定到该属性的控件立即变灰,这是我所期望的。

  2. 它通知我的 WPFICommand对象,它们不应该被允许执行(同样,仍然在 UI 线程上)。因为没有像对象那样INotifyPropertyChangedICommand东西,我被迫调用CommandManager.InvalidateRequerySuggested()强制 WPF 重新考虑我所有的命令对象的CanExecute状态(是的,我确实需要这样做:否则,这些控件都不会被禁用)。但是,与第 1 项不同的是,与手动设置属性ICommand的 UI 控件相比,使用对象的按钮/菜单项/等在视觉上更改为禁用状态所需的时间要长得多。IsEnabled

问题是,从用户体验的角度来看,这看起来很糟糕;我的一半控件立即变灰(因为它们的IsEnabled属性设置为 false),然后整整 1-2 秒后,我的另一半控件也效仿(因为CanExecute最终重新评估了它们的方法)。

所以,我的问题的第 1 部分:
听起来很愚蠢,有没有办法让我CommandManager.InvalidateRequerySuggested()更快地完成工作?我怀疑没有。

很公平,我的问题的第 2 部分:
我该如何解决这个问题?我希望同时禁用所有控件。否则它看起来不专业和尴尬。有任何想法吗?:-)

4

7 回答 7

59

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对象中影响其是否可以执行的事件,并且仅在需要重新查询时才调用检查。

现在,很多代码(我们内部框架的一部分)来做到这一点:

DelegateCommand从 Prism 使用,看起来像这样:

/// <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!
                    }
                })
            );
        }
    }
}
于 2009-12-07T03:16:43.217 回答
9

此解决方案是 Tomáš Kafka 在此线程中提出的解决方案的简化版本(感谢 Tomas 详细描述了他的解决方案)。

在 Tomas 的解决方案中,他有 1) DelegateCommand 2) CommandManagerHelper 3) DelegateCommandExtensions 4) NotifyPropertyChangedBaseExtensions 5) INotifyPropertyChangedWithRaise 6) ThreadTools

该解决方案 在 Delegate Command 本身中有 1) DelegateCommand 2) DelegateCommandExtensions 方法和 NotifyPropertyChangedBaseExtensions 方法。

注意由于我们的 wpf 应用程序遵循 MVVM 模式,并且我们在视图模型级别处理在 UI 线程中执行的命令,因此我们不需要获取对 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;
    }

}}

解决方案说明:

通常,当我们将 UI 元素(按钮)绑定到 ICommand 实现时,WPF 按钮会在 ICommand 实现中注册事件“CanExecuteChanged”。如果您的 Icommand 实现“CanExecuteChanged”挂钩到 CommandManager 的 RequesySuggest 事件(阅读这篇文章http:// joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/)然后当 CommandManager 检测到可能改变命令执行能力的条件时(如焦点转移和一些键盘事件),CommandManager 的 RequerySuggested 事件发生,这反过来将导致 Button'e 委托被调用,因为我们在“CanExecuteChanged”的实现中将按钮的委托连接到 CommandManager 的 RequerySuggested在我们的 DelegateCommand 中。

但问题是 ComandManager 无法始终检测到更改。因此,当我们的命令实现(DelegateCommand)检测到有变化时,它会引发“CanExecuteChanged”的解决方案。通常,当我们在视图模型中为 ICommand 的 CanExecute 声明委托时,我们绑定到视图模型中声明的属性,并且我们的 ICommand 实现可以监听“ propertychanged”事件。这就是 DelegateCommand 的“ListenForNotificationFrom”方法的作用。如果客户端代码未注册特定属性更改,则默认情况下,DelegateCommand 会侦听声明和定义命令的视图模型上的任何属性更改。

DelegateCommand 中的“ControlEvent”是存储 Button 的“CanExecuteChange EventHandler”的 EventHandler 列表,被声明为弱引用以避免内存泄漏。

ViewModel 将如何使用这个 DelegateCommand 有两种使用方式。(第二种用法更特定于您希望命令侦听的属性。

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));
    }

}

于 2011-03-31T16:03:32.463 回答
1

我建议研究ReactiveUI,特别是它提供的 ICommand 实现ReactiveCommand。它使用不同于 DelegateCommand/RelayCommand 的方法,后者是通过必须主动评估的 CanExecute 的委托来实现的。ReactiveCommand 的 CanExecute 值是使用 IObservables 推送的。

于 2014-05-14T10:30:56.750 回答
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() 是否由“发送者”键入。

修复很简单,对我有用 - 将签名更改为

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

抱歉,我没有把它描述得太清楚——在花了几个小时来解决这个问题之后,现在有点急于赶上我的开发者!

于 2013-03-13T11:58:02.333 回答
1

有没有办法让 CommandManager.InvalidateRequerySuggested() 更快地完成工作?

是的,有办法让它工作得更快!

  1. 实现Command将/缓存CanExecuteState在布尔变量中。
  2. 实现RaiseCanExecuteChanged重新计算的方法CanExecuteState,如果它真的改变为引发CanExecuteChanged事件。
  3. 实现CanExecute方法以简单地返回CanExecuteState
  4. InvalidateRequerySuggested方法被调用时,Command订阅者只会CanExecuteState通过调用方法读取变量CanExecute并检查它是否改变。这几乎是零开销。Commands几乎同时禁用/启用所有功能。
  5. 所有工作都将在RaiseCanExecuteChanged方法中完成,该方法仅对 a 调用一次,Command并且仅对有限的一组Commands.
于 2015-09-11T11:32:01.187 回答
0

尝试编写自己的绑定,在转换中调用 RaiseCanExecuteChanged()?更容易

于 2010-02-20T19:18:55.110 回答
0

只是为了澄清:

  1. 您想触发CanExecute何时更新Command property changed
  2. 创建您自己的绑定类,以检测Command property然后调用中的更改RaiseCanExecuteChanged()
  3. 使用此绑定CommandParameter

为我工作。

于 2010-02-20T19:32:32.230 回答