Files
ShrlAlgoToolkit/Melskin/Controls/SearchableComboBox.cs

106 lines
3.9 KiB
C#
Raw Normal View History

2026-02-12 21:29:00 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Melskin.Controls
{
/// <summary>
/// 搜索过滤的下拉框控件
/// 自定义了ItemTemplate的时候SearchableComboBox 设置 TextSearch.TextPath 附加属性,这个属性告诉 ComboBox“虽然我的下拉列表长得很复杂有颜色、有图标但当你需要把我当成文本处理比如显示在输入框里、或者搜索时请使用这个属性”
/// </summary>
public class SearchableComboBox : ComboBox
{
private TextBox _editableTextBox;
private bool _isInternalOperation;
static SearchableComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchableComboBox), new FrameworkPropertyMetadata(typeof(SearchableComboBox)));
}
public SearchableComboBox()
{
IsEditable = true;
IsTextSearchEnabled = false;
StaysOpenOnEdit = true;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
2026-02-23 15:05:15 +08:00
if (GetTemplateChild("PART_EditableTextBox") is TextBox _editableTextBox)
2026-02-12 21:29:00 +08:00
{
_editableTextBox.TextChanged += OnEditableTextBoxTextChanged;
// 点击时全选
_editableTextBox.GotFocus += (s, e) => _editableTextBox.SelectAll();
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (_isInternalOperation) return;
_isInternalOperation = true;
base.OnSelectionChanged(e);
// 重点 1选中项改变后必须彻底清空过滤器否则下次打开下拉框只剩下一项
this.Items.Filter = null;
_isInternalOperation = false;
}
private void OnEditableTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (_isInternalOperation) return;
string searchText = _editableTextBox.Text;
string selectedText = GetItemDisplayText(SelectedItem);
// 重点 2断开锁定逻辑
// 如果文本框的内容和当前选中项的文本不一致,说明用户正在打字替换内容
if (SelectedItem != null && searchText != selectedText)
{
_isInternalOperation = true;
SelectedItem = null; // 必须将 SelectedItem 设为 null否则 WPF 会强行还原文本
_isInternalOperation = false;
}
// 重点 3执行过滤
this.Items.Filter = item =>
{
if (string.IsNullOrEmpty(searchText)) return true;
string itemText = GetItemDisplayText(item);
// 只要包含关键字就显示(忽略大小写)
return itemText.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0;
};
// 自动打开下拉框
if (!IsDropDownOpen && _editableTextBox.IsFocused && !string.IsNullOrEmpty(searchText))
{
IsDropDownOpen = true;
}
}
private string GetItemDisplayText(object item)
{
if (item == null) return string.Empty;
if (item is ComboBoxItem cbi) return cbi.Content?.ToString() ?? string.Empty;
// 兼容 DisplayMemberPath 或 TextSearch.TextPath
string path = DisplayMemberPath;
if (string.IsNullOrEmpty(path)) path = TextSearch.GetTextPath(this);
if (!string.IsNullOrEmpty(path))
{
var prop = item.GetType().GetProperty(path);
if (prop != null) return prop.GetValue(item)?.ToString() ?? string.Empty;
}
2026-02-23 15:05:15 +08:00
return item.ToString()!;
2026-02-12 21:29:00 +08:00
}
}
}