Introduction
When I first started using Silverlight, I was amazed that there wasn't an easy way to set the focus of the cursor to a text box. I had just built a login page and wanted the cursor to default to the user name text box. After searching blogs and forums, I've finally come up with a solution that satisfies me. I hope this article helps you avoid the frustrations of some of the one-line responses you will see in some of the forum responses.
Background
The basis of my solution is to leverage a trigger action to invoke the Focus()
method on a control and then use event triggers to execute that trigger action. The attached example includes the SetFocusTrigger
and examples of using it via the Loaded
event, Click
event, and the (potentially contentious) DataTrigger
. I'll talk about the DataTrigger
a bit more at the end of the article, particularly to appease the MVVMers out there that are screaming "the View Model shouldn't tell the View which control should have focus".
Since we're talking MVVM, of which I'm a huge fan, I'll just raise that I didn't make the attached source code example a MVVM application, to keep it simple. I would strongly recommend that you don't set a DataContext
the way I have in the example. There are plenty of good articles to show you the right way.
Using the code
The first issue we should resolve before we get into the trigger and XAML is to set the focus on the Silverlight plug-in. If we don't do that at the very start of the application loading, then you can set the focus on a control as many times as you like, but you won't see the cursor until you click on the Silverlight application (effectively manually setting the focus on the Silverlight plug-in). The common way to do this is to use the HtmlPage
static class to set the plug-in focus, in the starting page (for example, the MainPage.xaml).
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(OnLoaded);
}
void OnLoaded(object sender, RoutedEventArgs e)
{
HtmlPage.Plugin.Focus();
}
Of course, if you're running your application out-of-browser, you don't need the code above (in fact, it will throw an exception).
Next, we need a TriggerAction
to invoke the Focus()
method on the desired control. I've gone for a TargetedTriggerAction
, although you could use the TriggerAction
as well in certain circumstances. I found the TargetedTriggerAction
gave me a bit more flexibility. So the code for this is very simple.
public class SetFocusTrigger : TargetedTriggerAction<Control>
{
protected override void Invoke(object parameter)
{
if (Target == null) return;
Target.Focus();
}
}
A TargetedTriggerAction
is created by inheriting from TargetedTriggerAction<control>
. The control
can be any Silverlight control, so you could inherit from TargetedTriggerAction<TextBox>
if you only wanted to set the focus on TextBox
controls. I've left it generic, using Control
, so that it will support any type of Silverlight control. To resolve the TargetedTriggerAction
, you'll need to add a reference to System.Windows.Interactivity.dll. This comes with Expression Blend and Expression Blend SDK (which is a free download from Microsoft).
Usually the only thing you have to do in the trigger action is override the Invoke
method. The Target
property is set in the trigger action XAML by the TargetName
property (as shown later).
Now we're ready to move onto the XAML. On your page, firstly add the namespace for System.Windows.Interactivity.dll, as follows:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
To have the focus set to a TextBox
once the page has loaded, you place a Triggers
collection after your LayoutRoot
(or any control really), select the Loaded
event as the trigger, and select the SetFocusTrigger
as the class to run. In the SetFocusTrigger
declaration, you put the name of the control that you want to receive the focus by using the TargetName
property.
<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>
Note, in the example above, you would also add a namespace for local
which would resolve to the namespace for your SetFocusTrigger
class.
So that's the most common use case, where you want the focus to default to a specific control once the page has loaded. There are also cases where you want the focus to change based on a user action, such as clicking on a control. The snippet below shows this using a Button
control.
<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"/>
And the last example is using the DataTrigger
event. Here's where some MVVM evangelists will tell you not to do this, but I've run into use cases where it was the best solution. The main objection to using a DataTrigger
, which means using a data change from the View Model, is that the View Model should have no knowledge of the View, so that implies that it should not tell the View where the cursor should go. Although that sounds like a good argument, in fact there are circumstances where the View needs to make a decision on where the cursor should go based on a change to a bound property in the View Model. So the View Model is not actually telling the View where the cursor should go, only that something has changed. The View then decides where the cursor should go. That, hopefully, then satisfies the MVVM design pattern. And this is how we do it. Firstly, you need to add a reference to Microsoft.Expression.Interactions.dll, which is also part of Expression Blend and in the Expression Blend SDK. Then in the XAML, add a namespace to that DLL.
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Now you can use the DataTrigger
event in the Triggers
collection and bind it to the property in the View Model that you are observing. Below is a very contrived example, to keep things simple. It's more likely that the View Model change would not be from a user interacting with the View, but rather from some other event (for example, a subscription method receiving an event from the EventAggregator
when using PRISM, that changes a property value).
<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>
Although this has been a rather lengthy explanation, once you look at the example code, you'll hopefully realise that this is a very simple solution (and let's face it, it should be simple to set the focus on a control - it was always simple in the ASP.NET and WinForms days).
History
- July 2011 - initial release.