WPF binding to property of all items in a collection - c#

I need to bind to a bool property, that is only true, when one of the properties in the collection is true.
This is the binding:
<tk:BusyIndicator IsBusy="{Binding Tabs, Converter={StaticResource BusyTabsToStateConverter}}">
And the viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<Tab> _tabs;
public ObservableCollection<Tab> Tabs
{
get
{ return _tabs; }
set
{
if (value != _tabs)
{
_tabs = value;
NotifyPropertyChanged();
}
}
}
The Tab class also has property change notification:
public class Tab : INotifyPropertyChanged
{
public bool IsBusy { get{...} set{...NotifyPropertyChanged();} }
This is the converter:
public class BusyTabsToStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tabs = value as ObservableCollection<Tab>;
return tabs.Any(tab => tab.IsBusy);
}
}
The problem is, when Tab.IsBusy changes the binding source is not notified, because it is bound to the observable collection and not to the IsBusy property.
Is there a way to make the notification trigger correctly when the IsBusy property on any of the items in the collection changes?

Instead of a Binding Converter, you could have a AnyTabBusy property in MainWindowViewModel, for which a change notification is fired by a PropertyChanged event handler, which is attached or detached to individual elements from the Tabs collection when they are added to or removed from the collection.
In the example below, the Tabs property is readonly. If it has to be writeable, you would have to attach and detach the TabsCollectionChanged handler in the Tabs setter.
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Tab> Tabs { get; } = new ObservableCollection<Tab>();
public bool AnyTabBusy
{
get { return Tabs.Any(t => t.IsBusy); }
}
public MainWindowViewModel()
{
Tabs.CollectionChanged += TabsCollectionChanged;
}
private void TabsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Tab tab in e.NewItems)
{
tab.PropertyChanged += TabPropertyChanged;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Tab tab in e.OldItems)
{
tab.PropertyChanged -= TabPropertyChanged;
}
break;
default:
break;
}
}
private void TabPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Tab.IsBusy))
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(AnyTabBusy)));
}
}
}
If you want to make this code reusable, you could put it into a derived collection class like shown below, where you could attach a handler for an ItemPropertyChanged event.
public class ObservableItemCollection<T>
: ObservableCollection<T> where T : INotifyPropertyChanged
{
public event PropertyChangedEventHandler ItemPropertyChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (INotifyPropertyChanged item in e.NewItems)
{
item.PropertyChanged += OnItemPropertyChanged;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (INotifyPropertyChanged item in e.OldItems)
{
item.PropertyChanged -= OnItemPropertyChanged;
}
break;
default:
break;
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
}
The view model could now be reduced to this:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableItemCollection<Tab> Tabs { get; }
= new ObservableItemCollection<Tab>();
public bool AnyTabBusy
{
get { return Tabs.Any(t => t.IsBusy); }
}
public MainWindowViewModel()
{
Tabs.ItemPropertyChanged += TabPropertyChanged;
}
private void TabPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Tab.IsBusy))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AnyTabBusy)));
}
}
}

I've taken #Clemens' answer, and converted in to an extension method that could make it easier to use on multiple collections. It will take an PropertyChangedEventHandler and automatically add and remove it from the items in the collection as they are added and removed. If you up-vote this, please up-vote#Clemens' answer also, since it is based on his work.
Be careful not use use an anonymous method as your PropertyChanged handler (this goes for all event handlers in general, not just this solution) without taking special precautions, as they can be difficult to remove.
(Note: this requires C# 7, as it uses a local function to make dealing with the CollectionChanged handler's delegate easier.)
public static class ObservableCollectionExtensions
{
public static Hook<TList> RegisterPropertyChangeHook<TList>(this ObservableCollection<TList> collection, PropertyChangedEventHandler handler) where TList : INotifyPropertyChanged
{
void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (TList item in e.NewItems)
{
item.PropertyChanged += handler;
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (TList item in e.OldItems)
{
item.PropertyChanged -= handler;
}
break;
default:
break;
}
}
return new Hook<TList>(collection, Collection_CollectionChanged);
}
public class Hook<TList> where TList : INotifyPropertyChanged
{
internal Hook(ObservableCollection<TList> collection, NotifyCollectionChangedEventHandler handler)
{
_handler = handler;
_collection = collection;
collection.CollectionChanged += handler;
}
private NotifyCollectionChangedEventHandler _handler;
private ObservableCollection<TList> _collection;
public void Unregister()
{
_collection.CollectionChanged -= _handler;
}
}
}
You can use it like this:
void Main()
{
var list = new ObservableCollection<Animal>();
list.RegisterPropertyChangeHook(OnPropertyChange);
var animal = new Animal(); // Has a "Name" property that raises PropertyChanged
list.Add(animal);
animal.Name="Charlie"; // OnPropertyChange called
list.Remove(animal);
animal.Name="Sam"; // OnPropertyChange not called
}
private void OnPropertyChange(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine($"property changed: {e.PropertyName}");
}
If you want to be able to unregister the hook, do this:
var hook = list.RegisterPropertyChangeHook(OnPropertyChange);
hook.Unregister();
Unregistering ended up being trickier than I expected, due to extension method classes not supporting generics. It uses the "memento" pattern to return an object that you can use to unregister later.

