ObservableCollection not working at all - c#

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
}

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

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.

How to fire Property Changed Event when it's not actually changing

so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}

Static object: manage set method

I have a static observableCollection.
It is updated from the UI, using a MVVM approach.
How can I understand when a mod is made? (I need to change the value of a boolean flag when a change is made)
This is my code:
public static event EventHandler serbatoiDisponibiliPerErogatoriChanged;
private static ObservableCollection<TabSerbatoi> p_serbatoiDisponibiliPerErogatori = new ObservableCollection<TabSerbatoi>();
public static ObservableCollection<TabSerbatoi> serbatoiDisponibiliPerErogatori
{
get { return p_serbatoiDisponibiliPerErogatori; }
set
{
p_serbatoiDisponibiliPerErogatori = value;
if (serbatoiDisponibiliPerErogatoriChanged != null)
serbatoiDisponibiliPerErogatoriChanged(null, EventArgs.Empty);
}
}
I have also tried to match a function to the event serbatoiDisponibiliPerErogatoriChanged, but it is never called, because, with the debug, I have seen that it entes in the set method only at the init of the window.
The weird point is that the obeservableCollection is correctly updated, but it never passes in the set method.
How can I do it?
UPDATE:
with the mot's answer, I have done this:
void test(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("test");
}
and
serbatoiDisponibiliPerErogatori.CollectionChanged += test;
but, again, it is never called... it never enters in the "test" function.. why?
The problem is that the collection is not being set again, it is modified in the inside.
If you want to track added/removed elements INSIDE the collection, you can register to the CollectionChanged event.
serbatoiDisponibiliPerErogatori.CollectionChanged += MyEventHandler;

Dependency Property Update Mechanism

I have a custom control which is having a dependency property defined in it and my control implements INotifyPropertyChanged interface.
Dependency Property is Collection of Custom Objects.
Scenario 1
DP is of type List, whatever change I make in the list, nothing updated in MainUI, because I believe WPF does not understand adding and removing objects in list. it understands completely new references so to achieve this, whenever I want to update my list on control I use
MyProperty=new List();
In this approach, my DP callback fires everytime but eventArgs.NewValue always remains zero(it updated the list on UI correctly) but because I need to write some logic in property changed callback based on e.NewItems.Count, in this case that didn't work. Please tell me why e.NewItems does not work.
Scenario 2
DP is of type ObservableCollection, so as my collection property in view model against which I am binding my DP. in this case my property change callbacks does not fire at all, because I never use "new" keyword again after initialzing the property first time. UI updates but property change still not fires. So my logic in property change call back does not gets executed.
How should I make any of them or both of them working.
I would use the ObservableCollection approach, and subscribe to it's CollectionChanged event.
That way you will get notified whenever the collection has been changed.
But the other approach should work as well. When you set the regular list to a new instance, the PropertyChangedCallback will be fired for the dependency property, and by examining the DependencyPropertyChangedEventArgs object you can get the new value.
XAML:
<StackPanel>
<Button Content="Add to observablecollection" Click="click1" />
<Button Content="Set list to new instance" Click="click2" />
</StackPanel>
Code-behind:
public partial class Window1 : Window
{
public ObservableCollection<string> Strings { get; set; }
public List<string> StringsList
{
get { return (List<string>)GetValue(StringsListProperty); }
set { SetValue(StringsListProperty, value); }
}
public static readonly DependencyProperty StringsListProperty =
DependencyProperty.Register("StringsList", typeof(List<string>), typeof(Window), new PropertyMetadata(null, StringsListPropertyChanged));
public Window1()
{
InitializeComponent();
Strings = new ObservableCollection<string>();
Strings.CollectionChanged += strings_CollectionChanged;
StringsList = new List<string> { "Test1", "Test2", "Test3", "Test4" };
}
void strings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Fires everytime the observablecollection has an item added/removed etc.
MessageBox.Show(string.Format("ObservableCollection has changed! Count is now {0}", this.Strings.Count.ToString()));
if (this.Strings.Count == 10)
Console.WriteLine("Collection contains 10 strings!!");
}
private static void StringsListPropertyChanged(DependencyObject e, DependencyPropertyChangedEventArgs args)
{
var newCount = ((List<string>)args.NewValue).Count.ToString();
MessageBox.Show(string.Format("Dependency property has changed! Count is now {0}", newCount));
}
private void click1(object sender, RoutedEventArgs e)
{
this.Strings.Add("Test1");
}
private void click2(object sender, RoutedEventArgs e)
{
this.StringsList = new List<string> { "Newitem1", "Newitem2" };
}
}
ObservableCollection inherits from both INotifyPropertyChanged and INotifyCollectionChanged. I think if you want to know when the collection changed you should use this interface:
INotifyCollectionChanged

Categories

Resources