WPF та початковий фокус


191

Здається, що коли програма WPF запускається, то на цьому нічого не зосереджено.

Це справді дивно. Усі інші рамки, якими я користувався, роблять саме те, що ви очікували: ставить початковий фокус на перший елемент управління в порядку вкладки. Але я підтвердив, що це WPF, а не лише моя програма - якщо я створю нове Вікно і просто поміщаю в нього TextBox і запускаю додаток, TextBox не фокусується, поки я не натискаю на нього або не натискаю Tab . Гидота.

Мій фактичний додаток складніше, ніж просто TextBox. У мене є кілька шарів UserControls в UserControls. Один з цих UserControls має Focusable = "True" та KeyDown / KeyUp оброблювачі, і я хочу, щоб він мав фокус, як тільки моє вікно відкриється. Я все ще дещо початківець з WPF, і мені не дуже пощастило з'ясувати, як це зробити.

Якщо я запускаю додаток і натискаю клавішу Tab, то фокус переходить до мого фокусируемого управління, і він починає працювати так, як мені хочеться. Але я не хочу, щоб мої користувачі мали натиснути Tab, перш ніж вони зможуть почати користуватися вікном.

Я розігрувався з FocusManager.FocusedElement, але я не впевнений, на якому контролі його встановити (вікно верхнього рівня? Батьків, яке містить фокусируемый елемент управління; сам фокусируемый елемент управління) або на що його встановити.

Що мені потрібно зробити, щоб мій глибоко вкладений елемент керування мав початковий фокус, як тільки відкриється вікно? Або ще краще, щоб сфокусувати перший фокусируемый елемент управління в порядку вкладки?

Відповіді:


165

Це також працює:

<Window FocusManager.FocusedElement="{Binding ElementName=SomeElement}">

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>

4
Я здивований, що я перший, хто прокоментував це. Мене розгубило, куди це пішло, бо це могло перейти майже до будь-якого контролю. Відповідаючи на це конкретне запитання, я думаю, що це піде у вікно, але ви можете прочитати зауваження на msdn.microsoft.com/en-us/library/…, щоб зрозуміти, як управління ви додаєте це до питань.
Joel McBeth

Я успішно використовував цей підхід на стекпанелі. Якщо вас цікавить, є приклад на stackoverflow.com/a/2872306/378115
Хуліо Нобре

Це працювало для мене набагато краще, ніж прийнята відповідь, тому що мені потрібно зосередити увагу на елементі, який знаходиться після першого.
Путердо Борато

163

У мене була яскрава ідея прокопати Reflector, щоб побачити, де використовується властивість Focusable, і знайшла шлях до цього рішення. Мені просто потрібно додати наступний код до конструктора мого Window:

Loaded += (sender, e) =>
    MoveFocus(new TraversalRequest(FocusNavigationDirection.First));

Це автоматично вибере перший елемент керування в порядку вкладки, тож це загальне рішення, яке повинно бути спроможне перенести у будь-яке вікно та просто працювати.


21
Додайте перетворіть це на поведінку. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf

6
@wekempf, я не був знайомий з ідеєю поведінки, але я вивчив це, і це зовсім не погана ідея. Якщо хтось (як я) ще не знайомий із прикріпленою поведінкою, ось пояснення: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Джо Уайт

1
Крім того, це працює, якщо потрібний елемент є UserControl, який містить фактичний фокусується елемент (навіть у глибокій ієрархії). Чудово!
Даніель Альбусхат

1
Чудова ідея, але іноді вона не спрацьовує, якщо контроль, який би прийняв фокус, є Button. Щоб виправити це, я перевертаю MoveFocusдзвінок над диспетчером за ContextIdleпріоритетом ( Backgroundабо вище не працює). Крім того, є FocusNavigationDirection.Firstте, що краще відповідає наміру і робить те ж саме в цьому випадку.
Антон Тихий

це має бути поведінка за замовчуванням! Юк (у оригінальному дописі) правильно!
NH.

61

На основі прийнятої відповіді, реалізованої як додана поведінка:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace UI.Behaviors
{
    public static class FocusBehavior
    {
        public static readonly DependencyProperty FocusFirstProperty =
            DependencyProperty.RegisterAttached(
                "FocusFirst",
                typeof(bool),
                typeof(FocusBehavior),
                new PropertyMetadata(false, OnFocusFirstPropertyChanged));

        public static bool GetFocusFirst(Control control)
        {
            return (bool)control.GetValue(FocusFirstProperty);
        }

        public static void SetFocusFirst (Control control, bool value)
        {
            control.SetValue(FocusFirstProperty, value);
        }

        static void OnFocusFirstPropertyChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            Control control = obj as Control;
            if (control == null || !(args.NewValue is bool))
            {
                return;
            }

            if ((bool)args.NewValue)
            {
                control.Loaded += (sender, e) =>
                    control.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            }
        }
    }
}

Використовуйте його так:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">

6
На мою думку, це далеко не найкраще рішення, яке я знайшов. Дякую!
Сіон

1
У цій відповіді є помилка в коді на виклик до DependencyProperty.RegisterAttached. Третім параметром має бути typeof(FocusBehavior), ні typeof(Control). Внесення цих змін не дозволить дизайнеру повідомити про властивість "FocusFirst", яка вже зареєстрована помилками "Control".
Тоні Вітабіле

@TonyVitabile Виправлено. Ви завжди можете редагувати та вдосконалювати відповіді, якщо зможете. :)
Мізіпзор

Не слід контролювати.Завантажений обробник подій буде скасовано під час вивантаження?
andreapier

@andreapier Ви могли б, якщо вам було байдуже, але пропуск скасування реєстрації не призведе до витоку пам'яті чи нічого. Вам потрібно хвилюватися лише про події, що спричиняють протікання пам’яті, якщо короткотривалий об’єкт має приєднаний метод до події на довгоживучому об’єкті. У цьому випадку термін експлуатації - це тривалість вікна, тож ви добре.
Джо Вайт


9

Якщо ця ж проблема була вирішена з простим рішенням: У головному вікні:

  <Window ....
        FocusManager.FocusedElement="{Binding ElementName=usercontrolelementname}"
         ... />

У контролі користувача:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }

3
Працює лише в тому випадку, якщо елемент керування знаходиться безпосередньо у вікні, а не якщо він вкладений у UserControl.
Джо Вайт

8

Після «Кошмару первинного фокусу WPF» та на основі деяких відповідей на стек, наступне для мене виявилося найкращим рішенням.

Спочатку додайте до свого 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 здебільшого не вдається через деякі рамкові умови змагань.

Я знайшов наступне рішення найкращим, оскільки воно використовується в усьому світі для всього додатка.

Сподіваюся, це допомагає ...

Оран


5
Використовуйте BeginInvokeзамість цього страшного Sleep(100)твердження.
l33t

8

Ви можете легко встановити набір керування як зосереджений елемент у XAML.

<Window>
   <DataGrid FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}">
     ...
   </DataGrid>
</Window>

Я ніколи не намагався встановити це в контролі користувача та побачити, чи працює це, але може.


Це звучить цікаво, тому що ви не маєте імена управління лише для проблеми фокусування. З іншого боку, мій тест із керуванням користувача не працював.
heringer

Це не дивує мене @heringer ... це було б як спробувати налаштувати фокус на <border> або подібний неінтерактивний елемент управління. Ви можете спробувати застосувати цей атрибут FocusedElement до інтерактивного елемента управління всередині usercontrol. Але це може бути не варіантом.
Саймон Гіллбі

Я використовував цей підхід на стекпанелі, щоб встановити, на якій дочірній кнопці я хотів зосередитись після завантаження форми. Велике спасибі
Хуліо Нобре

Будьте обережні, це може зробити прив'язки повністю порушеними. stackoverflow.com/questions/30676863 / ...
Der_Meister

2

Мінімальна версія відповіді Мізіпзора на C # 6+.

public static class FocusBehavior
{
    public static readonly DependencyProperty GiveInitialFocusProperty =
        DependencyProperty.RegisterAttached(
            "GiveInitialFocus",
            typeof(bool),
            typeof(FocusBehavior),
            new PropertyMetadata(false, OnFocusFirstPropertyChanged));

    public static bool GetGiveInitialFocus(Control control) => (bool)control.GetValue(GiveInitialFocusProperty);
    public static void SetGiveInitialFocus(Control control, bool value) => control.SetValue(GiveInitialFocusProperty, value);

    private static void OnFocusFirstPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var control = obj as Control;

        if (control == null || !(args.NewValue is bool))
            return;

        if ((bool)args.NewValue)
            control.Loaded += OnControlLoaded;
        else
            control.Loaded -= OnControlLoaded;
    }

    private static void OnControlLoaded(object sender, RoutedEventArgs e) => ((Control)sender).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}

Використовуйте у своєму XAML:

<Window local:FocusBehavior.GiveInitialFocus="True" />

1

Якщо ви схожі на мене, і ви використовуєте деякі рамки, які певним чином псують основну поведінку фокусування і роблять усі рішення вище невідповідними, ви все одно можете це зробити:

1 - Зверніть увагу на елемент, який отримує фокус (що б там не було!)

2 - Додайте це у свій код за xxx.xaml.cs

private bool _firstLoad;

3 - Додайте це до елемента, який отримує перший фокус:

GotFocus="Element_GotFocus"

4 - Додайте в код позаду метод Element_GotFocus та вкажіть елемент з ім'ям WPF, якому потрібен перший фокус:

private void Element_GotFocus(object sender, RoutedEventArgs e)
{
    if(_firstLoad)
    {
        this.MyElementWithFistFocus.Focus();
        _firstLoad = false;
    }
}

5 - Керуйте завантаженою подією

в XAML

Loaded="MyWindow_Loaded"   

в xaml.cs

private void MyWindow_Loaded(object sender, RoutedEventArgs e)
{
        _firstLoad = true;
        this.Element_GotFocus(null, null);
}

Сподіваюсь, це допоможе як крайнє рішення


0

Я також зіткнувся з тією ж проблемою. Я мав три текстові поля всередині контейнера полотна і хотів, щоб перше текстове поле було зосереджене, коли відкриється керування користувача. Код WPF дотримувався схеми MVVM. Я створив окремий клас поведінки для фокусування елемента і прив’язав його до моєї точки зору так.

Полотно коду поведінки

public  class CanvasLoadedBehavior : Behavior<Canvas>
{
    private Canvas _canvas;
    protected override void OnAttached()
    {
        base.OnAttached();
        _canvas = AssociatedObject as Canvas;
        if (_canvas.Name == "ReturnRefundCanvas")
        {

            _canvas.Loaded += _canvas_Loaded;
        }


    }

    void _canvas_Loaded(object sender, RoutedEventArgs e)
    {
        FocusNavigationDirection focusDirection = FocusNavigationDirection.Next;

        // MoveFocus takes a TraveralReqest as its argument.
        TraversalRequest request = new TraversalRequest(focusDirection);
        UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
        if (elementWithFocus != null)
        {
            elementWithFocus.MoveFocus(request);
        }

    }

}

Код для перегляду

