views:

2106

answers:

1

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).

+7  A: 

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.

Jon Skeet
Thanks for the feedback, i have clarified the example and made the question more generic. My example was a struct for simplicity, but I have changed it to a reference type to demonstrate the wider problem.
ee
Making it a struct with public fields certainly doesn't *add* to simplicity :) Is this in Windows Forms, WPF, ASP.NET or what?
Jon Skeet
Depends on what your heuristic for "simplicity" is (simplicity of code, simplicity of example, simplicity of using the data type in interop..etc.), but I digress. I seek to use the solution in windows forms, but the question is relevant to databinding in general, i think.
ee
Mutable structs very rarely mean simplicity in *any* context. Anyway, I've reproduced the problem, but I can't get it to work either. As you say, the binding seems to only be on the display side. Still looking.
Jon Skeet
Indeed, that did the trick. Thanks for the assistance, I appreciate it.
ee
I'd have thought using the Binding.Format and Binding.Parse event is the 'standard' solution. Why does this 'not appeal in quite the same way'?
Joe
Because the class has already said "When you need to do type conversion, use this class over here!" Why should every binding need to repeat that information?
Jon Skeet
Sure, but type conversion to/from a string is concerned with preserving information. Format/Parse is related to an editing UI (usually culture-sensitive). Nothing wrong with Type Conversion if the string is in a convenient format for editing. But nothing wrong with Format/Parse either.
Joe
... also Format/Parse is bidirectional, which is what is needed. With a Type converter you still need something to parse back from string to the custom Type. And Format/Parse doesn't preclude having a shared helper if the binding is repeated in several places.
Joe
@Joe: With the type converter and FormattingEnabled set to true, the tytpe converter is used bidirectionally too. That's the bit that wasn't working before, but does with FormattingEnabled set.
Jon Skeet