[英]Why is my ICommand.CanExecute(object) parameter null when I set CommandParameter to some Binding, but non-null when I set it to some static value?
[英]ICommand.CanExecute being passed null even though CommandParameter is set
我有一個棘手的問題,我將ContextMenu
綁定到一組ICommand
派生的對象,並通過樣式在每個MenuItem
上設置Command
和CommandParameter
屬性:
<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已經為您執行了此操作。
我相信這與此處記錄的連接問題有關:
我的解決方法如下:
在每個需要了解參數更改的命令中實現接口。
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 );
結果
如果InProgress
為true
則不啟用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.