BindingList<T> INotifyPropertyChanged unexpected behavior - c#

Suppose, I have objects:
public interface ITest
{
string Data { get; set; }
}
public class Test1 : ITest, INotifyPropertyChanged
{
private string _data;
public string Data
{
get { return _data; }
set
{
if (_data == value) return;
_data = value;
OnPropertyChanged("Data");
}
}
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged;
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
and its holder:
private BindingList<ITest> _listTest1;
public BindingList<ITest> ListTest1 { get { return _listTest1 ?? (_listTest1 = new BindingList<ITest>() { RaiseListChangedEvents = true }); }
}
Also, I subscribe to ListChangedEvent
public MainWindow()
{
InitializeComponent();
ListTest1.ListChanged += new ListChangedEventHandler(ListTest1_ListChanged);
}
void ListTest1_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("ListChanged1: " + e.ListChangedType);
}
And 2 test handlers:
For adding object
private void AddITestHandler(object sender, RoutedEventArgs e)
{
ListTest1.Add(new Test1 { Data = Guid.NewGuid().ToString() });
}
and for changing
private void ChangeITestHandler(object sender, RoutedEventArgs e)
{
if (ListTest1.Count == 0) return;
ListTest1[0].Data = Guid.NewGuid().ToString();
//if (ListTest1[0] is INotifyPropertyChanged)
// MessageBox.Show("really pch");
}
ItemAdded occurs, but ItemChanged not. Inside seeting proprty "Data" I found that no subscribers for my event PropertyChanged:
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged; // h is null! why??
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
Digging deeper i took reflector and discover BindingList:
protected override void InsertItem(int index, T item)
{
this.EndNew(this.addNewPos);
base.InsertItem(index, item);
if (this.raiseItemChangedEvents)
{
this.HookPropertyChanged(item);
}
this.FireListChanged(ListChangedType.ItemAdded, index);
}
private void HookPropertyChanged(T item)
{
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
if (changed != null) // Its seems like null reference! really??
{
if (this.propertyChangedEventHandler == null)
{
this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.Child_PropertyChanged);
}
changed.PropertyChanged += this.propertyChangedEventHandler;
}
}
Where am I wrong? Or this is known bug and i need to find some workaround?
Thanks!

BindingList<T> doesn't check if each particular item implements INotifyPropertyChanged. Instead, it checks it once for the Generic Type Parameter. So if your BindingList<T> is declared as follows:
private BindingList<ITest> _listTest1;
Then ITest should be inherited fromINotifyPropertyChanged in order to get BindingList raise ItemChanged events.

I think we may not have the full picture from your code here, because if I take the ITest interface and Test1 class verbatim (edit Oops - not exactly - because, as Nikolay says, it's failing for you because you're using ITest as the generic type parameter for the BindingList<T> which I don't here) from your code and write this test:
[TestClass]
public class UnitTest1
{
int counter = 0;
[TestMethod]
public void TestMethod1()
{
BindingList<Test1> list = new BindingList<Test1>();
list.RaiseListChangedEvents = true;
int evtCount = 0;
list.ListChanged += (object sender, ListChangedEventArgs e) =>
{
Console.WriteLine("Changed, type: {0}", e.ListChangedType);
++evtCount;
};
list.Add(new Test1() { Data = "yo yo" });
Assert.AreEqual(1, evtCount);
list[0].Data = "ya ya";
Assert.AreEqual(2, evtCount);
}
}
The test passes correctly - with evtCount ending up at 2, as it should be.

I found in constructor some interesting things:
public BindingList()
{
// ...
this.Initialize();
}
private void Initialize()
{
this.allowNew = this.ItemTypeHasDefaultConstructor;
if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) // yes! all you're right
{
this.raiseItemChangedEvents = true;
foreach (T local in base.Items)
{
this.HookPropertyChanged(local);
}
}
}
Quick fix 4 this behavior:
public class BindingListFixed<T> : BindingList<T>
{
[NonSerialized]
private readonly bool _fix;
public BindingListFixed()
{
_fix = !typeof (INotifyPropertyChanged).IsAssignableFrom(typeof (T));
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (RaiseListChangedEvents && _fix)
{
var c = item as INotifyPropertyChanged;
if (null!=c)
c.PropertyChanged += FixPropertyChanged;
}
}
protected override void RemoveItem(int index)
{
var item = base[index] as INotifyPropertyChanged;
base.RemoveItem(index);
if (RaiseListChangedEvents && _fix && null!=item)
{
item.PropertyChanged -= FixPropertyChanged;
}
}
void FixPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!RaiseListChangedEvents) return;
if (_itemTypeProperties == null)
{
_itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
}
var propDesc = _itemTypeProperties.Find(e.PropertyName, true);
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), propDesc));
}
[NonSerialized]
private PropertyDescriptorCollection _itemTypeProperties;
}
Thanks for replies!

