一个易用的 WPF 自动完成文本框

jopen 10年前

一个易用的 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&apos;re going to need some golf shoes"},              { 7, "This is going straight to the pool room"},              { 8, "We&apos;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>。

兴趣点

在写代码的过程中我学到很多。仍然有一些故障/缺失的特性是超越了我对代码实际的了解,特别是关于响应式程序库的:

  • 代码利用击键之间的延时来启动筛选过程。背后的思想是让筛选过程更有效率,而不是筛选在输入整个词后筛选。有时候控件会停止响应(插入文本不会执行筛选)。当它失去焦点敲入删除键或者回车键,筛选又开始工作。

  • 有效性: 当没有选项被选择的时候在控件边框画一个红色的框。我没有找到定制错误消息的方法。

  • 文本水印的颜色固定为淡灰色。当背景色是暗色的时候这是个问题。

  • 还没有多选功能。