在 Silverlight 中使用 XAML 为控件设置焦点
使用 XAML 中的事件和触发器在 Silverlight 中设置控件焦点。
引言
当我刚开始使用 Silverlight 时,我惊讶地发现没有简单的方法可以将光标焦点设置到文本框。我刚刚构建了一个登录页面,希望光标默认聚焦到用户名文本框。在搜索了博客和论坛后,我终于找到了一个令我满意的解决方案。我希望这篇文章能帮助您避免在论坛回复中看到的一些一句话回复带来的挫败感。
背景
我的解决方案的基础是利用触发器操作来调用控件的 Focus()
方法,然后使用事件触发器来执行该触发器操作。附件的示例包括 SetFocusTrigger
以及通过 Loaded
事件、Click
事件和(可能引起争议的)DataTrigger
使用它的示例。我将在文章结尾处详细介绍 DataTrigger
,特别是为了安抚那些尖叫着“视图模型不应该告诉视图哪个控件应该获得焦点”的 MVVM 支持者。
既然我们谈论 MVVM,我非常喜欢 MVVM,我就提一下我没有将附加的源代码示例做成 MVVM 应用程序,以保持简单。我强烈建议您不要像我在示例中所做的那样设置 DataContext
。有很多好文章可以向您展示正确的方法。
使用代码
在我们开始研究触发器和 XAML 之前,我们应该首先解决的一个问题是如何将焦点设置到 Silverlight 插件。如果我们不在应用程序加载之初就这样做,那么您可以随意多次设置控件的焦点,但直到您点击 Silverlight 应用程序(即手动将焦点设置到 Silverlight 插件)之前,您都看不到光标。常见的做法是使用 HtmlPage
静态类来设置插件的焦点,这需要在启动页面(例如,*MainPage.xaml*)中完成。
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(OnLoaded);
}
void OnLoaded(object sender, RoutedEventArgs e)
{
HtmlPage.Plugin.Focus();
}
当然,如果您的应用程序是脱机运行的,您就不需要上面的代码(实际上,它会抛出异常)。
接下来,我们需要一个 TriggerAction
来调用目标控件的 Focus()
方法。我选择了 TargetedTriggerAction
,尽管在某些情况下您也可以使用 TriggerAction
。我发现 TargetedTriggerAction
给了我更多的灵活性。所以这段代码非常简单。
public class SetFocusTrigger : TargetedTriggerAction<Control>
{
protected override void Invoke(object parameter)
{
if (Target == null) return;
Target.Focus();
}
}
通过继承 TargetedTriggerAction<control>
来创建一个 TargetedTriggerAction
。control
可以是任何 Silverlight 控件,所以如果您只想设置 TextBox
控件的焦点,您可以继承自 TargetedTriggerAction<TextBox>
。我将其保持通用,使用 Control
,以便它支持任何类型的 Silverlight 控件。要解析 TargetedTriggerAction
,您需要添加对 System.Windows.Interactivity.dll 的引用。这个 DLL 来自 Expression Blend 和 Expression Blend SDK(可以从 Microsoft 免费下载)。
通常,您在触发器操作中需要做的唯一一件事就是重写 Invoke
方法。Target
属性通过 TargetName
属性在触发器操作 XAML 中设置(如下所示)。
现在我们可以开始处理 XAML 了。在您的页面上,首先添加 System.Windows.Interactivity.dll 的命名空间,如下所示:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
为了在页面加载后将焦点设置到 TextBox
,您需要在 LayoutRoot
(或任何控件)之后放置一个 Triggers
集合,选择 Loaded
事件作为触发器,并选择 SetFocusTrigger
作为要运行的类。在 SetFocusTrigger
声明中,您使用 TargetName
属性来指定要接收焦点的控件的名称。
<StackPanel x:Name="LayoutRoot" Background="White">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<local:SetFocusTrigger TargetName="StartHere"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox x:Name="StartHere"/>
</StackPanel>
请注意,在上面的示例中,您还需要为 local
添加一个命名空间,该命名空间将解析到您的 SetFocusTrigger
类的命名空间。
这就是最常见的用例,您希望在页面加载后将焦点默认设置到特定控件。有时,您也希望焦点根据用户的操作而改变,例如点击某个控件。下面的代码片段展示了如何使用 Button
控件实现这一点。
<Button x:Name="MoveCursor"
Content="Click here to move cursor to test box below">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetFocusTrigger TargetName="MoveHereUsingClickEventTrigger"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBox x:Name="MoveHereUsingClickEventTrigger"/>
最后一个示例是使用 DataTrigger
事件。这就是一些 MVVM 传教士会告诉您不要这样做的部分,但我遇到过一些用例,这才是最佳解决方案。使用 DataTrigger
(意味着使用来自视图模型的更改)的主要反对意见是,视图模型不应该了解视图,因此它不应该告诉视图光标应该去哪里。尽管这听起来是个好论点,但实际上存在一些情况,视图需要根据视图模型中绑定属性的更改来决定光标应该去哪里。因此,视图模型实际上并没有告诉视图光标应该去哪里,只是告诉它某些东西已经改变了。然后视图决定光标应该去哪里。希望这能满足 MVVM 设计模式。这是我们的做法。首先,您需要添加对 Microsoft.Expression.Interactions.dll 的引用,它也是 Expression Blend 和 Expression Blend SDK 的一部分。然后在 XAML 中,为该 DLL 添加一个命名空间。
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
现在您可以在 Triggers
集合中使用 DataTrigger
事件,并将其绑定到您正在观察的视图模型中的属性。下面的示例非常刻意,以保持简单。视图模型更改不太可能是用户与视图交互的结果,而是来自其他事件(例如,在使用 PRISM 时,订阅方法从 EventAggregator
接收到一个更改属性值的事件)。
<StackPanel x:Name="LayoutRoot" Background="White">
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding IsDoingSomething}" Value="true">
<local:SetFocusTrigger TargetName="MoveHereUsingDataTrigger"/>
</ei:DataTrigger>
</i:Interaction.Triggers>
<CheckBox x:Name="MoveCursorVm"
IsChecked="{Binding IsDoingSomething, Mode=TwoWay}"
Content="Check this to move cursor to text box below"
Click="MoveCursorVmClick"/>
<TextBox x:Name="MoveHereUsingDataTrigger"/>
</StackPanel>
尽管这次解释相当冗长,但当您查看示例代码时,希望您会意识到这是一个非常简单的解决方案(而且,让我们面对现实吧,设置控件焦点应该很简单——在 ASP.NET 和 WinForms 时代一直很简单)。
历史
- 2011 年 7 月 - 初始发布。