简体   繁体   中英

Custom WinForms data binding with converter not working on nullable type (double?)

In my WinForms application I implemented custom data binding with support for value converters , similar to WPF.

The sample has a new binding class that derives from Binding and allows to attach a custom converter:

public class CustomBinding : Binding
{
    private readonly IValueConverter _converter;
    private readonly object _converterParameter;
    private readonly CultureInfo _converterCulture;

    public CustomBinding(string propertyName, object dataSource, string dataMember, IValueConverter valueConverter, CultureInfo culture, object converterParameter = null)
        : base(propertyName, dataSource, dataMember)
    {
        if (valueConverter != null)
            this._converter = valueConverter;

        this.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
        this.FormattingEnabled = false;

        this._converterCulture = culture;
        this._converterParameter = converterParameter;
    }

    public CustomBinding(string propertyName, object dataSource, string dataMember, IValueConverter valueConverter, object converterParameter = null)
        : base(propertyName, dataSource, dataMember)
    {
        if (valueConverter != null)
            this._converter = valueConverter;

        this._converterCulture = Thread.CurrentThread.CurrentUICulture;
        this._converterParameter = converterParameter;
    }

    protected override void OnFormat(ConvertEventArgs cevent)
    {
        if (this._converter != null)
        {
            var converterdValue = this._converter.Convert(cevent.Value, cevent.DesiredType, _converterParameter, _converterCulture);
            cevent.Value = converterdValue;
        }
        else base.OnFormat(cevent);
    }

    protected override void OnParse(ConvertEventArgs cevent)
    {
        if (this._converter != null)
        {
            var converterdValue = this._converter.ConvertBack(cevent.Value, cevent.DesiredType, _converterParameter, _converterCulture);
            cevent.Value = converterdValue;
        }
        else base.OnParse(cevent);
    }
}

There is also the interface IValueConverter that is similar to the interface of WPF:

public interface IValueConverter
{
    object Convert(object value, Type targetType, object parameter, CultureInfo culture);
    object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

With this I'm able to attach an own converter to any (two-way) binding. So far I've built a TimeSpanStringValueConverter that allows me to bind a TimeSpan field to a textbox as well as a InvertBooleanValueConverter to bind the opposite of a boolean field to any boolean property of any control. They work as expected!

Now I want to bind a property of the type double? to a textbox Text field. For this I wrote this converter:

public class NullableDoubleStringValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value != null ? ((double)value).ToString(culture) : String.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string text = Regex.Replace((string)value, @"[^0-9" + culture.NumberFormat.NumberDecimalSeparator + @"]", "");
        if (text == String.Empty)
        {
            return null;
        }
        double convertedValue;
        if (Double.TryParse(text, NumberStyles.Any, culture, out convertedValue))
        {
            return (double?)convertedValue;
        }
        return null;
    }
}

And I bind it to the textbox this way:

this.textBox1.DataBindings.Add(new CustomBinding("Text", obj, "MyProperty", new NullableDoubleStringValueConverter())); // obj is the data context object

When I set a break point in the ConvertBack method of the value converter I can clearly see how my string will be converted to a nullable double value after leaving the textbox, successfully. However, instead of my other cases, it will not update the data context object. And if I set a break point in the Convert method of my value converter I can see it will retrieve the initial value of MyProperty which is null when updating the Text of the textbox after leaving it. So, my textbox gets empty after typing any numeric value in it.

My question is: Why? And how can I make it work with nullable types ( double? )? If I change the type of MyProperty in the data context object to double it will accept my changed value. Unfortunately I need the nullable support. So when leaving the textbox empty I want to store null as well as showing an empty text box, when the value was null.

Download of Problem Case Solution

In fact, the bug is not in your code, but in the one you have downloaded ( CustomBinding ). Modify constructors to become

public CustomBinding(string propertyName, object dataSource, string dataMember, IValueConverter valueConverter, object converterParameter = null)
    : this(propertyName, dataSource, dataMember, valueConverter, Thread.CurrentThread.CurrentUICulture, converterParameter)
{ }

public CustomBinding(string propertyName, object dataSource, string dataMember, IValueConverter valueConverter, CultureInfo culture, object converterParameter = null)
    : base(propertyName, dataSource, dataMember, true)
{
    this._converter = valueConverter;
    this._converterCulture = culture;
    this._converterParameter = converterParameter;
}

and the problem will be solved. The essential part is that Binding.FormatingEnabled must be true in order all this to work (note the last argument to the base call, but you can set it later too). Also note that I removed

this.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;

line. The default value for this is OnValidation which is applicable for any control. OnPropertyChanged is applicable for immediate update controls like check boxes, radio buttons and non editable combo boxes. In any case, it is better to leave the responsibility of setting this property to the user of the class.

A side note unrelated to the question: You'd better off not stripping the invalid characters and let the exception to be thrown inside your ConvertBack method, otherwise you get a weird input values if the user types for instance "1-3"

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    var text = value != null ? ((string)value).Trim() : null;
    return !string.IsNullOrEmpty(text) ? (object)double.Parse(text, NumberStyles.Any, culture) : null;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM