Bind TextBox on Enter-key press

109,721

Solution 1

You can make yourself a pure XAML approach by creating an attached behaviour.

Something like this:

public static class InputBindingsManager
{

    public static readonly DependencyProperty UpdatePropertySourceWhenEnterPressedProperty = DependencyProperty.RegisterAttached(
            "UpdatePropertySourceWhenEnterPressed", typeof(DependencyProperty), typeof(InputBindingsManager), new PropertyMetadata(null, OnUpdatePropertySourceWhenEnterPressedPropertyChanged));

    static InputBindingsManager()
    {

    }

    public static void SetUpdatePropertySourceWhenEnterPressed(DependencyObject dp, DependencyProperty value)
    {
        dp.SetValue(UpdatePropertySourceWhenEnterPressedProperty, value);
    }

    public static DependencyProperty GetUpdatePropertySourceWhenEnterPressed(DependencyObject dp)
    {
        return (DependencyProperty)dp.GetValue(UpdatePropertySourceWhenEnterPressedProperty);
    }

    private static void OnUpdatePropertySourceWhenEnterPressedPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = dp as UIElement;

        if (element == null)
        {
            return;
        }

        if (e.OldValue != null)
        {
            element.PreviewKeyDown -= HandlePreviewKeyDown;
        }

        if (e.NewValue != null)
        {
            element.PreviewKeyDown += new KeyEventHandler(HandlePreviewKeyDown);
        }
    }

    static void HandlePreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            DoUpdateSource(e.Source);
        }
    }

    static void DoUpdateSource(object source)
    {
        DependencyProperty property =
            GetUpdatePropertySourceWhenEnterPressed(source as DependencyObject);

        if (property == null)
        {
            return;
        }

        UIElement elt = source as UIElement;

        if (elt == null)
        {
            return;
        }

        BindingExpression binding = BindingOperations.GetBindingExpression(elt, property);

        if (binding != null)
        {
            binding.UpdateSource();
        }
    }
}

Then in your XAML you set the InputBindingsManager.UpdatePropertySourceWhenEnterPressedProperty property to the one you want updating when the Enter key is pressed. Like this

<TextBox Name="itemNameTextBox"
         Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"
         b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="TextBox.Text"/>

(You just need to make sure to include an xmlns clr-namespace reference for "b" in the root element of your XAML file pointing to which ever namespace you put the InputBindingsManager in).

Solution 2

This is how I solved this problem. I created a special event handler that went into the code behind:

private void TextBox_KeyEnterUpdate(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        TextBox tBox = (TextBox)sender;
        DependencyProperty prop = TextBox.TextProperty;

        BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
        if (binding != null) { binding.UpdateSource(); }
    }
}

Then I just added this as a KeyUp event handler in the XAML:

<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />

The event handler uses its sender reference to cause it's own binding to get updated. Since the event handler is self-contained then it should work in a complex DataTemplate. This one event handler can now be added to all the textboxes that need this feature.

Solution 3

I don't believe that there's any "pure XAML" way to do what you're describing. You can set up a binding so that it updates whenever the text in a TextBox changes (rather than when the TextBox loses focus) by setting the UpdateSourceTrigger property, like this:

<TextBox Name="itemNameTextBox"
    Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />

If you set UpdateSourceTrigger to "Explicit" and then handled the TextBox's PreviewKeyDown event (looking for the Enter key) then you could achieve what you want, but it would require code-behind. Perhaps some sort of attached property (similar to my EnterKeyTraversal property) woudld work for you.

Solution 4

You could easily create your own control inheriting from TextBox and reuse it throughout your project.

Something similar to this should work:

public class SubmitTextBox : TextBox
{
    public SubmitTextBox()
        : base()
    {
        PreviewKeyDown += new KeyEventHandler(SubmitTextBox_PreviewKeyDown);
    }

    void SubmitTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            BindingExpression be = GetBindingExpression(TextBox.TextProperty);
            if (be != null)
            {
                be.UpdateSource();
            }
        }
    }
}

There may be a way to get around this step, but otherwise you should bind like this (using Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />

Solution 5

If you combine both Ben and ausadmin's solutions, you end up with a very MVVM friendly solution:

<TextBox Text="{Binding Txt1, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
    <TextBox.InputBindings>
        <KeyBinding Gesture="Enter" 
                    Command="{Binding UpdateTextBoxBindingOnEnterCommand}"
                    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

...which means you are passing the TextBox itself as the parameter to the Command.

This leads to your Command looking like this (if you're using a DelegateCommand-style implementation in your VM):

    public bool CanExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        return true;
    }

    public void ExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        TextBox tBox = parameter as TextBox;
        if (tBox != null)
        {
            DependencyProperty prop = TextBox.TextProperty;
            BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
            if (binding != null) 
                binding.UpdateSource();
        }
    }

This Command implementation can be used for any TextBox and best of all no code in the code-behind though you may want to put this in it's own class so there are no dependencies on System.Windows.Controls in your VM. It depends on how strict your code guidelines are.

Share:
109,721

Related videos on Youtube

Jobi Joy
Author by

Jobi Joy

