WPF dan fokus awal

191

Tampaknya ketika aplikasi WPF dimulai, tidak ada yang memiliki fokus.

Ini sangat aneh. Setiap kerangka kerja lain yang saya gunakan melakukan apa yang Anda harapkan: menempatkan fokus awal pada kontrol pertama dalam urutan tab. Tetapi saya telah mengkonfirmasi bahwa itu adalah WPF, bukan hanya aplikasi saya - jika saya membuat Window baru, dan hanya memasukkan TextBox di dalamnya, dan menjalankan aplikasi, TextBox tidak memiliki fokus sampai saya mengkliknya atau tekan Tab . Yuck.

Aplikasi saya yang sebenarnya lebih rumit dari sekedar TextBox. Saya memiliki beberapa lapisan UserControls dalam UserControls. Salah satu UserControls memiliki penangan Focusable = "True" dan KeyDown / KeyUp, dan saya ingin itu memiliki fokus segera setelah jendela saya terbuka. Saya masih agak pemula WPF, dan saya tidak punya banyak keberuntungan mencari tahu bagaimana melakukan ini.

Jika saya memulai aplikasi saya dan menekan tombol Tab, maka fokus beralih ke kontrol yang dapat saya fokuskan, dan mulai berfungsi seperti yang saya inginkan. Tapi saya tidak ingin pengguna saya harus menekan Tab sebelum mereka dapat mulai menggunakan jendela.

Saya telah bermain-main dengan FocusManager.FocusedElement, tapi saya tidak yakin kontrol mana yang digunakan (Window tingkat atas? Induk yang berisi kontrol yang dapat difokus? Kontrol yang dapat difokus sendiri?) Atau untuk mengaturnya.

Apa yang harus saya lakukan untuk mendapatkan kontrol mendalam saya untuk memiliki fokus awal segera setelah jendela terbuka? Atau lebih baik lagi, untuk memfokuskan kontrol fokus pertama dalam urutan tab?

Joe White
sumber

Jawaban:

165

Ini juga berfungsi:

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

   <DataGrid x:Name="SomeElement">
     ...
   </DataGrid>
</Window>
Sean
sumber
4
Saya terkejut saya orang pertama yang berkomentar tentang ini. Saya bingung ke mana ini pergi karena itu bisa berjalan di hampir semua kontrol. Dalam menjawab pertanyaan khusus ini, saya pikir itu akan muncul di jendela, tetapi Anda dapat membaca komentar di msdn.microsoft.com/en-us/library/… untuk memahami bagaimana kontrol yang Anda lampirkan ini pada masalah.
Joel McBeth
Saya telah menggunakan pendekatan ini pada stackpanel dengan sukses. Jika ada yang tertarik, ada contoh di stackoverflow.com/a/2872306/378115
Julio Nobre
Ini bekerja untuk saya jauh lebih baik daripada jawaban yang diterima karena saya perlu fokus pada elemen yang setelah pertama
Puterdo Borato
163

Saya memiliki ide cemerlang untuk menggali melalui Reflector untuk melihat di mana properti Focusable digunakan, dan menemukan jalan saya ke solusi ini. Saya hanya perlu menambahkan kode berikut ke konstruktor Window saya:

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

Ini akan secara otomatis memilih kontrol pertama dalam urutan tab, jadi ini adalah solusi umum yang harus dapat dimasukkan ke jendela mana saja dan Just Work.

Joe White
sumber
21
Tambahkan mengubahnya menjadi perilaku. <Window FocusBehavior.FocusFirst = "true"> ... </Window>
wekempf
6
@wekempf, saya tidak terbiasa dengan gagasan tentang perilaku, tapi saya melihatnya dan itu bukan ide yang buruk sama sekali. Jika orang lain (seperti saya) belum terbiasa dengan perilaku terlampir, inilah penjelasannya: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White
1
Selain itu, ini berfungsi jika elemen yang diinginkan adalah UserControl yang berisi elemen yang dapat difokus sebenarnya (bahkan dalam hierarki yang dalam). Bagus!
Daniel Albuschat
1
Ide bagus, tapi kadang tidak berhasil jika kontrol yang mau menerima fokus adalah a Button. Untuk memperbaikinya, saya membalik MoveFocuspanggilan melalui operator di ContextIdleprioritas ( Backgroundatau lebih tinggi tidak berfungsi). Juga, ada FocusNavigationDirection.Firstyang cocok dengan maksud lebih baik dan melakukan hal yang sama dalam kasus ini.
Anton Tykhyy
itu seharusnya menjadi perilaku default! Yuck (di pos asli) benar!
NH.
61

Berdasarkan jawaban yang diterima diterapkan sebagai perilaku terlampir:

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

Gunakan seperti ini:

<Window xmlns:Behaviors="clr-namespace:UI.Behaviors"
        Behaviors:FocusBehavior.FocusFirst="true">
Mizipzor
sumber
6
Menurut pendapat saya, sejauh ini solusi terbaik yang saya temukan. Terima kasih!
Shion
1
Ada bug dalam kode dalam jawaban ini dalam panggilan ke DependencyProperty.RegisterAttached. Parameter ketiga seharusnya typeof(FocusBehavior), bukan typeof(Control). Membuat perubahan ini akan mencegah desainer melaporkan properti 'FocusFirst' yang sudah terdaftar oleh kesalahan 'Kontrol'.
Tony Vitabile
@TonyVitabile Tetap. Anda selalu bebas mengedit dan meningkatkan jawaban jika Anda bisa. :)
Mizipzor
Seharusnya tidak mengontrol. Event handler yang dibatalkan deregistrasi saat membongkar?
andreapier
@ andreapier Anda bisa jika Anda peduli, tetapi melewatkan deregister tidak akan menyebabkan kebocoran memori atau apa pun. Anda hanya perlu khawatir tentang peristiwa yang menyebabkan kebocoran memori jika objek berumur pendek memiliki metode yang melekat pada suatu peristiwa pada objek berumur panjang. Dalam hal ini, masa pakai adalah masa jendela, jadi Anda baik-baik saja.
Joe White
14

