WPF - How to force a Command to re-evaluate 'CanExecute' via its CommandBindings
Asked Answered
D

6

139

I have a Menu where each MenuItem in the hierarchy has its Command property set to a RoutedCommand I've defined. The associated CommandBinding provides a callback for the evaluation of CanExecute which controls the enabled state of each MenuItem.

This almost works. The menu items initially come up with the correct enabled and disabled states. However when the data that my CanExecute callback uses changes, I need the command to re-request a result from my callback in order for this new state to be reflected in the UI.

There do not appear to be any public methods on RoutedCommand or CommandBinding for this.

Note that the callback is used again when I click or type into the control (I guess it's triggered on input because mouse-over doesn't cause the refresh).

Dairyman answered 27/8, 2009 at 10:54 Comment(0)
W
184

Not the prettiest in the book, but you can use the CommandManager to invalidate all commandbinding:

CommandManager.InvalidateRequerySuggested();

See more info on MSDN

Wenwenceslaus answered 27/8, 2009 at 11:5 Comment(4)
Thanks this worked just fine. There's a slight delay in the UI, but I'm not too worried about that. Also, I up-voted your answer immediately, then took the vote back to see whether it worked. Now that it's working, I can't re-apply the vote again. Not sure why SO has that rule in place.Dairyman
I edited your answer in order to re-apply my vote. I didn't change anything in the edit. Thanks again.Dairyman
I had the same problem happening when I was changing the content of a Texbox from the code-behind. If you edit it by hand it would work. In this app, they had the texbox being edited by a control that would popup, and when you saved the popup, it would change the Texbox.Text property. This solved the problem! Thanks @WenwenceslausFleam
Just note from other answer (https://mcmap.net/q/168148/-refresh-wpf-command) "it has to be called on the UI thread"Handspike
H
89

For anyone who comes across this later; If you happen to be using MVVM and Prism, then Prism's DelegateCommand implementation of ICommand provides a .RaiseCanExecuteChanged() method to do this.

Hazlip answered 17/6, 2011 at 16:49 Comment(5)
This pattern is found in other MVVM libraries too, e.g. MVVM Light.Metropolis
Unlike Prism, MVVM Light v5's source code indicates its RaiseCanExecuteChanged() simply calls CommandManager.InvalidateRequerySuggested().Inrush
a side note to MVVM Light in WPF, you need to use the namespace GalaSoft.MvvmLight.CommandWpf since GalaSoft.MvvmLight.Command will cause trouble mvvmlight.net/installing/changes#v5_0_2Excursionist
((RelayCommand)MyCommand).RaiseCanExecuteChanged(); worked for me, using GalaSoft.MvvmLight.Command - BUT after changing to CommandWPF, it worked without the need to call anything. Thanks @ExcursionistLietman
What if you aren't using a 3rd party library though?Ampersand
T
32

I couldnt use CommandManager.InvalidateRequerySuggested(); because I was getting performance hit.

I have used MVVM Helper's Delegating command, which looks like below (i have tweaked it a bit for our req). you have to call command.RaiseCanExecuteChanged() from VM

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);
}
Trudy answered 12/12, 2011 at 13:30 Comment(1)
Just an FYI I commented out CommandManager.RequerySuggested += value; I was getting a near constant/looping evaluation of my CanExecute code for some reason. Otherwise the solution worked as expected. Thanks!Pool
W
20

If you have rolled your own class that implements ICommand you can lose a lot of the automatic status updates forcing you to rely on manual refreshing more than should be needed. It can also break InvalidateRequerySuggested(). The problem is that a simple ICommand implementation fails to link the new command to the CommandManager.

The solution is to use the following:

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

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

This way subscribers attach to CommandManager rather than your class and can properly participate in command status changes.

Wateriness answered 8/2, 2017 at 10:5 Comment(1)
Straightforward, to the point, and allows people to have control over their ICommand implementations.Hoogh
A
2

I've implemented a solution to handle property dependency on commands, here the link https://mcmap.net/q/168149/-mvvm-bind-relaycommand-canexecute-to-a-property

thanks to that you'll end up having a command like this:

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 }
 );
Alberic answered 22/5, 2015 at 10:37 Comment(0)
F
-5

This is what worked for me: Put the CanExecute before the Command in the XAML.

Fransiscafransisco answered 11/3, 2019 at 16:55 Comment(2)
I'm willing to eat the -3 without complaining. I do wonder what I did wrong and would appreciate feedback letting me know if I was outright wrong, or what the problem is.Fransiscafransisco
@mustakos I think it's too short and lacks code. Most answers should have example code.Dreddy

© 2022 - 2024 — McMap. All rights reserved.