web-dev-qa-db-ja.com

CommandManager.InvalidateRequerySuggested()は十分な速度ではありません。私に何ができる?

ショートバージョン

CommandManager.InvalidateRequerySuggested()の呼び出しは、有効になるまでに私が望むよりもはるかに長い時間がかかります(UIコントロールが無効になるまでに1〜2秒の遅延)。

ロングバージョン

バックグラウンドスレッドベースのタスクプロセッサにタスクを送信するシステムがあります。この送信は、WPFUIスレッドで行われます。

この送信が発生すると、バックグラウンドスレッドを管理するオブジェクトは2つのことを行います。

  1. これは、いくつかのビューモデルが応答する「ビジー」イベント(まだUIスレッド上にある)を発生させます。このイベントを受信すると、彼らは自分たちにIsEnabledフラグをfalseに設定します。このプロパティにデータバインドされているビューのコントロールはすぐにグレー表示されます。これは私が期待することです。

  2. これは、WPF ICommandオブジェクトに、実行を許可してはならないことを通知します(これも、UIスレッド上にあります)。 INotifyPropertyChangedオブジェクトにはICommandのようなものがないため、CommandManager.InvalidateRequerySuggested()を呼び出して、WPFにすべてのコマンドオブジェクトのCanExecute状態を再検討させる必要があります(はい、実際には必要ですこれを行うには:それ以外の場合、これらのコントロールはいずれも無効になりません)。ただし、項目1とは異なり、ICommandオブジェクトを使用しているボタン/メニュー項目などが視覚的に無効状態に変わるには、IsEnabledプロパティが手動で設定されているUIコントロールよりもかなり長い時間がかかります。

問題は、UXの観点からすると、これは次のように見えることですひどい;コントロールの半分はすぐにグレー表示され(IsEnabledプロパティがfalseに設定されているため)、1〜2秒後に、コントロールの残りの半分がそれに続きます(CanExecuteメソッドが最終的に再評価されるため)。

それで、私の質問のパート1:
質問するのはばかげているように聞こえますが、CommandManager.InvalidateRequerySuggested()に仕事を早くさせる方法はありますか?ないのではないかと思います。

まあまあ、私の質問のパート2:
これを回避するにはどうすればよいですか?すべてのコントロールを同時に無効にしたいと思います。それ以外の場合は、専門的ではなく、ぎこちなく見えます。何か案は? :-)

32
Rob

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!
                    }
                })
            );
        }
    }
}
58
Tomáš Kafka

このソリューションは、このスレッドでTomášKafkaによって提案されたソリューションの縮小版です(彼のソリューションを詳細に説明してくれたTomasに感謝します)。

Tomasのソリューションでは1)DelegateCommand 2)CommandManagerHelper 3)DelegateCommandExtensions 4)NotifyPropertyChangedBaseExtensions 5)INotifyPropertyChangedWithRaise 6)ThreadTools

このソリューションには1)DelegateCommand 2)DelegateCommand自体のDelegateCommandExtensionsメソッドとNotifyPropertyChangedBaseExtensionsメソッドがあります。

wpfアプリケーションはMVVMパターンに従い、UIスレッドで実行されるviewmodelレベルでコマンドを処理するため、UIdisptacherへの参照を取得する必要はありません。 。

   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」に登録されます。「CanExecuteChanged」のIcommand実装がCommandManagerのRequesySuggestイベントにフックする場合(この記事を読む http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/ )その後、CommandManagerがコマンドの実行能力を変更する可能性のある条件を検出するたびに(フォーカスシフトや一部のキーボードイベントなどの変更)、CommandManagerのRequerySuggestedイベントが発生します。これにより、DelegateCommandの「CanExecuteChanged」の実装でボタンのデリゲートをCommandManagerのRequerySuggestedにフックしたため、Button'eデリゲートが呼び出されます。

ただし、問題は、ComandManagerが常に変更を検出できるとは限らないことです。したがって、コマンド実装(DelegateCommand)が変更を検出したときに「CanExecuteChanged」を発生させるという解決策です。通常、ビューモデルでICommandのCanExecuteの遅延を宣言すると、ビューモデルで宣言されたプロパティにバインドされ、ICommand実装は「これらのプロパティの「propertychanged」イベント。これが、DelegateCommandの「ListenForNotificationFrom」メソッドが行うことです。クライアントコードが特定のプロパティ変更に登録されていない場合、DelegateCommandはデフォルトで、コマンドが宣言および定義されているビューモデルのプロパティ変更をリッスンします。

ボタンの「CanExecuteChangeEventHandler」を格納するEventHandlerのリストであるDelegateCommandの「ControlEvent」は、メモリリークを回避するために弱参照として宣言されています。

ViewModelはこのDelegateCommandをどのように使用しますか2つの使用方法があります。 (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));
    }

}

8
Sankalp Saxena

Tomasには素晴らしい解決策がありますが、これが原因で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 Buttonクラスがそれにバインドされたコマンドの変更をリッスンするために使用します)。具体的には、CanExecuteChangedEventManagerは、コマンドCan-Execute()かどうかを判断するために呼び出す必要のある弱いイベントのコレクションを維持しますが、このコレクションは「送信者」によってキー設定されます。

修正は簡単で、私にとってはうまくいきます-署名をに変更します

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

申し訳ありませんが、私はそれをあまりよく説明していません-これを理解するために数時間かかった後、今私の開発者に追いつくために少し急いで!

1
nchaud

commandManager.InvalidateRequerySuggested()の作業を高速化する方法はありますか?

はい、それをより速く動作させる方法があります!

  1. Commandを実装して、ブール変数にCanExecuteStateを保持/キャッシュします。
  2. RaiseCanExecuteChangedメソッドを実装してCanExecuteStateを再計算し、それが実際に変更されてCanExecuteChangedイベントを発生させたかどうか。
  3. CanExecuteメソッドを実装して、単にCanExecuteStateを返します。
  4. InvalidateRequerySuggestedメソッドが呼び出されると、CommandサブスクライバーはCanExecuteStateメソッドを呼び出してCanExecute変数を読み取り、それが変更されたかどうかを確認するだけです。それはほとんどゼロのオーバーヘッドです。すべてのCommandsはほぼ同時に無効/有効になります。
  5. すべての作業はRaiseCanExecuteChangedメソッドで実行され、Commandに対して1回だけ呼び出され、Commandsの限られたセットに対してのみ呼び出されます。
1
Lightman

ReactiveUI 、具体的にはそれが提供するICommand実装、 ReactiveCommand を調べることをお勧めします。これは、アクティブに評価する必要があるCanExecuteのデリゲートで実装されるDelegateCommand/RelayCommandとは異なるアプローチを使用します。 CanExecuteのReactiveCommandの値は、IObservablesを使用してプッシュされます。

1
Wouter

変換内でRaiseCanExecuteChanged()を呼び出す独自のバインディングを作成してみてください。それは容易です

0
Angry Chipmonk

明確にするために:

  1. _Command property changed_のときにCanExecuteの更新を起動したい
  2. _Command property_の変更を検出し、RaiseCanExecuteChanged()を呼び出す独自のバインディングクラスを作成します
  3. このバインディングをCommandParameterで使用します

私のために働いた。

0
Angry Chipmonk