Tuesday, January 8, 2008

How can I update a text box on Enter and LostFocus?

In WPF, it is easier to learn databinding than it is to write volumes and volumes of code to update properties on your UI. If you're brand-new to databinding, I've got a getting started guide to WPF.

Today's challenge is letting the Enter key update the value of a databound text box. I've cracked open Visual Studio 2008, started a new WPF application and in Window1.xaml



To illustrate we'll use a Border and a TextBox. The TextBox will set the Width of the Border by databinding to it. Here's the relevant XAML (layout goop omitted):

    <TextBox
    Text="{Binding ElementName=_sampleBorder,Path=Width,Mode=TwoWay}"
    />
    <Border Name="_sampleBorder"
    Background="Blue" Width="10" Height="50"
    />


    ElementName
    We want to bind to the Border, so we give it a name "_sampleBorder". This can be refered to in the Binding using the ElementName syntax.

    Path
    We want to specifically bind to the Width property on the Border.

    Mode
    We want changes in the TextBox.Text to update the Border.Width, and vice versa - if somehow the Border.Width gets changed we want that to be reflected in the TextBox. To accomplish this, we set the Mode to TwoWay.


Build and run... no worky!

The Border.Width (10) shows up in the TextBox.Text property but you can't change it!Why? The default databinding mode is to update on TextBox.LostFocus. From a user expierience, this typically means only the Tab key or clicking somewhere else would update the value. In this example, we don't have another control that takes focus, so the UI doesn't update.

UpdateSourceTrigger, not the solution

We can change how often the binding automatically updates using the UpdateSourceTrigger parameter. UpdateSourceTrigger by is set to LostFocus by default. Other options include updating 'Explicit'ly and on 'PropertyChanged'.

'PropertyChanged' is usually not a great option for a text box, as people frequently make typing mistakes and the feedback is often too distracting (for those of us who aren't too busy looking at the keyboard while they type.) ;-)


Having the LostFocus cake and updating 'Explicit'ly too
Since there's isn't a 'LostFocusOrEnterKeyPressed', I kept the default 'LostFocus' behavior and manually called UpdateSource on the binding when the enter key was pressed.



    <TextBox
    Text="{Binding ElementName=_sampleBorder,Path=Width,Mode=TwoWay}"
    KeyDown="TextBox_KeyDown"
    />

    // look for the enter key
    private void TextBox_KeyDown(object sender, KeyEventArgs e) {
    TextBox textBox = sender as TextBox;
    if (e.Key == Key.Enter && textBox != null) {
    // fish out <TextBox Text={Binding ... }" and update the source of the binding
    BindingExpression binding = textBox.GetBindingExpression(TextBox.TextProperty);
    if (binding != null) {
    binding.UpdateSource();
    }
    }
    }


If you have more than one textbox, this gets a bit tedious. Fortunately, using routed events, you can use the AddEventHandler syntax to get all the KeyDown events for all textboxes in your window.



    public Window1() {
    InitializeComponent();
    this.AddHandler(TextBox.KeyDownEvent, new RoutedEventHandler(TextBox_KeyDown));
    }

    // look for the enter key
    private void TextBox_KeyDown(object sender, RoutedEventArgs e) {
    TextBox textBox = e.OriginalSource as TextBox;
    KeyEventArgs args = e as KeyEventArgs;

    if (args != null && args.Key == Key.Enter && textBox != null) {
    // fish out <TextBox Text={Binding ... }" and update the source of the binding
    BindingExpression binding = textBox.GetBindingExpression(TextBox.TextProperty);
    if (binding != null) {
    binding.UpdateSource();
    }
    }
    }



There's more than one way to accomplish most things in WPF; you could also apply a style to the TextBoxes you want to have this behavior...

     
    <Style TargetType="{x:Type TextBox}">
    <EventSetter Event="KeyDown" Handler="TextBox_KeyDown"/>
    </Style>



...or create a commanding solution, using FocusManager to get the focused TextBox...

... but the fundamental of calling binding.UpdateSource would likely re-appear in most solutions.

Source: [xaml cs ]

No comments: