Generic class for deep clone of Silverlight and CLR objects

by Jim McCurdy 23. July 2010 09:51

Update: As of 7/19/2010, I have updated the source code for the CloneObject class.  The update improves on the ability to clone arrays and other IEnumerable objects, and simplifies some other operations…

A few months ago, I had the need for a generic CloneObject class that would take any object as input, and create a deep copy of that object and all of it's properties.  I needed to clone objects for 2 purposes.  One purpose was to implement a drag and drop interface in a Silverlight application, where source objects would be cloned/copied on "DragStart" and added to the visual tree when "Dropped".  The second purpose was to implement an undo capability, where I would clone an object before editing it, and if edits were canceled, restore the original object. 

When looking for a generic clone solution, I wanted a class that would work with both generic CLR objects, as well as objects deriving from FrameElement, and included DependencyObject's and DependencyProperty's.  I considered the option to use serialization, but I wanted something that would work for all types, not just objects that were flagged as serializable.  So my solution uses reflection to accomplish the task. 

The hardest part of developing this CloneObject class, was testing all of the potential use cases.  The solution I offer here works well for the common use cases that I was able to test, but I am sure that others may find ways to make it more robust.  Readers are encouraged to comment with modifications.  Thanks to Justin Angel and Tamir Khason for their inspirational articles.  My solution took a slightly different direction over time, but they were the ones that got me started.

The code below has been tested and used with Silverlight 3 and Silverlight 4.

//#define DEBUG_TRACE
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace ClassLibrary1
{
	[Obfuscation(Exclude = true)]
	internal static class CloneObject
	{
		// Extension for any class object
		internal static TT DeepClone<TT>(this TT source)
		{ // Jim McCurdy's DeepClone
			TT clone = CloneRecursive(source);

			if (clone is FrameworkElement)
			{
				FrameworkElement cloneElement = (clone as FrameworkElement);
				cloneElement.Arrange(new Rect(0, 0, cloneElement.ActualWidth, cloneElement.ActualHeight));
			}

			return clone;
		}

		private static TT CloneRecursive<TT>(TT source)
		{
			if (source == null || source.GetType().IsValueType)
				return source;

			// Common types that do not have parameterless constructors
			if (source is string || source is Type || source is Uri || source is DependencyProperty)
				return source;

			TT clone = CloneCreate(source);
			if (clone == null)
				return source;

			if (source is IList && clone is IList)
				CloneList(source as IList, clone as IList);
			else
				CloneProperties(source, clone);

			return clone;
		}

		private static TT CloneCreate<TT>(TT source)
		{
			try
			{
#if DEBUG_TRACE
				string.Format("Clone create object Type={0}", 
					SimpleType(source.GetType())).Trace();
#endif
				Array sourceArray = (source as Array);
				if (sourceArray == null)
					return (TT)Activator.CreateInstance(source.GetType());
				if (sourceArray.Rank == 1)
					return (TT)(object)Array.CreateInstance(source.GetType().GetElementType(),
						sourceArray.GetLength(0));
				if (sourceArray.Rank == 2)
					return (TT)(object)Array.CreateInstance(source.GetType().GetElementType(),
						sourceArray.GetLength(0), sourceArray.GetLength(1));
			}
			catch (Exception ex)
			{
				if (ex.Message.Contains("No parameterless constructor"))
					return default(TT);
				string.Format("Can't create object Type={0}", SimpleType(source.GetType())).Trace();
				ex.DebugOutput();
			}

			return default(TT);
		}

		private static void CloneProperties(object source, object clone)
		{
			// The binding flags indicate what properties we will clone
			// Unfortunately, we cannot clone "internal" or "protected" properties
			BindingFlags flags = 
				BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public;

			// Clone dependency properties
			Type loopType = source.GetType();
			while (loopType != null && loopType != typeof(DependencyObject))
			{
				FieldInfo[] fieldInfoArray = loopType.GetFields(flags | BindingFlags.Static);
				foreach (FieldInfo field in fieldInfoArray)
					CloneDependencyProperty(source as DependencyObject, clone as DependencyObject, field);

				loopType = loopType.BaseType;
			}

			// Clone CLR properties
			PropertyInfo[] properties = source.GetType().GetProperties(flags);
			foreach (PropertyInfo property in properties)
				CloneProperty(source, clone, property);
		}

		private static void CloneDependencyProperty(DependencyObject sourceObject, 
			DependencyObject cloneObject, FieldInfo field)
		{
			try
			{
				if (sourceObject == null || cloneObject == null)
					return;
				if (field.IsPrivate || (field.Attributes & FieldAttributes.Private) != 0)
					return;
				if (!field.IsStatic || (field.Attributes & FieldAttributes.Static) == 0)
					return;

				// Blacklisted properties that can't (or shouldn't) be set
				if (field.Name == "NameProperty" && sourceObject is FrameworkElement) return;

				DependencyProperty dp = field.GetValue(sourceObject) as DependencyProperty;
				if (dp == null) // Event DependencyProperties will be null
					return;

				object sourceValue = sourceObject.GetValue(dp);
				if (sourceValue == null)
					return;
#if DEBUG_TRACE
				string.Format("Clone dependency property Name={0}, Value={2} for source Type={1}", 
					field.Name, SimpleType(sourceObject.GetType()), sourceValue).Trace();
#endif
				// Blacklisted properties that can't (or don't need to be) cloned
				bool doClone = true;
				if (field.Name == "DataContextProperty") doClone = false;
				//if (field.Name == "TargetPropertyProperty") doClone = false;

				object cloneValue = (doClone ? CloneRecursive(sourceValue) : sourceValue);
				cloneObject.SetValue(dp, cloneValue);
			}
			catch (Exception ex)
			{
				if (ex.Message.Contains("read-only"))
					return;
				if (ex.Message.Contains("read only"))
					return;
				if (ex.Message.Contains("does not fall within the expected range"))
					return;
				string.Format("Can't clone dependency property Name={0}, for source Type={1}", 
					field.Name, SimpleType(sourceObject.GetType())).Trace();
				ex.DebugOutput();
			}
		}

		private static void CloneProperty(object source, object clone, PropertyInfo property)
		{
			try
			{
				// Blacklisted properties that can't (or shouldn't) be set
				if (property.Name == "Name" && source is FrameworkElement) return;
				if (property.Name == "InputScope" && source is TextBox) return; // Can't get
				if (property.Name == "Watermark" && source is TextBox) return; // Can't get
				if (property.Name == "Source" && source is ResourceDictionary) return; // Can't set
				if (property.Name == "TargetType" && source is ControlTemplate) return; // Can't set

				bool publicSetter = (source.GetType().GetMethod("set_" + property.Name) != null);
				bool isList = (property.PropertyType.GetInterface("IList", true) != null);
				bool canWrite = property.CanWrite && publicSetter;
				bool canHandleWrite = canWrite || isList;
				if (!property.CanRead || !canHandleWrite || property.GetIndexParameters().Length != 0)
					return;

				object sourceValue = property.GetValue(source, null);
				if (sourceValue == null)
					return;

				if (!canWrite && isList)
				{
					IList cloneList = property.GetValue(clone, null) as IList;
					if (cloneList != null)
						CloneList(sourceValue as IList, cloneList);
					return;
				}
#if DEBUG_TRACE
				string.Format("Clone property Type={0}, Name={1}, Value={3} for source Type={2}", 
					SimpleType(property.PropertyType), property.Name, SimpleType(source.GetType()), 
					sourceValue).Trace();
#endif
				// Blacklisted properties that can't (or don't need to be) cloned
				bool doClone = true;
				if (source is FrameworkElement && property.Name == "DataContext") doClone = false;
				//if (property.Name == "TargetProperty") doClone = false;

				object cloneValue = (doClone ? CloneRecursive(sourceValue) : sourceValue);
				property.SetValue(clone, cloneValue, null); // possible MethodAccessException
			}
			catch (Exception ex)
			{
				string.Format("Can't clone property Type={0}, Name={1}, for source Type={2}", 
					SimpleType(property.PropertyType), property.Name, SimpleType(source.GetType())).Trace();
				ex.DebugOutput();
			}
		}

		private static void CloneList(IList sourceList, IList cloneList)
		{
			try
			{
				IEnumerator sourceEnumerator = sourceList.GetEnumerator();
				Array sourceArray = sourceList as Array;
				Array cloneArray = cloneList as Array;
				int dim0 = (sourceArray != null && sourceArray.Rank > 0 ? sourceArray.GetLowerBound(0) : 0);
				int dim1 = (sourceArray != null && sourceArray.Rank > 1 ? sourceArray.GetLowerBound(1) : 0);

				while (sourceEnumerator.MoveNext())
				{
					object sourceValue = sourceEnumerator.Current;
#if DEBUG_TRACE
					string.Format("Clone IList item {0}", sourceValue).Trace();
#endif
					object cloneValue = CloneRecursive(sourceValue);
					if (sourceArray == null)
						cloneList.Add(cloneValue);
					else
					if (sourceArray.Rank == 1)
						cloneArray.SetValue(cloneValue, dim0++);
					else
					if (sourceArray.Rank == 2)
					{
						cloneArray.SetValue(cloneValue, dim0, dim1);
						if (++dim1 > sourceArray.GetUpperBound(1))
						{
							dim1 = sourceArray.GetLowerBound(1);
							if (++dim0 > sourceArray.GetUpperBound(0))
								dim0 = sourceArray.GetLowerBound(0);
						}
					}
				}
			}
			catch (Exception ex)
			{
				string.Format("Can't clone IList item Type={0}", SimpleType(sourceList.GetType())).Trace();
				ex.DebugOutput();
			}
		}

		private static string SimpleType(Type type)
		{
			string typeName = type.ToString();
			int index = typeName.LastIndexOf('[');
			if (index < 0)
				return typeName.Substring(typeName.LastIndexOf('.') + 1);

			string collectionName = typeName.Substring(index);
			collectionName = collectionName.Substring(collectionName.LastIndexOf('.') + 1);
			typeName = typeName.Substring(0, index);
			typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);
			return typeName + '[' + collectionName;
		}
	}

	internal static class Extensions
	{
		// Extension for Exception  
		internal static void DebugOutput(this Exception ex)
		{
#if DEBUG
			Debug.WriteLine(ex.GetType().ToString() + ": " + ex.Message + Environment.NewLine);
#endif
		}

		// Extension for string  
		internal static void Trace(this string message)
		{
			Debug.WriteLine(message);
		}
	}
}
Bookmark and Share

