Brian Rice
Brian Rice

Reputation: 3257

Using an ExpandableObjectConverter on a Generic class for a PropertyGrid

I want to have a property grid that looks like this when it has two SteppedValue objects:

enter image description here

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

Answers (1)

Simon Mourier
Simon Mourier

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

Related Questions