Triggering an event in C# using INotifyPropertyChanged - c#

I'm new to C# and I'm trying to get a change in my datagrid to trigger a method in my main.
I've got my class:
public class siteMatch : INotifyPropertyChanged
{
public string SelectedTag
{
get { return _SelectedTag; }
set
{
if (value != _SelectedTag)
{
_SelectedTag = value;
OnPropertyChanged(_SelectedTag);
}
}
}
private string _SelectedTag;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
And I've got this in my main class:
public partial class MainWindow : Window
{
public ObservableCollection<siteMatch> sitesMatched = new ObservableCollection<siteMatch>();
public MainWindow()
{
InitializeComponent();
sitesMatched.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(items_CollectionChanged);
}
static void items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
}
Am I missing something here? The items_CollectionChanged method doesn't get triggered when I change something on the datagrid. I have a feeling that I haven't subscribed to the event properly. The OnPropertyChanged method gets triggered correctly but nothing happens after that.
Any help will be greatly appreciated.
Thanks,
Edit: I should point out that I only have access to .Net 4

It is my understanding that the Collection Changed is only triggered on creation of the collection, or in addition or deletion, but not when you change something inside the collection.
What you want to accomplish can be done with the events on the Datagrid itself,
e.g. by using the DataGridView.CellValueChanged Event.
Another solution is for you to expand the ObservableCollection class.
Have a look here : https://stackoverflow.com/a/5256827/3042778

Related

INotifyPropertyChanged in c# structures

Hello I have the following code:
public struct zoomInfo : INotifyPropertyChanged
{
public float zoom;
public float translateX;
public float translateY;
private int level;
public int zoomLevel
{
get { return level; }
set {
level = value;
OnPropertyChanged("zoomLevel");
}
}
//region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//endregion
};
When I bind this to a control.. it is not working. PropertyChangedEventHandler is always null.. But when I change this to class instead of struct, PropertyChangedEventHandler is not null and binding works perfectly. So the question is, does INotifyPropertyChanged only works on classes?
You can implement INotifyPropertyChanged in a struct, but you should never do that, because semantics of structs do not play well (I'd say at all) with this interface, and events in general. Consider this:
struct EventStruct : INotifyPropertyChanged {
private string _property;
public string Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now we just subscribe to event and change a property:
class Program {
static void Main() {
var s = new EventStruct();
s.PropertyChanged += OnPropertyChanged;
s.Property = "test";
Console.ReadKey();
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
Console.WriteLine(e.PropertyName + " changed");
}
}
Output is "Property changed". So you cannot say that structs and events do not work at all (or that INotifyPropertyChanged doesn't work with structs). It works, kind of, until you try to pass that struct anywhere:
class Program {
static void Main() {
var s = new EventStruct();
Bind(s);
s.Property = "test";
Console.ReadKey();
}
static void Bind(INotifyPropertyChanged item) {
// this is not the same instance of EventStruct,
// it's a copy, and event will never be fired on this copy
item.PropertyChanged += OnPropertyChanged;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
Console.WriteLine(e.PropertyName + " changed");
}
}
Passing struct to Bind method as INotifyPropertyChanged boxes that struct, which makes a copy, so you subscribe to PropertyChanged event using one instance of EventStruct (a copy), but event is fired on another instance of the struct. So event subscriber list is empty on that instance where event is actually fired. Same happens if you just pass struct and not interface (in this case struct is copied because passed by value):
static void Bind(EventStruct item) {
item.PropertyChanged += OnPropertyChanged;
}
If you will pass by reference, it will work again:
static void Bind(ref EventStruct item) {
item.PropertyChanged += OnPropertyChanged;
}
For that reason you should never implement events on structs, at least no use case comes to my mind where this can be useful and not lead into troubles.

PropertyChanged event no longer work after deserialization

I am using a class TrulyObservableCollection as listed below. I'm adding an event listener to the PropertyChanged event and that works fine initially. However as soon as I serialize and deserialize the collection and add my event listener then, the events no longer come through as can be seen from the unit test below. Can anyone explain why?
This is the class:
[Serializable]
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
[field:NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public TrulyObservableCollection() { init(); }
private void init()
{
CollectionChanged += TrulyObservableCollection_CollectionChanged;
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (Object item in e.NewItems)
(item as INotifyPropertyChanged).PropertyChanged += item_PropertyChanged;
if (e.OldItems != null)
foreach (Object item in e.OldItems)
(item as INotifyPropertyChanged).PropertyChanged -= item_PropertyChanged;
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(e.PropertyName));
}
}
Here is the unit test. SIEESerializer.Clone() does a binarize serialize and deserialize.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
TrulyObservableCollection<SomeData> tt = new TrulyObservableCollection<SomeData>();
tt.Add(new SomeData());
tt = SIEESerializer.Clone(tt) as TrulyObservableCollection<SomeData>;
tt.PropertyChanged += (s, e) =>
{
string ttt = ""; // we never get here..
};
tt[0].Master = true;
}
}
[Serializable]
public class SomeData : INotifyPropertyChanged
{
private bool master;
public bool Master
{
get { return master; }
set { master = value; SendPropertyChanged(); }
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged([CallerMemberName]string propertyName = null)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Updating UI Bound to Dependant Properties

I have several properties that return values dependent on another property's value. What is the best way to update the UI bound to a dependent property? See example below, how would you update a UI element bound to the TotalQuantity property of the Parent object when a Quantity in the Children collection changes?
public class Child : INotifyPropertyChanged
{
private int quantity;
public int Quantity
{
get
{
return quantity;
}
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
public class ParentObject : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
public ObservableCollection<Child> Children
{
get
{
return children;
}
set
{
children = value;
OnPropertyChanged("Children");
}
}
public int TotalQuantity
{
get
{
return Children.Sum(c => c.Quantity);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
When you instantiate your ObservableCollection, you need to subscribe to the CollectionChanged event.
Children.CollectionChanged += Children_CollectionChanged;
This will be called when an item is added/removed from the collection. You simply need to notify that TotalQuantity has changed.
void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("TotalQuantity");
}
In the case where you need to update the TotalQuantity property on the UI whenever a child changes, then you simply need to subscribe to the child's PropertyChanged event.
So, when you add an item to the collection, subscribe to the event:
Child myChild = new Child(); //Just an example, but you get the idea
myChild.PropertyChanged += myChild_PropertyChanged;
And in your event handler, you can test to see what property has changed.
void myChild_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Quantity")
OnPropertyChanged("TotalQuantity");
}
Or, you could just call OnPropertyChanged without checking if you're a badass. But I wouldn't recommend it just in case your model gains more properties in future.
Don't forget to unsubscribe the event before you remove the child from the collection.

Using INotifyPropertyChanged to calculate a total

I am having trouble with getting a total count calculation to run when the value of one of the items in a collection is changed. I do have the INotifyPropertChanged event on the class and the collection is an ObservableCollection<T>. I have also tried a BindingList<T> without luck.
public class Inventory
{
public ObservableCollection<Item> Items { get; set; }
public int TotalCount {
get { return Items.Select(i => i.Count).Sum(); }
}
}
public class Item : INotifyPropertyChanged {
private int count;
public int Count {
get { return count; }
set {
count = value;
NotifyPropertyChanged("Count");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
My next though was to register an event handler for each item that is added to the collection, but I am not quite sure how to manage the events that may have already been registered. It seems I am missing something simple here, can anyone point it out?
BTW, I am data binding in WPF to this class.
You need to handle CollectionChanged event, subscribe to newly added items and unsubscribe from deleted items. This is a tedious task to do it every time, so you can subclass ObservableCollection and override OnCollectionChanged.
For an example implementation, see GitHub/Alba.Framework/NotifyingCollection(T).cs (it has some dependencies within the project, so you won't be able to copy-paste it though). There's a simpler implementation on StackOverflow somewhere, but I can't find it at the moment.
Here is the code for that "tedious task" ... first in the Inventory constructor:
Items.CollectionChanged += Items_CollectionChanged;
Then in the body of the Inventory class:
void Items_CollectionChanged(object s, NotifyCollectionChangedEventArgs e) {
if (e.OldItems != null)
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
if (e.NewItems != null)
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
}
void ItemPropertyChanged(object s, PropertyChangedEventArgs e) {
if (e.PropertyName == "Count")
NotifyPropertyChanged("TotalCount");
}

ObservableCollection and Item PropertyChanged

I've seen lots of talk about this question but maybe I'm just too much of a newbie to get it. If I have an observable collection that is a collection of "PersonNames" as in the msdn example (http: //msdn.microsoft.com/en-us/library/ms748365.aspx), I get updates to my View if a PersonName is added or removed, etc. I want to get an update to my View when I change a property in the PersonName as well. Like if I change the first name. I can implement OnPropertyChanged for each property and have this class derive from INotifyPropertyChanged and that seems to get called as expected.
My question is, how does the View get the updated data from the ObservableCollection as the property changed does not cause any event for the ObservableCollection?
This is probably something really simple but why I can't seem to find an example surprises me. Can anyone shed any light on this for me or have any pointers to examples I would greatly appreciate it. We have this scenario in multiple places in our current WPF app and are struggling with figuring it out.
"Generally, the code responsible for displaying the data adds a PropertyChanged event handler to each object currently displayed onscreen."
Could someone please give me an example of what this means? My View binds to my ViewModel which has a ObservableCollection. This collection is made up of a RowViewModel which has properties that support the PropertiesChanged event. But I can't figure out how to make the collection update itself so my view will be updated.
Here is how you would attach/detach to each item's PropertyChanged event.
ObservableCollection<INotifyPropertyChanged> items = new ObservableCollection<INotifyPropertyChanged>();
items.CollectionChanged += items_CollectionChanged;
static void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += item_PropertyChanged;
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
We wrote this in the WPF-chat:
public class OcPropertyChangedListener<T> : INotifyPropertyChanged where T : INotifyPropertyChanged
{
private readonly ObservableCollection<T> _collection;
private readonly string _propertyName;
private readonly Dictionary<T, int> _items = new Dictionary<T, int>(new ObjectIdentityComparer());
public OcPropertyChangedListener(ObservableCollection<T> collection, string propertyName = "")
{
_collection = collection;
_propertyName = propertyName ?? "";
AddRange(collection);
CollectionChangedEventManager.AddHandler(collection, CollectionChanged);
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddRange(e.NewItems.Cast<T>());
break;
case NotifyCollectionChangedAction.Remove:
RemoveRange(e.OldItems.Cast<T>());
break;
case NotifyCollectionChangedAction.Replace:
AddRange(e.NewItems.Cast<T>());
RemoveRange(e.OldItems.Cast<T>());
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
Reset();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void AddRange(IEnumerable<T> newItems)
{
foreach (T item in newItems)
{
if (_items.ContainsKey(item))
{
_items[item]++;
}
else
{
_items.Add(item, 1);
PropertyChangedEventManager.AddHandler(item, ChildPropertyChanged, _propertyName);
}
}
}
private void RemoveRange(IEnumerable<T> oldItems)
{
foreach (T item in oldItems)
{
_items[item]--;
if (_items[item] == 0)
{
_items.Remove(item);
PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName);
}
}
}
private void Reset()
{
foreach (T item in _items.Keys.ToList())
{
PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName);
_items.Remove(item);
}
AddRange(_collection);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
private class ObjectIdentityComparer : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return object.ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
}
}
}
public static class OcPropertyChangedListener
{
public static OcPropertyChangedListener<T> Create<T>(ObservableCollection<T> collection, string propertyName = "") where T : INotifyPropertyChanged
{
return new OcPropertyChangedListener<T>(collection, propertyName);
}
}
Weak events
Keeps track of the same item being added multiple times to the collection
It ~bubbles~ up the property changed events of the children.
The static class is just for convenience.
Use it like this:
var listener = OcPropertyChangedListener.Create(yourCollection);
listener.PropertyChanged += (sender, args) => { //do you stuff}
Bill,
I'm sure that you have found a workaround or solution to your issue by now, but I posted this for anyone with this common issue. You can substitute this class for ObservableCollections that are collections of objects that implement INotifyPropertyChanged. It is kind of draconian, because it says that the list needs to Reset rather than find the one property/item that has changed, but for small lists the performance hit should be unoticable.
Marc
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace WCIOPublishing.Helpers
{
public class ObservableCollectionWithItemNotify<T> : ObservableCollection<T> where T: INotifyPropertyChanged
{
public ObservableCollectionWithItemNotify()
{
this.CollectionChanged += items_CollectionChanged;
}
public ObservableCollectionWithItemNotify(IEnumerable<T> collection) :base( collection)
{
this.CollectionChanged += items_CollectionChanged;
foreach (INotifyPropertyChanged item in collection)
item.PropertyChanged += item_PropertyChanged;
}
private void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e != null)
{
if(e.OldItems!=null)
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= item_PropertyChanged;
if(e.NewItems!=null)
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += item_PropertyChanged;
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var reset = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(reset);
}
}
}
As you found out, there is no collection-level event that indicates that a property of an item in the collection has changed. Generally, the code responsible for displaying the data adds a PropertyChanged event handler to each object currently displayed onscreen.
Instead of ObservableCollection simply use the BindingList<T>.
The following code shows a DataGrid binding to a List and to item's properties.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Values" Binding="{Binding Value}" />
</DataGrid.Columns>
</DataGrid>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1 {
public partial class MainWindow : Window {
public MainWindow() {
var c = new BindingList<Data>();
this.DataContext = c;
// add new item to list on each timer tick
var t = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
t.Tick += (s, e) => {
if (c.Count >= 10) t.Stop();
c.Add(new Data());
};
t.Start();
}
}
public class Data : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
System.Timers.Timer t;
static Random r = new Random();
public Data() {
// update value on each timer tick
t = new System.Timers.Timer() { Interval = r.Next(500, 1000) };
t.Elapsed += (s, e) => {
Value = DateTime.Now.Ticks;
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
};
t.Start();
}
public long Value { get; private set; }
}
}
Following is the code giving a simple explanation of answer by #Stack and showing how BindingList is observing if it has a item changed and shows ObservableCollection will not observe the change inside an item.
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace BindingListExample
{
class Program
{
public ObservableCollection<MyStruct> oc = new ObservableCollection<MyStruct>();
public System.ComponentModel.BindingList<MyStruct> bl = new BindingList<MyStruct>();
public Program()
{
oc.Add(new MyStruct());
oc.CollectionChanged += CollectionChanged;
bl.Add(new MyStruct());
bl.ListChanged += ListChanged;
}
void ListChanged(object sender, ListChangedEventArgs e)
{
//Observe when the IsActive value is changed this event is triggered.
Console.WriteLine(e.ListChangedType.ToString());
}
void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Observe when the IsActive value is changed this event is not triggered.
Console.WriteLine(e.Action.ToString());
}
static void Main(string[] args)
{
Program pm = new Program();
pm.bl[0].IsActive = false;
}
}
public class MyStruct : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isactive;
public bool IsActive
{
get { return isactive; }
set
{
isactive = value;
NotifyPropertyChanged("IsActive");
}
}
private void NotifyPropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}

Categories

Resources