107 lines
3.9 KiB
C#
107 lines
3.9 KiB
C#
|
|
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();
|
|||
|
|
_editableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox;
|
|||
|
|
if (_editableTextBox != null)
|
|||
|
|
{
|
|||
|
|
_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;
|
|||
|
|
}
|
|||
|
|
return item.ToString();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|