WPF và trọng tâm ban đầu


190

Có vẻ như khi một ứng dụng WPF khởi động, không có gì tập trung.

Điều này thật kỳ quái. Mọi khung công tác khác mà tôi đã sử dụng thực hiện đúng như những gì bạn mong đợi: đặt trọng tâm ban đầu vào điều khiển đầu tiên theo thứ tự tab. Nhưng tôi đã xác nhận rằng đó là WPF, không chỉ là ứng dụng của tôi - nếu tôi tạo một Cửa sổ mới và chỉ cần đặt TextBox vào đó và chạy ứng dụng, TextBox sẽ không tập trung cho đến khi tôi nhấp vào nó hoặc nhấn Tab . Kinh quá.

Ứng dụng thực tế của tôi phức tạp hơn chỉ là một TextBox. Tôi có một vài lớp UserControls trong UserControls. Một trong những UserControls có trình xử lý Focusable = "True" và KeyDown / KeyUp và tôi muốn nó có tiêu điểm ngay khi cửa sổ của tôi mở. Mặc dù vậy, tôi vẫn là một người mới làm quen với WPF và tôi không gặp nhiều may mắn khi tìm ra cách để làm điều này.

Nếu tôi khởi động ứng dụng của mình và nhấn phím Tab, thì tiêu điểm sẽ chuyển sang điều khiển có thể lấy nét của tôi và nó bắt đầu hoạt động theo cách tôi muốn. Nhưng tôi không muốn người dùng của mình phải nhấn Tab trước khi họ có thể bắt đầu sử dụng cửa sổ.

Tôi đã chơi xung quanh với FocusManager.F FocusedEuity, nhưng tôi không chắc nên cài đặt điều khiển nào (Cửa sổ cấp cao nhất? Phụ huynh có chứa điều khiển có thể lấy nét? Bản thân điều khiển có thể lấy nét?) Hoặc cài đặt điều khiển nào.

Tôi cần làm gì để có được sự kiểm soát sâu sắc của mình để có sự tập trung ban đầu ngay khi cửa sổ mở ra? Hoặc tốt hơn nữa, để tập trung kiểm soát tập trung đầu tiên theo thứ tự tab?

Câu trả lời:


164

Điều này cũng hoạt động:

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

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

4
Tôi ngạc nhiên Tôi là người đầu tiên nhận xét về điều này. Tôi đã bối rối không biết điều này đã đi đâu vì nó có thể đi vào hầu hết mọi sự kiểm soát. Để trả lời cho câu hỏi cụ thể này, tôi nghĩ rằng nó sẽ xuất hiện trên cửa sổ, nhưng bạn có thể đọc các nhận xét trên msdn.microsoft.com/en-us/l Library / Lỗi để hiểu cách bạn kiểm soát vấn đề này.
Joel McBeth

Tôi đã sử dụng phương pháp này trên stackpanel thành công. Nếu một người quan tâm, có một ví dụ tại stackoverflow.com/a/2872306/378115
Julio Nobre

Điều này làm việc cho tôi tốt hơn nhiều so với câu trả lời được chấp nhận bởi vì tôi cần tập trung vào yếu tố đứng sau.
Puterdo Borato

163

Tôi đã có ý tưởng sáng suốt để tìm hiểu về Reflector để xem tài sản Focusable được sử dụng ở đâu và tìm đường đến giải pháp này. Tôi chỉ cần thêm đoạn mã sau vào hàm tạo của Window:

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

Điều này sẽ tự động chọn điều khiển đầu tiên theo thứ tự tab, vì vậy đây là một giải pháp chung có thể được thả vào bất kỳ cửa sổ nào và Chỉ cần làm việc.


21
Thêm biến điều đó thành một hành vi. <Window FocusBehavior.F FocusFirst = "true"> ... </ Window>
wekempf

6
@wekempf, tôi không quen với ý tưởng về hành vi, nhưng tôi đã xem xét nó và đó không phải là một ý tưởng tồi. Nếu bất kỳ ai khác (như tôi) chưa quen với các hành vi đính kèm, thì đây là lời giải thích: codeproject.com/KB/WPF/AttachedBehaviors.aspx
Joe White

1
Ngoài ra, điều này hoạt động nếu phần tử mong muốn là UserControl chứa phần tử có thể tập trung thực tế (ngay cả trong phân cấp sâu). Tuyệt quá!
Daniel Albuschat

1
Ý tưởng tuyệt vời, nhưng đôi khi nó không hoạt động nếu điều khiển chấp nhận tiêu điểm là a Button. Để khắc phục điều này, tôi lật MoveFocuscuộc gọi qua bộ điều phối ContextIdleưu tiên ( Backgroundhoặc cao hơn không hoạt động). Ngoài ra, có một FocusNavigationDirection.Firstđiều phù hợp với ý định tốt hơn và làm điều tương tự trong trường hợp này.
Anton Tykhyy

đó nên là hành vi mặc định! Yuck (trong bài viết gốc) là đúng!
NH.

61

Dựa trên câu trả lời được chấp nhận được thực hiện như một hành vi đính kèm:

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

Sử dụng nó như thế này:

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

6
Theo tôi, đây là bởi đến nay các giải pháp tốt nhất mà tôi đã được tìm thấy. Cảm ơn!
Shion

1
Có một lỗi trong mã trong câu trả lời này trong cuộc gọi đến DependencyProperty.RegisterAttached. Tham số thứ ba nên typeof(FocusBehavior), không typeof(Control). Thực hiện thay đổi này sẽ ngăn người thiết kế báo cáo thuộc tính 'FocusFirst' đã được đăng ký bởi lỗi 'Kiểm soát'.
Tony Vitabile

