【问题标题】:CommandManager.InvalidateRequerySuggested() isn't fast enough. What can I do?CommandManager.InvalidateRequerySuggested() 不够快。我能做些什么?
【发布时间】:2010-12-17 15:23:54
【问题描述】:

短版

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

加长版

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

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

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

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

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

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

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

【问题讨论】:

    标签: wpf icommand


    【解决方案1】:

    CommandManager.InvalidateRequerySuggested() 尝试验证所有命令,这完全无效(在您的情况下很慢) - 在每次更改时,您都要求 每个命令 重新检查其 CanExecute()

    您需要该命令来了解其 CanExecute 依赖于哪些对象和属性,并建议仅在它们更改时重新查询。那样的话,如果你改变一个对象的属性,只有依赖它的命令才会改变它们的状态。

    这就是我解决问题的方法,但首先是一个预告片:

    // in ViewModel's constructor - add a code to public ICommand:
    this.DoStuffWithParameterCommand = new DelegateCommand<object>(
        parameter =>
            {
                //do work with parameter (remember to check against null)
            },
        parameter => 
            {
                //can this command execute? return true or false
            }
        )
        .ListenOn(whichObject, n => n.ObjectProperty /*type safe!*/, this.Dispatcher /*we need to pass UI dispatcher here*/)
        .ListenOn(anotherObject, n => n.AnotherObjectProperty, this.Dispatcher); // chain calling!
    

    该命令正在侦听来自对象的NotifyPropertyChanged 事件,这些事件会影响它是否可以执行,并且仅在需要重新查询时才调用检查。

    现在,有很多代码(我们内部框架的一部分)来执行此操作:

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

    /// <summary>
    ///     This class allows delegating the commanding logic to methods passed as parameters,
    ///     and enables a View to bind commands to objects that are not part of the element tree.
    /// </summary>
    public class DelegateCommand : ICommand
    {
        #region Constructors
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action executeMethod)
            : this(executeMethod, null, false)
        {
        }
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
            : this(executeMethod, canExecuteMethod, false)
        {
        }
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
        {
            if (executeMethod == null)
            {
                throw new ArgumentNullException("executeMethod");
            }
    
            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
            _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    
            this.RaiseCanExecuteChanged();
        }
    
        #endregion
    
        #region Public Methods
    
        /// <summary>
        ///     Method to determine if the command can be executed
        /// </summary>
        public bool CanExecute()
        {
            if (_canExecuteMethod != null)
            {
                return _canExecuteMethod();
            }
            return true;
        }
    
        /// <summary>
        ///     Execution of the command
        /// </summary>
        public void Execute()
        {
            if (_executeMethod != null)
            {
                _executeMethod();
            }
        }
    
        /// <summary>
        ///     Property to enable or disable CommandManager's automatic requery on this command
        /// </summary>
        public bool IsAutomaticRequeryDisabled
        {
            get
            {
                return _isAutomaticRequeryDisabled;
            }
            set
            {
                if (_isAutomaticRequeryDisabled != value)
                {
                    if (value)
                    {
                        CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
                    }
                    else
                    {
                        CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
                    }
                    _isAutomaticRequeryDisabled = value;
                }
            }
        }
    
        /// <summary>
        ///     Raises the CanExecuteChaged event
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }
    
        /// <summary>
        ///     Protected virtual method to raise CanExecuteChanged event
        /// </summary>
        protected virtual void OnCanExecuteChanged()
        {
            CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
        }
    
        #endregion
    
        #region ICommand Members
    
        /// <summary>
        ///     ICommand.CanExecuteChanged implementation
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (!_isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested += value;
                }
                CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
            }
            remove
            {
                if (!_isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested -= value;
                }
                CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
            }
        }
    
        bool ICommand.CanExecute(object parameter)
        {
            return CanExecute();
        }
    
        void ICommand.Execute(object parameter)
        {
            Execute();
        }
    
        #endregion
    
        #region Data
    
        private readonly Action _executeMethod = null;
        private readonly Func<bool> _canExecuteMethod = null;
        private bool _isAutomaticRequeryDisabled = false;
        private List<WeakReference> _canExecuteChangedHandlers;
    
        #endregion
    }
    
    /// <summary>
    ///     This class allows delegating the commanding logic to methods passed as parameters,
    ///     and enables a View to bind commands to objects that are not part of the element tree.
    /// </summary>
    /// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
    public class DelegateCommand<T> : ICommand
    {
        #region Constructors
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action<T> executeMethod)
            : this(executeMethod, null, false)
        {
        }
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
            : this(executeMethod, canExecuteMethod, false)
        {
        }
    
        /// <summary>
        ///     Constructor
        /// </summary>
        public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
        {
            if (executeMethod == null)
            {
                throw new ArgumentNullException("executeMethod");
            }
    
            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
            _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
        }
    
        #endregion
    
        #region Public Methods
    
        /// <summary>
        ///     Method to determine if the command can be executed
        /// </summary>
        public bool CanExecute(T parameter)
        {
            if (_canExecuteMethod != null)
            {
                return _canExecuteMethod(parameter);
            }
            return true;
        }
    
        /// <summary>
        ///     Execution of the command
        /// </summary>
        public void Execute(T parameter)
        {
            if (_executeMethod != null)
            {
                _executeMethod(parameter);
            }
        }
    
        /// <summary>
        ///     Raises the CanExecuteChaged event
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged();
        }
    
        /// <summary>
        ///     Protected virtual method to raise CanExecuteChanged event
        /// </summary>
        protected virtual void OnCanExecuteChanged()
        {
            CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
        }
    
        /// <summary>
        ///     Property to enable or disable CommandManager's automatic requery on this command
        /// </summary>
        public bool IsAutomaticRequeryDisabled
        {
            get
            {
                return _isAutomaticRequeryDisabled;
            }
            set
            {
                if (_isAutomaticRequeryDisabled != value)
                {
                    if (value)
                    {
                        CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
                    }
                    else
                    {
                        CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
                    }
                    _isAutomaticRequeryDisabled = value;
                }
            }
        }
    
        #endregion
    
        #region ICommand Members
    
        /// <summary>
        ///     ICommand.CanExecuteChanged implementation
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (!_isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested += value;
                }
                CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
            }
            remove
            {
                if (!_isAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested -= value;
                }
                CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
            }
        }
    
        bool ICommand.CanExecute(object parameter)
        {
            // if T is of value type and the parameter is not
            // set yet, then return false if CanExecute delegate
            // exists, else return true
            if (parameter == null &&
                typeof(T).IsValueType)
            {
                return (_canExecuteMethod == null);
            }
            return CanExecute((T)parameter);
        }
    
        void ICommand.Execute(object parameter)
        {
            Execute((T)parameter);
        }
    
        #endregion
    
        #region Data
    
        private readonly Action<T> _executeMethod = null;
        private readonly Func<T, bool> _canExecuteMethod = null;
        private bool _isAutomaticRequeryDisabled = false;
        private List<WeakReference> _canExecuteChangedHandlers;
    
        #endregion
    }
    
    /// <summary>
    ///     This class contains methods for the CommandManager that help avoid memory leaks by
    ///     using weak references.
    /// </summary>
    internal class CommandManagerHelper
    {
        internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
        {
            if (handlers != null)
            {
                // Take a snapshot of the handlers before we call out to them since the handlers
                // could cause the array to me modified while we are reading it.
    
                EventHandler[] callees = new EventHandler[handlers.Count];
                int count = 0;
    
                for (int i = handlers.Count - 1; i >= 0; i--)
                {
                    WeakReference reference = handlers[i];
                    EventHandler handler = reference.Target as EventHandler;
                    if (handler == null)
                    {
                        // Clean up old handlers that have been collected
                        handlers.RemoveAt(i);
                    }
                    else
                    {
                        callees[count] = handler;
                        count++;
                    }
                }
    
                // Call the handlers that we snapshotted
                for (int i = 0; i < count; i++)
                {
                    EventHandler handler = callees[i];
                    handler(null, EventArgs.Empty);
                }
            }
        }
    
        internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
        {
            if (handlers != null)
            {
                foreach (WeakReference handlerRef in handlers)
                {
                    EventHandler handler = handlerRef.Target as EventHandler;
                    if (handler != null)
                    {
                        CommandManager.RequerySuggested += handler;
                    }
                }
            }
        }
    
        internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
        {
            if (handlers != null)
            {
                foreach (WeakReference handlerRef in handlers)
                {
                    EventHandler handler = handlerRef.Target as EventHandler;
                    if (handler != null)
                    {
                        CommandManager.RequerySuggested -= handler;
                    }
                }
            }
        }
    
        internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
        {
            AddWeakReferenceHandler(ref handlers, handler, -1);
        }
    
        internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
        {
            if (handlers == null)
            {
                handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
            }
    
            handlers.Add(new WeakReference(handler));
        }
    
        internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
        {
            if (handlers != null)
            {
                for (int i = handlers.Count - 1; i >= 0; i--)
                {
                    WeakReference reference = handlers[i];
                    EventHandler existingHandler = reference.Target as EventHandler;
                    if ((existingHandler == null) || (existingHandler == handler))
                    {
                        // Clean up old handlers that have been collected
                        // in addition to the handler that is to be removed.
                        handlers.RemoveAt(i);
                    }
                }
            }
        }
    }
    

    然后我编写了一个ListenOn 扩展方法,将命令“绑定”到一个属性,并调用它的RaiseCanExecuteChanged

    public static class DelegateCommandExtensions
    {
        /// <summary>
        /// Makes DelegateCommnand listen on PropertyChanged events of some object,
        /// so that DelegateCommnand can update its IsEnabled property.
        /// </summary>
        public static DelegateCommand ListenOn<ObservedType, PropertyType>
            (this DelegateCommand delegateCommand, 
            ObservedType observedObject, 
            Expression<Func<ObservedType, PropertyType>> propertyExpression,
            Dispatcher dispatcher)
            where ObservedType : INotifyPropertyChanged
        {
            //string propertyName = observedObject.GetPropertyName(propertyExpression);
            string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression);
    
            observedObject.PropertyChanged += (sender, e) =>
            {
                if (e.PropertyName == propertyName)
                {
                    if (dispatcher != null)
                    {
                        ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged);
                    }
                    else
                    {
                        delegateCommand.RaiseCanExecuteChanged();
                    }
                }
            };
    
            return delegateCommand; //chain calling
        }
    
        /// <summary>
        /// Makes DelegateCommnand listen on PropertyChanged events of some object,
        /// so that DelegateCommnand can update its IsEnabled property.
        /// </summary>
        public static DelegateCommand<T> ListenOn<T, ObservedType, PropertyType>
            (this DelegateCommand<T> delegateCommand, 
            ObservedType observedObject, 
            Expression<Func<ObservedType, PropertyType>> propertyExpression,
            Dispatcher dispatcher)
            where ObservedType : INotifyPropertyChanged
        {
            //string propertyName = observedObject.GetPropertyName(propertyExpression);
            string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression);
    
            observedObject.PropertyChanged += (object sender, PropertyChangedEventArgs e) =>
            {
                if (e.PropertyName == propertyName)
                {
                    if (dispatcher != null)
                    {
                        ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged);
                    }
                    else
                    {
                        delegateCommand.RaiseCanExecuteChanged();
                    }
                }
            };
    
            return delegateCommand; //chain calling
        }
    }
    

    然后您需要将以下扩展名添加到NotifyPropertyChanged

        /// <summary>
    /// <see cref="http://dotnet.dzone.com/news/silverlightwpf-implementing"/>
    /// </summary>
    public static class NotifyPropertyChangedBaseExtensions
    {
        /// <summary>
        /// Raises PropertyChanged event.
        /// To use: call the extension method with this: this.OnPropertyChanged(n => n.Title);
        /// </summary>
        /// <typeparam name="T">Property owner</typeparam>
        /// <typeparam name="TProperty">Type of property</typeparam>
        /// <param name="observableBase"></param>
        /// <param name="expression">Property expression like 'n => n.Property'</param>
        public static void OnPropertyChanged<T, TProperty>(this T observableBase, Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChangedWithRaise
        {
            observableBase.OnPropertyChanged(GetPropertyName<T, TProperty>(expression));
        }
    
        public static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChanged
        {
            if (expression == null)
                throw new ArgumentNullException("expression");
    
            var lambda = expression as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }
    
            if (memberExpression == null)
                throw new ArgumentException("Please provide a lambda expression like 'n => n.PropertyName'");
    
            MemberInfo memberInfo = memberExpression.Member;
    
            if (String.IsNullOrEmpty(memberInfo.Name))
                throw new ArgumentException("'expression' did not provide a property name.");
    
            return memberInfo.Name;
        }
    }
    

    INotifyPropertyChangedWithRaise 在哪里(它建立了引发 NotifyPropertyChanged 事件的标准接口):

    public interface INotifyPropertyChangedWithRaise : INotifyPropertyChanged
    {
        void OnPropertyChanged(string propertyName);
    }
    

    最后一块拼图是这样的:

    public class ThreadTools
    {
        public static void RunInDispatcher(Dispatcher dispatcher, Action action)
        {
            RunInDispatcher(dispatcher, DispatcherPriority.Normal, action);
        }
    
            public static void RunInDispatcher(Dispatcher dispatcher, DispatcherPriority priority, Action action)
        {
            if (action == null) { return; }
    
            if (dispatcher.CheckAccess())
            {
                // we are already on thread associated with the dispatcher -> just call action
                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    //Log error here!
                }
            }
            else
            {
                // we are on different thread, invoke action on dispatcher's thread
                dispatcher.BeginInvoke(
                    priority,
                    (Action)(
                    () =>
                    {
                        try
                        {
                            action();
                        }
                        catch (Exception ex)
                        {
                            //Log error here!
                        }
                    })
                );
            }
        }
    }
    

    【讨论】:

    • 哇!非常感谢你,这看起来非常有帮助。我迫不及待想尝试一下:-)!
    • 不客气,这是相当多的代码,但是您可以使用 OnPropertyChanged 扩展方法来引发 NotifyPropertyChanged - 例如:this.OnPropertyChanged(n => n.MyProperty)。一个有问题的一点是,在某些情况下,监听事件的命令可能会导致内存泄漏——它应该使用一些弱事件监听器而不是observedObject.PropertyChanged += ...,但我还没有时间了解所有.net 内部结构参与其中。
    • +1,很棒的答案!我特别喜欢ListenOn 的想法,它非常聪明优雅
    • @TomášKafka 似乎是一个很好的解决方案,但如果我只对我的一个命令调用 RaiseCanExecuteChanged,那么所有其他命令也会更新,即它看起来与 InvalidateRequerySuggested 的作用相同。知道会发生什么吗?我不使用 ListenOn,而只是在 UI 调度程序上调用 RaiseCanExecuteChanged。
    【解决方案2】:

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

    在托马斯的解决方案中,他有 1) 委托命令 2) CommandManagerHelper 3) 委托命令扩展 4) NotifyPropertyChangedBaseExtensions 5) INotifyPropertyChangedWithRaise 6) 线程工具

    这个解决方案有 1) 委托命令 2) Delegate Command 本身的 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 事件(阅读本文@ 987654321@) 那么当 CommandManager 检测到可能会改变命令执行能力的条件(如焦点转移和一些键盘事件等变化)时,就会发生 CommandManager 的 RequerySuggested 事件,这反过来会导致 Button'e 委托被调用,因为我们挂钩了buttos 对 CommandManager 的 Requery 的委托在我们的 DelegateCommand 中执行“CanExecuteChanged”时建议。

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

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

    ViewModel 将如何使用这个 DelegateCommand 有两种使用方法。 (第二种用法更具体到您希望 Command 侦听的属性。

    delegateCommand = new DelegateCommand
                                          {
                                              ExecuteDelegate = Search,
                                              CanExecuteDelegate = (r) => !IsBusy
                                          };
    
              anotherDelegateCommand = new DelegateCommand
                                          {
                                              ExecuteDelegate = SearchOne,
                                              CanExecuteDelegate = (r) => !IsBusyOne
                                          }.ListenOn(this, n => n.IsBusyOne);
    

    详细的视图模型

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

    }

    【讨论】:

    • 我正在使用您的解决方案,但在某些我不完全了解的情况下,它不起作用。无论如何,我必须调用 InvalidateRequerySuggested。您知道什么会阻止您的解决方案发挥作用吗?
    【解决方案3】:

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

    抱歉,我描述得不够好 - 在花了几个小时来解决这个问题后,现在有点急于赶上我的开发人员!

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 我想过这个,但我得出的结论是,我根本不希望引发该事件,而不仅仅是将其过滤掉并且不处理它。
      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          澄清一下:

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

          为我工作。

          【讨论】:

            猜你喜欢
            • 2013-04-19
            • 1970-01-01
            • 2013-01-18
            • 1970-01-01
            • 2019-09-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-20
            相关资源
            最近更新 更多