Не могу установить фокус на дочерний элемент UserControl

У меня есть UserControl который содержит TextBox, Когда мое главное окно загружается, я хочу установить фокус на это текстовое поле, поэтому я добавил Focusable="True" GotFocus="UC_GotFocus" к UserControlопределение и FocusManager.FocusedElement="{Binding ElementName=login}" к моему основному определению окон. в UC_GotFocus метод, который я просто называю .Focus() на контроле, на котором я хочу сосредоточиться, но это не работает.

Все, что мне нужно сделать, это иметь TextBox в UserControl получить фокус при запуске приложения.

Любая помощь будет оценена, спасибо.

18 ответов

Решение

Недавно я исправил эту проблему для заставки входа в систему, которая отображается через раскадровку при первой загрузке главного окна.

Я считаю, что было два ключа для исправления. Один из них заключался в том, чтобы сделать содержащий элемент фокусом. Другой должен был обработать событие Storyboard Completed для раскадровки, которая была вызвана загружаемым окном.

Эта раскадровка делает видимым холст имени пользователя и пароля, а затем становится на 100% непрозрачным. Ключевым моментом является то, что элемент управления именем пользователя не был виден до запуска раскадровки, и поэтому этот элемент управления не мог получить фокус клавиатуры, пока он не был виден. На некоторое время меня оттолкнуло то, что он имел "фокус" (то есть фокус был верным, но, как оказалось, это был только логический фокус), и я не знал, что в WPF была концепция как логического, так и клавишного фокуса, пока не прочитал книгу Кента Бугаарта ответ и глядя на текст ссылки Microsoft WPF

Как только я это сделал, решение моей конкретной проблемы было простым:

1) Сделайте содержащий элемент фокусом

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

2) Присоедините обработчик завершенных событий к раскадровке

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

а также

3) Установите мое имя пользователя TextBox, чтобы иметь фокус клавиатуры в обработчике событий.

void UserNamePassword_Storyboard_Completed(object sender, EventArgs e)
{
 m_uxUsername.Focus();
}

Обратите внимание, что вызов item.Focus() приводит к вызову Keyboard.Focus(this), поэтому вам не нужно вызывать это явно. См. Этот вопрос о разнице между Keyboard.Focus(item) и item.Focus.

Это глупо, но это работает:

Вытяните поток, который ждет некоторое время, затем возвращается и устанавливает желаемый фокус. Это даже работает в контексте хоста элемента.

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

                            }));
                        }
                   );

}

"При установке начального фокуса при запуске приложения элемент для получения фокуса должен быть подключен к PresentationSource, а для элемента должны быть установлены свойства Focusable и IsVisible, равные true. Рекомендуемое место для установки начального фокуса в обработчике событий Loaded " (MSDN)

Просто добавьте обработчик события "Loaded" в конструктор вашего окна (или элемента управления), и в этом обработчике вызова вызовите метод Focus() для целевого элемента управления.

    public MyWindow() {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MyWindow_Loaded);
    }

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

Совсем недавно у меня был список, в котором размещались некоторые текстовые блоки. Я хотел иметь возможность дважды щелкнуть текстовый блок и превратить его в текстовое поле, затем сфокусироваться на нем и выделить весь текст, чтобы пользователь мог просто начать вводить новое имя (сродни Adobe Layers)

Во всяком случае, я делал это с событием, и оно просто не работало. Волшебная пуля для меня здесь заключалась в том, чтобы убедиться, что я установил событие для обработки. Я полагаю, что это устанавливало фокус, но как только событие пошло по пути, оно переключало логический фокус.

Мораль этой истории в том, что вы отмечаете событие как обработанное, это может быть вашей проблемой.

Так как я попробовал решение 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());

Я нашел хорошую серию постов в блоге о фокусе WPF.

Все они хороши для чтения, но третья часть посвящена настройке фокуса на элемент пользовательского интерфейса в UserControl.

  1. Установите пользовательский элемент управления на Focusable = "True" (XAML)
  2. Обработайте событие GotFocus на вашем элементе управления и вызовите yourTextBox.Focus()
  3. Обработайте событие Loaded в вашем окне и вызовите yourControl.Focus()

У меня есть образец приложения, работающего с этим решением, когда я печатаю. Если это не работает для вас, должно быть что-то конкретное для вашего приложения или среды, которая вызывает проблему. В вашем первоначальном вопросе я думаю, что проблема связана с привязкой. Надеюсь, это поможет.

WPF поддерживает два разных вида фокуса:

  1. Фокус клавиатуры
  2. Логический фокус

FocusedElement свойство получает или устанавливает логический фокус в пределах области фокуса. Я подозреваю, что ваш TextBox имеет логический фокус, но содержащая его область фокусировки не является активной областью фокусировки. Ergo, у него нет фокусировки на клавиатуре.

Итак, вопрос в том, есть ли у вас несколько областей фокусировки в вашем визуальном дереве?

Что меня устроило, так это атрибут FocusManager.FocusedElement. Сначала я попытался установить его на UserControl, но это не сработало.

Поэтому я попытался поместить его в первый дочерний элемент UserControl:

<UserControl x:Class="WpfApplication3.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid FocusManager.FocusedElement="{Binding ElementName=MyTextBox, Mode=OneWay}">
    <TextBox x:Name="MyTextBox"/>
</Grid>

... и это сработало!:)

Я преобразовал ответ fuzquat в метод расширения. Я использую это вместо Focus(), где 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();
                }));
            });
        }
    }
}

После 'WPF Initial Focus Nightmare' и нескольких ответов на стек, следующее оказалось для меня лучшим решением.

Сначала добавьте свой App.xaml OnStartup() следующим образом:

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

Затем добавьте событие WindowLoaded также в App.xaml:

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 в основном не выполняется из-за некоторых условий гонки.

Я нашел следующее решение лучше всего, так как оно используется глобально для всего приложения.

Надеюсь, поможет...

Оран

Мне не нравятся решения с настройкой другой области вкладок для UserControl. В этом случае при навигации по клавиатуре у вас будет две разные вставки: в окне и в другой - внутри пользовательского элемента управления. Мое решение - просто перенаправить фокус с пользовательского контроля на внутренний дочерний контроль. Установите фокус на пользовательский элемент управления (потому что по умолчанию это false):

<UserControl ..... Focusable="True">

и переопределить обработчики событий фокуса в коде:

protected override void OnGotFocus(RoutedEventArgs e)
{
    base.OnGotFocus(e);
    MyTextBox.Focus();
}

protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
    base.OnGotKeyboardFocus(e);
    Keyboard.Focus(MyTextBox);
}

Я обратил внимание на проблему, связанную с размещением WPC UserControls внутри ElementHosts, которые содержатся в Form, которая задается как дочерний элемент MDI через свойство MdiParent.

Я не уверен, что это та же проблема, с которой сталкиваются другие, но вы углубляетесь в детали, перейдя по ссылке ниже.

Проблема с настройкой фокуса в пользовательском элементе управления WPF, размещенном в ElementHost в дочерней MDI-форме WindowsForms

Предполагая, что вы хотите установить фокус для текстового поля "Имя пользователя", пользователь может вводить текст каждый раз, когда он появляется.

В конструкторе вашего контроля:

this.Loaded += (sender, e) => Keyboard.Focus(txtUsername);

Я установил его в PageLoaded() или загрузил элемент управления, но затем я вызываю асинхронную службу WCF и делаю вещи, которые, кажется, теряют фокус. Я должен установить это в конце всего, что я делаю. Это нормально и все, но иногда я делаю изменения в коде, а потом забываю, что я также устанавливаю курсор.

Попробовав комбинации предложенных выше советов, я смог надежно назначить фокус нужному текстовому полю в дочернем элементе управления UserControl следующим образом. В основном, сфокусируйтесь на дочернем элементе управления, и пусть дочерний UserControl фокусируется на его TextBox. Оператор focus TextBox сам по себе вернул true, но не дал желаемого результата, пока пользовательский элемент управления также не получил фокус. Я должен также отметить, что UserControl не смог запросить фокус для себя и должен был быть предоставлен окном.

Для краткости я пропустил регистрацию событий Loaded в Window и UserControl.

Окно

private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
    ControlXYZ.Focus();
}

UserControl

private void OnControlLoaded(object sender, RoutedEventArgs e)
{
    TextBoxXYZ.Focus();
}

У меня есть пользовательский элемент управления - панель стека с двумя текстовыми полями. Текстовые поля были добавлены в конструктор, а не в XAML. Когда я пытаюсь сфокусировать первое текстовое поле, ничего не происходит. Siggestion с событием Loaded решает мою проблему. Просто вызвал control.Focus() в событии Loaded и прочем.

У меня была такая же проблема с настройкой фокуса клавиатуры на холст в пользовательском элементе управления WPF. Мое решение

  1. В XAML установите элемент в Focusable="True"
  2. В element_mousemove событие создать простую проверку:

    if(!element.IsKeyBoardFocused)
        element.Focus();
    

В моем случае все работает нормально.

Другие вопросы по тегам