TextBox LostFocus is not raised when clicking on certain other controls
Asked Answered
H

4

14

In the following simple WPF application a TextBox is set to update a property when the focus is lost from that control, like so

<DockPanel>
    <ToolBar DockPanel.Dock="Top">
        <Button>Test</Button>
    </ToolBar>
    <TextBox Text="{Binding MyString}" />
</DockPanel>

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
}

public string MyString
{
    get { return _myString; }
    set { _myString = value; }
}

However when I run this application, enter some text in the text box and then click on the "Test" button my breakpoint on the MyString property is not raised, also any event handler for the LostFocus event is not raised either. These events are only raised when focus is lost from the control via some other means (e.g. the window is closed).

This is causing problems for us as in reality the "Test" button contains logic which relies on the MyString property being updated.

How can I ensure that the LostFocus event is correctly raised and that the binding is updated as I click on the "Test" button? It looks like the problem is somehow caused by the use of the ToolBar, as replacing the ToolBar with a standard button does not result in this behaviour.

Hermy answered 4/6, 2013 at 11:39 Comment(0)
H
6

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" />
Hermy answered 4/6, 2013 at 11:58 Comment(2)
The link to the Microsoft connect is broken. In full Microsoft style the link above brings to a page that says : "After nearly 10 years, Microsoft Connect has been retired in favor of new tools and services". I couldn't find the document you referenced either googling the kewords and searching directly the microsoft issues site. Maybe someone can help.Shockheaded
This didn't work for me (neither did the behaviour by Matt Sinclair below). The only thing I can think of is it's because my TextBox resides within a user control (which is placed in the tab control). The following did work for me: https://mcmap.net/q/779726/-wpf-data-bound-tabcontrol-doesn-39-t-commit-changes-when-new-tab-is-selectedLazo
H
3

Your attached property makes a couple of assumptions:

  • that nothing is depending on the distinction between the LostKeyboardFocus and LostFocus 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.

Haugh answered 4/6, 2013 at 12:35 Comment(0)
U
1

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>
Unexpressed answered 2/10, 2015 at 11:2 Comment(0)
L
0

I had a very similar case where a TextBox in a Toolbar was not updating its binding source when the main OK button on the window was clicked. But if you tabbed out first, it would work.

In my case I was able to alter the binding to use UpdateSourceTrigger=PropertyChanged and the problem was solved.

This would cause a lot more binding updates than the default binding behavior, but in this case that was not a problem.

Lauretta answered 5/5, 2020 at 19:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.