Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why people use CommandManager.InvalidateRequerySuggested() on ICommands?

Tags:

c#

wpf

I am making some custom ICommand implementation of my own and I see A LOT of implementations going like this:

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

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

As far as I can see this is poorly optimized code since calling RaiseCanExecuteChanged() will trigger ALL commands in the UI to check their ICommand.CanExecute status, when usually we just want one of them to verify it.

I think I read once this is the main code of some WPF ICommands like RoutedCommand, and for them it makes sense because they want to revalidate all ICommands automatically once some control loses focus and things like this, but still I don't understand why people repeat this pattern for their own ICommand implementations.

The code I have in mind is a simple event invocation such as:

public event EventHandler CanExecuteChanged;

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

I tested and this works, so Why all the examples on the web don't implement something as simple as this? Am I missing something?

I have read about memory leak problems using strong references in regular events, where the CommandManager only uses WeakReferences, which is good in case the View is Garbage Collected, but still, aren't there any solutions that won't compromise performance over memory footprint?

like image 494
Michel Feinstein Avatar asked Nov 14 '16 10:11

Michel Feinstein


4 Answers

Why all the examples on the web don't implement something as simple as this? Am I missing something?

I'm guessing it's mostly due to laziness... What you propose is indeed a better (more efficient) implementation. However, it's not complete: you still need to subscribe to CommandManager.RequerySuggested to raise CanExecuteChanged on the command.

like image 95
Thomas Levesque Avatar answered Nov 20 '22 14:11

Thomas Levesque


Very simply - if you are performing heavy work in ICommand.CanExecute() then you are using Commands very badly. If you follow that rule there should in fact be no serious performance implication to calling CommandManager.InvalidateRequerySuggested().

Pragmatically, it's a much easier implementation than what you've suggested.

Personally, I rather call CommandManager.InvalidateRequerySuggested() in a particular ViewModel when a property changes so that the feedback to the user is instantaneous (i.e. enabling a button as soon as a form is completed/valid).

like image 4
toadflakz Avatar answered Nov 20 '22 15:11

toadflakz


This question is quite old, it's 2019 now, but I have found another reason to use CommandManager.InvalidateRequerySuggested().

I have written my own custom ICommand class for a WPF application, in which I invoked CanExecuteChanged directly in the first place like this.

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

My WPF application makes heavy use of different threads and when the above method is called from another thread then the main UI thread, it throws no error, it is just kind of ignored. It was getting even worse, when I found out, that all code lines inside the calling method were skipped, which led to strange results.

I don't know exactly, but I guess the reason was, that CanExecuteChanged led to changes in my UI, which must not be changed from another thread.

However - the moment when I changed my ICommand to CommandManager.InvalidateRequerySuggested(), there was no more problem. It seems that CommandManager.InvalidateRequerySuggested() can be called from any thread and the UI still gets updated.

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

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

I thought this could be a valueable answer, because I debugged this issue for 3 hours, before I came to this solution. The problem finding this issue was, that there were no errors thrown while debugging. The code was just skipped. Very strange behaviour.

like image 1
Michael Avatar answered Nov 20 '22 15:11

Michael


This is an answer to this answer. It is true that the CanExecuteChanged?.Invoke(this, null); has to be called by the main UI thread.

Simply write it like follows:

public void RaiseCanExecuteChanged()
{
    Application.Current.Dispatcher.Invoke(() => CanExecuteChanged?.Invoke(this, null)); 
}

This solves your problem and you can requery just one command. It is however true that you should nevertheless make your CanExecute-Method as fast as possible, as it will anyways be periodically executed. It is best to have CanExecute just consist of a single return foo; where foo is a field you can set right before you call CommandManager.InvalidateRequerySuggested();.

like image 1
jonzbonz Avatar answered Nov 20 '22 15:11

jonzbonz