Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Setting focus on a control in Silverlight using XAML

0.00/5 (No votes)
8 Jul 2011 1  
Set focus on a control in Silverlight using events and triggers in XAML.

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here