C# 将菜单项绑定到ICommand CanExecute时ArgumentNullException

C# 将菜单项绑定到ICommand CanExecute时ArgumentNullException,c#,wpf,mvvm,commandbinding,C#,Wpf,Mvvm,Commandbinding,我正试图根据ObservableCollection中的对象禁用菜单项 MainViewModel: public ObservableCollection<ThumbnailModel> Thumbnails { get; set; } public MainWindowViewModel() { Thumbnails = new ObservableCollection<ThumbnailModel>(); this.CreateMenu(); }

我正试图根据ObservableCollection中的对象禁用菜单项

MainViewModel:

public ObservableCollection<ThumbnailModel> Thumbnails { get; set; }

public MainWindowViewModel()
{
    Thumbnails = new ObservableCollection<ThumbnailModel>();
    this.CreateMenu();
}

private void CreateMenu()
{
    //TODO: Add tooltip to menu with short description

    var items = new List<MenuItemViewModel>();

    var item = new MenuItemViewModel();
    item.MenuText = "File";

    item.MenuItems = new List<MenuItemViewModel> { 
        new MenuItemViewModel { MenuText = "Select all", MenuCommand = this.SelectAllCommand, IsEnabled = SelectAllCommand.CanExecute(Thumbnails) },
        new MenuItemViewModel { MenuText = "Unselect all", MenuCommand = this.UnselectAllCommand, IsEnabled = true },
    };

    items.Add(item);

    //And so on
    MenuItems = items;
}

public ICommand SelectAllCommand
{
    get
    {
        return this.selectAllCommand ??
            (this.selectAllCommand = new DelegateCommand(SelectAll, ((t) => ((ObservableCollection<ThumbnailModel>)t).Any(o => !o.IsChecked))));
    }
}
<Window.Resources>
    <!--Menu template-->
    <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}"
                              ItemsSource="{Binding Path=MenuItems}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding MenuCommand}"/>
                <Setter Property="CommandParameter" 
                        Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                <Setter Property="IsEnabled"
                        Value="{Binding IsEnabled}"/>
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding MenuIcon}" />
            <TextBlock Text="{Binding MenuText}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>


<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
知道这里出了什么问题吗?当然知道怎么解决吗

更新 以前必须检查过它,但是当CanExecute方法被命中时,参数为null,尽管我已经将它指定为
缩略图

委派命令:

using System;
using System.Windows.Input;

public class DelegateCommand : ICommand
{

    private readonly Action<object> execute;
    private readonly Predicate<object> canExecute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {}

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }

        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        this.execute(parameter);
    }

    public bool CanExecute(object parameter) // parameter is null when breakpoint is hit
    {
        return this.canExecute == null || this.canExecute(parameter);
    }
}
使用系统;
使用System.Windows.Input;
公共类DelegateCommand:ICommand
{
私有只读操作执行;
私有只读谓词canExecute;
公共DelegateCommand(操作执行)
:此(执行,空)
{}
公共DelegateCommand(操作执行,谓词canExecute)
{
if(execute==null)
{
抛出新的ArgumentNullException(“执行”);
}
this.execute=execute;
this.canExecute=canExecute;
}
公共事件事件处理程序CanExecuteChanged
{
添加{CommandManager.RequerySuggested+=value;}
删除{CommandManager.RequerySuggested-=value;}
}
public void Execute(对象参数)
{
执行(参数);
}
public bool CanExecute(object参数)//命中断点时参数为null
{
返回this.canExecute==null | | this.canExecute(参数);
}
}

如果我正确理解谓词(这是不确定的),那么每次调用该方法时都会执行它。但是我在作业时输入的参数呢?这只使用一次吗

谓词的定义如下:

public delegate bool Predicate<in T>( T obj)
委托是“p”或参数,比较部分是谓词或布尔值。。注意,在“隐含”中传入的类型是因为linq有一个静态类“Where”扩展方法,允许up传入任何将谓词作为parm的集合类型。像这样:

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, int, bool> predicate
)
公共静态IEnumerable其中(
这是一个数不清的来源,
Func谓词
)
根据MSFT site上Delegate命令的示例,我们看到了一个新创建的parm,第二个parm正在传递一个名为“CanSubmit”的方法(指针),以便在需要时调用

