Geeks With Blogs
Kyle Burns

I spend a lot of time building applications geared towards heavy duty data entry.  To me this means you have a lot of end users spending all day, every day cramming data into your app as quickly as possible.  It also means that I need a fair amount of control over how the keyboard interacts with the application in order to provide a customized experience when necessary (it’s also important to recognize when the default experience may not be what is initially asked for but provides sufficiently for needs).  I’ve seen a lot of conversation on the message boards regarding how to achieve greater control over the keyboard in Silverlight applications and thought I would take the opportunity to discuss how attached properties can be used to realize one “real world” use case.

Attached properties are a feature of XAML that allows for properties to be set on objects other than those actually owning the property.  When set, the property values are persisted in a global property store and acted upon by the owning type.  One very commonly seen example of the attached property is in positioning an object on its parent Canvas.  Canvas exposes Top and Left attached properties that when set on a child of the Canvas control its layout position.  In this example, we’ll create a custom attached property with which we will add KeyUp functionality to control navigation on the form with the keyboard left and right arrows.

As a sneak peek, let me show you how pretty our XAML making use of the code we’re about to write will be:

<TextBox Width="50" x:Name="target1"
         Nav:ArrowNavigationService.RightArrowTarget="{Binding ElementName=target2}"
         Nav:ArrowNavigationService.LeftArrowTarget="{Binding ElementName=target6}" />

Since we are essentially just writing .Net code (in my case I use C# whenever possible), we will start as in any C# application with a class.  This class will provide the service of handling arrow key navigation, so in my usually unimaginative manner I will call it ArrowNavigationService.

We will first need a couple of DependencyProperties to handle all the plumbing.  One represents an attached property to control behavior when the left arrow is pressed and the other controls the behavior for the right arrow.  We’ll use the static RegisterAttached method of DependencyProperty to establish the properties, specify they are intended to be attached to anything of type Control, and wire the change handler (we can use the same handler for both).  The declaration looks like this:

public static DependencyProperty RightArrowTargetProperty =   DependencyProperty.RegisterAttached("RightArrowTarget",                                                                           typeof(Control),                                                                           typeof(ArrowNavigationService),                                                                           new PropertyMetadata(null, OnArrowTargetChanged));

public static DependencyProperty LeftArrowTargetProperty = DependencyProperty.RegisterAttached("LeftArrowTarget",                                                           typeof(Control),                                                           typeof(ArrowNavigationService),                                                           new PropertyMetadata(null, OnArrowTargetChanged));

Because DependencyProperties are a creature separate from standard CLR properties, you don’t get to use the standard property get/set syntax and instead need to write method pairs for getting and setting the property values.  Our sets are as follows:

public static void SetRightArrowTarget(DependencyObject obj, ArrowNavigationService t)
{
    obj.SetValue(RightArrowTargetProperty, t);
}

public static ArrowNavigationService GetRightArrowTarget(DependencyObject obj)
{
    return (ArrowNavigationService)obj.GetValue(RightArrowTargetProperty);
}

public static void SetLeftArrowTarget(DependencyObject obj, ArrowNavigationService t)
{
    obj.SetValue(LeftArrowTargetProperty, t);
}

public static ArrowNavigationService GetLeftArrowTarget(DependencyObject obj)
{
    return (ArrowNavigationService)obj.GetValue(LeftArrowTargetProperty);
}

That takes care of the “plumbing” portion of setting up your class to expose attached properties and the real implementation details comes in the handler that gets called when the property changes (for example, when the initial value is set during the loading of the page).  The handler accepts the DependencyObject to which the property is being attached (the control declaring the properties in the XAML) and DependencyPropertyChangedEventArgs which exposes the value to which the property is being changed.  The code is simply attaching to the KeyUp event handler of the control on which the property is set (to be complete it should also detach the event from the e.OldValue but we’ll leave that out for now).  The part that bears a little explanation is the recursive walking through the chain of targets until one is found that can be focused or the end of the chain is reached.  This is to handle situations where based on the state of your application controls that would otherwise have been “next” are not available.  Here is the code:

private static void OnArrowTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var uiElement = d as UIElement;
            var controlToFocus = e.NewValue as Control;
            if (uiElement != null && controlToFocus != null)
            {
                uiElement.KeyUp += (sender, args) =>
                {
                    if ((args.Key == Key.Right && e.Property == RightArrowTargetProperty)
                        || (args.Key == Key.Left && e.Property == LeftArrowTargetProperty))
                    {
                        try
                        {
                            var focusable = GetNextFocusableControl(controlToFocus, e.Property);
                            if (focusable != null)
                            {
                                focusable.Focus();
                            }
                        }
                        finally
                        {
                            args.Handled = true;
                        }
                    }
                };
            }
        }

private static Control GetNextFocusableControl(Control target, DependencyProperty direction)
        {
            if (target.IsEnabled && target.Visibility == Visibility.Visible)
            {
                return target;
            }
            var newTarget = target.GetValue(direction) as Control;
            return newTarget != null ? GetNextFocusableControl(newTarget, direction) : null;
        }

Hopefully this posting has given a little bit of insight into the use of attached properties AND provided you with a useful morsel that you can use the next time you need to reach out and take control of the keyboard in your application.

Posted on Monday, March 28, 2011 6:24 PM | Back to top


Comments on this post: Keyboard Navigation In Silverlight Data Entry Forms

# re: Keyboard Navigation In Silverlight Data Entry Forms
Requesting Gravatar...
if you're trying to accomplish something like this using HTML, than you need a JavaScript solution and not Silverlight. You should take a look at the KeyPress event on your document. At least in IE, you're able to change the keycode in the event handler, so you could take the arrow keys (keycode 91 and 92) and translate them to tabs.
Left by Kyle on Mar 31, 2011 9:24 PM

Your comment:
 (will show your gravatar)


Copyright © Kyle Burns | Powered by: GeeksWithBlogs.net