CommandManager.InvalidateRequerySuggested () недостаточно быстр. Что я могу сделать? - PullRequest
32 голосов
/ 18 ноября 2009

Короткая версия

Звонки на CommandManager.InvalidateRequerySuggested() вступают в силу гораздо дольше, чем хотелось бы (задержка 1-2 секунды, прежде чем элементы управления пользовательского интерфейса отключаются).

Длинная версия

У меня есть система, в которой я отправляю задачи обработчику задач на основе фоновых потоков. Это происходит в потоке пользовательского интерфейса WPF.

Когда это происходит, объект, который управляет моим фоновым потоком, делает две вещи:

  1. Вызывает событие «занято» (все еще в потоке пользовательского интерфейса), на которое отвечают несколько моделей представлений; когда они получают это событие, они устанавливают для себя флаг IsEnabled на false. Элементы управления в моих представлениях, которые привязаны к этому свойству, немедленно выделяются серым цветом, чего я и ожидал.

  2. Он сообщает моим объектам WPF ICommand, что им нельзя разрешать выполнение (опять же, в потоке пользовательского интерфейса). Поскольку нет ничего похожего на INotifyPropertyChanged для ICommand объектов, я вынужден вызвать CommandManager.InvalidateRequerySuggested(), чтобы заставить WPF пересмотреть все CanExecute состояний моих командных объектов (да, мне действительно нужно это сделать : в противном случае ни один из этих элементов управления не будет отключен) . Однако, в отличие от элемента 1, моим кнопкам / пунктам меню / и т. Д., Которые используют объекты ICommand, требуется визуально для перехода в отключенное состояние, что значительно больше времени, чем для элементов управления пользовательского интерфейса, для которых свойство IsEnabled установлено вручную. .

Проблема в том, что с точки зрения UX это выглядит ужасно ; половина моих элементов управления сразу становится недоступной (потому что их свойство IsEnabled установлено в false), а затем через полные 1-2 секунды другая половина моих элементов управления следует их примеру (потому что их методы CanExecute наконец-то повторно оценивается).

Итак, часть 1 моего вопроса:
Как бы глупо это ни звучало, спросить, могу ли я заставить CommandManager.InvalidateRequerySuggested() сделать это быстрее? Я подозреваю, что нет.

Достаточно справедливо, часть 2 моего вопроса:
Как я могу обойти это? Я бы предпочел, чтобы все мои элементы управления были отключены одновременно. В противном случае это выглядит непрофессионально и неловко. Есть идеи? : -)

Ответы [ 7 ]

58 голосов
/ 07 декабря 2009

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!
                    }
                })
            );
        }
    }
}
8 голосов
/ 31 марта 2011

Это решение является сокращенной версией решения, предложенного Томашем Кафкой (спасибо Томасу за подробное описание его решения) в этой теме.

В решении Томаса он имел 1) DelegateCommand 2) CommandManagerHelper 3) DelegateCommandExtensions 4) NotifyPropertyChangedBaseExtensions 5) INotifyPropertyChangedWithRaise 6) ThreadTools

Это решение имеет 1) DelegateCommand 2) Метод DelegateCommandExtensions и метод NotifyPropertyChangedBaseExtensions в самой делегатской команде.

Примечание Поскольку наше wpf-приложение следует шаблону MVVM, и мы обрабатываем команды на уровне viewmodel, который выполняется в потоке пользовательского интерфейса, нам не нужно получать ссылку на диспетчер пользовательского интерфейса.

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

}}

Объяснение решения:

Обычно, когда мы связываем элемент пользовательского интерфейса (Button) с реализацией ICommand, кнопка WPF регистрирует событие «CanExecuteChanged» в реализации ICommand. Если ваша реализация Icommand для ловушки «CanExecuteChanged» связана с событием RequesySuggest CommandManager (см. Эту статью http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/) затем, когда когда-либо CommandManager обнаруживает условия, которые могут изменить способность выполнения команды (такие как смена фокуса и некоторые события клавиатуры), происходит событие RequerySuggested CommandManager, которое, в свою очередь, вызывает вызов делегата Button'e, так как мы подключил delgate buttos к RequerySuggested CommandManager в реализации CanExecuteChanged в нашей DelegateCommand.

