WPF e foco inicial

190

Parece que quando um aplicativo WPF é iniciado, nada tem foco.

Isso é realmente estranho. Qualquer outra estrutura que usei faz exatamente o que você esperaria: coloca o foco inicial no primeiro controle na ordem das guias. Mas confirmei que é o WPF, não apenas o meu aplicativo - se eu criar uma nova janela, colocar uma caixa de texto nela e executar o aplicativo, a caixa de texto não terá foco até eu clicar nela ou pressionar Tab . Que nojo.

Meu aplicativo atual é mais complicado do que apenas um TextBox. Eu tenho várias camadas de UserControls dentro de UserControls. Um desses UserControls possui manipuladores Focusable = "True" e KeyDown / KeyUp, e quero que ele tenha o foco assim que minha janela for aberta. No entanto, ainda sou um novato no WPF e não estou tendo muita sorte em descobrir como fazer isso.

Se eu iniciar o aplicativo e pressionar a tecla Tab, o foco será direcionado para o meu controle de foco e ele começará a funcionar da maneira que eu quero. Mas não quero que meus usuários tenham que pressionar Tab antes de começarem a usar a janela.

Eu brinquei com o FocusManager.FocusedElement, mas não tenho certeza em qual controle defini-lo (a janela de nível superior? O pai que contém o controle focalizável? O próprio controle focalizável?) Ou o que defini-lo.

O que preciso fazer para que meu controle profundamente aninhado tenha foco inicial assim que a janela abrir? Ou, melhor ainda, focar o primeiro controle focalizável na ordem das guias?

Joe White
fonte

Respostas:

164

Isso também funciona:

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

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>
Sean
fonte
4
Estou surpreso por ser a primeira pessoa que comentou sobre isso. Eu estava confuso sobre onde isso foi, porque poderia ter quase qualquer controle. Em resposta a essa pergunta específica, acho que seria exibida na janela, mas você pode ler as observações em msdn.microsoft.com/en-us/library/… para entender como o controle ao qual você anexa isso é importante.
Joel McBeth
Eu usei essa abordagem em um stackpanel com sucesso. Se alguém estiver interessado, há um exemplo em stackoverflow.com/a/2872306/378115
Julio Nobre
Isso funcionou para mim muito melhor do que a resposta aceita, porque eu preciso colocar o foco no elemento que é o primeiro.
Puterdo Borato
163

Tive a brilhante idéia de pesquisar no Reflector para ver onde a propriedade Focusable é usada e encontrei o caminho para esta solução. Eu só preciso adicionar o seguinte código ao construtor do meu Window:

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

Isso selecionará automaticamente o primeiro controle na ordem das guias, portanto, é uma solução geral que deve poder ser solta em qualquer janela e Just Work.

Joe White
fonte
21
Adicione transformar isso em um comportamento. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf
6
@wekempf, eu não estava familiarizado com a idéia de comportamentos, mas eu olhei para ela e isso não é uma má idéia. Se qualquer outra pessoa (como eu) não estiver familiarizado com comportamentos anexados, aqui está uma explicação: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White
1
Além disso, isso funciona se o elemento desejado for um UserControl que contém o elemento real focalizável (mesmo em hierarquias profundas). Ótimo!
Daniel Albuschat
1
Ótima idéia, mas às vezes não funciona se o controle que aceita o foco é a Button. Para corrigir isso, viro a MoveFocuschamada pelo despachante com ContextIdleprioridade ( Backgroundou superior, não funciona). Além disso, existe um FocusNavigationDirection.Firstque corresponde melhor à intenção e faz a mesma coisa neste caso.
precisa saber é o seguinte
esse deve ser o comportamento padrão! Yuck (no post original) está certo!
NH.
61

Com base na resposta aceita implementada como um comportamento anexado:

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

Use-o assim:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">
Mizipzor
fonte
6
Na minha opinião, esta é de longe a melhor solução que encontrei. Obrigado!
Shion 21/03
1
Há um erro no código nesta resposta na chamada para DependencyProperty.RegisterAttached. O terceiro parâmetro deve ser typeof(FocusBehavior), não typeof(Control). Fazer essa alteração impedirá que o designer relate a propriedade 'FocusFirst' já registrada por erros de 'Controle'.
Tony Vitabile
@TonyVitabile Fixed. Você sempre pode editar e melhorar as respostas, se puder. :)
Mizipzor 01/04/19
O manipulador de eventos Loaded não deve ser registrado durante o descarregamento?
21716
@andreapier Você poderia se importasse, mas pular o cancelador de registro não causaria um vazamento de memória ou algo assim. Você só precisa se preocupar com eventos que causam vazamento de memória se um objeto de vida curta tiver um método anexado a um evento em um objeto de vida longa. Nesse caso, a vida útil é a da janela, então você está bem.
Joe White
14

