简体   繁体   中英

How to force to call CanExecute in Ribbon using MVVM

How to force to call CanExecute in Ribbon using MVVM I have a simple app with tab control and edit control on each tab item. It is linked to a ViewModel where also commands (not the routed ones) are defined. They are used in Menu. Everything's been working fine so far.

Now I'd like to use a ribbon with buttons that are enabled according to some conditions (eg Save or WordWrap will be enabled only in case that there will be active any tab item). Unfortunately, the CanExecute method of all commands are called just once when the app is loading. How to ensure to check it when something in the ViewModel is changed?

More details (WPF 4.6.1):

RelayCommand:

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

Command for WordWrap:

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 and Opened documents properties:

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

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

NewFile Command execution:

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

Ribbon definition (just a part):

<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>

On the beginning, there no file opened, so WordWrap is disabled. So is the button at the Ribbon. When a NewFileCommand is executed, new tab is created with the control, but WordWrap command remains disabled.

I found this article WPF MVVM command canexecute enable/disable button where the recommendation is to call RaiseCanExecuteChanged in the ActiveDocument setter:

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

Although this works, it sounds very strange to me. Why should the property know all commands that use it and should take care of them? Is there any cleaner solution to this issue?

Why should the property know all commands that use it and should take care of them?

Because this is part of the application logic that the view model class implements. No one but this class can be supposed to know when to call the CanExecute method of the command.

So you actually need to raise the CanExecuteChanged event to tell WPF to refresh the status of the command/button whenever you want to and the way you do this is to call the RaiseCanExecuteChanged method of the DelegateCommand . Whenever and each time you want to refresh the command.

Is there any cleaner solution to this issue?

You could take a look at ReactiveUI . This is an MVVM library that has a concept of reactive properties and reactive commands which makes it easier to refresh a command whenever a property changes:

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

But if you don't use such a reactive framework you should call the RaiseCanExecuteChanged to raise the CanExecuteChanged event whenever you want to refresh the status of the command. There is no "cleaner" way of doing this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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