簡體   English   中英

即使設置了CommandParameter,ICommand.CanExecute也會傳遞null

[英]ICommand.CanExecute being passed null even though CommandParameter is set

我有一個棘手的問題,我將ContextMenu綁定到一組ICommand派生的對象,並通過樣式在每個MenuItem上設置CommandCommandParameter屬性:

<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。 我已經檢查過並且在調用之前正確地實例化了所選的notes集合(實際上它在其聲明中被賦值,因此它永遠不為null )。 我無法弄清楚CanEvaluate為何被傳遞為null

我已經確定ContextMenu中至少存在兩個錯誤導致其CanExecute調用在不同情況下不可靠。 設置命令時立即調用CanExecute。 以后的電話是不可預測的,當然不可靠。

我花了整整一夜的時間試圖找出它失敗的確切條件並尋找解決方法。 最后,我放棄並切換到觸發了所需命令的Click處理程序。

我確定我的一個問題是更改ContextMenu的DataContext會導致在綁定新的Command或CommandParameter之前調用CanExecute。

我知道這個問題的最佳解決方案是使用自己的Command和CommandBinding附加屬性,而不是使用內置的屬性:

  • 設置附加的Command屬性后,訂閱MenuItem上的Click和DataContextChanged事件,並訂閱CommandManager.RequerySuggested。

  • 當DataContext更改,RequerySuggested進入,或者您的兩個附加屬性發生更改時,使用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處理中的錯誤所帶來的所有問題。

希望有一天微軟將解決ContextMenu的問題,這種解決方法將不再是必要的。 我有一個責備案例坐在這里,我打算提交給Connect。 也許我應該接受球並且實際上做到了。

什么是RequerySuggested,為什么要使用它?

RequerySuggested機制是RoutedCommand有效處理ICommand.CanExecuteChanged的方法。 在非RoutedCommand世界中,每個ICommand都有自己的CanExecuteChanged訂戶列表,但對於RoutedCommand,任何訂閱ICommand.CanExecuteChanged的客戶端實際上都會訂閱CommandManager.RequerySuggested。 這個更簡單的模型意味着只要RoutedCommand的CanExecute可能發生變化,所有必要的就是調用CommandManager.InvalidateRequerySuggested(),它將執行與發出ICommand.CanExecuteChanged相同的功能,但同時為所有RoutedCommands和后台線程執行此操作。 此外,RequerySuggested調用組合在一起,因此如果發生許多更改,CanExecute只需要調用一次。

我建議您訂閱CommandManager.RequerySuggested而不是ICommand.CanExecuteChanged的原因是:1。每次Command附加屬性的值更改時,您不需要代碼來刪除舊訂閱並添加新訂閱,以及2。 CommandManager.RequerySuggested內置了一個弱引用功能,允許您設置事件處理程序並仍然是垃圾回收。 對ICommand執行相同操作需要您實現自己的弱引用機制。

另一方面,如果您訂閱CommandManager.RequerySuggested而不是ICommand.CanExecuteChanged,那么您將只獲得RoutedCommands的更新。 我只使用RoutedCommands這對我來說不是問題,但我應該提到,如果你經常使用常規ICommands,你應該考慮做一些弱訂閱ICommand.CanExecutedChanged的額外工作。 請注意,如果這樣做,您也不需要訂閱RequerySuggested,因為RoutedCommand.add_CanExecutedChanged已經為您執行了此操作。

我相信這與此處記錄的連接問題有關:

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。

我在DataGrid上遇到了這種情況,我需要上下文菜單來識別是否根據所選行啟用或禁用特定命令。 我發現是的,傳遞給命令的對象是null,並且只對所有行執行一次,無論是否有更改。

我所做的是在特定命令上調用RaiseCanExecuteChanged ,這將觸RaiseCanExecuteChanged格選擇更改事件中的啟用或禁用。


private void MyGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    VM.DeleteItem.RaiseCanExecuteChanged();
}

命令綁定分配

VM.DeleteItem 
    = new OperationCommand((o) => MessageBox.Show("Delete Me"),
                           (o) => (myGrid.SelectedItem as Order)?.InProgress == false );

結果

如果InProgresstrue則不啟用delete命令

在此輸入圖像描述

XAML

<DataGrid AutoGenerateColumns="True"
        Name="myGrid"
        ItemsSource="{Binding Orders}"
        SelectionChanged="MyGrid_OnSelectionChanged">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Copy"   Command="{Binding CopyItem}"/>
            <MenuItem Header="Delete" Command="{Binding DeleteItem}" />
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM