How does one databind a custom type to TextBox.Text?

el2iot2 picture el2iot2 · Jan 22, 2009 · Viewed 9.2k times · Source

I have a custom c# type like (just an example):

public class MyVector
{ 
   public double X {get; set;} 
   public double Y {get; set;} 
   public double Z {get; set;} 
   //...
}

And I want it to databind to TextBox.Text:

TextBox textBox;
public MyVector MyVectorProperty { get; set;}
//...
textBox.DataBindings.Add("Text", this, "MyVectorProperty");

Essentially I need conversion to and from a string for my custom value type. In the text box, I want something like "x, y, z" that can be edited to update the vector type. I assumed that I could do so by adding a TypeConverter derived class:

public class MyVectorConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, 
                                        Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        //...
        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, 
                                      Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        //...
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
                                       System.Globalization.CultureInfo culture,
                                       object value)
    {
        if (value is string)
        {
            MyVector MyVector;
            //Parse MyVector from value
            return MyVector;
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
                                     System.Globalization.CultureInfo culture, 
                                     object value, 
                                     Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            string s;
            //serialize value to string s
            return s;
        }
        //...
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

and associating it with my struct:

[TypeConverter(typeof(MyVectorConverter))]
public class MyVector { //... }

This appears to complete half of the battle. I can see MyVectorConverter getting called into, but something is amiss. It is called to see if it knows how to convert to string, then it is called to convert to string. However, it is never queried to see if it can convert FROM string nor to actually do the conversion. Furthermore, right after an edit in the textbox, the old value is immediately replaced (another CanConvertTo and ConvertTo sequence, restoring the old value). The end result is that the newly typed entry in the text box is reverted immediately after it is applied.

I feel as if there is just something simple missing. Is there? Is this entire project/approach doomed to failure? Does anyone else attempt such madness? How does one bi-directionally bind a custom, multipart type to a string-based control?

Solution: Bizarrely, all that is needed is for the "formatting" to be enabled on the Binding object. (thanks, Jon Skeet):

textBox.DataBindings.Add("Text", this, "MyVectorProperty"); //FAILS
textBox.DataBindings.Add("Text", this, "MyVectorProperty", true); //WORKS!

Oddly, all that my MSDN mentions about this parameter (formattingEnabled) is:

"true to format the displayed data; otherwise, false"

It mentions nothing about it being a requirement for the data to come back from the control (under these conditions).

Answer

Jon Skeet picture Jon Skeet · Jan 22, 2009

Got it!

Set the Binding.FormattingEnabled property to true. This seems to make it all work. You can do this with an overload to the ControlBindingsCollection.Add method which takes a Boolean parameter at the end. It's odd that it worked one way but not the other before, but certainly my test app now works...

(Old answer below)

I wouldn't be at all surprised if the fact that you're got a struct instead of a class was important here - as well as the way you're using fields instead of properties.

Try with a class using autoimplemented properties instead:

public class MyClass
{ 
   public int IntPart { get; set; } 
   public string StringPart { get; set; }
   //...
}

This may well not be the root of the problem, but using a mutable struct with public fields is just asking for trouble IMO.

EDIT: As mentioned in the comments, I've now got an example up and running. The Binding.Parse is being raised with the right value. Now to find out why the TypeConverter isn't being called...

EDIT: I've found a useful article which describes binding in more detail. It seems to suggest that the type converter is only used to convert "to" another type - so you'd need the type converter for string to know how to convert to the custom type. This seems pretty strange to me, admittedly, but there are two other options:

  • Use the Format and Parse events of Binding to do the conversion
  • Make the type implement IConvertible

Neither of these appeal in quite the same way, but they may be enough of a workaround for you. I'm sure there's a way to get this to work using TypeConverters, but I'm blowed if I can see it at the moment.