WPF和初始焦点

当WPF应用程序启动时,似乎没有任何东西具有焦点。

这真的很奇怪。我所使用的其他框架所做的正是您所期望的:将初始焦点放在选项卡顺序中的第一个控件上。但我已经确认,这是WPF,不只是我的应用程序-如果我创建一个新的窗口,只是把一个文本框在它,并运行应用程序,文本框没有焦点,直到我点击它或按Tab。讨厌的东西。

我的实际应用程序比一个文本框更复杂。我在UserControls中有几个UserControls层。其中一个UserControls有Focusable="True"和KeyDown/KeyUp处理程序,我希望它在我的窗口打开时就有焦点。不过,我在某种程度上仍然是一个WPF新手,我没有太多的运气来弄清楚如何做到这一点。

如果我启动我的应用并按下Tab键,焦点就会转到我的可聚焦控件,它开始以我想要的方式工作。但我不希望我的用户在开始使用窗口之前必须按Tab键。

我使用过FocusManager。FocusedElement,但我不确定要在哪个控件上设置它(顶级窗口?包含可聚焦控件的父控件?可聚焦控件本身?)或者设置为什么。

我需要做什么让我的深嵌套控件有初始焦点,只要窗口打开?或者更好,聚焦第一个可聚焦控件在制表符的顺序?

99387 次浏览

我有一个聪明的想法,通过Reflector挖掘,看看Focusable属性被使用的地方,并找到了这个解决方案。我只需要添加以下代码到我的窗口的构造函数:

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

这将自动选择选项卡顺序中的第一个控件,所以这是一个通用的解决方案,应该能够被拖放到任何窗口和只是工作。

我找到了另一个可能的解决方案。Mark Smith发布了一个FirstFocusedElement标记扩展用于FocusManager.FocusedElement。

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

这也有用:

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


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

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

首先,添加你的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最初的焦点主要是由于一些框架竞争条件而失败。

我发现下面的解决方案是最好的,因为它是用于整个应用程序的全局。

希望能有所帮助……

奥兰

基于被接受的作为附加行为实现的回答:

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">
<Window FocusManager.FocusedElement="{Binding ElementName=yourControlName}">
同样的问题用简单的方法解决了: 在主窗口中:

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

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

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

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

我也面临着同样的问题。我在画布容器内有三个文本框,并希望第一个文本框在用户控件打开时被聚焦。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>

c# 6+的Mizipzor的回答的最小版本。

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 -注意得到焦点的元素(不管是什么!)

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

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

上述解决方案并没有像我预期的那样工作,我已经稍微改变了由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的名称为附加属性。

这对我很有用,也许能帮助到其他人。