четвъртък, 16 януари 2014 г.

String to Object Custom Type Converter

If you are writing a custom control, you will most likely encounter the fallowing problem – converting a property type at design time. The only way to achieve that is to use a custom type converter, as described here.
In most cases the standard documentation will cover the basics. However there is a specific case in which the approach from the documentation will not work and you need to alter the String to Object custom converter logic.
The case is the following. When you have a property which value is decimal, integer or float, there are only strings available in the markup. So the problem we are facing is how to convert the property value from the entered markup string to a given type at design time. First the property must be of object type. You can box the entered values into an object but one issue still remains - how to prevent Visual Studio from throwing the “Cannot create an object of type 'System.Object' from its string representation”exception.
Here at ShieldUI, we encountered the same problem, when working on the Chart for ASP.NET control. We resolved it using the following approach.
Add an object as a type of the public property:
1
2
3
4
public Object ValueY
{
 
}
Add a TypeConverter attribute.
1
2
3
4
5
[TypeConverter(typeof(StringToObjectConverter))]
public Object ValueY
  {
 
  }
The purpose of the StringToObjectConverter is to convert the string value to an object and box it in a strong typed value. It has the following signature:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
internal class StringToObjectConverter : TypeConverter
 {
     public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
     {
         if (sourceType == typeof(string))
         {
             return true;
         }
 
         return base.CanConvertFrom(context, sourceType);
     }
 
     public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
     {
         string stringValue = value as string;
 
         if (stringValue != null)
         {
             return new ObjectWrapper(stringValue);
         }
 
         return base.ConvertFrom(context, culture, value);
     }
 
     public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
     {
         if (destinationType == typeof(InstanceDescriptor))
             return true;
 
         return base.CanConvertTo(context, destinationType);
 
     }
 
     public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
     {
         if (destinationType == typeof(InstanceDescriptor) && value is ObjectWrapper)
         {
             ObjectWrapper obj = value as ObjectWrapper;
 
             ConstructorInfo ctor = typeof(ObjectWrapper).GetConstructor(new Type[] { typeof(string) });
 
             if (ctor != null)
             {
                 return new InstanceDescriptor(ctor, new object[] { obj.Value.ToString() });
             }
         }
         return base.ConvertTo(context, culture, value, destinationType);
     }
 }
The tricky part here is to use a helper class which gets a string value and boxes it into an object. In our case this class is called ObjectWrapper and has the following signature:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  [TypeConverter(typeof(StringToObjectConverter))]
public class ObjectWrapper
{
    public ObjectWrapper()
    {
    }
 
    public ObjectWrapper(string value)
    {
        Value = value;
    }
 
    public object Value { get; set; }
}
Now we can cast the value in the property setter to that class and to store the real boxed value into the object, regardless that it is entered as a string in design time:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[TypeConverter(typeof(StringToObjectConverter))]
   public Object ValueY
   {
       get
       {
           return ViewState["ValueY"];
       }
       set
       {
           if (value is ObjectWrapper)
           {
               ViewState["ValueY"] = TypeChecker.ConvertToObject(((ObjectWrapper)value).Value.ToString());
           }
           else
           {
               object o = value;
               if (o is string)
               {
                   o = TypeChecker.ConvertToObject(value.ToString());
               }
               ViewState["ValueY"] = o;
           }
       }
   }
The TypeChecker is a helper class which converts the string value to the underling type and boxes is into an object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
       internal static class TypeChecker
    {
        static bool CanConvertTo<t>(string s)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
            return converter.IsValid(s);
        }
 
        public static object ConvertToObject(string input)
        {
            if (CanConvertTo<int>(input))
            {
               return Convert.ToInt32(input);
            }
            if (CanConvertTo<double>(input))
            {
                return double.Parse(input, CultureInfo.InvariantCulture);
            }
            if (CanConvertTo<decimal>(input))
            {
                return decimal.Parse(input, CultureInfo.InvariantCulture);
            }
            if (CanConvertTo<datetime>(input))
            {
                return Convert.ToDateTime(input);
            }
            if (CanConvertTo<timespan>(input))
            {
                return TimeSpan.Parse(input, CultureInfo.InvariantCulture);
            }
 
            return input;
        }
</timespan></datetime></decimal></double></int></t>
The result: You have an object property which can hold boxed strongly typed values from their string representation. All SeriesItem properties in ourShieldChart for ASP.NET AJAX control have the described type converters.

Няма коментари:

Публикуване на коментар