【问题标题】:Autocomplete combobox for WPFWPF 的自动完成组合框
【发布时间】:2015-03-13 19:43:40
【问题描述】:

我需要一个用于 WPF C# 的自动完成组合框。我尝试了几种方法,但没有任何效果。例如我尝试了一个组合框:

<ComboBox  Width="200"
      IsEditable="True"
      ItemsSource="{Binding Names}"
      IsTextSearchEnabled="True"
      HorizontalAlignment="Left"/>

Names 是字符串列表:Peter John、John、John Doe、Cathy、Howard、John Richards 等

如果您输入名称,例如约翰组合框应该展开,我应该看到

  • 约翰
  • 约翰·多伊
  • 约翰·理查兹
  • 彼得·约翰

但这不起作用。我该怎么做?

【问题讨论】:

  • 迄今为止最好的解决方案。但不幸的是,它只查找列表中的第一个字母。示例:我正在搜索“John”,但没有“Peter John”
  • 您可以配置FilterMode 例如使用Contains

标签: c# .net wpf combobox autocomplete


【解决方案1】:

经过大量的摆弄,我终于找到了一个完整的、可行的解决方案。 (或者看起来是这样。)

步骤 1. 修改 XAML 标记

你需要像这样修改你的 ComboBox:

<ComboBox
    ...
    IsTextSearchEnabled="False"
    ...
    PreviewTextInput="PreviewTextInput_EnhanceComboSearch"
    PreviewKeyUp="PreviewKeyUp_EnhanceComboSearch"
    DataObject.Pasting="Pasting_EnhanceComboSearch" />

即。 禁用默认文本搜索,并添加将负责用户添加、删除和粘贴文本的事件处理程序。

步骤 2. 添加一个辅助函数,该函数将获取 ComboBox 的内部 TextBox(因为 WPF)

为了让PreviewTextInput_EnhanceComboSearchPasting_EnhanceComboSearch 正常工作,您需要访问您的组合框的插入符号。不幸的是,要做到这一点,您需要遍历,呃,视觉树(hat tip to Matt Hamilton)。您可以在扩展方法中做到这一点,但我在 Page 类中使用了静态方法:

public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj == null) return null;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
    {
        var child = VisualTreeHelper.GetChild(depObj, i);

        var result = (child as T) ?? GetChildOfType<T>(child);
        if (result != null) return result;
    }
    return null;
}

步骤 3. 实现事件处理程序

请注意我用过

s => s.IndexOf(e.Text, StringComparison.InvariantCultureIgnoreCase) != -1 

相当于不区分大小写的s =&gt; s.Contains(e.Text)检查。请记住更改该部分以满足您的需求。

步骤 3.a 触发用户在 ComboBox 内输入内容的搜索

PreviewTextInput 处理程序运行时,组合框内的.Text 属性包含修改之前 中的文本。因此,我们需要使用GetChildOfType方法来获取ComboBox的内部TextBox,以便获得它的插入符号,这样我们就知道输入的字符到底插入到了哪里。

private void PreviewTextInput_EnhanceComboSearch(object sender, TextCompositionEventArgs e)
{
    ComboBox cmb = (ComboBox)sender;

    cmb.IsDropDownOpen = true;

    if (!string.IsNullOrEmpty(cmb.Text))
    {
        string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, e.Text);
        cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
    }
    else if (!string.IsNullOrEmpty(e.Text))
    {
        cmb.ItemsSource = Names.Where(s => s.IndexOf(e.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
    }
    else
    {
        cmb.ItemsSource = Names;
    }
}

步骤 3.b 在用户粘贴到 ComboBox 时触发搜索

DataObject.Pasting 处理程序的行为方式与PreviewTextInput 处理程序类似,因此我们再次需要插入符号。

private void Pasting_EnhanceComboSearch(object sender, DataObjectPastingEventArgs e)
{
    ComboBox cmb = (ComboBox)sender;

    cmb.IsDropDownOpen = true;

    string pastedText = (string)e.DataObject.GetData(typeof(string));
    string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, pastedText);

    if (!string.IsNullOrEmpty(fullText))
    {
        cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
    }
    else
    {
        cmb.ItemsSource = Names;
    }
}

步骤 3.c 在用户删除 ComboBox 内的文本时触发搜索(同时按空格键,因为 WPF)

这将在用户按下 Delete 或 Backspace 时触发。

还有空格,因为空格被PreviewTextInput忽略了,所以在示例中很难从“John Doe”和“John Richards”中过滤掉“John”。

