Understanding Delegate Command in Prism - c#

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();
}
}

Related

ObservableCollection<T> isn't getting notified of change in property of <T>

I have a an ObservableCollection of Component, a class with another ObservableCollection, a String and a ComponentVersion. The SelectedComponentVersion is being updated via my view correctly but I'm unable to get the property of Components to fire it's setter/OnPropertyChanged.
private ObservableCollection<Component> components
public ObservableCollection<Component> Components
{
get { return foo; }
set { foo = value; OnPropertyChanged(); }
}
Here is the class of Component.
public class Component : ViewModelBase
{
private string componentName;
private ObservableCollection<ComponentVersion> componentVersions;
private ComponentVersion selectedComponent;
public string ComponentName
{
get { return componentName; }
set { componentName = value; OnPropertyChanged(); }
}
public ObservableCollection<ComponentVersion> ComponentVersions
{
get { return componentVersions; }
set { componentVersions = value; OnPropertyChanged(); }
}
public ComponentVersion SelectedComponent
{
get { return selectedComponent; }
set { selectedComponent = value; OnPropertyChanged(); }
}
public Component(string componentName, List<ComponentVersion> componentVersion)
{
ComponentName = componentName;
ComponentVersions = componentVersion.ToObservableCollection();
}
}
I then have a binding from a listview onto the SelectedComponent property inside of Component.
I have read countless stack overflows about setting up CollectionChanged and have tried to implement it with no luck.
Components.CollectionChanged += stuff;
private void stuff(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new System.NotImplementedException();
}
but this is never hit as tested with breakpoints.
Am I missing something entirely, losing my mind or daft! Someone please give me a point in the right direction, if any of this makes any sense at all.
P.S another solution I though of would be to place an invisible button inside the listview and have that send a command to tell the vm that a selected item has been updated.
In cases like this where I want to do something when the property on an item inside the collection changes, I usually hook up a PropertyChanged event in the CollectionChanged event
Here's a code example :
public MyViewModel()
{
// Setup Collection, with a CollectionChanged event
Components = new ObservableCollection<Component>();
Components.CollectionChanged += Components_CollectionChanged;
}
// In the CollectionChanged event (items getting added or removed from collection),
// hook up the PropertyChanged event
void Components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyType item in e.NewItems)
item.PropertyChanged += Component_PropertyChanged;
if (e.OldItems != null)
foreach(MyType item in e.OldItems)
item.PropertyChanged -= Component_PropertyChanged;
}
// In the PropertyChanged event, run some code if SelectedComponent property changed
void Component_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedComponent")
DoWork();
}
Note that we are using two separate events here.
CollectionChanged event runs when the collection itself changes. This means it gets set to a new collection, or item(s) get added or removed from the collection.
This captures newly added items and hooks up the PropertyChanged
handler, or detaches the PropertyChanged handler for items being
removed from the collection.
PropertyChanged event runs when a property changes and fires the event. You'll use this event to run code when the SelectedComponent property on any item in the collection changes
Well, components hasn't changed. I'm not sure if you set up your handler correctly, but even if you did, a CollectionChanged event is only fired, if the collection changed (item added or removed).
Lets say you have a collection of cars and all are red.
You pick one car and set it's color to blue.
The collection has not changed. It's still the very same cars. No car is missing, no car was added.
You probably want to attach yourself to the handlers of all cars instead of the handler of the collection.
So to sum it up:
ObservableCollection<T> isn't getting notified of change in property of <T>
That's true and it's by design.
When adding items to the collection you want to "hook up" the events you are interested in for those items, then you can act as needed. As mentioned above, the collection isn't changing it is the individual items that are changing.
One thing you could do is extend the ObservableCollection class and override the functionality so that when the collection is changed, an item is either added or removed, you then go through the items in the collection and "hook up" the events you're interested in. One thing to note is you may have to go through the collection and remove the event handlers and "hook up" them again in order to stop getting multiple event handlers being set for the items in the list.
ObservableCollection<T> does not send notifications about property changes of the elements. However, the System.ComponentModel namespace contains another collection, which supports that: you can consider to use BindingList<T> instead, which is also supported by WPF (elements must implement INotifyPropertyChanged).
Please note though that BindingList<T> scales poorly and its performance starts to decline above hundreds and thousands of elements as it always searches for the changed element sequentially in order to return the element index in its ListChanged event.

Better way to implement a behavior

I'm relatively new to WPF and Behaviors.
I have this behavior, I need to execute DoSomething() every time I set IsRedundant in the ViewModel.
Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if ture => set it to false, If false => set it to true). IsRedundant only used to raise the property changed event and for nothing else.
Is there a better way of achieving this ?
Any ideas ?
wpf
<i:Interaction.Behaviors>
<local:UIElementBehavior Redundant="{Binding IsRedundant, Mode=TwoWay}"/ >
</i:Interaction.Behaviors>
C#
class UIElementBehavior : Behavior<UIElement>
{
public static readonly DependencyProperty RedundantProperty = DependencyProperty.Register(
"Redundant",
typeof(bool),
typeof(UIElementBehavior),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DoSomething));
public bool Redundant
{
get { return (bool)GetValue(RedundantProperty); }
set { SetValue(RedundantProperty, value); }
}
private static void DoSomething(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do something on the AssociatedObject
}
}
Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if true => set it to false, If false => set it to true)
The problem is that you are using binding. Binding required target to be dependency property. And those are special, their setters aren't called, so you have to use callback to get informed when their value is changed via binding.
Moreover there is internally a check if value is different, for performance reasons callback is not called if value is the same, so you must change it as you do already.
An alternative solution is to simply add event in the view model:
public class ViewModel: INotifyPropertyChanged
{
public EventHandler SomethingHappens;
// call this to tell something to listener if any (can be view or another view model)
public OnSomethingHappens() => SomethingHappens?.Invoke(this, EventArgs.Empty);
...
}
Now you can subscribe/unsubscribe in the view to/from this event and do something in the event handler. If you are purist, then refactor code from the view into reusable behavior.
Is it shorter? Nope. Is it more clear? Yes, compared to using bool and such "wonderful" code:
IsRedundant = false;
IsRedundant = true; // lol
I was using bool properties like you do to inform the view in the past.
Then I used events.
Now I use combination of both. Every view model already implements INotifyPropertyChanged so why not use it?
Think about IsRedundant as a state. It can be used not only to trigger some method, but also used by the view to run animations via data triggers, control visibility of dynamic layout, etc. So you need a normal bool property in view model.
The view then can subscribe to/unsubscribe from PropertyChanged and simply have to check:
if(e.PropertyName == nameof(ViewModel.IsRedudant)) { ... }

ObservableCollection not working at all

I have this piece of code:
private ObservableCollection<Stats> _stats;
public StatisticsViewModel()
{
Stats = new ObservableCollection<Stats>();
Stats.Add(new Stats() { Type = "Min", Price = 100, Legend = Legend.Default });
}
public ObservableCollection<Stats> Stats
{
get
{
return _stats;
}
set
{
if (_stats != value)
{
_stats = value;
RaisePropertyChanged("Stats");
}
}
}
When the new collection is created, the set of Stats is called. However, when adding the object, it does not and so it does not execute RaisePropertyChanged...
What could I do to resolve this issue?
When the new collection is created, the set of Stats is called.
Correct. This is because the code is setting the Stats property, which invokes the setter method.
However, when adding the object, it does not and so it does not execute RaisePropertyChanged
Also correct. When calling .Add on an object the code isn't setting the property which holds that object. So there's no reason for the setter method to be invoked. The Stats property itself isn't being changed in this case, you're just interacting with the object.
What could I do to resolve this issue?
The ObservableCollection class exposes two events that you can subscribe to. The CollectionChanged event is raised when the collection itself changes, and the PropertyChanged event is raised when a property value on the collection changes.
For example, if you want to handle the event when the collection changes, you can subscribe to that event:
Stats.CollectionChanged += CollectionChangeHandler;
// elsewhere...
private void CollectionChangeHandler(object sender, NotifyCollectionChangedEventArgs e)
{
// do something to respond to the changed collection
}

how to provide change notification for a property when a subproperty changes?

This is such a basic question, but I don't think I've done this before despite having bound so many properties. I originally was planning to bind a class called TimeScale to various objects.
In class A we have a dependency property that I want to call change notification on. However, change notification is not done manually through this class.
public TimeScale AxisTimeScale
{
get { return (TimeScale)GetValue(AxisTimeScaleProperty); }
set { SetValue(AxisTimeScaleProperty, value); }
}
public static readonly DependencyProperty AxisTimeScaleProperty =
DependencyProperty.Register("AxisTimeScale",
typeof(TimeScale), typeof(SignalPanel),
new FrameworkPropertyMetadata(new TimeScale()));
this binds to source class B
private class B
{
private TimeScale _GraphTimeScale;
public TimeScale GraphTimeScale
{
get { return _GraphTimeScale; }
set
{
if (value != _GraphTimeScale)
{
_GraphTimeScale = value;
OnPropertyChanged("GraphTimeScale");
}
}
}
}
Looking at it again I guess all I really want is to call propertychanged on a dependency property, but since I didn't implement Inotifypropertychanged, I am wondering how i do that.
I think DependencyObject already implements Inotifypropertychanged, so I have access to this:
OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, old value, new value));
However, inserting the same object into both the old value and new value slots results in the PropertyChanged event not firing (I assume the implementation checks whether the two values are the same before firing the event). I want to avoid creating a new object if possible. I guess one option is to override OnPropertyChanged. Nope that also requires me to have a dependency propertychanged event args.
Update
OnPropertyChanged("TimeScale");
to
OnPropertyChanged("GraphTimeScale");
Or,
you can wrap the TimeScale class with an ObservableObject so that you can subscribe to object change events and raise them from there.
More info: http://msdn.microsoft.com/en-us/library/ff653818.aspx
Subscribe to the PropertyChanged notification of NumberOfUnits, and then raise OnPropertyChanged("GraphTimeScale") in the property changed event handler.
Would be interested if there is a better way though.

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