Completely Controlling Tab Order in Silverlight

I’ve just started a new job as a more pure software developer (as opposed to PM/BA)  so you can expect to see more blog posts of a purely technical nature here in the coming months. Today, we’ll be taking look at controlling tab order in complex silverlight forms.

The Problem

I was inspired to write this post after hours of fruitless googling to build a silverlight data entry form with a series of dynamic controls lists. The client wanted a form whose questions were populated from a separate database. That requirement in and of itself is fairly straight forward, just a matter of using an ItemsControl or one of it’s descendants. However, what I found is that these controls generated a whole series of extra hidden controls that captured tabs, confusing the user and slowing down the data entry process. In searching for a solution I found little documentation on the whole question of tab order in general. Eventually I managed to piece together a solution and understanding that seemed like good blogging material. In this post I hope to offer a fairly comprehensive view of the Silverlight tab and focus model, as well as providing techniques to debug and control tab and focus issues.

The Preliminaries

Before we get into the real meat of the post, we need to spend a little time reviewing the key pieces of Silverlight’s tab and focus model. On the surface, the model appears pretty simple. Most controls have  the “IsTabStop” property that controls whether they participate in tab order at all. For more fine grained control, you can use the “TabIndex” property to set an explicit order for items in the same container control(like a stackpanel or grid). Finally if you have a container control you can use the “TabNavigation” property to affect how the tab order treats the children of that container. The default value, “Local” will allow you to tab inside of the container. “Once” will mean only the parent container will participate in tab order, and “Cycle” means that the tab order will not exit from the container unless the user clicks outside of it.

For most situations this will give you pretty fine grained control over keyboard tabbing and focus. However, once you have an app where you’re dynamically generating controls using ItemsControls, ListBoxes, TabControls, etc. things get more complicated. Certain composite controls in the silverlight toolkit also cause problems, since each one of the controls that makes them up may end up claiming an individual tab. And if you’ve got container control’s nested inside of eachother, it may be difficult to simply set tab index across the board.

Knowing is Half the Battle

What makes debugging these issues especially difficult is that Silverlight doesn’t really make it very clear what control has focus. Most UI elements will get a thin blue border when they have keyboard focus, but some controls, like ContentControl or ListBoxItem have no visual indication they’ve been selected. To resolve this, I ended up creating a static helper class based off of this blog post.  http://codeblog.larsholm.net/2009/12/focushelper/ The code of the class is below

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Threading;
using System.Diagnostics;

public static class FocusHelper
{
    public static void Start(TextBox debugBox)
    {
        focusBorderBrush = new SolidColorBrush(Colors.Red);
        focusBackground = new SolidColorBrush(Colors.Red);
        focusBackground.Opacity = 0.1;

        focusTimer = new Timer(new TimerCallback((o) =>
        {
            try
            {
                System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    object temp = null;

                    if (System.Windows.Application.Current.Host.Content.ZoomFactor == 1)
                        temp = FocusManager.GetFocusedElement();

                    if (temp != lastFocus)
                    {
                        if (temp is Control)
                        {
                            Control conTemp = temp as Control;
                            var conTempParent = conTemp.Parent;
                            //Give the last control back its original color
                            if (lastFocus != null)
                            {
                                lastFocus.BorderBrush = lastBrush;
                                lastFocus.BorderThickness = lastThickness;
                                lastFocus.Background = lastBackground;
                            }

                            lastFocus = temp as Control;
                            lastBrush = lastFocus.BorderBrush;
                            lastThickness = lastFocus.BorderThickness;
                            lastBackground = lastFocus.Background;

                            lastFocus.BorderBrush = focusBorderBrush;
                            lastFocus.BorderThickness = new Thickness(1);
                            lastFocus.Background = focusBackground;

                            Debug.WriteLine("Current Focus: Control " + conTemp.Name + " of Type " + conTemp + " in " + conTempParent);

                        }
                    }
                });
            }
            catch
            {
            }

        }), null, 0, 100);
    }

    private static System.Threading.Timer focusTimer;
    private static Control lastFocus = null;
    private static Thickness lastThickness;
    private static Brush lastBrush;
    private static Brush lastBackground;
    private static Brush focusBorderBrush;
    private static Brush focusBackground;
}

Simply put, once the Start() method of this class has been invoked, it will constantly poll the FocusManager.GetFocusedElement() method of Silverlight to determine which control has focus. It will put a more noticeable red border around that control, and it will write the control’s name (if it has one), the type, and the type of it’s parent, to the Output window of Visual Studio. In  my case, I just put FocusHelper.Start() in the Load method of the view I was debugging and wrapped it in a compiler directive to ensure it’s only run in debug mode.

With this in place, it’s now easy to figure out what control’s may be stealing tab order. In my case the two main culprits were the ListBoxItem and the ContentControl.

Styling More Than Style

Once I knew what controls were disrupting my tab order, the next step was to set their “IsTabStop” property to false. The only problem is that these controls weren’t declared anywhere in the xaml, they were being autogenerated by the template of Silverlight’s ListBox and ItemsControls.

In order to set the properties of those autogenerated controls, we can make use of a new Silverlight 4 feature, implicit styling. Implicit styling allows us to declare a style and have it automatically apply to all controls of a certain type that are within the style’s scope. What’s important to note about styling in Silverlight, is that it goes beyond just visual properties. Style can be used to set just about any property of a control, including “IsTabStop” false.

With implicit styling, the concept of scope is key. Based on the parent control you declare the style inside of, it will only affect controls that are children of that parent. In my case, I specifically wanted the controls that were children of my ListBoxes and ItemControls’. To get at these I declare my styles under the Resources property of the relevant ListBoxes and Item’s Controls like so

<ItemsControl IsTabStop="False" ItemsSource="{Binding Path=Items}">
	<ItemsControl.Resources>
		<Style TargetType="ContentControl">
			<Setter Property="IsTabStop" Value="false"/>
		</Style>
	</ItemsControl.Resources>
	<ItemsControl.ItemTemplate>

Now all of those autogenerated controls will end up with their IsTabStop property appropriately set to false.

What’s Missing

Now there’s one thing I haven’t covered here, which is controlling the Tab Index when you have dynamically generated controls. Fortunately in my case, the order of the controls on the page matched up with the order I wanted the user to tab through them.  I can forsee some complicated scenario’s where you might want to be autogenerating that number for a set of dynamic controls. I’ll leave that one as an exercise to the readers, as my math professor used to say.

Advertisements

2 thoughts on “Completely Controlling Tab Order in Silverlight

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s