wpf ICommandCanExecute在PropertyChanged之后未触发?

wr98u20j  于 2023-05-08  发布在  其他
关注(0)|答案(7)|浏览(194)

我得到了一个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后,该按钮不会立即启用,而是 * 只有在我在应用程序窗口内单击 * 后才启用。
你能帮助我理解这种行为并告诉我如何解决这个问题吗?

进一步阅读:wpf command pattern - when does it query canexecute

wtzytmuj

wtzytmuj1#

ICommand接口公开了一个事件ICommand.CanExecuteChanged,用于通知UI何时重新确定命令驱动UI组件的IsEnabled状态。
根据您正在使用的RelayCommand的实现,您可能需要引发此事件;许多实现都公开了一个方法,如RelayCommand.RaiseCanExecuteChanged(),您可以调用它来强制刷新UI。
RelayCommand的一些实现使用CommandManager.RequerySuggested,在这种情况下,您需要调用CommandManager.InvalidateRequerySuggested()来强制刷新UI。
长话短说,您需要从属性设置器调用这些方法之一。

更新

由于按钮的状态正在确定时,活动焦点正在改变,我相信正在使用的CommandManager。因此,在属性的setter中,在分配了backing字段之后,调用CommandManager.InvalidateRequerySuggested()

更新2

RelayCommand的实现来自MVVM light工具包。当从WPF/.NET使用时,实现 Package 了从CommandManager公开的方法和事件。这将意味着这些命令在大多数情况下(UI被更改,或者焦点元素被更改)自动工作。但在少数情况下,例如本例,您需要手动强制命令重新查询。使用此库实现此目的的正确方法是在RelayCommand上调用RaiseCanExecuteChanged()方法。

hjzp0vay

hjzp0vay2#

这是如此重要和容易错过,我在重复@Samir在评论中所说的话。Laurent Bugnion先生在他的blog中写道:

  • 然而,在WPF 4和WPF 4.5中,有一个问题:将MVVM Light升级到V5后,CommandManager将停止工作。您将观察到的是,当RelayCommand的CanExecute委托返回false时,您的UI元素(按钮等)将停止被禁用/启用。
  • 如果你赶时间,这里是修复:在任何使用RelayCommand的类中,替换以下行:*
using GalaSoft.MvvmLight.Command;
  • 与:*
using GalaSoft.MvvmLight.CommandWpf;
qvsjd97n

qvsjd97n3#

你可以试试CommandManager.InvalidateRequerySuggested
不管怎么说,这在过去有时并没有帮助我。对我来说,最好的解决方案是将boolean属性绑定到Button.IsEnabled依赖属性。
在你的情况下

IsEnabled={Binding IsTestrunInProgress}
nbysray5

nbysray54#

问题是,每当访问ICommandProperty TestrunStartCommand时,它总是返回新的命令对象。
一个简单的解决方法是创建ICommand对象一次,然后反复使用它。

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

这是一个相当简单的修复和它为我工作。

gr8qqesn

gr8qqesn5#

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

kyks70gy6#

Blockquote
在Command类中将CanExecutedChanged更改为

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();
    }
}
7fyelxc5

7fyelxc57#

如果你使用的是BackgroundWorker(不一定是OP要求的),CommandManager.InvalidateRequerySuggested()只会在ProgressChanged回调中工作,而不会在DoWork回调中工作。

//Not going to work.
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    Dispatcher.CurrentDispatcher.Invoke(CommandManager.InvalidateRequerySuggested, DispatcherPriority.Render);
}

//Works!
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Dispatcher.CurrentDispatcher.Invoke(CommandManager.InvalidateRequerySuggested, DispatcherPriority.Render);
}

相关问题