Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding with UpdateSourceTrigger==LostFocus do not fire for Menu or Toolbar interaction

I noticed that bindings with UpdateSourceTrigger==LostFocus do not get updated when the user activates the menu or the toolbar.

This leads to the unfortunate situation that the last change that the user made gets lost when the user selects "Save File" from the menu or toolbar.

Is there an easy way around this or do I have to change all my bindings to UpdateSourceTrigger=PropertyChanged.

like image 909
thumbmunkeys Avatar asked Apr 12 '11 06:04

thumbmunkeys


4 Answers

While there are useful answers here, IMHO none are really the best way. For me, the best and second-best options are:

  1. Fix the tab order so that there are focusable elements in the same focus scope as the TextBox both before and after the TextBox in tab order. This is IMHO the best option, but it should be used only when this is a reasonable user interface choice. I.e. there already are UI elements that would naturally surround the TextBox.
  2. Handle the LostKeyboardFocus event and explicitly update the binding then. Note that while the TextBox hasn't lost focus in terms of the focus scope, it does always lose the keyboard focus if you tab out of it.

The second option looks like this:

<TextBox Text="{Binding SomeProperty}" LostKeyboardFocus="TextBox_LostKeyboardFocus"/>
private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    BindingOperations.GetBindingExpression((DependencyObject)sender, TextBox.TextProperty)?.UpdateSource();
}

To elaborate:

The accepted answer defers updating the bound source property until some specific scenario (such as executing a "Save" command) that is known to need the property value occurs. Unfortunately, this negates one key benefits of the MVVM paradigm, which is one doesn't need to worry about when things happen per se, since the binding engine is supposed to take care of everything.

The highest-voted answer improves on this slightly, but as noted in the comments below it, it's still a scenario-specific solution. It would need to be applied for every element in the UI that has its own focus scope. But even worse is that it actually modifies the UI behavior for an element that is otherwise completely unrelated to the element we actually care about. Good coding practices mean fixing the original problem, rather than applying some unrelated change that just happens to have a side-effect that works in our favor.

If one can simply arrange the tab order in the UI such that there is an element in the same focus scope as the TextBox that immediately follows the TextBox, then IMHO that'd be the ideal solution. It means that the UI works predictably for the user and the code is aligned with the simplest implementation possible.

But this may not always be possible. In some cases, the natural tab order demands that the TextBox is immediately preceded or followed by a Menu, ToolBar, or other element that is its own focus scope. In such cases, to me the most direct approach is simply to modify the "focus lost" behavior for the binding by explicitly handling the LostKeyboardFocus event and updating the binding source when that event happens.

like image 133
Peter Duniho Avatar answered Dec 04 '22 18:12

Peter Duniho


I know this is a bit old, but for any future reader, simply setting the following on my ToolBar worked for me:

FocusManager.IsFocusScope="False"
like image 41
dotNET Avatar answered Dec 04 '22 19:12

dotNET


The problem is that the TextBox does, in fact, not lose focus when the menu item is activated. Thus, the UpdateSourceTrigger LostFocus does not fire. Depending on your (view)model, UpdateSourceTrigger PropertyChanged might or might not be a feasible workaround.

For me, PropertyChanged was not an option (I need to validate the data after the user finished entering it, not in between), so I used a workaround by calling this method before "Save File" (or any other menu/toolbar entry that requires an up-to-date model):

Public Shared Sub SaveFocusedTextBox()
    Dim focusedTextBox = TryCast(Keyboard.FocusedElement, TextBox)
    If focusedTextBox IsNot Nothing Then
        Dim be = focusedTextBox.GetBindingExpression(TextBox.TextProperty)
        If be IsNot Nothing Then be.UpdateSource()
    End If
End Sub

A few other approaches for this problem can be found in this related question:

  • WPF Databind Before Saving

(In fact, credit for this method goes to rudigrobler's answer in that question.)

like image 28
Heinzi Avatar answered Dec 04 '22 17:12

Heinzi


This works well for me:

Private Sub MenuItem_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)

  Keyboard.FocusedElement.RaiseEvent(New RoutedEventArgs With {.RoutedEvent = LostFocusEvent})

End Sub
like image 38
Bazhosh Avatar answered Dec 04 '22 18:12

Bazhosh