Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

WPF Control Factory

, 20 Apr 2010
This article explains some advantages and disadvantages of factories, and shows one to use for generating WPF Controls.
WpfControlsAndSample.zip
WpfControlsAndSample
Pfz.ClassLibraries
Pfz
Caching
Collections
DataTypes
Extensions
Pfz.snk
Properties
Ranges
Threading
Pfz.WpfControls
BoundGridParts
Extensions
Pfz.WpfControls.snk
Properties
TypeConverters
WpfControlFactorySample
bin
Debug
Pfz.dll
Pfz.WpfControls.dll
WpfControlFactorySample.exe
Properties
Settings.settings
WpfControlFactorySample.suo
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Pfz.Threading;

namespace Pfz.Caching
{
	/// <summary>
	/// A list that only keeps weak-references to it's items.
	/// Ideal if at some point you only need to do a for-each over all the
	/// non-collected items. Use WeakHashSet if you need to remove the items
	/// or calls Contains frequently.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	[Serializable]
	public class WeakList<T>:
		ThreadSafeDisposable,
		ISerializable,
		IList<T>
	where
		T: class
	{
		#region Private fields
			private GCHandle[] fHandles;
		#endregion

		#region Constructors
			/// <summary>
			/// Creates an empty weak-list.
			/// </summary>
			public WeakList():
				this(32)
			{
			}
			
			/// <summary>
			/// Creates an empty weak-list using the given minCapacity to it.
			/// </summary>
			/// <param name="initialCapacity">The initialCapacity of the list. The default value is 32.</param>
			public WeakList(int initialCapacity)
			{
				if (initialCapacity < 1)
					throw new ArgumentOutOfRangeException("initialCapacity", "The initial accepted capacity value is 1.");
					
				fHandles = new GCHandle[initialCapacity];
				GCUtils.Collected += p_Collected;
			}
		#endregion
		#region Dispose
			/// <summary>
			/// Releases all handles.
			/// </summary>
			protected override void Dispose(bool disposing)
			{
				// if the dispose was called manullay, we must unregister ourselves from the GCUtils.Collected method.
				// we don't need to do this if this object is being collected, as the Collected event is also we and, so,
				// it was already removed from there.
				if (disposing)
					GCUtils.Collected -= p_Collected;

				// here we will free all allocated handles. After all, even if the objects can be collected, the
				// handles must be freed.
				var handles = fHandles;
				if (handles != null)
				{
					int count = handles.Length;
					for (int i=0; i<count; i++)
					{
						var handle = handles[i];
						if (handle.IsAllocated)
							handle.Free();
					}

					fHandles = null;
				}

				base.Dispose(disposing);
			}
		#endregion
		#region p_Collected
			private void p_Collected()
			{
				AbortSafe.Lock
				(
					DisposeLock,
					delegate
					{
						if (WasDisposed)
						{
							GCUtils.Collected -= p_Collected;
							return;
						}
						
						int allocated = 0;
						
						int count = Count;
						for(int i=0; i<count; i++)
						{
							var handle = fHandles[i];
							
							if (handle.IsAllocated && handle.Target != null)
								allocated ++;
						}
						
						int minCapacity = count / 2;
						if (minCapacity < 32)
							minCapacity = 32;
							
						if (allocated < minCapacity)
							allocated = minCapacity;
						
						if (allocated != fHandles.Length)
						{
							var newHandles = new GCHandle[allocated];
							try
							{
							}
							finally
							{
								int newIndex = 0;
								for(int i=0; i<count; i++)
								{
									var handle = fHandles[i];
									
									if (!handle.IsAllocated)
										continue;
									
									var target = handle.Target;
									if (target == null)
										handle.Free();
									else
									{
										newHandles[newIndex] = handle;
										newIndex++;
									}
								}
								for(int i=count; i<fHandles.Length; i++)
								{
									var handle = fHandles[i];
									if (handle.IsAllocated)
										handle.Free();
								}
								
								Count = newIndex;
								fHandles = newHandles;
							}
						}
					}
				);
			}
		#endregion
		
		#region Properties
			#region Count
				/// <summary>
				/// Gets an approximate count of the items added.
				/// </summary>
				public int Count { get; private set; }
			#endregion
			#region this[]
				/// <summary>
				/// Gets or sets an item at a given index.
				/// </summary>
				public T this[int index]
				{
					get
					{
						T result = default(T);
						
						AbortSafe.Lock
						(
							DisposeLock,
							delegate
							{
								CheckUndisposed();
								
								if (index < 0 || index >= Count)
									throw new ArgumentOutOfRangeException("index");
								
								result = (T)fHandles[index].Target;
							}
						);
						
						return result;
					}
					set
					{
						AbortSafe.Lock
						(
							DisposeLock,
							delegate
							{
								CheckUndisposed();
								
								if (index < 0 || index >= Count)
									throw new ArgumentOutOfRangeException("index");

								var handle = fHandles[index];
								if (!handle.IsAllocated)
								{
									if (value != null)
									{
										try
										{
										}
										finally
										{
											fHandles[index] = GCHandle.Alloc(value, GCHandleType.Weak);
										}
									}

									return;
								}

								handle.Target = value;
							}
						);
					}
				}
			#endregion
		#endregion
		#region Methods
			#region Clear
				/// <summary>
				/// Clears all the items in the list.
				/// </summary>
				public void Clear()
				{
					AbortSafe.Lock
					(
						DisposeLock,
						delegate
						{
							CheckUndisposed();
							
							Count = 0;
						}
					);
				}
			#endregion
			#region Add
				/// <summary>
				/// Adds an item to the list.
				/// </summary>
				public void Add(T item)
				{
					if (item == null)
						throw new ArgumentNullException("item");
					
					AbortSafe.Lock
					(
						DisposeLock,
						delegate
						{
							CheckUndisposed();
							
							int count = Count;
							if (count == fHandles.Length)
								Array.Resize(ref fHandles, count * 2);
							
							var handle = fHandles[count];
							if (handle.IsAllocated)
								handle.Target = item;
							else
							{
								try
								{
								}
								finally
								{
									fHandles[count] = GCHandle.Alloc(item, GCHandleType.Weak);
								}
							}

							Count++;
						}
					);
				}
			#endregion
			#region ToList
				/// <summary>
				/// Gets a strong-list with all the non-collected items present
				/// in this list.
				/// </summary>
				public List<T> ToList()
				{
					List<T> result = new List<T>();
					
					AbortSafe.Lock
					(
						DisposeLock,
						delegate
						{
							CheckUndisposed();
							
							int count = Count;
							for (int i=0; i<count; i++)
							{
								object target = fHandles[i].Target;
								
								if (target != null)
									result.Add((T)target);
							}
						}
					);
						
					return result;
				}
			#endregion
			
			#region Contains
				/// <summary>
				/// Returns true if an item exists in the collection, false otherwise.
				/// </summary>
				public bool Contains(T item)
				{
					return IndexOf(item) != -1;
				}
			#endregion
			#region IndexOf
				/// <summary>
				/// Gets the IndexOf an item in this list, or -1 if it does not exist.
				/// Note that just after checking for the index of an item, it could
				/// be collected, so this is not really useful.
				/// </summary>
				public int IndexOf(T item)
				{
					if (item == null)
						throw new ArgumentNullException("item");
				
					int result = -1;
					
					AbortSafe.Lock
					(
						DisposeLock,
						() =>
						{
							CheckUndisposed();
							
							int count = Count;
							for (int i=0; i<count; i++)
							{
								GCHandle handle = fHandles[i];
								if (handle.Target == item)
								{
									result = i;
									return;
								}
							}
						}
					);
					
					return result;
				}
			#endregion
			
			#region RemoveAt
				/// <summary>
				/// Removes an item at a given index.
				/// Note that, different from normal lists, the following items do
				/// not move and the count is not immediatelly updated.
				/// </summary>
				public void RemoveAt(int index)
				{
					AbortSafe.Lock
					(
						DisposeLock,
						() =>
						{
							CheckUndisposed();
							
							if (index < 0 || index >= Count)
								throw new ArgumentOutOfRangeException("index");
							
							var handle = fHandles[index];
							if (handle.IsAllocated)
								handle.Target = null;
						}
					);
				}
			#endregion
			#region Remove
				/// <summary>
				/// Tries to remove an item from this list and returns if it was
				/// found and removed. Note that the Count and the following items 
				/// are not automatically updated, as this will only happen in the next
				/// garbage collection.
				/// </summary>
				public bool Remove(T item)
				{
					bool result = false;
					
					AbortSafe.Lock
					(
						DisposeLock,
						() =>
						{
							int index = IndexOf(item);
							if (index != -1)
							{
								fHandles[index].Target = null;
								result = true;
							}
						}
					);
					
					return result;
				}
			#endregion
			
			#region GetEnumerator
				/// <summary>
				/// Gets an enumerator over the non-collected items of this
				/// list.
				/// </summary>
				public IEnumerator<T> GetEnumerator()
				{
					return ToList().GetEnumerator();
				}
			#endregion
		#endregion
		
		#region Interfaces
			#region ISerializable Members
				/// <summary>
				/// Creates the WeakList from the serialization info.
				/// </summary>
				protected WeakList(SerializationInfo info, StreamingContext context):
					this(32)
				{
				}
				
				/// <summary>
				/// Creates serialization info.
				/// </summary>
				protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
				{
				}

				void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
				{
					GetObjectData(info, context);
				}
			#endregion
			#region IList<T> Members
				void IList<T>.Insert(int index, T item)
				{
					throw new NotImplementedException();
				}
			#endregion
			#region ICollection<T> Members
				void ICollection<T>.CopyTo(T[] array, int arrayIndex)
				{
					ToList().CopyTo(array, arrayIndex);
				}
				bool ICollection<T>.IsReadOnly
				{
					get
					{
						return false;
					}
				}
			#endregion
			#region IEnumerable Members
				IEnumerator IEnumerable.GetEnumerator()
				{
					return GetEnumerator();
				}
			#endregion
		#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 Code Project Open License (CPOL)

About the Author

Paulo Zemek
Architect
Canada Canada
I started to program computers when I was 11 years old, as a hobbist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.
 
At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do they work easier, faster and with less errors.
 
Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com
 
Codeproject MVP 2012
Microsoft MVP 2013

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 20 Apr 2010
Article Copyright 2010 by Paulo Zemek
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid