14

is there a way to update a binding before or when a command is triggered? I have some text fields I can edit and save using a command, accessible via a keyboard shortcut. As the binding is usually only updated when the text field loses focus, the last change is not kept when pressing the key to save the data. Instead I have to tab out of the text field first to make it update and then save it.

Is there a way to force the update in an elegant way? I am using MVVM (but not any MVVM framework), so I’d like to keep UI specific things out of the command code. Also I don’t really want to change the binding to update on every change, it’s fine to have it update only when the focus is lost.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
poke
  • 369,085
  • 72
  • 557
  • 602

7 Answers7

11

Rather than changing focus you could also just update the binding source if the current element is a TextBox. You could do something similar for other controls but in my experience I've only had this problem with TextBox.

  // if the current focused element is textbox then updates the source.     
    var focusedElement = Keyboard.FocusedElement as FrameworkElement;
    
    if (focusedElement is TextBox)
    {
       var expression = focusedElement.GetBindingExpression(TextBox.TextProperty);
        if (expression != null) expression.UpdateSource();
    }
Ashiquzzaman
  • 5,129
  • 3
  • 27
  • 38
Kai G
  • 3,371
  • 3
  • 26
  • 30
  • Good idea, where would you put that though in a MVVM setup? — Also didn’t know that there actually is an `UpdateSource` method on the binding expression, thanks for that! – poke Jul 12 '12 at 08:40
  • I think you'd have to put it in your attached property - just figured it's an improvement on advancing focus. You could also look at using something like CompositeCommand from the Enterprise Library to handle this functionality, but it really depends on how your application is structured. – Kai G Jul 12 '12 at 11:47
  • This looks like an elegant solution. However, it doesn't work when you click a menu item. The focused element is the menu item. Though, the text box still keeps focus as well (the LostFocus event is not raised). That is frustrating. – Jiří Skála Jul 02 '21 at 14:44
  • To my previous comment: It is only true if the menu item is checkable. – Jiří Skála Jul 02 '21 at 15:00
5

On your TextBox, you need to set the UpdateSourceTrigger on your text binding which defines when the source will be updated with the textbox value. By default it's LostFocus for the Text property, which is exactly what's happening - it only updates the source when it loses focus. You should set the value of UpdateSourceTrigger to PropertyChanged and it will update each time the textbox value changes.

e.g.

<TextBox ... Text="{Binding Foo, UpdateSourceTrigger=PropertyChanged}"/>

Path is the default property when using the Binding command, so the above is equal to Path=Foo

user3791372
  • 4,445
  • 6
  • 44
  • 78
  • 1
    From the question: *“Also I don’t really want to change the binding to update on every change”*. – poke Jan 15 '16 at 19:56
  • Updating the binding on PropertyChanged doesn't work well, e.g., when entering a decimal number. Say you want to enter "1.1". As you type "1.", the binding gets updated and the text box content changes to "1". – Jiří Skála Jul 02 '21 at 13:17
2

Here is the class I used for a similar situation:

public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.TextChanged += TextBox_TextChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.TextChanged -= TextBox_TextChanged;
    }

    void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
        bindingExpression.UpdateSource();
    }
}

XAML:

<TextBox Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
    <i:Interaction.Behaviors>
        <h:TextBoxUpdatesTextBindingOnPropertyChanged />
    </i:Interaction.Behaviors>
</TextBox>

Calling OnPropertyChanged("YourPropertyName") will alert the view to update the bound value.

Otherwise check out this answer: WPF TextBox DataBind on EnterKey press.

Community
  • 1
  • 1
Steve Konves
  • 2,648
  • 3
  • 25
  • 44
  • The problem is the not the binding from data→view, it’s the other way around. I want to force an update on the data side. – poke Jun 18 '12 at 15:26
  • Ok, the link I posted shows how to create a behavior which will update the binding (on the data side) when a certain event is thrown on the Control. If you wire it up to the KeyPressed or KeyUp events, you should get the effect you need. – Steve Konves Jun 18 '12 at 15:29
  • That solution includes setting the `UpdateSourceTrigger` to `PropertyChanged` which should already be enough but is, as said in the question, not what I want. I’m looking for a way to do this without changing the trigger. – poke Jun 18 '12 at 15:33
  • I added a class I use to my original answer. – Steve Konves Jun 18 '12 at 15:42
  • +1, I use code very similar to the [attached behavior](http://stackoverflow.com/questions/563195/wpf-textbox-databind-on-enterkey-press) that you linked at the bottom of your answer. It updates the source whenever the Enter key is pressed, and to update the source from the code, just raise the Property Changed event, such as `OnPropertyChanged("PropertyName")` – Rachel Jun 18 '12 at 18:06
  • Uhm… as I said, I do not want to have to change the binding. And also as I said before, setting the `UpdateSourceTrigger` to `PropertyChanged` already updates the binding with every change that is made to the text field, so your class is not only quite redundant but also bloats the binding in a very unnecessary way. – poke Jun 18 '12 at 20:55
  • Based on the default behavior of a TextBox.Text Binding, your notification property will not raise the PropertyChanged event until AFTER the change is completed, that is, when the TextBox looses focus. If you don't want to change the binding, you are either going to have to handle every change (eg. listen for TextChanged with a custom behavior or code behind) or don't call the command until after the TextBox looses focus, thereby pushing the TextBox.Text value back to your property. TextBox.Text, by default, raises PropertyChanged differently that other controls' dependency properties. – Steve Konves Jun 18 '12 at 21:34
  • Yes, I know that, which is why I was asking for an elegant MVVM-way to achieve this (the focus losing). – poke Jun 19 '12 at 07:42
  • look at my solution, this did not break mvvm and its works like a charme :) – blindmeis Jun 19 '12 at 14:02
2

I have solved this now by explicitely setting the focus to some other element before asking for the values. This obviously makes the element that currently has the focus lose it and update the binding.

To set the focus, I have written an attached property, inspired by answers on other questions. Also, together with my other question I made this somewhat automated.

So to use it, I basically attach my property to an element, in this case a tab control:

<TabControl c:Util.ShouldFocus="{Binding ShouldClearFocus}">

In my view model, I have a simple boolean property ShouldClearFocus that is a standard property raising a PropertyChangedEvent, so data binding works. Then I simply set ShouldClearFocus to true when I want to reset the focus. The attached property automatically sets the focus and resets the property value again. That way I can keep setting ShouldClearFocus without having to set it to false in between.

The attached property is a standard implementation with this as its change handler:

public static void ShouldFocusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    if (!(bool)e.NewValue || !(obj is FrameworkElement))
        return;

    FrameworkElement element = (FrameworkElement)obj;
    if (element.Focusable)
        element.Focus();

    // reset value
    BindingExpression bindingExpression = BindingOperations.GetBindingExpression(obj, ShouldFocusProperty);
    if (bindingExpression != null)
    {
        PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
        if (property != null)
            property.SetValue(bindingExpression.DataItem, false, null);
    }
    else
        SetShouldFocus(obj, false);
}
Community
  • 1
  • 1
poke
  • 369,085
  • 72
  • 557
  • 602
0

i use the following in my projects. the problem is that the button did not recieve the focus.

in my app.xaml.cs

    EventManager.RegisterClassHandler(typeof(Button), ButtonBase.ClickEvent, new RoutedEventHandler(ButtonClick));

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        if (sender != null && sender is Button)
        {
            (sender as Button).Focus();
        }
    }
blindmeis
  • 22,175
  • 7
  • 55
  • 74
  • That solution would look acceptable but I don’t have a button that is clicked. The command is invoked by a `KeyBinding`. – poke Jun 20 '12 at 10:39
0

I think the best way to do this is to set the binding's UpdateSourceTrigger=PropertyChanged, and then using Reactive Extensions to throttle the event stream. This way the property will only update after the user has stopped typing for a certain amount of time (1/2 a second is a good amount).

This is great because the property will not update all the time, but it will also update without needing the focus to change. Exactly what you want.

Here is some really useful information on Rx

There is an MVVM extensions library that has bindable IObservables here. The example they have here is doing exactly what I suggested.

You asked for elegance: here it is. And its not that much work either.

Jason Ridge
  • 1,868
  • 15
  • 27
0

Listening to command invocation on UI can be tricky (if not impossible as no answer managed it so far). Rather than hacky flipping attached properties, I would suggest using Messenger pattern such as the one in MvvmLight:

public class ForceUpdateBindingsMessage
{
    public ViewModelBase ViewModel { get; }

    public ForceUpdateBindingsMessage(ViewModelBase viewModel)
    {
        ViewModel = viewModel;
    }
}

// ViewModel
public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new RelayCommand(() =>
{
    // ...push binding updating responsibility to whoever listens to this (desperate) message
    MessengerInstance.Send(new ForceUpdateBindingsMessage(this));
    // the bindings should be up-to-date now        
}));

Now to the "UpdateSource" part, the ForceUpdateBindingsMessage should be handled by some parent control, I chose MainWindow so that the VeiwModel's bindings update everywhere while limiting the updates just to the properties of invoking ViewModel:

public MainWindow()
{
    InitializeComponent();
    Messenger.Default.Register<ForceUpdateBindingsMessage>(this, msg =>
    {
        UpdateBindingsOfViewModel(ViewModelBase viewModel);
    });
}

private void UpdateBindingsOfViewModel(ViewModelBase viewModel)
{
    foreach (var tb in this.GetChildren<TextBox>())
    {
        var binding = tb.GetBindingExpression(TextBox.TextProperty);
        if (binding?.DataItem == msg.ViewModel)
        {
            binding.UpdateSource();
        }
    }
}

where GetChildren<T> is the commonly used extension helper method:

public static IEnumerable<T> GetChildren<T>(this Visual parent) where T : Visual
{
    if (parent == null)
        yield break;

    var queue = new Queue<Visual>();
    queue.Enqueue(parent);

    while (queue.Count > 0)
    {
        var element = queue.Dequeue();
        if (element is T tchild)
            yield return tchild;

        int numVisuals = VisualTreeHelper.GetChildrenCount(element);
        for (int i = 0; i < numVisuals; i++)
        {
            if (VisualTreeHelper.GetChild(element, i) is Visual child)
            {
                queue.Enqueue(child);
            }
        }
    }
}
wondra
  • 3,271
  • 3
  • 29
  • 48