【Winform】-ComboBox實作"模糊搜尋"

 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當作資料來源處理的,不過基本上裊裊的就不分享了。