<Canvas  Name="ReturnRefundCanvas" Height="200" Width="1466" DataContext="{Binding RefundSearchViewModel}">
                <i:Interaction.Behaviors>
                    <b:CanvasLoadedBehavior />
                </i:Interaction.Behaviors>
                <uc:Keyboard Canvas.Left="973" Canvas.Top="111" ToolTip="Keyboard" RenderTransformOrigin="-2.795,9.787"></uc:Keyboard>
                <Label  Style="{StaticResource Devlbl}" Canvas.Left="28" Content="Return and Refund Search" Canvas.Top="10" />
                <Image Height="30" Width="28" Canvas.Top="6" Canvas.Left="5" Source="pack://application:,,,/HomaKiosk;component/images/searchF.png">
                    <Image.OpacityMask>
                        <ImageBrush ImageSource="pack://application:,,,/HomaKiosk;component/images/searchF.png"/>
                    </Image.OpacityMask>
                </Image>

                <Separator Height="4" Canvas.Left="6" Margin="0" Canvas.Top="35" Width="1007"/>

                <ContentControl Canvas.Top="45" Canvas.Left="21"
                    ContentTemplate="{StaticResource ErrorMsg}"
                    Visibility="{Binding Error, Converter={c:StringNullOrEmptyToVisibilityConverter}}" 
                    Content="{Binding Error}" Width="992"></ContentControl>

                <Label  Style="{StaticResource Devlbl}" Canvas.Left="29" Name="FirstName" Content="First Name" Canvas.Top="90" />
                <wpf:AutoCompleteTextBox  Style="{StaticResource AutoComp}" Height="32" Canvas.Left="33" ToolTip="First Name"  Canvas.Top="120" Width="205"                     Padding="10,5" TabIndex="1001"
                    VerticalAlignment="Top"

                    Watermark=""
                    IconPlacement="Left"
                    IconVisibility="Visible"
                    Delay="100"

                    Text="{Binding FirstName, Mode=TwoWay, TargetNullValue=''}" 
                    Provider="{Binding FirstNameSuggestions}">
                    <wpf:AutoCompleteTextBox.ItemTemplate>
                        <DataTemplate>
                            <Border Padding="5">
                                <StackPanel Orientation="Vertical">
                                    <TextBlock Text="{Binding}"
                   FontWeight="Bold" />
                                </StackPanel>
                            </Border>
                        </DataTemplate>
                    </wpf:AutoCompleteTextBox.ItemTemplate>
                </wpf:AutoCompleteTextBox>

                <Label Style="{StaticResource Devlbl}" Canvas.Left="250" Content="Last Name" Canvas.Top="90" />
                <wpf:AutoCompleteTextBox  Style="{StaticResource AutoComp}" Height="32" ToolTip="Last Name" Canvas.Left="250"  Canvas.Top="120" Width="205" Padding="10,5"  TabIndex="1002"
                    VerticalAlignment="Top"
                    Watermark=""
                    IconPlacement="Left"
                    IconVisibility="Visible"
                    Delay="100"
                   Text="{Binding LastName, Mode=TwoWay, TargetNullValue=''}" 
                    Provider="{Binding LastNameSuggestions}">
                    <wpf:AutoCompleteTextBox.ItemTemplate>
                        <DataTemplate>
                            <Border Padding="5">
                                <StackPanel Orientation="Vertical">
                                    <TextBlock Text="{Binding}"
                   FontWeight="Bold" />
                                </StackPanel>
                            </Border>
                        </DataTemplate>
                    </wpf:AutoCompleteTextBox.ItemTemplate>
                </wpf:AutoCompleteTextBox>

                <Label Style="{StaticResource Devlbl}" Canvas.Left="480" Content="Receipt No" Canvas.Top="90" />
                             <wpf:AutoCompleteTextBox  Style="{StaticResource AutoComp}" Height="32" ToolTip="Receipt No" Canvas.Left="480"  Canvas.Top="120" Width="205" Padding="10,5"  TabIndex="1002"
                    VerticalAlignment="Top"
                    Watermark=""
                    IconPlacement="Left"
                    IconVisibility="Visible"
                    Delay="100"
                    Text="{Binding ReceiptNo, Mode=TwoWay, TargetNullValue=''}" 
                    Provider="{Binding ReceiptIdSuggestions}">
                    <wpf:AutoCompleteTextBox.ItemTemplate>
                        <DataTemplate>
                            <Border Padding="5">
                                <StackPanel Orientation="Vertical" >
                                    <TextBlock Text="{Binding}"
                   FontWeight="Bold">

                                    </TextBlock>
                                </StackPanel>
                            </Border>
                        </DataTemplate>
                    </wpf:AutoCompleteTextBox.ItemTemplate>
                    <i:Interaction.Behaviors>
                        <b:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9]+$" MaxLength="15" />
                    </i:Interaction.Behaviors>
                </wpf:AutoCompleteTextBox>
                <!--<Label Style="{StaticResource Devlbl}" Canvas.Left="710" Content="Duration" Canvas.Top="79" />-->
                <!--<ComboBox AllowDrop="True" Canvas.Left="710" ToolTip="Duration" Canvas.Top="107" Width="205" TabIndex="1004"
                    Style="{StaticResource CommonComboBox}"      
                    ItemsSource="{Binding Durations}" DisplayMemberPath="Description" SelectedValuePath="Id" SelectedValue="{Binding SelectedDate, Mode=TwoWay}">

                </ComboBox>-->

                <Button Content="Search" Style="{StaticResource MyButton}" ToolTip="Search" 
                    Canvas.Top="116" Canvas.Left="710" Cursor="Hand" 
                    Command="{Binding SearchCommand}" TabIndex="2001">
                </Button>
                <Button Content="Clear" Style="{StaticResource MyButton}"  ToolTip="Clear"
                    Canvas.Top="116" Canvas.Left="840" Cursor="Hand" 
                    Command="{Binding ClearCommand}" TabIndex="2002">
                </Button>
                <Image Height="25" Width="25" Canvas.Top="175" Canvas.Left="25" Source="pack://application:,,,/HomaKiosk;component/images/chkpending.png"/>
                <Label  Style="{StaticResource LegendLbl}" Canvas.Left="50" Content="Check Returned and Payment Pending" Canvas.Top="178" />
                <Image Height="25" Width="25" Canvas.Top="175" Canvas.Left="300" Source="pack://application:,,,/HomaKiosk;component/images/chkrepaid.png"/>
                <Label  Style="{StaticResource LegendLbl}" Canvas.Left="325" Content="Repaid" Canvas.Top="178" />
                <Image Height="25" Width="25" Canvas.Top="175" Canvas.Left="395" Source="pack://application:,,,/HomaKiosk;component/images/refund.png"/>
                <Label  Style="{StaticResource LegendLbl}" Canvas.Left="415" Content="Refunded" Canvas.Top="178" />
                 </Canvas>

0

Наведене вище рішення не спрацювало, як очікувалося, я трохи змінив поведінку, запропоновану Мізіпзором, наступним чином:

З цієї частини

if ((bool)args.NewValue)
        {
            control.Loaded += (sender, e) =>
                   control.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }

До цього

if ((bool)args.NewValue)
        {
            control.Loaded += (sender, e) => control.Focus();
        }

І я не пов'язую цю поведінку з вікном або UserControl, але для управління я хочу сфокусуватися спочатку, наприклад:

<TextBox ui:FocusBehavior.InitialFocus="True" />

О, вибачте за різні імена, я використовую ім'я InitialFocus для доданого властивості.

І це працює для мене, можливо, це могло б допомогти комусь іншому.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.