How to handle add to list event?
Asked Answered
K

10

48

I have a list like this:

List<Controls> list = new List<Controls>

How to handle adding new position to this list?

When I do:

myObject.myList.Add(new Control());

I would like to do something like this in my object:

myList.AddingEvent += HandleAddingEvent

And then in my HandleAddingEvent delegate handling adding position to this list. How should I handle adding new position event? How can I make this event available?

Kazue answered 19/8, 2009 at 13:28 Comment(0)
S
49

You could inherit from List and add your own handler, something like

using System;
using System.Collections.Generic;

namespace test
{
    class Program
    {

        class MyList<T> : List<T>
        {

            public event EventHandler OnAdd;

            public new void Add(T item) // "new" to avoid compiler-warnings, because we're hiding a method from base-class
            {
                if (null != OnAdd)
                {
                    OnAdd(this, null);
                }
                base.Add(item);
            }
        }

        static void Main(string[] args)
        {
            MyList<int> l = new MyList<int>();
            l.OnAdd += new EventHandler(l_OnAdd);
            l.Add(1);
        }

        static void l_OnAdd(object sender, EventArgs e)
        {
            Console.WriteLine("Element added...");
        }
    }
}

Warning

  1. Be aware that you have to re-implement all methods which add objects to your list. AddRange() will not fire this event, in this implementation.

  2. We did not overload the method. We hid the original one. If you Add() an object while this class is boxed in List<T>, the event will not be fired!

MyList<int> l = new MyList<int>();
l.OnAdd += new EventHandler(l_OnAdd);
l.Add(1); // Will work

List<int> baseList = l;
baseList.Add(2); // Will NOT work!!!
Seve answered 19/8, 2009 at 13:33 Comment(5)
MattH is correct, that'd be the better solution. BindingList<T> would also be a solution. msdn.microsoft.com/en-us/library/ms132742.aspxCahra
ObservableCollection<T> raises its events after the action, not before. But the BindingList<T> has the AddingNew eventSumerian
Neither ObservableCollection or BindingList extend List. You will lose functionality if you use them.Haldan
ObservableCollection also has to be used on UI thread if it contains UI. https://mcmap.net/q/217726/-how-do-i-update-an-observablecollection-via-a-worker-threadKwapong
what is if (null != OnAdd) for ?Unbowed
E
83

I believe What you're looking for is already part of the API in the ObservableCollection(T) class. Example:

ObservableCollection<int> myList = new ObservableCollection<int>();

myList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
    delegate(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)                    
    {
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
        {
            MessageBox.Show("Added value");
        }
    }
);

myList.Add(1);
Embree answered 19/8, 2009 at 13:53 Comment(3)
ObservableCollection<T> looks like the correct way to accomplish this.Floater
ObservableCollection has to be used on UI thread if it contains UI. https://mcmap.net/q/217726/-how-do-i-update-an-observablecollection-via-a-worker-threadKwapong
ObservableCollection<> is almost a drop-in replacement, but of course it had to be missing methods like RemoveRange(), just to be difficult.Consumerism
S
49

You could inherit from List and add your own handler, something like

using System;
using System.Collections.Generic;

namespace test
{
    class Program
    {

        class MyList<T> : List<T>
        {

            public event EventHandler OnAdd;

            public new void Add(T item) // "new" to avoid compiler-warnings, because we're hiding a method from base-class
            {
                if (null != OnAdd)
                {
                    OnAdd(this, null);
                }
                base.Add(item);
            }
        }

        static void Main(string[] args)
        {
            MyList<int> l = new MyList<int>();
            l.OnAdd += new EventHandler(l_OnAdd);
            l.Add(1);
        }

        static void l_OnAdd(object sender, EventArgs e)
        {
            Console.WriteLine("Element added...");
        }
    }
}

Warning

  1. Be aware that you have to re-implement all methods which add objects to your list. AddRange() will not fire this event, in this implementation.

  2. We did not overload the method. We hid the original one. If you Add() an object while this class is boxed in List<T>, the event will not be fired!

