目录

WPF-ComboBox 模糊搜索

目录

ComboBox 模糊搜索 示例

 <ComboBox
     Name="cmbAllLineName"
     Grid.Row="5"
     Grid.Column="1"
     Margin="5"
     VerticalAlignment="Center"
     Background="#F5F5F5"
     IsEditable="True"
     IsTextSearchEnabled="False"
     Loaded="cmbAllLineName_Loaded"
     StaysOpenOnEdit="True"
     Unloaded="cmbAllLineName_Unloaded" />
private List<string> allStations;
private TextBox editableTextBox;
private bool isUpdating = false;
private int savedCaretIndex;

   private void cmbAllLineName_Loaded(object sender, RoutedEventArgs e)
   {
       // 加载数据库数据
       allStations = manRailwayModel.GetLineNames();
       cmbAllLineName.ItemsSource = allStations;

       // 延迟查找内部 TextBox
       cmbAllLineName.Dispatcher.InvokeAsync(() =>
       {
           editableTextBox = (TextBox)cmbAllLineName.Template.FindName("PART_EditableTextBox", cmbAllLineName);

           if (editableTextBox != null)
           {
               // 先移除,防止重复订阅
               editableTextBox.TextChanged -= EditableTextBox_TextChanged;
               editableTextBox.TextChanged += EditableTextBox_TextChanged;
           }
       });
   }

   private void cmbAllLineName_Unloaded(object sender, RoutedEventArgs e)
   {
       // 取消订阅,防止内存泄漏
       if (editableTextBox != null)
       {
           editableTextBox.TextChanged -= EditableTextBox_TextChanged;
       }
   }

   private void EditableTextBox_TextChanged(object sender, TextChangedEventArgs e)
   {
       // 基础拦截
       if (isUpdating || editableTextBox == null)
       {
           return;
       }

       // 如果文本变化是因为用户选择了下拉框中的某一项,则不进行过滤
       // 这里增加一个判断:如果 ItemSource 还没被过滤过(Count == allStations.Count),且 Text 等于 SelectedItem,则认为是选择操作
       if (cmbAllLineName.SelectedItem != null &&
           string.Equals(cmbAllLineName.SelectedItem.ToString(), editableTextBox.Text, StringComparison.Ordinal))
       {
           return;
       }

       isUpdating = true;
       string text = editableTextBox.Text;

       try
       {
           // 保存光标位置
           savedCaretIndex = editableTextBox.CaretIndex;

           // 空值处理:恢复原始列表
           if (string.IsNullOrWhiteSpace(text))
           {
               // 只有当当前源不是全部数据时才赋值,减少赋值开销
               if (cmbAllLineName.ItemsSource != allStations)
               {
                   cmbAllLineName.ItemsSource = allStations;
               }
               // 空的时候通常关闭下拉,或者保持开启取决于需求
               cmbAllLineName.IsDropDownOpen = false;

               // 清除选中项,防止某些情况下 Text 被清空
               cmbAllLineName.SelectedIndex = -1;
               return;
           }

           // 过滤逻辑 (建议使用 ToList() 立即执行查询)
           var filtered = allStations
               .Where(x => x != null && x.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
               .ToList();

           // 更新数据源
           // 只有结果集数量不同,或者第一个元素不同时才更新(比 SequenceEqual 快且足够防止闪烁)
           var currentItems = cmbAllLineName.ItemsSource as List<string>;
           bool needUpdate = currentItems == null ||
                             currentItems.Count != filtered.Count ||
                             (filtered.Count > 0 && currentItems.Count > 0 && filtered[0] != currentItems[0]);

           if (needUpdate)
           {
               cmbAllLineName.ItemsSource = filtered;

               // 重要:ItemsSource 更新后,IsDropDownOpen 可能会自动变成 false,需要重新打开
               cmbAllLineName.IsDropDownOpen = true;
           }
           else if (!cmbAllLineName.IsDropDownOpen && filtered.Count > 0)
           {
               // 如果数据源没变但下拉框关了,重新打开
               cmbAllLineName.IsDropDownOpen = true;
           }

           // 如果过滤结果为空,可以选择关闭下拉框
           if (filtered.Count == 0)
           {
               cmbAllLineName.IsDropDownOpen = false;
           }

           // ItemsSource 改变会导致 Text 被清空,必须恢复
           // 此时不需要再次触发事件,所以不需要改 isUpdating,但这里是 try-finally 块内
           if (editableTextBox.Text != text)
           {
               editableTextBox.Text = text;
           }

           // 恢复光标
           // 确保光标位置不超过当前文本长度
           editableTextBox.CaretIndex = (savedCaretIndex > editableTextBox.Text.Length)
                                        ? editableTextBox.Text.Length
                                        : savedCaretIndex;

           // 清除选中项 (必须在 Text 恢复之后)
           // 这样可以防止 WPF 自动高亮列表中的第一项并更改 Text
           cmbAllLineName.SelectedIndex = -1;
       }
       finally
       {
           isUpdating = false;
       }
   }