Implement MouseWheel support for Silverlight 3 controls (and for Silverlight 4 Slider and TreeView)

by Jim McCurdy 29. January 2010 10:43

In the upcoming Silverlight 4 release, mousewheel support for controls will be implemented out of the box for the ScrollViewer, DataGrid, and ListBox controls.  However Silverlight 3 still requires you to roll your own mouse wheel support.  And Silverlight 4 does not offer a scrolling solution for Slider and TreeView.

The following MouseWheelProps class provides generic mousewheel support for all Silverlight controls that support the IScrollProvider interface (ScrollViewer, ListBox, DataGrid) or the IRangeValueProvider interface (Slider).  This class implements a single attached property called Enable that can be added directly to a control, or as a style for a control.

Step1: Add the following MouseWheelProps class to your Silverlight project:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace ClassLibrary
{
	public static class MouseWheelProps
	{
		// The Enabled attached property
		public static readonly DependencyProperty EnabledProperty = 
			DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(MouseWheelProps),
				new PropertyMetadata(false, OnEnabledPropertyChanged));

		public static bool GetEnabled(FrameworkElement element)
		{
			return (bool)element.GetValue(EnabledProperty);
		}

		public static void SetEnabled(FrameworkElement element, bool value)
		{
			element.SetValue(EnabledProperty, value);
		}

		private static void OnEnabledPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			UIElement element = sender as UIElement;
			if (element == null)
				return;
			bool enabled = (bool)e.NewValue;
			if (enabled)
				element.MouseWheel += OnMouseWheel;
			else
				element.MouseWheel -= OnMouseWheel;
		}

		private static void OnMouseWheel(object sender, MouseWheelEventArgs e)
		{
			if (e.Handled)
				return;

			UIElement element = sender as UIElement;
			if (element == null)
				return;

			AutomationPeer peer = FrameworkElementAutomationPeer.FromElement(element);
			if (peer == null)
				peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
			if (peer == null)
				return;

			// try to get the scroll provider or the range provider
			IScrollProvider m_ScrollProvider = peer.GetPattern(PatternInterface.Scroll) as IScrollProvider;
			IRangeValueProvider m_RangeValueProvider = null;
			if ((element is Control) && (element as Control).HasFocus())
				m_RangeValueProvider = peer.GetPattern(PatternInterface.RangeValue) as IRangeValueProvider;
			if (m_ScrollProvider == null && m_RangeValueProvider == null)
				return;

			// set scroll amount
			const double kMultiplier = 5.0; // x times the default
			const double kFactor = kMultiplier / (30 * 120);
			double delta = e.Delta;
			delta *= kFactor;
			int direction = Math.Sign(delta);

			bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
			bool controlKey = (Keyboard.Modifiers & ModifierKeys.Control) != 0;

			if (m_ScrollProvider != null)
			{
				e.Handled = true;
#if true
				if (m_ScrollProvider.VerticallyScrollable && !controlKey)
				{
					double percent = m_ScrollProvider.VerticalScrollPercent - (delta * m_ScrollProvider.VerticalViewSize);
					if (percent < 0) percent = 0;
					if (percent > 100) percent = 100;
					m_ScrollProvider.SetScrollPercent(m_ScrollProvider.HorizontalScrollPercent, percent);
				}
				else
				if (m_ScrollProvider.HorizontallyScrollable && controlKey)
				{
					double percent = m_ScrollProvider.HorizontalScrollPercent - (delta * m_ScrollProvider.HorizontalViewSize);
					if (percent < 0) percent = 0;
					if (percent > 100) percent = 100;
					m_ScrollProvider.SetScrollPercent(percent, m_ScrollProvider.VerticalScrollPercent);
				}
#else
				ScrollAmount scrollAmount = (direction < 0) ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement;
				if (m_ScrollProvider.VerticallyScrollable && !controlKey)
					m_ScrollProvider.Scroll(ScrollAmount.NoAmount, scrollAmount);
				else
				if (m_ScrollProvider.HorizontallyScrollable && controlKey)
					m_ScrollProvider.Scroll(scrollAmount, ScrollAmount.NoAmount);
#endif
			}

			if (m_RangeValueProvider != null)
			{
				e.Handled = true;
				double newValue = m_RangeValueProvider.Value + (direction < 0 ? -m_RangeValueProvider.LargeChange : m_RangeValueProvider.LargeChange);
				if (newValue >= m_RangeValueProvider.Minimum && newValue <= m_RangeValueProvider.Maximum)
					m_RangeValueProvider.SetValue(newValue);
			}
		}
	}

	internal static class ExtensionMethods
	{
		// Extension for UIElement
		internal static bool HasFocus(this UIElement element)
		{
			if (element == null)
				return false;

			DependencyObject focusedElement = FocusManager.GetFocusedElement() as DependencyObject;
			while (focusedElement != null)
			{
				if (element == focusedElement)
					return true;
				focusedElement = VisualTreeHelper.GetParent(focusedElement);
			}

			return false;
		}
	}
}