To propagate notification from Model to Collection of Model, You need to have a Notifiable property in Collection itself.
Maybe you can extend the ObservableCollection and have a Property in that which can notify the UI

There's no way to get this for free unfortunately. I would create an IsBusy property on MainWindowViewModel. When Tabs is set, add a listener for collection changes and have that update the IsBusy property.

Related

Run a method after editing a cell on a datagrid

I am using WPF and CaliburnMicro for binding data. I want to run an Add() method every time I finished editing a cell but the problem is the property for the datagrid does not execute it.
Here is my code:
<Window x:Class="DataGrid_NotifyOfPropertyChanged.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataGrid_NotifyOfPropertyChanged.Views"
mc:Ignorable="d"
Title="ShellView" Height="450" Width="800">
<Grid>
<DataGrid x:Name="Numbers" CanUserAddRows="False"/>
</Grid>
public class NumbersModel
{
public int Number { get; set; }
}
ShellViewModel
public class ShellViewModel: Screen
{
public ShellViewModel()
{
Numbers.Add(new NumbersModel { Number = 1 });
Numbers.Add(new NumbersModel { Number = 2 });
}
private BindableCollection<NumbersModel> _numbers = new BindableCollection<NumbersModel>();
public BindableCollection<NumbersModel> Numbers
{
get { return _numbers; }
set {
_numbers = value;
Add();
}
}
public void Add()
{
double result = 0;
foreach(var i in _numbers.ToList())
{
result += i.Number;
}
MessageBox.Show(result.ToString());
}
}
You must let NumbersModel implement the INotifyPropertyChanged interface.
This way you can get a notification when a property has changed. You generally always have to implement this interface on every class that serves as a binding source.
Data binding would work without this interface, but the performance will drastically degrade. For applications with many bindings, this will be an issue.
The solution is to listen to property changes of NumbersModel.Number. I therefore introduced a dedicated event NumberChanged.
The event NumberChanged is optional. You can also listen to PropertyChanged and then use a switch statement to filter the required property by property name. I think a dedicated event significantly increases readability and understanding of the context, opposed to a cluttered switch block.
To prevent memory leaks when removing items, you must unsubscribe from every event of NumbersModel your are listening to.
You therefore also need to listen to the CollectionChanged event of the Numbers collection:
NumbersModel.cs
public class NumbersModel : INotifyPropertyChanged
{
private int number;
public int Number
{
get => this.number;
set
{
if (value == this.number)
{
return;
}
this.number = value;
OnPropertyChanged();
OnNumberChanged();
}
}
public event EventHandler NumberChanged;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnNumberChanged()
{
this.NumberChanged?.Invoke(this, EventArgs.Empty);
}
}
ShellViewModel.cs
public class ShellViewModel : Screen
{
public ShellViewModel()
{
this.Numbers = new BindableCollection<NumbersModel>();
this.Numbers.Add(new NumbersModel {Number = 1});
this.Numbers.Add(new NumbersModel {Number = 2});
}
public void Add()
{
double result = this._numbers.Sum(numbersModel => numbersModel.Number);
MessageBox.Show(result.ToString());
}
private BindableCollection<NumbersModel> _numbers;
public BindableCollection<NumbersModel> Numbers
{
get => this._numbers;
set
{
// Unsubscribe from old collection
if (this.Numbers != null)
{
this.Numbers.CollectionChanged -= OnCollectionChanged;
}
this._numbers = value;
// Subscribe to new collection
if (this.Numbers != null)
{
this.Numbers.CollectionChanged += OnCollectionChanged;
Add();
}
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (NumbersModel newItem in e.NewItems.Cast<NumbersModel>())
{
newItem.NumberChanged += OnNumberChanged;
}
break;
}
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
{
foreach (NumbersModel newItem in e.OldItems.Cast<NumbersModel>())
{
newItem.NumberChanged -= OnNumberChanged;
}
break;
}
}
}
private void OnNumberChanged(object sender, EventArgs e)
{
Add();
}
}
I want to run an Add() method every time I finished editing a cell
The way to do this using Caliburn.Micro would be to use an EventTrigger and an ActionMessage to handle the CellEditEnding event:
<DataGrid x:Name="Numbers" CanUserAddRows="False"
cal:Message.Attach="[Event CellEditEnding] = [Action Add()]" />

How to get edited values in ObservableCollectionin WPF(MVVM)

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.

C# Nested Class

