C# 将菜单项绑定到ICommand CanExecute时ArgumentNullException
我正试图根据ObservableCollection中的对象禁用菜单项 MainViewModel: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(); }
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); }));
}
}