MyList<int> l = new MyList<int>();
l.OnAdd += new EventHandler(l_OnAdd);
l.Add(1); // Will work

List<int> baseList = l;
baseList.Add(2); // Will NOT work!!!
Seve answered 19/8, 2009 at 13:33 Comment(5)
MattH is correct, that'd be the better solution. BindingList<T> would also be a solution. msdn.microsoft.com/en-us/library/ms132742.aspxCahra
ObservableCollection<T> raises its events after the action, not before. But the BindingList<T> has the AddingNew eventSumerian
Neither ObservableCollection or BindingList extend List. You will lose functionality if you use them.Haldan
ObservableCollection also has to be used on UI thread if it contains UI. https://mcmap.net/q/217726/-how-do-i-update-an-observablecollection-via-a-worker-threadKwapong
what is if (null != OnAdd) for ?Unbowed
H
28

What you need is a class that has events for any type of modification that occurs in the collection. The best class for this is BindingList<T>. It has events for every type of mutation which you can then use to modify your event list.

Hydrated answered 19/8, 2009 at 13:37 Comment(1)
In my experience AddingNew event wasn't always fired. ListChanged event with e.ListChangedType == ListChangedType.ItemAdded was more robustSumerian
T
18

You can't do this with List<T>. However, you can do it with ObservableCollection<T>. See ObservableCollection<T> Class.

Travel answered 19/8, 2009 at 13:34 Comment(4)
What is the difference using List or Collection if you still need to derive it? There isn't any events that raises when the collection changes.Pernicious
Good catch. I don't know what I was thinking of. It's ObservableCollection<T> that has the events.Travel
that is right :) we had to point out something. ObservableCollection<T> and BindingList<T> are in .Net 4.0; so if you need this speciality in .Net 3.5 or before you had to derive List or Collection and add custom events.Pernicious
@bahadirarslan - That is not correct, ObservableCollection is in .NET 3.0 and 3.5. Namespace: System.Collections.ObjectModel Assembly: WindowsBase (in WindowsBase.dll)Inebriant
B
7

To be clear: If you only need to observe the standard-functionalities you should use ObservableCollection(T) or other existing classes. Never rebuild something you already got.

..But.. If you need special events and have to go deeper, you should not derive from List! If you derive from List you can not overloead Add() in order to see every add.

Example:

public class MyList<T> : List<T>
{
    public void Add(T item) // Will show us compiler-warning, because we hide the base-mothod which still is accessible!
    {
        throw new Exception();
    }
}

public static void Main(string[] args)
{
    MyList<int> myList = new MyList<int>(); // Create a List which throws exception when calling "Add()"
    List<int> list = myList; // implicit Cast to Base-class, but still the same object

    list.Add(1);              // Will NOT throw the Exception!
    myList.Add(1);            // Will throw the Exception!
}

It's not allowed to override Add(), because you could mees up the functionalities of the base class (Liskov substitution principle).

But as always we need to make it work. But if you want to build your own list, you should to it by implementing the an interface: IList<T>.

Example which implements a before- and after-add event:

public class MyList<T> : IList<T>
{
    private List<T> _list = new List<T>();

    public event EventHandler BeforeAdd;
    public event EventHandler AfterAdd;

    public void Add(T item)
    {
        // Here we can do what ever we want, buffering multiple events etc..
        BeforeAdd?.Invoke(this, null);
        _list.Add(item);
        AfterAdd?.Invoke(this, null);
    }

    #region Forwarding to List<T>
    public T this[int index] { get => _list[index]; set => _list[index] = value; }
    public int Count => _list.Count;
    public bool IsReadOnly => false;
    public void Clear() => _list.Clear();
    public bool Contains(T item) => _list.Contains(item);
    public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
    public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
    public int IndexOf(T item) => _list.IndexOf(item);
    public void Insert(int index, T item) => _list.Insert(index, item);
    public bool Remove(T item) => _list.Remove(item);
    public void RemoveAt(int index) => _list.RemoveAt(index);
    IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
    #endregion
}

