10

派生オブジェクトContextMenuのセットに をバインドし、スタイルを介してそれぞれにおよびプロパティを設定するというトリッキーな問題があります。ICommandCommandCommandParameterMenuItem

<ContextMenu
    ItemsSource="{Binding Source={x:Static OrangeNote:Note.MultiCommands}}">
    <ContextMenu.Resources>
        <Style
            TargetType="MenuItem">
            <Setter
                Property="Header"
                Value="{Binding Path=Title}" />
            <Setter
                Property="Command"
                Value="{Binding}" />
            <Setter
                Property="CommandParameter"
                Value="{Binding Source={x:Static OrangeNote:App.Screen}, Path=SelectedNotes}" />
...

ただし、ICommand.Execute( object )必要に応じて選択されたメモのセットが渡されICommand.CanExecute( object )ますが (メニューの作成時に呼び出されます)、null が渡されます。確認したところ、選択したメモ コレクションは、呼び出しが行われる前に適切にインスタンス化されています (実際には、宣言で値が割り当てられているため、決して ではありませんnull)。CanEvaluate が渡される理由がわかりませんnull

4

3 に答える 3

11

私は、ContextMenu に少なくとも 2 つのバグがあり、さまざまな状況で CanExecute 呼び出しが信頼できないものになっていると判断しました。Command が設定されると、すぐに CanExecute を呼び出します。その後の呼び出しは予測不可能であり、確かに信頼できません。

失敗する正確な条件を追跡し、回避策を探して、一晩中過ごしました。最後に、私はあきらめて、目的のコマンドを起動する Click ハンドラーに切り替えました。

私の問題の 1 つは、ContextMenu の DataContext を変更すると、新しい Command または CommandParameter がバインドされる前に CanExecute が呼び出される可能性があることであると判断しました。

この問題に対する私が知っている最善の解決策は、組み込みのプロパティを使用する代わりに、Command および CommandBinding に独自の添付プロパティを使用することです。

  • 添付の Command プロパティが設定されている場合は、MenuItem の Click イベントと DataContextChanged イベントをサブスクライブし、CommandManager.RequerySuggested もサブスクライブします。

  • DataContext が変更された場合、RequerySuggested が入った場合、または 2 つの添付プロパティのいずれかが変更された場合、Dispatcher.BeginInvoke を使用してディスパッチャ操作をスケジュールします。これにより、CanExecute() が呼び出され、MenuItem で IsEnabled が更新されます。

  • Click イベントが発生したら、CanExecute を実行し、合格した場合は Execute() を呼び出します。

使用法は通常の Command および CommandParameter と同じですが、代わりに添付プロパティを使用します。

<Setter Property="my:ContexrMenuFixer.Command" Value="{Binding}" />
<Setter Property="my:ContextMenuFixer.CommandParameter" Value="{Binding Source=... }" />

このソリューションは機能し、ContextMenu の CanExecute 処理のバグに関するすべての問題を回避します。

いつか Microsoft が ContextMenu の問題を修正し、この回避策が不要になることを願っています。Connect に提出する予定の再現ケースがここのどこかにあります。おそらく、私はボールに乗って実際にやるべきです。

RequerySuggested とは何ですか? なぜ使用するのですか?

RequerySuggested メカニズムは、ICommand.CanExecuteChanged を効率的に処理する RoutedCommand の方法です。非 RoutedCommand の世界では、各 ICommand は CanExecuteChanged へのサブスクライバーの独自のリストを持っていますが、RoutedCommand の場合、ICommand.CanExecuteChanged をサブスクライブするすべてのクライアントは、実際には CommandManager.RequerySuggested をサブスクライブします。この単純なモデルは、RoutedCommand の CanExecute が変更される可能性があるときはいつでも、CommandManager.InvalidateRequerySuggested() を呼び出すだけでよいことを意味します。これは、ICommand.CanExecuteChanged を起動するのと同じことを行いますが、すべての RoutedCommand に対して同時にバックグラウンド スレッドで行います。さらに、RequerySuggested 呼び出しは結合されるため、多数の変更が発生した場合でも、CanExecute を 1 回呼び出すだけで済みます。

ICommand.CanExecuteChanged の代わりに CommandManager.RequerySuggested をサブスクライブすることをお勧めする理由は次のとおりです。 CommandManager.RequerySuggested には弱い参照機能が組み込まれており、イベント ハンドラーを設定してもガベージ コレクションを実行できます。ICommand で同じことを行うには、独自の弱参照メカニズムを実装する必要があります。

これの裏返しとして、ICommand.CanExecuteChanged の代わりに CommandManager.RequerySuggested をサブスクライブすると、RoutedCommands の更新のみが取得されるということです。私は RoutedCommands のみを使用しているので、これは私にとっては問題ではありませんが、通常の ICommands を使用する場合は、ICommand.CanExecutedChanged を弱くサブスクライブするという余分な作業を行うことを検討する必要があることを言及しておく必要がありました。これを行う場合、RoutedCommand.add_CanExecutedChanged が既にこれを行っているため、RequerySuggested もサブスクライブする必要はありません。

于 2010-06-12T02:40:51.317 に答える
10

これは、ここに記録された接続の問題に関連していると思います。

https://connect.microsoft.com/VisualStudio/feedback/details/504976/command-canexecute-still-not-requeried-after-commandparameter-change?wa=wsignin1.0

私の回避策は次のとおりです。

  1. バインドされたコマンド パラメーターに依存関係プロパティが添付された静的クラスを作成する
  2. カスタム コマンドで CanExecuteChanged を手動で発生させるためのカスタム インターフェイスを作成する
  3. パラメータの変更を知る必要がある各コマンドにインターフェイスを実装します。

    public interface ICanExecuteChanged : ICommand
    {
        void RaiseCanExecuteChanged();
    }
    
    public static class BoundCommand
    {
        public static object GetParameter(DependencyObject obj)
        {
            return (object)obj.GetValue(ParameterProperty);
        }
    
        public static void SetParameter(DependencyObject obj, object value)
        {
            obj.SetValue(ParameterProperty, value);
        }
    
        public static readonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached("Parameter", typeof(object), typeof(BoundCommand), new UIPropertyMetadata(null, ParameterChanged));
    
        private static void ParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as ButtonBase;
            if (button == null)
            {
                return;
            }
    
            button.CommandParameter = e.NewValue;
            var cmd = button.Command as ICanExecuteChanged;
            if (cmd != null)
            {
                cmd.RaiseCanExecuteChanged();
            }
        }
    }
    

コマンドの実装:

    public class MyCustomCommand : ICanExecuteChanged
    {
        public void Execute(object parameter)
        {
            // Execute the command
        }

        public bool CanExecute(object parameter)
        {
            Debug.WriteLine("Parameter changed to {0}!", parameter);
            return parameter != null;
        }

        public event EventHandler CanExecuteChanged;

        public void RaiseCanExecuteChanged()
        {
            EventHandler temp = this.CanExecuteChanged;
            if (temp != null)
            {
                temp(this, EventArgs.Empty);
            }
        }
    }

Xaml の使用法:

    <Button Content="Save"
        Command="{Binding SaveCommand}"
        my:BoundCommand.Parameter="{Binding Document}" />

これは私が思いついた最も簡単な修正であり、MVVM スタイルの実装でうまく機能します。また、BoundCommand パラメーターの変更で CommandManager.InvalidateRequerySuggested() を呼び出して、RoutedCommands でも機能するようにすることもできます。

于 2011-02-03T22:14:50.377 に答える