当命令 CanExecute 为 false 时,按钮没有变为禁用状态。

12

我有一个非常简单的窗口,里面有一个按钮,与一个带有命令的ViewModel相关联。

如果MyCommand.CanExecute()为false,我期望该按钮被禁用。但是似乎WPF只会在窗口第一次绘制时设置IsEnabled属性。任何后续操作都不会影响按钮的可见状态。我正在使用Prism中的DelegateCommand。

我的视图:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Button Content="Click Here" Command="{Binding MyCommand}" Width="100" Height="50"/>
</Grid>

和我的视图模型:

public class MyVM : NotificationObject
{
    public MyVM()
    {
        _myCommand = new DelegateCommand(DoStuff, CanDoStuff);
    }

    private void DoStuff()
    {
        Console.WriteLine("Command Executed");
        _myCommand.RaiseCanExecuteChanged();
    }

    private bool CanDoStuff()
    {
        var result =  DateTime.Now.Second % 2 == 0;
        Console.WriteLine("CanExecute is {0}", result);
        return result;
    }

    private DelegateCommand _myCommand;

    public ICommand MyCommand
    {
        get
        {
            return _myCommand;
        }
    }
}

当我的应用程序加载时,有50%的时间按钮会被正确地禁用。但是,如果窗口加载时按钮处于启用状态,并且我单击按钮执行命令,则希望按钮在50%的情况下变为禁用状态,但它从未发生过。命令不会执行,但我仍然可以单击按钮。如何让WPF理解当CanExecute()为false时应禁用按钮?


你需要做的第一件事是打开数据绑定的调试信息:http://i.stack.imgur.com/MF8i5.png 然后重新运行并检查输出窗口,看看有什么错误。如果没有与您的命令绑定相关的错误,则您的RaiseCanExecuteChanged()实现不正确/有缺陷。 - user1228
你的CanDOStuff方法真的很奇怪!它可以使按钮变为禁用状态,但在下一秒中,你的命令就可以执行,但按钮仍处于禁用状态...非常奇怪。但是如果你的CanExecute可能会改变并且UI没有更新因为CanExecuteChanged没有被引发,那么你应该调用CommandManager.InvalidateRequerySuggested()。 - Kapitán Mlíko
@Viktor 我知道这很奇怪,它本应该是一个返回随机 true/false 的愚蠢示例。但 CommandManager.InvalidateRequerySuggested 没有任何效果。 - Greg Ferreri
@Will 输出中没有错误,而且RaiseCanExecuteChanged是微软的 Prism 库的一部分,不是自己编写的。 - Greg Ferreri
2个回答

8

我看到您正在使用Prism及其NotificationObjectDelegateCommand,因此我们应该期望在RaiseCanExecuteChanged()中没有错误。

然而,出现这种行为的原因是Prism的RaiseCanExecuteChanged是同步执行的,因此在我们仍然处于实现ICommand.Execute()的过程中调用了CanDoStuff(),结果似乎被忽略了。

如果您创建另一个具有自己命令的按钮,并从那个命令/按钮调用_myCommand.RaiseCanExecuteChanged(),第一个按钮将按您的预期启用/禁用。

或者,如果您尝试使用MVVM Light和RelayCommand进行相同的操作,则会成功,因为MVVM Light的RaiseCanExecuteChanged调用CommandManager.InvalidateRequerySuggested(),该方法使用Dispatcher.CurrentDispatcher.BeginInvoke异步调用回调CanDoStuff,避免了Prism实现中出现的问题。


谢谢您的解释。我尝试了您提供的两个建议,它们都很准确。看起来这是Prism的一个不幸限制。 - Greg Ferreri

0
你可以尝试这个(必须有Microsoft.Practices.Prism.dll)。
public class ViewModel
{
    public DelegateCommand ExportCommand { get; }

    public ViewModel()
    {
        ExportCommand = new DelegateCommand(Export, CanDoExptor);
    }

    private void Export()
    {
        //logic
    }

    private bool _isCanDoExportChecked;

    public bool IsCanDoExportChecked
    {
        get { return _isCanDoExportChecked; }
        set
        {
            if (_isCanDoExportChecked == value) return;

            _isCanDoExportChecked = value;
            ExportCommand.RaiseCanExecuteChanged();
        }
    }

    private bool CanDoExptor()
    {
        return IsCanDoExportChecked;
    }
}

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