I've noticed a strange behaviour with RaisePropertyChanged while adding objects to a ViewModel property.
private List<string> _items;
public List<string> Items
{
get
{
if(_items == null){ _items = new List<string>(); }
return _itmes;
}
set
{
_items = value;
RaisePropertyChanged("Items");
}
}
When ever I add objects to the collection through the property
Items.Add("new string");
RaisePropertyChanged never gets called.
What is the best way of getting the RaisePropertyChanged to function the way that I would like it to?
Your setter will be called when you change the collection, not if the contents of the collection has been changed.
What you need is a collection that informs you about changes. Look at the ObservableCollection<T>-class. Register to its CollectionChanged event for beeing informed about changes.
Property with a setter
The following example shows you how you can work with a settable property that holds an observable collection. The complexity of the example is because the collection can be set from the outsite of the instance. If you don't need this feature, the solution will get be much more simple.
private ObservableCollection<string> _items;
public ObservableCollection<string> Items {
get {
if (_items == null) {
// Create a new collection and subscribe to the event
Items=new ObservableCollection<string>();
}
return _items;
}
set {
if(value !=_items){
if (null != _items) {
// unsubscribe for the old collection
_items.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_items_CollectionChanged);
}
_items = value;
if (null != _items) {
// subscribe for the new collection
_items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_items_CollectionChanged);
}
// Here you will be informed, when the collection itselfs has been changed
RaisePropertyChanged("Items");
}
}
}
void _items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
// Here you will be informed, if the content of the collection has been changed.
}
Property without a setter
If you don't need to have the collection setable, register to CollectionChanged while creating the collection. However you have then to remove the setter of your property. Otherwise will not be informed of changes if the collection has been changed:
private ObservableCollection<string> _items;
public ObservableCollection<string> Items {
get {
if (_items == null) {
// Create a new collection and subscribe to the event
_items=new ObservableCollection<string>();
_items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler
}
return _items;
}
}
void _items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
// Here you will be informed, if the content of the collection has been changed.
}
Additional information
Look at INotifyCollectionChanged for further information about collection changes. The above examples you also can use more generic with IEnumerable<string> and INotifyCollectionChanged.
Of course it never gets called because you are actually not setting the property (the setter of Items property is not getting called) when you add an item to the collection.
In case of collection modification what you need is to raise a CollectionChanged event. For this purpose you need to use ObservableCollection<T> instead of usual List<T>:
private ObservableCollection<string> _items;
public ObservableCollection<string> Items
{
get
{
if(_items == null){ _items = new ObservableCollection<string>(); }
return _itmes;
}
set
{
_items = value;
RaisePropertyChanged("Items");
}
}
Now, when you add items to the collection, the CollectionChanged event will be raised and your UI will get updated accordingly.
Related
Source code is here:
https://github.com/djangojazz/BubbleUpExample
The problem is I am wanting an ObservableCollection of a ViewModel to invoke an update when I update a property of an item in that collection. I can update the data that it is bound to just fine, but the ViewModel that holds the collection is not updating nor is the UI seeing it.
public int Amount
{
get { return _amount; }
set
{
_amount = value;
if (FakeRepo.Instance != null)
{
//The repo updates just fine, I need to somehow bubble this up to the
//collection's source that an item changed on it and do the updates there.
FakeRepo.Instance.UpdateTotals();
OnPropertyChanged("Trans");
}
OnPropertyChanged(nameof(Amount));
}
}
I basically need the member to tell the collection where ever it is called: "Hey I updated you, take notice and tell the parent you are a part of. I am just ignorant of bubble up routines or call backs to achieve this and the limited threads I found were slightly different than what I am doing. I know it could possible be done in many ways but I am having no luck.
In essence I just want to see step three in the picture below without having to click on the column first.
Provided that your underlying items adhere to INotifyPropertyChanged, you can use an observable collection that will bubble up the property changed notification such as the following.
public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
foreach (INotifyPropertyChanged item in args.NewItems)
item.PropertyChanged += item_PropertyChanged;
if (args.OldItems != null)
foreach (INotifyPropertyChanged item in args.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
private void OnItemPropertyChanged(T sender, PropertyChangedEventArgs args)
{
if (ItemPropertyChanged != null)
ItemPropertyChanged(this, new ItemPropertyChangedEventArgs<T>(sender, args.PropertyName));
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e);
}
}
You should do two things to get it to work:
first: you should refactor the RunningTotal property so it can raise the property changed event. Like so:
private int _runningTotal;
public int RunningTotal
{
get => _runningTotal;
set
{
if (value == _runningTotal)
return;
_runningTotal = value;
OnPropertyChanged(nameof(RunningTotal));
}
}
Second thing you should do is calling the UpdateTotals after you add a DummyTransaction to the Trans. An option could be to refactor the AddToTrans method in the FakeRepo
public void AddToTrans(int id, string desc, int amount)
{
Trans.Add(new DummyTransaction(id, desc, amount));
UpdateTotals();
}
public class PricingGrpModel
{
public string Name { get; set; }
public string Description { get; set; }
}
private ObservableCollection<PricingGrpModel> _myCollection;
public ObservableCollection<PricingGrpModel> myCollection
{
get { return _myCollection; }
set { _myCollection= value; OnPropertyChanged("myCollection"); }
}
myCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(myCollection_CollectionChanged);
void myCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
try
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
break;
case NotifyCollectionChangedAction.Remove:
break;
}
}
catch(Exception exception)
{
}
}
I have bound 'myCollection' ObservableCollection to DataGrid's ItemsSource. The Collection Change event fired while adding or removing the Row. But I couldn't track the changes of existing row value. how can I get a notification when an item's property in an ObservableCollection has been changed?
you can use NewItems and OldItems properties of the NotifyCollectionChangedEventArgs.
When you call ObservableCollection.Remove(), the removed items will be present in OldItems property:
void myCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (var removedItem in e.OldItems)
{
}
}
}
The only problem is, that when you call ObservableCollection.Clear() method, the OldItems property is null.
If you want to access also cleared items, you will have to create your custom class by inheriting from ObservableCollection and override RemoveItem protected method. RemoveItem method is invoked also when you call ObservableCollection.Clear() You can use my ExtendedObservableCollection from this answer: Trigger InotifyPropertyChanged/CollectionChanged on ObservableCollection
You can't directly track the item Changes via the CollectionChanged Event.
What you can do is:
Add and Remove an EventHandler to the PricingGrpModel.PropertyChanged in the CollectionChanged EventHandler for each item in the ObservableCollection.
Consider the following object, part of a WPF MVVM application:
public class MyObject : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private bool _isSelected;
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
And its use in the following ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
private List<MyObject> _myObjects;
public List<MyObject> MyObjects
{
get
{
return _myObjects;
}
set
{
_myObjects = value;
OnPropertyChanged("MyObjects");
}
}
public bool CanDoSomething
{
get
{
return MyObjects.Where(d => d.IsSelected).Count() > 0;
}
}
}
In this situation, I can track which of my objects have been selected, and selecting them will fire OnPropertyChanged and so can notify the parent view.
However, CanDoSomething will always be false because there's nowhere I can fire an OnPropertyChanged to create a notification. If I put it in MyObject, it doesn't know anything about the property and so does nothing. There's nowhere to put it in the ViewModel because there's nothing that reacts when an object in the list is selected.
I've tried substituting the List for an ObservableCollection and a custom "TrulyObservableCollection" (see Notify ObservableCollection when Item changes ) but neither work.
How can I get round this, without resorting to click events?
I feel like if I had a better idea of what your end goal was I might be able to recommend a better approach. There is some stuff going on that just feels a little out place. Like maybe 'CanDoSomething' should be part of a command object. And I am wondering if more than one MyObject be selected at a time? If not then I would approach this in an entirely differnt way.
So anyway, you want to update CanDoSomething any time the IsSelected property of one of the items in MyObjects changes. It sounds like you were using an ObservableCollection at one point and then abandoned it. That was a mistake. You need to update CanDoSomething any time one of two events occur; the first is when items are added to or removed from MyObjects and the second is when the IsSelected property of any of the objects in MyObjects changes. For the first event you need something that implements INotifyCollectionChanged, i.e. an ObservableCollection. You already have the second event covered because the objects implement INotifyPropertyChanged. So you just have to combine those two things.
In the following example I have taken your code and made some changes. To start with I changed MyObjects back to an ObservableCollection<MyObject>. It does not have a setter because I have found that there usually is not good reason to change an observable collection; just add and remove objects as necessary. Then in the viewmodel's constructor I am register for the CollectionChanged event of MyObjects. In that handler I am grabbing items that are added to the collection and hooking up their PropertyChanged event to the OnIsSelectedChanged event handler and I am unhooking the PropertyChanged event from OnIsSelectedChanged for any objects that were removed from the collection. Because items have been added or removed we have no idea what the state of IsSelected may be of the objects in MyObjects so this is a good opportunity to update CanDoSomething, and I do at the bottom of the event handler. Finally, the OnIsSelectedChanged is where the other half of the magic happens. Every object in MyObjects will have their PropertyChanged event hooked up to this event handler. Whenever the IsSelected property on any of these objects changes the event handler will update CanDoSomething.
public class MyViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged gubbins
public MyViewModel()
{
this._myObjects.CollectionChanged += (o, e) =>
{
if (e.NewItems != null)
{
foreach (var obj in e.NewItems.OfType<MyObject>())
{
obj.PropertyChanged += this.OnIsSelectedChanged;
}
}
if (e.OldItems != null)
{
foreach (var obj in e.OldItems.OfType<MyObject>())
{
obj.PropertyChanged -= this.OnIsSelectedChanged;
}
}
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
};
}
private readonly ObservableCollection<MyObject> _myObjects =
new ObservableCollection<MyObject>();
public ObservableCollection<MyObject> MyObjects
{
get
{
return _myObjects;
}
}
private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected);
}
}
private bool _canDoSomething;
public bool CanDoSomething
{
get { return this._canDoSomething; }
private set
{
if (_canDoSomething != value)
{
_canDoSomething = value;
OnPropertyChanged("CanDoSomething");
}
}
}
}
First create a class that defines this attached property:
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
Then just bind this attached property to a delegate command in your view model: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"
You can find more detail in this blog post: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/
Let me know if it works
I'm using a TrulyObservableCollection as a datasource in a WPF DataGrid. My class implements the PropertyChange event properly (I get notification when a property changes). The CollectionChanged event gets triggered as well. However, my issue lies in the connection between the PropertyChanged event and CollectionChanged event. I can see in the PropertyChanged event which item is being changed (in this case the sender object), however I can't seem to find a way to see which one is changed from within the CollectionChanged event. The sender object is the whole collection. What's the best way to see which item has changed in the CollectionChanged event? The relevant code snippets are below. Thank you for your help, and let me know if there needs to be some clarification.
Code for setting up the collection:
private void populateBret()
{
bretList = new TrulyObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Do stuff here with the specific item that has changed
}
Class that is used in the collection:
public class bretItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _blID;
public string _blGroup;
[DataMember]
public int blID
{
get { return _blID; }
set
{
_blID = value;
OnPropertyChanged("blID");
}
}
[DataMember]
public string blGroup
{
get { return _blGroup; }
set
{
_blGroup = value;
OnPropertyChanged("blGroup");
}
}
protected void OnPropertyChanged (String name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
TrulyObservableCollection class
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
foreach (var item in list)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
EDIT:
In the item_PropertyChanged event the NotifyCollectionChangedEventArgs are set with NotifyCollectionChangedAction.Reset. This causes the OldItems and NewItems to be null, therefore I can't get the changed item in that case. I can't use .Add as the Datagrid is updated with an additional item. I can't appear to get .Replace to work either to get the changed item.
How about this:
In your ViewModel that contains the ObservableCollection of bretItem, the ViewModel subscribes to the CollectionChanged event of the ObservableCollection.
This will prevent the need of a new class TrulyObservableCollection derived from ObservableCollection that is coupled to the items within its collection.
Within the handler in your ViewModel, you can add and remove the PropertyChanged event handler as you are now. Since it is now your ViewModel that is being informed of the changes to objects within the collection, you can take the appropriate action.
public class BretListViewModel
{
private void populateBret()
{
bretList = new ObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var bret = sender as bretItem;
//Update the database now!
//One note:
//The ObservableCollection raises its change event as each item changes.
//You should consider a method of batching the changes (probably using an ICommand)
}
}
A Thing of Note:
As an aside, it looks like you are breaking the MVVM pattern based upon this snippet:
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
You probably should consider loading your ViewModel and binding your View to it instead of coding logic in the code-behind of your View.
It's not appropriate to use the collection changed event in this way because it's only meant to be fired when adding/removing items from the collection. Which is why you've hit a wall. You're also in danger of breaking the Liskov substitution principle with this approach.
It's probably better to implement the INotifyPropertyChanged interface on your collection class and fire that event when one of your items fires its property changed event.
In my class I've got an ObservableCollection and I'm listening to its CollectionChanged event.
Thing is when I do Fou.ButtonData = an ObservableCollection variable , CollectionChanged event is never called and CreateButtons() never happens.
How can I make this work?
Thanks
The code
class Fou
{
private ObservableCollection<string> buttonData;
public ObservableCollection<string> ButtonData
{
get { return buttonData; }
set { buttonData = value; }
}
public Fou()
{
buttonData = new ObservableCollection<string>();
buttonData.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(buttonData_CollectionChanged);
}
void buttonData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
CreateButtons();
}
}
From the code sample it looks like the event is never raised because the ObservableCollection<string> is never actually changed. It is created, it's event assigned to and never actually modified after that. Is there some other code which modifies the collection.
Additionally there is a bug in the setter of ButtonData. You are allowing another piece of code to change the ObservableCollection<string> but you are not listening to it's CollectionChanged event nor are you disconnecting from the previous one. I would make this a readonly property or change it to the following
public ObservableCollection<string> ButtonData
{
get { return buttonData; }
set
{
if (buttonData != null) {
buttonData.CollectionChanged -= buttonData_CollectionChanged;
}
buttonData = value;
if (buttonData != null) {
buttonData.CollectionChanged += buttonData_CollectionChanged;
}
}
}