CanExecute Logic for DelegateCommand - c#

Update: The focus became MVVM instead of the actual question so I'm updating it.
I'm having a problem with CanExecute for DelegateCommand. It doesn't update before I call RaiseCanExecuteChanged, is this the desired behavior?
I uploaded a simple sample project reproducing this problem here : http://dl.dropbox.com/u/39657172/DelegateCommandProblem.zip
The problem is this, I have two Buttons like this. One is Binding Command to a RelayCommand implementation and the other is binding to the Prism implementation of DelegateCommand
<Button Command="{Binding DelegateSaveCommand}"/>
<Button Command="{Binding RelaySaveCommand}"/>
The ViewModel ICommands
DelegateSaveCommand = new DelegateCommand(Save, CanSaveDelegate);
RelaySaveCommand = new RelayCommand(param => Save(), param => CanSaveRelay);
and the CanExecute method/predicate
public bool CanSaveDelegate()
{
return HasChanges;
}
public bool CanSaveRelay
{
get { return HasChanges; }
}
Both are using the property HasChanges. When HasChanges is updated, only the CanSaveRelay updates. Is this the way it's meant to be?

As it already was mentioned, this is intended behavior of DelagateCommand, not a bug.
DelegateCommand doesn't raise CanExecuteChanged event automatically, you have to raise that event manually by calling RaiseCanExecuteChanged when appropriate. Whereas RelayCommand relays on CommandManager.RequerySuggested event for that. This event is raised every time the user clicks somewhere or presses a button.
For situations when it is not very convenient or there is no appropriate place for calling RaiseCanExecuteChanged (like in your scenario you have to subscribe to PropertyChanged event on the model, etc) I have created the following simple wrapper that ensures that the CanExecute method of the wrapped command is executed automatically on CommandManager.RequerySuggested event:
public class AutoCanExecuteCommandWrapper : ICommand
{
public ICommand WrappedCommand { get; private set; }
public AutoCanExecuteCommandWrapper(ICommand wrappedCommand)
{
if (wrappedCommand == null)
{
throw new ArgumentNullException("wrappedCommand");
}
WrappedCommand = wrappedCommand;
}
public void Execute(object parameter)
{
WrappedCommand.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return WrappedCommand.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
You can use it like this:
DelegateSaveCommand = new AutoCanExecuteCommandWrapper(new DelegateCommand(Save, CanSaveDelegate));

If you want to stick to DelegateCommand you can use ObservesCanExecute:
DelegateSaveCommand = new DelegateCommand(Save, CanSaveDelegate).ObservesCanExecute(CanSaveDelegate);
Note that there is also ObservesProperty available if you are using a property for your CanExecute check. But then your property has to call NotifyPropertyChanged.

There is a bug in the DelegateCommand provided by Prism which doesn't raise the CanExecute event. I beat my head against the wall for a day until I dove into the DelegateCommand class provided by the Prism framework. I don't have the code with me, but I can post my resolution in a bit.
The alternative is to use one of the other RelayCommand frameworks out there.
Edit
Rather than reposting the code, there are other SO questions that provide resolutions:
WPF-Prism CanExecute method not being called
And Kent B. has a good article: MVVM Infrastructure: DelegateCommand

Related

Understanding Delegate Command in Prism

I'm struggling to understand the usage of delegate commands (from Prism) and I build a dummmy application in which I intend to do the following.
I have the command as
private readonly DelegateCommand selectAll;
public ICommand SelectAll
{
get { return selectAll; }
}
and use it as
selectAll= new DelegateCommand(SelectAll,CanSelectAll);
private bool CanSelectAll()
{
if (AllSelectedItems.Count()>3)
{
return true;
}
return false;
}
public IList<Student> AllItemsSelected
{
get => m_Items;
set => Set(ref m_Items, value);
}
I can see the button being disabled as expected when my ViewModel gets initialized but after even though sometimes this AllSelectedItems.count > 3, it doesn't seem to update and notify the UI.
What am I doing wrong here?
When you create the command, tell it to observe the property AllItemsSelected, like this:
selectAll= new DelegateCommand(SelectAll,CanSelectAll)
.ObservesProperty(() => AllItemsSelected);
That will make the command's state update every time AllItemsSelected changes.
This function, ObservesProperty is a nice feature of Prism. It lets you set up one-time monitoring of all your properties on which that comand's state depends.
The CanSelectAll method is not called automatically when the collection changes, after all how should the command know when to reevaluate the the condition? You have to explicitly tell it to do so.
An ICommand exposes a CanExecutChanged event that must be raised to notify the element binding the command to call the CanExecute method in order to evaluate if the command can be executed or not. This usually enables or disables the element in the UI, e.g. a Button. When and how this event is raised depends on the concrete implementation of the ICommand interface.
In Prism for DelegateCommands, this can be done in two different ways.
Call the RaiseCanExecuteChanged on the command. This could be done in the setter of your AllItemsSelected property.
public IList<Student> AllItemsSelected
{
get => m_Items;
set
{
Set(ref m_Items, value);
selectAll.RaiseCanExecuteChanged();
}
}
Another way of doing this is using the ObservesProperty method when instantiating the command. You pass a lambda for the property to be observed and the command will automatically raise the CanExecuteChanged event once a PropertyChanged event is raised for it. That means this mechanism only works if your view model implements INotifyPropertyChanged and your property raises PropertyChanged.
selectAll= new DelegateCommand(SelectAll, CanSelectAll).ObservesProperty(() => AllItemsSelected);
Which mechanism you choose is up to you. For your specific case it is important to know how AllItemsSelected changes. If you always assign a new collection once the selection changes, the examples above will work, since then each time the setter of the property is called and PropertyChanged is raised and therefore ObservesProperty will pick up the change and call CanExecutChanged for example.
However, if you reuse the same collection, e.g. only add and delete items from it, this will not work, as the actual collection object does not change, which means no call to the setter and no PropertyChanged. In this case put the call to RaiseCanExecuteChanged into the method that adds, deletes or modifies the collection.
In case the collection is modified somewhere else e.g. items are added through the UI directly to the collection, you would have to use a collection type that supports notifying collection changes like ObservableCollection<T> (through the CollectionChanged event). You could add a handler to CollectionChanged which calls RaiseCanExecuteChanged.
public class MyViewModel : BindableBase
{
private readonly DelegateCommand _selectAll;
public MyViewModel()
{
_selectAll = new DelegateCommand(ExecuteSelectAll, CanExecuteSelectAll);
AllSelectedItems = new ObservableCollection<Student>();
AllSelectedItems.CollectionChanged += OnAllSelectedItemsChanged;
}
public ICommand SelectAll => _selectAll;
public ObservableCollection<Student> AllSelectedItems
{
get => m_Items;
set => Set(ref m_Items, value);
}
private void ExecuteSelectAll()
{
// ...your code.
}
private bool CanExecuteSelectAll()
{
return AllSelectedItems.Count > 3;
}
private void OnAllSelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_selectAll.RaiseCanExecuteChanged();
}
}

Is a command a sort of event handler or why does my button execute the command?

I'm learning about wpf, delegates, event and I have some clue on what does what, but I'm a little lost when it comes to implementing ICommand
I have a class the implements the ICommand interface like this
class RelayCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute) : this (execute, null)
{
}
public RelayCommand(Action<object> execute, Func<object,bool> canExecute)
{
this._execute = execute;
this._canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
//throw new NotImplementedException();
return true;
}
public void Execute(object parameter)
{
//throw new NotImplementedException();
this._execute(parameter);
}
public void OnCanExecute()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
And then my ViewModel that uses i.
class PersonViewModel
{
public ICommand ICommandPresenter { get; set; }
public PersonModel PM;
private string _btnString = "";
#region Propertys
public string ButtonString
{
get {return _btnString; }
set
{
if (_btnString.Equals(value))
{
return;
}
_btnString = value;
}
}
public string Name { get { return PM.Name; } set
{
PM.Name = value;
}
}
#endregion Propertys
public PersonViewModel()
{
PM = new PersonModel();
ICommandPresenter = new RelayCommand(ChangeName);
}
public void ChangeName(object a)
{
string s = a as string;
MessageBox.Show("Hello from PersonViewModel Commander: " + s);
}
}
This is where it gets confusing for me. In the RelayCommand class I have an event CanExecuteChanged but that event is never executed/fired. From what I've understood from my reading on events is that you don't "need" to have subscribers to an event, but if you're going to have an event you should atleast have somewhere in the code that executes it. But I don't have that in my code but for some reason my button still does what I command it to do. I still understand that I've clicked the button but I don't have anything in my code that is subscribed to that button.
Why is my button able to execute my code even though I don't have an event connected to it?
Since I don't have any subscribers connected to CanExecuteChanged event does it becomes useless?
Is the command acting like an event? if so, please describe the whole process from clicking the button to executing the command.
CanExecuteChanged is member of ICommand class and, simplifying the things, is used by wpf framework to enable/disable your button depending on result of CanExecute() method. CanExecute is not tight to the code you want to execute when you click the button, but to the condition, when it's legal to be done.
Command executes your code, because you send pointer to your method(ChangeName method) here:
ICommandPresenter = new RelayCommand(ChangeName);
So you are not using CanExecuteChange at all, because you are invoking this constructor:
public RelayCommand(Action<object> execute).
To have CanExecute you have to invoke overloaded constructor that accepts CanExecute predicate:
public RelayCommand(Action<object> execute, Func<object,bool> canExecute)
And to invoke it just pass some function that returns a bool as second parameter:
ICommandPresenter = new RelayCommand(ChangeName, ()=>MyCustomLogicWhenButtonIsActive());
Based on what I saw in .Net's source code, the command assigned to the button's Command property is executed in the following way:
Clicking the button calls the button's OnClick() method (located in the button's ButtonBase base class).
The OnClick() method (see source here) calls the Commands.CommandHelpers.ExecuteCommandSource() method, passing this as the command source parameter.
The ExecuteCommandSource() (see source here) further calls the CriticalExecuteCommandSource() method, passing the same command source as a parameter.
Finally, the CriticalExecuteCommandSource() method (see source here) accesses the command source's Command member, and:
checks if the command's CanExecute() method returns true,
if it's true, it calls the command's Execute() method.
(If you use a RelayCommand implementation, then obviously, the RelayCommand class relays the call to the specific method you passed to its constructor when you instantiated it.)
So, to answer your question whether commands are fired through an event:
The source indicates that the OnClick() method executes the command directly, not through event handling. As you can see in the source, this method does raise the Click event, but it does so separately from the command execution.
Though, it remains unanswered how the OnClick() method is being called in the first place by the framework; I have no idea about that.

How to prevent CommandManager from invoking CanExecute whenever there is UI interaction?

Our UI currently has a lot of controls that are bound to commands with some complex CanExecutes. The problem we are facing is that whenever CommandManager determines that the UI needs to be re-evaulated, all commands run their CanExecute, which in turn, causes quite a performance hit for specific scenarios.
Reading this post: How does CommandManager.RequerySuggested work?
It seems that the CommandManager will re-evaulate on simple key down, mouse move events, etc. Is there a way to prevent this from happening, and instead, have the command manager re-evaluate when manually invoked?
A solution might be to implement a simpler version of the RelayCommand class that simply stores the event handlers itself and exposes a public method to fire them when appropriate:
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
// Further ICommand implementation omitted...
public void Invalidate()
{
var handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
You then call the following in your viewModel to re-evaluate the command:
fooCommand.Invalidate();
Of course, this leaves you with the opposite problem that you now have to manually re-evaluate all commands...
Edit
To elaborate on the comments, most RelayCommand's implement the CanExecuteChanged event like this:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
When the UI subscribes to the command's CanExecuteChanged event it is actually being indirectly subscribed to the CommandManager.RequerySuggested event which is why your CanExecute method is being called every time the CommandManager suggest a re-query.
The simpler RelayCommand I have suggested avoids this problem by not subscribing to the CommandManager.RequerySuggested event.

Windows Store App Development - InvalidateRequerySuggested

in regular WPF projects I've used
CommandManager.InvalidateRequerySuggested();
in order to force a value converter to be executed again.
Now, in Windows Store App development this handy command is no longer available.
Does an equivalent command or something else, which does the trick, exist?
Thanks a lot for your help!
The CommandManager doesn't exist in WinRT. You need to manually refresh listeners. Here's my example implementation of DelegateCommand<T> that illustrates this:
using System;
using System.Windows.Input;
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> m_executeAction;
private readonly Predicate<T> m_canExecutePredicate;
public DelegateCommand(Action<T> executeAction)
: this(executeAction, null)
{
}
public DelegateCommand(Action<T> executeAction, Predicate<T> canExecutePredicate)
{
if (executeAction == null)
{
throw new ArgumentNullException("executeAction");
}
m_executeAction = executeAction;
m_canExecutePredicate = canExecutePredicate;
}
public event EventHandler Executed;
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
public bool CanExecute(T parameter)
{
var result = true;
var canExecutePredicate = m_canExecutePredicate;
if (canExecutePredicate != null)
{
result = canExecutePredicate(parameter);
}
return result;
}
public void Execute(T parameter)
{
m_executeAction(parameter);
RaiseExecuted();
}
public void Refresh()
{
RaiseCanExecuteChanged();
}
protected virtual void RaiseExecuted()
{
var handler = Executed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
The WPF version of this class indirectly uses CommandManager.InvalidateRequerySuggested by implementing CanExecuteChanged as follows:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
However, in WinRT this is not supported and in my WinRT version any code that invalidates the state of the delegate command must call the Refresh method to cause bound items in the view to requery the command.
I think the best solution to your specific problem would be to implement INotifyPropertyChanged in your view model. Invoking the PropertyChanged event on this interface is equivalent to my Refresh method and forces bound elements in the view to re-evaluate themselves and, thus, to re-run all associated IValueConverter instances.
According to Microsoft Developer Network it does work with Windows 8 and Framework 4.5. However, it does state the following:
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.
But since it doesn't mention Windows Mobile for Windows Devices below WinRT being compliant that above command may not work for those devices, but if it is for Windows Store for WinRT and Windows 8 it should work just fine.
If I misunderstood your question please let me know and I'll try to assist further.
Article about command here:

ICommand CanExecute not triggering after PropertyChanged?

I got a WPF application that shows a button bound to a command like that:
<Button Command="{Binding Path=TestrunStartCommand}" Content="GO!">
The command is defined like that:
public ICommand TestrunStartCommand
{
get { return new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress); }
}
public bool IsTestrunInProgress
{
get{
return _isTestrunInProgress;
}
set{
_isTestrunInProgress = value;
RaisePropertyChanged(IsTestrunInProgressPropertyName);
}
}
The problem is, the button won't be enabled immediately after I set IsTestrunInProgress to false, but only after I click inside the application window.
Could you help me understand this behaviour and show me how to fix this?
Further reading:
wpf command pattern - when does it query canexecute
The ICommand interface exposes an event ICommand.CanExecuteChanged which is used to inform the UI when to re-determine the IsEnabled state of command driven UI components.
Depending upon the implementation of the RelayCommand you are using, you may need to raise this event; Many implementations expose a method such as RelayCommand.RaiseCanExecuteChanged() which you can invoke to force the UI to refresh.
Some implementations of the RelayCommand make use of CommandManager.RequerySuggested, in which case you will need to call CommandManager.InvalidateRequerySuggested() to force the UI to refresh.
Long story short, you will need to call one of these methods from your property setter.
Update
As the state of the button is being determined when the active focus is changing, I believe the CommandManager is being used. So in the setter of your property, after assigning the backing field, invoke CommandManager.InvalidateRequerySuggested().
Update 2
The RelayCommand implementation is from the MVVM light toolkit. When consumed from WPF/.NET, the implementation wraps the methods and events exposed from the CommandManager. This will mean that these commands work automagically in the majority of situations (where the UI is altered, or the focused element is changed). But in a few cases, such as this one, you will need to manually force the command to re-query. The proper way to do this using this library would be to call the RaiseCanExecuteChanged() method on the RelayCommand.
This is so important and easy to miss, I am repeating what #Samir said in a comment. Mr Laurent Bugnion wrote in his blog:
In WPF 4 and WPF 4.5, however, there is a catch: The CommandManager will stop working after you upgrade MVVM Light to V5. What you will observe is that your UI elements (buttons, etc) will stop getting disabled/enabled when the RelayCommand’s CanExecute delegate returns false.
If you are in a hurry, here is the fix: In any class that uses the RelayCommand, replace the line saying:
using GalaSoft.MvvmLight.Command;
with:
using GalaSoft.MvvmLight.CommandWpf;
You can try with CommandManager.InvalidateRequerySuggested.
Anyway this did not help me sometimes in the past. For me the best solution turned out to be to bind the boolean property to the Button.IsEnabled dependency property.
In your case something like
IsEnabled={Binding IsTestrunInProgress}
The issue is, the ICommand Property TestrunStartCommand is always returning a new command object whenever it is accessed.
A simple fix is to create the ICommand object once and use it again and again.
private ICommand _testRunCommand = null;
public ICommand TestrunStartCommand
{
get
{
return _testRunCommand ?? (_testRunCommand = new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress));
}
}
This was quite a simple fix and it worked for me.
Addition to Riegardt Steyn's answer above: https://stackoverflow.com/a/33503341/1964969
If you don't want to change Command to CommandWpf usage (as that two RelayCommand versions are not compatible inbetween), another workaround could be to not instantiate a command at the declaration place. Use constructor code instead:
public class SomeVMClass
{
// CanExecute won't work:
// Declaration and instantiation same place
public RelayCommand MyCommand1 => new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);
// CanExecute will work
// Declaration only
public RelayCommand MyCommand2 { get; private set; }
public SomeVMClass()
{
// Let's instantiate our declared command
MyCommand2 = new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);
...
Blockquote
In your Command class change CanExcutedChanged to this
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
This is example of my command class
public class SaveConfigCommand : ICommand
{
public MyViewModel VM { get; set; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public SaveConfigCommand(MyViewModel vm)
{
VM = vm;
}
public bool CanExecute(object? parameter)
{
MyObjectModel model = parameter as MyObjectModel;
if (model == null)
return false;
// Validate others properties here
return true;
}
public void Execute(object? parameter)
{
VM.MyMethodInViewModel();
}
}

Categories

Resources