Click here to Skip to main content
15,891,529 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.Text;
using System.ComponentModel;

namespace Odbms.SmartLists
{
    /// <summary>
    /// EventList with named items
    /// </summary>
    /// <remarks>In this kind of list is not allowed to add items with the same name</remarks>
    /// <typeparam name="T">Type of the items contained in the list</typeparam>
    public class NamedList<T> : EventList<T> where T : class, INamed
    {
        private bool _caseSensitive = true;
        private NamedList<T> _readOnlyNamedList = null;

        #region Constructors
        /// <summary>
        /// Creates a new NamedList
        /// </summary>
        public NamedList()
        {
        }

        /// <summary>
        /// Creates a new readonly NamedList referencing the specified list
        /// </summary>
        /// <param name="list">Base list</param>
        protected NamedList(IList<T> list, bool readOnly)
            : base(list, readOnly)
        {
        }
        #endregion

        #region Public Members
        /// <summary>
        /// Gets the element with the specified name
        /// </summary>
        /// <param name="name">Name of the element</param>
        /// <returns>The element with the specified name</returns>
        public T this[string name]
        {
            get
            {
                T item = this.FindName(name);
                if (item == null)
                    throw new ArgumentException("There are no objects with '" + name + "' name in the collection");

                return item;
            }
        }

        /// <summary>
        /// Check if there is an elment with the specified name
        /// </summary>
        /// <param name="name">Name to look for in the list items</param>
        /// <returns>True if there is an item with the specified name, false if it doesn't exists</returns>
        public bool ExistsName(string name)
        {
            return (this.FindName(name) != null);
        }

        /// <summary>
        /// Get or set the way the Items name are checked for duplicated entries
        /// </summary>
        public bool CaseSensitive
        {
            get { return _caseSensitive; }
            set
            {
                if (this.IsReadOnly)
                    throw new ReadOnlyListException();

                //check if you can change the value to case insensitive
                if(!value)
                    foreach (T item in this)
                        if (this.FindName(item.Name, item, value) != null)
                            throw new InvalidOperationException("Cannot set CaseSensitive to false because there would be duplicated items names");

                _caseSensitive = value;
            }
        }

        /// <summary>
        /// Creates a readonly reference to this list
        /// </summary>
        /// <returns></returns>
        public override EventList<T> AsReadOnly()
        {
            return this.AsReadOnlyNamedList();
        }

        /// <summary>
        /// Creates a readonly reference to this list
        /// </summary>
        /// <returns></returns>
        public NamedList<T> AsReadOnlyNamedList()
        {
            if (this.IsReadOnly)
                return this;

            if (_readOnlyNamedList == null)
                _readOnlyNamedList = new NamedList<T>(this, true);

            return _readOnlyNamedList;
        }
        #endregion

        #region Protected and overriden members
        /// <summary>
        /// Checks if there is already an item with the same Name on the list
        /// </summary>
        /// <param name="item">Item to be added</param>
        /// <returns>True if the operation must be cancelled</returns>
        /// <exception cref="DuplicatedNameException"></exception>
        protected override bool OnBeforeAdd(T item)
        {
            if (this.FindName(item.Name) != null)
                throw new DuplicatedNameException(item.Name);

            return base.OnBeforeAdd(item);
        }

        /// <summary>
        /// After adding an item we register for change Name events
        /// </summary>
        /// <param name="item">Item added to the list</param>
        protected override void OnAfterAdd(T item)
        {
            item.BeforeNameChanged += new CancelRenameEventHandler(this.ItemBeforeNameChanged);
            base.OnAfterAdd(item);
        }

        /// <summary>
        /// Check if the item can change it's name
        /// </summary>
        /// <param name="item">Item that will change it's name</param>
        /// <param name="newName">The name that will be set</param>
        protected virtual void OnItemNameChanged(T item, string newName)
        {
            if (this.FindName(item.Name, item) != null)
                throw new DuplicatedNameException(item.Name);
        }