Saya menemukan solusi lain yang mungkin. Mark Smith memposting ekstensi markup FirstFocusedElement untuk digunakan dengan FocusManager.FocusedElement.

<UserControl x:Class="FocusTest.Page2"
    xmlns:FocusTest="clr-namespace:FocusTest"
    FocusManager.FocusedElement="{FocusTest:FirstFocusedElement}">
Joe White
sumber
Benar-benar licin! Terima kasih!
Andy
9

Apakah masalah yang sama diselesaikan dengan solusi sederhana: Di jendela utama:

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

Di kontrol pengguna:

private void UserControl_GotFocus_1(object sender, RoutedEventArgs e)
        {
            targetcontrol.Focus();
            this.GotFocus -= UserControl_GotFocus_1;  // to set focus only once
        }
Vladik Y
sumber
3
Hanya berfungsi jika kontrol langsung di dalam Window, tidak jika itu bersarang di dalam UserControl.
Joe White
8

Setelah memiliki 'WPF Initial Focus Nightmare' dan berdasarkan pada beberapa jawaban di stack, berikut ini terbukti bagi saya untuk menjadi solusi terbaik.

Pertama, tambahkan App.xaml OnStartup () berikut ini:

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

Kemudian tambahkan acara 'WindowLoaded' juga di 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));

        }));
    }

Masalah threading harus digunakan karena fokus awal WPF sebagian besar gagal karena beberapa kondisi ras kerangka.

Saya menemukan solusi berikut yang terbaik karena digunakan secara global untuk seluruh aplikasi.

Semoga ini bisa membantu ...

Oran

OrPaz
sumber
5
Gunakan BeginInvokebukannya Sleep(100)pernyataan menakutkan itu .
l33t
8

Anda dapat dengan mudah mengatur kontrol itu sendiri sebagai elemen fokus di XAML.

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

Saya tidak pernah mencoba mengatur ini dalam kontrol pengguna dan melihat apakah ini berfungsi, tetapi mungkin.

Simon Gillbee
sumber
Kedengarannya menarik, karena Anda tidak perlu memberi nama kontrol hanya untuk masalah fokus. Di sisi lain, pengujian saya dengan kontrol pengguna tidak berhasil.
heringer
Itu tidak mengejutkan saya @heringer ... itu seperti mencoba untuk menetapkan fokus pada <border> atau kontrol non-interaktif serupa. Anda bisa mencoba menerapkan atribut FocusedElement ini pada kontrol interaktif di dalam kontrol pengguna. Tapi itu mungkin bukan pilihan.
Simon Gillbee
Saya telah menggunakan pendekatan ini pada stackpanel untuk mengatur tombol anak mana yang ingin saya fokuskan setelah formulir dimuat. Terima kasih banyak
Julio Nobre
Hati-hati, ini bisa membuat binding benar-benar rusak. stackoverflow.com/questions/30676863/…
Der_Meister
2

Versi minimal dari jawaban Mizipzor untuk 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));
}

Gunakan di XAML Anda:

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

Jika Anda seperti saya, dan Anda menggunakan beberapa kerangka kerja yang, entah bagaimana, mengacaukan perilaku fokus dasar, dan membuat semua solusi di atas tidak relevan, Anda masih dapat melakukan ini:

1 - Catat elemen yang mendapatkan fokus (apa pun itu!)

2 - Tambahkan ini dalam kode Anda di belakang xxx.xaml.cs

private bool _firstLoad;

3 - Tambahkan ini pada elemen yang mendapatkan fokus pertama:

GotFocus="Element_GotFocus"

4 - Tambahkan metode Element_GotFocus dalam kode di belakang, dan tentukan elemen bernama WPF yang membutuhkan fokus pertama:

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

5 - Kelola acara yang Dimuat

dalam XAML

Loaded="MyWindow_Loaded"   

dalam xaml.cs

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

Semoga ini akan membantu sebagai solusi resor terakhir

G.Dealmeida
sumber
0

Saya juga menghadapi masalah yang sama. Saya memiliki tiga kotak teks di dalam wadah kanvas dan ingin kotak teks pertama difokuskan ketika kontrol pengguna terbuka. Kode WPF mengikuti pola MVVM. Saya membuat kelas perilaku terpisah untuk memfokuskan elemen dan mengikatnya dengan pandangan saya seperti ini.

Kode perilaku kanvas

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

    }

}

Kode untuk dilihat

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

Solusi di atas tidak berfungsi seperti yang diharapkan untuk saya, saya telah sedikit mengubah perilaku yang diusulkan oleh Mizipzor sebagai berikut:

Dari bagian ini

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

Untuk ini

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

Dan saya tidak melampirkan perilaku ini ke Window atau UserControl, tetapi untuk mengontrol saya ingin fokus pada awalnya, misalnya:

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

Oh, maaf untuk penamaan yang berbeda, saya menggunakan nama InitialFocus untuk properti terlampir.

Dan ini bekerja untuk saya, mungkin bisa membantu orang lain.

BrightShadow
sumber
-1
<Window FocusManager.FocusedElement="{Binding ElementName=yourControlName}">
dnk.nitro
sumber
3
Harap berikan lebih banyak konteks untuk jawaban Anda.
Anirudh Ramanathan