Search code examples
.netpropertygridtypeconverter

TypeConverter reuse in PropertyGrid .NET winforms


I would like to ask if it's possible to reuse the same TypeConverter when implementing a custom drop down list in a PropertyGrid, but with different items. Consider the following TypeConverter to implement a 2 item (dog/cat) drop down list in a PropertyGrid.

Public Class MyList : Inherits System.ComponentModel.StringConverter
    Public items As String() = New String() {"dog", "cat"} 

    Public Overloads Overrides Function GetStandardValues(ByVal context As System.ComponentModel.ITypeDescriptorContext) As System.ComponentModel.TypeConverter.StandardValuesCollection
        Return New StandardValuesCollection(items)
    End Function

    Public Overloads Overrides Function GetStandardValuesSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return True
    End Function

    Public Overloads Overrides Function GetStandardValuesExclusive(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
        Return True
    End Function
End Class

This TypeConverter is used as such:

<TypeConverter(GetType(MyList))>
Public Class MyDogAndCatList
   ...
End Class

I would now like to reuse this same TypeConverter MyList, but with different list items. Eg. instead of dog/cat, I'd like to use cow/sheep and then define the complex data class. Something like this:

cow_sheep = new MyList({"cow", "sheep"})
<TypeConverter(GetType(cow_sheep)), ...

However this doesn't seem possible because the TypeConverter must be a Class and cannot be an instance. I would like to reuse the TypeConverter because I have many lists to deal with and I don't want to define a new TypeConverter for each list while practically all overridden functions are always the same.

I understand I could build by base converter and then inherit from it to simplify my code a bit. But that would still mean dealing with more than 100 different classes, something I sure think is not the right approach.

Does anybody know how to do that? Any help is welcome. Thank you in advance.


Solution

  • You cannot change the TypeConverterAttribute to add something to it, but you can add any number of custom attributes to the property, so you can create your own attribute to hold custom data.

    Here is some sample C# code:

    public class Animals
    {
        // both properties have the same TypeConverter, but they use different custom attributes
        [TypeConverter(typeof(ListTypeConverter))]
        [ListTypeConverter(new [] { "cat", "dog" })]
        public string Pets { get; set; }
    
        [TypeConverter(typeof(ListTypeConverter))]
        [ListTypeConverter(new [] { "cow", "sheep" })]
        public string Others { get; set; }
    }
    
    // this is your custom attribute
    // Note attribute can only use constants (as they are added at compile time), so you can't add a List object here
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class ListTypeConverterAttribute : Attribute
    {
        public ListTypeConverterAttribute(string[] list)
        {
            List = list;
        }
    
        public string[] List { get; set; }
    }
    
    // this is the type converter that knows how to use the ListTypeConverterAttribute attribute
    public class ListTypeConverter : TypeConverter
    {
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
    
        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            var list = new List<string>();
    
            // the context contains the property descriptor
            // the property descriptor has all custom attributes defined on the property
            // just get it (with proper null handling)
            var choices = context.PropertyDescriptor.Attributes.OfType<ListTypeConverterAttribute>().FirstOrDefault()?.List;
            if (choices != null)
            {
                list.AddRange(choices);
            }
            return new StandardValuesCollection(list);
        }
    }