WPF和最初的重点


190

似乎当WPF应用程序启动时,没有焦点。

这真的很奇怪。我使用过的所有其他框架都可以实现您所期望的:将最初的焦点放在制表符顺序中的第一个控件上。但是我已经确认它是WPF,而不仅仅是我的应用程序-如果我创建一个新的Window,然后在其中放置一个TextBox,然后运行该应用程序,则在我单击它或按Tab之前,TextBox不会具有焦点。uck

我的实际应用程序比仅TextBox更复杂。我在UserControls中有几层UserControls。这些用户控件之一具有Focusable =“ True”和KeyDown / KeyUp处理程序,我希望它在窗口打开后立即具有焦点。不过,我还是WPF的新手,我不太想办法。

如果启动我的应用程序并按Tab键,则焦点将移到我的可聚焦控件上,并且它将按照我想要的方式开始工作。但是我不希望我的用户在开始使用该窗口之前必须先按下Tab键。

我已经使用过FocusManager.FocusedElement,但是我不确定要在哪个控件上进行设置(顶级Window?包含可聚焦控件的父级?可聚焦控件本身?)或将其设置为什么。

窗口打开后,我该怎么做才能使深层嵌套的控件具有最初的焦点?还是更好地将第一个可聚焦控件按选项卡顺序聚焦?

Answers:


164

这也有效:

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

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

4
我很惊讶我是对此发表评论的第一人。我对它的去向感到困惑,因为它几乎可以进行任何控制。为了回答这个特定的问题,我认为它将在窗口中显示,但是您可以阅读msdn.microsoft.com/zh-cn/library/…上的说明,以了解将控件附加到其中的重要性。
Joel McBeth

我已经在堆栈面板上成功使用了这种方法。如果有兴趣,请访问stackoverflow.com/a/2872306/378115
Julio Nobre

这对我来说比接受的答案要好得多,因为我需要把重点放在之后的要素上。
Puterdo Borato

162

我有一个绝妙的主意,那就是在Reflector中进行挖掘,以了解使用了Focusable属性的地方,并找到了解决该问题的方法。我只需要在Window的构造函数中添加以下代码:

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

这将自动按选项卡顺序选择第一个控件,因此这是一个常规解决方案,应该可以将其放到任何窗口和Just Work中。


21
添加将其转换为行为。<Window FocusBehavior.FocusFirst =“ true”> ... </ Window>
wekempf,2009年

6
@wekempf,我对行为的概念并不熟悉,但是我调查了一下,这根本不是一个坏主意。如果其他任何人(例如我)还不熟悉附加的行为,则有一个解释:codeproject.com/KB/WPF/AttachedBehaviors.aspx
乔·怀特2009年

1
此外,如果所需元素是包含实际可聚焦元素的UserControl(即使在深层次结构中),也可以使用。大!
Daniel Albuschat 2015年

1
好主意,但如果接受焦点的控件是,那有时就行不通Button。要解决此问题,我将MoveFocus呼叫ContextIdle优先转移到调度程序上(Background或更高的级别不起作用)。同样,FocusNavigationDirection.First在这种情况下,有一个可以更好地匹配意图的功能。
安东·泰克

那应该是默认行为!uck(原帖)是对的!
NH。

61

基于实现为附加行为的公认答案

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

像这样使用它:

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

6
我认为,这是迄今为止找到的最好的解决方案。谢谢!
Shion

1
调用时此答案中的代码中有错误DependencyProperty.RegisterAttached。第三个参数应该typeof(FocusBehavior)不是typeof(Control)。进行此更改将防止设计人员报告“控件”错误已注册的“ FocusFirst”属性。
Tony Vitabile,2016年

@TonyVitabile固定。如果可以的话,您始终可以自由编辑和改进答案。:)
Mizipzor '16

Control.Loaded事件处理程序是否应在卸载期间注销?
andreapier

@andreapier如果可以的话,可以这样做,但是跳过注销将不会导致内存泄漏或其他任何问题。您只需要担心如果短期对象具有附加到长期对象上的事件的方法,则事件会导致内存泄漏。在这种情况下,生存期就是窗口的生存期,所以您就可以了。
乔·怀特

14

我找到了另一个可能的解决方案。马克·史密斯(Mark Smith)发布了与FocusManager.FocusedElement一起使用的FirstFocusedElement标记扩展

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

完全光滑!谢谢!
安迪

8

在经历了“ WPF最初的焦点梦m”并基于一些答案之后,以下对我来说是最好的解决方案。

首先,将您的App.xaml OnStartup()添加如下:

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

然后在App.xaml中也添加“ WindowLoaded”事件:

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

        }));
    }

必须使用线程问题,因为WPF最初的关注主要是由于某些框架争用条件而失败。

我发现以下解决方案是最好的,因为它在整个应用程序中都被全局使用。

希望能帮助到你...

奥兰


5
使用BeginInvoke而不是那个令人恐惧的Sleep(100)声明。
l33t

8

是否有相同的问题通过简单的解决方案解决了:在主窗口中:

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

在用户控件中:

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

3
仅在控件直接位于Window内时有效,而不是嵌套在UserControl内。
乔·怀特

8

您可以轻松地将控件集本身设置为XAML中的焦点元素。

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

我从来没有尝试过在用户控件中设置它,看看是否可行,但是可能。


这听起来很有趣,因为您不必仅针对焦点问题来命名控件。另一方面,我对用户控件的测试无效。
heringer 2015年

这并不令我@heringer感到惊讶……这就像是试图将焦点放在<border>或类似的非交互式控件上一样。您可以尝试在用户控件内部的交互式控件上应用此FocusedElement属性。但这可能不是一个选择。
西蒙·吉尔比

我已经在堆栈面板上使用了这种方法来设置表单加载后要关注的子按钮。非常感谢
Julio Nobre

注意,这可能会使绑定完全损坏。stackoverflow.com/questions/30676863/…–
Der_Meister

2

Mizipzor对于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));
}

在您的XAML中使用:

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

1

如果您像我一样,并且正在使用一些框架,这些框架以某种方式弄乱了基本焦点行为,并且使上述所有解决方案都不相关,那么您仍然可以这样做:

1-注意获得焦点的元素(无论它是什么!)

2-将其添加到您的代码中xxx.xaml.cs的后面

private bool _firstLoad;

3-将其添加到第一个焦点的元素上:

GotFocus="Element_GotFocus"

4-在后面的代码中添加Element_GotFocus方法,并指定需要首先关注的WPF命名元素:

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

5-管理已载入事件

在XAML中

Loaded="MyWindow_Loaded"   

在xaml.cs中

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

希望这将作为最后的解决方案


0

我也面临同样的问题。我在画布容器中有三个文本框,并希望在打开用户控件时将第一个文本框对准焦点。WPF代码遵循MVVM模式。我创建了一个单独的行为类来关注元素,并将其绑定到我的视图中,如下所示。

画布行为代码

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

    }

}

查看代码

<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

上面的解决方案无法按我预期的方式工作,我对Mizipzor提出的行为进行了一些更改,如下所示:

从这部分

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

对此

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

我并没有将此行为附加到Window或UserControl,但是要进行控制,我想首先关注,例如:

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

哦,抱歉,我为附加属性使用了InitialFocus名称。

这正在为我工​​作,也许可以帮助其他人。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.