【问题标题】:How to force to call CanExecute in Ribbon using MVVM如何使用 MVVM 在 Ribbon 中强制调用 CanExecute
【发布时间】:2017-07-13 22:59:48
【问题描述】:

如何使用 MVVM 在 Ribbon 中强制调用 CanExecute 我有一个简单的应用程序,其中包含每个选项卡项上的选项卡控件和编辑控件。它链接到一个 ViewModel,其中还定义了命令(不是路由的)。它们用于菜单。到目前为止一切正常。

现在我想使用带有根据某些条件启用的按钮的功能区(例如,只有在任何选项卡项处于活动状态时才会启用保存或 WordWrap)。不幸的是,所有命令的 CanExecute 方法在应用程序加载时只调用一次。当 ViewModel 中的某些内容发生更改时,如何确保检查它?

更多细节(WPF 4.6.1):

中继命令:

public class RelayCommand : ICommand
{
    private Action _execute;
    private Func<bool> _canExecute;

    public RelayCommand(Action execute, Func<bool> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute != null)
            return _canExecute();
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _execute();
    }
}

自动换行命令:

private RelayCommand _ChangeWordWrapCommand;

public RelayCommand ChangeWordWrapCommand
{
    get
    {
        if (_ChangeWordWrapCommand == null)
            _ChangeWordWrapCommand = new RelayCommand(
                () => { ((TextDocument)ActiveDocument).WordWrap = !((TextDocument)ActiveDocument).WordWrap; },
                () => { return (ActiveDocument != null); }
            );

        return _ChangeWordWrapCommand;
    }
}

ActiveDocument 和 Opened 文档属性:

public ObservableCollection<Document> OpenedDocuments { get; private set; }

private Document _ActiveDocument; 
public Document ActiveDocument
{
    get { return _ActiveDocument; }
    set
    {
        _ActiveDocument = value;
        OnPropertyChanged(nameof (ActiveDocument));
    }
}

NewFile 命令执行:

private Document NewFile()
{
    TextDocument result = new TextDocument(new Model.TextDocument());
    result.FileName = $"Untitled {UntitledFileNumber++}";
    OpenedDocuments.Add(result);
    ActiveDocument = result;
    return result;
}

功能区定义(只是一部分):

<Ribbon x:Name="Ribbon" SelectedIndex="0" UseLayoutRounding="False">
    <RibbonTab Header="Home" KeyTip="H" >
        <RibbonGroup x:Name="Files" Header="Files" >
            <RibbonButton  Label="New" KeyTip="N" Command="{Binding Path=NewFileCommand, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
            <RibbonButton  Label="WordWrap" KeyTip="W" Command="{Binding Path=ChangeWordWrapCommand, Mode=OneWay, UpdateSourceTrigger=Default}" />
        </RibbonGroup>
    </RibbonTab>
</Ribbon>

一开始,没有打开文件,所以 WordWrap 被禁用。功能区上的按钮也是如此。执行 NewFileCommand 时,会使用控件创建新选项卡,但 WordWrap 命令仍处于禁用状态。

我发现这篇文章WPF MVVM command canexecute enable/disable button 建议在 ActiveDocument 设置器中调用 RaiseCanExecuteChanged:

public Document ActiveDocument
{
    get { return _ActiveDocument; }
    set
    {
        _ActiveDocument = value;
        OnPropertyChanged(nameof(ActiveDocument));
        ChangeWordWrapCommand.RaiseCanExecuteChanged();///really
    }
}

虽然这可行,但对我来说听起来很奇怪。为什么属性应该知道所有使用它的命令并且应该处理它们?这个问题有什么更干净的解决方案吗?

【问题讨论】:

  • CommandManager.RequerySuggested ?

标签: c# wpf mvvm


【解决方案1】:

为什么属性应该知道所有使用它的命令并且应该处理它们?

因为这是视图模型类实现的应用程序逻辑的一部分。除了这个类之外没有人应该知道何时调用命令的CanExecute 方法。

因此,您实际上需要引发CanExecuteChanged 事件来告诉WPF 随时刷新命令/按钮的状态,并且您执行此操作的方式是调用DelegateCommand 的RaiseCanExecuteChanged 方法。无论何时何地,您都想刷新命令。

这个问题有什么更简洁的解决方案吗?

您可以查看ReactiveUI。这是一个 MVVM 库,它具有响应式属性和响应式命令的概念,可以在属性更改时更轻松地刷新命令:

ChangeWordWrapCommand = ReactiveCommand.Create(this.WhenAnyValue(x => x.ActiveDocument));

但是,如果您不使用这样的响应式框架,您应该在想要刷新命令状态时调用RaiseCanExecuteChanged 来引发CanExecuteChanged 事件。没有“更清洁”的方式来做到这一点。

【讨论】:

    猜你喜欢
    • 2022-12-18
    • 2013-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-27
    • 1970-01-01
    • 2022-12-09
    • 2016-08-14
    相关资源
    最近更新 更多