Reputation: 3257
I want to have a property grid that looks like this when it has two SteppedValue objects:
But I would also like the SteppedValue object to be a generic, for example:
var fastEma = new SteppedValue<int>();
var slowEma = new SteppedValue<float>();
I have this mostly working by implementing ExpandableObjectConverter... but the only problem I am having is when I instantiate the object to return in ConvertFrom... I can't figure how to get the System.Type of T (from the instance of the generic) at this point.
Problem Code
public override object ConvertFrom(ITypeDescriptorContext iTypeDescriptorContext, CultureInfo cultureInfo, object value)
{
if (value is string)
{
var classType = typeof(SteppedValue<>);
Type[] typeArgs = { typeof(int) }; // <== Need to determine this
// needs to be T of the generic
// instance (not "typeof(int)")
var instanceType = classType.MakeGenericType(typeArgs);
object o = Activator.CreateInstance(instanceType, value);
return o;
}
return base.ConvertFrom(iTypeDescriptorContext, cultureInfo, value);
}
Full Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace Test
{
[TypeConverter(typeof(SteppedValueConverter))]
public class SteppedValue<T>
{
public T Begin { get; set; }
public T End { get; set; }
public T Step { get; set; }
public SteppedValue(T begin, T end, T step)
{
Begin = begin;
End = end;
Step = step;
}
public SteppedValue(string str)
{
var parts = ParseParts(str);
Begin = parts[0];
End = parts[1];
Step = parts[2];
}
public static T ToT(object obj)
{
return (T)Convert.ChangeType(obj, typeof(T));
}
public static SteppedValue<T> Parse(string str)
{
return new SteppedValue<T>(str);
}
public List<T> ParseParts(string str)
{
while (str.Contains(" ")) str = str.Replace(" ", " ");
var arr = str.Trim().Split(' ');
if (arr.Length == 0)
return new List<T> { default(T), default(T), default(T) };
else if (arr.Length == 1)
return new List<T> { ToT(arr[0]), ToT(arr[0]), default(T) };
else if (arr.Length == 3 && arr[1].ToLower() == "to")
return new List<T> { ToT(arr[0]), ToT(arr[2]), default(T) };
else if (arr.Length == 5 && arr[1].ToLower() == "to" && arr[3].ToLower() == "step")
return new List<T> { ToT(arr[0]), ToT(arr[2]), ToT(arr[4]) };
throw new Exception("Unable to parse'" + str + "'");
}
public override string ToString()
{
if (Begin.Equals(End))
return Begin.ToString();
else if (Step == null || Step.ToString() == "")
return Begin + " to " + End;
else
return Begin + " to " + End + " step " + Step;
}
}
public class SteppedValueConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext iTypeDescriptorContext, Type type)
{
if (type == typeof(string))
return true;
return base.CanConvertFrom(iTypeDescriptorContext, type);
}
public override object ConvertFrom(ITypeDescriptorContext iTypeDescriptorContext, CultureInfo cultureInfo, object value)
{
if (value is string)
{
var classType = typeof(SteppedValue<>);
Type[] typeArgs = { typeof(int) }; // <== Need to determine this
var instanceType = classType.MakeGenericType(typeArgs);
object o = Activator.CreateInstance(instanceType, value);
return o;
}
return base.ConvertFrom(iTypeDescriptorContext, cultureInfo, value);
}
public override object ConvertTo(ITypeDescriptorContext iTypeDescriptorContext, CultureInfo cultureInfo, object value, Type destinationType)
{
if (destinationType == typeof(string) && value.GetType().GetGenericTypeDefinition() == typeof(SteppedValue<>))
return value.ToString();
return base.ConvertTo(iTypeDescriptorContext, cultureInfo, value, destinationType);
}
}
}
Upvotes: 1
Views: 3493
Reputation: 138776
You can get the type from the context, like this (in your SteppedValue case, it's the first generic type):
Type steppedValueType = iTypeDescriptorContext.PropertyDescriptor.PropertyType.GetGenericArguments()[0];
Upvotes: 2