带有转换器的自定义 WinForms 数据绑定不适用于可空类型(双精度?)
Custom WinForms data binding with converter not working on nullable type (double?)
在我的 WinForms 应用程序中,我实现了 custom data binding with support for value converters,类似于 WPF。
示例有一个新的绑定 class,它派生自 Binding
并允许附加自定义转换器:
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);
}
}
还有类似于WPF的界面的界面IValueConverter
:
public interface IValueConverter
{
object Convert(object value, Type targetType, object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
有了这个,我可以将自己的转换器附加到任何(双向)绑定。到目前为止,我已经构建了一个 TimeSpanStringValueConverter
允许我将 TimeSpan
字段绑定到文本框以及 InvertBooleanValueConverter
将布尔字段的反面绑定到任何布尔值 属性 任何控件。他们按预期工作!
现在我想将 double?
类型的 属性 绑定到文本框 Text
字段。为此我写了这个转换器:
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;
}
}
我这样将它绑定到文本框:
this.textBox1.DataBindings.Add(new CustomBinding("Text", obj, "MyProperty", new NullableDoubleStringValueConverter())); // obj is the data context object
当我在值转换器的 ConvertBack
方法中设置断点时,我可以清楚地看到我的字符串在离开文本框后将如何成功转换为可为 null 的双精度值。但是,与我的其他情况不同,它不会更新数据上下文对象。如果我在我的值转换器的 Convert
方法中设置一个断点,我可以看到它会检索 MyProperty
的初始值,该值在离开后更新文本框的 Text
时为 null它。所以,我的文本框在输入任何数值后变空了。
我的问题是:为什么?我怎样才能使它与可空类型 (double?
) 一起工作?如果我将数据上下文对象中 MyProperty
的类型更改为 double
,它将接受我更改的值。 不幸的是,我需要可空支持。因此,当将文本框留空时,我想存储 null 并在值为 null 时显示一个空文本框。
事实上,错误不在您的代码中,而是在您下载的代码中 (CustomBinding
)。修改构造函数成为
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;
}
问题就解决了。重要的部分是 Binding.FormatingEnabled
必须是 true
才能使所有这些工作(注意碱基调用的最后一个参数,但您也可以稍后设置它)。另请注意,我删除了
this.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
行。默认值为 OnValidation
,适用于任何控件。 OnPropertyChanged
适用于立即更新控件,如复选框、单选按钮和不可编辑的组合框。无论如何,最好将设置此 属性 的责任留给 class 的用户。
与问题无关的旁注:你最好不要剥离无效字符并让异常在你的 ConvertBack
方法中抛出,否则如果用户键入你会得到一个奇怪的输入值例如“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;
}
在我的 WinForms 应用程序中,我实现了 custom data binding with support for value converters,类似于 WPF。
示例有一个新的绑定 class,它派生自 Binding
并允许附加自定义转换器:
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);
}
}
还有类似于WPF的界面的界面IValueConverter
:
public interface IValueConverter
{
object Convert(object value, Type targetType, object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
有了这个,我可以将自己的转换器附加到任何(双向)绑定。到目前为止,我已经构建了一个 TimeSpanStringValueConverter
允许我将 TimeSpan
字段绑定到文本框以及 InvertBooleanValueConverter
将布尔字段的反面绑定到任何布尔值 属性 任何控件。他们按预期工作!
现在我想将 double?
类型的 属性 绑定到文本框 Text
字段。为此我写了这个转换器:
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;
}
}
我这样将它绑定到文本框:
this.textBox1.DataBindings.Add(new CustomBinding("Text", obj, "MyProperty", new NullableDoubleStringValueConverter())); // obj is the data context object
当我在值转换器的 ConvertBack
方法中设置断点时,我可以清楚地看到我的字符串在离开文本框后将如何成功转换为可为 null 的双精度值。但是,与我的其他情况不同,它不会更新数据上下文对象。如果我在我的值转换器的 Convert
方法中设置一个断点,我可以看到它会检索 MyProperty
的初始值,该值在离开后更新文本框的 Text
时为 null它。所以,我的文本框在输入任何数值后变空了。
我的问题是:为什么?我怎样才能使它与可空类型 (double?
) 一起工作?如果我将数据上下文对象中 MyProperty
的类型更改为 double
,它将接受我更改的值。 不幸的是,我需要可空支持。因此,当将文本框留空时,我想存储 null 并在值为 null 时显示一个空文本框。
事实上,错误不在您的代码中,而是在您下载的代码中 (CustomBinding
)。修改构造函数成为
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;
}
问题就解决了。重要的部分是 Binding.FormatingEnabled
必须是 true
才能使所有这些工作(注意碱基调用的最后一个参数,但您也可以稍后设置它)。另请注意,我删除了
this.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
行。默认值为 OnValidation
,适用于任何控件。 OnPropertyChanged
适用于立即更新控件,如复选框、单选按钮和不可编辑的组合框。无论如何,最好将设置此 属性 的责任留给 class 的用户。
与问题无关的旁注:你最好不要剥离无效字符并让异常在你的 ConvertBack
方法中抛出,否则如果用户键入你会得到一个奇怪的输入值例如“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;
}