Introduction
Currently, in Silverlight 3, ComboBox
and ComboBoxItems
cannot be selected via the keyboard much like it can be in any major language. In Windows Forms, HTML, Flash/Flex applications, etc., a user can select a ComboBox
/ListBox
/Select
box and by typing in keys, can select the desired input. This poses a problem for Accessibility reasons, and may turn away some developers as a result.
Many existing solutions available on the internet make use of 3rd party DLLs, which may not be an option for some developers. This solution consists of writing a straight-forward extension method.
Background
It has become a common expectation that a user may access all ComboBox
's items via the keyboard - an important accessibility feature. Tabbing to a control, they should be able to begin typing and retrieve the desired ComboBoxItem
. This functionality does not exist in Silverlight by default, and thus needs to be written.
Some implementations get the user so far as selecting the first matching item, but not beyond that, and only when the ComboBox
is closed. Thus in a list containing "Alabama," "Alaska" and "Arkansas", typing the letter "a" would only get the user as far as "Alabama." Typing in "L" would not yield "Alabama" or "Alaska" but "Louisiana." However, because it is important to maintain the user's expectations, and thus the user should be able to select "Alaska" by typing in: a-l-a-s.
Using the Code
Any intermediate developer familiar with C# and Silverlight should have an easy time implementing the following code.
Before listing the code, the implementation is as follows:
string[] state = new string[]
{ "Alabama", "Alaska", "Arizona", "Arkansas", "Delaware", "Louisiana", "Maine" };
ComboBox comboBox = new ComboBox();
for (int i = 0; i < state.Length; i++)
{
ComboBoxItem comboBoxItem = new ComboBoxItem();
comboBoxItem.Content = state[i];
comboBox.Items.Add(comboBoxItem);
}
comboBox.SetKeyboardSelection(true);
comboBox.SetKeyboardSelection(false);
As is noted in the above comment, the SetKeyboardSelection()
must only be called after all ComboBoxItems
have been added to the ComboBox
control.
The are two methods that allow for keyboard selection. SetKeyboardSelection()
is the only one that needs to be called to enable selection via the keyboard. The other method, KeyPressSearch()
is declared inside SetKeyboardSelection
and is used to do the actual searching:
public static class Extensions
{
public static void SetKeyboardSelection(this ComboBox comboBox, bool enable)
{
string searchStringEnabled = "KeyboardSelectionEnabled";
string comboBoxTag = comboBox.Tag==null? "" : comboBox.Tag.ToString();
bool isKeyboardEnabled = comboBoxTag.Contains(searchStringEnabled);
#region KeyPressSearch
KeyEventHandler keyPressSearch = delegate(object sender, KeyEventArgs e)
{
string key = e.Key.ToString();
if (key.Length > 1 && (key.StartsWith("D") || key.StartsWith("NumPad")))
{ key = key.Replace("NumPad", "").Replace("D", "");
}
else if (key.Length > 1)
{
comboBox.Tag = searchStringEnabled + "||";
return;
}
string searchHistoryPartsString = comboBox.Tag ==
null ? searchStringEnabled + "||" : comboBox.Tag.ToString();
string[] searchHistoryParts = (searchHistoryPartsString.Contains("|")) ?
searchHistoryPartsString.Split('|') : new string[0];
int historyExpiration = 1500; string searchStringHistory = searchHistoryParts.Length == 3 ?
searchHistoryParts[1] : "";
string searchStringTimeStampString = searchHistoryParts.Length == 3 ?
searchHistoryParts[2] : "";
DateTime searchStringTimeStamp;
string searchString = key;
if (DateTime.TryParse(searchStringTimeStampString, out searchStringTimeStamp)
&& DateTime.Now.Subtract
(searchStringTimeStamp).TotalMilliseconds < historyExpiration)
{ searchString = searchStringHistory + key;
}
for (int i = 0; i < comboBox.Items.Count; i++)
{
if (comboBox.Items[i].GetType() == typeof(ComboBoxItem) &&
((ComboBoxItem)comboBox.Items[i]).Content.ToString().StartsWith
(searchString, StringComparison.InvariantCultureIgnoreCase))
{
comboBox.SelectedIndex = i;
comboBox.Tag = searchStringEnabled + "|" +
searchString + "|" + DateTime.Now;
break;
}
}
};
#endregion
if (!isKeyboardEnabled && enable)
{
comboBox.Tag = searchStringEnabled + "||";
comboBox.DropDownOpened += delegate
{
comboBox.Tag = searchStringEnabled + "||";
};
comboBox.DropDownClosed += delegate
{
comboBox.Tag = searchStringEnabled + "||";
};
comboBox.KeyUp += keyPressSearch;
for (int i = 0; i < comboBox.Items.Count; i++)
{
if (comboBox.Items[i].GetType() == typeof(ComboBoxItem))
{
((ComboBoxItem)comboBox.Items[i]).KeyUp += keyPressSearch;
}
}
}
else if (isKeyboardEnabled && !enable)
{
comboBox.KeyUp -= keyPressSearch;
for (int i = 0; i < comboBox.Items.Count; i++)
{
if (comboBox.Items[i].GetType() == typeof(ComboBoxItem))
{
((ComboBoxItem)comboBox.Items[i]).KeyUp -= keyPressSearch;
}
}
comboBox.Tag = "";
}
else
{
comboBox.KeyUp -= keyPressSearch;
comboBox.Tag = "";
}
}
}
The way all this works is that each ComboBoxItem
is given a KeyUp
event that conducts the search. This way, the searching can be done regardless of whether the ComboBox
is open or closed. The current search history, which is preserved in the ComboBox
Tag property is stored for 1.5 seconds (if the user searches "A-l-a" and then pauses two seconds and starts searching "D", they will be conducting a new search.)
Points of Interest
It's worth pointing out that the KeyPressSearch()
method uses the KeyEventArgs
object, which exposes two ways to obtain the key pressed, the enum KeyEventArgs.Key
, which is defined in System.Windows.Input.Key
and KeyEventArgs.PlatformKeyCode
which is an integer that is platform-specific.
There is a trade-off here. Because System.Windows.Input.Key
is rather limited in the keys it exposes, searching is limited to alpha-numeric, and thus special characters (!, -, +, @, #, $ etc.) are ignored. Some of these characters can easily be accepted, but not all.
Using KeyEventArgs.PlatformKeyCode
allows a greater range of keys, however, because the key-codes are platform specific, greater consideration needs to be made when accepting and denying ranges, as it depends on what OS a user is using.
History
- Changed implementation to store history in Tag property for better results
- Added ability to remove keyboard selection, and re-add it. This is useful for when items refresh, or change. In this case, keyboard selection should be removed and added once more
- Resolved bug which occurred when using multiple
ComboBoxes
- Last change: March 24, 2010