check point

This commit is contained in:
meelstorm
2025-07-14 12:03:59 +02:00
commit d3cb790bd9
431 changed files with 44078 additions and 0 deletions

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

View 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)
{
}
}
}

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

View File

@@ -0,0 +1,14 @@
using System;
namespace Inspectron.Settings.Attributes
{
public class SettingDescriptionAttribute: Attribute
{
public string Description { get; }
public SettingDescriptionAttribute(string description)
{
Description = description;
}
}
}

View File

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

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

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

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

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

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

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Configurations>Debug;Release;CPU</Configurations>
</PropertyGroup>
</Project>

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

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

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

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