WPF e focus iniziale


191

Sembra che quando si avvia un'applicazione WPF, nulla ha lo stato attivo.

Questo è davvero strano. Ogni altro framework che ho usato fa esattamente quello che ti aspetteresti: mette il focus iniziale sul primo controllo nell'ordine di tabulazione. Ma ho confermato che si tratta di WPF, non solo della mia app: se creo una nuova finestra, inserisco semplicemente una TextBox ed eseguo l'app, la TextBox non ha lo stato attivo finché non faccio clic su di essa o premo Tab . Che schifo.

La mia app attuale è più complicata di una semplice TextBox. Ho diversi livelli di UserControls in UserControls. Uno di questi UserControls ha i gestori Focusable = "True" e KeyDown / KeyUp, e voglio che abbia lo stato attivo non appena la mia finestra si apre. Sono comunque un po 'un novizio del WPF, e non ho molta fortuna a capire come farlo.

Se avvio la mia app e premo il tasto Tab, lo stato attivo passa al mio controllo attivabile e inizia a funzionare nel modo desiderato. Ma non voglio che i miei utenti debbano premere Tab prima di poter iniziare a utilizzare la finestra.

Ho giocato con FocusManager.FocusedElement, ma non sono sicuro su quale controllo impostarlo (la finestra di livello superiore? Il genitore che contiene il controllo attivabile? Il controllo attivabile stesso?) O su cosa impostarlo.

Cosa devo fare per fare in modo che il mio controllo profondamente annidato abbia lo stato iniziale non appena si apre la finestra? O meglio ancora, focalizzare il primo controllo focalizzabile nell'ordine di tabulazione?

Risposte:


165

Funziona anche questo:

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

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

4
Sono sorpreso di essere il primo a commentare questo. Ero confuso su dove fosse andato perché poteva andare quasi su qualsiasi controllo. In risposta a questa domanda specifica, penso che andrebbe sulla finestra, ma puoi leggere le osservazioni su msdn.microsoft.com/en-us/library/… per capire come il controllo che attribuisci a questo è importante.
Joel McBeth,

Ho usato questo approccio su uno stackpanel con successo. Se uno è interessato, c'è un esempio su stackoverflow.com/a/2872306/378115
Julio Nobre,

Questo ha funzionato per me molto meglio della risposta accettata perché ho bisogno di concentrarmi sull'elemento che è dopo prima.
Puterdo Borato

163

Ho avuto la brillante idea di scavare in Reflector per vedere dove viene utilizzata la proprietà Focusable e ho trovato la mia strada per questa soluzione. Devo solo aggiungere il seguente codice al costruttore di Windows:

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

Questo selezionerà automaticamente il primo controllo nell'ordine di tabulazione, quindi è una soluzione generale che dovrebbe essere in grado di essere rilasciata in qualsiasi finestra e Just Work.


21
Aggiungi trasformalo in un comportamento. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf

6
@wekempf, non avevo familiarità con l'idea dei comportamenti, ma l'ho esaminato e non è affatto una cattiva idea. Se qualcun altro (come me) non ha già familiarità con i comportamenti associati, ecco una spiegazione: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White,

1
Inoltre, funziona se l'elemento desiderato è un UserControl che contiene l'elemento focalizzabile effettivo (anche in gerarchie profonde). Grande!
Daniel Albuschat,

1
Ottima idea, ma a volte non funziona se il controllo che accetterebbe il focus è a Button. Per risolvere questo problema, capovolgo la MoveFocuschiamata sul dispatcher in via ContextIdleprioritaria ( Backgroundo superiore non funziona). Inoltre, ce n'è uno FocusNavigationDirection.Firstche corrisponde meglio all'intento e fa la stessa cosa in questo caso.
Anton Tykhyy,

quello dovrebbe essere il comportamento predefinito! Yuck (nel post originale) ha ragione!
NH.

61

In base alla risposta accettata implementata come comportamento allegato:

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

Usalo in questo modo:

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

6
Secondo me, questa è di gran lunga la migliore soluzione che ho trovato. Grazie!
Shion,

1
C'è un bug nel codice in questa risposta nella chiamata a DependencyProperty.RegisterAttached. Il terzo parametro dovrebbe essere typeof(FocusBehavior), non typeof(Control). Apportare questa modifica impedirà al progettista di segnalare la proprietà 'FocusFirst' già registrata da errori 'Control'.
Tony Vitabile

@TonyVitabile Risolto. Sei sempre libero di modificare e migliorare le risposte se puoi. :)
Mizipzor