The type of elements that you parameterize BindingList<> with (ITest in your case) must be inherited from INotifyPropertyChanged. Options:
Change you inheritance tree ITest: INotifyPropertyChanged
Pass concrete class to the generic BindingList

Related

How to get Type from PropertyChanged event?

I will explain what I am trying to do first.
I have a quite a few of DataGrids and each DataGrid use different classes for there data type and instead of subscribing an Event handler for each one, I was hoping to make a generic event handler and get the type from from the sender object.
I am using EntityFramework Database First
One example of one of the classes:
public partial class StaffData : INotifyPropertyChanged
{
public long ID { get; set; }
public string StaffNameFirst { get; set; }
public string StaffNameSecond { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My ViewModel:
VeiwModelBase holds the INotifyPropertyChanged data.
public class MasterViewModel : ViewModelBase
{
public static ObservableCollection<StaffData> MasterDataBinding
{
get { return _mMasterData; }
private set
{
if (value == _mMasterData)
return;
_mMasterData = value;
OnPropertyChanged();
}
}
public MasterViewModel()
{
_mMasterData.CollectionChanged += master_CollectionChanged;
}
public void master_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//asign PropertyChanged event here
}
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
SaveData(newRowData);
}
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
dataBaseEntities.SaveChanges();
}
}
These are the two methods above which I can't seem to figure this out, I have tried countless ways using Getype with not much success (I left my last attempt in hopefully to illustrate what I am trying to do). I have commented out how I am normally going about this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
//var newRowData = sender as StaffData
SaveData(newRowData);
}
//private static void SaveData(StaffData newRowData)
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
//var originalData = dataBaseEntities.StaffData.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
entities.SaveChanges();
}
When trying to use the variable as a type I get this error,
Error CS0118 'foo' is a variable but is used like a
type
Is there a way to get the type when you don't know which datagrid will implement the PropertyChanged event and use it so as you can make a generic event handler for all the Datagrid controls?
Or am I going about this the wrong way?
Not sure if I really understand your question, but you could check the type of the sender argument at runtime and call an appropriate method like this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is StaffData)
{
DoSomething((StaffData)sender);
}
else if (sender is SomeOtherData)
{
DoSomething((SomeOtherData)sender);
}
...
}
private void DoSomething(StaffData data)
{
...
}
private void DoSomething(SomeOtherData data)
{
...
}
However, I'd prefer to have different PropertyChanged handler methods for different sender types.
You cant get the type inside the propertyChanged event handler but you can get the property name from PropertyChangedEventArgs.
Something like:
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "SomePropertyName")
{
//... do your stuf
}
}

How to update a ListBox if an element was changed c#

Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)

ISet<T> that notifies on add and remove?

