33

I have some textboxes where I would like focus to behave a little differently than normal for a WPF application. Basically, I would like them to behave more like a textbox behaves on a webpage. That is, if I click anywhere outside of the textbox, it will lose its focus. What is the best way to do so?

If the answer is to programmatically remove focus, what is the best way to detect a Mouseclick outside of the bounds? What if the element I'm clicking on will be the new recipient of focus?

JacobJ
  • 3,677
  • 3
  • 28
  • 32
  • If you're clicking on a different UI element, the textbox should automatically lose focus. – Merlyn Morgan-Graham Jun 27 '11 at 05:52
  • 7
    @Merlyn Morgan-Graham: No, It doesn't. – love Computer science Jun 27 '11 at 05:58
  • @Charlie: My comment was in response to "What if the element I'm clicking on will be the new recipient of focus?" I agree that if the user clicks just anywhere outside the box (e.g. the Window) it won't raise the `LostFocus` event, but another focusable element (e.g. another text box), it will. – Merlyn Morgan-Graham Jun 27 '11 at 06:51
  • @Merlyn Morgan-Graham: Agree, and its an obvious thing that if you click on another text box, previous will lose focus but thats not the case here OP wants it to lose focus by clicking anywhere out side textBox. – love Computer science Jun 27 '11 at 06:56

14 Answers14

34

Rather than adding new control to window, I think you should give your Grid a name and react to the MouseDown event on your window, moving the focus to the Grid itself. Something like this:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" 
    Height="412" Width="569" 
    MouseDown="Window_MouseDown" 
    Name="window1">

    <Grid ShowGridLines="False" 
          Background="#01FFFFFF"
          KeyDown="Grid_KeyDown" 
          Name="grid1" 
          Focusable="True">

          <TextBox Width="120" Margin="117,61,0,0" 
                   Name="textBox1" 
                   VerticalAlignment="Top" 
                   HorizontalAlignment="Left"/>
    </Grid>
</Window>

code behind:

private void window1_MouseDown(object sender, MouseButtonEventArgs e)
{
    grid1.Focus();
}
Scott Solmer
  • 3,871
  • 6
  • 44
  • 72
love Computer science
  • 1,824
  • 4
  • 20
  • 39
  • 1
    Ah, this worked. To expand on the solution, there is no need to name the window either - just override OnMouseDown for a slightly cleaner bit of XAML. – JacobJ Jun 27 '11 at 06:54
  • 2
    @JacobJ: oops.. what i wanted to write was add name to your 'grid' not window. Edited. thanks for pointing out! – love Computer science Jun 27 '11 at 07:00
  • 1
    I tried it out too. Not sure why, but this isn't working for me. `LostFocus` isn't getting called for the text boxes I have in my window. – Merlyn Morgan-Graham Jun 27 '11 at 07:01
  • 2
    @ Merlyn Morgan-Graham: In simple words, what you need to do is, Add Name Property to your grid, make grid focusable, on mouse down event of window: set focus to that grid. is that what you have tried? – love Computer science Jun 27 '11 at 07:08
  • 1
    @Charlie: "make grid focusable" is the part I missed. Thanks! +1 – Merlyn Morgan-Graham Jun 27 '11 at 07:25
  • 1
    make sure your grid has *non-transparent* background color, else it cannot respond to mousedown event. – user3162662 May 16 '16 at 08:25
  • You can also to omit the name of the grid: `((sender as Window).Content as Grid).Focus();`. – Kassi Oct 15 '20 at 09:48
  • 1
    @user3162662: The background color doesn't need to be non-transparent. But it needs to be specified (i.e. not null) for the mousedown event to be fired. You can use `Background="Transparent"` and the event will fire. – birgersp Aug 22 '21 at 09:38
  • 1
    Unfortuntely, this will only be of limited use when you have the `TextBox` contained inside a `UserControl`. The "lose focus" functionality is then limited to the area of the user control. – Felix May 17 '22 at 11:24
  • This doesn't work when you have other controls in your window that handle the click, because then the click event won't bubble up to the window. So in most cases, this is a limited solution. – Josh Noe Jun 01 '22 at 18:01
19

I think, better way to solve this problem is adding MouseDown event handler to window with code behind:

