WPF und anfänglicher Fokus

190

Es scheint, dass beim Start einer WPF-Anwendung nichts den Fokus hat.

Das ist wirklich komisch. Jedes andere Framework, das ich verwendet habe, macht genau das, was Sie erwarten: Konzentriert sich zunächst auf das erste Steuerelement in der Tabulatorreihenfolge. Ich habe jedoch bestätigt, dass es sich um WPF handelt, nicht nur um meine App. Wenn ich ein neues Fenster erstelle und einfach eine TextBox einfüge und die App ausführe, hat die TextBox keinen Fokus, bis ich darauf klicke oder die Tabulatortaste drücke . Yuck.

Meine eigentliche App ist komplizierter als nur eine TextBox. Ich habe mehrere Ebenen von UserControls in UserControls. Eines dieser UserControls verfügt über die Handler Focusable = "True" und KeyDown / KeyUp, und ich möchte, dass es den Fokus hat, sobald sich mein Fenster öffnet. Ich bin allerdings immer noch ein WPF-Neuling, und ich habe nicht viel Glück, herauszufinden, wie das geht.

Wenn ich meine App starte und die Tabulatortaste drücke, wird der Fokus auf meine fokussierbare Steuerung gelegt und funktioniert wie gewünscht. Ich möchte jedoch nicht, dass meine Benutzer die Tabulatortaste drücken müssen, bevor sie das Fenster verwenden können.

Ich habe mit FocusManager.FocusedElement herumgespielt, bin mir aber nicht sicher, auf welches Steuerelement es eingestellt werden soll (das Fenster der obersten Ebene? Das übergeordnete Steuerelement, das das fokussierbare Steuerelement enthält? Das fokussierbare Steuerelement selbst?) Oder auf was es eingestellt werden soll.

Was muss ich tun, damit mein tief verschachteltes Steuerelement beim Öffnen des Fensters den ersten Fokus erhält? Oder noch besser, um das erste fokussierbare Steuerelement in der Tabulatorreihenfolge zu fokussieren?

Joe White
quelle

Antworten:

164

Das funktioniert auch:

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

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>
Sean
quelle
4
Ich bin überrascht, dass ich die erste Person bin, die dies kommentiert hat. Ich war verwirrt, wohin das führte, weil es fast jede Kontrolle übernehmen konnte. Als Antwort auf diese spezielle Frage denke ich, dass sie im Fenster angezeigt wird , aber Sie können die Anmerkungen auf msdn.microsoft.com/en-us/library/… lesen, um zu verstehen, wie das Steuerelement, mit dem Sie dies verknüpfen , von Bedeutung ist.
Joel McBeth
Ich habe diesen Ansatz mit Erfolg auf einem Stackpanel angewendet. Wenn man interessiert ist, gibt es ein Beispiel unter stackoverflow.com/a/2872306/378115
Julio Nobre
Dies funktionierte für mich viel besser als die akzeptierte Antwort, da ich mich auf das Element konzentrieren muss, das zuerst kommt.
Puterdo Borato
163

Ich hatte die gute Idee, in Reflector zu stöbern, um zu sehen, wo die Focusable-Eigenschaft verwendet wird, und fand meinen Weg zu dieser Lösung. Ich muss nur den folgenden Code zum Konstruktor meines Fensters hinzufügen:

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

Dadurch wird automatisch das erste Steuerelement in der Tabulatorreihenfolge ausgewählt. Dies ist also eine allgemeine Lösung, die in jedes Fenster und in Just Work eingefügt werden kann.

Joe White
quelle
21
Fügen Sie das in ein Verhalten um. <Window FocusBehavior.FocusFirst = "true"> ... </
Windows
6
@wekempf, ich war mit der Idee von Verhaltensweisen nicht vertraut, aber ich habe sie untersucht und das ist überhaupt keine schlechte Idee. Wenn jemand anderes (wie ich) noch nicht mit angehängten Verhaltensweisen vertraut ist, hier eine Erklärung: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White
1
Dies funktioniert außerdem, wenn das gewünschte Element ein UserControl ist, das das tatsächlich fokussierbare Element enthält (auch in tiefen Hierarchien). Toll!
Daniel Albuschat
1
Tolle Idee, aber manchmal funktioniert es nicht, wenn das Steuerelement, das den Fokus akzeptiert, a ist Button. Um dies zu beheben, drehe ich den MoveFocusAnruf mit ContextIdlePriorität über den Dispatcher ( Backgroundoder höher funktioniert nicht). Es gibt auch eine FocusNavigationDirection.First, die der Absicht besser entspricht und in diesem Fall dasselbe tut.
Anton Tykhyy
das sollte das Standardverhalten sein! Yuck (im Originalbeitrag) ist richtig!
NH.
61

Basierend auf der akzeptierten Antwort, die als angehängtes Verhalten implementiert wurde:

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

Verwenden Sie es so:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">
Mizipzor
quelle
6
Meiner Meinung nach ist dies bei weitem die beste Lösung, die ich gefunden habe. Vielen Dank!
Shion
1
Es gibt einen Fehler im Code in dieser Antwort im Aufruf von DependencyProperty.RegisterAttached. Der dritte Parameter sollte typeof(FocusBehavior)nicht sein typeof(Control). Durch diese Änderung wird verhindert, dass der Designer die Eigenschaft 'FocusFirst' meldet, die bereits durch 'Control'-Fehler registriert wurde.
Tony Vitabile
@ TonyVitabile behoben. Sie können die Antworten jederzeit bearbeiten und verbessern, wenn Sie können. :)
Mizipzor
Sollte nicht gesteuert werden. Geladener Ereignishandler wird beim Entladen abgemeldet?
andreapier
@andreapier Du könntest es, wenn es dich interessiert, aber das Überspringen des Deregisters würde keinen Speicherverlust oder irgendetwas verursachen. Sie müssen sich nur um Ereignisse kümmern, die zu Speicherverlusten führen, wenn an ein kurzlebiges Objekt eine Methode an ein Ereignis an einem langlebigen Objekt angehängt ist. In diesem Fall entspricht die Lebensdauer der des Fensters, sodass es Ihnen gut geht.
Joe White
14

