Keyboard selection on Silverlight ListBox and ComboBox

By Fons Sonnemans, posted on
3595 Views 3 Comments

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.

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>

    publicclassKeyboardSelectionBehavior : Behavior<Selector> {

 

        ///<summary>

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

        ///</summary>

        publicstring SelectionMemberPath { get; set; }

 

        public KeyboardSelectionBehavior() {}

 

        ///<summary>

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

        ///</summary>

        protectedoverridevoid 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 = newBindingEvaluator();

            if (path != null) {

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

                    .Select(item => {

                        // retrieve the value using the supplied Path

                        var binding = newBinding();

                        binding.Path = newPropertyPath(path);

                        binding.Source = item;

 

                        BindingOperations.SetBinding(evaluator,

                            BindingEvaluator.TargetProperty, binding);

                        var value = evaluator.Target;

 

                        returnnewItem { Index = index++,

                                Text = Convert.ToString(value) };

                    });

            } else {

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

                    .Select(item => newItem { 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>

        privateclassItem {

            publicint Index;

            publicstring Text;

        }

 

        ///<summary>

        /// Helper class used for property path value retrieval

        ///</summary>

        privateclassBindingEvaluator : FrameworkElement {

 

            publicstaticreadonlyDependencyProperty TargetProperty =

                DependencyProperty.Register(

                    "Target",

                    typeof(object),

                    typeof(BindingEvaluator), null);

 

            publicobject 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.

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.

Leave a comment

Blog comments

Sathya

22-Mar-2013 8:32
Hi Fons Sonnemans, Thanku for ur solution. It works for me..

SathyaK

22-Mar-2013 8: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

harshal

20-Nov-2013 12:51
thanks a lot lot