65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 2 中支持键盘选择的组合框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (9投票s)

2010年3月19日

CPOL

3分钟阅读

viewsIcon

56590

downloadIcon

536

目前,ComboBoxItems 无法使用键盘选择,只能使用鼠标。本文试图解决这个问题。

引言

目前,在 Silverlight 3 中,无法通过键盘选择 ComboBoxComboBoxItems,不像在任何主要语言中那样。 在 Windows Forms、HTML、Flash/Flex 应用程序等中,用户可以选择一个 ComboBox/ListBox/Select 框,并通过键入键来选择所需的输入。 这给可访问性带来了问题,并可能因此让一些开发者望而却步。

互联网上许多现有的解决方案都使用了第三方 DLL,对于一些开发者来说,这可能不是一个选择。 此解决方案包括编写一个简单的扩展方法。

背景

用户可以通过键盘访问所有 ComboBox 的条目已经成为一种普遍的期望,这是一个重要的辅助功能特性。 在 Tab 键定位到控件后,他们应该能够开始输入并检索所需的 ComboBoxItem。 此功能在 Silverlight 中默认不存在,因此需要编写。

一些实现可以让用户选择到第一个匹配的条目,但仅此而已,并且仅当 ComboBox 关闭时。 因此,在包含“Alabama”、“Alaska”和“Arkansas”的列表中,键入字母“a”只会让用户选择到“Alabama”。 键入“L”不会产生“Alabama”或“Alaska”,而是“Louisiana”。 但是,由于保持用户的期望非常重要,因此用户应该能够通过键入以下内容来选择“Alaska”:a-l-a-s。

Using the Code

任何熟悉 C# 和 Silverlight 的中级开发者都应该能够轻松实现以下代码。

在列出代码之前,实现如下

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);
}

//Must enable keyboard selection **AFTER** it all items have bene added to the ComboBox
comboBox.SetKeyboardSelection(true);

//To disable keyboard selection...
//this is useful if the items of a selection box change - 
//keyboard selection should be reset.
comboBox.SetKeyboardSelection(false);

如上述注释中所述,必须所有 ComboBoxItems 添加到 ComboBox 控件之后才能调用 SetKeyboardSelection()

有两种方法允许键盘选择。 SetKeyboardSelection() 是启用键盘选择唯一需要调用的方法。 另一种方法 KeyPressSearch()SetKeyboardSelection 内部声明,用于进行实际搜索

public static class Extensions
{
    /*
     * SetKeyboardSelection enables keyboard selection on all
     * ComboBoxItems, as well as on the ComboBox itself (it has not already been added).
     * In addition, it tracks the "search history" that is created as the user types.
     * This is done to allow the user to type in more letters to narrow down
     * results (ie. "Ala" = Alabama, Alaska; "Alab" = Alabama)
     */
    public static void SetKeyboardSelection(this ComboBox comboBox, bool enable)
    {
        string searchStringEnabled = "KeyboardSelectionEnabled";
        string comboBoxTag = comboBox.Tag==null? "" : comboBox.Tag.ToString();
        //See if our search history control already exists 
        //by analyzing the combobox tag...
        bool isKeyboardEnabled = comboBoxTag.Contains(searchStringEnabled);

        /*
         * KeyPressSearch is defined as an anonymous delegate, 
         * that SetKeyboardSelection delegates
         * to the KeyUp events of ComboBoxItems and the parent ComboBox.
         */
        #region KeyPressSearch
        KeyEventHandler keyPressSearch = delegate(object sender, KeyEventArgs e)
        {
            //Since Key has only certain values, A-Z, D0-D9, NumPad0-9, Space, etc. 
            //let's just focus on letters, and numbers, and ignore all other keys... 
            //if they're pressed, clear the search history another option is to 
            //use PlatformKeyCode, but since it's platform specific, let's not.
            string key = e.Key.ToString();
            if (key.Length > 1 && (key.StartsWith("D") || key.StartsWith("NumPad")))
            { //remove the D/NumPad prefix to get the digit
                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; 	//In 1.5 seconds, clear the history, 
					//and start new...
            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)
            {   //search history is valid and has not yet expired...
                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 + "||";

            //Reset the search history on open and close
            comboBox.DropDownOpened += delegate
            {
                comboBox.Tag = searchStringEnabled + "||";
            };
            comboBox.DropDownClosed += delegate
            {
                comboBox.Tag = searchStringEnabled + "||";
            };

            //Add handler to parent control, so that we search even 
            //when combobox is closed, yet focused
            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)
        {
            //Remove handler
            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
        {
            //Remove handler
            comboBox.KeyUp -= keyPressSearch;
            comboBox.Tag = "";
        }
    }
}

所有这些工作的方式是,每个 ComboBoxItem 都会获得一个 KeyUp 事件来执行搜索。 这样,无论 ComboBox 是打开还是关闭,都可以进行搜索。 当前搜索历史记录(保存在 ComboBox Tag 属性中)会保存 1.5 秒(如果用户搜索“A-l-a”,然后暂停两秒并开始搜索“D”,他们将进行新的搜索。)

关注点

值得指出的是,KeyPressSearch() 方法使用 KeyEventArgs 对象,该对象公开两种获取按键的方式:enum KeyEventArgs.Key(在 System.Windows.Input.Key 中定义)和 KeyEventArgs.PlatformKeyCode(一个平台特定的整数)。

这里有一个权衡。 由于 System.Windows.Input.Key 在其公开的键方面相当有限,因此搜索仅限于字母数字,因此特殊字符(!、-、+、@、#、$ 等)将被忽略。 其中一些字符可以很容易地被接受,但并非所有字符。

使用 KeyEventArgs.PlatformKeyCode 允许更大的键范围,但是,由于键代码是平台特定的,因此在接受和拒绝范围时需要进行更多的考虑,因为它取决于用户正在使用的操作系统。

历史

  • 更改了实现,将历史记录存储在 Tag 属性中以获得更好的结果
  • 添加了删除键盘选择和重新添加它的能力。 这对于项目刷新或更改时很有用。 在这种情况下,应删除键盘选择并再次添加
  • 解决了使用多个 ComboBoxes 时出现的错误
  • 上次更改:2010 年 3 月 24 日
© . All rights reserved.