private void window_MouseDown(object sender, MouseButtonEventArgs e)
{
    Keyboard.ClearFocus();
}
NightEagle
  • 211
  • 2
  • 8
  • 1
    I don't have much knowledge regarding this, but won't this also clear the focus when pressing on the textfield itself? – Anyone Jan 05 '14 at 21:17
  • No, because if you click on textbox, the click event is handled by textbox and it is not routed to window. Therefore Windows.MouseDown is never called when you click on textbox. – Liero Mar 19 '15 at 13:08
  • 1
    This prevents the `LostFocus` event from being called on the text box. – skiwi Jul 01 '16 at 21:04
  • 4
    I know I'm a little late, but this is (approximately) the solution I went with for this very same issue I just wanted to reply to the thing @skiwi presented. I was able to get around this by grabbing `Keyboard.FocusedElement` before clearing focus, then doing `element.RaiseEvent(new RoutedEventArgs(UIElement.LostFocusEvent))` -- I had to do this because my TextBox's border depends on focus state. – Brandon Hood Feb 14 '17 at 19:54
4

Another way that worked for me was using

Mouse.AddPreviewMouseDownOutsideCapturedElementHandler

For example, say you had a TextBlock that when clicked, should become editable by showing a focused TextBox. Then when the user clicked outside the TextBox, it should be hidden again. Here's how you can do it:

private void YourTextBlock_OnMouseDown(object sender, MouseButtonEventArgs e)
{
    YourTextBox.Visibility = Visibility.Visible;
    YourTextBox.Focus();
    CaptureMouse();
    Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnMouseDownOutsideElement);
}

private void OnMouseDownOutsideElement(object sender, MouseButtonEventArgs e)
{
    Mouse.RemovePreviewMouseDownOutsideCapturedElementHandler(this, OnMouseDownOutsideElement);
    ReleaseMouseCapture();
    YourTextBox.Visibility = Visibility.Hidden;
}
Mr. Bungle
  • 1,696
  • 17
  • 21
  • Not working unless having a `MessageBox.Show` within `YourTextBlock_OnMouseDown`. `CaptureMouse` returns true and `OnMouseDownOutsideElement` won't be called. – Meow Cat 2012 Jul 31 '19 at 13:07
  • This might work under certain situations, but I found it extremely finicky. YMMV. – Josh Noe Jun 01 '22 at 18:51
  • I liked the approach, because in contrast to the other answers it doesn't require having control over the element receiving the click. But, unfortunately, it also fired when I clicked _in_ the TextBox which lead to annoying side effects, because the event handler was implemented to handle events _outside_. And I found no way - within the handler - to distinguish between the two scenarios.., – mike Jan 29 '23 at 00:29
  • 1
    Another good approach can be found here: https://stackoverflow.com/a/39275232 – mike Feb 01 '23 at 11:30
4

To avoid code behind you can use this Behavior The behavior

 public class ClearFocusOnClickBehavior : Behavior<FrameworkElement>
 {
    protected override void OnAttached()
    {
        AssociatedObject.MouseDown += AssociatedObject_MouseDown;
        base.OnAttached();
    }

    private static void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        Keyboard.ClearFocus();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
    }
}

Useing in XAML:

On any element outside the text box that you want him to clear the focus on click add:

    <i:Interaction.Behaviors>
        <behaviors:ClearFocusOnClickBehavior/>
    </i:Interaction.Behaviors>
shmoltz
  • 146
  • 2
4

It is important to mention that a textbox catches both the mouse and the keyboard events, so in order to move or remove the focus from a text box you have to release these 2 elements.

Let's start with the keyboard, Keyboard.ClearFocus(); will remove the keyboard focus from the textbox, and it's great for hiding the blinking cursor, but won't move the focus from the textbox, in other words the textbox will remain as focused element but without displaying the cursor. You can check that by printing FocusManager.GetFocusedElement(this); after Keyboard.ClearFocus();.

So, if you have a LostFocus event attached to the textbox or similar event, it will not be triggered because the element has not yet lost focus.

PreviewMouseDown += (s, e) => FocusManager.SetFocusedElement(this, null);
PreviewMouseDown += (s, e) => Keyboard.ClearFocus();

As a solution, you should set the Window focused element to null, by calling: System.Windows.Input.FocusManager.SetFocusedElement(this, null); Now if you are wondering if we can get rid of the Keyboard.ClearFocus(); then the answer is no, because we are going to remove the mouse focus, but the keyboard focus will still be there. (you will notice the blinking cursor which is still there, and you will be able to type some text in the textbox).

ynsbl.eng
  • 142
  • 2
  • 8
  • 1
    While this code snippet may be the solution, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-‌​code-based-answers) helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Ian Campbell Sep 25 '20 at 03:28
  • This is an important point and should be upvoted! – Wachid Susilo Jan 17 '23 at 08:38