I'd like an ISet<T> with two additional events ItemAdded and ItemRemoved.
One option I considered was deriving MyHashSet<T> from HashSet<T> but since Add and Remove are not virtual, it would require the use of new. Maybe this is a valid use of the keyword?
Another option I thought would be to implement ISet<T> and delegate everything to a private instance of HashSet<T>. This feels like a bulky solution.
Is there a pattern or framework class that gets me the same result but doesn't require less than elegant/ideal coding?
Based on the comments I received (thanks) here's what I've got:
public class NotifyingHashSet<T>
{
private HashSet<T> hashSet = new HashSet<T>();
public bool Add(T item)
{
bool added = hashSet.Add(item);
if(added && ItemAdded != null)
{
ItemAdded(this, new NotifyingHashSetEvent<T>(item));
}
return added;
}
public bool Remove(T item)
{
bool removed = hashSet.Remove(item);
if(removed && ItemRemoved != null)
{
ItemRemoved(this, new NotifyingHashSetEvent<T>(item));
}
return removed;
}
public event EventHandler<NotifyingHashSetEvent<T>> ItemAdded;
public event EventHandler<NotifyingHashSetEvent<T>> ItemRemoved;
}
public class NotifyingHashSetEvent<T> : EventArgs
{
public NotifyingHashSetEvent(T item)
{
Item = item;
}
public T Item { get; set; }
}
I would recommend inheriting rather than composing in this case.
This will ensure that you get all that HashSet offers like:
Other collection methods such as Contains and other Set operations such as IsSubsetOf
Collection initializers
You could assign it to base type, HashSet<int> foo = new NotifyingHashSet<int>()
My implementation looks like this:
public class NotifyingHashSet<T> : HashSet<T>
{
public new void Add(T item)
{
OnItemAdded(new NotifyHashSetChanged<T>(item));
base.Add(item);
}
public new void Remove(T item)
{
OnItemRemoved(new NotifyHashSetChanged<T>(item));
base.Remove(item);
}
public event EventHandler<NotifyHashSetChanged<T>> ItemAdded;
public event EventHandler<NotifyHashSetChanged<T>> ItemRemoved;
protected virtual void OnItemRemoved(NotifyHashSetChanged<T> e)
{
if (ItemRemoved != null) ItemRemoved(this, e);
}
protected virtual void OnItemAdded(NotifyHashSetChanged<T> e)
{
if (ItemAdded != null) ItemAdded(this, e);
}
}
public class NotifyHashSetChanged<T> : EventArgs
{
private readonly T _item;
public NotifyHashSetChanged(T item)
{
_item = item;
}
public T ChangedItem
{
get { return _item; }
}
}
Some tests to check stuff:
[TestClass]
public class NotifyingHashSetTests
{
[TestMethod]
public void ShouldAddToNotifyingHashSet()
{
var notifyingHashSet = new NotifyingHashSet<int>();
notifyingHashSet.ItemAdded += (sender, changed) => Assert.AreEqual(changed.ChangedItem, 1);
notifyingHashSet.Add(1);
}
[TestMethod]
public void ShouldRemoveFromNotifyingHashSet()
{
//can use collection initializer
var notifyingHashSet = new NotifyingHashSet<int> { 1 };
notifyingHashSet.ItemRemoved += (sender, changed) => Assert.AreEqual(changed.ChangedItem, 1);
notifyingHashSet.Remove(1);
}
[TestMethod]
public void ShouldContainValueInNotifyingHashSet()
{
//can use collection initializer
var notifyingHashSet = new NotifyingHashSet<int> { 1 };
Assert.IsTrue(notifyingHashSet.Contains(1));
}
[TestMethod]
public void ShouldAssignToHashSet()
{
HashSet<int> notifyingHashSet = new NotifyingHashSet<int> { 1 };
Assert.IsTrue(notifyingHashSet.IsSubsetOf(new List<int>{ 1,2 }));
}
}
Your own answer demonstrates how you can wrap a HashSet<T> and Srikanth's answer demonstrates how you can derive from HashSet<T>. However, when you derive from HashSet<T> you have to make sure the new class also correctly implements the Add and Remove methods of the ICollection<T> interface. So I have modified Srikanth's answer to properly create an ISet<T> implementation with notifications that derives from HashSet<T> by using explicit interface implementation of the relevant methods of ICollection<T>:
public class NotifyingHashSet<T> : HashSet<T>, ICollection<T> {
new public void Add(T item) {
((ICollection<T>) this).Add(item);
}
new public Boolean Remove(T item) {
return ((ICollection<T>) this).Remove(item);
}
void ICollection<T>.Add(T item) {
var added = base.Add(item);
if (added)
OnItemAdded(new NotifyHashSetEventArgs<T>(item));
}
Boolean ICollection<T>.Remove(T item) {
var removed = base.Remove(item);
if (removed)
OnItemRemoved(new NotifyHashSetEventArgs<T>(item));
return removed;
}
public event EventHandler<NotifyHashSetEventArgs<T>> ItemAdded;
public event EventHandler<NotifyHashSetEventArgs<T>> ItemRemoved;
protected virtual void OnItemRemoved(NotifyHashSetEventArgs<T> e) {
var handler = ItemRemoved;
if (handler != null)
ItemRemoved(this, e);
}
protected virtual void OnItemAdded(NotifyHashSetEventArgs<T> e) {
var handler = ItemAdded;
if (handler != null)
ItemAdded(this, e);
}
}
public class NotifyHashSetEventArgs<T> : EventArgs {
public NotifyHashSetEventArgs(T item) {
Item = item;
}
public T Item { get; private set; }
}
This class also behaves the same way as your class by only firing events when an element actually is added or removed from the set. E.g., adding the same element twice in succession will only fire one event.