public MyClass()
{
   this.submitCommand = new DelegateCommand<int?>(this.Submit, this.CanSubmit);
}

private bool CanSubmit(int? customerId)
{
  return (customerId.HasValue && customers.Contains(customerId.Value));
}
publicmyclass()
{
this.submitCommand=新的DelegateCommand(this.Submit,this.CanSubmit);
}
私有布尔可提交(int?customerId)
{
return(customerId.HasValue&&customers.Contains(customerId.Value));
}

在一步一步地、跌跌撞撞地阅读代码时,终于找到了答案

原来

默认情况下,当菜单项的命令无法执行时,菜单项将被禁用 已执行(CanExecute=false)

(在???中找不到任何与此相关的参考)

因此,解决方案变得简单多了,因为我不再需要我的
MenuItemViewModel
上的IsEnabled属性

我的XAML现在看起来像:

<Window.Resources>
    <!--Menu template-->
    <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}"
                              ItemsSource="{Binding Path=MenuItems}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding MenuCommand}"/>
                <Setter Property="CommandParameter" 
                        Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                <!-- No longer needed. By default menu items become disabled when its command cannot be executed (CanExecute = false).
                <Setter Property="IsEnabled"
                        Value="{Binding IsEnabled}"/>-->
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding MenuIcon}" />
            <TextBlock Text="{Binding MenuText}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>

我将其分解为两个语句:get{return this.selectAllCommand???(this.selectAllCommand=newdelegateCommand(SelectAll,((t)=>((observeCollection)t)。Any(o=>!o.IsChecked));},以便在调用它时可以看到更多。SelectAllCommand是否为Null,如果不是,那么它会返回您期望的结果吗?我已经按照您的建议做了,但结果是一样的。我在CanExecute中设置了一个断点,谓词不为null,但当进一步执行时,会出现异常。但正如我在问题的底部所提到的:当我从代码中执行相同的断点时(我已将其放置在VM的ctor中),它会返回false,因为集合中没有任何项(在ctor中初始化).我认为使用ICommand接口使我们不必像WPF/XAML系统那样明确地设置enable属性,对吗?此HResult=-2147467261不好,因为它可能是一个损坏的指针问题?可能是因为该值为空而导致的后遗症?我在这里猜。另外,我不关心委托命令,但我想知道CanExecute选项包含在哪里,方法调用的后备存储是什么?正常工作了吗。阅读您的答案,我注意到我对谓词没有错,但完全误解了MenuItem和CanExecute的工作原理。无法将您的答案标记为解决方案,但您的努力得到了我的+1。非常感谢,非常好!是的,事实上,任何项目的CanExecute都会自动处理已启用的属性(Wpf内部处理该属性,并且对程序员来说是透明的)。
public MyClass()
{
   this.submitCommand = new DelegateCommand<int?>(this.Submit, this.CanSubmit);
}

private bool CanSubmit(int? customerId)
{
  return (customerId.HasValue && customers.Contains(customerId.Value));
}
<Window.Resources>
    <!--Menu template-->
    <HierarchicalDataTemplate DataType="{x:Type viewModels:MenuItemViewModel}"
                              ItemsSource="{Binding Path=MenuItems}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Command"
                        Value="{Binding MenuCommand}"/>
                <Setter Property="CommandParameter" 
                        Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                <!-- No longer needed. By default menu items become disabled when its command cannot be executed (CanExecute = false).
                <Setter Property="IsEnabled"
                        Value="{Binding IsEnabled}"/>-->
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding MenuIcon}" />
            <TextBlock Text="{Binding MenuText}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>
    public ICommand SelectAllCommand
    {
        get
        {
            return this.selectAllCommand ?? (this.selectAllCommand = new DelegateCommand(SelectAll, delegate(object obj) { return Thumbnails.Any(t => !t.IsChecked); }));
        }
    }