75

Does anyone know how I can force CanExecute to get called on a custom command (Josh Smith's RelayCommand)?

Typically, CanExecute is called whenever interaction occurs on the UI. If I click something, my commands are updated.

I have a situation where the condition for CanExecute is getting turned on/off by a timer behind the scenes. Because this is not driven by user interaction, CanExecute is not called until the user interacts with the UI. The end result is that my Button remains enabled/disabled until the user clicks on it. After the click, it is updated correctly. Sometimes the Button appears enabled, but when the user clicks it changes to disabled instead of firing.

How can I force an update in code when the timer changes the property that affects CanExecute? I tried firing PropertyChanged (INotifyPropertyChanged) on the property that affects CanExecute, but that did not help.

Example XAML:

<Button Content="Button" Command="{Binding Cmd}"/>

Example code behind:

private ICommand m_cmd;
public ICommand Cmd
{
    if (m_cmd == null)
        m_cmd = new RelayCommand(
            (param) => Process(),
            (param) => EnableButton);

    return m_cmd;
}

// Gets updated from a timer (not direct user interaction)
public bool EnableButton { get; set; }
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Josh G
  • 14,068
  • 7
  • 62
  • 74
  • Did you try to raise INotifyPropertyChanged for the Command? You don't need to have a field for the Command, just return new one each time. This combination should work. Or create new Command only for the case when you need the forcing. – egaga Feb 15 '13 at 17:03

7 Answers7

105

Calling System.Windows.Input.CommandManager.InvalidateRequerySuggested() forces the CommandManager to raise the RequerySuggested event.

Remarks: The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.

ˈvɔlə
  • 9,204
  • 10
  • 63
  • 89
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 1
    Do you suggest calling this from a ViewModel class? – Josh G Apr 23 '09 at 19:10
  • 3
    Not necessarily, as that may make your class hard to test. Try it, and move it into a service if necessary. Another option is to add a method to RelayCommand that allows you to raise CanExecuteChanged just for that command (CommandManager.InvalidRequerySuggested invalidates all commands, which is somewhat of an overkill). – Kent Boogaart Apr 23 '09 at 19:15
  • 26
    Interesting... It works, but it has to be called on the UI thread. I'm not surprised. – Josh G Apr 23 '09 at 19:16
  • So I have to fire CanExecuteChanged for the event to get evaluated? – Josh G Apr 23 '09 at 19:16
  • OK, it clicks now. Thanks, Kent. – Josh G Apr 23 '09 at 19:26
  • ...Clicks... no pun intended! :-) – Josh G Apr 23 '09 at 19:26
  • Heh, heh. Was thinking about my previous comment about raising CanExecuteChanged for that command only. That will only be possible if a separate list of event handlers is maintained. I'm still pondering whether that's a good idea or not. – Kent Boogaart Apr 23 '09 at 20:27
  • Any more thoughts here, Kent? – Thomas Apr 24 '09 at 15:34
  • @Tom: seems to me it should work fine. Basically you would have an EventHandlerList that you maintain in conjunction with adding handlers to CommandManager.CanExecuteChanged. Then you have a method to raise the CanExecuteChanged for that command only by using the EventHandlerList. In theory it should perform better, but I haven't tried it myself, and there's a little bit of overhead by managing the two lists. – Kent Boogaart Apr 24 '09 at 16:10
  • In my case I use Commands inside the Microsoft Ribbon. The CommandManager.InvalidateRequerySuggested() method works only if I give the focus to my ribbon in addition. – Nicolas Dec 14 '10 at 12:26
  • A bit overkill to invalidate all commands, but it worked well in my case. – bryanbcook Mar 25 '11 at 16:51
  • 4
    I used the MVVM-Light messenger and created a simple RefreshCommandStatus message that my ViewModels can now send out. The main window listens for this message and calls CommandManager.InvalidateRequerySuggest() – cordialgerm May 19 '11 at 18:06
  • 5
    Must be run on UI Thread, so might as well do it like this: `UI.Dispatcher.Invoke(() => { CommandManager.InvalidateRequerySuggested(); });` – Fandi Susanto Sep 27 '16 at 16:02
