WPF - 单击文本框外部时移除焦点

34

我有一些文本框,希望WPF应用程序中的焦点行为与正常行为略有不同。基本上,我希望它们的行为更像网页上的文本框。也就是说,如果我在文本框之外的任何地方单击,它将失去焦点。最好的方法是什么?

如果答案是通过编程方式删除焦点,那么检测范围之外的鼠标单击的最佳方法是什么?如果我点击的元素将成为新的焦点接收者怎么办?


如果您点击其他的UI元素,文本框应该自动失去焦点。 - Merlyn Morgan-Graham
7
不,它没有。 - love Computer science
@Charlie:我的评论是针对“如果我点击的元素将成为新的焦点接收者怎么办?” 我同意,如果用户只是在框外的任何地方(例如窗口)单击,它不会引发“LostFocus”事件,但如果有另一个可聚焦的元素(例如另一个文本框),它会引发该事件。 - Merlyn Morgan-Graham
@Merlyn Morgan-Graham:同意,如果您单击另一个文本框,先前的文本框将失去焦点,这是显而易见的,但在这种情况下,OP希望通过单击文本框外的任何位置来使其失去焦点。 - love Computer science
14个回答

34

与其向窗口添加新控件,我认为你应该给你的 Grid 命名,并在窗口上响应 MouseDown 事件,将焦点移动到 Grid 本身。类似于这样:

<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>

后端代码:

private void window1_MouseDown(object sender, MouseButtonEventArgs e)
{
    grid1.Focus();
}

2
@JacobJ:哎呀,我想写的是将名称添加到你的“网格”而不是窗口。已编辑。感谢指出! - love Computer science
2
@ Merlyn Morgan-Graham:简而言之,您需要做的是,向网格添加名称属性,使其可聚焦,在窗口的鼠标按下事件中:将焦点设置为该网格。这是您尝试过的吗? - love Computer science
1
@Charlie:我错过了“使网格可聚焦”的部分。谢谢!+1 - Merlyn Morgan-Graham
2
请确保您的网格具有非透明背景颜色,否则它将无法响应mousedown事件。 - user3162662
2
@user3162662:背景颜色不需要是非透明的。但是必须指定(即非空),才能触发mousedown事件。您可以使用Background="Transparent",事件将会被触发。 - birgersp
显示剩余5条评论

19

我的想法是,更好的解决此问题的方法是在窗口中添加MouseDown事件处理程序,代码如下:

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

1
我对此并没有太多的了解,但是当在文本字段上按下时,这也不会清除焦点吗? - Anyone
不可以,因为如果你点击文本框,点击事件会被文本框处理,而不会路由到窗口。因此,当你点击文本框时,Windows.MouseDown 永远不会被调用。 - Liero
1
这将防止在文本框上调用“LostFocus”事件。 - skiwi
4
我知道我有点晚,但这大概是我用来解决同样问题的解决方案,我只是想回复一下@skiwi提出的东西。在清除焦点之前,我能够通过获取Keyboard.FocusedElement,然后执行element.RaiseEvent(new RoutedEventArgs(UIElement.LostFocusEvent))来绕过这个问题 - 我不得不这样做,因为我的文本框的边框取决于焦点状态。 - Brandon Hood

4
为了避免代码后台,您可以使用此行为。该行为
 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;
    }
}

在XAML中使用:

在您希望在单击时清除其焦点的文本框外任何元素上添加以下内容:

    <i:Interaction.Behaviors>
        <behaviors:ClearFocusOnClickBehavior/>
    </i:Interaction.Behaviors>

4

我发现另一种方法是使用Mouse.AddPreviewMouseDownOutsideCapturedElementHandler

比如,假设你有一个元素,当用户点击它时,应该出现聚焦的元素,以实现编辑功能。当用户再次在外面单击时,这个将被隐藏。以下是实现的代码:

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;
}

只有在YourTextBlock_OnMouseDown中使用MessageBox.Show才能正常工作。 CaptureMouse返回true,OnMouseDownOutsideElement将不会被调用。 - Meow Cat 2012
这在某些情况下可能有效,但我发现它非常敏感。你的情况可能会有所不同。 - Josh Noe
我喜欢这个方法,因为与其他答案不同,它不需要控制接收单击的元素。但是,不幸的是,当我点击TextBox时,它也会触发,导致令人讨厌的副作用,因为事件处理程序被实现为处理外部事件。而且我没有找到在处理程序内区分两种情况的方法... - mike
1
另一个好的方法可以在这里找到:https://dev59.com/2mkv5IYBdhLWcg3w3Uc0#39275232 - mike

4

需要注意的是,文本框可以捕获鼠标和键盘事件,因此为了移动或删除文本框的焦点,必须释放这两个元素。

首先来说键盘,Keyboard.ClearFocus();将从文本框中移除键盘焦点,并且它可以很好地隐藏闪烁的光标,但不会移动文本框的焦点,换句话说,文本框仍然保持为焦点元素,但没有显示光标。您可以在Keyboard.ClearFocus();之后打印FocusManager.GetFocusedElement(this);来检查。

所以,如果您已经将LostFocus事件或类似事件附加到文本框上,因为该元素尚未失去焦点,所以该事件不会被触发。

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

作为解决方案,您应该通过调用以下代码将窗口聚焦元素设置为null:System.Windows.Input.FocusManager.SetFocusedElement(this, null); 现在,如果您想知道我们是否可以摆脱Keyboard.ClearFocus();,答案是否定的,因为我们将移除鼠标焦点,但键盘焦点仍然存在。(您会注意到文本框中仍有闪烁的光标,并且您仍然可以输入一些文本)。

1
虽然这段代码片段可能是解决方案,但包括解释有助于提高您的帖子质量。请记住,您正在回答未来读者的问题,而这些人可能不知道您提出代码建议的原因。 - Ian Campbell
这是一个重要的观点,应该被点赞! - Wachid Susilo

3

我尝试了在React Native WPF应用程序中选择的答案,但它没有触发文本框的失去焦点事件,因此文本框没有失去焦点。以下解决方案适用于我。

将鼠标按下事件绑定到窗口:

<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>

事件是:

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);
    }
}

1
这真的很好用,让我省了很多的烦恼。谢谢! - tbarela

1

我不是100%确定,但如果您在容器元素(Grid、StackPanel等)上将Focusable设置为true,则应该会将焦点从文本框中移开。


在提问之前,我尝试过这个方法,但它并没有完全解决问题。除了在容器元素上设置Focusable之外,根据Charlie Sharma的解决方案,我还需要在mousedown事件中手动设置焦点。 - JacobJ

1
如果您点击了可以获取焦点的元素,则会得到所需内容。例如,如果您有一个面板,可以处理面板的mouseClick事件来实现您的需求,或者使用Richard Szalay的建议。

1
我遇到了类似的问题,但是当我在文本框周围包裹一个ScrollViewer控件时,所有的文本框都会在单击文本框外的任何位置时自动失去焦点。

我想你的意思是在文本框周围包裹一个滚动条,而不是反过来。建议进行编辑。在我的文本框周围包裹一个滚动条解决了这个问题。 - Jaswir

0

这可以用很少的代码完成。只需在文本框或其他控件所放置的窗口中设置窗口的 Focusable="True",并在其 MouseDown 事件上添加以下内容:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接