WPF:如何以编程方式从TextBox中移除焦点

117

我想要在我的WPF TextBox中添加一个简单的(至少我认为是简单的)行为。

当用户按下Escape键时,我希望他正在编辑的TextBox拥有它开始编辑时的文本,并且我想将焦点从TextBox中移除。

我没有任何问题来设置文本以便它在编辑开始时有价值。

问题在于如何移除元素的焦点。我不想将焦点移到任何其他组件,我只想让TextBox失去焦点。我需要创建一个不可见的元素来设置焦点吗,以便我的TextBox可以失去焦点?

11个回答

174

1
这正是我今晚正在寻找的! - Josh
9
有一个问题:当我在代码中使用Keyboard.ClearFocus()并且点击其他地方后,ListBox中的AutoCompleteTextBox无法失去焦点。这并不总是清除焦点。 - ANeves
3
“ClearFocus” 导致最近聚焦的控件不会触发“GotFocus”事件,但其他控件仍会触发该事件。这对我的自定义屏幕键盘来说是一个大问题。它确实会导致插入符号消失,这可能就是“键盘焦点”所涉及的所有内容。也许我更关心的是像“鼠标焦点”这样的东西。 - Grault
4
谢谢,Grault。我遇到了同样的问题。我想到的最好方法是将焦点转移到其他控件,使用other.Focus()实现。 - Tor Klingberg
7
@Grault 这只是清除键盘焦点,而不是逻辑焦点(它会触发“GotFocus”事件)。在您的程序中总会有具有逻辑焦点的元素。在清除键盘焦点之前,请使用“LostKeyboardFocus”事件或将焦点转移到另一个元素(这将随之移动逻辑焦点)。 - Chirimorin
显示剩余2条评论

61

我一直在使用的代码:

// Move to a parent that can take focus
FrameworkElement parent = (FrameworkElement)textBox.Parent;
while (parent != null && parent is IInputElement && !((IInputElement)parent).Focusable)
{
    parent = (FrameworkElement)parent.Parent;
}

DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent as IInputElement);

3
这段代码很棒,但是Keyboard.ClearFocus()会带来一些意外的副作用。 - patrick
4
@patrick,您指的是哪些意外副作用?能给出相关示例吗? - ANeves
@ANeves 虽然已经晚了两年,但是 ClearFocus 会导致最近获得焦点的控件不会触发 GotFocus 事件,而其他控件仍会触发。这对于我的自定义屏幕键盘来说是一个大问题。 - Grault
4
这是一个很好的解决方案。我也遇到了Keyboard.ClearFocus()的问题。在模态窗口中运行ClearFocus()时,将导致TextBox和Window都失去焦点,这意味着KeyDown事件不再发送到Window。相反,改为将焦点更改为父项(可能是Window),未来的KeyDown事件就不会丢失了。在我的实际示例中,Window正在查找“Key.Escape”并调用Close()。如果你在任何地方运行ClearFocus(),这将停止工作。 - Denis P
1
不幸的是,这个解决方案会导致在焦点父元素周围出现一个虚线轮廓。 - redcurry
显示剩余6条评论

40

因为以上回答都对我没有用,而被接受的答案仅适用于键盘焦点,所以我采取了以下方法:

// Kill logical focus
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(textBox), null);
// Kill keyboard focus
Keyboard.ClearFocus();

会导致逻辑和键盘焦点都失效。


2
非常感谢,我已经尝试了其他所有将焦点移至其他地方的方法,你的方法是有效的。 - Derf Skren

19

有点晚了,但这对我很有帮助,以下是内容。

自从.Net 3.0以来,FrameworkElement拥有一个MoveFocus函数,这对我起到了作用。


有关指令,请参考 -> https://msdn.microsoft.com/zh-cn/library/system.windows.frameworkelement.movefocus.aspx - Carter Medlin
请确保检查此方法的返回值。如果遍历遇到由控件组合定义的制表位,并且遍历请求未请求换行,则可能返回false。 - aderesh

9
您可以将焦点设置为可聚焦的祖先元素。即使文本框位于没有同一模板内的可聚焦祖先元素中,此代码也可以正常工作:
DependencyObject ancestor = textbox.Parent;
while (ancestor != null)
{
    var element = ancestor as UIElement;
    if (element != null && element.Focusable)
    {
        element.Focus();
        break;
    }

    ancestor = VisualTreeHelper.GetParent(ancestor);
}

6
据我所知,无法完全移除焦点。您的窗口中总会有某个元素拥有焦点。

5

对我来说,这很棘手,尤其是在使用LostFocus绑定时。 然而,我的解决方法是添加一个空标签并聚焦于它。

<Label Name="ResetFocusArea" Focusable="True" FocusVisualStyle="{x:Null}" />

...

OnKeyDown(object sender, RoutedEventArgs e)
{
  //if is Esc
  ResetFocusArea.Focus();
}

3

使用LPL的答案对我有用,但这也使我无法选择下拉菜单中的任何选项。为了解决这个问题,我添加了一个检查,以查看焦点元素是否是文本框。

在按下回车键时进行相同的检查,我的最终代码如下:

    public Menu()
    {
        InitializeComponent();
        this.PreviewMouseDown += PreviewMouseDownEventHandler;
        this.KeyDown += WindowKeyDownHandler;
    }
    void ClearFocus()
    {
        UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
        if (elementWithFocus is System.Windows.Controls.TextBox tb)
        {
            if (Keyboard.FocusedElement != null)
            {
                Keyboard.FocusedElement.RaiseEvent(new RoutedEventArgs(UIElement.LostFocusEvent));
                Keyboard.ClearFocus();
            }
        }
    }

    private void PreviewMouseDownEventHandler(object sender, MouseButtonEventArgs e)
    {
        ClearFocus();
    }
    private void WindowKeyDownHandler(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            ClearFocus();
        }
    }

有了这个,我就不需要为每个文本框添加一个失去焦点事件,它可以很容易地扩展到其他元素,并且不会破坏程序的其他部分的兼容性。

2
我的回答并没有直接回答上述问题,但我觉得它的措辞已经使它成为了关于程序化摆脱焦点的“问题”。一个常见的场景是用户能够在左键单击根控件(如窗口)的背景时清除焦点。

因此,为了实现这一点,您可以创建一个附加行为,将焦点切换到动态创建的控件(在我的情况下是空标签)。最好将此行为用于最高级别的元素,例如窗口,因为它会遍历其子元素以查找可以添加虚拟标签的面板。

public class LoseFocusOnLeftClick : Behavior<FrameworkElement>
{
    private readonly MouseBinding _leftClick;
    private readonly Label _emptyControl = new Label() { Focusable = true, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };

    public LoseFocusOnLeftClick()
    {
        _leftClick = new MouseBinding(new RelayCommand(LoseFocus), new MouseGesture(MouseAction.LeftClick));
    }

    protected override void OnAttached()
    {
        AssociatedObject.InputBindings.Add(_leftClick);
        AssociatedObject.Loaded += AssociatedObject_Loaded;
    }        

    protected override void OnDetaching()
    {
        AssociatedObject.InputBindings.Remove(_leftClick);
        AssociatedObject.Loaded -= AssociatedObject_Loaded;
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        AssociatedObject.Loaded -= AssociatedObject_Loaded;

        AttachEmptyControl();
    }

    private void AttachEmptyControl()
    {            
        DependencyObject currentElement = AssociatedObject;
        while (!(currentElement is Panel))
        {
            currentElement = VisualTreeHelper.GetChild(currentElement, 0);
        }

        ((Panel)currentElement).Children.Add(_emptyControl);
    }

    private void LoseFocus()
    {            
        _emptyControl.Focus();
    }
}

2
在Windows Phone开发中,我只需要在PhoneApplicationPage中使用Focus()或者this.Focus(),就可以轻松实现聚焦。

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