29

I was aware of CommandManager.InvalidateRequerySuggested() a long time ago, and used it, but it wasn't working for me sometimes. I finally figured out why this was the case! Even though it doesn't throw like some other actions, you HAVE to call it on the main thread.

Calling it on a background thread will appear to work, but sometimes leave the UI disabled. I really hope this helps somebody, and saves them the hours I just wasted.

SilverSideDown
  • 1,162
  • 11
  • 14
17

A workaround for that is binding IsEnabled to a property:

<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>

and then implement this property in your ViewModel. This also makes it a bit easier for the UnitTesting to work with the properties rather than commands to see if the command can be executed at a certain point of time.

I, personally, find it more convenient.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
tridy.net
  • 253
  • 2
  • 3
6

Probably this variant will suit you:

 public interface IRelayCommand : ICommand
{
    void UpdateCanExecuteState();
}

Implementation:

 public class RelayCommand : IRelayCommand
{
    public event EventHandler CanExecuteChanged;


    readonly Predicate<Object> _canExecute = null;
    readonly Action<Object> _executeAction = null;

   public RelayCommand( Action<object> executeAction,Predicate<Object> canExecute = null)
    {
        _canExecute = canExecute;
        _executeAction = executeAction;
    }


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

    public void UpdateCanExecuteState()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, new EventArgs());
    }



    public void Execute(object parameter)
    {
        if (_executeAction != null)
            _executeAction(parameter);
        UpdateCanExecuteState();
    }
}

Using simple:

public IRelayCommand EditCommand { get; protected set; }
...
EditCommand = new RelayCommand(EditCommandExecuted, CanEditCommandExecuted);

 protected override bool CanEditCommandExecuted(object obj)
    {
        return SelectedItem != null ;
    }

    protected override void EditCommandExecuted(object obj)
    {
        // Do something
    }

   ...

    public TEntity SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;

            //Refresh can execute
            EditCommand.UpdateCanExecuteState();

            RaisePropertyChanged(() => SelectedItem);
        }
    }

XAML:

<Button Content="Edit" Command="{Binding EditCommand}"/>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
R.Titov
  • 3,115
  • 31
  • 35
4

Thanks guys for the tips. Here's a bit of code on how to marshal that call from a BG thread to the UI thread:

private SynchronizationContext syncCtx; // member variable

In the constructor:

syncCtx = SynchronizationContext.Current;

On the background thread, to trigger the requery:

syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );

Hope that helps.

-- Michael

KyleMit
  • 30,350
  • 66
  • 462
  • 664
Michael Kennedy
  • 3,202
  • 2
  • 25
  • 34
  • 3
    Seems like it would be better to call Dispatcher.BeginInvoke() – Josh G Mar 07 '11 at 16:53
  • Hi Josh. Maybe it would be better. Internally, Dispatcher.BeginInvoke() uses the SynchronizationContextSwitcher class which delegates to the SynchronizationContext anyway... – Michael Kennedy Apr 06 '11 at 00:24
0

To update only a single GalaSoft.MvvmLight.CommandWpf.RelayCommand you could use

mycommand.RaiseCanExecuteChanged();

and for me i've created an Extension method:

public static class ExtensionMethods
    {
        public static void RaiseCanExecuteChangedDispatched(this RelayCommand cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }

        public static void RaiseCanExecuteChangedDispatched<T>(this RelayCommand<T> cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }
    }
Coden
  • 2,579
  • 1
  • 18
  • 25
0

Didn't see this mentioned here so adding in 2023.

I sorted this issue by installing the Prism library via NuGet (VS suggested it to me for "DelegateCommand") and used DelegateCommand instead of own RelayCommand implementation. I then just called command.RaiseCanExecuteChanged() the same way I would call OnPropertyChanged(propName). Works great for me.

kub1x
  • 3,272
  • 37
  • 38