Introduction
While developing Silverlight applications, we had to face the issue of binding textboxes to various types of data. And as we are allowed to enter strings in textboxes,
we may not receive data in the View Model (or bound property) due to incompatible source data types.
E.g., a TextBox
being bound with an int
source property. Here the source property will never receive the value if the user enters non-numeric characters,
leaving two different values in the View and the View Model.
This article explains two approaches for solving this problem.
Background
This article deals with format incompatibility between the binding target type and the source type. The approaches described below do not restrict the user from entering
incompatible data but make sure to check content compatibility using TypeConvereterAttribute
. The article assumes that the reader has a basic knowledge of databinding
and is comfortable with the notions of source and target properties.
Using the code
In Silverlight/WPF, we having attributes to give extra information for data. This article leverages this attribute approach for handling type conversion.
The following code snippet depicts a sample type converter for the integer data type:
public class IntTypeConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
if (value == null)
{
throw new FormatException(string.Format(
"Null value not supported for {0}", typeof(int)));
}
try
{
return int.Parse(value.ToString());
}
catch
{
return default(int);
}
}
}
Once TypeAttribute
is declared, it has to be associated with the source property which would get bound to the TextBox
.
The following code snippet will help you achieve this.
[TypeConverter(typeof(IntTypeConverter))]
public int Field1
{
get
{
return field1;
}
set
{
field1 = value;
OnPropertyChanged("Field1");
}
}
Approach 1: TypeSafeTextBox
TypeSafeTextBox
subscribes to the lost focus event, and uses BindingExpression
to extract the source property and its TypeConverter
information.
Once we receive this information, it checks for conversion.
public class TypeSafeTextBox : TextBox
{
public TypeSafeTextBox()
{
this.LostFocus += new RoutedEventHandler(TypeSafeTextBox_LostFocus);
}
void TypeSafeTextBox_LostFocus(object sender, RoutedEventArgs e)
{
try
{
BindingExpression textBindingExpression = this.GetBindingExpression(TextBox.TextProperty);
if (textBindingExpression != null && textBindingExpression.ParentBinding != null)
{
PropertyInfo propertyInfo = GetPropertyInfo(
textBindingExpression.DataItem.GetType(),
textBindingExpression.ParentBinding.Path.Path);
if (propertyInfo != null)
{
var attributes = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);
if (attributes != null && attributes.Length > 0)
{
var convAttribute = attributes[0] as TypeConverterAttribute;
if (convAttribute != null)
{
var converterType = Type.GetType(convAttribute.ConverterTypeName, false);
if (converterType != null)
{
TypeConverter conv = (Activator.CreateInstance(converterType) as TypeConverter);
if (conv != null)
{
object value = conv.ConvertFrom(null, Thread.CurrentThread.CurrentUICulture,
this.Text);
this.Text = value.ToString();
}
}
}
}
}
}
}
catch (Exception)
{
this.SetValue(TextProperty, DependencyProperty.UnsetValue);
}
}
private PropertyInfo GetPropertyInfo(Type type, string path)
{
if (path.Contains("."))
{
string startPath = path.Substring(0, path.IndexOf("."));
string endPath = path.Substring(path.IndexOf(".") + 1);
return GetPropertyInfo(type.GetProperty(startPath).PropertyType, endPath);
}
return type.GetProperty(path);
}
}
The above code describes how type conversion is performed when the text box loses its focus, restricting the user to enter only valid data else setting the default data type value.
GetPropertyInfo
in the above code helps in iterating through the complex source binding.
Approach 2: Attached dependency property
We can avoid creating the inherited TypeSafeTextBox
control by leveraging the attached dependency property feature.
The following code snippet shows how the attached property can be created with a type safe check.
public static readonly DependencyProperty CheckProperty =
DependencyProperty.RegisterAttached("Check",typeof (bool),
typeof (TypeSafeAttachment),
new PropertyMetadata(false,new PropertyChangedCallback(CheckChanged)));
public static void SetCheck(UIElement element, bool value)
{
element.SetValue(CheckProperty, value);
}
public static bool GetCheck(UIElement element)
{
return (bool) element.GetValue(CheckProperty);
}
private static void CheckChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
if(textBox!=null)
{
textBox.LostFocus -= new RoutedEventHandler(TypeSafeTextBox_LostFocus);
textBox.LostFocus += new RoutedEventHandler(TypeSafeTextBox_LostFocus);
}
}
The sample source code below shows how the above two approaches can be used for binding in XAML:
<TextBox TextBoxDemo:TypeSafeAttachment.Check="True"
Text="{Binding Field1, Mode=TwoWay}" Margin="5" /></TextBox />
// Using TypeSafeTextBox
<TypeSafeTextBox Text="{Binding Field1, Mode=TwoWay}" Margin="5" />
</TypeSafeTextBox />
// Using TypeSafeTextBox for nested binding source Data.Field1
<TypeSafeTextBox Text="{Binding Data.Field1, Mode=TwoWay}" Margin="5" />
Conclusion
As described above, you can use the derived textbox (TypeSafeTextBox
) or the attached dependency property for restricting a textbox from having an incompatible data type set.
There is no preference in using a particular approach, you can choose any.