Encontrei outra solução possível. Mark Smith postou uma extensão de marcação FirstFocusedElement para uso com o FocusManager.FocusedElement.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">
Joe White
fonte
Totalmente liso! Obrigado!
1013 Andy
8

Depois de ter um 'Pesadelo de foco inicial do WPF' e com base em algumas respostas na pilha, o seguinte provou ser a melhor solução.

Primeiro, adicione seu App.xaml OnStartup () da seguinte maneira:

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

Em seguida, adicione o evento 'WindowLoaded' também no 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));

        }));
    }

O problema de encadeamento deve ser usado, pois o foco inicial do WPF falha principalmente devido a algumas condições de corrida da estrutura.

Encontrei a melhor solução a seguir, pois é usada globalmente em todo o aplicativo.

Espero que ajude...

Oran

OrPaz
fonte
5
Use em BeginInvokevez dessa Sleep(100)afirmação assustadora .
L23t
8

Tinha o mesmo problema resolvido com uma solução simples: Na janela principal:

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

No controle do usuário:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }
Vladik Y
fonte
3
Funciona apenas se o controle estiver diretamente dentro da janela, não se estiver aninhado dentro de um UserControl.
1811 Joe White
8

Você pode facilmente ter o controle definido como o elemento focado no XAML.

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

Eu nunca tentei definir isso em um controle de usuário e ver se isso funciona, mas pode.

Simon Gillbee
fonte
Parece interessante, porque você não precisa nomear o controle apenas para um problema de foco. Por outro lado, meu teste com controle de usuário não funcionou.
Heringer
Isso não me surpreende @heringer ... seria como tentar focar em um <border> ou em um controle não interativo semelhante. Você pode tentar aplicar esse atributo FocusedElement em um controle interativo dentro do controle do usuário. Mas isso pode não ser uma opção.
Simon Gillbee
Eu usei essa abordagem em um stackpanel para definir qual botão filho eu queria focar após o carregamento do formulário. Muito obrigado
Julio Nobre
Tenha cuidado, pois pode tornar as ligações totalmente quebradas. stackoverflow.com/questions/30676863/…
Der_Meister
2

Uma versão mínima da resposta de Mizipzor para 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));
}

Use no seu XAML:

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

Se você é como eu e está usando algumas estruturas que, de alguma forma, atrapalham os comportamentos básicos de foco e tornam todas as soluções acima irrelevantes, você ainda pode fazer isso:

1 - Observe o elemento que recebe o foco (seja o que for!)

2 - Adicione isso no seu código por trás de xxx.xaml.cs

private bool _firstLoad;

3 - Adicione isso ao elemento que recebe o primeiro foco:

GotFocus="Element_GotFocus"

4 - Adicione o método Element_GotFocus no código por trás e especifique o elemento nomeado WPF que precisa do primeiro foco:

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

5 - Gerenciar o evento carregado

em XAML

Loaded="MyWindow_Loaded"   

em xaml.cs

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

Espero que isso ajude como uma solução de último recurso

G.Dealmeida
fonte
0

Eu também enfrentei o mesmo problema. Eu tinha três caixas de texto dentro do contêiner de lona e queria que a primeira caixa de texto fosse focada quando o controle do usuário fosse aberto. O código WPF seguiu o padrão MVVM. Criei uma classe de comportamento separada para focar o elemento e vinculei-o à minha visão dessa maneira.

Código de comportamento da tela

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

    }

}

Código para visualização

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

A solução acima não estava funcionando como esperado para mim, alterei um pouco o comportamento proposto por Mizipzor da seguinte maneira:

Desta parte

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

Para isso

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

E eu não estou anexando esse comportamento ao Window ou UserControl, mas para controlar, quero focar inicialmente, por exemplo:

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

Ah, desculpe por nomes diferentes. Estou usando o nome InitialFocus para a propriedade anexada.

E isso está funcionando para mim, talvez possa ajudar outra pessoa.

BrightShadow
fonte
-1
<Window FocusManager.FocusedElement="{Binding ElementName=yourControlName}">
dnk.nitro
fonte
3
Forneça mais contexto à sua resposta.
Anirudh Ramanathan
13
A duplicata exata desta resposta foi postada quase três anos atrás .
Joe White