WPF et focus initial

193

Il semble que lorsqu'une application WPF démarre, rien n'a le focus.

C'est vraiment bizarre. Tous les autres frameworks que j'ai utilisés font exactement ce que vous attendez: met l'accent initial sur le premier contrôle dans l'ordre de tabulation. Mais j'ai confirmé qu'il s'agissait de WPF, pas seulement de mon application - si je crée une nouvelle fenêtre et y mets simplement une zone de texte et exécute l'application, la zone de texte n'a pas le focus tant que je n'ai pas cliqué dessus ou que j'ai appuyé sur Tab . Beurk.

Mon application actuelle est plus compliquée qu'une simple zone de texte. J'ai plusieurs couches de UserControls dans UserControls. Un de ces UserControls a des gestionnaires Focusable = "True" et KeyDown / KeyUp, et je veux qu'il ait le focus dès que ma fenêtre s'ouvre. Cependant, je suis toujours un peu novice de WPF et je n'ai pas beaucoup de chance de savoir comment faire cela.

Si je démarre mon application et que j'appuie sur la touche Tab, le focus va à mon contrôle focusable et il commence à fonctionner comme je le souhaite. Mais je ne veux pas que mes utilisateurs doivent appuyer sur Tab avant de pouvoir commencer à utiliser la fenêtre.

J'ai joué avec FocusManager.FocusedElement, mais je ne sais pas sur quel contrôle le définir (la fenêtre de niveau supérieur? Le parent qui contient le contrôle focusable? Le contrôle focusable lui-même?) Ou sur quoi le définir.

Que dois-je faire pour que mon contrôle profondément imbriqué ait le focus initial dès que la fenêtre s'ouvre? Ou mieux encore, pour concentrer le premier contrôle focusable dans l'ordre de tabulation?

Joe White
la source

Réponses:

170

Cela fonctionne aussi:

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

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>
Sean
la source
4
Je suis surpris que je sois la première personne à avoir commenté cela. J'étais confus quant à savoir où cela allait, car cela pouvait aller sur presque n'importe quel contrôle. En réponse à cette question spécifique, je pense que cela irait sur la fenêtre, mais vous pouvez lire les remarques sur msdn.microsoft.com/en-us/library/ ... pour comprendre comment le contrôle que vous attachez à cela est important.
Joel McBeth
J'ai utilisé cette approche sur un stackpanel avec succès. Si quelqu'un est intéressé, il y a un exemple sur stackoverflow.com/a/2872306/378115
Julio Nobre
Cela a fonctionné beaucoup mieux pour moi que la réponse acceptée car je dois me concentrer sur l'élément qui est après en premier.
Puterdo Borato
163

J'ai eu la brillante idée de fouiller dans Reflector pour voir où la propriété Focusable est utilisée, et j'ai trouvé mon chemin vers cette solution. J'ai juste besoin d'ajouter le code suivant au constructeur de ma fenêtre:

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

Cela sélectionnera automatiquement le premier contrôle dans l'ordre de tabulation, c'est donc une solution générale qui devrait pouvoir être déposée dans n'importe quelle fenêtre et Just Work.

Joe White
la source
21
Ajoutez transformer cela en comportement. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf
6
@wekempf, je n'étais pas familier avec l'idée des comportements, mais je me suis penché dessus et ce n'est pas du tout une mauvaise idée. Si quelqu'un d'autre (comme moi) n'est pas déjà familier avec les comportements attachés, voici une explication: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White
1
En outre, cela fonctionne si l'élément souhaité est un UserControl qui contient l'élément focalisable réel (même dans les hiérarchies profondes). Génial!
Daniel Albuschat
1
Excellente idée, mais parfois cela ne fonctionne pas si le contrôle qui accepterait le focus est un Button. Pour résoudre ce problème, je retourne l' MoveFocusappel sur le répartiteur en ContextIdlepriorité ( Backgroundou plus ne fonctionne pas). En outre, il y a un FocusNavigationDirection.Firstqui correspond mieux à l'intention et fait la même chose dans ce cas.
Anton Tykhyy
cela devrait être le comportement par défaut! Beurk (dans le message original) a raison!
NH.
61

Basé sur la réponse acceptée implémentée en tant que comportement attaché:

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

Utilisez-le comme ceci:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">
Mizipzor
la source
6
À mon avis, c'est de loin la meilleure solution que j'ai trouvée. Merci!
Shion
1
Il y a un bogue dans le code de cette réponse dans l'appel à DependencyProperty.RegisterAttached. Le troisième paramètre devrait être typeof(FocusBehavior), non typeof(Control). Cette modification empêchera le concepteur de signaler la propriété «FocusFirst» déjà enregistrée par des erreurs «Control».
Tony Vitabile
@TonyVitabile Fixe. Vous êtes toujours libre de modifier et d'améliorer les réponses si vous le pouvez. :)
Mizipzor
Le gestionnaire d'événements control.Loaded ne devrait-il pas être annulé lors du déchargement?
andreapier
@andreapier Vous pourriez si vous vous en souciez, mais sauter le désenregistrement ne causerait pas de fuite de mémoire ou quoi que ce soit. Vous ne devez vous soucier des événements qui provoquent des fuites de mémoire que si un objet de courte durée a une méthode attachée à un événement sur un objet de longue durée. Dans ce cas, la durée de vie est celle de la fenêtre, donc tout va bien.
Joe White
14

J'ai trouvé une autre solution possible. Mark Smith a publié une extension de balisage FirstFocusedElement à utiliser avec FocusManager.FocusedElement.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">
Joe White
la source
Totalement lisse! Merci!
Andy
9

Avait le même problème résolu avec une solution simple: Dans la fenêtre principale:

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

Dans le contrôle utilisateur:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }
Vladik Y
la source
4
Ne fonctionne que si le contrôle est directement à l'intérieur de la fenêtre, pas s'il est imbriqué dans un UserControl.
Joe White
8

Après avoir eu un 'WPF Initial Focus Nightmare' et sur la base de quelques réponses sur la pile, ce qui suit s'est avéré être la meilleure solution.

Tout d'abord, ajoutez votre App.xaml OnStartup () les éléments suivants:

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

Ajoutez ensuite l'événement 'WindowLoaded' également dans 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));

        }));
    }

Le problème de threading doit être utilisé car le focus initial de WPF échoue principalement en raison de certaines conditions de concurrence du cadre.

J'ai trouvé la meilleure solution car elle est utilisée dans le monde entier pour l'ensemble de l'application.

J'espère que cela aide...

Oran

OrPaz
la source
5
Utilisez à la BeginInvokeplace de cette Sleep(100)déclaration effrayante .
l33t
8

Vous pouvez facilement définir le contrôle lui-même en tant qu'élément ciblé dans XAML.

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

Je n'ai jamais essayé de définir cela dans un contrôle utilisateur et de voir si cela fonctionne, mais cela peut.

Simon Gillbee
la source
Cela semble intéressant, car vous ne devez pas nommer le contrôle uniquement pour un problème de focus. En revanche, mon test avec contrôle utilisateur n'a pas fonctionné.
heringer
Cela ne me surprend pas @heringer ... ce serait comme essayer de mettre le focus sur un <border> ou un contrôle non interactif similaire. Vous pouvez essayer d'appliquer cet attribut FocusedElement sur un contrôle interactif à l'intérieur du usercontrol. Mais ce n'est peut-être pas une option.
Simon Gillbee
J'ai utilisé cette approche sur un stackpanel pour définir le bouton enfant sur lequel je voulais me concentrer une fois le formulaire chargé. Merci beaucoup
Julio Nobre
Attention, cela peut rendre les liaisons totalement cassées. stackoverflow.com/questions/30676863/…
Der_Meister
2

Une version minimale de la réponse de Mizipzor pour 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));
}

Utilisez dans votre XAML:

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

Si vous êtes comme moi et que vous utilisez des frameworks qui, d'une manière ou d'une autre, gâchent les comportements de mise au point de base et rendent toutes les solutions ci-dessus non pertinentes, vous pouvez toujours le faire:

1 - Notez l'élément qui obtient le focus (quel qu'il soit!)

2 - Ajoutez ceci dans votre code derrière xxx.xaml.cs

private bool _firstLoad;

3 - Ajoutez ceci sur l'élément qui obtient le premier focus:

GotFocus="Element_GotFocus"

4 - Ajoutez la méthode Element_GotFocus dans le code derrière, et spécifiez l'élément nommé WPF qui a besoin du premier focus:

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

5 - Gérer l'événement Loaded

en XAML

Loaded="MyWindow_Loaded"   

dans xaml.cs

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

J'espère que cela aidera en tant que solution de dernier recours

G.Dealmeida
la source
0

J'ai également rencontré le même problème. J'avais trois zones de texte à l'intérieur du conteneur de canevas et je voulais que la première zone de texte soit focalisée lorsque le contrôle utilisateur s'ouvre. Le code WPF a suivi le modèle MVVM. J'ai créé une classe de comportement distincte pour focaliser l'élément et l'ai lié à ma vue comme ceci.

Code de comportement du canevas

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 pour la vue

<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
la source
0

La solution ci-dessus ne fonctionnait pas comme prévu pour moi, j'ai légèrement modifié le comportement proposé par Mizipzor comme suit:

De cette partie

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

Pour ça

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

ET Je n'attache pas ce comportement à Window ou UserControl, mais pour contrôler je veux me concentrer initialement, par exemple:

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

Oh, désolé pour un nom différent J'utilise le nom InitialFocus pour la propriété jointe.

Et cela fonctionne pour moi, peut-être que cela pourrait aider quelqu'un d'autre.

BrightShadow
la source
-1
<Window FocusManager.FocusedElement="{Binding ElementName=yourControlName}">
dnk.nitro
la source
3
Veuillez donner plus de contexte à votre réponse.
Anirudh Ramanathan
13
Copie exacte de cette réponse publiée il y a près de trois ans .
Joe White