private void PreviewKeyUp_EnhanceComboSearch(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Back || e.Key == Key.Delete)
    {
        ComboBox cmb = (ComboBox)sender;

        cmb.IsDropDownOpen = true;

        if (!string.IsNullOrEmpty(cmb.Text))
        {
            cmb.ItemsSource = Names.Where(s => s.IndexOf(cmb.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
        }
        else
        {
            cmb.ItemsSource = Names;
        }
    }
}

...应该够了。

【讨论】:

  • 还不错,但如果按几次 Escape,所有条目都会消失。
  • @Chris ...或者如果您选择以前输入的文本并输入一个字母。是的,还有更多的边缘情况需要覆盖。我想当我有时间的时候我会做更多的研究,但在这一点上,这似乎确实是你需要一个经过良好测试的库的事情。
  • 我不知道这太直接了:-|
【解决方案2】:

使用 PreviewTextInput 事件进行过滤并像这样显示下拉列表:

private void ComboBox_TextInput_1(object sender, TextCompositionEventArgs e)
    {           
        cmbperson.IsDropDownOpen = true;
        cmbperson.ItemsSource = DataBase.Persons.Where(p => p.Name.Contains(e.Text)).ToList();
    }

【讨论】:

  • 不幸的是,在 PreviewTextInput e.Text 将是用户输入的任何内容,即。键盘的单个字符。此外,由于这是 PreviewTextInput,ComboBox.Text 将是用户键入字符 之前 框中的内容。但是,关于 IsDropDownOpen 的注释帮助我找到了解决方案,所以我没有反对。
  • 文本前 + 用户输入的最后一件事!
  • nvm 很乱。
【解决方案3】:

我建议您使用为自动完成而不是组合框而制作的控件。许多公司都提供这样的控件,this 一个是免费的并且被认为是好的。

【讨论】:

  • 感谢 Moti,但我认为您的建议不适用于 C#,对吧?
  • @Struct 用于WPF,适用于c#。控件本身是用VB.NET 编写的,但是一旦它被编译成msil,它就和库用户帐户上的c# 一样好。
  • 现在有一个C# version,如果需要自定义,这很好。
【解决方案4】:

我为WPF 创建了一个自动完成功能,可以帮助您。 按照下面的链接到 github: https://github.com/rhpontes/AutocompleteWpf

希望对你有帮助。

【讨论】:

  • 请确保在发布链接之前添加足够的内容描述。
【解决方案5】:

这是对我有用的实现:

<ComboBox
    Name="ItemsControl"
    IsEditable="True"
    KeyUp="OnItemsControlKeyUp"

我检查自上次应用过滤器后文本是否已更改(以避免在按下非字母数字键时进行过滤)。

private string _textBeforeFilter;

private void OnItemsControlKeyUp(object sender, KeyEventArgs e)
{
    var arrowKey = e.Key >= Key.Left && e.Key <= Key.Down;

    // if arrow key (navigating) or the text hasn't changed, then a we don't need to filter
    if (arrowKey || ItemsControl.Text.EqualsIgnoreCase(_textBeforeFilter)) return;

    _textBeforeFilter = ItemsControl.Text;

    var textIsEmpty = string.IsNullOrWhiteSpace(ItemsControl.Text);

    var itemsViewOriginal = (CollectionView) CollectionViewSource.GetDefaultView(ItemsControl.ItemsSource);
    // if the text is empty, then we show everything, otherwise filter based on the text 
    itemsViewOriginal.Filter = o => textIsEmpty || ((string) o).ContainsIgnoreCase(ItemsControl.Text);
}

注意: EqualsIgnoreCaseContainsIgnoreCase 是扩展方法:

public static bool EqualsIgnoreCase(this string source, string value)
{
    return source.Equals(value, StringComparison.OrdinalIgnoreCase);
}

public static bool ContainsIgnoreCase(this string source, string value)
{
    return source.Contains(value, StringComparison.OrdinalIgnoreCase);
}

【讨论】:

    【解决方案6】:

    在 XAML 中,您应该设置 IsEditable=True 并为 PreviewKeyDown 事件添加处理程序:

    private void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            var cmb = (ComboBox)sender;
            cmb.IsDropDownOpen = true;
            var textbox = cmb.Template.FindName("PART_EditableTextBox", cmb) as TextBox;
            cmb.ItemsSource = CurrentStorage.Organisations.Where(p => string.IsNullOrEmpty(cmb.Text) || p.Name.ToLower().Contains(textbox.Text.ToLower())).ToList();
        }
    

    【讨论】:

    • 您好,欢迎来到 Stack Overflow。请编辑您的答案以提供解释,以更好地帮助来这里寻找相关但不同问题的建议的人们。有关更多建议,请参阅How do I write a good answer
    【解决方案7】:

    使用 ComboBox.Items.Filter 显示适合文本框中写入的文本的项目。这是一个例子:

                If cmb.Text = "" Then
                    cmb.Items.Filter = Nothing
                Else
                    Dim T = cmb.Text
                    cmb.Items.Filter = Nothing
                    Dim I = cmb.Items.IndexOf(T)
                    cmb.Items.Filter = Function(x As String)
                                           If x.StartsWith(T) Then Return True
                                           If x.Contains(" " & T) Then Return True
                                           Return False
                                       End Function
    
                    If I = -1 Then
                        cmb.SelectedIndex = -1
                        cmb.Text = T
                        Dim txt As TextBox = cmb.Template.FindName("PART_EditableTextBox", cmb)
                        txt.SelectionStart = T.Length
                    Else
                        cmb.SelectedIndex = 0
                    End If
    
                End If
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多