check point
This commit is contained in:
164
framework/Inspectron.Settings/AdaptablePath.cs
Normal file
164
framework/Inspectron.Settings/AdaptablePath.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public class AdaptablePath<T> : Path<T>, IAdaptable, IDecoratable
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor</summary>
|
||||
/// <param name="last">Single object making up the path</param>
|
||||
public AdaptablePath(T last)
|
||||
: base(last)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor</summary>
|
||||
/// <param name="path">Path as sequence of objects</param>
|
||||
public AdaptablePath(IEnumerable<T> path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor</summary>
|
||||
/// <param name="path">Path as collection of objects</param>
|
||||
public AdaptablePath(ICollection<T> path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
#region IAdaptable, IDecoratable, and Related Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets an adapter of the specified type or null</summary>
|
||||
/// <param name="type">Adapter type</param>
|
||||
/// <returns>Adapter of the specified type or null</returns>
|
||||
public object GetAdapter(Type type)
|
||||
{
|
||||
object adapter = Last.As(type);
|
||||
if (adapter != null)
|
||||
return adapter;
|
||||
|
||||
if (type.IsAssignableFrom(GetType()))
|
||||
return this;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all decorators of the specified type</summary>
|
||||
/// <param name="type">Decorator type</param>
|
||||
/// <returns>Enumeration of non-null decorators that are of the specified type. The enumeration may be empty.</returns>
|
||||
public IEnumerable<object> GetDecorators(Type type)
|
||||
{
|
||||
foreach (object obj in Last.AsAll(type))
|
||||
yield return obj;
|
||||
}
|
||||
|
||||
// implement the following members as a convenience when extension methods aren't available
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter</summary>
|
||||
/// <typeparam name="U">Desired type, must be ref type</typeparam>
|
||||
/// <returns>Converted reference for the given object or null</returns>
|
||||
public U As<U>()
|
||||
where U : class
|
||||
{
|
||||
return Adapters.As<U>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
|
||||
/// <typeparam name="U">Desired type, must be ref type</typeparam>
|
||||
/// <returns>Converted reference for the given object</returns>
|
||||
public U Cast<U>() where U : class
|
||||
{
|
||||
return Adapters.Cast<U>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given reference can be converted to one of
|
||||
/// the desired type</summary>
|
||||
/// <typeparam name="U">Adapter type, must be ref type</typeparam>
|
||||
/// <returns>True iff the given object can be converted</returns>
|
||||
public bool Is<U>()
|
||||
where U : class
|
||||
{
|
||||
return Adapters.Is<U>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumeration of all decorators that can convert a reference to the given type</summary>
|
||||
/// <typeparam name="U">Decorator type, must be ref type</typeparam>
|
||||
/// <returns>Enumerable returning all decorators of the given type</returns>
|
||||
public IEnumerable<U> AsAll<U>()
|
||||
where U : class
|
||||
{
|
||||
return Adapters.AsAll<U>(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates object with path</summary>
|
||||
/// <param name="lhs">Prefix object</param>
|
||||
/// <param name="rhs">Optional path</param>
|
||||
/// <returns>Concatenated path, with lhs as first object</returns>
|
||||
public static AdaptablePath<T> operator +(T lhs, AdaptablePath<T> rhs)
|
||||
{
|
||||
if (rhs == null)
|
||||
return new AdaptablePath<T>(lhs);
|
||||
T[] path = new T[1 + rhs.Count];
|
||||
path[0] = lhs;
|
||||
rhs.CopyTo(path, 1);
|
||||
return new AdaptablePath<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates path with object</summary>
|
||||
/// <param name="lhs">Optional path</param>
|
||||
/// <param name="rhs">Suffix object</param>
|
||||
/// <returns>Concatenated path, with rhs as last object</returns>
|
||||
public static AdaptablePath<T> operator +(AdaptablePath<T> lhs, T rhs)
|
||||
{
|
||||
if (lhs == null)
|
||||
return new AdaptablePath<T>(rhs);
|
||||
T[] path = new T[lhs.Count + 1];
|
||||
lhs.CopyTo(path, 0);
|
||||
path[lhs.Count] = rhs;
|
||||
return new AdaptablePath<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates 2 paths</summary>
|
||||
/// <param name="lhs">First path. Can be null.</param>
|
||||
/// <param name="rhs">Second path. Can be null.</param>
|
||||
/// <returns>Concatenated path, with rhs as prefix and lhs as suffix. Is null if both lhs and rhs are null.</returns>
|
||||
public static AdaptablePath<T> operator +(AdaptablePath<T> lhs, AdaptablePath<T> rhs)
|
||||
{
|
||||
if (lhs == null)
|
||||
return rhs;
|
||||
if (rhs == null)
|
||||
return lhs;
|
||||
T[] path = new T[lhs.Count + rhs.Count];
|
||||
lhs.CopyTo(path, 0);
|
||||
rhs.CopyTo(path, lhs.Count);
|
||||
return new AdaptablePath<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the path type to another type</summary>
|
||||
/// <typeparam name="U">Desired type</typeparam>
|
||||
/// <param name="item">Item to convert</param>
|
||||
/// <returns>Item converted to given type or null</returns>
|
||||
protected override U Convert<U>(T item)
|
||||
{
|
||||
U u = item.As<U>();
|
||||
return u;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
framework/Inspectron.Settings/AdaptationException.cs
Normal file
25
framework/Inspectron.Settings/AdaptationException.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public class AdaptationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor</summary>
|
||||
/// <param name="message">Message explaining why this object couldn't be adapted</param>
|
||||
public AdaptationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor</summary>
|
||||
/// <param name="message">Message explaining why this object couldn't be adapted</param>
|
||||
/// <param name="innerException">The exception that prevented adaptation. Will become the
|
||||
/// InnerException property.</param>
|
||||
public AdaptationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
336
framework/Inspectron.Settings/Adapters.cs
Normal file
336
framework/Inspectron.Settings/Adapters.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public static class Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter</summary>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <param name="type">Desired type, should be ref type</param>
|
||||
/// <returns>Converted reference for the given object or null</returns>
|
||||
public static object As(this object reference, Type type)
|
||||
{
|
||||
if (reference == null)
|
||||
return null;
|
||||
|
||||
if (type == null)
|
||||
throw new ArgumentNullException("type");
|
||||
|
||||
// is the adapted object compatible?
|
||||
if (type.IsAssignableFrom(reference.GetType()))
|
||||
return reference;
|
||||
|
||||
// try to get an adapter
|
||||
var adaptable = reference as IAdaptable;
|
||||
if (adaptable != null)
|
||||
{
|
||||
object adapter = adaptable.GetAdapter(type);
|
||||
if (adapter != null)
|
||||
return adapter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter</summary>
|
||||
/// <typeparam name="T">Desired type, must be ref type</typeparam>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <returns>Converted reference for the given object or null</returns>
|
||||
public static T As<T>(this object reference)
|
||||
where T : class
|
||||
{
|
||||
if (reference == null)
|
||||
return null;
|
||||
|
||||
// try a normal cast
|
||||
var converted = reference as T;
|
||||
|
||||
// if that fails, try to get an adapter
|
||||
if (converted == null)
|
||||
{
|
||||
var adaptable = reference as IAdaptable;
|
||||
if (adaptable != null)
|
||||
converted = adaptable.GetAdapter(typeof(T)) as T;
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter</summary>
|
||||
/// <typeparam name="T">Desired type, must be ref type</typeparam>
|
||||
/// <param name="adaptable">Adaptable object</param>
|
||||
/// <returns>Converted reference for the given object or null</returns>
|
||||
public static T As<T>(this IAdaptable adaptable)
|
||||
where T : class
|
||||
{
|
||||
if (adaptable == null)
|
||||
return null;
|
||||
|
||||
// try a normal cast
|
||||
var converted = adaptable as T;
|
||||
|
||||
// if that fails, try to get an adapter
|
||||
if (converted == null)
|
||||
converted = adaptable.GetAdapter(typeof(T)) as T;
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <param name="type">Desired type, should be ref type</param>
|
||||
/// <returns>Converted reference for the given object</returns>
|
||||
public static object Cast(this object reference, Type type)
|
||||
{
|
||||
object converted = As(reference, type);
|
||||
if (converted == null)
|
||||
throw new AdaptationException(type.Name + " adapter required");
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
|
||||
/// <typeparam name="T">Desired type, must be ref type</typeparam>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <returns>Converted reference for the given object</returns>
|
||||
public static T Cast<T>(this object reference)
|
||||
where T : class
|
||||
{
|
||||
T converted = As<T>(reference);
|
||||
if (converted == null)
|
||||
throw new AdaptationException(typeof(T).Name + " adapter required");
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a reference to the given type by first trying a CLR cast, and then
|
||||
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
|
||||
/// <typeparam name="T">Desired type, must be ref type</typeparam>
|
||||
/// <param name="adaptable">Adaptable object</param>
|
||||
/// <returns>Converted reference for the given object</returns>
|
||||
public static T Cast<T>(this IAdaptable adaptable)
|
||||
where T : class
|
||||
{
|
||||
T converted = As<T>(adaptable);
|
||||
if (converted == null)
|
||||
throw new AdaptationException(typeof(T).Name + " adapter required");
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the given reference can be converted to one of
|
||||
/// the desired type</summary>
|
||||
/// <param name="reference">Reference to test</param>
|
||||
/// <param name="type">Desired type, should be ref type</param>
|
||||
/// <returns>True iff the given object can be converted</returns>
|
||||
public static bool Is(this object reference, Type type)
|
||||
{
|
||||
return As(reference, type) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the given reference can be converted to one of
|
||||
/// the desired type</summary>
|
||||
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
|
||||
/// <param name="reference">Reference to test</param>
|
||||
/// <returns>True iff the given object can be converted</returns>
|
||||
public static bool Is<T>(this object reference)
|
||||
where T : class
|
||||
{
|
||||
return As<T>(reference) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if the given reference can be converted to one of
|
||||
/// the desired type</summary>
|
||||
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
|
||||
/// <param name="adaptable">Adaptable object</param>
|
||||
/// <returns>True iff the given object can be converted</returns>
|
||||
public static bool Is<T>(this IAdaptable adaptable)
|
||||
where T : class
|
||||
{
|
||||
return As<T>(adaptable) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all decorators that can convert a reference to the given type</summary>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <param name="type">Decorator type, should be ref type</param>
|
||||
/// <returns>Enumerable returning all decorators of the given type.
|
||||
/// Decorators are never null. The enumeration may be empty.</returns>
|
||||
public static IEnumerable<object> AsAll(this object reference, Type type)
|
||||
{
|
||||
if (reference != null)
|
||||
{
|
||||
// if IDecoratable, use that to get decorators
|
||||
var decoratable = reference as IDecoratable;
|
||||
if (decoratable != null)
|
||||
return decoratable.GetDecorators(type);
|
||||
|
||||
// is the decorated object compatible?
|
||||
if (type.IsAssignableFrom(reference.GetType()))
|
||||
return new object[] { reference };
|
||||
}
|
||||
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all decorators that can convert a reference to the given type</summary>
|
||||
/// <typeparam name="T">Decorator type, must be ref type</typeparam>
|
||||
/// <param name="reference">Reference to convert</param>
|
||||
/// <returns>Enumerable returning all decorators of the given type.
|
||||
/// Decorators are never null. The enumeration may be empty.</returns>
|
||||
public static IEnumerable<T> AsAll<T>(this object reference)
|
||||
where T : class
|
||||
{
|
||||
if (reference != null)
|
||||
{
|
||||
// if IDecoratable, use that to get decorators
|
||||
var decoratable = reference as IDecoratable;
|
||||
if (decoratable != null)
|
||||
return AsAll<T>(decoratable);
|
||||
|
||||
// otherwise, cast
|
||||
var t = reference as T;
|
||||
if (t != null)
|
||||
return new T[] { t };
|
||||
}
|
||||
|
||||
return new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration of all decorators that can convert a reference to the given type</summary>
|
||||
/// <typeparam name="T">Decorator type, must be ref type</typeparam>
|
||||
/// <param name="decoratable">Decoratable object</param>
|
||||
/// <returns>Enumerable returning all decorators of the given type</returns>
|
||||
public static IEnumerable<T> AsAll<T>(this IDecoratable decoratable)
|
||||
where T : class
|
||||
{
|
||||
if (decoratable != null)
|
||||
{
|
||||
foreach (object decorator in decoratable.GetDecorators(typeof(T)))
|
||||
yield return decorator as T;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an adapter that converts an enumerable to an enumerable of another type</summary>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <param name="type">Adapter type, should be ref type</param>
|
||||
/// <returns>Enumerable returning adapted items</returns>
|
||||
public static IEnumerable<object> AsIEnumerable(this IEnumerable enumerable, Type type)
|
||||
{
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
object adapter = As(item, type);
|
||||
if (adapter != null)
|
||||
yield return adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumeration for an adapter that converts an enumerable to an enumerable of another type</summary>
|
||||
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <returns>Enumerable returning adapted items</returns>
|
||||
public static IEnumerable<T> AsIEnumerable<T>(this IEnumerable enumerable)
|
||||
where T : class
|
||||
{
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
T adapter = As<T>(item);
|
||||
if (adapter != null)
|
||||
yield return adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if any of the items in the enumerable are
|
||||
/// adaptable to the given type</summary>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <param name="type">Adapter type, should be ref type</param>
|
||||
/// <returns>True iff any of the items in the enumerable are adaptable to
|
||||
/// the given type</returns>
|
||||
public static bool Any(this IEnumerable enumerable, Type type)
|
||||
{
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
object adapter = As(item, type);
|
||||
if (adapter != null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if any of the items in the enumerable are
|
||||
/// adaptable to the given type</summary>
|
||||
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <returns>True iff any of the items in the enumerable are adaptable to
|
||||
/// the given type</returns>
|
||||
public static bool Any<T>(this IEnumerable enumerable)
|
||||
where T : class
|
||||
{
|
||||
return Any(enumerable, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if all of the items in the enumerable are
|
||||
/// adaptable to the given type</summary>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <param name="type">Adapter type, should be ref type</param>
|
||||
/// <returns>True iff all of the items in the enumerable are adaptable to
|
||||
/// the given type</returns>
|
||||
public static bool All(this IEnumerable enumerable, Type type)
|
||||
{
|
||||
if (enumerable != null)
|
||||
{
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
object adapter = As(item, type);
|
||||
if (adapter == null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating if all of the items in the enumerable are
|
||||
/// adaptable to the given type</summary>
|
||||
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
|
||||
/// <param name="enumerable">Enumerable to adapt</param>
|
||||
/// <returns>True iff all of the items in the enumerable are adaptable to
|
||||
/// the given type</returns>
|
||||
public static bool All<T>(this IEnumerable enumerable)
|
||||
where T : class
|
||||
{
|
||||
return All(enumerable, typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Inspectron.Settings.Attributes
|
||||
{
|
||||
public class SettingDescriptionAttribute: Attribute
|
||||
{
|
||||
public string Description { get; }
|
||||
|
||||
public SettingDescriptionAttribute(string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Inspectron.Settings.Attributes
|
||||
{
|
||||
public class SettingPreviewAttribute: System.Attribute
|
||||
{
|
||||
public Type PreviewClass { get; }
|
||||
public string PreviewFunction { get; }
|
||||
|
||||
|
||||
public SettingPreviewAttribute(Type previewClass, string previewFunction)
|
||||
{
|
||||
PreviewClass = previewClass;
|
||||
PreviewFunction = previewFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
494
framework/Inspectron.Settings/BoundPropertyDescriptor.cs
Normal file
494
framework/Inspectron.Settings/BoundPropertyDescriptor.cs
Normal file
@@ -0,0 +1,494 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// A specialization of System.ComponentModel.PropertyDescriptor that is bound
|
||||
/// to a specific property of an object or type. If the property's setter is private,
|
||||
/// this BoundPropertyDescriptor's IsReadOnly property is true.</summary>
|
||||
/// <remarks>Use this class to expose an object or type's property for property editing.</remarks>
|
||||
public class BoundPropertyDescriptor : PropertyDescriptor
|
||||
{
|
||||
public delegate object OnClickDelegate(object sender, EventArgs e);
|
||||
public OnClickDelegate OnClick { get; set; }
|
||||
/// <summary>
|
||||
/// Constructor for instance properties</summary>
|
||||
/// <param name="owner">Property owner</param>
|
||||
/// <param name="expression">Lambda expression that accesses the property;
|
||||
/// e.g., () => myObject.MyProperty</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
public BoundPropertyDescriptor(
|
||||
object owner,
|
||||
Expression<Func<object>> expression,
|
||||
string displayName,
|
||||
string category,
|
||||
string description)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPropertyInfo(expression);
|
||||
Init(owner, null, null, propertyInfo, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static properties</summary>
|
||||
/// <param name="ownerType">Type holding static property</param>
|
||||
/// <param name="expression">Lambda expression that accesses the property;
|
||||
/// e.g., () => MyClass.MyProperty</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
public BoundPropertyDescriptor(
|
||||
Type ownerType,
|
||||
Expression<Func<object>> expression,
|
||||
string displayName,
|
||||
string category,
|
||||
string description)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPropertyInfo(expression);
|
||||
Init(null, ownerType, null, propertyInfo, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for instance properties</summary>
|
||||
/// <param name="owner">Property owner</param>
|
||||
/// <param name="expression">Lambda expression that accesses the property;
|
||||
/// e.g., () => myObject.MyProperty</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
/// <param name="converter">TypeConverter for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
object owner,
|
||||
Expression<Func<object>> expression,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor,
|
||||
TypeConverter converter)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPropertyInfo(expression);
|
||||
Init(owner, null, null, propertyInfo, editor, converter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static properties</summary>
|
||||
/// <param name="ownerType">Type holding static property</param>
|
||||
/// <param name="expression">Lambda expression that accesses the property;
|
||||
/// e.g., () => MyClass.MyProperty</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
/// <param name="converter">TypeConverter for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
Type ownerType,
|
||||
Expression<Func<object>> expression,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor,
|
||||
TypeConverter converter)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
PropertyInfo propertyInfo = GetPropertyInfo(expression);
|
||||
Init(null, ownerType, null, propertyInfo, editor, converter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for instance properties</summary>
|
||||
/// <param name="owner">Property owner</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
public BoundPropertyDescriptor(
|
||||
object owner,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(owner, null, name, null, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for instance properties</summary>
|
||||
/// <param name="owner">Property owner</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
object owner,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(owner, null, name, null, editor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for instance properties</summary>
|
||||
/// <param name="owner">Property owner</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
/// <param name="converter">TypeConverter for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
object owner,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor,
|
||||
TypeConverter converter)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(owner, null, name, null, editor, converter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static properties</summary>
|
||||
/// <param name="ownerType">Type holding static property</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
public BoundPropertyDescriptor(
|
||||
Type ownerType,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(null, ownerType, name, null, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static properties</summary>
|
||||
/// <param name="ownerType">Type holding static property</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
Type ownerType,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(null, ownerType, name, null, editor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for static properties</summary>
|
||||
/// <param name="ownerType">Type holding static property</param>
|
||||
/// <param name="name">Property name</param>
|
||||
/// <param name="displayName">Property display name</param>
|
||||
/// <param name="category">Property category</param>
|
||||
/// <param name="description">Property description</param>
|
||||
/// <param name="editor">Editor for property</param>
|
||||
/// <param name="converter">TypeConverter for property</param>
|
||||
public BoundPropertyDescriptor(
|
||||
Type ownerType,
|
||||
string name,
|
||||
string displayName,
|
||||
string category,
|
||||
string description,
|
||||
object editor,
|
||||
TypeConverter converter)
|
||||
|
||||
: this(displayName, category, description)
|
||||
{
|
||||
Init(null, ownerType, name, null, editor, converter);
|
||||
}
|
||||
|
||||
private void Init(
|
||||
object owner,
|
||||
Type ownerType,
|
||||
string name,
|
||||
PropertyInfo propertyInfo,
|
||||
object editor,
|
||||
TypeConverter converter)
|
||||
{
|
||||
m_owner = owner;
|
||||
|
||||
// if given the property owner, ignore the ownerType parameter
|
||||
if (owner != null)
|
||||
ownerType = owner.GetType();
|
||||
m_ownerType = ownerType;
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (propertyInfo == null)
|
||||
throw new ArgumentException("either 'name' or 'propertyInfo' must be non-null");
|
||||
name = propertyInfo.Name;
|
||||
}
|
||||
|
||||
// if given the property info, don't use reflection to find it
|
||||
_propertyInfo = propertyInfo;
|
||||
if (_propertyInfo == null)
|
||||
{
|
||||
_propertyInfo = m_ownerType.GetProperty(
|
||||
name,
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static);
|
||||
|
||||
if (_propertyInfo == null)
|
||||
throw new ArgumentException(name + ": Property doesn't exist");
|
||||
}
|
||||
|
||||
// look at "set_" method to determine if property is read-only.
|
||||
// (PropertyInfo.CanWrite will return true if there is a set
|
||||
// accessor, even if that accessor is made inaccessible using
|
||||
// asymmetric accessor accessibility.)
|
||||
MethodInfo setInfo = m_ownerType.GetMethod("set_" + name,
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static);
|
||||
|
||||
m_readOnly = (setInfo == null);
|
||||
|
||||
m_editor = editor;
|
||||
m_typeConverter = converter;
|
||||
}
|
||||
|
||||
private BoundPropertyDescriptor(
|
||||
string displayName,
|
||||
string category,
|
||||
string description)
|
||||
|
||||
: base(displayName,
|
||||
new Attribute[] { new CategoryAttribute(category),
|
||||
new DescriptionAttribute(description), })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When overridden in a derived class, returns whether resetting an object changes its value</summary>
|
||||
/// <param name="component">The component to test for reset capability</param>
|
||||
/// <returns>True iff resetting the component changes its value</returns>
|
||||
public override bool CanResetValue(object component)
|
||||
{
|
||||
object defaultValue;
|
||||
return GetDefaultValue(out defaultValue) && !Object.Equals(GetValue(null), defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component this property is bound to. Is null, if bound to a static class's property.</summary>
|
||||
public object Owner
|
||||
{
|
||||
get { return m_owner; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the component this property is bound to</summary>
|
||||
public override Type ComponentType
|
||||
{
|
||||
get { return m_ownerType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property</summary>
|
||||
public override Type PropertyType
|
||||
{
|
||||
get { return _propertyInfo.PropertyType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this property is read-only</summary>
|
||||
public override bool IsReadOnly
|
||||
{
|
||||
get { return m_readOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value for this property of the component to the default value</summary>
|
||||
/// <param name="component">The component with the property value that is to be reset to the default value</param>
|
||||
public override void ResetValue(object component)
|
||||
{
|
||||
object defaultValue;
|
||||
GetDefaultValue(out defaultValue);
|
||||
SetValue(component, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the value of this property needs to be persisted</summary>
|
||||
/// <param name="component">The component with the property to be examined for persistence</param>
|
||||
/// <returns>True iff the property should be persisted</returns>
|
||||
public override bool ShouldSerializeValue(object component)
|
||||
{
|
||||
object val = GetValue(component);
|
||||
|
||||
object defaultValue;
|
||||
if (!GetDefaultValue(out defaultValue) && val == null)
|
||||
return false;
|
||||
else
|
||||
return !val.Equals(defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Owner's value (if the Owner is not null) or the component's value of the property</summary>
|
||||
/// <param name="component">Component to examine</param>
|
||||
/// <returns>The value of a property</returns>
|
||||
public override object GetValue(object component)
|
||||
{
|
||||
if (m_owner != null)
|
||||
component = m_owner;
|
||||
return _propertyInfo.GetValue(component, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the Owner (if not null) or component</summary>
|
||||
/// <param name="component">Component</param>
|
||||
/// <param name="value">The new value</param>
|
||||
public override void SetValue(object component, object value)
|
||||
{
|
||||
if (m_owner != null)
|
||||
component = m_owner;
|
||||
_propertyInfo.SetValue(component, value, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value</summary>
|
||||
/// <param name="result">Is set to the default value or null, if it couldn't be determined</param>
|
||||
/// <returns>Whether or not the default value was determined</returns>
|
||||
/// <remarks>Uses reflection to look for a DefaultValueAttribute</remarks>
|
||||
public virtual bool GetDefaultValue(out object result)
|
||||
{
|
||||
bool foundDefault = false;
|
||||
result = null;
|
||||
object[] attributes = _propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
foundDefault = true;
|
||||
result = (attributes[0] as DefaultValueAttribute).Value;
|
||||
if (result != null && result.GetType() != _propertyInfo.PropertyType)
|
||||
{
|
||||
// Default value type is not the same as the property type; convert it.
|
||||
// This can happen if the property's type is not CLS-compliant (e.g. UInt32).
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(result);
|
||||
if (converter.CanConvertTo(_propertyInfo.PropertyType))
|
||||
{
|
||||
result = converter.ConvertTo(result, _propertyInfo.PropertyType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try using the converter associated with the source instead of the target
|
||||
// (Not sure if this is useful for not, but can it hurt?)
|
||||
converter = TypeDescriptor.GetConverter(_propertyInfo.PropertyType);
|
||||
if (converter.CanConvertFrom(result.GetType()))
|
||||
{
|
||||
result = converter.ConvertFrom(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an editor of the specified type</summary>
|
||||
/// <param name="editorBaseType">Base type of editor, which is used to differentiate between multiple
|
||||
/// editors that a property supports</param>
|
||||
/// <returns>An instance of the requested editor type, or null if an editor cannot be found</returns>
|
||||
public override object GetEditor(Type editorBaseType)
|
||||
{
|
||||
if (m_editor != null &&
|
||||
editorBaseType.IsInstanceOfType(m_editor))
|
||||
{
|
||||
return m_editor;
|
||||
}
|
||||
|
||||
if (editorBaseType.IsEnum)
|
||||
{
|
||||
|
||||
}
|
||||
return base.GetEditor(editorBaseType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type converter for this property</summary>
|
||||
public override TypeConverter Converter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_typeConverter != null)
|
||||
return m_typeConverter;
|
||||
|
||||
return base.Converter;
|
||||
}
|
||||
}
|
||||
|
||||
public PropertyInfo PropertyInfo => _propertyInfo;
|
||||
|
||||
// Does not return null. Will throw an exception if 'expression' is poorly formed.
|
||||
private static PropertyInfo GetPropertyInfo(Expression<Func<object>> expression)
|
||||
{
|
||||
PropertyInfo propertyInfo = null;
|
||||
MemberExpression memberExpression = expression.Body as MemberExpression;
|
||||
if (memberExpression != null)
|
||||
{
|
||||
// this is the usual case for when a property has a public getter and setter
|
||||
propertyInfo = memberExpression.Member as PropertyInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the setter is private, the expression is a UnaryExpression type for some reason.
|
||||
UnaryExpression unaryExpression = expression.Body as UnaryExpression;
|
||||
if (unaryExpression != null)
|
||||
{
|
||||
memberExpression = unaryExpression.Operand as MemberExpression;
|
||||
if (memberExpression != null)
|
||||
propertyInfo = memberExpression.Member as PropertyInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (propertyInfo == null)
|
||||
throw new ArgumentException(
|
||||
"lambda expression was not properly formed." +
|
||||
" Should be \" => myObject.MyProperty\" or" +
|
||||
" \" => MyClass.MyProperty\"");
|
||||
|
||||
return propertyInfo;
|
||||
}
|
||||
|
||||
object m_owner;
|
||||
Type m_ownerType;
|
||||
PropertyInfo _propertyInfo;
|
||||
bool m_readOnly;
|
||||
private object m_editor;
|
||||
private TypeConverter m_typeConverter;
|
||||
}
|
||||
}
|
||||
43
framework/Inspectron.Settings/Event.cs
Normal file
43
framework/Inspectron.Settings/Event.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public static class Event
|
||||
{
|
||||
public static void Raise(this EventHandler handler, object sender, EventArgs e)
|
||||
{
|
||||
if (handler != null) handler(sender, e);
|
||||
}
|
||||
|
||||
public static void Raise<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs
|
||||
{
|
||||
if (handler != null) handler(sender, e);
|
||||
}
|
||||
|
||||
public static bool RaiseCancellable(this CancelEventHandler handler, object sender, CancelEventArgs e)
|
||||
{
|
||||
if (handler != null)
|
||||
foreach (CancelEventHandler h in handler.GetInvocationList())
|
||||
{
|
||||
h(sender, e);
|
||||
if (e.Cancel) break;
|
||||
}
|
||||
|
||||
return e.Cancel;
|
||||
}
|
||||
|
||||
public static bool RaiseCancellable<T>(this EventHandler<T> handler, object sender, T e)
|
||||
where T : CancelEventArgs
|
||||
{
|
||||
if (handler != null)
|
||||
foreach (EventHandler<T> h in handler.GetInvocationList())
|
||||
{
|
||||
h(sender, e);
|
||||
if (e.Cancel) break;
|
||||
}
|
||||
|
||||
return e.Cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
framework/Inspectron.Settings/IAdaptable.cs
Normal file
13
framework/Inspectron.Settings/IAdaptable.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public interface IAdaptable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an adapter of the specified type or null</summary>
|
||||
/// <param name="type">Adapter type</param>
|
||||
/// <returns>Adapter of the specified type or null if no adapter available</returns>
|
||||
object GetAdapter(Type type);
|
||||
}
|
||||
}
|
||||
14
framework/Inspectron.Settings/IDecoratable.cs
Normal file
14
framework/Inspectron.Settings/IDecoratable.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public interface IDecoratable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all decorators of the specified type</summary>
|
||||
/// <param name="type">Decorator type</param>
|
||||
/// <returns>Enumeration of non-null decorators that are of the specified type. The enumeration may be empty.</returns>
|
||||
IEnumerable<object> GetDecorators(Type type);
|
||||
}
|
||||
}
|
||||
20
framework/Inspectron.Settings/ITreeView.cs
Normal file
20
framework/Inspectron.Settings/ITreeView.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public interface ITreeView
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the root object of the tree view</summary>
|
||||
object Root
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains enumeration of the children of the given parent object</summary>
|
||||
/// <param name="parent">Parent object</param>
|
||||
/// <returns>Enumeration of children of the parent object</returns>
|
||||
IEnumerable<object> GetChildren(object parent);
|
||||
}
|
||||
}
|
||||
8
framework/Inspectron.Settings/Inspectron.Settings.csproj
Normal file
8
framework/Inspectron.Settings/Inspectron.Settings.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Configurations>Debug;Release;CPU</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
845
framework/Inspectron.Settings/InspectronSettings.cs
Normal file
845
framework/Inspectron.Settings/InspectronSettings.cs
Normal file
@@ -0,0 +1,845 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
|
||||
public class InspectronSettings
|
||||
{
|
||||
|
||||
public InspectronSettings(string path=null)
|
||||
{
|
||||
Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
|
||||
|
||||
AssemblyName assemblyName = assembly.GetName();
|
||||
_applicationName = assemblyName.Name;
|
||||
Version version = assemblyName.Version;
|
||||
_versionString = version.Major + "." + version.Minor;
|
||||
|
||||
string startupPath = Path.GetDirectoryName(new Uri(assemblyName.CodeBase).LocalPath);
|
||||
_defaultSettingsPath = Path.Combine(startupPath, "DefaultSettings.xml");
|
||||
|
||||
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
_settingsPath = string.Format("{0}\\{1}\\{2}\\AppSettings.xml", appDataPath, _applicationName, _versionString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_settingsPath = Path.Combine(path,"AppSettings.xml");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
internal Path<object> GetSettingsPath(string pathName)
|
||||
{
|
||||
string[] pathSegments = pathName.Split('/', 16);
|
||||
object[] path = new object[pathSegments.Length + 1];
|
||||
|
||||
// first node is the settings tree root
|
||||
Tree<object> node = (Tree<object>)_userSettings;
|
||||
path[0] = _userSettings;
|
||||
|
||||
// middle nodes are folders
|
||||
for (int i = 1; i < path.Length - 1; i++)
|
||||
{
|
||||
node = GetOrCreateFolder(pathSegments[i - 1], node);
|
||||
path[i] = node;
|
||||
}
|
||||
|
||||
// leaf node is user settings object
|
||||
foreach (Tree<object> leaf in node.Children)
|
||||
{
|
||||
UserSettingsInfo info = leaf.Value as UserSettingsInfo;
|
||||
if (info != null && info.Name == pathSegments[pathSegments.Length - 1])
|
||||
{
|
||||
path[path.Length - 1] = leaf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Path<object>(path);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the current state of all properties (Memento pattern)</summary>
|
||||
public object State
|
||||
{
|
||||
get
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Serialize(stream);
|
||||
return stream;
|
||||
}
|
||||
set
|
||||
{
|
||||
MemoryStream stream = value as MemoryStream;
|
||||
if (stream == null)
|
||||
throw new ArgumentException("Not a valid memento");
|
||||
stream.Position = 0;
|
||||
Deserialize(stream);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PropertyDescriptor> UserPropertyDescriptors
|
||||
{
|
||||
get
|
||||
{
|
||||
var userSettings = UserSettings as Tree<object>;
|
||||
if (userSettings == null)
|
||||
throw new InvalidOperationException("userSettings");
|
||||
var all = userSettings.LevelOrder.Where(x => x.Value is UserSettingsInfo);
|
||||
foreach (Tree<object> node in all)
|
||||
{
|
||||
UserSettingsInfo info = node.Value as UserSettingsInfo;
|
||||
if (info != null)
|
||||
{
|
||||
foreach (PropertyDescriptor property in info.Settings)
|
||||
yield return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private static readonly object _unusedComponent = new object();
|
||||
public void SetDefaults()
|
||||
{
|
||||
foreach (SettingsInfo info in _settings.Values)
|
||||
{
|
||||
foreach (SettingsInfo.Setting setting in info.Settings.Values)
|
||||
{
|
||||
if (setting.PropertyDescriptor != null &&
|
||||
setting.PropertyDescriptor.CanResetValue(_unusedComponent))
|
||||
{
|
||||
setting.PropertyDescriptor.ResetValue(_unusedComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load default settings if exist.
|
||||
if (File.Exists(_defaultSettingsPath))
|
||||
{
|
||||
using (Stream stream = File.OpenRead(_defaultSettingsPath))
|
||||
Deserialize(stream);
|
||||
}
|
||||
}
|
||||
internal List<PropertyDescriptor> GetProperties(Tree<object> tree)
|
||||
{
|
||||
var info = tree.Value as UserSettingsInfo;
|
||||
if (info != null)
|
||||
return info.Settings;
|
||||
|
||||
return null;
|
||||
}
|
||||
protected class SettingsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor with name</summary>
|
||||
/// <param name="name">Name associated with this group of settings</param>
|
||||
public SettingsInfo(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a setting to group of settings as a name and value, replacing the previous value if present</summary>
|
||||
/// <param name="name">Setting name</param>
|
||||
/// <param name="valueString">Setting value</param>
|
||||
public void Add(string name, string valueString)
|
||||
{
|
||||
Setting setting;
|
||||
if (Settings.TryGetValue(name, out setting))
|
||||
{
|
||||
setting.Set(name, valueString);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.Add(name, new Setting(name, valueString));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a setting to group of settings as a PropertyDescriptor, replacing the previous value if present</summary>
|
||||
/// <param name="descriptor">PropertyDescriptor describing setting</param>
|
||||
public void Add(PropertyDescriptor descriptor)
|
||||
{
|
||||
Setting setting;
|
||||
if (Settings.TryGetValue(descriptor.Name, out setting))
|
||||
{
|
||||
setting.Set(descriptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.Add(descriptor.Name, new Setting(descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name associated with this group of settings</summary>
|
||||
public readonly string Name;
|
||||
/// <summary>
|
||||
/// Dictionary for names and values of settings in group</summary>
|
||||
public readonly SortedDictionary<string, Setting> Settings = new SortedDictionary<string, Setting>();
|
||||
|
||||
/// <summary>
|
||||
/// Class for handling individual setting information</summary>
|
||||
public class Setting
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor with PropertyDescriptor</summary>
|
||||
/// <param name="descriptor">PropertyDescriptor describing setting</param>
|
||||
public Setting(PropertyDescriptor descriptor)
|
||||
{
|
||||
PropertyDescriptor = descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with name and value of setting</summary>
|
||||
/// <param name="name">Setting name</param>
|
||||
/// <param name="valueString">Setting value</param>
|
||||
public Setting(string name, string valueString)
|
||||
{
|
||||
Name = name;
|
||||
ValueString = valueString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a setting's value with name and value of setting</summary>
|
||||
/// <param name="name">Setting name</param>
|
||||
/// <param name="valueString">Setting value</param>
|
||||
public void Set(string name, string valueString)
|
||||
{
|
||||
Name = name;
|
||||
ValueString = valueString;
|
||||
if (PropertyDescriptor != null)
|
||||
{
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a setting's value with PropertyDescriptor</summary>
|
||||
/// <param name="descriptor">PropertyDescriptor describing setting</param>
|
||||
public void Set(PropertyDescriptor descriptor)
|
||||
{
|
||||
PropertyDescriptor = descriptor;
|
||||
if (Name != null && ValueString != null)
|
||||
{
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValue()
|
||||
{
|
||||
if (!CanMakeChanges)
|
||||
return;
|
||||
|
||||
object value = GetValue(PropertyDescriptor.PropertyType, ValueString);
|
||||
PropertyDescriptor.SetValue(null, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting name</summary>
|
||||
public string Name;
|
||||
/// <summary>
|
||||
/// Setting value</summary>
|
||||
public string ValueString;
|
||||
/// <summary>
|
||||
/// Setting PropertyDescriptor</summary>
|
||||
public PropertyDescriptor PropertyDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set whether property descriptors are allowed to make changes</summary>
|
||||
/// <remarks>Used when persisted settings are being loaded from disk
|
||||
/// to prevent property descriptors from being set multiple times as
|
||||
/// in the case where DefaultSettings.xml and AppSettings.xml both exist</remarks>
|
||||
public static bool CanMakeChanges { private get; set; }
|
||||
}
|
||||
private static object GetValue(Type type, string valueString)
|
||||
{
|
||||
object value = null;
|
||||
try
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(type);
|
||||
if (CanConvertToAndFromString(converter))
|
||||
{
|
||||
value = converter.ConvertFromInvariantString(valueString);
|
||||
}
|
||||
else
|
||||
{
|
||||
// deserialize
|
||||
byte[] data = Convert.FromBase64String(valueString);
|
||||
using (MemoryStream stream = new MemoryStream(data))
|
||||
{
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
value = formatter.Deserialize(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
private static bool CanConvertToAndFromString(TypeConverter converter)
|
||||
{
|
||||
return converter.CanConvertFrom(typeof(string)) &&
|
||||
converter.CanConvertTo(typeof(string));
|
||||
}
|
||||
/// <summary>
|
||||
/// Class for group of user settings</summary>
|
||||
public class UserSettingsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor with name and PropertyDescriptors</summary>
|
||||
/// <param name="name">Name of group of user settings</param>
|
||||
/// <param name="settings">PropertyDescriptors with settings</param>
|
||||
public UserSettingsInfo(string name, PropertyDescriptor[] settings)
|
||||
{
|
||||
Name = name;
|
||||
Settings = new List<PropertyDescriptor>(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of group of user settings</summary>
|
||||
public readonly string Name;
|
||||
/// <summary>
|
||||
/// PropertyDescriptors with settings</summary>
|
||||
public readonly List<PropertyDescriptor> Settings;
|
||||
}
|
||||
private readonly SortedDictionary<string, SettingsInfo> _settings = new SortedDictionary<string, SettingsInfo>();
|
||||
private ITreeView _userSettings = new TreeView(string.Empty);
|
||||
|
||||
public void RegisterSettings(string uid, params PropertyDescriptor[] settings)
|
||||
{
|
||||
SettingsInfo settingsInfo;
|
||||
if (!_settings.TryGetValue(uid, out settingsInfo))
|
||||
{
|
||||
settingsInfo = new SettingsInfo(uid);
|
||||
_settings.Add(uid, settingsInfo);
|
||||
}
|
||||
|
||||
foreach (PropertyDescriptor descriptor in settings)
|
||||
settingsInfo.Add(descriptor);
|
||||
}
|
||||
public ITreeView UserSettings
|
||||
{
|
||||
get { return _userSettings; }
|
||||
}
|
||||
|
||||
public void RegisterSimple(object owner,Expression<Func<object>> expression,string pathName,string name,string category="General",string description="")
|
||||
{
|
||||
var descriptor = new BoundPropertyDescriptor(owner,expression, name, category,
|
||||
description);
|
||||
|
||||
RegisterSettings(pathName,descriptor);
|
||||
|
||||
RegisterUserSettings(pathName,descriptor);
|
||||
}
|
||||
|
||||
public void RegisterUserSettings(string pathName, params PropertyDescriptor[] settings)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathName))
|
||||
throw new ArgumentException("pathName");
|
||||
|
||||
string[] path = pathName.Split('/', 16);
|
||||
|
||||
// get root folder
|
||||
Tree<object> folder = UserSettings as Tree<object>;
|
||||
if (folder == null)
|
||||
throw new InvalidOperationException("userSettings");
|
||||
|
||||
// for each subsequent segment of the path, get folder
|
||||
for (int i = 0; i < path.Length - 1; i++)
|
||||
folder = GetOrCreateFolder(path[i], folder);
|
||||
|
||||
// get the node that should hold the settings, if it already exists
|
||||
string name = path[path.Length - 1];
|
||||
UserSettingsInfo existing = null;
|
||||
int index = 0;
|
||||
foreach (Tree<object> node in folder.Children)
|
||||
{
|
||||
UserSettingsInfo info = node.Value as UserSettingsInfo;
|
||||
if (info != null)
|
||||
{
|
||||
if (info.Name == name)
|
||||
{
|
||||
existing = info;
|
||||
break;
|
||||
}
|
||||
if (info.Name.CompareTo(name) < 0)
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// add the settings, either by merging with the existing node or creating a new one
|
||||
if (existing != null)
|
||||
{
|
||||
foreach (PropertyDescriptor pd in settings)
|
||||
existing.Settings.Add(pd);
|
||||
}
|
||||
else
|
||||
{
|
||||
Tree<object> node = new Tree<object>(new UserSettingsInfo(name, settings));
|
||||
folder.Children.Insert(index, node);
|
||||
}
|
||||
}
|
||||
private static string GetMutexName(string pathName)
|
||||
{
|
||||
string safeName = pathName;
|
||||
|
||||
//255 characters will break IpcChannel constructor. 250 works.
|
||||
if (safeName.Length > 250)
|
||||
safeName = safeName.Substring(safeName.Length - 250);
|
||||
|
||||
// The Mutex constructor will crash if given '\', unless it's part of a valid path.
|
||||
// NextInstanceMonitor.ActivateApplication() will crash if there are '/' characters.
|
||||
safeName = safeName.Replace('/', '-');
|
||||
safeName = safeName.Replace('\\', '-');
|
||||
|
||||
return safeName;
|
||||
}
|
||||
protected void Serialize(Stream stream)
|
||||
{
|
||||
Saving.Raise(this, EventArgs.Empty);
|
||||
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes"));
|
||||
XmlElement root = xmlDoc.CreateElement("settings");
|
||||
xmlDoc.AppendChild(root);
|
||||
|
||||
// add application name and version to the root element.
|
||||
root.SetAttribute("appName", _applicationName);
|
||||
root.SetAttribute("appVersion", _versionString);
|
||||
|
||||
foreach (SettingsInfo info in _settings.Values)
|
||||
{
|
||||
XmlElement block = xmlDoc.CreateElement("block");
|
||||
block.SetAttribute("id", info.Name);
|
||||
|
||||
foreach (SettingsInfo.Setting setting in info.Settings.Values)
|
||||
{
|
||||
PropertyDescriptor descriptor = setting.PropertyDescriptor;
|
||||
if (descriptor != null)
|
||||
{
|
||||
object value = descriptor.GetValue(null);
|
||||
if (CanWriteValue(value))
|
||||
WriteValue(descriptor.Name, value, block);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteValue(setting.Name, setting.ValueString, block);
|
||||
}
|
||||
}
|
||||
// skip empty block
|
||||
if (block.ChildNodes.Count > 0)
|
||||
root.AppendChild(block);
|
||||
}
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.CloseOutput = false;
|
||||
settings.Indent = true;
|
||||
|
||||
using (XmlWriter writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
xmlDoc.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
private bool CanWriteValue(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(value.GetType());
|
||||
return CanConvertToAndFromString(converter) || value.GetType().IsSerializable;
|
||||
}
|
||||
private void WriteValue(string name, object value, XmlElement block)
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
// skip persisting if any exception occurs
|
||||
string valueString = null;
|
||||
Type type = value.GetType();
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(type);
|
||||
if (CanConvertToAndFromString(converter))
|
||||
{
|
||||
valueString = converter.ConvertToInvariantString(value);
|
||||
}
|
||||
else if (type.IsSerializable)
|
||||
{
|
||||
// serialize
|
||||
BinaryFormatter formatter = new BinaryFormatter();
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
formatter.Serialize(stream, value);
|
||||
valueString = Convert.ToBase64String(stream.GetBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(valueString))
|
||||
return;
|
||||
|
||||
XmlDocument xmlDoc = block.OwnerDocument;
|
||||
XmlElement elmValue = xmlDoc.CreateElement("value");
|
||||
elmValue.SetAttribute("name", name);
|
||||
elmValue.SetAttribute("type", type.Name);
|
||||
|
||||
// if the valueString is xmlDoc then
|
||||
XmlDocument temp = StringToXmlDoc(valueString);
|
||||
if (temp != null)
|
||||
{
|
||||
// remove xml declaration if exists
|
||||
XmlDeclaration decl = temp.FirstChild as XmlDeclaration;
|
||||
if (decl != null)
|
||||
temp.RemoveChild(decl);
|
||||
elmValue.InnerXml = temp.DocumentElement.OuterXml;
|
||||
}
|
||||
else
|
||||
{
|
||||
elmValue.InnerText = valueString;
|
||||
}
|
||||
|
||||
block.AppendChild(elmValue);
|
||||
}
|
||||
private XmlDocument StringToXmlDoc(string strXml)
|
||||
{
|
||||
XmlDocument xmlDoc = null;
|
||||
try
|
||||
{
|
||||
int len = (strXml.Length > 20) ? 20 : strXml.Length;
|
||||
string test = RemoveAllWhiteSpace(strXml.Substring(0, len)).ToLower();
|
||||
if (test.Contains("<?xmlversion="))
|
||||
{
|
||||
xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(strXml);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
xmlDoc = null;
|
||||
}
|
||||
return xmlDoc;
|
||||
|
||||
}
|
||||
public static string RemoveAllWhiteSpace(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return s;
|
||||
StringBuilder result = new StringBuilder(s.Length);
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
char c = s[i];
|
||||
if (!char.IsWhiteSpace(c))
|
||||
result.Append(c);
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
public void SaveSettings()
|
||||
{
|
||||
string tempNew = string.Empty;
|
||||
|
||||
string mutexName = GetMutexName(_settingsPath);
|
||||
using (Mutex saveMutex = new Mutex(false, mutexName))
|
||||
{
|
||||
try
|
||||
{
|
||||
saveMutex.WaitOne();
|
||||
|
||||
// Create zero-size file.
|
||||
tempNew = Path.GetTempFileName();
|
||||
|
||||
using (Stream stream = File.Create(tempNew))
|
||||
Serialize(stream);
|
||||
|
||||
// Make sure the settings directory exists. Do nothing if it already exists.
|
||||
string settingsDir = Path.GetDirectoryName(_settingsPath);
|
||||
Directory.CreateDirectory(settingsDir);
|
||||
|
||||
// Erase old backup (if any) and move current settings file (if any).
|
||||
string tempBackup = Path.Combine(settingsDir, "~Settings.xml");
|
||||
if (File.Exists(tempBackup)) // seems unnecessary, but Dan put this check in the WPF version --Ron
|
||||
File.Delete(tempBackup);
|
||||
if (File.Exists(_settingsPath))
|
||||
File.Move(_settingsPath, tempBackup);
|
||||
|
||||
// Move temporary file to be the new settings file, then delete backup.
|
||||
File.Move(tempNew, _settingsPath);
|
||||
File.Delete(tempBackup);
|
||||
}
|
||||
catch (TargetInvocationException)
|
||||
{
|
||||
// Catch and ignore TargetInvocationException happening if Windows
|
||||
// is shut down with the application still running.
|
||||
// TO DO: Find a way to successfully save settings and exit on shutdown.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Attempt clean-up. No exception is thrown if file doesn't exist.
|
||||
File.Delete(tempNew);
|
||||
saveMutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
}
|
||||
private Tree<object> GetOrCreateFolder(string name, Tree<object> tree)
|
||||
{
|
||||
// search for folder
|
||||
Tree<object> result = null;
|
||||
int index = 0;
|
||||
foreach (Tree<object> child in tree.Children)
|
||||
{
|
||||
string folderName = child.Value as string;
|
||||
if (folderName != null)
|
||||
{
|
||||
if (folderName == name)
|
||||
{
|
||||
result = child;
|
||||
break;
|
||||
}
|
||||
|
||||
if (folderName.CompareTo(name) < 0)
|
||||
index++;
|
||||
}
|
||||
else // child is UserSettingsInfo
|
||||
{
|
||||
index++; // folders should follow settings nodes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if not found, create it
|
||||
if (result == null)
|
||||
{
|
||||
result = new Tree<object>(name);
|
||||
tree.Children.Insert(index, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler Saving;
|
||||
public event EventHandler Loading;
|
||||
public event EventHandler Reloaded;
|
||||
|
||||
|
||||
private class TreeView : Tree<object>, ITreeView
|
||||
{
|
||||
public TreeView(object root)
|
||||
: base(root)
|
||||
{
|
||||
}
|
||||
|
||||
#region ITreeView Members
|
||||
|
||||
public object Root
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public IEnumerable<object> GetChildren(object parent)
|
||||
{
|
||||
foreach (object child in ((Tree<object>)parent).Children)
|
||||
yield return child;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IItemView Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets item's display information</summary>
|
||||
/// <param name="item">Item being displayed</param>
|
||||
/// <param name="info">Item info, to fill out</param>
|
||||
public void GetInfo(object item, ItemInfo info)
|
||||
{
|
||||
object value = ((Tree<object>)item).Value;
|
||||
if (value is string)
|
||||
{
|
||||
info.Label = (string)value;
|
||||
//info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.FolderImage);
|
||||
info.AllowSelect = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettingsInfo settingsInfo = value as UserSettingsInfo;
|
||||
info.Label = settingsInfo.Name;
|
||||
info.AllowLabelEdit = false;
|
||||
//info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.PreferencesImage);
|
||||
info.IsLeaf = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string _settingsPath;
|
||||
private string _defaultSettingsPath;
|
||||
private string _applicationName;
|
||||
private string _versionString;
|
||||
private string _propertyViewState;
|
||||
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
Loading.Raise(this, EventArgs.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
bool defaultSettingsExists = File.Exists(_defaultSettingsPath);
|
||||
bool appSettingsExists = File.Exists(_settingsPath);
|
||||
|
||||
// only update property descriptors during the DefaultSettings.xml pass
|
||||
// if DefaultSettings.xml exists and AppSettings.xml does not exist
|
||||
SettingsInfo.CanMakeChanges = defaultSettingsExists && !appSettingsExists;
|
||||
|
||||
// first, load default settings, if they exist:
|
||||
if (defaultSettingsExists)
|
||||
{
|
||||
using (Stream stream = File.OpenRead(_defaultSettingsPath))
|
||||
Deserialize(stream);
|
||||
}
|
||||
|
||||
// restore for AppSettings.xml pass
|
||||
SettingsInfo.CanMakeChanges = true;
|
||||
|
||||
// now load user settings, overriding defaults:
|
||||
if (appSettingsExists)
|
||||
{
|
||||
using (Stream stream = File.OpenRead(_settingsPath))
|
||||
Deserialize(stream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
// restore value
|
||||
SettingsInfo.CanMakeChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ApplyStoredSettings()
|
||||
{
|
||||
foreach (SettingsInfo info in _settings.Values)
|
||||
{
|
||||
foreach (SettingsInfo.Setting setting in info.Settings.Values)
|
||||
{
|
||||
if (setting.PropertyDescriptor is BoundPropertyDescriptor pd)
|
||||
{
|
||||
if (setting.ValueString == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
object v;
|
||||
if (setting.PropertyDescriptor.PropertyType.IsEnum)
|
||||
{
|
||||
v = Enum.Parse(setting.PropertyDescriptor.PropertyType, setting.ValueString);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = Convert.ChangeType(setting.ValueString, setting.PropertyDescriptor.PropertyType);
|
||||
}
|
||||
pd.SetValue(pd.Owner, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected bool Deserialize(Stream stream)
|
||||
{
|
||||
// create XML DOM from stream
|
||||
// if failed, display message box and return
|
||||
try
|
||||
{
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.Load(stream);
|
||||
|
||||
XmlElement root = xmlDoc.DocumentElement;
|
||||
|
||||
// get all the blocks
|
||||
XmlNodeList blocks = root.SelectNodes("block");
|
||||
if (blocks == null || blocks.Count == 0)
|
||||
throw new Exception("The setting file is empty");
|
||||
|
||||
foreach (XmlElement block in blocks)
|
||||
{
|
||||
try
|
||||
{
|
||||
string id = block.GetAttribute("id");
|
||||
|
||||
SettingsInfo info;
|
||||
if (!_settings.TryGetValue(id, out info))
|
||||
{
|
||||
info = new SettingsInfo(id);
|
||||
_settings.Add(id, info);
|
||||
}
|
||||
|
||||
// get a list of value element in each block
|
||||
XmlNodeList valueNodes = block.SelectNodes("value");
|
||||
|
||||
// skip over empty block
|
||||
if (valueNodes == null || valueNodes.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (XmlElement xmlElement in valueNodes)
|
||||
{
|
||||
string name = xmlElement.GetAttribute("name");
|
||||
string valueString = GetElementValueString(xmlElement);
|
||||
|
||||
info.Add(name, valueString);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
ApplyStoredSettings();
|
||||
Reloaded.Raise(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private static string GetElementValueString(XmlElement element)
|
||||
{
|
||||
string valueString = element.InnerText;
|
||||
if (string.IsNullOrEmpty(valueString))
|
||||
valueString = element.InnerXml;
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
148
framework/Inspectron.Settings/ItemInfo.cs
Normal file
148
framework/Inspectron.Settings/ItemInfo.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public abstract class ItemInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for items with no associated images to be drawn</summary>
|
||||
public ItemInfo()
|
||||
{
|
||||
CheckBoxEnabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets item's label</summary>
|
||||
/// <remarks>Default is empty string if item has no label</remarks>
|
||||
public string Label
|
||||
{
|
||||
get { return m_label; }
|
||||
set { m_label = value; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets item's description</summary>
|
||||
/// <remarks>Default is empty string if item has no description</remarks>
|
||||
public string Description
|
||||
{
|
||||
get { return m_description; }
|
||||
set { m_description = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether item should have a check box control</summary>
|
||||
/// <remarks>Default is false</remarks>
|
||||
public bool HasCheck
|
||||
{
|
||||
get { return m_hasCheck; }
|
||||
set { m_hasCheck = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether check box is enabled</summary>
|
||||
/// <remarks>Default is true.
|
||||
/// This property makes sense only if HasCheck is true</remarks>
|
||||
public bool CheckBoxEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether item is checked</summary>
|
||||
/// <remarks>Default is false</remarks>
|
||||
public abstract bool Checked
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether item is a leaf (has no sub-items)</summary>
|
||||
/// <remarks>Used by tree controls to inhibit drawing the node expander; default
|
||||
/// is false</remarks>
|
||||
public bool IsLeaf
|
||||
{
|
||||
get { return m_isLeaf; }
|
||||
set { m_isLeaf = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the label is editable</summary>
|
||||
/// <remarks>Used by tree controls to inhibit editing the node label; default
|
||||
/// is true</remarks>
|
||||
public bool AllowLabelEdit
|
||||
{
|
||||
get { return m_allowLabelEdit; }
|
||||
set { m_allowLabelEdit = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the item is selectable</summary>
|
||||
/// <remarks>Used by tree controls to inhibit selecting the node; default
|
||||
/// is true</remarks>
|
||||
public bool AllowSelect
|
||||
{
|
||||
get { return m_allowSelect; }
|
||||
set { m_allowSelect = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this item is expanded in the view. Set by
|
||||
/// Control adapters - client code shouldn't set this value.</summary>
|
||||
public bool IsExpandedInView
|
||||
{
|
||||
get { return m_isExpandedInView; }
|
||||
set { m_isExpandedInView = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets index of item's image in image list</summary>
|
||||
/// <remarks>Default is -1 if item has no image.
|
||||
/// DAN: This is not required by WPF so could be moved to WinFormsItemInfo</remarks>
|
||||
public int ImageIndex
|
||||
{
|
||||
get { return m_imageIndex; }
|
||||
set { m_imageIndex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets index of item's "State" image in image list</summary>
|
||||
/// <remarks>Default is -1 if item has no "State" image.
|
||||
/// DAN: This is not required by WPF so could be moved to WinFormsItemInfo</remarks>
|
||||
public int StateImageIndex
|
||||
{
|
||||
get { return m_stateImageIndex; }
|
||||
set { m_stateImageIndex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets item's properties for lists and tree lists</summary>
|
||||
/// <remarks>Default is an empty array if item has no properties</remarks>
|
||||
public object[] Properties
|
||||
{
|
||||
get { return m_properties; }
|
||||
set { m_properties = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets item's mouse hover over text</summary>
|
||||
public string HoverText
|
||||
{
|
||||
get { return m_hoverText; }
|
||||
set { m_hoverText = value; }
|
||||
}
|
||||
|
||||
private string m_label = string.Empty;
|
||||
private string m_description = string.Empty;
|
||||
private int m_imageIndex = -1;
|
||||
private int m_stateImageIndex = -1;
|
||||
private object[] m_properties = new object[0];
|
||||
private bool m_hasCheck;
|
||||
private bool m_isLeaf;
|
||||
private bool m_allowLabelEdit = true;
|
||||
private bool m_allowSelect = true;
|
||||
private bool m_isExpandedInView;
|
||||
private string m_hoverText = string.Empty;
|
||||
}
|
||||
}
|
||||
405
framework/Inspectron.Settings/Path.cs
Normal file
405
framework/Inspectron.Settings/Path.cs
Normal file
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
public class Path<T> : IList<T>, IEquatable<Path<T>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor using single object</summary>
|
||||
/// <param name="last">Single object making up the path</param>
|
||||
public Path(T last)
|
||||
{
|
||||
m_path = new T[1];
|
||||
m_path[0] = last;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor using sequence of objects</summary>
|
||||
/// <param name="path">Path, as sequence of objects</param>
|
||||
public Path(IEnumerable<T> path)
|
||||
{
|
||||
m_path = path.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor using collection of objects</summary>
|
||||
/// <param name="path">Path, as collection of objects</param>
|
||||
public Path(ICollection<T> path)
|
||||
{
|
||||
m_path = new T[path.Count];
|
||||
path.CopyTo(m_path, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the first object in the path</summary>
|
||||
public T First
|
||||
{
|
||||
get { return m_path[0]; }
|
||||
set { m_path[0] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last object in the path</summary>
|
||||
public T Last
|
||||
{
|
||||
get { return m_path[m_path.Length - 1]; }
|
||||
set { m_path[m_path.Length - 1] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a prefix with the specified length</summary>
|
||||
/// <param name="length">Prefix length</param>
|
||||
/// <returns>Prefix with the specified length</returns>
|
||||
public Path<T> Prefix(int length)
|
||||
{
|
||||
CheckSubPathLength(length);
|
||||
|
||||
T[] path = new T[length];
|
||||
Array.Copy(m_path, 0, path, 0, length);
|
||||
|
||||
return new Path<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a suffix with the specified length</summary>
|
||||
/// <param name="length">Suffix length</param>
|
||||
/// <returns>Suffix with the specified length</returns>
|
||||
public Path<T> Suffix(int length)
|
||||
{
|
||||
CheckSubPathLength(length);
|
||||
|
||||
T[] path = new T[length];
|
||||
int offset = m_path.Length - length;
|
||||
Array.Copy(m_path, offset, path, 0, length);
|
||||
|
||||
return new Path<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts path to a path of another type</summary>
|
||||
/// <typeparam name="U">Path type to convert to</typeparam>
|
||||
/// <returns>Path of new type</returns>
|
||||
public Path<U> Convert<U>()
|
||||
where U : class
|
||||
{
|
||||
U[] converted = new U[m_path.Length];
|
||||
for (int i = 0; i < m_path.Length; i++)
|
||||
converted[i] = Convert<U>(m_path[i]);
|
||||
return new Path<U>(converted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the path type to another type</summary>
|
||||
/// <typeparam name="U">Desired type</typeparam>
|
||||
/// <param name="item">Item to convert</param>
|
||||
/// <returns>Item, converted to given type, or null</returns>
|
||||
protected virtual U Convert<U>(T item)
|
||||
where U : class
|
||||
{
|
||||
U u = item as U;
|
||||
return u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests path for equality</summary>
|
||||
/// <param name="other">Other path</param>
|
||||
/// <returns>True iff this path is equivalent to other</returns>
|
||||
public bool Equals(Path<T> other)
|
||||
{
|
||||
if (object.Equals(other, null))
|
||||
return false;
|
||||
|
||||
if (m_path.Length != other.m_path.Length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < m_path.Length; i++)
|
||||
if (!m_path[i].Equals(other.m_path[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests object for equality</summary>
|
||||
/// <param name="obj">Other object</param>
|
||||
/// <returns>True iff this path is equivalent to other object</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
Path<T> path = obj as Path<T>;
|
||||
if (path != null)
|
||||
return Equals(path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains hash code</summary>
|
||||
/// <returns>Hash code</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (T obj in m_path)
|
||||
hash ^= obj.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests paths for equality</summary>
|
||||
/// <param name="o1">First path</param>
|
||||
/// <param name="o2">Second path</param>
|
||||
/// <returns>True iff paths are equivalent</returns>
|
||||
public static bool operator ==(Path<T> o1, Path<T> o2)
|
||||
{
|
||||
if (object.Equals(o1, null))
|
||||
return object.Equals(o2, null);
|
||||
else
|
||||
return o1.Equals(o2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests paths for inequality</summary>
|
||||
/// <param name="o1">First path</param>
|
||||
/// <param name="o2">Second path</param>
|
||||
/// <returns>True iff paths are not equivalent</returns>
|
||||
public static bool operator !=(Path<T> o1, Path<T> o2)
|
||||
{
|
||||
if (object.Equals(o1, null))
|
||||
return !object.Equals(o2, null);
|
||||
else
|
||||
return !o1.Equals(o2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates object with path</summary>
|
||||
/// <param name="lhs">Prefix object</param>
|
||||
/// <param name="rhs">Optional path, may be null</param>
|
||||
/// <returns>Concatenated path, with lhs as first object</returns>
|
||||
public static Path<T> operator +(T lhs, Path<T> rhs)
|
||||
{
|
||||
if (rhs == null)
|
||||
return new Path<T>(lhs);
|
||||
T[] path = new T[1 + rhs.Count];
|
||||
path[0] = lhs;
|
||||
Array.Copy(rhs.m_path, 0, path, 1, rhs.Count);
|
||||
return new Path<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates path with object</summary>
|
||||
/// <param name="lhs">Optional path, may be null</param>
|
||||
/// <param name="rhs">Suffix object</param>
|
||||
/// <returns>Concatenated path, with rhs as last object</returns>
|
||||
public static Path<T> operator +(Path<T> lhs, T rhs)
|
||||
{
|
||||
if (lhs == null)
|
||||
return new Path<T>(rhs);
|
||||
T[] path = new T[lhs.Count + 1];
|
||||
Array.Copy(lhs.m_path, 0, path, 0, lhs.Count);
|
||||
path[path.Length - 1] = rhs;
|
||||
return new Path<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concatenates two paths</summary>
|
||||
/// <param name="lhs">First path. Can be null.</param>
|
||||
/// <param name="rhs">Second path. Can be null.</param>
|
||||
/// <returns>Concatenated path, with rhs as prefix and lhs as suffix. Is null if both lhs and rhs are null.</returns>
|
||||
public static Path<T> operator +(Path<T> lhs, Path<T> rhs)
|
||||
{
|
||||
if (lhs == null)
|
||||
return rhs;
|
||||
if (rhs == null)
|
||||
return lhs;
|
||||
T[] path = new T[lhs.Count + rhs.Count];
|
||||
Array.Copy(lhs.m_path, 0, path, 0, lhs.Count);
|
||||
Array.Copy(rhs.m_path, 0, path, lhs.Count, rhs.Count);
|
||||
return new Path<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enumeration of each path's last item; i.e., the property 'Last'</summary>
|
||||
/// <param name="paths">Enumeration of Path objects, whose Last property is returned</param>
|
||||
/// <returns>Last property of each Path in 'paths', in the same order as 'paths'</returns>
|
||||
public static IEnumerable<T> GetLastItems(IEnumerable<Path<T>> paths)
|
||||
{
|
||||
foreach (Path<T> path in paths)
|
||||
yield return path.Last;
|
||||
}
|
||||
|
||||
#region IList<T> Members
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"></see></summary>
|
||||
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"></see></param>
|
||||
/// <returns>
|
||||
/// The index of item if found in the list; otherwise -1
|
||||
/// </returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
for (int i = 0; i < m_path.Length; i++)
|
||||
if (m_path[i].Equals(item))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"></see> at the specified index</summary>
|
||||
/// <param name="index">The zero-based index at which item should be inserted</param>
|
||||
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"></see></param>
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only</exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see></exception>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="T:System.Collections.Generic.IList`1"></see> item at the specified index</summary>
|
||||
/// <param name="index">The zero-based index of the item to remove</param>
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only</exception>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see></exception>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item at the specified index</summary>
|
||||
/// <value>Index at which to set value</value>
|
||||
public T this[int index]
|
||||
{
|
||||
get { return m_path[index]; }
|
||||
set { m_path[index] = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<T> Members
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
|
||||
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see>
|
||||
/// is read-only</exception>
|
||||
public void Add(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see>
|
||||
/// is read-only</exception>
|
||||
public void Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value</summary>
|
||||
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
|
||||
/// <returns>
|
||||
/// True iff item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>
|
||||
/// </returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
foreach (T obj in m_path)
|
||||
if (obj.Equals(item))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>,
|
||||
/// starting at a particular <see cref="T:System.Array"></see> index</summary>
|
||||
/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements
|
||||
/// copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>.
|
||||
/// The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
|
||||
/// <param name="arrayIndex">The zero-based index in array at which copying begins</param>
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException">ArrayIndex is less than 0</exception>
|
||||
/// <exception cref="T:System.ArgumentNullException">Array is null</exception>
|
||||
/// <exception cref="T:System.ArgumentException">Array is multidimensional.-or-
|
||||
/// arrayIndex is equal to or greater than the length of array.-or-
|
||||
/// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than
|
||||
/// the available space from arrayIndex to the end of the destination array.-or-
|
||||
/// Type T cannot be cast automatically to the type of the destination array.</exception>
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
m_path.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
|
||||
public int Count
|
||||
{
|
||||
get { return m_path.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only</summary>
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
|
||||
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
|
||||
/// <returns>
|
||||
/// True iff item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
|
||||
/// This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
|
||||
/// </returns>
|
||||
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only</exception>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable<T> Members
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection</summary>
|
||||
/// <returns>
|
||||
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection
|
||||
/// </returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)m_path).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_path.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor</summary>
|
||||
private Path(T[] path)
|
||||
{
|
||||
m_path = path;
|
||||
}
|
||||
|
||||
private void CheckSubPathLength(int length)
|
||||
{
|
||||
if (length < 1)
|
||||
throw new InvalidOperationException("Length must be > 0");
|
||||
if (length > m_path.Length)
|
||||
throw new InvalidOperationException("Length greater than path length");
|
||||
}
|
||||
|
||||
private readonly T[] m_path;
|
||||
}
|
||||
}
|
||||
336
framework/Inspectron.Settings/Tree.cs
Normal file
336
framework/Inspectron.Settings/Tree.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
|
||||
namespace Inspectron.Settings
|
||||
{
|
||||
[Serializable]
|
||||
public class Tree<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor of empty tree</summary>
|
||||
public Tree()
|
||||
: this(default(T))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for tree with one node</summary>
|
||||
/// <param name="value">Value associated with this tree</param>
|
||||
public Tree(T value)
|
||||
{
|
||||
m_value = value;
|
||||
m_children = new ChildCollection(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tree's parent. Is null if this is a root node.</summary>
|
||||
public Tree<T> Parent
|
||||
{
|
||||
get { return m_parent; }
|
||||
set
|
||||
{
|
||||
if (m_parent != value)
|
||||
{
|
||||
if (m_parent != null)
|
||||
m_parent.Children.Remove(this);
|
||||
|
||||
m_parent = value;
|
||||
|
||||
if (m_parent != null)
|
||||
m_parent.Children.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of children nodes. Is the same as 'this' because this Tree implements IList.</summary>
|
||||
public IList<Tree<T>> Children
|
||||
{
|
||||
get { return m_children; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value associated with the tree</summary>
|
||||
public T Value
|
||||
{
|
||||
get { return m_value; }
|
||||
set { m_value = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for equality</summary>
|
||||
/// <param name="obj">Other object</param>
|
||||
/// <returns>True iff object is a tree with the same structure and values as this tree</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
Tree<T> other = obj as Tree<T>;
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (!m_value.Equals(other.m_value))
|
||||
return false;
|
||||
|
||||
if (m_children.Count != other.m_children.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < m_children.Count; i++)
|
||||
if (!(m_children[i]).Equals(other.m_children[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests for similarity</summary>
|
||||
/// <param name="other">Other tree</param>
|
||||
/// <returns>True iff other has the same structure as this tree</returns>
|
||||
/// <remarks>Same structure means same node structure</remarks>
|
||||
public bool Similar(Tree<T> other)
|
||||
{
|
||||
if (this == other)
|
||||
return true;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (m_children.Count != other.m_children.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < m_children.Count; i++)
|
||||
if (!(m_children[i]).Similar(other.m_children[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns hash code for tree</summary>
|
||||
/// <returns>Hash code</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int result = 0;
|
||||
foreach (Tree<T> tree in PreOrder)
|
||||
result ^= tree.Value.GetHashCode();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts tree to string of the form "(Value(Child1),...,(ChildN))"</summary>
|
||||
/// <returns>String representation of tree</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append('(');
|
||||
Stringify(builder);
|
||||
builder.Append(')');
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void Stringify(StringBuilder builder)
|
||||
{
|
||||
builder.Append(m_value.ToString());
|
||||
if (!IsLeaf)
|
||||
{
|
||||
builder.Append('(');
|
||||
bool firstTime = true;
|
||||
foreach (Tree<T> t in m_children)
|
||||
{
|
||||
if (firstTime)
|
||||
firstTime = false;
|
||||
else
|
||||
builder.Append(',');
|
||||
t.Stringify(builder);
|
||||
}
|
||||
builder.Append(')');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if this tree is a descendant of another</summary>
|
||||
/// <param name="ancestor">Possible ancestor</param>
|
||||
/// <returns>True iff this tree is a descendant of the other</returns>
|
||||
/// <remarks>A tree is considered a descendant of itself</remarks>
|
||||
public bool IsDescendantOf(Tree<T> ancestor)
|
||||
{
|
||||
Tree<T> descendant = this;
|
||||
while (descendant != null)
|
||||
{
|
||||
if (ancestor == descendant)
|
||||
return true;
|
||||
descendant = descendant.Parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether a tree is a leaf (no children)</summary>
|
||||
public bool IsLeaf
|
||||
{
|
||||
get { return m_children.Count == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets level, or depth, in tree</summary>
|
||||
public int Level
|
||||
{
|
||||
get
|
||||
{
|
||||
int result = 0;
|
||||
Tree<T> ancestor = m_parent;
|
||||
while (ancestor != null)
|
||||
{
|
||||
result++;
|
||||
ancestor = ancestor.Parent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of descendants, including the tree itself</summary>
|
||||
public int DescendantCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int n = 0;
|
||||
foreach (Tree<T> tree in PreOrder)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration of all the nodes of the tree in pre-order (depth first).
|
||||
/// For example, the root node is first, followed by its first child (and its
|
||||
/// children and so on) and then the second child of the root (and its children
|
||||
/// and so on) etc.</summary>
|
||||
public IEnumerable<Tree<T>> PreOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
Stack<Tree<T>> nodes = new Stack<Tree<T>>();
|
||||
nodes.Push(this);
|
||||
while (nodes.Count > 0)
|
||||
{
|
||||
Tree<T> node = nodes.Pop();
|
||||
yield return node;
|
||||
|
||||
// push children in reverse order
|
||||
for (int i = node.m_children.Count - 1; i >= 0; i--)
|
||||
nodes.Push(node.m_children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration of all the nodes of the tree in post-order. This means that for
|
||||
/// each node, the children are visited first (starting with the first child) and then
|
||||
/// the parent node is enumerated.</summary>
|
||||
public IEnumerable<Tree<T>> PostOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
// push each non-leaf node twice to represent nodes whose children haven't been visited
|
||||
// rather than storing a bit on each node.
|
||||
Stack<Tree<T>> nodes = new Stack<Tree<T>>();
|
||||
nodes.Push(this);
|
||||
if (!IsLeaf)
|
||||
nodes.Push(this);
|
||||
|
||||
while (nodes.Count > 1)
|
||||
{
|
||||
Tree<T> node = nodes.Pop();
|
||||
if (node != nodes.Peek())
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// push children in reverse order
|
||||
for (int i = node.m_children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Tree<T> child = node.m_children[i];
|
||||
nodes.Push(child);
|
||||
if (!child.IsLeaf)
|
||||
nodes.Push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return nodes.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration of all the nodes in a breadth-first order. This means that the
|
||||
/// root is enumerated first (level 0), followed by all of its children (level 1),
|
||||
/// followed by all of their children (level 2), and so on.</summary>
|
||||
public IEnumerable<Tree<T>> LevelOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
Queue<Tree<T>> nodes = new Queue<Tree<T>>();
|
||||
nodes.Enqueue(this);
|
||||
while (nodes.Count > 0)
|
||||
{
|
||||
Tree<T> node = nodes.Dequeue();
|
||||
yield return node;
|
||||
|
||||
// queue children
|
||||
foreach (Tree<T> child in node.m_children)
|
||||
nodes.Enqueue(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ChildCollection : Collection<Tree<T>>
|
||||
{
|
||||
public ChildCollection(Tree<T> parent)
|
||||
{
|
||||
m_parent = parent;
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, Tree<T> item)
|
||||
{
|
||||
item.m_parent = m_parent;
|
||||
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
Items[index].m_parent = null;
|
||||
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, Tree<T> item)
|
||||
{
|
||||
Items[index].m_parent = null;
|
||||
item.m_parent = m_parent;
|
||||
|
||||
base.SetItem(index, item);
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
foreach (Tree<T> subTree in Items)
|
||||
subTree.m_parent = null;
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
private readonly Tree<T> m_parent;
|
||||
}
|
||||
|
||||
private T m_value;
|
||||
private Tree<T> m_parent;
|
||||
private readonly ChildCollection m_children;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user