3

I have tried the selected answer in react native WPF application but it was not triggering lost focus of textbox due to which textbox was not losing the focus. Following solution work for me.

Bound a mouse-down event to Window:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="412" Width="569" MouseDown="window_MouseDown" Name="window1" >
    <Grid ShowGridLines="False" KeyDown="Grid_KeyDown" Name="grid1" Focusable="True">
          <TextBox HorizontalAlignment="Left" Margin="117,61,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
    </Grid>
</Window>

and event is:

private void window_MouseDown(object sender, MouseButtonEventArgs e)
{
    TextBox textBox = Keyboard.FocusedElement as TextBox;
    if (textBox != null)
    {
        TraversalRequest tRequest = new TraversalRequest(FocusNavigationDirection.Next);
        textBox.MoveFocus(tRequest);
    }
}
Zaheer Ahmed
  • 28,160
  • 11
  • 74
  • 110
1

I was running into a similar issue, but when I wrapped a ScrollViewer control around my textboxes , all the textboxes would automatically lose focus when clicking anywhere outside the texboxes.

Jaswir
  • 172
  • 11
  • I think you mean wrapping a scrollviewer around your textboxes not the other way around. Suggested an edit. Wrapping a scrollviewer around my textbox solved the issue – Jaswir Jun 03 '19 at 12:04
1

I'm not 100% sure, but if you set Focusable to true on the container element (Grid, StackPanel, etc) then it should take the focus away from the text box.

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • I tried this before asking the question, and it didn't quite do it. In addition to setting Focusable on the container element, I needed to actually manually set focus on mousedown as per Charlie Sharma's solution. – JacobJ Jun 27 '11 at 06:55
1

If you clicked on the element, which can grab the focus, you get what you need. if, for example, you have some panel, you can handle panel's mouseClick event to achive your needs, or use Richard Szalay advice.

stukselbax
  • 5,855
  • 3
  • 32
  • 54
0

You can register the PreviewMouseLeftButtonDownEvent to every type of element in App.xaml.cs on application startup.

EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewMouseLeftButtonDownEvent,
                new MouseButtonEventHandler(FocusElement));

private void FocusElement(object sender, MouseButtonEventArgs e)
{
     var parent = e.OriginalSource as DependencyObject;
     if (parent is UIElement element && !element.Focusable)
     {
         element.Focusable = true;
         element.Focus();
         element.Focusable = false;
     }
}
Krauz
  • 1
0

This could be done with a small amount of code. Just set the window's Focusable="True" where the textboxes or other controls are placed and add this on it's MouseDown event:

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
     FocusManager.SetFocusedElement(this, this);
}
-1

you cant explicitly loose the focus of a control

you can set the focus to other control instead

**txt.Focusable=true;
label.focus();
Keyboard.Focus(txtPassword);**

try this

-1

Another approach, you can use the IsKeyboardFocusedChanged event:

MyTextBox.IsKeyboardFocusedChanged += MyTextBox_IsKeyboardFocusedChanged;

private void MyTextBox_IsKeyboardFocusedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (Convert.ToBoolean(e.NewValue))
    {
        // it's focused
    }
    else
    {
        // it's not focused
    }
}    
Mayer Spitz
  • 2,577
  • 1
  • 20
  • 26
-1
public class ClearFocusOnOutsideClickBehavior : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {

        AssociatedObject.GotFocus += AssociatedObjectOnGotFocus;
        AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
        base.OnAttached();
    }

    private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs e)
    {
        App.Current.MainWindow.MouseUp -= _paren_PreviewMouseUp;
    }

    private void AssociatedObjectOnGotFocus(object sender, RoutedEventArgs e)
    {
        App.Current.MainWindow.MouseUp += _paren_PreviewMouseUp;
    }

    private void _paren_PreviewMouseUp(object sender, MouseButtonEventArgs e)
    {
        Keyboard.ClearFocus();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.GotFocus -= AssociatedObjectOnGotFocus;
        AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;
    }
}

Using in XAML:

<TextBox Height="30" Width="200">
            <i:Interaction.Behaviors>
                <behaviours:ClearFocusOnOutsideClickBehavior/>
            </i:Interaction.Behaviors>
 </TextBox>
  • This is only going to work in a very specific context, where your Behavior has access to this unexplained `App.Current.MainWindow`. – Hugh W Feb 19 '23 at 11:46