Il gestore degli eventi caricati non dovrebbe essere cancellato durante lo scarico?
andreapier,

@andreapier Potresti se ti importasse, ma saltare la cancellazione non causerebbe una perdita di memoria o altro. Devi solo preoccuparti degli eventi che causano perdite di memoria se un oggetto di breve durata ha un metodo collegato a un evento su un oggetto di lunga durata. In questo caso, la durata è quella della finestra, quindi stai bene.
Joe White,

14

Ho trovato un'altra possibile soluzione. Mark Smith ha pubblicato un'estensione di markup FirstFocusedElement da utilizzare con FocusManager.FocusedElement.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">

Totalmente lucido! Grazie!
Andy,

9

Lo stesso problema è stato risolto con una soluzione semplice: nella finestra principale:

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

Nel controllo utente:

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

3
Funziona solo se il controllo è direttamente all'interno della finestra, non se è nidificato all'interno di un controllo utente.
Joe White,

8

Dopo aver avuto un "Incubo di messa a fuoco iniziale WPF" e basato su alcune risposte in pila, quanto segue si è rivelato per me la soluzione migliore.

Innanzitutto, aggiungi App.xaml OnStartup () i seguenti:

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

Quindi aggiungi l'evento 'WindowLoaded' anche in 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));

        }));
    }

Il problema del threading deve essere utilizzato poiché il focus iniziale di WPF fallisce principalmente a causa di alcune condizioni di gara del framework.

Ho trovato la seguente soluzione migliore in quanto viene utilizzata a livello globale per l'intera app.

Spero che sia d'aiuto...

Oran


5
Usa BeginInvokeinvece di quella Sleep(100)dichiarazione spaventosa .
l33t

8

Puoi facilmente impostare il controllo stesso come elemento attivo in XAML.

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

Non ho mai provato a impostarlo in un controllo utente e vedere se funziona, ma potrebbe.


Sembra interessante, perché non devi nominare il controllo solo per un problema di focus. D'altra parte, il mio test con controllo utente non ha funzionato.
Heringer,

Non mi sorprende che @heringer ... sarebbe come cercare di focalizzare l'attenzione su un <border> o un controllo non interattivo simile. Potresti provare ad applicare questo attributo FocusedElement su un controllo interattivo all'interno di usercontrol. Ma questa potrebbe non essere un'opzione.
Simon Gillbee,

Ho usato questo approccio su uno stackpanel per impostare quale pulsante figlio volevo mettere a fuoco una volta caricato il modulo. Grazie mille
Julio Nobre il

Fai attenzione, potrebbe rompere completamente gli attacchi. stackoverflow.com/questions/30676863/...
Der_Meister

2

Una versione minima della risposta di Mizipzor per 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));
}

Usa nel tuo XAML:

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

1

Se sei come me e stai utilizzando alcuni framework che, in qualche modo, confondono i comportamenti di focus di base e rendono irrilevanti tutte le soluzioni sopra, puoi ancora farlo:

1 - Nota l'elemento che ottiene il focus (qualunque esso sia!)

2 - Aggiungi questo nel tuo codice dietro xxx.xaml.cs

private bool _firstLoad;

3 - Aggiungi questo sull'elemento che ottiene il primo focus:

GotFocus="Element_GotFocus"

4 - Aggiungi il metodo Element_GotFocus nel codice dietro e specifica l'elemento denominato WPF che necessita del primo focus:

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

5 - Gestisci l'evento Loaded

in XAML

Loaded="MyWindow_Loaded"   

in xaml.cs

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

Spero che questo possa essere di aiuto come ultima soluzione


0

Ho anche affrontato lo stesso problema. Avevo tre caselle di testo all'interno del contenitore della tela e volevo che la prima casella di testo fosse focalizzata quando si apriva il controllo utente. Il codice WPF ha seguito il modello MVVM. Ho creato una classe di comportamento separata per focalizzare l'elemento e l'ho legato alla mia vista in questo modo.

Codice di comportamento della 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);
        }

    }

}

Codice per la visualizzazione

<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

La soluzione sopra non funzionava come previsto per me, ho modificato leggermente il comportamento proposto da Mizipzor come segue:

Da questa parte

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

A questa

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

E non sto associando questo comportamento a Window o UserControl, ma per controllare voglio concentrarmi inizialmente, ad esempio:

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

Oh, scusa per il nome diverso sto usando il nome InitialFocus per la proprietà allegata.

E questo funziona per me, forse potrebbe aiutare qualcun altro.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.