I can't register event CollectionChanged of ObservableCollection

I try to run some code when collection is changed. I keep collection as property in Data class:
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set { deviceAdjustedConfigurations = value; }
}
and register it in code like that:
Data.DeviceAdjustedConfigurations.CollectionChanged += new NotifyCollectionChangedEventHandler(DeviceAdjustedConfigurationsCollectionChanged);
But after registration CollectionChanged is null and the appropriate code in delegated method is not run. In this place DeviceAdjustedConiguration already contains some data. What am I doing wrong?
You should avoid having a set property accessor for collection types, one reason being the one you experienced here with events. Another problem is if someone caches the collection and adds items to it later.
var old = obj.DeviceAdjustedConfigurations;
obj.DeviceAdjustedConfigurations = new ObservableCollection<OfferedConfiguration>();
old.Add(new OfferedConfiguration()); // what should happen here?
instead, remove the set-accessor and use the existing collection directly.
obj.DeviceAdjustedConfigurations.Add(new OfferedConfiguration());
If you really need to set the collection, you need to handle this with for instance a property change event from the class that owns the DeviceAdjustedConfigurations.
public class Item
{
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set
{
if (deviceAdjustedConfigurations != value)
{
onDeviceConfigurationsChanging(deviceAdjustedConfigurations, value);
deviceAdjustedConfigurations = value;
}
}
}
public static event EventHandler<ConfigurationChangedEventArgs> DeviceConfigurationsChanging;
private static void onDeviceConfigurationsChanging(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
var handler = DeviceConfigurationsChanging;
if (handler != null)
{
handler(null, new ConfigurationChangedEventArgs(oldList, newList));
}
}
}
public class ConfigurationChangedEventArgs : EventArgs
{
public ConfigurationChangedEventArgs(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
OldList = oldList;
NewList = newList;
}
public ObservableCollection<OfferedConfiguration> OldList { get; private set; }
public ObservableCollection<OfferedConfiguration> NewList { get; private set; }
}
public class Consumer
{
public void foo()
{
Item.DeviceConfigurationsChanging += updateEvents;
}
private void updateEvents(object sender, ConfigurationChangedEventArgs args)
{
args.OldList.CollectionChanged -= onCollectionChanged;
args.NewList.CollectionChanged += onCollectionChanged;
}
private void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { }
}

List<T> firing Event on Change

