1. ホーム
  2. wpf

[解決済み] WPFのCanExecuteを最初に呼び出すとCommandParameterがNULLになる。

2023-03-06 22:22:23

質問

WPF と、ItemsControl の DataTemplate 内のボタンにバインドされているコマンドで問題に遭遇しました。シナリオは非常に単純明快です。ItemsControl はオブジェクトのリストにバインドされており、ボタンをクリックすることで、リスト内の各オブジェクトを削除できるようにしたいのです。ボタンはコマンドを実行し、コマンドは削除を行う。CommandParameterは、削除したいオブジェクトにバインドされています。こうすることで、ユーザーが何をクリックしたのかがわかります。ユーザーは、自分自身のオブジェクトしか削除できないはずです。したがって、ユーザーが正しい権限を持っていることを確認するために、Command の "CanExecute" 呼び出しでいくつかのチェックを行う必要があります。

問題は、CanExecute に渡されるパラメーターは、最初に呼び出されたときは NULL であることです - したがって、コマンドを有効/無効にするロジックを実行できません。しかし、常に有効にし、ボタンをクリックしてコマンドを実行すると、CommandParameter は正しく渡されます。つまり、CommandParameterに対するバインディングは機能していることになります。

ItemsControlとDataTemplateのXAMLは以下のようになります。

<ItemsControl 
    x:Name="commentsList"
    ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
    Width="Auto" Height="Auto">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Button                             
                    Content="Delete"
                    FontSize="10"
                    Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}" 
                    CommandParameter="{Binding}" />
            </StackPanel>                       
         </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

ご覧の通り、Commentsオブジェクトのリストがあります。DeleteCommentCommandのCommandParameterはCommandオブジェクトにバインドされるようにしたいです。

私の質問は、この問題を経験したことがある人がいるかどうかということです。CanExecute は私の Command で呼び出されますが、パラメーターは初回では常に NULL です - なぜでしょうか?

更新しました。 少し問題を絞り込むことができました。空の Debug ValueConverter を追加して、CommandParameter がデータバインドされたときにメッセージを出力できるようにしました。問題はCanExecuteメソッドがCommandParameterがボタンにバインドされる前に実行されることであることがわかりました。私はCommandParameterをCommandの前に設定しようとしました(提案されたように) - しかし、それはまだうまくいきません。それを制御する方法についての任意のヒント。

更新2です。 バインディングがいつ"done"されたかを検出し、コマンドの再評価を強制できるような方法はありますか? また、コマンド オブジェクトの同じインスタンスにバインドする複数のボタン (ItemsControl の各アイテムに 1 つ) があることは問題でしょうか?

更新 3: 再現したバグをSkyDriveにアップロードしました。 http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip

どのように解決するのですか?

同じような問題に出くわしたので、信頼できる TriggerConverter を使って解決しました。

public class TriggerConverter : IMultiValueConverter
{
    #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        // First value is target value.
        // All others are update triggers only.
        if (values.Length < 1) return Binding.DoNothing;
        return values[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

この値変換器は、任意の数のパラメータを受け取り、そのうちの最初のパラメータを変換後の値として送り返します。あなたの場合のMultiBindingで使用すると、次のようになります。

<ItemsControl 
    x:Name="commentsList"
    ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
    Width="Auto" Height="Auto">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Button                             
                    Content="Delete"
                    FontSize="10"
                    CommandParameter="{Binding}">
                    <Button.Command>
                        <MultiBinding Converter="{StaticResource TriggerConverter}">
                            <Binding Path="DataContext.DeleteCommentCommand"
                                     ElementName="commentsList" />
                            <Binding />
                        </MultiBinding> 
                    </Button.Command>
                </Button>
            </StackPanel>                                       
         </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

これを動作させるには、TriggerConverterをリソースとしてどこかに追加する必要がある。これで、CommandParameterの値が利用可能になる前に、Commandプロパティが設定されるようになった。.ではなく、RelativeSource.SelfとCommandParameterにバインドしても同じ効果が得られます。