Click here to Skip to main content
15,881,812 members
Articles / Programming Languages / C#

Silverlight Behaviors and Triggers: TextBox Magic

Rate me:
Please Sign up or sign in to vote.
4.59/5 (9 votes)
7 Oct 2009CPOL3 min read 36.5K   18   8
The TextBox control is popular in Silverlight, but comes with a few nuances. Learn how to filter textboxes and force databinding with attached behaviors.

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 she/he 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.

C#
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:

C#
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:

XML
<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:

C#
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:

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

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

Jeremy Likness

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Program Manager Microsoft
United States United States
Note: articles posted here are independently written and do not represent endorsements nor reflect the views of my employer.

I am a Program Manager for .NET Data at Microsoft. I have been building enterprise software with a focus on line of business web applications for more than two decades. I'm the author of several (now historical) technical books including Designing Silverlight Business Applications and Programming the Windows Runtime by Example. I use the Silverlight book everyday! It props up my monitor to the correct ergonomic height. I have delivered hundreds of technical presentations in dozens of countries around the world and love mentoring other developers. I am co-host of the Microsoft Channel 9 "On .NET" show. In my free time, I maintain a 95% plant-based diet, exercise regularly, hike in the Cascades and thrash Beat Saber levels.

I was diagnosed with young onset Parkinson's Disease in February of 2020. I maintain a blog about my personal journey with the disease at https://strengthwithparkinsons.com/.


Comments and Discussions

 
SuggestionA ready madee filter class Pin
jamalnsr16-May-12 19:53
jamalnsr16-May-12 19:53 
GeneralMy vote of 5 Pin
Howard Richards20-Sep-11 4:05
Howard Richards20-Sep-11 4:05 
GeneralMy vote of 5 Pin
vav9-Mar-11 18:47
vav9-Mar-11 18:47 
GeneralExcellent Pin
limbejeff17-Aug-10 9:45
limbejeff17-Aug-10 9:45 
GeneralThank You Pin
TomSavage14-Jul-10 4:03
TomSavage14-Jul-10 4:03 
GeneralSource Code Download Pin
VBTechman29-Oct-09 4:57
VBTechman29-Oct-09 4:57 
GeneralMy vote of 1 Pin
Member 30186427-Oct-09 4:48
Member 30186427-Oct-09 4:48 
GeneralRe: My vote of 1 Pin
Member 280362825-Nov-09 22:04
Member 280362825-Nov-09 22:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.