@TonyVitabile Đã sửa. Bạn luôn tự do chỉnh sửa và cải thiện câu trả lời nếu có thể. :)
Mizipzor

Không nên kiểm soát. Trình xử lý sự kiện đã tải được hủy đăng ký trong khi dỡ tải?
andreapier

@andreapier Bạn có thể nếu bạn quan tâm, nhưng bỏ qua việc hủy đăng ký sẽ không gây rò rỉ bộ nhớ hoặc bất cứ điều gì. Bạn chỉ cần lo lắng về các sự kiện gây rò rỉ bộ nhớ nếu một đối tượng tồn tại ngắn có một phương thức được gắn vào một sự kiện trên một đối tượng tồn tại lâu. Trong trường hợp này, thời gian tồn tại là của cửa sổ, vì vậy bạn ổn.
Joe White

14

Tôi tìm thấy một giải pháp khả thi khác. Mark Smith đã đăng một tiện ích mở rộng đánh dấu FirstF FocusedEuity để sử dụng với FocusManager.F FocusedEuity.

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

Hoàn toàn trơn tru! Cảm ơn bạn!
Andy

8

Sau khi có 'Cơn ác mộng tập trung ban đầu của WPF' và dựa trên một số câu trả lời trên stack, những điều sau đây đã chứng minh cho tôi là giải pháp tốt nhất.

Đầu tiên, thêm App.xaml OnStartup () của bạn như sau:

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

Sau đó, thêm sự kiện 'WindowLoaded' trong 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));

        }));
    }

Vấn đề luồng phải được sử dụng làm tiêu điểm ban đầu của WPF hầu hết không thành công do một số điều kiện chạy đua khung.

Tôi tìm thấy giải pháp sau đây tốt nhất vì nó được sử dụng trên toàn cầu cho toàn bộ ứng dụng.

Hy vọng nó giúp...

Oran


5
Sử dụng BeginInvokethay cho Sleep(100)tuyên bố đáng sợ đó .
l33t

8

Có cùng một vấn đề đã giải quyết nó bằng giải pháp đơn giản: Trong cửa sổ chính:

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

Trong điều khiển người dùng:

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

3
Chỉ hoạt động nếu điều khiển trực tiếp bên trong Cửa sổ, không hoạt động nếu nó được lồng bên trong UserControl.
Joe White

8

Bạn có thể dễ dàng đặt bộ điều khiển làm thành phần tập trung trong XAML.

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

Tôi chưa bao giờ thử thiết lập điều này trong một điều khiển người dùng và xem nếu nó hoạt động, nhưng nó có thể.


Nghe có vẻ thú vị, vì bạn không đặt tên cho điều khiển chỉ dành cho vấn đề trọng tâm. Mặt khác, thử nghiệm của tôi với kiểm soát người dùng không hoạt động.
heringer

Điều đó không làm tôi ngạc nhiên @heringer ... điều đó sẽ giống như cố gắng tập trung vào <viền> hoặc điều khiển không tương tác tương tự. Bạn có thể thử áp dụng thuộc tính FocusedEuity này trên một điều khiển tương tác bên trong điều khiển người dùng. Nhưng đó có thể không phải là một lựa chọn.
Simon Gillbee

Tôi đã sử dụng phương pháp này trên stackpanel để đặt nút con nào tôi muốn lấy nét sau khi biểu mẫu được tải. Cảm ơn rất nhiều
Julio Nobre

Hãy cẩn thận, nó có thể làm cho các ràng buộc hoàn toàn bị phá vỡ. stackoverflow.com/questions/30676863/
Mạnh

2

Một phiên bản tối thiểu của câu trả lời của Mizipzor cho 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));
}

Sử dụng trong XAML của bạn:

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

1

Nếu bạn giống như tôi và bạn đang sử dụng một số khung công tác, bằng cách nào đó, làm rối tung các hành vi tập trung cơ bản và làm cho tất cả các giải pháp trên không liên quan, bạn vẫn có thể làm điều này:

1 - Lưu ý yếu tố lấy trọng tâm (dù đó là gì!)

2 - Thêm mã này vào mã của bạn phía sau xxx.xaml.cs

private bool _firstLoad;

3 - Thêm phần này vào phần tử lấy trọng tâm đầu tiên:

GotFocus="Element_GotFocus"

4 - Thêm phương thức Element_GotF Focus trong mã phía sau và chỉ định phần tử có tên WPF, người cần tiêu điểm đầu tiên:

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

5 - Quản lý sự kiện đã tải

trong XAML

Loaded="MyWindow_Loaded"   

trong xaml.cs

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

Hy vọng điều này sẽ giúp như là một giải pháp cuối cùng


0

Tôi cũng phải đối mặt với vấn đề tương tự. Tôi đã có ba hộp văn bản bên trong thùng chứa vải và muốn hộp văn bản đầu tiên được tập trung khi điều khiển người dùng mở ra. Mã WPF theo mẫu MVVM. Tôi đã tạo một lớp hành vi riêng để tập trung vào phần tử và liên kết nó với quan điểm của tôi như thế này.

Mã hành vi Canvas

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

    }

}

Mã để xem

<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

Giải pháp trên không hoạt động như mong đợi đối với tôi, tôi đã thay đổi một chút hành vi do Mizipzor đề xuất như sau:

Từ phần này

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

Để điều này

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

ANd Tôi không đính kèm hành vi này vào Window hoặc UserControl, nhưng để kiểm soát tôi muốn tập trung ban đầu, ví dụ:

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

Ồ, xin lỗi vì cách đặt tên khác tôi đang sử dụng tên InitialF Focus cho thuộc tính đính kèm.

Và điều này đang làm việc cho tôi, có lẽ nó có thể giúp đỡ người khác.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.