一個易用的 WPF 自動完成文本框
一個易用的 WPF 自動完成文本框
介紹
這篇文章的目的是在社區中分享一些我上個月完成代碼,讓一個簡單的文本框擁有自定義的自動完成過濾器。這個想法的靈感來自于GMail的搜索功能。在我的項目中,自定義的控件需要如下所有我需要的功能:
-
它是容易使用的,集成到項目中時,需要的代碼要盡量的少。
-
它需要兼容WCF。我的想法是像GMail一樣創建一個分層的應用,過濾功能需要在服務器端執行,然后將結果通過WCF通道傳送。
-
它需要過濾自定義數據(來自于數據庫或者自定義的列表)并可搜索多個字段,像GMail一樣,建議類似的結果。
-
所有的過濾需要異步完成,因此我將使用Reactive庫。
-
需要和鍵盤及鼠標交互
-
需要強制用戶從可用的列表中選擇一個選項
-
完全兼容我項目中使用的MVVM模板
-
需要通知用戶過濾器已提交,當過濾完成時。
-
需要讓用戶瀏覽結果列表,并選擇一個選項。
-
所有的代碼,除了過濾代碼都需要封裝在自定義控件內
-
水印文本支持
-
我也需要有在內部視圖模型選擇默認選項的能力,并反映它在控件中。
我在網上發現了幾個樣例源碼,但是全部都沒有達到我上述的要求。我沒有寫全部的代碼,我的代碼是基于在這篇優秀的文章中介紹的項目。我接受所有關于怎樣讓這些代碼更完善的建議。
背景
我已經花了幾個月的時間在移植VB6應用程序到新技術上。經過幾周的上網搜索以及學習新技術,我設法從我自己的項目開始。一個我面對的問題是,如何讓用戶從多個表格中選擇一項。例如我需要讓用戶針對一個客戶訂單來從客戶表格中選擇一個客戶。正常情況下我會在客戶端的下拉列表框中填入所以的客戶讓用戶自己選擇一個,但是這個方法不高效。思路應該是讓用戶輸入一些數據(至少三個字)來篩選數據,并返回可能的匹配結果給最終用戶。
代碼用例
從XAML的角度來看, 用控件是非常簡單的:
<Window x:Class="TextBoxAutoCompleteTest.Window1" xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml xmlns:ac="clr-namespace:WpfAutoComplete.Controls;assembly=WpfAutoComplete" Title="Autocomplete Text Box Project" Height="300" Width="300"> <Grid> <StackPanel> <Label Content="This is an Autocomplete Textbox" /> <ac:TextBoxAutoComplete Name="autoTxtBoxEng" SearchDataProvider="{Binding Path=MySearchProviderEng}" SelectedListBoxValue="{Binding Path=PhraseNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" WatermarkText="Type in filtering text here..."/> </StackPanel> </Grid> </Window>
關于代碼的一些說明:
-
SearchDataProvider 屬性是用于數據篩選的類。這個類需要實現ISearchDataProvider接口。
-
SelectedListBoxValue 是可選項,它指向由控件更新的VM的屬性。
-
WatermarkText 是當沒有輸入的時候所顯示的文字。
這是一個簡單的SearchDataProvider類的例子,它用字典作為采樣:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TextBoxAutoCompleteTest { class MyDataProviderEng : WpfAutoComplete.ISearchDataProvider { public WpfAutoComplete.SearchResult DoSearch(string searchTerm) { return new WpfAutoComplete.SearchResult { SearchTerm = searchTerm, Results = dict.Where(item => item.Value.ToUpperInvariant().Contains(searchTerm.ToUpperInvariant())).ToDictionary(v => v.Key, v => v.Value) }; } public WpfAutoComplete.SearchResult SearchByKey(object Key) { return new WpfAutoComplete.SearchResult { SearchTerm = null, Results = dict.Where(item => item.Key.ToString()==Key.ToString()).ToDictionary(v => v.Key, v => v.Value) }; } private readonly Dictionary<object, string> dict = new Dictionary<object, string> { { 1, "The badger knows something"}, { 2, "Your head looks something like a pineapple"}, { 3, "Crazy like a box of green frogs"}, { 4, "The billiard table has green cloth"}, { 5, "The sky is blue"}, { 6, "We're going to need some golf shoes"}, { 7, "This is going straight to the pool room"}, { 8, "We're going to Bonnie Doon"}, { 9, "Spring forward - Fall back"}, { 10, "Gerry had a plan which involved telling all"}, { 11, "When is the summer coming"}, { 12, "Take you time and tell me what you saw"}, { 13, "All hands on deck"} }; } }
該代碼可很簡單地用于采用WCF服務來篩選并返回可選項目而不是一個預定義好地字典。看一下新實現的ISearchDataProvider:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Ohmio.Client.DataProviders { class DataProviderClientes : WpfAutoComplete.ISearchDataProvider { private OhmioService.OhmioServiceClient serviceClient = new OhmioService.OhmioServiceClient(); public WpfAutoComplete.SearchResult DoSearch(string searchTerm) { return new WpfAutoComplete.SearchResult { SearchTerm = searchTerm, Results = (serviceClient.EnumClientes(searchTerm.ToUpperInvariant())).ToDictionary(x=>(object)x.Key , y=>y.Descripcion) }; } public WpfAutoComplete.SearchResult SearchByKey(object Key) { return new WpfAutoComplete.SearchResult { SearchTerm = null, Results = (serviceClient.EnumClientes(Key.ToString())).ToDictionary(v => (object)v.Key, v => v.Descripcion) }; } } }
在這個例子中,使用的WCF服務有一個叫作EnumClientes的方法。該方法得到一個篩選參數,通過數據庫執行篩選,然后返回一個轉換成字典對象的List<object>。
興趣點
在寫代碼的過程中我學到很多。仍然有一些故障/缺失的特性是超越了我對代碼實際的了解,特別是關于響應式程序庫的:
-
代碼利用擊鍵之間的延時來啟動篩選過程。背后的思想是讓篩選過程更有效率,而不是篩選在輸入整個詞后篩選。有時候控件會停止響應(插入文本不會執行篩選)。當它失去焦點敲入刪除鍵或者回車鍵,篩選又開始工作。
-
有效性: 當沒有選項被選擇的時候在控件邊框畫一個紅色的框。我沒有找到定制錯誤消息的方法。
-
文本水印的顏色固定為淡灰色。當背景色是暗色的時候這是個問題。
-
還沒有多選功能。