using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Stylet.Samples.ModelValidation
{
///
/// Type which can be converter to and from both a T and a string. Useful for binding a TextBox to, say, an int.
///
///
/// Stringable{T} has two value properties - StringValue, which is always set, and Value, which is set if Value can be converted to a T.
/// If you create a new Stringable{T} from a T, both Value and StringValue are set. If you Create one from a string (using Stringable{T}.FromString)
/// then StringValue is set, and Value is set if that string value can be converted to a T>
/// IsValid indicates whether Value could be set.
///
/// This type has an associated TypeConverter, StringableConverter, which will be used by WPF to convert this Stringable{T} to and from a string.
///
// I would make this a struct, but it will have to be boxed to go through the TypeConverter, so there's no point
[TypeConverter(typeof(StringableConverter))]
public class Stringable : IEquatable>
{
private readonly string _stringValue;
///
/// String representation of the value
///
public string StringValue
{
get { return this._stringValue; }
}
private readonly T _value;
///
/// Actual value, or default(T) if IsValid is false
///
public T Value
{
get { return this._value; }
}
private readonly bool _isValid;
///
/// True if Value ias a proper value (i.e. we were constructed from a T, or we were constructed from a string which could be converted to a T)
///
public bool IsValid
{
get { return this._isValid; }
}
///
/// Create a new instance, representing the given value
///
/// Value to represent
public Stringable(T value) : this(value, value.ToString(), true) { }
private Stringable(T value, string stringValue, bool isValid)
{
this._value = value;
this._stringValue = stringValue;
this._isValid = isValid;
}
///
/// Create a new instance from the given string. If the string can be converted to a T, then IsValue is true and Value contains the converted value.
/// If not, IsValid is false and Value is default(T)
///
///
///
public static Stringable FromString(string stringValue)
{
T dest = default(T);
bool isValid = false;
// The TypeConverter for String can't convert it into anything else, so don't bother getting that
var fromConverter = TypeDescriptor.GetConverter(typeof(T));
if (fromConverter.CanConvertFrom(typeof(string)) && fromConverter.IsValid(stringValue))
{
dest = (T)fromConverter.ConvertFrom(stringValue);
isValid = true;
}
return new Stringable(dest, stringValue, isValid);
}
public static implicit operator T(Stringable stringable)
{
return stringable.Value;
}
public static implicit operator Stringable(T value)
{
return new Stringable(value);
}
public override string ToString()
{
return this.StringValue;
}
public override bool Equals(object obj)
{
return base.Equals(obj as Stringable);
}
public bool Equals(Stringable other)
{
return other != null && EqualityComparer.Default.Equals(this.Value, other.Value) && this.StringValue == other.StringValue;
}
public static bool operator ==(Stringable o1, Stringable o2)
{
if (Object.ReferenceEquals(o1, o2))
return true;
if ((object)o1 == null || (object)o2 == null)
return false;
return o1.Equals(o2);
}
public static bool operator !=(Stringable o1, Stringable o2)
{
return !(o1 == o2);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 27 + this.Value.GetHashCode();
hash = hash * 27 + this.StringValue.GetHashCode();
return hash;
}
}
}
///
/// TypeConverter for Stringable{T}
///
///
/// This is used by WPF. This means that if a Stringable{T} property is bound to a string control (e.g. TextBox), then this TypeConverter
/// is used to convert from that string back to a Stringable{T}
///
public class StringableConverter : TypeConverter
{
private readonly Type valueType;
private readonly Func generator;
public StringableConverter(Type type)
{
if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(Stringable<>) || type.GetGenericArguments().Length != 1)
throw new ArgumentException("Incompatible type", "type");
this.valueType = type;
// Generate a Func which gives us a Stringable, given a string
// WPF instantiates us once, then uses us lots, so the overhead of doing this here is worth it
var fromMethod = type.GetMethod("FromString", BindingFlags.Static | BindingFlags.Public);
var param = Expression.Parameter(typeof(string));
this.generator = Expression.Lambda>(Expression.Call(fromMethod, param), param).Compile();
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
return this.generator(value as string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || destinationType == this.valueType || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return String.Empty;
// Common case = just call the overloaded ToString - no need for reflection
if (destinationType == typeof(string))
return value.ToString();
var valueType = value.GetType();
if (destinationType.IsAssignableFrom(this.valueType) && typeof(Stringable<>).IsAssignableFrom(valueType))
{
var valueProperty = valueType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
return valueProperty.GetValue(value);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}