无法将焦点设置到UserControl的子元素

39
我有一个包含TextBoxUserControl. 当我的主窗口加载时,我想将焦点设置到这个TextBox上。所以我在UserControl的定义中添加了Focusable="True" GotFocus="UC_GotFocus",并在主窗口的定义中添加了FocusManager.FocusedElement="{Binding ElementName=login}". 在UC_GotFocus方法中,我只需要在要聚焦的控件上调用.Focus(), 但是这并没有生效。
我只需要让应用程序启动时UserControl中的TextBox得到焦点。
感谢提供任何帮助。
18个回答

28

最近我解决了一个问题,即通过故事板显示登录闪屏,当主窗口首次加载时。我认为解决这个问题有两个关键点。一是将包含元素设置为焦点范围。另一个是处理由加载窗口触发的故事板完成事件。

该故事板使用户名和密码画布可见,然后渐变为100%不透明。关键在于,在故事板运行之前,用户名控件是不可见的,因此该控件无法获得键盘焦点直到它可见。让我感到困惑一段时间的是,它具有“焦点”(即焦点为true),但事实证明这只是逻辑焦点,并且我不知道WPF有逻辑焦点和键盘焦点的概念,直到阅读了Kent Boogaart的答案并查看了Microsoft的WPF 链接文本

一旦我做到了这一点,我的特定问题的解决方案就很简单:

1) 将包含元素设置为焦点范围

<Canvas FocusManager.IsFocusScope="True" Visibility="Collapsed">
    <TextBox x:Name="m_uxUsername" AcceptsTab="False" AcceptsReturn="False">
    </TextBox>
</Canvas>

2) 将已完成的事件处理程序附加到Storyboard。

    <Storyboard x:Key="Splash Screen" Completed="UserNamePassword_Storyboard_Completed">
...
</Storyboard>

并且

3) 在故事板完成事件处理程序中将我的用户名文本框设置为键盘焦点。

void UserNamePassword_Storyboard_Completed(object sender, EventArgs e)
{
 m_uxUsername.Focus();
}
请注意,调用 item.Focus() 会导致调用 Keyboard.Focus(this),因此您不需要显式地调用此方法。有关 Keyboard.Focus(item) 和 item.Focus 之间的区别,请参见此问题:什么是 Keyboard.Focus(item) 和 item.Focus 之间的区别?

1
谢谢!在我的特定情况中,涉及到一个包含TextBox的UserControl需要初始焦点,我使用了UserControl的Loaded事件并输入textBox.Focus();。我不需要第一步(IsFocusScope=true)。 - Anders

18

虽然很愚蠢,但它有效:

弹出一个线程等待一段时间,然后返回并设置你想要的焦点。即使在元素主机的上下文中也可以工作。

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{

 System.Threading.ThreadPool.QueueUserWorkItem(
                   (a) =>
                        {
                            System.Threading.Thread.Sleep(100);
                            someUiElementThatWantsFocus.Dispatcher.Invoke(
                            new Action(() =>
                            {
                                someUiElementThatWantsFocus.Focus();

                            }));
                        }
                   );

}

我同意这不是我喜欢使用的东西,但目前为止我找到的最强大的机制。有些情况下,uiElement.Focus() 会返回 false,比如竞争条件、CommandBindings 等等。如果我所知道的只是最终应该能够聚焦,那么我现在确实使用了它,因为在运行时尝试所有其他方法太耗时了(为此编写单元测试很困难)。尽管如此,我仍不建议将其作为最佳实践,而是作为最后手段。 - Simon D.
嘿,老兄,我偷了你的解决方案并在这里使用了它:http://stackoverflow.com/questions/7520945/textbox-embedded-in-a-listbox-initial-focus 希望你不介意:D - Davide Piras
运行良好,是一个简单的答案。 - Patrick
重新启动一个旧的解决方案,但我最近遇到了这个问题,这是对我有效的方法。 - iamchrisfryer
在尝试了其他四个顶级解决方案之后,唯一帮助我的东西。 - Riva

7
在应用程序启动时设置初始焦点时,接收焦点的元素必须连接到PresentationSource,并且该元素的Focusable和IsVisible属性必须设置为true。推荐的设置初始焦点的位置是在Loaded事件处理程序中。只需在Window(或Control)的构造函数中添加“Loaded”事件处理程序,在该事件处理程序中调用目标控件的Focus()方法即可。
    public MyWindow() {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MyWindow_Loaded);
    }

    void MyWindow_Loaded(object sender, RoutedEventArgs e) {
        textBox.Focus();
    }

7
最近,我有一个列表框,其中包含一些文本块。我想双击文本块后,它能变成一个文本框,然后聚焦并选择所有文本,以便用户可以直接开始输入新名称(类似于Adobe图层)。
无论如何,我用事件来实现这个功能,但它就是不起作用。对我而言,最重要的是确保将事件标记为已处理。我认为它确实设置了焦点,但是一旦事件走完路径,它就会切换逻辑焦点。
故事的寓意是,确保将事件标记为已处理,这可能是你遇到的问题。

1
谢谢,这个提示让我摆脱了困境。将鼠标按下事件标记为已处理!我在用户控件的鼠标按下事件中调用了this.Focus()。我看到GotFocus立即被触发,然后是LostFocus,焦点转移到了我的用户控件的父级。原因?我没有标记鼠标按下事件已处理,所以我猜父控件在继续处理我的鼠标按下事件后,将焦点从我身上取走了! - dthorpe

5

自从我尝试了Fuzquat的解决方案并发现它是最通用的,我想分享一个不同版本的解决方案,因为有些人抱怨它看起来很凌乱。所以这里是:

                casted.Dispatcher.BeginInvoke(new Action<UIElement>(x =>
                {
                    x.Focus();
                }), DispatcherPriority.ApplicationIdle, casted);

没有Thread.Sleep,也没有ThreadPool。希望足够简洁。

更新:

由于人们似乎更喜欢漂亮的代码:

public static class WpfExtensions
{
    public static void BeginInvoke<T>(this T element, Action<T> action, DispatcherPriority priority = DispatcherPriority.ApplicationIdle) where T : UIElement
    {
        element.Dispatcher.BeginInvoke(priority, action);
    }
}

现在你可以这样调用它:
child.BeginInvoke(d => d.Focus());

3

1
请清理一下,所有链接都已失效。 - Wouter
那个人的博客似乎已经消失了。我搜索了一下,但没有找到它是否已经迁移到其他地方。抱歉!我回答那个问题已经快10年了! - Ashley Davis

3
  1. 将您的用户控件设置为Focusable =“True”(XAML)
  2. 处理您的控件上的GotFocus事件并调用yourTextBox.Focus()
  3. 处理窗口上的Loaded事件并调用yourControl.Focus()

我正在打字时使用此解决方案运行示例应用程序。如果这对您不起作用,则必须有某些特定于您的应用程序或环境的问题。在您最初的问题中,我认为绑定是导致问题的原因。 希望这可以帮助您。


1
这种方法的缺点是,即使在切换之前它不在那里,当从另一个窗口切换回来时,焦点也会放在你的文本框上。 - Ilya Serbis

3

WPF支持两种不同的焦点类型:

  1. 键盘焦点
  2. 逻辑焦点

FocusedElement 属性可以在焦点范围内获取或设置逻辑焦点。我怀疑你的 TextBox 已经有了逻辑焦点,但它所在的焦点范围不是活动焦点范围。因此,它没有键盘焦点。

那么问题是,你的可视树中是否有多个焦点范围?


嗯,我对焦点范围一无所知。是时候进行一些研究了!祝好,肯特。 - EightyOne Unite
我可能错了,但我记得在某个地方读到过UserControls有自己的隐式FocusScope。也许如果这是真的,那么原帖提问者在他们的可视树中是否有多个焦点范围的答案显然是肯定的! - jpierson

2

在经历了“WPF初始焦点噩梦”并参考了一些Stack的答案后,我发现以下方法对我来说是最好的解决方案。

首先,在你的App.xaml文件的OnStartup()方法中添加以下内容:

EventManager.RegisterClassHandler(typeof(Window), Window.LoadedEvent,
          new RoutedEventHandler(WindowLoaded));

然后在 App.xaml 中添加“WindowLoaded”事件:

void WindowLoaded(object sender, RoutedEventArgs e)
    {
        var window = e.Source as Window;
        System.Threading.Thread.Sleep(100);
        window.Dispatcher.Invoke(
        new Action(() =>
        {
            window.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));

        }));
    }

由于一些框架竞争条件,WPF初始焦点通常会失败,因此必须使用线程问题。

我发现以下解决方案最佳,因为它在整个应用程序中全局使用。

希望有所帮助...

Oran


2

我把fuzquat的答案转化为一个扩展方法。在Focus()无法工作的情况下,我使用这个方法。

using System;
using System.Threading;
using System.Windows;

namespace YourProject.Extensions
{
    public static class UIElementExtension
    {
        public static void WaitAndFocus(this UIElement element, int ms = 100)
        {
            ThreadPool.QueueUserWorkItem(f =>
            {
                Thread.Sleep(ms);

                element.Dispatcher.Invoke(new Action(() =>
                {
                    element.Focus();
                }));
            });
        }
    }
}

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