Jani Hyytiäinen
Jani Hyytiäinen

Reputation: 5407

How to return a property as value from TypeConverter in a PropertyGrid

I have a following type of class assigned to PropertyGrid:

public class Message{
    [TypeConverter(typeof(UserConverter))]
    public int SenderId { get; set; }
}

And a Converter

public class UserConverter: TypeConverter
{
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true;
    }
    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        return new StandardValuesCollection(_users);
    }
}

And of course the User

public class User
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public override string ToString()
    {
        return User.fullName;
    }
}

All goes fine so far (note: the Sender property in the image below is same as SenderId in the Message class above. My example here is highly simplified for easier readability):

ComboBox showing full name

Until I pick an item in the list, I get an exception in mscorlib:

System.ArgumentException occurred
  _HResult=-2147024809
  _message=Object of type 'System.String' cannot be converted to type 'System.Int32'.
  HResult=-2147024809
  IsTransient=false
  Message=Object of type 'System.String' cannot be converted to type 'System.Int32'.
  Source=mscorlib
  StackTrace:
       at System.ComponentModel.ReflectPropertyDescriptor.SetValue(Object component, Object value)
  InnerException: 

It doesn't even seem to go through the TypeConverter of mine. I realize the property type is int and I've tried overriding ConvertTo in the UserConverter but it doesn't seem to hit the method at all when I pick an item in the ComboBox.

How do I control the return value of such a combo box? In this specific scenario, I would like to return the Message.SenderId instead of it's object.ToString() override.

Upvotes: 2

Views: 3960

Answers (2)

Marco
Marco

Reputation: 1016

I imagine you've found the solution by now, but anyway. The thing is that it's trying to find a converter for int to User... If you add this to the constructor of your converter (probably in your case in the static constructor):

Attribute[] attr = new Attribute[1];
        TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(UserConverter));
        attr[0] = vConv;
        TypeDescriptor.AddAttributes(typeof(int), attr);

It will work, but it will also show all ints on your class as a dropdownbox for users ;-) Which is simply not what you want.

It would be cool if we could simply supply a cast for the User class like this:

public static implicit operator int(User user) 
{
    return user.Id;  // implicit conversion
}

But unfortunately this does not work as well. It will keep trying to use the Int32 and throwing exceptions.

The only way to get this working is to write a wrapper for the Id:

public class SenderId
{
    public int Id { get; set; }
    public string DisplayMember {get; set;}
    // this is how it will display the items:
    public override string ToString()
        {
            return $"{DisplayMember} [{Id}]";
        }
}

You should also implement Equals and GetHashCode. Then use it like this:

public class Message
{
    [TypeConverter(typeof(UserConverter))]
    public SenderId SenderId { get; set; }
}

Converter:

public class UserConverter: TypeConverter
{
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true;
    }
    public override StandardValuesCollection GetStandardValues(
        ITypeDescriptorContext context)
    {
        return new StandardValuesCollection(_senderIds);
    }
}

Cheers.

Upvotes: 0

Yurii
Yurii

Reputation: 4911

I've tried overriding ConvertTo in the UserConverter but it doesn't seem to hit the method at all

In addition to ConvertTo you should also override CanConvertFrom and ConvertFrom methods, to specify that your converter actually can convert between desired types.

There is no need to override CanConvertTo, because according to MSDN:

It is not necessary to override this method for conversion to a string type.Source

The following, is a simple implementation of these methods based on the models you provided:

public override bool CanConvertFrom(
    ITypeDescriptorContext context, Type sourceType)
{
    return sourceType == typeof(string);
}

public override object ConvertFrom(
    ITypeDescriptorContext context, CultureInfo culture, object value)
{
    return users.OfType<User>().First(u => u.ToString() == value.ToString()).Id;
}

public override object ConvertTo(
    ITypeDescriptorContext context, 
    CultureInfo culture, 
    object value, 
    Type destinationType)
{
    if (destinationType == typeof(string))
    {
        if (value is int)
        {
            return users.OfType<User>().First(u => u.Id == (int)value).ToString();
        }
        return value.ToString();
    }
    return base.ConvertTo(context, culture, value, destinationType);
}

Note: this is a really primitive implementation without any checks for invalid values, exception handling etc. So I discourage using it in production.

Upvotes: 1

Related Questions