47

我有一个WPF 应用程序,它显示了一个绑定到如下命令的按钮:

<Button Command="{Binding Path=TestrunStartCommand}" Content="GO!">

该命令的定义如下:

public ICommand TestrunStartCommand
{
    get { return new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress); }
}

public bool IsTestrunInProgress
{
    get{
        return _isTestrunInProgress;
    }
    set{
        _isTestrunInProgress = value;
        RaisePropertyChanged(IsTestrunInProgressPropertyName);
    }
}   

IsTestrunInProgress问题是,在我设置为 false后,该按钮不会立即启用,但只有在我单击 application window 后才会启用。

你能帮我理解这种行为并告诉我如何解决这个问题吗?

进一步阅读: wpf 命令模式 - 什么时候查询可以执行

4

6 回答 6

61

ICommand接口公开了一个事件ICommand.CanExecuteChanged,用于通知 UI 何时重新确定IsEnabled命令驱动的 UI 组件的状态。

根据RelayCommand您正在使用的实现,您可能需要引发此事件;许多实现都公开了一种方法,例如RelayCommand.RaiseCanExecuteChanged()您可以调用该方法来强制刷新 UI。

RelayCommandmake 的 一些实现CommandManager.RequerySuggested,在这种情况下,您将需要调用CommandManager.InvalidateRequerySuggested()以强制刷新 UI。

长话短说,您将需要从您的属性设置器中调用这些方法之一。

更新

由于在活动焦点发生变化时正在确定按钮的状态,我相信CommandManager正在使用。因此,在您的属性的设置器中,在分配支持字段后,调用CommandManager.InvalidateRequerySuggested().

更新 2

RelayCommand实现来自 MVVM 轻量级工具包。当从 WPF/.NET 使用时,实现包装从CommandManager. 这将意味着这些命令在大多数情况下(UI 更改或焦点元素更改)会自动运行。但在少数情况下,例如这种情况,您将需要手动强制命令重新查询。使用此库执行此操作的正确方法是RaiseCanExecuteChanged()RelayCommand.

于 2013-01-23T12:12:32.163 回答
60

这非常重要且容易错过,我在重复@Samir 在评论中所说的话。Laurent Bugnion 先生在他的博客中写道:

然而,在 WPF 4 和 WPF 4.5 中,有一个问题:将 MVVM Light 升级到 V5 后,CommandManager 将停止工作。您将观察到当 RelayCommand 的 CanExecute 委托返回 false 时,您的 UI 元素(按钮等)将停止禁用/启用。

如果你赶时间,这里是修复:在任何使用 RelayCommand 的类中,替换以下行:

using GalaSoft.MvvmLight.Command;

和:

using GalaSoft.MvvmLight.CommandWpf;
于 2015-11-03T15:47:05.573 回答
5

您可以尝试使用CommandManager.InvalidateRequerySuggested

无论如何,这在过去有时对我没有帮助。对我来说,最好的解决方案是将布尔属性绑定到Button.IsEnabled依赖属性。

在你的情况下,像

IsEnabled={Binding IsTestrunInProgress}
于 2013-01-23T12:13:02.580 回答
0

问题是,每当访问 ICommand 属性 TestrunStartCommand 时,它总是返回一个新的命令对象。

一个简单的解决方法是创建一次 ICommand 对象并一次又一次地使用它。

private ICommand _testRunCommand = null;
public ICommand TestrunStartCommand
{
    get 
    { 
        return _testRunCommand ?? (_testRunCommand = new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress)); 
    }
}

这是一个非常简单的修复,它对我有用。

于 2017-12-24T23:04:46.110 回答
0

除了上面 Riegardt Steyn 的回答:https ://stackoverflow.com/a/33503341/1964969

如果您不想更改Command用法CommandWpf(因为两个 RelayCommand 版本之间不兼容),另一种解决方法可能是不在声明位置实例化命令。改用构造函数代码:

public class SomeVMClass
{
    // CanExecute won't work:
    // Declaration and instantiation same place
    public RelayCommand MyCommand1 => new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);

    // CanExecute will work
    // Declaration only
    public RelayCommand MyCommand2 { get; private set; }

    public SomeVMClass()
    {
        // Let's instantiate our declared command
        MyCommand2 = new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);
       ...
于 2021-09-15T11:12:31.120 回答
0

块引用

在您的 Command 类中将 CanExcutedChanged 更改为此

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

这是我的命令类的示例

public class SaveConfigCommand : ICommand
{
    public MyViewModel VM { get; set; }

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

    public SaveConfigCommand(MyViewModel vm)
    {
        VM = vm;
    }

    public bool CanExecute(object? parameter)
    {
        MyObjectModel model = parameter as MyObjectModel;

        if (model == null)
            return false;

        // Validate others properties here 

        return true;
    }

    public void Execute(object? parameter)
    {
        VM.MyMethodInViewModel();
    }
}
于 2022-02-01T07:44:59.430 回答