Keyboard selection on Silverlight ListBox and ComboBox

By Fons Sonnemans, 7-2-2010

Silverlight doesn't support keyboard selection on a ListBox or Combox. I have created a small Behavior which fixes this problem. You can attach the KeyboardSelectionBehavior to a ListBox or ComboBox using Microsoft Expression Blend. You drag it from the Assets and drop it on your ComboBox or ListBox. If you have a custom ItemTemplate you will have to set the SelectionMemberPath property.

Try my behavior below. If you press a key on the ComboBox or ListBoxes it will select the next item starting with the given key.

Get Microsoft Silverlight

The ComboBox in this example is not databound, The behavior uses the Convert.ToString() method to convert the Content of each ListBoxItem/ComboBoxItem to a string. An invariant case insensitive StartWith() comparison is used to find the next item.

The left ListBox is databound to SampleData containing Employees. The behavior uses the DisplayMemberPath of the ListBox. The Name of the databound Employee is used for keyboard selection.

<ListBox Margin="79,64,0,23" DisplayMemberPath="Name"

    ItemsSource="{Binding Employees}" HorizontalAlignment="Left" Width="176">

    <i:Interaction.Behaviors>

        <local:KeyboardSelectionBehavior />

    </i:Interaction.Behaviors>

</ListBox>

The right ListBox is also databound but has a custom ItemTemplate. We can't use the DisplayMemberPath. The behavior has a SelectionMemberPath property to specify which property to use for selection. In this example it is set to the Name of the databound Employee.

<ListBox Margin="272,23,23,23"

    ItemsSource="{Binding Employees}">

    <i:Interaction.Behaviors>

        <local:KeyboardSelectionBehavior SelectionMemberPath="Name"/>

    </i:Interaction.Behaviors>

    <ListBox.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

                <Image Source="{Binding Image}" Height="32" Width="32"/>

                <StackPanel>

                    <TextBlock Text="{Binding Name}" FontWeight="Bold"/>

                    <TextBlock Text="{Binding Description}"/>

                </StackPanel>

            </StackPanel>

        </DataTemplate>

    </ListBox.ItemTemplate>

</ListBox>

Here is the code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Data;

using System.Windows.Input;

using System.Windows.Interactivity;

 

namespace ReflectionIT.Silverlight.Behaviors {

 

    ///<summary>

    /// This behavior can be attached to a ListBox or ComboBox to

    /// add keyboard selection

    ///</summary>

    public class KeyboardSelectionBehavior : Behavior<Selector> {

 

        ///<summary>

        /// Gets or sets the Path used to select the text

        ///</summary>

        public string SelectionMemberPath { get; set; }

 

        public KeyboardSelectionBehavior() {}

 

        ///<summary>

        /// Attaches to the specified object: subscribe on KeyDown event

        ///</summary>

        protected override void OnAttached() {

            base.OnAttached();

            this.AssociatedObject.KeyDown += DoKeyDown;

        }

 

        void DoKeyDown(object sender, KeyEventArgs e) {

 

            // Create a list of strings and indexes

            int index = 0;

            IEnumerable<Item> list = null;

            var path = SelectionMemberPath ??

                this.AssociatedObject.DisplayMemberPath;

            var evaluator = new BindingEvaluator();

            if (path != null) {

                list = this.AssociatedObject.Items.OfType<object>()

                    .Select(item => {

                        // retrieve the value using the supplied Path

                        var binding = new Binding();

                        binding.Path = new PropertyPath(path);

                        binding.Source = item;

 

                        BindingOperations.SetBinding(evaluator,

                            BindingEvaluator.TargetProperty, binding);

                        var value = evaluator.Target;

 

                        return new Item { Index = index++,

                                Text = Convert.ToString(value) };

                    });

            } else {

                list = this.AssociatedObject.Items.OfType<ListBoxItem>()

                    .Select(item => new Item { Index = index++,

                                Text = Convert.ToString(item.Content) });

            }

            // Sort the list starting at next selectedIndex to the end and

            // then from the beginning

            list = list.OrderBy(item => item.Index <=

                this.AssociatedObject.SelectedIndex ?

                item.Index + this.AssociatedObject.Items.Count : item.Index);

 

            // Find first starting with

            var text = e.Key.ToString();

            var first = list.FirstOrDefault(item => item.Text.StartsWith(text,

                StringComparison.InvariantCultureIgnoreCase));

            if (first != null) {

                // found

                this.AssociatedObject.SelectedIndex = first.Index;

            }

        }

 

        ///<summary>

        /// Helper class

        ///</summary>

        private class Item {

            public int Index;

            public string Text;

        }

 

        ///<summary>

        /// Helper class used for property path value retrieval

        ///</summary>

        private class BindingEvaluator : FrameworkElement {

 

            public static readonly DependencyProperty TargetProperty =

                DependencyProperty.Register(

                    "Target",

                    typeof(object),

                    typeof(BindingEvaluator), null);

 

            public object Target {

                get { return GetValue(TargetProperty); }

                set { SetValue(TargetProperty, value); }

            }

        }

 

    }

}

Updated 1-feb-2011

Sudeept Malik send me an email with some code changes which now makes it possible to use the keyboard in an open ComboBox.

You can download the code including the sample application here.

Leave a Comment

Leave a Comment
Name
Comment
5 + 4 =

3 Comments

  • harshal
    20 nov 2013 12:51
    thanks a lot lot
  • SathyaK
    22 mrt 2013 08:41
    Hi, How to modify this solution for searching more than one letter in a string? For ex If i need to search "Apple " in combo box means i need to type "App". Please guide me. Thanks in advance Regards, Sathya
  • Sathya
    22 mrt 2013 08:32
    Hi Fons Sonnemans, Thanku for ur solution. It works for me..

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.