Wintellect  

Browse by Tags

All Tags » silverlight controls   (RSS)

The TextBox control is popular in Silverlight but comes with a few nuances. For example, the default behavior is that the data is not bound until the control loses focus! This can be awkward when you have a form with a disabled save button. The save button won't enable until the text box contains content, but it won't "know" about the content until the box loses focus, so the user is forced to tab from the last field before they can save.

I will cover two topics specific to the TextBox: the first is a behavior and trigger that resolve the issue with the "late data binding." The second is a filter behavior that allows you to control exactly what goes into the text box.

Let's get started! As you recall, the recipe for creating a new attached property is to declare the properties on a static class and register them as an attached property. We're going to start with the filter. As you know, the TextBox control itself takes any type of character input. A numeric field can be validated which prevents it from binding to the object, but why let the user type anything but a digit in the first place? Prevention is the best medicine, so why not filter the field?

We'll start with a class called TextBoxFilters to hold our attached properties specific to the text box. To filter the text box, we want to hook into the KeyDown event, check to see if it's a key we like, and set the event to "handled" if we don't want it (this prevents it from bubbling up and making it into the form). Of course, we have to be careful because we're not just checking for digits. Users must be able to navigate into and out of the field using TAB or fix their input using BACK.

The first part of the class is here. We create a convenient list of key presses that should always be allowed, then provide a method that tells us if it is a digit. Notice that we need to check the presence of the SHIFT key because Key.D1 becomes an exclamation mark when the SHIFT key is pressed.

public static class TextBoxFilters
{
    private static readonly List<Key> _controlKeys = new List<Key>
                                                            {
                                                                Key.Back,
                                                                Key.CapsLock,
                                                                Key.Ctrl,
                                                                Key.Down,
                                                                Key.End,
                                                                Key.Enter,
                                                                Key.Escape,
                                                                Key.Home,
                                                                Key.Insert,
                                                                Key.Left,
                                                                Key.PageDown,
                                                                Key.PageUp,
                                                                Key.Right,
                                                                Key.Shift,
                                                                Key.Tab,
                                                                Key.Up
                                                            };

    private static bool _IsDigit(Key key)
    {
        bool shiftKey = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
        bool retVal;
        if (key >= Key.D0 && key <= Key.D9 && !shiftKey)
        {
            retVal = true;
        }
        else
        {
            retVal = key >= Key.NumPad0 && key <= Key.NumPad9;
        }
        return retVal; 
    }
}

Next, we need to build our dependency properties. We'll provide a getter and setter and the registration. When the property is set, we'll check to see if the control is a TextBox. If it is, we hook into the KeyDown event and suppress it. The code looks like this:


public static bool GetIsPositiveNumericFilter(DependencyObject src)
{
    return (bool) src.GetValue(IsPositiveNumericFilterProperty);
}

public static void SetIsPositiveNumericFilter(DependencyObject src, bool value)
{
    src.SetValue(IsPositiveNumericFilterProperty, value);
}

public static DependencyProperty IsPositiveNumericFilterProperty = DependencyProperty.RegisterAttached(
    "IsPositiveNumericFilter", typeof (bool), typeof (TextBoxFilters),
    new PropertyMetadata(false, IsPositiveNumericFilterChanged));

public static void IsPositiveNumericFilterChanged(DependencyObject src, DependencyPropertyChangedEventArgs args)
{
    if (src != null && src is TextBox)
    {
        TextBox textBox = src as TextBox; 
        
        if ((bool)args.NewValue)
        {
            textBox.KeyDown += _TextBoxPositiveNumericKeyDown;
        }
    }
}

static void _TextBoxPositiveNumericKeyDown(object sender, KeyEventArgs e)
{
    bool handled = true;

    if (_controlKeys.Contains(e.Key) || _IsDigit(e.Key))
    {
        handled = false;
    }

    e.Handled = handled;
}

That's it ... now we have a handy attached behavior. If you include the namespace in your XAML and refer to it as Behaviors, you'll be able to attach the property to a text box like this:



<TextBox DataContext="{Binding MyNumericField}" Behaviors:TextBoxFilters.IsPositiveNumericFilter="True"/>

That's it - just attach the behavior, fire up your application, and try to put anything but a digit into your text box!

Now that we know how to attach that behavior, wouldn't it be nice to have some instant validation and databinding as well? This isn't difficult at all with dependency properties. All you need to do is hook into the event that is fired when the text changes, and force an update to the binding. The code for that behavior looks like this:

public static bool GetIsBoundOnChange(DependencyObject src)
{
    return (bool) src.GetValue(IsBoundOnChangeProperty);
}

public static void SetIsBoundOnChange(DependencyObject src, bool value)
{
    src.SetValue(IsBoundOnChangeProperty, value);
}

public static DependencyProperty IsBoundOnChangeProperty = DependencyProperty.RegisterAttached(
    "IsBoundOnChange", typeof(bool), typeof(TextBoxFilters),
    new PropertyMetadata(false, IsBoundOnChangeChanged));

public static void IsBoundOnChangeChanged(DependencyObject src, DependencyPropertyChangedEventArgs args)
 {
     if (src != null && src is TextBox)
     {
         TextBox textBox = src as TextBox;

         if ((bool)args.NewValue)
         {
             textBox.TextChanged += _TextBoxTextChanged;
         }
     }
 }

static void _TextBoxTextChanged(object sender, TextChangedEventArgs e)
 {
     if (sender is TextBox)
     {
         TextBox tb = sender as TextBox;
         BindingExpression binding = tb.GetBindingExpression(TextBox.TextProperty);
         if (binding != null)
         {
             binding.UpdateSource();
         }
     }
 }

As you can see, we hook into the text changed event, grab the binding and force it to update. Now you will have instant feedback. Of course, all validation rules are followed so if the binding causes a validation error, your validation will display instantly and the underlying objects will not be databound.

The new "super" text box that is filtered and binds as the text changes looks like this:


<TextBox DataContext="{Binding MyNumericField}" Behaviors:TextBoxFilters.IsPositiveNumericFilter="True"
   Behaviors:TextBoxfilters.IsBoundOnChange="True"/>

That's all there is too it ... keep your users honest by preventing them from typing something they don't need.

Jeremy Likness

I've had some fun with dynamic controls lately. Wanted to share a few caveats that I found for those of you trying to spin some of your own.

The premise is simple: we have XML configuration that drives the UI, so based on the values parsed from the XML, we generate the appropriate control.

I implemented this by created a data container for the XML. I parsed the XML using LINQ to XML and then databound my UI elements to the object graph. The main work is done with a converter for the control. The XAML looks like this:

...
<ContentControl DataContext="{Binding}" Content="{Binding Converter={StaticResource ParmControl}}" HorizontalContentAlignment="Stretch" Margin="2"/>
...

As you can see, I'm "bound" to my property, with the content of the convert being passed to a converter. The convert itself returns the dynamic Silverlight control. The code returns a FrameworkElement, but builds out the appropriate type of control based on the type of the property passed in. For example, for the text box, we'll create a binding and then return a TextBox like this:

Binding dbBinding = new Binding("Value") 
{
    Mode = BindingMode.TwoWay,
    NotifyOnValidationError = true,
    ValidatesOnExceptions = true
};

switch(prop.ParameterType)
{
    case PropertyType.TextBox:
        TextBox box = new TextBox
                          {
                              TextAlignment = TextAlignment.Left,
                              TextWrapping = TextWrapping.Wrap,
                              HorizontalAlignment = HorizontalAlignment.Stretch,                                          
                          };
        box.SetBinding(TextBox.TextProperty, dbBinding);
        retVal = box;
        break;

Note that I am still databind the control to my own property, so as the user interacts with the UI, we will still get appropriate updates. The property itself exposes a method that returns an XElement fragment, so that when we save, we simply walk the object graph and append the fragments until we have the original XML document we began with to post back to the server (of course, there are some optimizations, like only sending values that changed - this is done with an IsDirty flag on the property).

The only caveat so far is that the content container needed to set HorizontalContentAlignment, something that threw me off for a bit until I realized what the property does. Note the convention for setting the binding: we set the binding on the control, but then pass the "owner" of the binding, which in this case is the TextBox class itself. That's the way those dependency properties work.

What was more interesting was generating a combo box. The combo box shows a nice enumeration of values (key/value) but the value on my property is just a simple value. Therefore, having the combo box bind properly meant having a value converter. When attaching value converters dynamically, a few things to keep in mind ...

When you specify the converter in your XAML with a key, what really happens is a single instance of the converter is created and referenced by the key. Whenever you reference the converter, you are referencing the same instance and the framework handles pulling your properties/bindings and passing them into the converter.

To follow this pattern, instead of creating a new instance every time I spin off a combo box, I have a single instance in my control converter:

...
private static readonly ParameterEnumConverter _parameterEnumConverter = new ParameterEnumConverter();
...

Then the converter is simply set to the binding:

...
dbBinding.Converter = _parameterEnumConverter;
...

In this case, I'm going to bind two items to the ComboBox - first, the binding to update my actual property (the dbBinding you saw earlier), and second, the binding for the source collection of items. I happen to hold those items in a property called Enumerations, so the second binding looks like this:

Binding comboBinding = new Binding("Enumerations") { Mode = BindingMode.OneWay };
// set the actual item binding
comboBox.SetBinding(Selector.SelectedItemProperty, dbBinding); 
// set the drop down list binding 
comboBox.SetBinding(ItemsControl.ItemsSourceProperty, comboBinding); 

In both cases I'm traversing up the tree to where the properties are derived, so instead of passing the ComboBox property, its the Selector (which the combo uses for the selected item behavior) and the ItemsControl (which the combo inherits from).

Likewise, the check box would bind to ToggleButton.

Once all of the magic is done, the items emit into the control container and become happy, active members of the Silverlight application. Thanks to databinding, all I have to do is plug into a save event and grab my root object to parse out the changes.

Jeremy Likness