简体   繁体   English

"PropertyChanged 后 ICommand CanExecute 不触发?"

[英]ICommand CanExecute not triggering after PropertyChanged?

I got a WPF application that shows a button bound to a command like that:我有一个WPF 应用程序,它显示了一个绑定到如下命令的按钮:

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

The command is defined like that:该命令的定义如下:

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

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

The problem is, the button won't be enabled immediately after I set IsTestrunInProgress to false, but only after I click inside the application window .问题是,在我将IsTestrunInProgress设置为 false 后,该按钮不会立即启用,但只有在我在应用程序窗口内单击后才会启用。

Could you help me understand this behaviour and show me how to fix this?你能帮我理解这种行为并告诉我如何解决这个问题吗?

Further reading: wpf command pattern - when does it query canexecute进一步阅读: wpf 命令模式 - 什么时候查询可以执行

The ICommand interface exposes an event ICommand.CanExecuteChanged which is used to inform the UI when to re-determine the IsEnabled state of command driven UI components. ICommand接口公开一个事件ICommand.CanExecuteChanged ,用于通知UI何时重新确定命令驱动的UI组件的IsEnabled状态。

Depending upon the implementation of the RelayCommand you are using, you may need to raise this event; 根据您使用的RelayCommand的实现,您可能需要引发此事件; Many implementations expose a method such as RelayCommand.RaiseCanExecuteChanged() which you can invoke to force the UI to refresh. 许多实现都公开了一个方法,例如RelayCommand.RaiseCanExecuteChanged() ,您可以调用该方法来强制UI刷新。

Some implementations of the RelayCommand make use of CommandManager.RequerySuggested , in which case you will need to call CommandManager.InvalidateRequerySuggested() to force the UI to refresh. RelayCommand某些实现使用CommandManager.RequerySuggested ,在这种情况下,您需要调用CommandManager.InvalidateRequerySuggested()来强制UI刷新。

Long story short, you will need to call one of these methods from your property setter. 简而言之,您需要从属性设置器中调用其中一种方法。

Update 更新

As the state of the button is being determined when the active focus is changing, I believe the CommandManager is being used. 由于在活动焦点发生变化时确定了按钮的状态,我相信正在使用CommandManager So in the setter of your property, after assigning the backing field, invoke CommandManager.InvalidateRequerySuggested() . 因此,在属性的setter中,在分配后备字段后,调用CommandManager.InvalidateRequerySuggested()

Update 2 更新2

The RelayCommand implementation is from the MVVM light toolkit. RelayCommand实现来自MVVM light工具包。 When consumed from WPF/.NET, the implementation wraps the methods and events exposed from the CommandManager . 从WPF / .NET使用时,实现将包装从CommandManager公开的方法和事件。 This will mean that these commands work automagically in the majority of situations (where the UI is altered, or the focused element is changed). 这意味着这些命令在大多数情况下(UI被更改,或者焦点元素被更改)自动运行。 But in a few cases, such as this one, you will need to manually force the command to re-query. 但在少数情况下,例如这个,你需要手动强制命令重新查询。 The proper way to do this using this library would be to call the RaiseCanExecuteChanged() method on the RelayCommand . 正确的方法做到这一点使用这个库将调用RaiseCanExecuteChanged()的方法RelayCommand

This is so important and easy to miss, I am repeating what @Samir said in a comment. 这是非常重要且容易错过,我在重复@Samir在评论中所说的内容。 Mr Laurent Bugnion wrote in his blog : Laurent Bugnion先生在他的博客中写道:

In WPF 4 and WPF 4.5, however, there is a catch: The CommandManager will stop working after you upgrade MVVM Light to V5. 但是,在WPF 4和WPF 4.5中,有一个问题:在将MVVM Light升级到V5后,CommandManager将停止工作。 What you will observe is that your UI elements (buttons, etc) will stop getting disabled/enabled when the RelayCommand's CanExecute delegate returns false. 您将观察到当RelayCommand的CanExecute委托返回false时,您的UI元素(按钮等)将停止被禁用/启用。

If you are in a hurry, here is the fix: In any class that uses the RelayCommand, replace the line saying: 如果你赶时间,这里有修复:在使用RelayCommand的任何类中,替换行说:

using GalaSoft.MvvmLight.Command;

with: 有:

using GalaSoft.MvvmLight.CommandWpf;

You can try with CommandManager.InvalidateRequerySuggested . 您可以尝试使用CommandManager.InvalidateRequerySuggested

Anyway this did not help me sometimes in the past. 无论如何,这在过去有时对我没有帮助。 For me the best solution turned out to be to bind the boolean property to the Button.IsEnabled dependency property. 对我来说,最好的解决方案是将布尔属性绑定到Button.IsEnabled依赖项属性。

In your case something like 在你的情况下像

IsEnabled={Binding IsTestrunInProgress}

The issue is, the ICommand Property TestrunStartCommand is always returning a new command object whenever it is accessed. 问题是,ICommand Property TestrunStartCommand在访问时始终返回一个新的命令对象。

A simple fix is to create the ICommand object once and use it again and again. 一个简单的解决方法是创建一次ICommand对象并一次又一次地使用它。

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

This was quite a simple fix and it worked for me. 这是一个非常简单的修复,它对我有用。

Addition to Riegardt Steyn's answer above: https://stackoverflow.com/a/33503341/1964969除了上面 Riegardt Steyn 的回答: https ://stackoverflow.com/a/33503341/1964969

If you don't want to change Command to CommandWpf usage (as that two RelayCommand versions are not compatible inbetween), another workaround could be to not instantiate a command at the declaration place.如果您不想将Command更改为CommandWpf用法(因为两个 RelayCommand 版本之间不兼容),另一种解决方法可能是不在声明位置实例化命令。 Use constructor code instead:改用构造函数代码:

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);
       ...

Blockquote块引用

<\/blockquote>

In your Command class change CanExcutedChanged to this在您的 Command 类中将 CanExcutedChanged 更改为此

 public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }<\/code><\/pre>

This is example of my command class这是我的命令类的示例

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(); } }<\/code><\/pre>"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM