WPF i początkowa fokus

193

Wygląda na to, że po uruchomieniu aplikacji WPF nic nie jest skupione.

To jest naprawdę dziwne. Każdy inny framework, którego użyłem, robi dokładnie to, czego można się spodziewać: początkowo skupia się na pierwszej kontrolce w kolejności tabulacji. Ale potwierdziłem, że jest to WPF, a nie tylko moja aplikacja - jeśli utworzę nowe okno i po prostu umieszczę w nim TextBox i uruchomię aplikację, TextBox nie jest aktywny, dopóki go nie kliknę lub nie naciśnę Tab . Fuj.

Moja aktualna aplikacja jest bardziej skomplikowana niż zwykły TextBox. Mam kilka warstw UserControls w ramach UserControls. Jedna z tych kontrolek UserControls ma moduły obsługi Focusable = "True" i KeyDown / KeyUp i chcę, aby była aktywna, gdy tylko otworzy się moje okno. Nadal jestem nowicjuszem w WPF i nie mam szczęścia, aby dowiedzieć się, jak to zrobić.

Jeśli uruchomię aplikację i naciśnę klawisz Tab, fokus przejdzie do mojej kontrolki, na której można ustawić fokus, i zacznie działać tak, jak chcę. Ale nie chcę, aby moi użytkownicy musieli naciskać Tab, zanim będą mogli rozpocząć korzystanie z okna.

Bawiłem się z FocusManager.FocusedElement, ale nie jestem pewien, którą kontrolkę ustawić (okno najwyższego poziomu? Rodzic, który zawiera kontrolkę, którą można ustawić?) Ani na co ją ustawić.

Co muszę zrobić, aby moja głęboko zagnieżdżona kontrola miała początkową fokus zaraz po otwarciu okna? Albo jeszcze lepiej, aby skupić się na pierwszej kontrolce, którą można ustawić w kolejności tabulacji?

Joe White
źródło

Odpowiedzi:

170

To też działa:

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

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>
Sean
źródło
4
Jestem zaskoczony, że jestem pierwszą osobą, która to skomentowała. Byłem zdezorientowany, dokąd to poszło, ponieważ mogło to przejąć prawie każdą kontrolę. W odpowiedzi na to konkretne pytanie myślę, że pojawi się w oknie, ale możesz przeczytać uwagi na msdn.microsoft.com/en-us/library/…, aby zrozumieć, w jaki sposób kontrola, którą przypisujesz, ma znaczenie.
Joel McBeth
Z powodzeniem zastosowałem to podejście na panelu typu stackpanel. Jeśli ktoś jest zainteresowany, to przykład na stackoverflow.com/a/2872306/378115
Julio Nobre
To zadziałało dla mnie znacznie lepiej niż zaakceptowana odpowiedź, ponieważ muszę skupić się na elemencie, który jest po pierwszym.
Puterdo Borato
163

Wpadłem na świetny pomysł, aby przekopać się przez Reflector, aby zobaczyć, gdzie jest używana właściwość Focusable, i znalazłem drogę do tego rozwiązania. Muszę tylko dodać następujący kod do konstruktora mojego okna:

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

Spowoduje to automatyczne wybranie pierwszej kontrolki w kolejności zakładek, więc jest to ogólne rozwiązanie, które powinno być możliwe do upuszczenia w dowolnym oknie i Just Work.

Joe White
źródło
21
Dodaj zamień to w zachowanie. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf
6
@wekempf, nie znałem idei zachowań, ale przyjrzałem się temu i to wcale nie jest zły pomysł. Jeśli ktoś inny (taki jak ja) nie jest jeszcze zaznajomiony z załączonymi zachowaniami, oto wyjaśnienie: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White
1
Ponadto działa to, jeśli pożądanym elementem jest UserControl, który zawiera rzeczywisty element, na który można ustawić fokus (nawet w głębokich hierarchiach). Świetny!
Daniel Albuschat
1
Świetny pomysł, ale czasami nie działa, jeśli formant akceptujący fokus to Button. Aby to naprawić, przerzucam MoveFocuspołączenie przez dyspozytora z ContextIdlepriorytetem ( Backgroundlub wyższym nie działa). Istnieje również element, FocusNavigationDirection.Firstktóry lepiej pasuje do intencji i robi to samo w tym przypadku.
Anton Tykhyy
powinno to być zachowanie domyślne! Fuj (w oryginalnym poście) ma rację!
NH.
61

Na podstawie zaakceptowanej odpowiedzi zaimplementowanej jako załączone zachowanie:

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

Użyj tego w ten sposób:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">
Mizipzor
źródło
6
Moim zdaniem jest to zdecydowanie najlepsze rozwiązanie, jakie znalazłem. Dzięki!
Shion
1
W kodzie tej odpowiedzi w wywołaniu do jest błąd DependencyProperty.RegisterAttached. Trzeci parametr powinien być typeof(FocusBehavior), nie typeof(Control). Wprowadzenie tej zmiany uniemożliwi projektantowi zgłaszanie właściwości „FocusFirst” już zarejestrowanej przez błędy „Control”.
Tony Vitabile
@TonyVitabile Fixed. Jeśli możesz, zawsze możesz edytować i poprawiać odpowiedzi. :)
Mizipzor
Czy procedura obsługi zdarzenia control.Loaded nie powinna być wyrejestrowywana podczas zwalniania?
andreapier
@andreapier Mógłbyś, gdybyś się przejmował, ale pominięcie wyrejestrowania nie spowodowałoby wycieku pamięci ani nic takiego. Musisz się martwić tylko o zdarzenia powodujące wycieki pamięci, jeśli krótkotrwały obiekt ma metodę dołączoną do zdarzenia na obiekcie o długiej żywotności. W tym przypadku żywotność jest taka, jak okno, więc wszystko w porządku.
Joe White
14

Znalazłem inne możliwe rozwiązanie. Mark Smith opublikował rozszerzenie znaczników FirstFocusedElement do użytku z FocusManager.FocusedElement.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">
Joe White
źródło
Całkowicie zgrabny! Dziękuję Ci!
Andy
9

Ten sam problem rozwiązał go prostym rozwiązaniem: W głównym oknie:

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

W kontroli użytkownika:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }
Vladik Y
źródło
4
Działa tylko wtedy, gdy formant znajduje się bezpośrednio w oknie, a nie jeśli jest zagnieżdżony w UserControl.
Joe White
8

Po „WPF Initial Focus Nightmare” i na podstawie kilku odpowiedzi na stosie, poniższe okazały się najlepszym rozwiązaniem.

Najpierw dodaj swoją App.xaml OnStartup (), co następuje:

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

Następnie dodaj zdarzenie „WindowLoaded” również w 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));

        }));
    }

Problem z wątkami musi być używany, ponieważ początkowy fokus WPF najczęściej kończy się niepowodzeniem z powodu pewnych warunków wyścigu platformy.

Uważam, że poniższe rozwiązanie jest najlepsze, ponieważ jest używane globalnie dla całej aplikacji.

Mam nadzieję, że to pomoże...

Oran

OrPaz
źródło
5
Użyj BeginInvokezamiast tego przerażającego Sleep(100)stwierdzenia.
l33t
8

Kontrolkę można łatwo ustawić jako skoncentrowany element w języku XAML.

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

Nigdy nie próbowałem ustawiać tego w kontroli użytkownika i sprawdzać, czy to działa, ale może.

Simon Gillbee
źródło
Brzmi interesująco, ponieważ nie musisz nazywać kontrolki tylko dla kwestii skupienia. Z drugiej strony, mój test z kontrolą użytkownika nie zadziałał.
heringer
To mnie nie dziwi @heringer ... to byłoby jak próba ustawienia fokusu na <border> lub podobnej nieinteraktywnej kontrolce. Możesz spróbować zastosować ten atrybut FocusedElement na interaktywnej kontrolce wewnątrz usercontrol. Ale to może nie być opcja.
Simon Gillbee,
Użyłem tego podejścia na panelu stosu, aby ustawić przycisk podrzędny, na którym chcę się skupić po załadowaniu formularza. Wielkie dzięki
Julio Nobre
Uważaj, może to spowodować całkowite zerwanie wiązań. stackoverflow.com/questions/30676863/…
Der_Meister
2

Minimalna wersja odpowiedzi Mizipzora na 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));
}

Użyj w swoim XAML:

<Window local:FocusBehavior.GiveInitialFocus="True" />
Drew Noakes
źródło
1

Jeśli jesteś podobny do mnie i używasz ram, które w jakiś sposób psują podstawowe zachowania związane z fokusem i sprawiają, że wszystkie powyższe rozwiązania są nieistotne, nadal możesz to zrobić:

1 - Zwróć uwagę na element, który ma fokus (cokolwiek to jest!)

2 - Dodaj to w swoim kodzie za xxx.xaml.cs

private bool _firstLoad;

3 - Dodaj to do elementu, który ma pierwszeństwo:

GotFocus="Element_GotFocus"

4 - Dodaj metodę Element_GotFocus w kodzie za nim i określ nazwany element WPF, który potrzebuje pierwszego fokusu:

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

5 - Zarządzaj zdarzeniem Loaded

w języku XAML

Loaded="MyWindow_Loaded"   

w xaml.cs

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

Mam nadzieję, że to pomoże w ostateczności

G.Dealmeida
źródło
0

Ja też stanąłem przed tym samym problemem. Miałem trzy pola tekstowe w kontenerze kanwy i chciałem, aby pierwsze pole tekstowe było aktywne po otwarciu kontrolki użytkownika. Kod WPF był zgodny ze wzorcem MVVM. Stworzyłem oddzielną klasę zachowań, aby skupić się na elemencie i powiązałem go z moim widokiem w ten sposób.

Kod zachowania płótna

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

    }

}

Kod do przeglądania

<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>
BSG
źródło
0

Powyższe rozwiązanie nie działało zgodnie z oczekiwaniami dla mnie, nieznacznie zmieniłem zachowanie zaproponowane przez Mizipzora w następujący sposób:

Z tej części

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

Do tego

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

I nie dołączam tego zachowania do Window lub UserControl, ale do sterowania chcę się początkowo skupić, np:

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

Och, przepraszam za inne nazewnictwo, używam nazwy InitialFocus dla dołączonej właściwości.

I to działa dla mnie, może mogłoby pomóc komuś innemu.

BrightShadow
źródło
-1
<Window FocusManager.FocusedElement="{Binding ElementName=yourControlName}">
dnk.nitro
źródło
3
Podaj więcej kontekstu swojej odpowiedzi.
Anirudh Ramanathan
13
Dokładny duplikat tej odpowiedzi opublikowany prawie trzy lata temu .
Joe White,