当前位置:  开发笔记 > 开发工具 > 正文

CommandManager.InvalidateRequerySuggested()不够快.我能做什么?

如何解决《CommandManager.InvalidateRequerySuggested()不够快.我能做什么?》经验,为你挑选了2个好方法。

CommandManager.InvalidateRequerySuggested() tries to validate all commands, which is totally ineffective (and in your case slow) - on every change, you are asking every command to recheck its CanExecute()!

You'd need the command to know on which objects and properties is its CanExecute dependent, and suggest requery only when they change. That way, if you change a property of an object, only commands that depend on it will change their state.

This is how I solved the problem, but at first, a teaser:

// in ViewModel's constructor - add a code to public ICommand:
this.DoStuffWithParameterCommand = new DelegateCommand(
    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!


The command is listening on NotifyPropertyChanged events from object that affect whether it can execute, and invokes the check only when a requery is needed.

Now, a lot of code (part of our in-house framework) to do this:

I use DelegateCommand from Prism, that looks like this:

/// 
///     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.
/// 
public class DelegateCommand : ICommand
{
    #region Constructors

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, null, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod)
        : this(executeMethod, canExecuteMethod, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
        if (executeMethod == null)
        {
            throw new ArgumentNullException("executeMethod");
        }

        _executeMethod = executeMethod;
        _canExecuteMethod = canExecuteMethod;
        _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;

        this.RaiseCanExecuteChanged();
    }

    #endregion

    #region Public Methods

    /// 
    ///     Method to determine if the command can be executed
    /// 
    public bool CanExecute()
    {
        if (_canExecuteMethod != null)
        {
            return _canExecuteMethod();
        }
        return true;
    }

    /// 
    ///     Execution of the command
    /// 
    public void Execute()
    {
        if (_executeMethod != null)
        {
            _executeMethod();
        }
    }

    /// 
    ///     Property to enable or disable CommandManager's automatic requery on this command
    /// 
    public bool IsAutomaticRequeryDisabled
    {
        get
        {
            return _isAutomaticRequeryDisabled;
        }
        set
        {
            if (_isAutomaticRequeryDisabled != value)
            {
                if (value)
                {
                    CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
                }
                else
                {
                    CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
                }
                _isAutomaticRequeryDisabled = value;
            }
        }
    }

    /// 
    ///     Raises the CanExecuteChaged event
    /// 
    public void RaiseCanExecuteChanged()
    {
        OnCanExecuteChanged();
    }

    /// 
    ///     Protected virtual method to raise CanExecuteChanged event
    /// 
    protected virtual void OnCanExecuteChanged()
    {
        CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    #endregion

    #region ICommand Members

    /// 
    ///     ICommand.CanExecuteChanged implementation
    /// 
    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 _canExecuteMethod = null;
    private bool _isAutomaticRequeryDisabled = false;
    private List _canExecuteChangedHandlers;

    #endregion
}

/// 
///     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.
/// 
/// Type of the parameter passed to the delegates
public class DelegateCommand : ICommand
{
    #region Constructors

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, null, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod)
        : this(executeMethod, canExecuteMethod, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
        if (executeMethod == null)
        {
            throw new ArgumentNullException("executeMethod");
        }

        _executeMethod = executeMethod;
        _canExecuteMethod = canExecuteMethod;
        _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    }

    #endregion

    #region Public Methods

    /// 
    ///     Method to determine if the command can be executed
    /// 
    public bool CanExecute(T parameter)
    {
        if (_canExecuteMethod != null)
        {
            return _canExecuteMethod(parameter);
        }
        return true;
    }

    /// 
    ///     Execution of the command
    /// 
    public void Execute(T parameter)
    {
        if (_executeMethod != null)
        {
            _executeMethod(parameter);
        }
    }

    /// 
    ///     Raises the CanExecuteChaged event
    /// 
    public void RaiseCanExecuteChanged()
    {
        OnCanExecuteChanged();
    }

    /// 
    ///     Protected virtual method to raise CanExecuteChanged event
    /// 
    protected virtual void OnCanExecuteChanged()
    {
        CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    /// 
    ///     Property to enable or disable CommandManager's automatic requery on this command
    /// 
    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

    /// 
    ///     ICommand.CanExecuteChanged implementation
    /// 
    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 _executeMethod = null;
    private readonly Func _canExecuteMethod = null;
    private bool _isAutomaticRequeryDisabled = false;
    private List _canExecuteChangedHandlers;

    #endregion
}

/// 
///     This class contains methods for the CommandManager that help avoid memory leaks by
///     using weak references.
/// 
internal class CommandManagerHelper
{
    internal static void CallWeakReferenceHandlers(List 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 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 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 handlers, EventHandler handler)
    {
        AddWeakReferenceHandler(ref handlers, handler, -1);
    }

    internal static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize)
    {
        if (handlers == null)
        {
            handlers = (defaultListSize > 0 ? new List(defaultListSize) : new List());
        }

        handlers.Add(new WeakReference(handler));
    }

    internal static void RemoveWeakReferenceHandler(List 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
{
    /// 
    /// Makes DelegateCommnand listen on PropertyChanged events of some object,
    /// so that DelegateCommnand can update its IsEnabled property.
    /// 
    public static DelegateCommand ListenOn
        (this DelegateCommand delegateCommand, 
        ObservedType observedObject, 
        Expression> 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
    }

    /// 
    /// Makes DelegateCommnand listen on PropertyChanged events of some object,
    /// so that DelegateCommnand can update its IsEnabled property.
    /// 
    public static DelegateCommand ListenOn
        (this DelegateCommand delegateCommand, 
        ObservedType observedObject, 
        Expression> 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

    /// 
/// 
/// 
public static class NotifyPropertyChangedBaseExtensions
{
    /// 
    /// Raises PropertyChanged event.
    /// To use: call the extension method with this: this.OnPropertyChanged(n => n.Title);
    /// 
    /// Property owner
    /// Type of property
    /// 
    /// Property expression like 'n => n.Property'
    public static void OnPropertyChanged(this T observableBase, Expression> expression) where T : INotifyPropertyChangedWithRaise
    {
        observableBase.OnPropertyChanged(GetPropertyName(expression));
    }

    public static string GetPropertyName(Expression> 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!
                    }
                })
            );
        }
    }
}