I have a class Animal. Within the class Animal I have a list of another class called Dogs.
class Animal
{
List<Dog> Dogs;
int CountNeutedDogs;
class Dog
{
// some properties
boolean neuted;
}
}
It's possible that the list can contain about 500 dogs at any one time. I want to have an event so that if the value of neuted property in a dog changes the CountNeutedDogs is informed and updated. How do I do this?
Edit
I should explain the list Dogs is bound to a datagrid. The user can change the neuted property from false to true and vice versa. I have a get & set for the property. So the neuted value can change and I need to update the CountNeutedDogs value which is why I was thinking of using an event but not sure how to do it.
If you make CountNeutedDogs a property like so, it will always be correct without events updating:
int CountNeutedDogs
{
get
{
return this.Dogs.Where(d => d.neuted).Count();
}
}
This is a follow-up on the Comments:
I made an example, (not tested!!) how you can solve this by implementing the ObservableCollection and INotifyPropertyChanged
public class Dog : INotifyPropertyChanged
{
private bool _isNeutered;
public bool IsNeutered
{
get { return _isNeutered; }
set
{
if (_isNeutered != value)
{
_isNeutered = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsNeutered"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Animals : INotifyPropertyChanged
{
private ObservableCollection<Dog> _dogs = new ObservableCollection<Dog>();
public Animals()
{
_dogs.CollectionChanged += Dogs_CollectionChanged;
}
// NOTE, I haven't checked this!! But I think it should be something like this:
private void Dogs_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
item.PropertyChanged += Item_PropertyChanged;
break;
case NotifyCollectionChangedAction.Reset:
case NotifyCollectionChangedAction.Remove:
foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
item.PropertyChanged -= Item_PropertyChanged;
break;
}
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("NeuteredDogsCount"));
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("NeuteredDogsCount"));
}
public IEnumerable<Dog> Dogs
{
get { return _dogs; }
}
public int NeuteredDogsCount
{
get
{
return Dogs.Where(dog => dog.IsNeutered).Count();
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So when the collection changes, the notify of the NeuteredDogsCount property is sent. This way the bounded controls will re-evaluate the NeuteredDogsCount property.
Instead of 'normal' properties, the IsNeutered can be changed to a DependencyProperty.

WPF / C# Updating property change of an item in an ObservableCollection to the ListBox

I have a list box:
<ListBox x:Name="lbxAF" temsSource="{Binding}">
that gets its data from this from this modified Observable Collection:
public ObservableCollectionEx<FileItem> folder = new ObservableCollectionEx<FileItem>();
which is created within a class that uses FileSystemWatcher to monitor a specific folder for addition, deletion and modification of files.
The ObservableCollection was modified (hence the Ex at the end) so that I can modify it from an outside thread (code is not mine, I actually did some searching through this website and found it, works like a charm):
// This is an ObservableCollection extension
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
// Override the vent so this class can access it
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
System.Collections.Specialized.NotifyCollectionChangedEventHandler eventHanlder = CollectionChanged;
if (eventHanlder == null)
return;
Delegate[] delegates = eventHanlder.GetInvocationList();
// Go through the invocation list
foreach (System.Collections.Specialized.NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread do this:
if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
// Else, execute handler as is
else
{
handler(this, e);
}
}
}
}
}
The collection is made up of these:
public class FileItem
{
public string Name { get; set; }
public string Path { get; set; }
}
which allow me to store names and paths of files.
Everything works great as far as deletion and addition of files, and the List Box gets updated flawlessly with respect to those two... however, if I change the name of any of the files, it doesn't update the list box.
How would I notify list box of the changes in FileItem's properties? I assumed that ObservableCollection would handle that, but apparently it raises flag only when FileItem is added or deleted, not when its contents are changed.
Your FileItem class should implement INotifyPropertyChanged. Below is a simple working implementation of it.
public class FileItem : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set {
if (_Name != value)
{
_Name = value;
OnPropertyChanged("Name");
}
}
}
private string _Path;
public string Path
{
get { return _Path; }
set {
if (_Path != value)
{
_Path = value;
OnPropertyChanged("Path");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
That's how the ObservableCollection works - it monitors only insertion/deletion/moving of items.
To update the View when each item (or FileItem, in your case) changes, the FileItem must implement INotifyPropertyChanged and fire the appropriate event when you set each property that you want to observe.
Here's an example of how to do this: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
Try this simple one:
public class NotifyObservableCollection<TItem> : ObservableCollection<TItem>
where TItem : class , INotifyPropertyChanged, new()
{
#region Fields
private Action _itemPropertyChanged;
#endregion
#region Constructor
public NotifyObservableCollection(Action itemPropertyChanged)
{
_itemPropertyChanged = itemPropertyChanged;
}
#endregion
#region Methods
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var notifyItem = item as INotifyPropertyChanged;
if (notifyItem != null)
{
notifyItem.PropertyChanged += ItemPropertyChanged;
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
var notifyItem = item as INotifyPropertyChanged;
if (notifyItem != null)
{
notifyItem.PropertyChanged -= ItemPropertyChanged;
}
}
}
base.OnCollectionChanged(e);
}
#endregion
#region Private Methods
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(_itemPropertyChanged!=null)
{
_itemPropertyChanged();
}
}
#endregion
}

Get changed item from CollectionChanged event using TrulyObservableCollection

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.

Categories