RelayCommand<T> 中的 CanExecute 不起作用

11

我正在使用MVVM Light V3 alpha 3编写一个WPF 4应用程序(使用VS2010 RC),在这里遇到了一些奇怪的行为...

我有一个命令,用于打开一个Window,然后该Window创建了ViewModel等内容 - 在这里没有任何奇怪的地方。

在那个Window中,我有一些RelayCommand,例如:

CategoryBeenSelected = new RelayCommand(() => OnCategoryUpdate = true);

再次没有什么奇怪的问题-它按照我预期的工作。

问题是我无法使用泛型RelayCommand具有CanExecute方法/lambda表达式。

这个可以工作:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory);

但这样不行:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory, CanDeleteCategory);
点击按钮后窗口没有弹出。我的意思是,我点击打开窗口的按钮时,应用程序会被阻塞,并且几秒钟后,窗口的 InitializeComponent 方法抛出了一个 NullReferenceException(对象引用未设置为对象的实例)。
简而言之,如果在 RelayCommand<T> 上放置 CanExecute 方法,则拥有该 ViewModel(具有 RelayCommand<T>)的 Window 无法实例化。如果移除 CanExecute,则窗口将显示。
问题出在哪里?我很困惑。
谢谢。
编辑:根据要求,以下是堆栈跟踪信息:

很奇怪:反射器告诉我们函数 CanExecute 是这样定义的:public bool CanExecute(object parameter) { return (this._canExecute == null) || this._execute((T)parameter)); }。没有任何东西会抛出异常。 - Vlad
也许您可以尝试在一个更小的示例上重现此问题? - Vlad
啊哈,我有了新东西。如果你使用一个带有字符串或对象的RelayCommand,它就能正常工作,如果你使用其他类型(int,bool,double等),它就会挂掉。实际上是否发送参数都是无关紧要的。 关于这个例子。它发生在WPF3.5/Mvvm light 2和WPF4/MVVM light 3 alpha3中。你可以运行什么?(我不知道是否需要MVVM light安装程序) - Jesus Rodriguez
我只能在明天工作时运行。WPF3.5 就可以了。 - Vlad
我找到了这个 bug,它在 RelayCommand<T> 类中。我会给类的创建者发送一封电子邮件。 - Jesus Rodriguez
也许此时参数是 null - Win4ster
3个回答

7
似乎 RelayCommand 将参数的值转换为泛型 T。但是,正如异常所提示的那样,您无法将 null 转换为结构体!如果您使用可空结构体初始化 RelayCommand,则它将按预期工作!
RelayCommand<int?> or RelayCommand<Nullable<int>>

HTH


嗯,那应该是原因... 但是有点奇怪... 我没有看到任何使用可空类型的代码... - Jesus Rodriguez
是的,这是正确的。doubleint都是值类型,不能为null。如果将它们变成可空类型,就可以工作了。将null强制转换为结构体会产生异常!请参见Vlad的评论,其中您可以看到对T的强制转换的方法! - Arcturus
尝试编译double test = (double)null;..在泛型世界中,你会得到一个运行时异常! ;) - Arcturus

2
Arcturus正确地确定了问题所在,但我对使用可空的基元类型并不赞成。除非我有很好的理由使用可空的基元类型,否则个人不喜欢它们。
相反,我将RelayCommand的实现更改如下:
    bool ICommand.CanExecute(object parameter)
    {
        if (parameter == null && typeof(T).IsValueType)
        {
            return CanExecute(default(T));
        }
        return CanExecute((T)parameter);
    }

我暂时没有对通用的Execute方法做出相同的更改,因为如果命令确实需要一个参数,失败并不是不合理的。

CanExecute方法的问题在于,在某些绑定评估之前,WPF系统有时会调用它。例如:

        <Button Content="Fit To Width" Command="{Binding Path=FitToWidthCommand}" CommandParameter="{Binding ElementName=imageScrollViewer, Path=ActualWidth}" />
        <Button Content="Fit To Height" Command="{Binding Path=FitToHeightCommand}" CommandParameter="{Binding ElementName=imageScrollViewer, Path=ActualHeight}" />

在上面的XAML代码中,您会注意到命令参数绑定到控件的实际宽度。然而,在WPF调用按钮命令的CanExecute之前,“imageScrollViewer”控件可能还没有被布局或渲染,因此没有实际的宽度/高度。当用户单击按钮并调用Execute时,控件已经被布局,所以值会发送给命令。如果没有 - 我认为失败是可以预料的 - 但只有在用户实际点击按钮时才会发生。
当然,我不喜欢CanExecute和Execute的不同行为,但目前它似乎符合框架所提出的限制。也许我会遇到这种情况会感到烦恼,但到目前为止我一直很喜欢这个变化。

1

非常晚才开始,但我一直在为此烦恼,问题是我导入了错误的命名空间。

我应该导入:

using GalaSoft.MvvmLight.CommandWpf;

但我导入了:

using GalaSoft.MvvmLight.Command;

希望这能帮到某些人!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接