33

短縮版

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

ロングバージョン

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

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

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

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

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

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

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

4

7 に答える 7

59

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、実行できるかどうかに影響するオブジェクトからのイベントをリッスンし、再クエリが必要な場合にのみチェックを呼び出します。

さて、これを行うための多くのコード(社内フレームワークの一部):

私はDelegateCommandPrismから使用していますが、これは次のようになります。

/// <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!
                    }
                })
            );
        }
    }
}
于 2009-12-07T03:16:43.217 に答える
9

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

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

このソリューションには、 1)DelegateCommand 2)DelegateCommandExtensionsメソッドとDelegateCommand自体の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イベントが発生し、「CanExecuteChanged」の実装でボタンのデリゲートをCommandManagerのRequerySuggestedにフックしたため、Button'eデリゲートが呼び出されます。DelegateCommandで。

ただし、問題は、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));
    }

}

于 2011-03-31T16:03:32.463 に答える
1

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

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

于 2013-03-13T11:58:02.333 に答える
1

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

于 2014-05-14T10:30:56.750 に答える
1

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

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

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

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

于 2010-02-20T19:18:55.110 に答える
0

明確にするために:

  1. CanExecuteいつの更新を起動したいCommand property changed
  2. Command propertyの変更を検出してから呼び出す独自のバインディングクラスを作成しますRaiseCanExecuteChanged()
  3. このバインディングをで使用するCommandParameter

私のために働いた。

于 2010-02-20T19:32:32.230 に答える