Keyboard helper class for wp7 vs tab index

I’ve recently blogged on tumblr about a way to plug the missing tab-to-next-control functionaly in wp7 when using the software keyboard (SIP). I’ve reafactored the code to make it a little easier to use, plus added a little bug fix concerning isEnabled=false controls.

<!--ContentPanel - place additional content here-->

<Grid x:Name="ContentGrid" Grid.Row="1">

  <StackPanel Orientation="Vertical">

    <CheckBox IsTabStop="True" TabIndex="4" Content="Some check"></CheckBox>

    <TextBox TabIndex="1" Text="start here" KeyUp="TextBox_KeyUp"></TextBox>

    <TextBox IsTabStop="True" Text="Won't tab here cause not set tabindex"></TextBox>

    <TextBox IsEnabled="False" TabIndex="2" Text="not here cause disabled" KeyUp="TextBox_KeyUp"></TextBox>

    <TextBox TabIndex="3" Text="next stop here" KeyUp="TextBox_KeyUp"></TextBox>

  </StackPanel>

</Grid>


A little bit of code-behind is needed;

public partial class MainPage : PhoneApplicationPage

{

    private KeyboardHelper keyboardHelper;

 

    public MainPage()

    {

        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        this.IsTabStop = true;

    }

 

    void MainPage_Loaded(object sender, RoutedEventArgs e)

    {

        this.keyboardHelper = new KeyboardHelper(this, LayoutRoot);

    }

 

    private void TextBox_KeyUp(object sender, KeyEventArgs e)

    {

        if (e.Key == Key.Enter)

        {

            keyboardHelper.HandleReturnKey(sender);

        }

    }

}

…and the supporting helper class;

// Wp7Keyboard helper by @pauliom

using System;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

using System.Collections.Generic;

using Microsoft.Phone.Controls;

 

namespace Wp7Keyboard

{

    /// <summary>

    /// Helper to provide missing tab-to-next-control functionality

    /// </summary>

    public class KeyboardHelper

    {

        private List<Control> tabbedControls;

        private Control lastTabbedControl = null;

        private Panel layoutRoot;

        private PhoneApplicationPage page;

 

        /// <summary>

        /// Constructor to support a phone page

        /// </summary>

        /// <param name="page"></param>

        /// <param name="layoutRoot"></param>

        public KeyboardHelper(PhoneApplicationPage page, Panel layoutRoot)

        {

            this.layoutRoot = layoutRoot;

            this.page = page;

            RefeshTabbedControls(layoutRoot);

        }

 

        /// <summary>

        /// Refresh the tabbed controls collection, helpful if you dynamically alter the controls

        /// </summary>

        /// <param name="layoutRoot"></param>

        public void RefeshTabbedControls(Panel layoutRoot)

        {

            this.tabbedControls = GetChildsRecursive(layoutRoot).OfType<Control>().Where(c => c.IsTabStop && c.TabIndex != int.MaxValue).OrderBy(c => c.TabIndex).ToList();

            if (this.tabbedControls != null && this.tabbedControls.Count > 0)

            {

                this.lastTabbedControl = this.tabbedControls[this.tabbedControls.Count - 1];

            }

        }

 

        // code from 'tucod'

        IEnumerable<DependencyObject> GetChildsRecursive(DependencyObject root)

        {

            List<DependencyObject> elts = new List<DependencyObject>();

            elts.Add(root);

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)

                elts.AddRange(GetChildsRecursive(VisualTreeHelper.GetChild(root, i)));

 

            return elts;

        }

 

        /// <summary>

        /// Process a return key from the client controls key-up event

        /// </summary>

        /// <param name="sender"></param>

        internal void HandleReturnKey(object sender)

        {

            Control eventSender = sender as Control;

            if (eventSender != null)

            {

                Control thisControlInTabbedList = tabbedControls.FirstOrDefault(c => c == eventSender);

                if (thisControlInTabbedList != null)

                {

                    // what's the next control?                                                                       

                    SetFocusOnNextControl(thisControlInTabbedList);

                }

            }

        }

 

        private void SetFocusOnNextControl(Control thisControlInTabbedList)

        {

            if (lastTabbedControl == thisControlInTabbedList)

            {

                // we've come the end so remove the keyboard

                this.page.Focus();

            }

            else

            {

                Control nextControl = tabbedControls.FirstOrDefault(c => c.TabIndex > thisControlInTabbedList.TabIndex);

                bool wasFocusSet = false;

                if (nextControl != null)

                {

                    wasFocusSet = nextControl.Focus();

                }

 

                if (!wasFocusSet)

                {

                    SetFocusOnNextControl(nextControl);

                }

 

            }

        }

    }

}

Hope you find it useful, again maybe it won’t be needed after RTM, finders crossed.
BTW I’m trying out new blog engines hence the scatter of links.