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?
CodePudding user response:
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.
CodePudding user response:
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 DelegateCommand
s, this can be done in two different ways.
Call the
RaiseCanExecuteChanged
on the command. This could be done in the setter of yourAllItemsSelected
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 theCanExecuteChanged
event once aPropertyChanged
event is raised for it. That means this mechanism only works if your view model implementsINotifyPropertyChanged
and your property raisesPropertyChanged
.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();
}
}