Now we've got all methods we want and didn't have to implement much. The main change in our code is, that our variables will be IList<T> instead of List<T>, ObservableCollection<T> or what ever.

And now the big wow: All of those implement IList<T>:

IList<int> list1 = new ObservableCollection<int>();
IList<int> list2 = new List<int>();
IList<int> list3 = new int[10];
IList<int> list4 = new MyList<int>();

Which brings us to the next point: Use Interfaces instead of classes. Your code should never depend on implementation-details!

Borodin answered 12/4, 2019 at 9:4 Comment(1)
How is this answer not marked as the solution? I'd rather do this than implementing from List tbh...Iolaiolande
F
2

One simple solution is to introduce an Add method for the list in your project and handle the event there. It doesn't answer the need for an event handler but can be useful for some small projects.

AddToList(item) // or
AddTo(list,item)
////////////////////////

void AddTo(list,item)
{
    list.Add(item);
    // event handling
}

instead of

list.Add(item);
Fluecure answered 26/12, 2014 at 19:19 Comment(0)
B
1

You cannot do this with standard collections out of the box - they just don't support change notifications. You could build your own class by inheriting or aggregating a existing collection type or you could use BindingList<T> that implements IBindingList and supports change notifications via the ListChanged event.

Beedon answered 19/8, 2009 at 13:43 Comment(1)
That's wrong, as the other answers say, the ObservableCollection can do it.Overflow
D
1

To piggy-back off Ahmad's use of Extension Methods, you can create your own class where the list is private with a public get method and a public add method.

public class MyList
{
    private List<SomeClass> PrivateSomeClassList;
    public List<SomeClass> SomeClassList
    {
        get
        {
            return PrivateSomeClassList;
        }
    }

    public void Add(SomeClass obj)
    {
        // do whatever you want
        PrivateSomeClassList.Add(obj);
    }
}

However, this class only provides access to List<> methods that you manually expose...hence may not be useful in cases where you need a lot of that functionality.

Depside answered 17/3, 2015 at 18:12 Comment(0)
R
0

No need for adding an event just add the method.

public class mylist:List<string>
{
  public void Add(string p)
  {
     // Do cool stuff here
     base.Add(p);
  }
}
Raynell answered 3/2, 2016 at 16:56 Comment(1)
(new MyList() aus List<string>).Add() will not call your method! You don't override you only hide the base method.Borodin
Y
0

//List overrider class

public class ListWithEvents<T> : List<T>
    {

        public delegate void AfterChangeHandler();

        public AfterChangeHandler OnChangeEvent;        

        public Boolean HasAddedItems = false;
        public Boolean HasRemovedItems = false;

        public bool HasChanges
        {
            get => HasAddedItems || HasRemovedItems;
            set
            {                
                HasAddedItems = value;
                HasRemovedItems = value; 
            }
        }

        public new void Add(T item)
        {
            base.Add(item);
            HasAddedItems = true;
            if(OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void AddRange(IEnumerable<T> collection)
        {
            base.AddRange(collection);
            HasAddedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void Insert(int index,T item)
        {
            base.Insert(index, item);
            HasAddedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void InsertRange(int index, IEnumerable<T> collection)
        {
            base.InsertRange(index, collection);
            HasAddedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }


        public new void Remove(T item)
        {
            base.Remove(item);
            HasRemovedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void RemoveAt(int index)
        {
            HasRemovedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void RemoveRange(int index,int count)
        {
            HasRemovedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public new void Clear()
        {
            base.Clear();
            HasRemovedItems = true;
            if (OnChangeEvent != null) OnChangeEvent();
            OnChange();
        }

        public virtual void OnChange()
        {

        }
  
    }
Yore answered 31/7, 2020 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.