VP of Engineering www.identitymine.com We build experiences on all platforms #Windows #Xbox #Windows10 #HTML #WPF #XAML #WinUI #xamarin (iOS and Android)

Updated on July 08, 2022

Comments

  • Jobi Joy
    Jobi Joy almost 2 years

    The default databinding on TextBox is TwoWay and it commits the text to the property only when TextBox lost its focus.

    Is there any easy XAML way to make the databinding happen when I press the Enter key on the TextBox?. I know it is pretty easy to do in the code behind, but imagine if this TextBox is inside some complex DataTemplate.

  • Mikhail Poda
    Mikhail Poda over 12 years
    In WPF/Silverlight you should never use inheritance - it messes styles and is not as flexible as Attached Behaviors. For example with Attached Behaviors you can have both Watermark and UpdateOnEnter on the same textbox.
  • Syaiful Nizam Yahya
    Syaiful Nizam Yahya over 11 years
    i tried <TextBox x:Name="itemNameTextBox" Text="{Binding Source, ElementName=webBrowserCustom, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed=‌​"TextBox.Text"/> but it seems webbrowser doesnt update its source. anything i missed? i didnt handle any event. i assume by binding 2 way, its auto handle event. am i wrong? thanks.
  • ihake
    ihake almost 10 years
    In an effort to save future readers a few minutes of fiddling, I just used this in my project, and the sample XAML provided above didn't work right. The source was updated on every character change. Changing "UpdateSourceTrigger=PropertyChanged" to "UpdateSourceTrigger=Explicit" fixed the issue. Now it all works as desired.
  • VoteCoffee
    VoteCoffee over 9 years
    @ihake: I think your recommended change will also prevent updating on focus lost though
  • VoteCoffee
    VoteCoffee over 9 years
    Nice. Very clean. If a multibinding is involved, you may need to use BindingOperations.GetBindingExpressionBase(tBox, prop); and then binding.UpdateTarget();
  • j riv
    j riv almost 9 years
    Something tells me this is an underrated post. A lot of answers are popular because they are "XAML-y". But this one appears to save space in most occasions.
  • David Hollinshead
    David Hollinshead almost 9 years
    UpdateSourceTrigger=PropertyChanged may be inconvenient when typing a real number. For example "3." causes a problem when bound to a float. I'd recommend not specifying UpdateSourceTrigger (or setting to LostFocus) in addition to the attached behaviour. This gives the best of both worlds.
  • Jamin
    Jamin over 8 years
    +1 This also allows you to leave alone UpdateSourceTrigger in case you have already painstakingly got your TextBox bindings to already behave the way you want (with styles, validation, twoway, etc.), but currently just won't receive input after pressing Enter.
  • Nicholas Miller
    Nicholas Miller over 8 years
    Excellent work! Tiny nit-pick: When the UpdatePropertySourceWhenEnterPressed changes from a valid value to a different valid value, you are unsubscribing & re-subscribing to the PreviewKeyDown event unnecessarily. Instead, all you should need is to check whether or not the e.NewValue is null or not. If not null, then subscribe; otherwise, if null, then unsubscribe.
  • Nicholas Miller
    Nicholas Miller over 8 years
    Another thing, I've noticed that calling binding.UpdateSource() clears validation errors. I needed to check first if the update should occur by looking at BindingExpression.HasError.
  • sth_Weird
    sth_Weird over 8 years
    This answer may be simpler than the one marked as answer, but it has some limitations. Image you want to do some kind of check in the set-function of the bound property (checking if the input is valid). You do not want to call that check function after every key the user pressed, do you (especially not when the function takes some time).?
  • Oystein
    Oystein over 6 years
    This is the cleanest approach that I've found, and in my opinion should be marked as the answer. Added an additional null check on sender, a check on Key.Return (Enter key on my keyboard returns Key.Return), and Keyboard.ClearFocus() to remove focus from the TextBox after updating the source property. I've made edits to the answer that's awaiting peer review.
  • Nik
    Nik about 6 years
    I love this solution, thanks for posting it! For anyone who needs to attach this behaviour to numerous TextBoxes in your application, I posted an extension to this answer on how you can achieve that very easily. See the post here
  • Allender
    Allender about 5 years
    Agree with comments above. But for me KeyDown event was more appropriate
  • CAD bloke
    CAD bloke almost 5 years
    I used KeyBinding in the XAML to fire this method because my UI has a "Default" control that catches the enter key. You need to catch it within this TextBox to stop it propagating up the UI tree to the "Default" control.
  • Ben
    Ben almost 5 years
    NOTE for UWP projects: the binding has be obtained from the textbox itself, tBox.GetBindingExpression(props) because for some reason they left out the static method.
  • hfann
    hfann over 3 years
    This works well in a dialog situation when a button has IsDefault property set to true.
  • Joe
    Joe over 3 years
    This could also be even simpler if you had a default style for TextBox with an EventSetter that directed all KeyUp events (or KeyDown if you prefer) to the handler that's listed. If the style were in a ResourceDictionary, you could just make some code-behind for the dictionary. Then you would not even need to specify the KeyUp/KeyDown handler on each TextBox