//*****************************************************************************/
// 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
}