TextBox LostFocus is not raised when clicking on certain other controls
The following behavior will fix this:
public class TextBoxUpdateOnLostKeyboardFocusBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
if (AssociatedObject != null)
{
base.OnAttached();
AssociatedObject.LostKeyboardFocus += OnKeyboardLostFocus;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.LostKeyboardFocus -= OnKeyboardLostFocus;
base.OnDetaching();
}
}
private void OnKeyboardLostFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null && e.NewFocus == null)
{
// Focus on the closest focusable ancestor
FrameworkElement parent = (FrameworkElement) textBox.Parent;
while (parent is IInputElement && !((IInputElement) parent).Focusable)
{
parent = (FrameworkElement) parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent);
}
}
}
You can attach it to your TextBox as follows:
<TextBox>
<i:Interaction.Behaviors>
<behaviors1:TextBoxUpdateOnLostKeyboardFocusBehavior />
</i:Interaction.Behaviors>
</TextBox>
Your attached property makes a couple of assumptions:
- that nothing is depending on the distinction between the
LostKeyboardFocus
andLostFocus
events - that the bindings on the element it's attached to will actually respond to the
LostFocus
event (they could have UpdateSourceTrigger.Explicit)
Instead, you could enumerate the bindings on the element and directly call UpdateSource:
private void CommitBindings(DependencyObject element) {
var localValueEnumerator = element.GetLocalValueEnumerator();
while (localValueEnumerator.MoveNext()) {
var entry = localValueEnumerator.Current;
if (BindingOperations.IsDataBound(element, entry.Property)) {
var bindingExpression = (BindingExpressionBase)entry.Value;
bindingExpression.UpdateSource();
}
}
}
Also, instead of handling each TextBox
individually, you could handle the container and use OldFocus to get the actual element that lost keyboard focus.
In this case the text box doesn't actually loose logical focus and so the event is never raised - essentially I actually want the LostKeyboardFocus
event, and not the LostFocus
event to trigger the update.
This issue is similar to WPF: Data bound TabControl doesn't commit changes when new tab is selected and there is a Microsoft connect item for it here with a number of potential solutions, however I fixed this using an attached property like so.
public static readonly DependencyProperty BindOnLostKeyboardFocusProperty =
DependencyProperty.RegisterAttached("BindOnLostKeyboardFocus", typeof(bool), typeof(MainWindow), new PropertyMetadata(default(bool), BindOnLostKeyboardFocusChanged));
private static void BindOnLostKeyboardFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var control = o as UIElement;
if (control != null)
{
if ((bool) e.NewValue)
{
control.AddHandler(LostKeyboardFocusEvent, new RoutedEventHandler(ControlLostKeyboardFocus));
}
else
{
control.RemoveHandler(LostKeyboardFocusEvent, new RoutedEventHandler(ControlLostKeyboardFocus));
}
}
}
private static void ControlLostKeyboardFocus(object sender, RoutedEventArgs e)
{
var control = (UIElement)sender;
control.RaiseEvent(new RoutedEventArgs(LostFocusEvent));
}
This just means that whenever LostKeyboardFocus
is raised for that control, it goes ahead and raises an additional LostFocus
event causing the binding to update. Its used like so
<TextBox Text="{Binding Test}" LostKeyboardFocus="UIElement_OnLostKeyboardFocus" local:MainWindow.BindOnLostKeyboardFocus="True" />