Ich habe eine andere mögliche Lösung gefunden. Mark Smith hat eine FirstFocusedElement-Markup-Erweiterung zur Verwendung mit FocusManager.FocusedElement veröffentlicht.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">
Joe White
quelle
Total schick! Danke dir!
Andy
8

Nach einem 'WPF Initial Focus Nightmare' und basierend auf einigen Antworten auf dem Stapel erwies sich das Folgende für mich als die beste Lösung.

Fügen Sie Ihrer App.xaml OnStartup () zunächst Folgendes hinzu:

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

Fügen Sie dann das Ereignis 'WindowLoaded' auch in App.xaml hinzu:

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

        }));
    }

Das Threading-Problem muss verwendet werden, da der anfängliche Fokus von WPF aufgrund einiger Framework-Race-Bedingungen meistens fehlschlägt.

Ich fand die folgende Lösung am besten, da sie global für die gesamte App verwendet wird.

Ich hoffe es hilft...

Oran

OrPaz
quelle
5
Verwenden Sie BeginInvokeanstelle dieser beängstigenden Sleep(100)Aussage.
13.
8

Hatte das gleiche Problem mit einfacher Lösung gelöst: Im Hauptfenster:

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

In der Benutzersteuerung:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }
Vladik Y.
quelle
3
Funktioniert nur, wenn sich das Steuerelement direkt im Fenster befindet, nicht, wenn es in einem UserControl verschachtelt ist.
Joe White
8

Sie können das Steuerelement problemlos selbst als fokussiertes Element in XAML festlegen.

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

Ich habe noch nie versucht, dies in einer Benutzersteuerung festzulegen und zu prüfen, ob dies funktioniert, aber es kann sein.

Simon Gillbee
quelle
Es klingt interessant, weil Sie das Steuerelement nicht nur für ein Fokusproblem benennen müssen. Andererseits hat mein Test mit Benutzersteuerung nicht funktioniert.
Heringer
Das überrascht mich nicht @heringer ... das wäre wie der Versuch, den Fokus auf eine <Bordgrenze> oder ein ähnliches nicht interaktives Steuerelement zu legen. Sie können versuchen, dieses FocusedElement-Attribut auf ein interaktives Steuerelement innerhalb der Benutzersteuerung anzuwenden. Dies ist jedoch möglicherweise keine Option.
Simon Gillbee
Ich habe diesen Ansatz in einem Stackpanel verwendet, um festzulegen, auf welche untergeordnete Schaltfläche ich mich beim Laden des Formulars konzentrieren möchte. Vielen Dank
Julio Nobre
Seien Sie vorsichtig, es kann zu Bindungen kommen. stackoverflow.com/questions/30676863/…
Der_Meister
2

Eine minimale Version von Mizipzors Antwort für 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));
}

Verwendung in Ihrer XAML:

<Window local:FocusBehavior.GiveInitialFocus="True" />
Drew Noakes
quelle
1

Wenn Sie wie ich sind und einige Frameworks verwenden, die das grundlegende Fokusverhalten irgendwie durcheinander bringen und alle oben genannten Lösungen irrelevant machen, können Sie dies trotzdem tun:

1 - Beachten Sie das Element, das den Fokus erhält (was auch immer es ist!)

2 - Fügen Sie dies in Ihren Code hinter xxx.xaml.cs ein

private bool _firstLoad;

3 - Fügen Sie dies zu dem Element hinzu, das den ersten Fokus erhält:

GotFocus="Element_GotFocus"

4 - Fügen Sie die Element_GotFocus-Methode in den Code dahinter ein und geben Sie das WPF-benannte Element an, das den ersten Fokus benötigt:

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

5 - Verwalten Sie das geladene Ereignis

in XAML

Loaded="MyWindow_Loaded"   

in xaml.cs

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

Hoffe, dies wird als letzter Ausweg helfen

G.Dealmeida
quelle
0

Ich hatte auch das gleiche Problem. Ich hatte drei Textfelder im Canvas-Container und wollte, dass das erste Textfeld beim Öffnen des Benutzersteuerelements fokussiert wird. Der WPF-Code folgte dem MVVM-Muster. Ich habe eine separate Verhaltensklasse zum Fokussieren des Elements erstellt und es so an meine Ansicht gebunden.

Canvas-Verhaltenscode

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

    }

}

Code zur Ansicht

<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
quelle
0

Die obige Lösung hat bei mir nicht wie erwartet funktioniert. Ich habe das von Mizipzor vorgeschlagene Verhalten wie folgt geringfügig geändert:

Von diesem Teil

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

Dazu

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

Und ich hänge dieses Verhalten nicht an Window oder UserControl an, aber zur Steuerung möchte ich mich zunächst konzentrieren, z.

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

Oh, entschuldigen Sie die unterschiedliche Benennung. Ich verwende den InitialFocus-Namen für die angehängte Eigenschaft.

Und das funktioniert für mich, vielleicht könnte es jemand anderem helfen.

BrightShadow
quelle