3

I've been using Josh Smith's implementation of RelayCommand in a couple of large projects for some years now. However today I've come across a scenario where the CanExecute on one of my commands isn't refreshing. I'm at a loss as to what's causing it - the view-model isn't doing anything that I haven't done dozens of times already.

The VM's constructor creates a couple of commands. The first one is a "start" command:-

StartCommand = new RelayCommand(o => StartAsync(), o => true);

The StartAsync() method looks like this:-

private async void StartAsync()
{
    IsRunning = true;
    await Task.Run(() => SomeLongRunningProcess(); }
    IsRunning = false;
}

There is also a "save" command:-

SaveCommand = new RelayCommand(o => Save(), o => !IsRunning);

('IsRunning' is a bog-standard property, implementing INotifyPropertyChanged. As well as being used for the "CanExecute" delegate, it's also bound to the IsEnabled property of a few controls in the view to enable/disable them).

When I click my "Start" button (bound to 'StartCommand'), the "Save" button is correctly disabled. The b/g process runs to completion, then IsRunning is set to false, but this doesn't trigger the "Save" button to become enabled. It only enables if I click somewhere on my view.

(The controls whose IsEnabled property is bound to the VM IsRunning property do enable and disable correctly, by the way).

I've come across a few SO articles about this, but nothing really explains why this happens. My workaround was to bind the button's IsEnabled property to 'IsRunning', but it's frustrating that this particular view refused to play ball. Any thoughts on what might be causing this? Common sense says it's something specific to this view/VM, but I'm stumped (and I'm not going to post the code here - there's too much of it).

Andrew Stephens
  • 9,413
  • 6
  • 76
  • 152
  • Is `SomeLongRunningProcess()` a background thread or a thread other than the UI thread? Could this be an issue of cross thread access to the UI components? – CodingGorilla Mar 01 '16 at 13:38
  • Your Commands could implement the `CanExecuteChanged` event. Have you tried that? – XAMlMAX Mar 01 '16 at 13:41
  • @CodingGorilla It is a b/g thread, however when it finishes awaiting, the 'IsRunning = false' takes place on the UI thread. I've used the same technique within commands on other views/VMs without issue. – Andrew Stephens Mar 01 '16 at 13:43
  • @XAMlMAX I saw something similar here: http://stackoverflow.com/a/19684334/981831. However when I add this method to my RelayCommand implementation I get the error: "The event CanExecuteChanged can only appear on the left hand side of += or -=". – Andrew Stephens Mar 01 '16 at 13:45
  • 1
    Have a look at [this](http://stackoverflow.com/a/8475103/2029607). – XAMlMAX Mar 01 '16 at 13:46
  • await Task.Run(() => SomeLongRunningProcess(); } this cause the behavior in my projects --- sometimes :) i call CommandManager.InvalidateRequerySuggested(); at the end of the Execute method – blindmeis Mar 01 '16 at 13:56

1 Answers1

6

Yes, because the version of RelayCommand you're using is depending on CommandManager.RequerySuggested event and it's not accurate.

Its documentation states that

Occurs when the CommandManager detects conditions that might change the ability of a command to execute.

Basically it guesses all the possible events where your data could be changed. It can never know when your ViewModel/Model is changed. It isn't listening for the property change notifications.

If you want to react immediately without waiting for CommandManager to guess, you need to fire the ICommand.CanExecuteChanged manually yourself when model is updated.

You saw that the event not fired unless you click the window or something but do note that it could fire several times too

Community
  • 1
  • 1
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • Curious why this works on my other views/VMs, but unfortunately I can't see anything that I'm doing differently, so I guess it's some very subtle difference. I read somewhere that only mouse and keyboard events trigger the CommandManager, but I'm a little skeptical about this. There are many, many places in my VMs where I set the values of properties (or even just private vars) that are used in the CanExecute logic of commands, and it usually "just works" when I'm nowhere near the mouse or keyboard. – Andrew Stephens Mar 01 '16 at 15:49
  • 1
    @AndrewStephens Hard to guess. But keyboard or mouse events are not the only things which causes invalidation I guess. If I'm not wrong, they are raised when window gets activated/deactivated etc. You can [check out the source](http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Input/Command/CommandManager.cs,fb01095b2fe73140,references) if you're curious – Sriram Sakthivel Mar 01 '16 at 18:26