Sankalp Saxe.. 8

这个解决方案是TomášKafka提出的解决方案的简化版本(感谢Tomas详细描述了他的解决方案).

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

该解决方案具有 1)DelegateCommand 2)委托命令本身中的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 CanExecuteDelegate { get; set; }

    private List propertiesToListenTo;
    private List ControlEvent;

    public DelegateCommand()
    {
        ControlEvent= new List();
    }

    public List PropertiesToListenTo
    {
        get { return propertiesToListenTo; }
        set
        {
            propertiesToListenTo = value;
        }
    }

    private Action executeDelegate;

    public Action ExecuteDelegate
    {
        get { return executeDelegate; }
        set
        {
            executeDelegate = value;
            ListenForNotificationFrom((INotifyPropertyChanged)executeDelegate.Target);
        }
    }

    public static ICommand Create(Action 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 viewModel, Expression> propertyExpression) where TObservedType : INotifyPropertyChanged
    {
        string propertyName = GetPropertyName(propertyExpression);
        viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
        {
            if (e.PropertyName == propertyName) RaiseCanExecuteChanged();
        });
        return this;
    }

    public void ListenForNotificationFrom(TObservedType viewModel) where TObservedType : INotifyPropertyChanged
    {
        viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
        {
           RaiseCanExecuteChanged();
        });
    }

    private string GetPropertyName(Expression> 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事件(请阅读本文http:// joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/)然后当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的"CanExecuteChange EventHandler"的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));
    }

}



1> Tomáš Kafka..:

CommandManager.InvalidateRequerySuggested() tries to validate all commands, which is totally ineffective (and in your case slow) - on every change, you are asking every command to recheck its CanExecute()!

You'd need the command to know on which objects and properties is its CanExecute dependent, and suggest requery only when they change. That way, if you change a property of an object, only commands that depend on it will change their state.

This is how I solved the problem, but at first, a teaser:

// in ViewModel's constructor - add a code to public ICommand:
this.DoStuffWithParameterCommand = new DelegateCommand(
    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!


The command is listening on NotifyPropertyChanged events from object that affect whether it can execute, and invokes the check only when a requery is needed.

Now, a lot of code (part of our in-house framework) to do this:

I use DelegateCommand from Prism, that looks like this:

/// 
///     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.
/// 
public class DelegateCommand : ICommand
{
    #region Constructors

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, null, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod)
        : this(executeMethod, canExecuteMethod, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
        if (executeMethod == null)
        {
            throw new ArgumentNullException("executeMethod");
        }

        _executeMethod = executeMethod;
        _canExecuteMethod = canExecuteMethod;
        _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;

        this.RaiseCanExecuteChanged();
    }

    #endregion

    #region Public Methods

    /// 
    ///     Method to determine if the command can be executed
    /// 
    public bool CanExecute()
    {
        if (_canExecuteMethod != null)
        {
            return _canExecuteMethod();
        }
        return true;
    }

    /// 
    ///     Execution of the command
    /// 
    public void Execute()
    {
        if (_executeMethod != null)
        {
            _executeMethod();
        }
    }

    /// 
    ///     Property to enable or disable CommandManager's automatic requery on this command
    /// 
    public bool IsAutomaticRequeryDisabled
    {
        get
        {
            return _isAutomaticRequeryDisabled;
        }
        set
        {
            if (_isAutomaticRequeryDisabled != value)
            {
                if (value)
                {
                    CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
                }
                else
                {
                    CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
                }
                _isAutomaticRequeryDisabled = value;
            }
        }
    }

    /// 
    ///     Raises the CanExecuteChaged event
    /// 
    public void RaiseCanExecuteChanged()
    {
        OnCanExecuteChanged();
    }

    /// 
    ///     Protected virtual method to raise CanExecuteChanged event
    /// 
    protected virtual void OnCanExecuteChanged()
    {
        CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    #endregion

    #region ICommand Members

    /// 
    ///     ICommand.CanExecuteChanged implementation
    /// 
    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 _canExecuteMethod = null;
    private bool _isAutomaticRequeryDisabled = false;
    private List _canExecuteChangedHandlers;

    #endregion
}

/// 
///     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.
/// 
/// Type of the parameter passed to the delegates
public class DelegateCommand : ICommand
{
    #region Constructors

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, null, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod)
        : this(executeMethod, canExecuteMethod, false)
    {
    }

    /// 
    ///     Constructor
    /// 
    public DelegateCommand(Action executeMethod, Func canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
        if (executeMethod == null)
        {
            throw new ArgumentNullException("executeMethod");
        }

        _executeMethod = executeMethod;
        _canExecuteMethod = canExecuteMethod;
        _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    }

    #endregion

    #region Public Methods

    /// 
    ///     Method to determine if the command can be executed
    /// 
    public bool CanExecute(T parameter)
    {
        if (_canExecuteMethod != null)
        {
            return _canExecuteMethod(parameter);
        }
        return true;
    }

    /// 
    ///     Execution of the command
    /// 
    public void Execute(T parameter)
    {
        if (_executeMethod != null)
        {
            _executeMethod(parameter);
        }
    }

    /// 
    ///     Raises the CanExecuteChaged event
    /// 
    public void RaiseCanExecuteChanged()
    {
        OnCanExecuteChanged();
    }

    /// 
    ///     Protected virtual method to raise CanExecuteChanged event
    /// 
    protected virtual void OnCanExecuteChanged()
    {
        CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
    }

    /// 
    ///     Property to enable or disable CommandManager's automatic requery on this command
    /// 
    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

    /// 
    ///     ICommand.CanExecuteChanged implementation
    /// 
    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 _executeMethod = null;
    private readonly Func _canExecuteMethod = null;
    private bool _isAutomaticRequeryDisabled = false;
    private List _canExecuteChangedHandlers;

    #endregion
}

/// 
///     This class contains methods for the CommandManager that help avoid memory leaks by
///     using weak references.
/// 
internal class CommandManagerHelper
{
    internal static void CallWeakReferenceHandlers(List 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 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 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 handlers, EventHandler handler)
    {
        AddWeakReferenceHandler(ref handlers, handler, -1);
    }

    internal static void AddWeakReferenceHandler(ref List handlers, EventHandler handler, int defaultListSize)
    {
        if (handlers == null)
        {
            handlers = (defaultListSize > 0 ? new List(defaultListSize) : new List());
        }

        handlers.Add(new WeakReference(handler));
    }

    internal static void RemoveWeakReferenceHandler(List 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
{
    /// 
    /// Makes DelegateCommnand listen on PropertyChanged events of some object,
    /// so that DelegateCommnand can update its IsEnabled property.
    /// 
    public static DelegateCommand ListenOn
        (this DelegateCommand delegateCommand, 
        ObservedType observedObject, 
        Expression> 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
    }

    /// 
    /// Makes DelegateCommnand listen on PropertyChanged events of some object,
    /// so that DelegateCommnand can update its IsEnabled property.
    /// 
    public static DelegateCommand ListenOn
        (this DelegateCommand delegateCommand, 
        ObservedType observedObject, 
        Expression> 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

    /// 
/// 
/// 
public static class NotifyPropertyChangedBaseExtensions
{
    /// 
    /// Raises PropertyChanged event.
    /// To use: call the extension method with this: this.OnPropertyChanged(n => n.Title);
    /// 
    /// Property owner
    /// Type of property
    /// 
    /// Property expression like 'n => n.Property'
    public static void OnPropertyChanged(this T observableBase, Expression> expression) where T : INotifyPropertyChangedWithRaise
    {
        observableBase.OnPropertyChanged(GetPropertyName(expression));
    }

    public static string GetPropertyName(Expression> 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!
                    }
                })
            );
        }
    }
}



2> Sankalp Saxe..:

这个解决方案是TomášKafka提出的解决方案的简化版本(感谢Tomas详细描述了他的解决方案).

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

该解决方案具有 1)DelegateCommand 2)委托命令本身中的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 CanExecuteDelegate { get; set; }

    private List propertiesToListenTo;
    private List ControlEvent;

    public DelegateCommand()
    {
        ControlEvent= new List();
    }

    public List PropertiesToListenTo
    {
        get { return propertiesToListenTo; }
        set
        {
            propertiesToListenTo = value;
        }
    }

    private Action executeDelegate;

    public Action ExecuteDelegate
    {
        get { return executeDelegate; }
        set
        {
            executeDelegate = value;
            ListenForNotificationFrom((INotifyPropertyChanged)executeDelegate.Target);
        }
    }

    public static ICommand Create(Action 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 viewModel, Expression> propertyExpression) where TObservedType : INotifyPropertyChanged
    {
        string propertyName = GetPropertyName(propertyExpression);
        viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
        {
            if (e.PropertyName == propertyName) RaiseCanExecuteChanged();
        });
        return this;
    }

    public void ListenForNotificationFrom(TObservedType viewModel) where TObservedType : INotifyPropertyChanged
    {
        viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
        {
           RaiseCanExecuteChanged();
        });
    }

    private string GetPropertyName(Expression> 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事件(请阅读本文http:// joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/)然后当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的"CanExecuteChange EventHandler"的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));
    }

}

推荐阅读
保佑欣疼你的芯疼
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有