Но проблема в том, что ComandManager не всегда может обнаружить изменения. Следовательно, решение - вызвать CanExecuteChanged, когда наша реализация команды (DelegateCommand) обнаруживает изменение. Обычно, когда мы объявляем delagate для CanExecute ICommand в нашей viewmodel, мы связываемся со свойствами, объявленными в нашей viewmodel, и наша реализация ICommand может прослушивать " propertyaged "события на этих свойствах. Вот что делает метод ListenForNotificationFrom в DelegateCommand. Если клиентский код не регистрируется для определенных изменений свойств, DelegateCommand по умолчанию прослушивает любые изменения свойств в модели представления, где команда объявлена ​​и определена.

«ControlEvent» в DelegateCommand, который является списком EventHandler, в котором хранятся кнопки «CanExecuteChange EventHandler» объявлен слабой ссылкой, чтобы избежать утечек памяти.

Как ViewModel будет использовать эту DelegateCommand Есть 2 способа его использования. (второе использование более конкретно относится к свойствам, которые вы хотите, чтобы команда слушала.

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 голос
/ 11 сентября 2015

есть ли способ заставить CommandManager.InvalidateRequerySuggested () работать быстрее?

Да, есть способ заставить его работать быстрее!

  1. Реализация Command для хранения / кэширования CanExecuteState в логической переменной.
  2. Реализовать RaiseCanExecuteChanged метод для пересчета CanExecuteState и, если он действительно изменился, вызвать событие CanExecuteChanged.
  3. Реализуйте метод CanExecute, чтобы просто вернуть CanExecuteState.
  4. Когда вызывается метод InvalidateRequerySuggested Command подписчики будут читать только переменную CanExecuteState, вызывая метод CanExecute и проверять, изменился он или нет. Это почти ноль накладных расходов. Все Commands будут отключены / включены почти одновременно.
  5. Вся работа будет выполняться в методе RaiseCanExecuteChanged, который будет вызываться только один раз для Command и только для ограниченного набора Commands.
1 голос
/ 14 мая 2014

Я бы предложил рассмотреть ReactiveUI и, в частности, реализацию ICommand, которую он предоставляет, ReactiveCommand . Он использует иной подход, чем DelegateCommand / RelayCommand, которые реализованы с делегатами для CanExecute, которые должны активно оцениваться. Значение ReactiveCommand для CanExecute передается с использованием IObservables.

1 голос
/ 13 марта 2013

У Томаса есть хорошее решение, но, пожалуйста, обратите внимание, что есть серьезная ошибка в том, что CanExecute не всегда срабатывает при привязке к кнопке из-за этого:

// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
            EventHandler handler = callees[i];
            handler(null, EventArgs.Empty);
}

Параметр 'null', переданный в, вызывает проблемы с CanExecuteChangedEventManager (используется классом кнопки WPF для прослушивания изменений любой связанной с ним Команды). В частности, CanExecuteChangedEventManager поддерживает коллекцию слабых событий, которые необходимо вызвать, чтобы определить, является ли команда Can-Execute (), но эта коллекция, ключом «отправителя».

Исправление простое и работает для меня - измените подпись на

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

Извините, я не очень хорошо описал это - в некотором порыве, чтобы догнать моего разработчика сейчас, потратив на это несколько часов!

0 голосов
/ 20 февраля 2010

Просто чтобы уточнить:

  1. Вы хотите запустить обновление CanExecute, когда Command property changed
  2. Создайте свой собственный класс привязки, который обнаруживает изменения в Command property и затем вызывает RaiseCanExecuteChanged()
  3. Используйте эту привязку в CommandParameter

Работал на меня.

0 голосов
/ 20 февраля 2010

Попробуйте написать свою собственную привязку, которая вызывает ваш RaiseCanExecuteChanged () в конвертах? это легче

...