Step 2: Add the attached property c:MouseWheelProps.Enabled="True" either directly to a control, or as a Style that can be referenced by a control.

Added directly to a control:

<UserControl x:Class="SilverlightApplication.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:app="clr-namespace:SilverlightApplication"
	xmlns:DataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
>
	<Grid>
		<DataGrid:DataGrid c:MouseWheelProps.Enabled="True" />
	</Grid>
</UserContro>

Added as a Style:

<UserControl x:Class="SilverlightApplication.MainPage"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:app="clr-namespace:SilverlightApplication"
	xmlns:DataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
>
	<Grid>
		<Grid.Resources>
			<Style x:Key="CommonDataGrid" TargetType="DataGrid:DataGrid">
				<Setter Property="c:MouseWheelProps.Enabled" Value="True" />
			</Style>
		</Grid.Resources>
		<DataGrid:DataGrid Style="{StaticResource CommonDataGrid}" />
	</Grid>
</UserContro>

That's all there is to it!

Bookmark and Share

Easy access to Silverlight file resources with my ResourceFile class

by Jim McCurdy 19. November 2009 22:53

Silverlight application developers have several choices when deploying file resources for a Silverlight application.  For my own Silverlight application, YinYangMoney, I developed a ResourceFile class to make access to those resource files flexible and simple - regardless of the deployment choice.  I will be sharing that class with you in this blog post.

Background on Silverlight File Resources

As I talk about Silverlight File Resources here, do not confuse them with Silverlight StaticResources defined and used in your XAML files:

<Application.Resources>
    <System:String x:Key="Greeting">Hello World!</System:String>
</Application.Resources>

and can be accessed in XAML as:

<TextBlock Text="{StaticResource Greeting}" />

or in C# code as:

string greeting = Application.Current.Resources["Greeting"];

Silverlight File Resources are different.  They are typically non-executable data files used by your Silverlight application.  This includes resources such as text, XAML, XML, HTML, font, image (JPG or PNG), audio, or video files.  You can organize these resource files in folders, sub-folders, or even package them in ZIP files.  My ResourceFile class will get you to these resources regardless of the file type, size, or folder organization.

Get Started

Start by using Visual Studio to add the resource files to your Silverlight application or library projects.  Organize them in any project folders you want, but I would typically root them under a common Content or Resource folder.  Secondly, configure the files for deployment by setting the Build Action property in the files' Visual Studio Properties Window.  Set the Build Action as appropriate:

  • Content. This build action will include the file in the application XAP package.

  • Resource. This build action will embed the file in the project assembly. This applies to project assemblies that you deploy either inside or outside the application XAP package.

  • None. Use this build action for resource files that you want to retrieve on demand.  You will typically deploy on-demand files at the same server location as your application package.  I am not aware of any case where using this build action is practical.

The Build Action drop-down list also offers several other settings.  However, as of this writing, you can only use Resource, Content, and None with Silverlight projects.  For example, do not use the Embedded Resource build action, which uses a format that Silverlight cannot recognize.

Accessing Silverlight File Resources

Accessing Silverlight file resources that are deployed with different Build Actions (and in or out of ZIP files), normally require different access methods.  However, my ResourceFile class makes these differences transparent to the application, so you can change any file's Build Action without breaking your code's access to its resources.  It also makes accessing file resources in a library assembly very easy.

ResourceFile is a static class with public methods to read entire file resources, or get file streams to read them yourself.  I also added a little helper class which I called From.  The From class is used to indicate the assembly that contains resource files.

The code below has been tested and used with Silverlight 3 and Silverlight 4.  You can also find my simple test project here: ResourceFileTest.zip

using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using System.Xml;

namespace ClassLibrary
{
	// Get resource files, or resource file streams, either from...
	//  o  an assembly in the XAP (Resource build action)
	//  o  the XAP itself (Content build action)
	//
	// Typical usage:
	//
	// Get content directly: bitmaps, file text, fonts, XAML, or XML
	//
	//		BitmapImage bitmap1 = ResourceFile.GetBitmap("Images/Clouds.jpg", From.App);
	//
	// Get content from a ZIP:
	//
	//		BitmapImage bitmap2 = ResourceFile.GetBitmapFromZip("Content/Zips/Files.zip", "Clouds.jpg", From.App);
	//
	// Get content streams:
	//
	//		Stream bitmapStream = ResourceFile.GetStream("Images/Clouds.jpg", From.App);
	//		Stream zipStream = ResourceFile.GetStream("Content/Zips/Files.zip", From.App);

	internal static class ResourceFile
	{
		// Get a bitmap from an assembly or the XAP
		internal static BitmapImage GetBitmap(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetBitmapFromStream(stream);
		}

		// Get a bitmap from a zip file in an assembly or the XAP
		internal static BitmapImage GetBitmapFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetBitmapFromStream(stream);
		}

		// Get a bitmap from a stream
		internal static BitmapImage GetBitmapFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			BitmapImage bitmap = new BitmapImage();
			bitmap.SetSource(stream);
			return bitmap;
		}

		// Get file text from an assembly or the XAP
		internal static string GetFileText(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetFileTextFromStream(stream);
		}

		// Get file text from a zip file in an assembly or the XAP
		internal static string GetFileTextFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetFileTextFromStream(stream);
		}

		// Get file text from a stream
		internal static string GetFileTextFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			using (StreamReader reader = new StreamReader(stream))
			{
				if (reader == null)
					return null;
				return reader.ReadToEnd();
			}
		}

		// Get a font from an assembly or the XAP
		internal static FontSource GetFont(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetFontFromStream(stream);
		}

		// Get a font from a zip file in an assembly or the XAP
		internal static FontSource GetFontFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetFontFromStream(stream);
		}

		// Get a font from a stream
		internal static FontSource GetFontFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			return new FontSource(stream);
		}

		// Get XAML from an assembly or the XAP
		internal static UIElement GetXaml(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetXamlFromStream(stream);
		}

		// Get XAML from a zip file in an assembly or the XAP
		internal static UIElement GetXamlFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetXamlFromStream(stream);
		}

		// Get XAML from a stream
		internal static UIElement GetXamlFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			return XamlReader.Load(GetFileTextFromStream(stream)) as UIElement;
		}

		// Get an XML Reader from an assembly or the XAP
		internal static XmlReader GetXmlReader(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetXmlReaderFromStream(stream);
		}

		// Get an XML Reader from a zip file in an assembly or the XAP
		internal static XmlReader GetXmlReaderFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetXmlReaderFromStream(stream);
		}

		// Get an XML Reader from a stream
		internal static XmlReader GetXmlReaderFromStream(Stream stream)
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.DtdProcessing = DtdProcessing.Ignore;
			settings.IgnoreWhitespace = false;
			settings.IgnoreProcessingInstructions = true;
			settings.IgnoreComments = true;
			try
			{
				return XmlReader.Create(stream, settings);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message, "Alert", MessageBoxButton.OK);
				return null;
			}
		}

		// Get an XML text string from an XmlReader
		internal static string GetXmlFromXmlReader(XmlReader xmlReader)
		{
			return (xmlReader != null ? xmlReader.ReadOuterXml() : null);
		}

		// Get a resource file stream info from an assembly or the XAP
		internal static StreamResourceInfo GetStreamInfo(string relativeUri, From assembly)
		{
			StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(relativeUri, UriKind.Relative));
			if (streamInfo != null) // found in the XAP
				return streamInfo;

			string componentFormat = assembly.Name + ";component/{0}";
			string assemblyUri = string.Format(componentFormat, relativeUri);
			streamInfo = Application.GetResourceStream(new Uri(assemblyUri, UriKind.Relative));
			if (streamInfo != null) // found in the assembly
				return streamInfo;

			// Last chance!
			// Resource files added to a VS project as "links" (Add / Existing Item / Add As Link)
			// must be accessed without a path
			int pathMarker = relativeUri.LastIndexOf('/');
			if (pathMarker < 0)
				return null;

			assemblyUri = string.Format(componentFormat, relativeUri.Substring(pathMarker + 1));
			streamInfo = Application.GetResourceStream(new Uri(assemblyUri, UriKind.Relative));
			if (streamInfo != null) // found in the assembly
				return streamInfo;

			return null; // not found
		}

		// Get a resource file stream from an assembly or the XAP
		internal static Stream GetStream(string relativeUri, From assembly)
		{
			StreamResourceInfo streamInfo = GetStreamInfo(relativeUri, assembly);
			if (streamInfo == null)
				return null;

			return streamInfo.Stream;
		}

		// Get a resource file stream from inside a ZIP stream
		internal static Stream GetStreamFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream zipStream = GetStream(relativeZipUri, assembly);
			if (zipStream == null)
				return null;

			return GetStreamFromZipStream(zipStream, relativeUri);
		}

		// Get a resource file stream from inside a ZIP stream
		internal static Stream GetStreamFromZipStream(Stream zipStream, string relativeUri)
		{
			if (zipStream == null)
				return null;

			StreamResourceInfo zipInfo = new StreamResourceInfo(zipStream, null);
			StreamResourceInfo streamInfo = Application.GetResourceStream(zipInfo, new Uri(relativeUri, UriKind.Relative));
			if (streamInfo == null)
				streamInfo = Application.GetResourceStream(new Uri(relativeUri, UriKind.Relative));
			if (streamInfo == null)
				return null;

			return streamInfo.Stream;
		}
	}

	// Calls to access file resources are assisted by a tiny static class called "From"
	// which is used to indicate the assembly that contains resource files.
	//	From.App - the current application assembly
	//	From.This - the caller's assembly
	//	From.Library - a library assembly
	//	From.Type<ClassLibrary> - an assembly containing a known type
	internal class From
	{
		internal Assembly Assembly { get; set; }
		internal string Name { get { return (Assembly != null ? Assembly.FullName.Substring(0, Assembly.FullName.IndexOf(',')) : null); } }

		// From current application assembly
		internal static From App { get { return new From() { Assembly = Application.Current.GetType().Assembly }; } }

		// From the caller's assembly
		internal static From This { get { return new From() { Assembly = Assembly.GetCallingAssembly() }; } }

		// From a library assembly
		internal static From Library { get { return new From() { Assembly = Assembly.GetExecutingAssembly() }; } }

		// From an assembly containing a known type
		internal static From Type<TT>() { return new From() { Assembly = typeof(TT).Assembly }; }
	}
}
As a side note, I discovered a shortcut to accessing embedded font resources directly from XAML:
	<TextBlock x:Name="x_TestText" FontFamily="CacMoose.ttf#CAC Moose" /> 
or… 
	<TextBlock x:Name="x_TestText" FontFamily="Content/Zips/Files.zip#CAC Moose" />


You will need to set the CacMose.ttf file's Build Action to Resource.

If you want to read more about Silverlight resource files, check this out from MSDN: http://msdn.microsoft.com/en-us/library/cc296240(VS.95).aspx

Bookmark and Share

A TextBox that selects its text on focus for Silverlight

by Jim McCurdy 28. August 2009 22:36

A minor annoyance of mine is that there is no way to wire up a standard Silverlight TextBox to select its text when it receives the keyboard focus; either via a mouse click or a tab key.

And since users are accustomed to web apps, browsers, and desktop applications that offer the the convenience of selecting textbox text  upon focus, I wanted that behavior in my Silverlight applications.   So to satisfy user expectations as a matter of consistency, I wrote a very simple derived class, TextBoxEx, that will offer this functionality.  The TextBoxEx class derives from TextBox, and can be referenced in XAML for any and all of your TextBox’s.  There are no methods to call.  It just listens for Focus events and selects it own text.  Very simple.

Usage is as follows:

  • In XAML, reference the assembly where you implement the TextBoxEx class listed below, and add as many TextBoxEx elements as you need.  The example below uses data binding to display a username.
<UserControl x:Class="MyApp.MainPage"
	xmlns="http://schemas.microsoft.com/client/2007"     
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     
	xmlns:c="clr-namespace:ClassLibrary;assembly=ClassLibrary"  
>  
.     
.     
.     
<c:TextBoxEx Text="{Binding User.Name, Mode=TwoWay}" Width="120" />

The code below has been tested and used with Silverlight 3 and Silverlight 4.

using System.Windows;
using System.Windows.Controls;

namespace ClassLibrary
{
	// This TextBox derived class selects all text when it receives focus
	public class TextBoxEx : TextBox
	{
		public TextBoxEx()
		{
			base.GotFocus += OnGotFocus;
		}

		private void OnGotFocus(object sender, RoutedEventArgs e)
		{
			base.SelectAll();
		}
	}
}
Bookmark and Share

Settings for Silverlight using IsolatedStorageSettings

by Jim McCurdy 14. August 2009 22:34

Here is a simple static class, Settings, that offers methods to Read and Write application settings and preferences from Silverlight applications.  These settings are similar to the ones that Windows apps would store in the registry or in an INI file, but in this case, are written to Silverlight’s isolated storage.

This class wraps Silverlight’s IsolatedStorageSettings class, so remember; since a user has the freedom to purge isolated storage at will, this class is mostly useful for storing application preferences like layout sizes and locations, and other creature comforts for regular users.  This class can read or write any data type to settings storage; the IsolatedStorageSettings class will automatically serialize and deserialize the settings for you.

The code below has been tested and used with Silverlight 3 and Silverlight 4.

using System.IO.IsolatedStorage;

namespace ClassLibrary
{
	public static class Settings
	{
		public static TT Read<TT>(string name)
		{
			return Read<TT>(name, default(TT));
		}

		public static TT Read<TT>(string name, TT defaultValue)
		{
			IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
			TT value;
			if (settings == null || !settings.TryGetValue<TT>(name, out value))
				return defaultValue;
			return value;
		}

		public static void Write<TT>(string name, TT value)
		{
			IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
			if (settings == null)
				return;
			if (settings.Contains(name))
				settings[name] = value;
			else
				settings.Add(name, value);
			settings.Save();
		}
	}
}
Bookmark and Share

The simplest way to detect DoubleClick in Silverlight

by Jim McCurdy 7. August 2009 22:32

Here is a simple static class, MouseButtonHelper, that offers a single method, IsDoubleClick,  to determine if a standard MouseLeftButtonDown or MouseLeftButtonUp event is a double click.  In the past I have used timers, and Triggers and Behaviors to accomplish the same thing, but this approach is less code, less XAML, and uses a lot less resources.

Usage is as follows:

  • In XAML or in code behind, handle the standard MouseLeftButtonDown or MouseLeftButtonUp event for the object you wish to perform double click handling.
  • Implement the handler function for the above event as follows:
public void OnMouseButtonDown(object sender, MouseButtonEventArgs e)
{
	bool doubleClick = MouseButtonHelper.EventIsDoubleClick(sender, e);
	if (doubleClick)
		MessageBox.Show("Double click detected!", "Alert", MessageBoxButton.OK);
}

The code below has been tested and used with Silverlight 3 and Silverlight 4.

using System;
using System.Windows;
using System.Windows.Input;

namespace ClassLibrary
{
	internal static class MouseButtonHelper
	{
		private const long k_DoubleClickSpeed = 500;
		private const double k_MaxMoveDistance = 20;

		private static long m_LastClickTicks = 0;
		private static Point m_LastPosition;
		private static object m_LastSender;

		internal static bool IsDoubleClick(object sender, MouseButtonEventArgs e)
		{
			bool senderMatch = sender.Equals(m_LastSender);
			m_LastSender = sender;

			long clickTicks = DateTime.Now.Ticks;
			Point position = e.GetPosition(null);
			if (senderMatch)
			{
				long elapsedTicks = clickTicks - m_LastClickTicks;
				long elapsedTime = elapsedTicks / TimeSpan.TicksPerMillisecond;
				double distance = position.Distance(m_LastPosition);
				if (elapsedTime <= k_DoubleClickSpeed && distance <= k_MaxMoveDistance)
				{
					// Double click!
					m_LastClickTicks = 0;
					return true;
				}
			}

			// Not a double click
			m_LastClickTicks = clickTicks;
			m_LastPosition = position;
			return false;
		}

		private static double Distance(this Point pointA, Point pointB)
		{
			double x = pointA.X - pointB.X;
			double y = pointA.Y - pointB.Y;
			return Math.Sqrt(x * x + y * y);
		}
	}
}
Bookmark and Share

Cookies for Silverlight

by Jim McCurdy 31. July 2009 22:30

Here is a simple static class, Cookie, that offers methods to Read and Write cookies from Silverlight applications.  You can also specify the cookie expiration as a number of days:

  • expireDays = 0, indicates a session cookie that will not be written to disk
  • expireDays = -1, indicates that the cookie will not expire and will be permanent
  • expireDays = n, indicates that the cookie will expire in “n” days

The code below has been tested and used with Silverlight 3 and Silverlight 4.

using System;
using System.Windows.Browser;

namespace ClassLibrary
{
	public static class Cookie
	{
		public static bool Exists(string key, string value)
		{
			return HtmlPage.Document.Cookies.Contains(key + "=" + value);
		}

		public static string Read(string key)
		{
			string[] cookies = HtmlPage.Document.Cookies.Split(';');
			foreach (string cookie in cookies)
			{
				string[] keyValuePair = cookie.Split('=');
				if (keyValuePair.Length == 2 && key == keyValuePair[0].Trim())
					return keyValuePair[1].Trim();
			}

			return null;
		}

		public static void Write(string key, string value, int expireDays)
		{
			// expireDays = 0, indicates a session cookie that will not be written to disk 
			// expireDays = -1, indicates that the cookie will not expire and will be permanent
			// expireDays = n, indicates that the cookie will expire in “n” days
			string expires = "";
			if (expireDays != 0)
			{
				DateTime expireDate = (expireDays > 0 ?
				DateTime.Now + TimeSpan.FromDays(expireDays) :
				DateTime.MaxValue);
				expires = ";expires=" + expireDate.ToString("R");
			}

			string cookie = key + "=" + value + expires;
			HtmlPage.Document.SetProperty("cookie", cookie);
		}

		public static void Delete(string key)
		{
			DateTime expireDate = DateTime.Now - TimeSpan.FromDays(1); // yesterday
			string expires = ";expires=" + expireDate.ToString("R");
			string cookie = key + "=" + expires;
			HtmlPage.Document.SetProperty("cookie", cookie);
		}
	}
}
Bookmark and Share

ColorFromString for Silverlight or .NET

by Jim McCurdy 24. July 2009 18:59

Here is a simple static class, ColorFromString, that offers a single string extension method, ToColor, to create Color objects from various strings representations of color:

  • Named colors
    • “Red”
    • “Yellow”
    • “Chartreuse”
  • ARGB colors as Hex (8 nibbles)
    • “#FF00FF00” for opaque green
    • “#80FF00FF” for translucent magenta
  • RGB colors as Hex (6 nibbles)
    • “#0000FF” for blue
    • “#FFFF00” for yellow
  • ARGB colors as comma or space separated decimal
    • “255,0,255,0” for opaque green
    • “128,255,0,255” for translucent magenta
  • RGB colors as comma or space separated decimal
    • “0,0,255” for blue
    • “255,255,0” for yellow

The code below has been tested and used with Silverlight 3 and Silverlight 4., but can be made to work with .NET by changing the System references.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Media;

namespace ClassLibrary
{
	public static class ColorFromString
	{
		private static Dictionary<string, Color> namedColors =
		new Dictionary<string, Color>();

		// Extension for string
		public static Color ToColor(this string value)
		{
			if (value == null)
				return Colors.Red;

			// Named Colors
			string valueLower = value.ToLower();
			if (namedColors.ContainsKey(valueLower))
				return namedColors[valueLower];

			// #ARGB and #RGB Hex Colors
			if (value[0] == '#')
				value = value.Remove(0, 1);

			int length = value.Length;
			if ((length == 6 || length == 8) && IsHexColor(value))
			{
				if (length == 8)
					return Color.FromArgb(
					byte.Parse(value.Substring(0, 2), NumberStyles.HexNumber),
					byte.Parse(value.Substring(2, 2), NumberStyles.HexNumber),
					byte.Parse(value.Substring(4, 2), NumberStyles.HexNumber),
					byte.Parse(value.Substring(6, 2), NumberStyles.HexNumber));

				if (length == 6)
					return Color.FromArgb(0xff,
					byte.Parse(value.Substring(0, 2), NumberStyles.HexNumber),
					byte.Parse(value.Substring(2, 2), NumberStyles.HexNumber),
					byte.Parse(value.Substring(4, 2), NumberStyles.HexNumber));
			}

			// A,R,G,B and R,G,B Colors
			string[] argb = value.Split(
			new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (argb != null)
			{
				if (argb.Length == 4)
					return Color.FromArgb(
					byte.Parse(argb[0]), byte.Parse(argb[1]), byte.Parse(argb[2]),
					byte.Parse(argb[3]));

				if (argb.Length == 3)
					return Color.FromArgb(0xff,
					byte.Parse(argb[0]), byte.Parse(argb[1]), byte.Parse(argb[2]));
			}

			return Colors.Red;
		}

		private static bool IsHexColor(string value)
		{
			if (value == null)
				return false;

			foreach (char c in value.ToCharArray())
				if (!Uri.IsHexDigit(c))
					return false;

			return true;
		}

		static ColorFromString()
		{
			namedColors.Add("aliceblue", ToColor("#f0f8ff"));
			namedColors.Add("antiquewhite", ToColor("#faebd7"));
			namedColors.Add("aqua", ToColor("#00ffff"));
			namedColors.Add("aquamarine", ToColor("#7fffd4"));
			namedColors.Add("azure", ToColor("#f0ffff"));
			namedColors.Add("beige", ToColor("#f5f5dc"));
			namedColors.Add("bisque", ToColor("#ffe4c4"));
			namedColors.Add("black", ToColor("#000000"));
			namedColors.Add("blanchedalmond", ToColor("#ffebcd"));
			namedColors.Add("blue", ToColor("#0000ff"));
			namedColors.Add("blueviolet", ToColor("#8a2be2"));
			namedColors.Add("brown", ToColor("#a52a2a"));
			namedColors.Add("burlywood", ToColor("#deb887"));
			namedColors.Add("cadetblue", ToColor("#5f9ea0"));
			namedColors.Add("chartreuse", ToColor("#7fff00"));
			namedColors.Add("chocolate", ToColor("#d2691e"));
			namedColors.Add("coral", ToColor("#ff7f50"));
			namedColors.Add("cornflowerblue", ToColor("#6495ed"));
			namedColors.Add("cornsilk", ToColor("#fff8dc"));
			namedColors.Add("crimson", ToColor("#dc143c"));
			namedColors.Add("cyan", ToColor("#00ffff"));
			namedColors.Add("darkblue", ToColor("#00008b"));
			namedColors.Add("darkcyan", ToColor("#008b8b"));
			namedColors.Add("darkgoldenrod", ToColor("#b8860b"));
			namedColors.Add("darkgray", ToColor("#a9a9a9"));
			namedColors.Add("darkgreen", ToColor("#006400"));
			namedColors.Add("darkkhaki", ToColor("#bdb76b"));
			namedColors.Add("darkmagenta", ToColor("#8b008b"));
			namedColors.Add("darkolivegreen", ToColor("#556b2f"));
			namedColors.Add("darkorange", ToColor("#ff8c00"));
			namedColors.Add("darkorchid", ToColor("#9932cc"));
			namedColors.Add("darkred", ToColor("#8b0000"));
			namedColors.Add("darksalmon", ToColor("#e9967a"));
			namedColors.Add("darkseagreen", ToColor("#8fbc8f"));
			namedColors.Add("darkslateblue", ToColor("#483d8b"));
			namedColors.Add("darkslategray", ToColor("#2f4f4f"));
			namedColors.Add("darkturquoise", ToColor("#00ced1"));
			namedColors.Add("darkviolet", ToColor("#9400d3"));
			namedColors.Add("deeppink", ToColor("#ff1493"));
			namedColors.Add("deepskyblue", ToColor("#00bfff"));
			namedColors.Add("dimgray", ToColor("#696969"));
			namedColors.Add("dodgerblue", ToColor("#1e90ff"));
			namedColors.Add("firebrick", ToColor("#b22222"));
			namedColors.Add("floralwhite", ToColor("#fffaf0"));
			namedColors.Add("forestgreen", ToColor("#228b22"));
			namedColors.Add("fuchsia", ToColor("#ff00ff"));
			namedColors.Add("gainsboro", ToColor("#dcdcdc"));
			namedColors.Add("ghostwhite", ToColor("#f8f8ff"));
			namedColors.Add("gold", ToColor("#ffd700"));
			namedColors.Add("goldenrod", ToColor("#daa520"));
			namedColors.Add("gray", ToColor("#808080"));
			namedColors.Add("green", ToColor("#008000"));
			namedColors.Add("greenyellow", ToColor("#adff2f"));
			namedColors.Add("honeydew", ToColor("#f0fff0"));
			namedColors.Add("hotpink", ToColor("#ff69b4"));
			namedColors.Add("indianred", ToColor("#cd5c5c"));
			namedColors.Add("indigo", ToColor("#4b0082"));
			namedColors.Add("ivory", ToColor("#fffff0"));
			namedColors.Add("khaki", ToColor("#f0e68c"));
			namedColors.Add("lavender", ToColor("#e6e6fa"));
			namedColors.Add("lavenderblush", ToColor("#fff0f5"));
			namedColors.Add("lawngreen", ToColor("#7cfc00"));
			namedColors.Add("lemonchiffon", ToColor("#fffacd"));
			namedColors.Add("lightblue", ToColor("#add8e6"));
			namedColors.Add("lightcoral", ToColor("#f08080"));
			namedColors.Add("lightcyan", ToColor("#e0ffff"));
			namedColors.Add("lightgoldenrodyellow", ToColor("#fafad2"));
			namedColors.Add("lightgreen", ToColor("#90ee90"));
			namedColors.Add("lightgrey", ToColor("#d3d3d3"));
			namedColors.Add("lightpink", ToColor("#ffb6c1"));
			namedColors.Add("lightsalmon", ToColor("#ffa07a"));
			namedColors.Add("lightseagreen", ToColor("#20b2aa"));
			namedColors.Add("lightskyblue", ToColor("#87cefa"));
			namedColors.Add("lightslategray", ToColor("#778899"));
			namedColors.Add("lightsteelblue", ToColor("#b0c4de"));
			namedColors.Add("lightyellow", ToColor("#ffffe0"));
			namedColors.Add("lime", ToColor("#00ff00"));
			namedColors.Add("limegreen", ToColor("#32cd32"));
			namedColors.Add("linen", ToColor("#faf0e6"));
			namedColors.Add("magenta", ToColor("#ff00ff"));
			namedColors.Add("maroon", ToColor("#800000"));
			namedColors.Add("mediumaquamarine", ToColor("#66cdaa"));
			namedColors.Add("mediumblue", ToColor("#0000cd"));
			namedColors.Add("mediumorchid", ToColor("#ba55d3"));
			namedColors.Add("mediumpurple", ToColor("#9370db"));
			namedColors.Add("mediumseagreen", ToColor("#3cb371"));
			namedColors.Add("mediumslateblue", ToColor("#7b68ee"));
			namedColors.Add("mediumspringgreen", ToColor("#00fa9a"));
			namedColors.Add("mediumturquoise", ToColor("#48d1cc"));
			namedColors.Add("mediumvioletred", ToColor("#c71585"));
			namedColors.Add("midnightblue", ToColor("#191970"));
			namedColors.Add("mintcream", ToColor("#f5fffa"));
			namedColors.Add("mistyrose", ToColor("#ffe4e1"));
			namedColors.Add("moccasin", ToColor("#ffe4b5"));
			namedColors.Add("navajowhite", ToColor("#ffdead"));
			namedColors.Add("navy", ToColor("#000080"));
			namedColors.Add("oldlace", ToColor("#fdf5e6"));
			namedColors.Add("olive", ToColor("#808000"));
			namedColors.Add("olivedrab", ToColor("#6b8e23"));
			namedColors.Add("orange", ToColor("#ffa500"));
			namedColors.Add("orangered", ToColor("#ff4500"));
			namedColors.Add("orchid", ToColor("#da70d6"));
			namedColors.Add("palegoldenrod", ToColor("#eee8aa"));
			namedColors.Add("palegreen", ToColor("#98fb98"));
			namedColors.Add("paleturquoise", ToColor("#afeeee"));
			namedColors.Add("palevioletred", ToColor("#db7093"));
			namedColors.Add("papayawhip", ToColor("#ffefd5"));
			namedColors.Add("peachpuff", ToColor("#ffdab9"));
			namedColors.Add("peru", ToColor("#cd853f"));
			namedColors.Add("pink", ToColor("#ffc0cb"));
			namedColors.Add("plum", ToColor("#dda0dd"));
			namedColors.Add("powderblue", ToColor("#b0e0e6"));
			namedColors.Add("purple", ToColor("#800080"));
			namedColors.Add("red", ToColor("#ff0000"));
			namedColors.Add("rosybrown", ToColor("#bc8f8f"));
			namedColors.Add("royalblue", ToColor("#4169e1"));
			namedColors.Add("saddlebrown", ToColor("#8b4513"));
			namedColors.Add("salmon", ToColor("#fa8072"));
			namedColors.Add("sandybrown", ToColor("#f4a460"));
			namedColors.Add("seagreen", ToColor("#2e8b57"));
			namedColors.Add("seashell", ToColor("#fff5ee"));
			namedColors.Add("sienna", ToColor("#a0522d"));
			namedColors.Add("silver", ToColor("#c0c0c0"));
			namedColors.Add("skyblue", ToColor("#87ceeb"));
			namedColors.Add("slateblue", ToColor("#6a5acd"));
			namedColors.Add("slategray", ToColor("#708090"));
			namedColors.Add("snow", ToColor("#fffafa"));
			namedColors.Add("springgreen", ToColor("#00ff7f"));
			namedColors.Add("steelblue", ToColor("#4682b4"));
			namedColors.Add("tan", ToColor("#d2b48c"));
			namedColors.Add("teal", ToColor("#008080"));
			namedColors.Add("thistle", ToColor("#d8bfd8"));
			namedColors.Add("tomato", ToColor("#ff6347"));
			namedColors.Add("turquoise", ToColor("#40e0d0"));
			namedColors.Add("violet", ToColor("#ee82ee"));
			namedColors.Add("wheat", ToColor("#f5deb3"));
			namedColors.Add("white", ToColor("#ffffff"));
			namedColors.Add("whitesmoke", ToColor("#f5f5f5"));
			namedColors.Add("yellow", ToColor("#ffff00"));
			namedColors.Add("yellowgreen", ToColor("#9acd32"));
		}
	}
}

Here are the color patches:

Aliceblue
F0F8FF
Antiquewhite
FAEBD7
Aqua
00FFFF
Aquamarine
7FFFD4
Azure
F0FFFF
Beige
F5F5DC
Bisque
FFE4C4
Black
000000
Blanchedalmond
FFEBCD
Blue
0000FF
Blueviolet
8A2BE2
Brown
A52A2A
Burlywood
DEB887
Cadetblue
5F9EA0
Chartreuse
7FFF00
Chocolate
D2691E
Coral
FF7F50
Cornflowerblue
6495ED
Cornsilk
FFF8DC
Crimson
DC143C
Cyan
00FFFF
Darkblue
00008B
Darkcyan
008B8B
Darkgoldenrod
B8860B
Darkgray
A9A9A9
Darkgreen
006400
Darkkhaki
BDB76B
Darkmagenta
8B008B
Darkolivegreen
556B2F
Darkorange
FF8C00
Darkorchid
9932CC
Darkred
8B0000
Darksalmon
E9967A
Darkseagreen
8FBC8F
Darkslateblue
483D8B
Darkslategray
2F4F4F
Darkturquoise
00CED1
Darkviolet
9400D3
deeppink
FF1493
Deepskyblue
00BFFF
Dimgray
696969
Dodgerblue
1E90FF
Firebrick
B22222
Floralwhite
FFFAF0
Forestgreen
228B22
Fuchsia
FF00FF
Gainsboro
DCDCDC
Ghostwhite
F8F8FF
Gold
FFD700
Goldenrod
DAA520
Gray
808080
Green
008000
Greenyellow
ADFF2F
Honeydew
F0FFF0
Hotpink
FF69B4
Indianred
CD5C5C
Indigo
4B0082
Ivory
FFFFF0
Khaki
F0E68C
Lavender
E6E6FA
Lavenderblush
FFF0F5
Lawngreen
7CFC00
Lemonchiffon
FFFACD
Lightblue
ADD8E6
Lightcoral
F08080
Lightcyan
E0FFFF
Lightgoldenrodyellow
FAFAD2
Lightgreen
90EE90
Lightgrey
D3D3D3
Lightpink
FFB6C1
Lightsalmon
FFA07A
Lightseagreen
20B2AA
Lightskyblue
87CEFA
Lightslategray
778899
Lightsteelblue
B0C4DE
Lightyellow
FFFFE0
Lime
00FF00
Limegreen
32CD32
Linen
FAF0E6
Magenta
FF00FF
Maroon
800000
Mediumaquamarine
66CDAA
Mediumblue
0000CD
Mediumorchid
BA55D3
Mediumpurple
9370D8
Mediumseagreen
3CB371
Mediumslateblue
7B68EE
Mediumspringgreen
00FA9A
Mediumturquoise
48D1CC
Mediumvioletred
C71585
Midnightblue
191970
Mintcream
F5FFFA
Mistyrose
FFE4E1
Moccasin
FFE4B5
Navajowhite
FFDEAD
Navy
000080
Oldlace
FDF5E6
Olive
808000
Olivedrab
688E23
Orange
FFA500
Orangered
FF4500
Orchid
DA70D6
Palegoldenrod
EEE8AA
Palegreen
98FB98
Paleturquoise
AFEEEE
Palevioletred
D87093
Papayawhip
FFEFD5
Peachpuff
FFDAB9
Peru
CD853F
Pink
FFC0CB
Plum
DDA0DD
Powderblue
B0E0E6
Purple
800080
Red
FF0000
Rosybrown
BC8F8F
Royalblue
4169E1
Saddlebrown
8B4513
Salmon
FA8072
Sandybrown
F4A460
Seagreen
2E8B57
Seashell
FFF5EE
Sienna
A0522D
Silver
C0C0C0
Skyblue
87CEEB
Slateblue
6A5ACD
Slategray
708090
Snow
FFFAFA
Springgreen
00FF7F
Steelblue
4682B4
Tan
D2B48C
Teal
008080
Thistle
D8BFD8
Tomato
FF6347
Turquoise
40E0D0
Violet
EE82EE
Wheat
F5DEB3
White
FFFFFF
Whitesmoke
F5F5F5
Yellow
FFFF00
YellowGreen
9ACD32
Bookmark and Share

My Photo Jim is always looking for new clients in need of software development expertise.  He is particularly well suited for Silverlight, .NET, ASP.NET, and WPF projects.

Professional Biography

Jim McCurdy operates Face to Face Software, where he works designing, developing, and managing software projects for a variety of clients.

Jim currently specializes in development projects for Silverlight, .NET, ASP.NET, and WPF platforms. One such project is a complete web site using Silverlight, .NET, and C#; a unique financial and lifestyle planning web application at YinYangMoney.com.

Jim has worked in the software industry for as long as he can remember, and has broad expertise in Web, Windows, and systems technologies. Jim has been a team player on many agile development projects throughout his career, and is a founding member at software startups Astral Development and Powerhouse Entertainment.

You can Email Jim at Face to Face Software.


Welcome Readers