WPF - 如何通过CommandBindings强制重新评估命令的'CanExecute'

139

我有一个菜单(Menu),其中层次结构中的每个MenuItem都将其Command属性设置为我定义的RoutedCommand。相关的CommandBinding提供了一个回调来评估CanExecute,以控制每个MenuItem的启用状态。

这几乎可以工作。菜单项最初使用正确的启用和禁用状态出现。但是,当我CanExecute回调使用的数据发生更改时,我需要命令重新请求从我的回调中得到结果,以便在UI中反映此新状态。

似乎没有任何公共方法可用于此RoutedCommand或CommandBinding。

请注意,当我单击或输入控件时会再次使用回调(我猜这是因为鼠标悬停不会导致刷新)。

6个回答

183

虽然不太美观,但您可以使用CommandManager来使所有命令绑定无效:

CommandManager.InvalidateRequerySuggested();

MSDN上查看更多信息。


1
谢谢,这个很好用。UI 有一点延迟,但我不太担心。此外,我立即点赞了你的回答,然后取消了投票,以查看它是否有效。现在它有效了,我无法再次应用投票。不确定为什么 SO 制定了这样的规则。 - Drew Noakes
5
我修改了你的回答以便重新投票。我在编辑中没有改变任何内容。再次感谢。 - Drew Noakes
当我从代码后台更改Texbox的内容时,我遇到了同样的问题。如果您手动编辑它,则可以正常工作。在此应用程序中,他们使用控件编辑Texbox,并在保存弹出窗口时更改Texbox.Text属性。这解决了问题!谢谢@Arcturus - Dzyann
11
请注意另一个答案中的说明(https://dev59.com/UXRA5IYBdhLWcg3w6SXZ):“必须在UI线程上调用”。 - Samvel Siradeghyan

89

对于以后遇到此问题的任何人; 如果您正在使用MVVM和Prism,则Prism的DelegateCommand实现ICommand提供了一个.RaiseCanExecuteChanged()方法来完成此操作。


12
这种模式在其他 MVVM 库中也被发现,例如 MVVM Light。 - Peter Lillevold
2
与Prism不同,MVVM Light v5的源代码表明其RaiseCanExecuteChanged()只是调用了CommandManager.InvalidateRequerySuggested() - Peter
4
在WPF中使用MVVM Light时,需要使用命名空间GalaSoft.MvvmLight.CommandWpf。因为使用GalaSoft.MvvmLight.Command会导致问题。详情请访问http://www.mvvmlight.net/installing/changes#v5_0_2。 - fuchs777
1
((RelayCommand)MyCommand).RaiseCanExecuteChanged(); 对我有用,使用了GalaSoft.MvvmLight.Command - 但是在改用CommandWPF后,它可以不需要调用任何东西就能工作。谢谢@fuchs777 - Robin Bennett
1
如果您没有使用第三方库呢? - Vidar

32

由于性能问题,我无法使用CommandManager.InvalidateRequerySuggested();

我使用了MVVM Helper的委托命令,代码如下(为了适应我们的需求进行了一些调整)。您需要从VM中调用command.RaiseCanExecuteChanged()

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

/// <summary>
/// This method can be used to raise the CanExecuteChanged handler.
/// This will force WPF to re-query the status of this command directly.
/// </summary>
public void RaiseCanExecuteChanged()
{
    if (canExecute != null)
        OnCanExecuteChanged();
}

/// <summary>
/// This method is used to walk the delegate chain and well WPF that
/// our command execution status has changed.
/// </summary>
protected virtual void OnCanExecuteChanged()
{
    EventHandler eCanExecuteChanged = _internalCanExecuteChanged;
    if (eCanExecuteChanged != null)
        eCanExecuteChanged(this, EventArgs.Empty);
}

3
提醒一下,我注释掉了CommandManager.RequerySuggested += value;。由于某些原因,我的CanExecute代码一直得到近乎不断的循环评估。否则,解决方案按预期工作。谢谢! - robaudas

20
如果您已经自定义了一个实现了 ICommand 接口的类,那么您可能会失去很多自动状态更新功能,不得不依赖于手动刷新。这可能会破坏 InvalidateRequerySuggested() 的功能。问题在于简单的 ICommand 实现不能将新命令与 CommandManager 关联起来。
解决方案如下:
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }

使用这种方式,订阅者会附加到CommandManager,而不是您的类,并且可以正确参与命令状态的更改。


2
简单明了,直截了当,让人们对其ICommand实现拥有控制权。 - Akoi Meexx

2

我已经实现了一个解决命令属性依赖关系的方案,这里是链接:https://dev59.com/1YLba4cB1Zd3GeqPbzwr#30394333

多亏了这个方案,您最终将拥有像这样的命令:

this.SaveCommand = new MyDelegateCommand<MyViewModel>(this,
    //execute
    () => {
      Console.Write("EXECUTED");
    },
    //can execute
    () => {
      Console.Write("Checking Validity");
       return PropertyX!=null && PropertyY!=null && PropertyY.Length < 5;
    },
    //properties to watch
    (p) => new { p.PropertyX, p.PropertyY }
 );

-5
这是对我有效的方法:在XAML中将CanExecute放在Command之前。

1
我愿意默默承受-3的后果而不抱怨。 我确实想知道我做错了什么,如果我完全错了或者有问题,请给我反馈。 - rmustakos
1
@mustakos 我认为这篇回答太短了,缺少代码。大多数回答应该有示例代码。 - steve

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