I created a Class EventList inheriting List which fires an Event each time something is Added, Inserted or Removed:
public class EventList<T> : List<T>
{
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
public new void Add(T item)
{
base.Add(item);
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
...
}
At the Moment I use it as a Property like this:
public EventList List
{
get { return m_List; }
set
{
m_List.ListChanged -= List_ListChanged;
m_List = value;
m_List.ListChanged += List_ListChanged;
List_ListChanged();
}
}
Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?
Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.
You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChanged interface inherited)
private readonly ObservableCollection<T> list;
public ctor() {
list = new ObservableCollection<T>();
list.CollectionChanged += listChanged;
}
public ObservableCollection<T> List { get { return list; } }
public void Clear() { list.Clear(); }
private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
// list changed
}
This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.
With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)
So the code above can be simplified to
public ctor() {
List = new ObservableCollection<T>();
List.CollectionChanged += OnListChanged;
}
public ObservableCollection<T> List { get; }
public void Clear()
{
List.Clear();
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// react to list changed
}
ObservableCollection is a List with a CollectionChanged event
ObservableCollection.CollectionChanged Event
For how to wire up the event handler see answer from Patrick. +1
Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.
public class ObservableCollection<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservableCollection<T> vm)
{
// this will fire on add, remove, and change
// if want to prevent an insert this in not the right spot for that
// the OPs use of word prevent is not clear
// -1 don't be a hater
vm.NotifyPropertyChanged("Ts");
}
public ObservableCollection()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}
If you do not want to or can not convert to an Observable Collection, try this:
public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
private List<T> list; // initialize this in your constructor.
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
private void notify()
{
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
public new void Add(T item)
{
list.Add(item);
notify();
}
public List<T> Items {
get { return list; }
set {
list = value;
notify();
}
}
...
}
Now, for your property, you should be able to reduce your code to this:
public EventList List
{
get { return m_List.Items; }
set
{
//m_List.ListChanged -= List_ListChanged;
m_List.Items = value;
//m_List.ListChanged += List_ListChanged;
//List_ListChanged();
}
}
Why? Setting anything in the EventList.Items will call your private notify() routine.
I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.
using System;
using System.Collections;
using System.Collections.Generic;
namespace YourNamespace
{
public class ObjectDoesNotMatchTargetBaseTypeException : Exception
{
public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
: base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
targetType, actualObject.GetType()))
{
}
}
/// <summary>
/// Allows you to react, when items were added or removed to a generic List.
/// </summary>
public abstract class NoisyList<TItemType> : List<TItemType>, IList
{
#region Public Methods
/******************************************/
int IList.Add(object item)
{
CheckTargetType(item);
Add((TItemType)item);
return Count - 1;
}
void IList.Remove(object item)
{
CheckTargetType(item);
Remove((TItemType)item);
}
public new void Add(TItemType item)
{
base.Add(item);
OnItemAdded(item);
}
public new bool Remove(TItemType item)
{
var result = base.Remove(item);
OnItemRemoved(item);
return result;
}
#endregion
# region Private Methods
/******************************************/
private static void CheckTargetType(object item)
{
var targetType = typeof(TItemType);
if (item.GetType().IsSubclassOf(targetType))
throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
}
#endregion
#region Abstract Methods
/******************************************/
protected abstract void OnItemAdded(TItemType addedItem);
protected abstract void OnItemRemoved(TItemType removedItem);
#endregion
}
}
If an ObservableCollection is not the solution for you, you can try that:
A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.
public class ChangeListCountEventArgs : EventArgs
{
public int NewCount
{
get;
set;
}
public ChangeListCountEventArgs(int newCount)
{
NewCount = newCount;
}
}
B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:
public class CustomList<T> : List<T>
{
public event EventHandler<ChangeListCountEventArgs> ListCountChanged;
public new int Count
{
get
{
ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
return base.Count;
}
}
public CustomList()
{ }
public CustomList(List<T> list) : base(list)
{ }
public CustomList(CustomList<T> list) : base(list)
{ }
}
C) Finally subscribe to your event:
var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) =>
{
// get the count thanks to e.NewCount
};

Categories