        /// <summary>
        /// After removing an item removes the event handler for change Name events
        /// </summary>
        /// <param name="item">Item that has been removed</param>
        protected override void OnAfterRemove(T item)
        {
            item.BeforeNameChanged -= new CancelRenameEventHandler(this.ItemBeforeNameChanged);
            base.OnAfterRemove(item);
        }

        /// <summary>
        /// Checks if there is already an item with the same Name 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>
        /// <exception cref="DuplicatedNameException"></exception>
        protected override bool OnBeforeInsert(int index, T item)
        {
            if (index < this.Count)
                if (this.FindName(item.Name, this[index]) != null)
                    throw new DuplicatedNameException(item.Name);

            return base.OnBeforeInsert(index, item);
        }

        /// <summary>
        /// Removes the event handler for change Name events of all the items removed
        /// </summary>
        /// <param name="items">Items removed from the list</param>
        protected override void OnAfterClear(T[] items)
        {
            foreach (T item in items)
                item.BeforeNameChanged -= new CancelRenameEventHandler(this.ItemBeforeNameChanged);

            base.OnAfterClear(items);
        }

        /// <summary>
        /// Event handler for the items BeforeNameChanged event
        /// </summary>
        /// <param name="sender">Item</param>
        /// <param name="args">Event arguments</param>
        private void ItemBeforeNameChanged(object sender, CancelRenameEventArgs args)
        {
            this.OnItemNameChanged((T)sender, args.NewName);
        }
        #endregion

        #region Private methods
        private T FindName(string name)
        {
            return this.FindName(name, null);
        }

        private T FindName(string name, INamed exclude)
        {
            return this.FindName(name, exclude, this.CaseSensitive);
        }

        private T FindName(string name, INamed exclude, bool caseSensitive)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("Object name can't be empty");

            if (caseSensitive)
            {
                foreach (T obj in this)
                    if (obj != exclude)
                        if (obj.Name.Equals(name))
                            return obj;
            }
            else
            {
                name = name.ToLower();

                foreach (T obj in this)
                    if (obj != exclude)
                        if (obj.Name.ToLower().Equals(name))
                            return obj;
            }

            return null;
        }
        #endregion
    }

    #region Interfaces
    /// <summary>
    /// Object that have a Name (property)
    /// </summary>
    public interface INamed
    {
        /// <summary>
        /// Event fired before the Name of the object is changed
        /// Operation can be canceled
        /// </summary>
        event CancelRenameEventHandler BeforeNameChanged;

        /// <summary>
        /// Event fired when the object has changed it's Name
        /// </summary>
        event EventHandler NameChanged;

        /// <summary>
        /// Name of the object
        /// </summary>
        string Name { get; }
    }
    #endregion

    #region Event arguments and delagates
    /// <summary>
    /// Event handler for a rename operation
    /// </summary>
    /// <param name="sender">Item that will be renaimed</param>
    /// <param name="args">Event data</param>
    public delegate void CancelRenameEventHandler(object sender, CancelRenameEventArgs args);

    /// <summary>
    /// Event arguments data of a Rename operation
    /// </summary>
    public class CancelRenameEventArgs : CancelEventArgs
    {
        string _newName = String.Empty;

        /// <summary>
        /// Event arguments data of a Rename operation
        /// </summary>
        /// <param name="newName">New Name that will be set</param>
        public CancelRenameEventArgs(string newName)
        {
            _newName = newName;
        }

        /// <summary>
        /// Get the new name that will be set
        /// </summary>
        public string NewName
        {
            get { return _newName; }
        }
    }
    #endregion

    #region Exceptions
    /// <summary>
    /// Represent an error that the list has already an item with the same name
    /// </summary>
    [global::System.Serializable]
    public class DuplicatedNameException : Exception
    {
        /// <summary>
        /// Represent an error that the list has already an item with the same name
        /// </summary>
        /// <param name="name"></param>
        public DuplicatedNameException(string name) : base("An element with the name '" + name + "' is already present on the list") { }
        /// <summary>
        /// Initializes a new instance of the System.Exception class with serialized data
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected DuplicatedNameException(
          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