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