gpt4 book ai didi

wpf - CommandManager.InvalidateRequerySuggested() 不够快。我能做些什么?

转载 作者:行者123 更新时间:2023-12-03 08:35:39 27 4
gpt4 key购买 nike

短版

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

加长版

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

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

  • 它引发了多个 View 模型响应的“忙碌”事件(仍在 UI 线程上);当他们收到这个事件时,他们设置了 IsEnabled标记自己到false .我的 View 中的控件(数据绑定(bind)到此属性)立即变灰,这是我所期望的。
  • 它通知我的 WPF ICommand他们不应该被允许执行的对象(同样,仍然在 UI 线程上)。因为没有像 INotifyPropertyChanged 这样的东西对于 ICommand对象,我被迫调用CommandManager.InvalidateRequerySuggested()强制 WPF 重新考虑我所有的命令对象的 CanExecute状态(是的,我确实需要这样做:否则,这些控件都不会被禁用)。但是,与第 1 项不同的是,使用 ICommand 的按钮/菜单项/等需要更长的时间。与具有 IsEnabled 的 UI 控件相比,对象在视觉上更改为禁用状态属性手动设置。

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

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

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

    最佳答案

    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从棱镜,看起来像这样:
    /// <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扩展方法,将命令“绑定(bind)”到属性,并调用其 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 事件的标准接口(interface)):
    public interface INotifyPropertyChangedWithRaise : INotifyPropertyChanged
    {
    void OnPropertyChanged(string propertyName);
    }

    最后一 block 拼图是这样的:
    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!
    }
    })
    );
    }
    }
    }

    关于wpf - CommandManager.InvalidateRequerySuggested() 不够快。我能做些什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1751966/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com