Click here to Skip to main content
15,891,184 members
Articles / General Programming / Performance

Implement Selectable Virtual List

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
3 Jan 2013CPOL8 min read 30K   540   15  
This article is Part 2 of the data display performance optimizing series. The Selectable Virtual List is a list where you can select individual items in the list, and move it out or in to the list. You can also use the select all checkbox to select all items in the list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

namespace SelectableVirtualList
{
	public class VirtualList<T> : IList<T>, IList, INotifyCollectionChanged
	{
		public const int IndexNotFound = -1;
		private readonly IObjectGenerator<T> _objectGenerator;
		private readonly List<int> _removedItemIndexList = new List<int>();

		private T[] _cache;

		public VirtualList(IObjectGenerator<T> generator)
		{
			int maxItems = generator != null ? generator.Count : 0;
			_objectGenerator = generator;
			_cache = new T[maxItems];
		}

		internal T[] Cache
		{
			get { return _cache; }
			set { _cache = value; }
		}

		protected IObjectGenerator<T> ObjectGenerator
		{
			get { return _objectGenerator; }
		}

		protected List<int> RemovedItemIndexList
		{
			get { return _removedItemIndexList; }
		}

		#region IList Members
		public bool IsFixedSize
		{
			get { return false; }
		}

		object IList.this[int index]
		{
			get { return this[index]; }
			set { this[index] = (T)value; }
		}

		public int Add(object value)
		{
			var item = (T)value;
			int index = ObjectGenerator.IndexOf(item);
			if (index != IndexNotFound)
			{
				Insert(index, item);
			}
			OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
			return index;
		}

		public bool Contains(object value)
		{
			int index = ObjectGenerator.IndexOf((T)value);
			if (_removedItemIndexList.Contains(index))
				return false;
			return index != IndexNotFound;
		}

		public int IndexOf(object value)
		{
			return IndexOf((T)value);
		}

		public void Insert(int index, object value)
		{
			Insert(index, (T)value);
		}

		public void Remove(object value)
		{
			Remove((T)value);
		}

		public void CopyTo(Array array, int index)
		{
		}

		public virtual bool IsSynchronized
		{
			get { return false; }
		}

		public object SyncRoot
		{
			get { return this; }
		}
		#endregion

		#region Internal helper methods required for caching
		protected bool IsItemCached(int index)
		{
			// If the object is NULL, then it is empty
			return (_cache[index] != null);
		}

		public void CacheItem(int index)
		{
			// Obtain only a single object
			_cache[index] = _objectGenerator.CreateObject(index);
		}
		#endregion

		#region IList<T> Members
		public int IndexOf(T item)
		{
			int index = ObjectGenerator.IndexOf(item);
			if (!_removedItemIndexList.Contains(index))
				return index;
			return IndexNotFound;
		}

		public void Insert(int index, T item)
		{
			if (_removedItemIndexList.Contains(index))
				_removedItemIndexList.Remove(index);
		}

		public void RemoveAt(int index)
		{
			_removedItemIndexList.Add(index);
		}

		public T this[int index]
		{
			get { return Get(AdjustIndex(index)); }
			set { Insert(index, value); }
		}

		public void Add(T item)
		{
			Add(item as object);
		}

		public void Clear()
		{
			_removedItemIndexList.Clear();
			int numberOfItems = Count;
			for (int i = 0; i < numberOfItems; i++)
			{
				_removedItemIndexList.Add(1);
			}
		}

		public bool Contains(T item)
		{
			return (IndexOf(item) != -1);
		}

		public void CopyTo(T[] array, int arrayIndex)
		{
			_cache.CopyTo(array, arrayIndex);
		}

		public bool Remove(T item)
		{
			try
			{
				int itemPosition = ObjectGenerator.IndexOf(item);
				RemoveAt(itemPosition);
				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
				return true;
			}
			catch (Exception)
			{
				return false;
			} 
		}

		public int Count
		{
			get { return Cache.Length - _removedItemIndexList.Count; }
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return new VirtualEnumerator(this);
		}

		public IEnumerator<T> GetEnumerator()
		{
			return new VirtualEnumerator(this);
		}
		#endregion

		#region Internal IEnumerator implementation
		protected class VirtualEnumerator : IEnumerator<T>
		{
			private VirtualList<T> _collection;
			private int _cursor;

			public VirtualEnumerator(VirtualList<T> collection)
			{
				_collection = collection;
				_cursor = 0;
			}

			public VirtualList<T> VirtualList
			{
				get { return _collection; }
				set { _collection = value; }
			}

			#region IEnumerator<T> Members
			public T Current
			{
				get { return _collection[_cursor]; }
			}

			object IEnumerator.Current
			{
				get { return Current; }
			}

			public bool MoveNext()
			{
				// Check if we are behind
				if (_collection.Count == 0 || _cursor == _collection.Count - 1)
					return false;

				// Increment cursor
				++_cursor;
				return true;
			}

			public void Reset()
			{
				// Reset cursor
				_cursor = 0;
			}

			public void Dispose()
			{
				// NOP
			}
			#endregion
		}
		#endregion

		protected virtual T Get(int index)
		{
			if (!IsItemCached(index))
			{
				CacheItem(index);
			}
			return _cache[index];
		}

		private int AdjustIndex(int index)
		{
			int adjustedIndex = index;
			List<int> orderedRemovedItemList = (from each in _removedItemIndexList
												orderby each ascending
												select each).ToList();
			for (int i = 0; i < orderedRemovedItemList.Count; i++)
			{
				int removedItemPosition = orderedRemovedItemList[i];
				if (removedItemPosition <= adjustedIndex)
				{
					adjustedIndex++;
				}
			}
			return adjustedIndex;
		}

		public event NotifyCollectionChangedEventHandler CollectionChanged;
		public void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			NotifyCollectionChangedEventHandler handler = CollectionChanged;
			if (handler != null)
			{
				handler(this, e);
			}
		}
	}

}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
I am a passionated software developer / engineer with strong desire to develop in a simple, fast, beautiful way with the skillset such as simple design, refactoring, TDD. i worked in J2EE for about 5 years, now i work as a .NET devleoper.

Comments and Discussions