Click here to Skip to main content
15,884,177 members
Articles / Programming Languages / C#

SmartLists - Extended Lists with Events

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
29 Sep 2010BSD2 min read 17.8K   122   10  
SmartLists library is a collection of classes that extends the standard C# List object
//*****************************************************************************/
// Copyright (c) 2010 Luigi Grilli
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//*****************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Odbms.SmartLists
{
    /// <summary>
    /// A list that supports event On before/after Add, Insert, Remove, Swap and Clear
    /// </summary>
    /// <typeparam name="T">Type of the items contained in the list</typeparam>
    public class EventList<T> : IList<T> where T : class
    {
        #region Delegates
        /// <summary>Event handler for item events</summary>
        public delegate void ItemEventHandler(object sender, ItemEventArgs<T> args);
        /// <summary>Event handler for item events that can be canceled</summary>
        public delegate void CancelItemEventHandler(object sender, CancelItemEventArgs<T> args);
        /// <summary>Event handler for swap events</summary>
        public delegate void SwapItemsEventHandler(object sender, SwapEventArgs args);
        /// <summary>Event handler for swap events that can be canceled</summary>
        public delegate void CancelSwapItemsEventHandler(object sender, CancelSwapEventArgs args);
        /// <summary>Event handler for insert events</summary>
        public delegate void InsertEventHandler(object sender, InsertEventArgs<T> args);
        /// <summary>Event handler for insert or remove events that can be canceled</summary>
        public delegate void CancelInsertRemoveEventHandler(object sender, CancelInsertRemoveEventArgs<T> args);
        #endregion

        #region Events
        /// <summary>
        /// Event fired before a new item is added to the list
        /// You can cancel this operation setting Cancel to true
        /// </summary>
        public event CancelItemEventHandler BeforeAdd;
        /// <summary>
        /// Event fired after a new item has been added to the list
        /// </summary>
        public event ItemEventHandler AfterAdd;

        /// <summary>
        /// Event fired before an item is removed from the list
        /// You can cancel this operation setting Cancel to true
        /// </summary>
        public event CancelInsertRemoveEventHandler BeforeRemove;
        /// <summary>
        /// Event fired after an item has been removed from the list
        /// </summary>
        public event ItemEventHandler AfterRemove;

        /// <summary>
        /// Event fired before two items are swapped
        /// You can cancel this operation setting Cancel to true
        /// </summary>
        public event CancelSwapItemsEventHandler BeforeSwap;
        /// <summary>
        /// Event fired after two items have been swapped
        /// </summary>
        public event SwapItemsEventHandler AfterSwap;

        /// <summary>
        /// Event fired before all items on the list are removed
        /// You can cancel this operation setting Cancel to true
        /// </summary>
        public event CancelEventHandler BeforeClear;
        /// <summary>
        /// Event fired after all items on the list have been removed
        /// </summary>
        public event EventHandler<ClearEventArgs<T>> AfterClear;

        /// <summary>
        /// Event fired before inserting an item on the list
        /// You can cancel this operation setting Cancel to true
        /// </summary>
        public event CancelInsertRemoveEventHandler BeforeInsert;
        /// <summary>
        /// Event fired after an item has been inserted on the list
        /// </summary>
        public event InsertEventHandler AfterInsert;
        #endregion

        #region Private Members
        private IList<T> _list = null;
        private bool _readOnly = false;
        private EventList<T> _readOnlyList = null;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a new EventList
        /// The internal list is istanciated as a standard List
        /// </summary>
        public EventList()
            : this(new List<T>(), false)
        {
        }

        /// <summary>
        /// Creates a new readonly EventList referencing the specified EventList
        /// </summary>
        /// <param name="list">Base list</param>
        protected EventList(IList<T> list, bool readOnly)
        {
            this.InternalList = list;
            this.IsReadOnly = readOnly;
        }
        #endregion

        #region Public Members
        /// <summary>
        /// Swap two items on the list
        /// </summary>
        /// <param name="obj1">First item to swap</param>
        /// <param name="obj2">Second item to swap</param>
        public void Swap(T obj1, T obj2)
        {
            this.Swap(this.IndexOf(obj1), this.IndexOf(obj2));
        }

        /// <summary>
        /// Swap two items
        /// </summary>
        /// <param name="index1">Index of the first item to swap</param>
        /// <param name="index2">Index of the second item to swap</param>
        public void Swap(int index1, int index2)
        {
            if (this.IsReadOnly)
                throw new ReadOnlyListException();

            if (this.OnBeforeSwap(index1, index2))
                return;

            T swapItem = this.InternalList[index1];
            this.InternalList[index1] = this.InternalList[index2];
            this.InternalList[index2] = swapItem;
            this.OnAfterSwap(index1, index2);
        }

        /// <summary>
        /// Copies the elements of the list to a new array
        /// </summary>
        /// <returns>Array containing all the list items</returns>
        public T[] ToArray()
        {
            T[] items = new T[this.Count];

            for (int i = 0; i < this.Count; i++)
                items[i] = this[i];

            return items;
        }

        /// <summary>
        /// Creates a IBindingList to edit this list
        /// </summary>
        /// <returns>IBindingList to edit this list</returns>
        public EventListBinding<T> CreateBinding()
        {
            return new SmartLists.EventListBinding<T>(this);
        }
        #endregion

        #region IList<T> Members
        /// <summary>
        /// Searches for the specified object and returns the zero-based index of the
        /// first occurrence within the list
        /// </summary>
        /// <param name="item">The object to locate in the list. The value can be null for reference types.</param>
        /// <returns>The zero-based index of the first occurrence of item within the entire list, if found; otherwise, –1.</returns>
        public int IndexOf(T item)
        {
            return this.InternalList.IndexOf(item);
        }

        /// <summary>
        /// Inserts a new item on the list at the specified index
        /// </summary>
        /// <param name="index">Index where to insert the item</param>
        /// <param name="item">Item to be inserted</param>
        public void Insert(int index, T item)
        {
            if (this.IsReadOnly)
                throw new ReadOnlyListException();

            if (this.OnBeforeInsert(index, item))
                return;

            this.InternalList.Insert(index, item);
            this.OnAfterInsert(index, item);
        }

        /// <summary>
        /// Removes the item on the list at the specified index
        /// </summary>
        /// <param name="index">Index of the item to be removed</param>
        public virtual void RemoveAt(int index)
        {
            this.InternalRemoveAt(index);
        }

        /// <summary>
        /// Removes the item on the list at the specified index
        /// </summary>
        /// <param name="index">Index of the item to be removed</param>
        /// <returns>True if removed, False if not</returns>
        protected bool InternalRemoveAt(int index)
        {
            if (this.IsReadOnly)
                throw new ReadOnlyListException();

            if (this.OnBeforeRemove(index, this[index]))
                return false;

            T obj = this[index];
            this.InternalList.RemoveAt(index);
            this.OnAfterRemove(obj);
            return true;
        }

        /// <summary>
        /// Gets the item with the specified index
        /// </summary>
        /// <param name="index">Index of the item</param>
        /// <returns>Item with the specified index</returns>
        public T this[int index]
        {
            get
            {
                return this.InternalList[index];
            }
            set
            {
                //TODO: verify it's is possible to do without creating a mess
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// A readonly reference to this list
        /// </summary>
        /// <returns>A readonly reference to this list</returns>
        public virtual EventList<T> AsReadOnly()
        {
            if (this.IsReadOnly)
                return this;

            if(_readOnlyList == null)
                _readOnlyList = new EventList<T>(this, true);

            return _readOnlyList;
        }
        #endregion

        #region ICollection<T> Members
        /// <summary>
        /// Adds a new item at the end of the list
        /// </summary>
        /// <param name="item">Item to be added</param>
        public virtual void Add(T item)
        {
            if (this.IsReadOnly)
                throw new ReadOnlyListException();

            if (this.OnBeforeAdd(item))
                return;

            this.InternalList.Add(item);
            this.OnAfterAdd(item);
        }

        /// <summary>
        /// Removes all items from the list
        /// </summary>
        public void Clear()
        {
            if (this.IsReadOnly)
                throw new ReadOnlyListException();

            if (this.OnBeforeClear())
                return;

            T[] items = this.ToArray();
            this.InternalList.Clear();
            this.OnAfterClear(items);
        }

        /// <summary>
        /// Check if the list contains the specified item
        /// </summary>
        /// <param name="item">Item to be found</param>
        /// <returns>True if the item is on the list, otherwise false</returns>
        public bool Contains(T item)
        {
            return this.InternalList.Contains(item);
        }


        /// <summary>
        /// Copies the entire list to a compatible one-dimensional
        /// array, starting at the specified index of the target array.
        /// </summary>
        /// <param name="array">Array to copy to</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins</param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            this.InternalList.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Gets the number of elements actually contained on the list
        /// </summary>
        public int Count
        {
            get { return this.InternalList.Count; }
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the list 
        /// </summary>
        /// <param name="item">Item to be removed</param>
        /// <returns>True if the item has been removed, otherwise false</returns>
        public bool Remove(T item)
        {
            return this.InternalRemoveAt(this.IndexOf(item));
        }

        /// <summary>
        /// Get a flag that specify it the list is readonly (is so you can't add/insert/remove/clear the list)
        /// </summary>
        public bool IsReadOnly
        {
            get { return _readOnly; }
            protected set { _readOnly = value; }
        }
        #endregion

        #region IEnumerable<T> Members
        /// <summary>
        /// Returns an enumerator that iterates through the list
        /// </summary>
        /// <returns></returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.InternalList.GetEnumerator();
        }
        #endregion

        #region IEnumerable Members
        /// <summary>
        /// Returns an enumerator that iterates through the list
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.InternalList.GetEnumerator();
        }
        #endregion

        #region Events Generator Methods
        /// <summary>
        /// Called before adding a new item to the list
        /// </summary>
        /// <param name="item">Item to add to the list</param>
        /// <returns>True if the operation must be cancelled</returns>
        protected virtual bool OnBeforeAdd(T item)
        {
            if (this.BeforeAdd == null)
                return false;

            CancelItemEventArgs<T> args = new CancelItemEventArgs<T>(item);
            this.BeforeAdd(this, args);
            return args.Cancel;
        }

        /// <summary>
        /// Called before removing an item
        /// </summary>
        /// <param name="index">Index of the item to be removed from the list</param>
        /// <param name="item">Item to be removed from the list</param>
        /// <returns>True if the operation must be cancelled</returns>
        protected virtual bool OnBeforeRemove(int index, T item)
        {
            if (this.BeforeRemove == null)
                return false;

            CancelInsertRemoveEventArgs<T> args = new CancelInsertRemoveEventArgs<T>(index, item);
            this.BeforeRemove(this, args);
            return args.Cancel;
        }

        /// <summary>
        /// Called before swapping two items
        /// </summary>
        /// <param name="index1">Index of the first item</param>
        /// <param name="index2">Index of the second item</param>
        /// <returns>True if the operation must be cancelled</returns>
        protected virtual bool OnBeforeSwap(int index1, int index2)
        {
            if (this.BeforeSwap == null)
                return false;

            CancelSwapEventArgs args = new CancelSwapEventArgs(index1, index2);
            this.BeforeSwap(this, args);
            return args.Cancel;
        }

        /// <summary>
        /// Called after adding a new item to the list
        /// </summary>
        /// <param name="item">Item added</param>
        protected virtual void OnAfterAdd(T item)
        {
            if (this.AfterAdd != null)
                this.AfterAdd(this, new ItemEventArgs<T>(item));
        }

        /// <summary>
        /// Called after removing an item from the list
        /// </summary>
        /// <param name="item">Item removed</param>
        protected virtual void OnAfterRemove(T item)
        {
            if (this.AfterRemove != null)
                this.AfterRemove(this, new ItemEventArgs<T>(item));
        }

        /// <summary>
        /// Called after swapping two items
        /// </summary>
        /// <param name="index1">Index of the first item swapped</param>
        /// <param name="index2">Index of the second item swapped</param>
        protected virtual void OnAfterSwap(int index1, int index2)
        {
            if (this.AfterSwap != null)
                this.AfterSwap(this, new SwapEventArgs(index1, index2));
        }

        /// <summary>
        /// Called before removing all items from the list
        /// </summary>
        /// <returns>True if the operation must be cancelled</returns>
        protected virtual bool OnBeforeClear()
        {
            if (this.BeforeClear == null)
                return false;

            CancelEventArgs args = new CancelEventArgs();
            this.BeforeClear(this, args);
            return args.Cancel;
        }

        /// <summary>
        /// Called after all items from the list have been removed
        /// </summary>
        /// <param name="items">Items removed from the list</param>
        protected virtual void OnAfterClear(T[] items)
        {
            if (this.AfterClear != null)
                this.AfterClear(this, new ClearEventArgs<T>(items));
        }

        /// <summary>
        /// Called before inserting a new item on the list
        /// </summary>
        /// <param name="index">Index where the item will be inserted</param>
        /// <param name="item">Item to be inserted</param>
        /// <returns>True if the operation must be cancelled</returns>
        protected virtual bool OnBeforeInsert(int index, T item)
        {
            if (this.BeforeInsert == null)
                return false;

            CancelInsertRemoveEventArgs<T> args = new CancelInsertRemoveEventArgs<T>(index, item);
            this.BeforeInsert(this, args);
            return args.Cancel;
        }

        /// <summary>
        /// Called after a new item has been inserted on the list
        /// </summary>
        /// <param name="index">Index where the item has been inserted</param>
        /// <param name="item">Item inserted</param>
        protected virtual void OnAfterInsert(int index, T item)
        {
            if (this.AfterInsert != null)
                this.AfterInsert(this, new InsertEventArgs<T>(index, item));
        }
        #endregion

        #region Protected Members
        /// <summary>
        /// Get or set the internal list containing the list's items
        /// </summary>
        protected IList<T> InternalList
        {
            get { return _list; }
            set { _list = value; }
        }
        #endregion
    }

    #region Events arguments objects
    /// <summary>
    /// Event data containing a reference to an item
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ItemEventArgs<T> : EventArgs
    {
        private T _item;

        /// <summary>
        /// Event data containing a reference to an item
        /// </summary>
        /// <param name="item"></param>
        public ItemEventArgs(T item)
        {
            _item = item;
        }

        /// <summary>
        /// Item
        /// </summary>
        public T Item
        {
            get { return _item; }
        }
    }

    /// <summary>
    /// Event data about an Insert operation
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class InsertEventArgs<T> : ItemEventArgs<T>
    {
        private int _index;

        /// <summary>
        /// Event data about an Insert operation
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        public InsertEventArgs(int index, T item)
            : base(item)
        {
            _index = index;
        }

        /// <summary>
        /// Index where the item has been inserted
        /// </summary>
        public int Index
        {
            get { return _index; }
        }
    }

    /// <summary>
    /// Event data about an item operation that can be canceled
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CancelItemEventArgs<T> : CancelEventArgs
    {
        private T _item;

        /// <summary>
        /// Event data about an item operation that can be canceled
        /// </summary>
        /// <param name="item"></param>
        public CancelItemEventArgs(T item)
        {
            _item = item;
        }

        /// <summary>
        /// Item
        /// </summary>
        public T Item
        {
            get { return _item; }
        }
    }

    /// <summary>
    /// Event data about an insert or remove operation that can be canceled
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CancelInsertRemoveEventArgs<T> : CancelItemEventArgs<T>
    {
        private int _index;

        /// <summary>
        /// Event data about an insert operation that can be canceled
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        public CancelInsertRemoveEventArgs(int index, T item)
            : base(item)
        {
            _index = index;
        }

        /// <summary>
        /// Index where the item will be inserted
        /// </summary>
        public int Index
        {
            get { return _index; }
        }
    }

    /// <summary>
    /// Event data about a swap operation
    /// </summary>
    public class SwapEventArgs : EventArgs
    {
        private int _index1 = -1, _index2 = -1;

        /// <summary>
        /// Event data about a swap operation
        /// </summary>
        /// <param name="index1">Index of the first item to be swapped</param>
        /// <param name="index2">Index of the second item to be swapped</param>
        public SwapEventArgs(int index1, int index2)
        {
            _index1 = index1;
            _index2 = index2;
        }

        /// <summary>
        /// Index of the first item to be swapped
        /// </summary>
        public int Index1
        {
            get { return _index1; }
        }

        /// <summary>
        /// Index of the second item to be swapped
        /// </summary>
        public int Index2
        {
            get { return _index2; }
        }
    }

    /// <summary>
    /// Event data about a swap operation that can be canceled
    /// </summary>
    public class CancelSwapEventArgs : CancelEventArgs
    {
        private int _index1 = -1, _index2 = -1;

        /// <summary>
        /// Event data about a swap operation that can be canceled
        /// </summary>
        /// <param name="index1">Index of the first item to be swapped</param>
        /// <param name="index2">Index of the second item to be swapped</param>
        public CancelSwapEventArgs(int index1, int index2)
        {
            _index1 = index1;
            _index2 = index2;
        }

        /// <summary>
        /// Index of the first item to be swapped
        /// </summary>
        public int Index1
        {
            get { return _index1; }
        }

        /// <summary>
        /// Index of the second item to be swapped
        /// </summary>
        public int Index2
        {
            get { return _index2; }
        }
    }

    /// <summary>
    /// Event data about a clear operation
    /// </summary>
    /// <typeparam name="T">Type of the items contained in the list</typeparam>
    public class ClearEventArgs<T> : EventArgs
    {
        private T[] _items = null;

        /// <summary>
        /// Event data about a clear operation
        /// </summary>
        /// <param name="items">Items removed from the list</param>
        public ClearEventArgs(T[] items)
        {
            this.Items = items;
        }

        /// <summary>
        /// Items removed from the list
        /// </summary>
        public T[] Items
        {
            get { return _items; }
            protected set { _items = value; }
        }
    }
    #endregion

    #region Interfaces
    /// <summary>
    /// Objects implementing this interface can be locked to readonly
    /// </summary>
    public interface IReadOnly
    {
        /// <summary>
        /// If true the object properties are readonly
        /// </summary>
        bool ReadOnly
        {
            get;
        }
    }
    #endregion

    #region Exceptions
    /// <summary>
    /// Represent erros when tring to modify a readonly object
    /// </summary>
    [global::System.Serializable]
    public class ReadOnlyListException : Exception
    {
        /// <summary>
        /// Represent erros when tring to modify a readonly EventList
        /// </summary>
        public ReadOnlyListException() : base("The EventList is redonly") { }

        /// <summary>
        /// Initializes a new instance of the ReadOnlyListException class with serialized data
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected ReadOnlyListException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
    #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions