Generic class for deep clone of Silverlight and CLR objects

by Jim McCurdy 23. July 2010 09:51
Update: As of 8/11/2010, I have released the following update of the source code for my CloneObject class.  This update adds the ability to clone any attached properties found in the loaded assemblies, and also removes an unnecessary dependency property loop which will speed up some operations… Update: As of 7/19/2010, I have updated the source code for my CloneObject class.  The update improves on the ability to clone arrays and other IEnumerable objects, and simplifies some other operations… 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.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Resources; namespace ClassLibrary { //[Obfuscation(Exclude = true)] internal static class CloneObject { private static List<FieldInfo> _attachedProperties = null; // Extension for any class object internal static TT DeepClone<TT>(this TT source, bool? cloneAttachedProperties = null) { // Jim McCurdy's DeepClone if (cloneAttachedProperties == null) cloneAttachedProperties = (source is DependencyObject); // The first time this method is called, compute a list of all // attached properties that exist in this XAP's assemblies if (cloneAttachedProperties == true && _attachedProperties == null) { _attachedProperties = new List<FieldInfo>(); List<Assembly> assemblies = GetLoadedAssemblies(); foreach (Assembly assembly in assemblies) GetAttachedProperties(_attachedProperties, assembly); } 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) CloneList(source as IList, clone as IList); 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; if (source is DependencyObject) { DependencyObject sourcedp = source as DependencyObject; DependencyObject clonedp = clone as DependencyObject; // Clone attached properties if (_attachedProperties != null && _attachedProperties.Count > 0) foreach (FieldInfo field in _attachedProperties) CloneDependencyProperty(sourcedp, clonedp, field, true); // Clone dependency properties FieldInfo[] fields = source.GetType().GetFields(flags | BindingFlags.Static); foreach (FieldInfo field in fields) CloneDependencyProperty(sourcedp, clonedp, field, false); } // 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, bool isAttached) { try { // 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 = null; try { sourceValue = sourceObject.GetValue(dp); } catch (Exception) { } if (sourceValue == null) return; // Don't set attached properties if we don't have to if (isAttached) { Type sourceType = sourceValue.GetType(); if (sourceType.IsValueType && sourceValue.Equals(Activator.CreateInstance(sourceType))) 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 { if (!property.CanRead || !property.CanWrite || property.GetIndexParameters().Length != 0) return; // 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); if (!publicSetter && !isList) return; object sourceValue = property.GetValue(source, null); if (sourceValue == null) return; if (!publicSetter && 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; } private static List<Assembly> GetLoadedAssemblies() { List<Assembly> assemblies = new List<Assembly>(); foreach (AssemblyPart part in Deployment.Current.Parts) { StreamResourceInfo sri = Application.GetResourceStream(new Uri(part.Source, UriKind.Relative)); if (sri == null) continue; Assembly assembly = new AssemblyPart().Load(sri.Stream); if (assembly != null && !assemblies.Contains(assembly)) assemblies.Add(assembly); } // Additional assemblies that are not found when examining of Deployment.Current.Parts above Type[] types = { typeof(System.Windows.Application), // System.Windows.dll, #if INCLUDE_ASSEMBLIES_WITHOUT_ATTACHED_PROPERTIES typeof(System.Action), // mscorlib.dll, typeof(System.Uri), // System.dll, typeof(System.Lazy<int>), // System.Core.dll, typeof(System.Net.Cookie), // System.Net.dll, typeof(System.Runtime.Serialization.StreamingContext), // System.Runtime.Serialization.dll, typeof(System.ServiceModel.XmlSerializerFormatAttribute), // System.ServiceModel.dll, typeof(System.Windows.Browser.BrowserInformation), // System.Windows.Browser.dll, typeof(System.Xml.ConformanceLevel), // System.Xml.dll, #endif }; foreach (Type type in types) { Assembly assembly = type.Assembly; if (assembly != null && !assemblies.Contains(assembly)) assemblies.Add(assembly); } return assemblies; } private static bool GetAttachedProperties(List<FieldInfo> attachedProperties, Assembly assembly) { BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static; foreach (Type type in assembly.GetTypes()) { FieldInfo[] fields = type.GetFields(flags); MethodInfo[] methods = null; foreach (FieldInfo field in fields) { if (!field.FieldType.Is(typeof(DependencyProperty))) continue; if (!field.Name.EndsWith("Property")) continue; string fieldName = field.Name.Replace("Property", ""); string getName = "Get" + fieldName; string setName = "Set" + fieldName; bool foundGet = false; bool foundSet = false; if (methods == null) methods = type.GetMethods(flags); foreach (MethodInfo method in methods) { if (method.Name == getName && method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType.Is(typeof(DependencyObject))) foundGet = true; else if (method.Name == setName && method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType.Is(typeof(DependencyObject))) foundSet = true; if (foundGet && foundSet) break; } if (!(foundGet && foundSet)) continue; try { DependencyProperty dp = field.GetValue(null) as DependencyProperty; } catch (Exception) { continue; } // Found an attached Dependency Property attachedProperties.Add(field); } } return true; } } internal static class Extensions { // Extension for Type internal static bool Is(this Type type, Type baseType) { return (type.Equals(baseType) || type.IsSubclassOf(baseType)); } // 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); } } }

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:c="clr-namespace:ClassLibrary" 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:c="clr-namespace:ClassLibrary" 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!

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

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(); } } }

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(); } } }

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 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 = 10; private static long _LastClickTicks = 0; private static Point _LastPosition; private static WeakReference _LastSender; internal static bool IsDoubleClick(object sender, MouseButtonEventArgs e) { Point position = e.GetPosition(null); long clickTicks = DateTime.Now.Ticks; long elapsedTicks = clickTicks - _LastClickTicks; long elapsedTime = elapsedTicks / TimeSpan.TicksPerMillisecond; bool quickClick = (elapsedTime <= k_DoubleClickSpeed); bool senderMatch = (_LastSender != null && sender.Equals(_LastSender.Target)); if (senderMatch && quickClick && position.Distance(_LastPosition) <= k_MaxMoveDistance) { // Double click! _LastClickTicks = 0; _LastSender = null; return true; } // Not a double click _LastClickTicks = clickTicks; _LastPosition = position; if (!quickClick) _LastSender = new WeakReference(sender); 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); } } }

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); } } }

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

About Me

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

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 contract development projects for Silverlight, WPF, and ASP.NET platforms. Recent projects include two complete web sites developed using Silverlight, .NET, and C#. The first is YinYangMoney.com, a unique financial and lifestyle planning web application. The second is McPivot.com, a unique web application that presents custom queries and visualization of baseball data back to the 1800's.

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.

Blog Archive

Welcome Readers