Winform算是一個成熟且存在已久的應用程式框架,然而有些需求與時俱進後卻沒有跟著更新,像是現今使用者很容易要求的下拉式選單模糊搜尋就沒有。 PS:例如GOOGLE查詢輸入"耶穌得"就會跳出"信耶穌得水牛"這樣的效果。(套JQUERY的說法就是jquery.autocomplete)
本文將分享該需求的實作。(原生的ComboBox僅支援項目的"前段文字"符合輸入,無法查出區段文字符合的項目)
實作重點
1.其實最關鍵的一點就是要保留原始資料,因此針對DataSource的部分要隱藏住原有的Prop,這樣才能分別處理外部傳入的原始資料與內部處理的資料異動(或者是要開新的Prop處理,不過這樣要考量的事情就會就會突然暴增很多,划不來)。
2.在取得外部接收的"所有資料"後,記得要將Items重新ToList做潛層複製。
3.自動完成屬性記得關閉,這樣才可以看出下拉選單查詢異動的變化。(因此我直接強制設定為None了)
4.當DataSource設定為Null時,DisplayMember會跟著被清空,因此要配合這個行為補回該參數。
5.DroppedDwon參數變動時遇到空集合也會有相似的問題。
6.DataSource改變時,連帶的Text屬性也會跟著調整,因此這部分也要做補回參數。
實作程式碼
public partial class FuzzyComboBox : ComboBox
{
[Browsable(false)]
public new AutoCompleteMode AutoCompleteMode { get; } = AutoCompleteMode.None;
public new object DataSource
{
get => OutSideDataSource;
set
{
OutSideDataSource = value;
base.DataSource = value;
_OriginalItems = Items.Cast<object>().ToList();
}
}
object OutSideDataSource {get;set;}
IList<object> _OriginalItems { get; set; }
public FuzzyComboBox()
{
InitializeComponent();
base.AutoCompleteMode = this.AutoCompleteMode;
}
protected override void OnTextUpdate(EventArgs e)
{
string inputString = Text;
SelectionLength = 0;
IList<object> searchResult = GetDataSource(Text);
if (searchResult.Any())
{
base.DataSource = searchResult;
DroppedDown = true;
SelectedIndex = 0;
}
else
{
string oldDisplayMember = DisplayMember;
base.DataSource = null;
DisplayMember = oldDisplayMember;
Items.Add("");
DroppedDown = false;
Items.Clear();
SelectedIndex = -1;
}
Text = inputString;
SelectionStart = inputString.Length;
base.OnTextUpdate(e);
}
protected override void OnLeave(EventArgs e)
{
Text = GetSuggestText();
base.OnLeave(e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter)
{
Text = GetSuggestText();
}
base.OnKeyPress(e);
}
string GetSuggestText()
{
if (SelectedItem != null)
{
return GetItemText(SelectedItem);
}
else if (Items.Count > 0)
{
SelectedItem = Items[0];
return GetItemText(Items[0]);
}
else
{
return Text;
}
}
IList<object> GetDataSource(string compareText)
{
if (string.IsNullOrEmpty(Text) || _OriginalItems.Count < 1)
{
return _OriginalItems;
}
return _OriginalItems
.Cast<object>()
.Where(item =>
{
string text = GetItemText(item);
return text.Contains(compareText);
})
.ToList();
}
}
實作Demo
分別測試常見的字串集合與物件集合。
string[] dataSource = new string[] {
"apple",
"pen",
"applepen",
"pine apple",
"egg",
"eggplant",
"工作",
"高薪的工作",
"輕鬆的工作",
"有正妹的工作",
"能下班的工作"
};
fuzzyComboBoxString.DataSource = dataSource;
SimpleClass[] dataSourceObj = new SimpleClass[] {
new SimpleClass{ Text="[T]apple",Value="apple", },
new SimpleClass{ Text="[T]pen",Value="pen", },
new SimpleClass{ Text="[T]applepen",Value="applepen", },
new SimpleClass{ Text="[T]pine apple",Value="pine apple", },
new SimpleClass{ Text="[T]egg",Value="egg", },
new SimpleClass{ Text="[T]eggplant",Value="eggplant", },
new SimpleClass{ Text="[T]工作",Value="工作", },
new SimpleClass{ Text="[T]高薪的工作",Value="高薪的工作", },
new SimpleClass{ Text="[T]輕鬆的工作",Value="輕鬆的工作", },
new SimpleClass{ Text="[T]有正妹的工作",Value="有正妹的工作", },
new SimpleClass{ Text="[T]能下班的工作",Value="能下班的工作", },
};
fuzzyComboBoxClass.DataSource = dataSourceObj;
/*
this.fuzzyComboBoxClass.DisplayMember = "Text";
this.fuzzyComboBoxClass.ValueMember = "Value";
*/
/*
class SimpleClass
{
public string Text { get; set; }
public string Value { get; set; }
}
*/
備註
1.如果你的下拉選單(總數)資料是會動態增減的話,那必須要外部DataSource重新指派多次。或者必須新開一個Prop(如OriginalDataSource),又或是要整個複寫掉原有DataSource與Items以及的關係。(關鍵在於如何保有原始資料,同時並可正確顯示當下應有的Items)
2.MSDN上有看到使用DataTable當作資料來源處理的,不過基本上裊裊的就不分享了。