Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Type safe textbox in Silverlight

0.00/5 (No votes)
7 Dec 2011 1  
Creating a type safe textbox in Silverlight or providing type safe behavior to textbox.

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:

// Using attached property 
<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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here