WPF基于ComboBox实现(花里胡哨)搜索功能与占位符提示
目标
首先,网上还有很多更高级的方式来实现题目中描述的功能,比如自定义样式,自定义控件(应该叫那个)。在这里,我只记录我的个人经历。毕竟花了很长时间才明白其中的一些原理。
一、实现搜索功能
ComboBox本身已经具备搜索功能,只需要将其IsEditable和IsTextSearchEnable开关打开就可以了。但是这个搜索功能是比较简单的,它能够帮你定位到第一个(如果有的话)匹配项。我想实现的是根据搜索文字更新列表内容,理由是这样更加酷炫。下面是实现方法(和一些坑)。
1. ComboBox定义
首先!我们需要关闭IsTextSearchEnable,理由是:它不仅帮我们定位匹配项,同时还会将SelectedItem也设置成这一项,这样会给我们的代码带来逻辑混乱。具体解释如下:
从属性角度来看,ComboBox的SelectedIndex,SelectedItem和Text的更新顺序是从前往后的,搜索逻辑本来应该是基于Text去更新源,但是更新源的操作会导致Selected的更新,进而再次引起Text更新,那么逻辑就会变得未知且不可控(甚至程序行为会出现有断点和没断点时两个样的情况)从事件的角度来说,我观察到很奇怪的一点,即控件内部由Selected引起的更新优先级是高于用户的输入行为的,假如说我输入“六”,代码自动选中“六六六”这一项并开始更新Text以及其他属性,等到这些更新都完成了,用户输入的“六”才会开始去更新Text(甚至会更新多次),最终导致界面显示的和代码选中的内容不一致,而且由于逻辑上的混乱,很难在这个情况下去解决不一致的问题。
我们所用到的ComboBox大概长这样:
<ComboBox ItemsSource="{Binding FilterItems}"
Text="{Binding SearchText}"
IsEditable="True" IsTextSearchEnable="False"/>
基于这个定义,我们将搜索逻辑放到searchText的set方法中实现。
2. .cs代码
在.cs文件中(Model或者ViewModel都可以,如果是要将这个ComboBox写成自定义控件的话,Model是更方便的选择,并且后面许多功能都没法使用Binding),我们定义上面绑定的属性如下:
privateList<T> ItemsSource;publicList<T> FilterItems {get;set;}privatestring mSearchText;publicstring SearchText
{get{return mSearchText;}set{
mSearchText=value;
FilterItems=ItemsSource.Where(x=>x.Contains(value)).ToList();}}
至此,我们已经实现了一个可以搜索的ComboBox,效果是:在ComboBox里面输入文字,点击展开列表可以看到匹配项,点击能够选择。这篇文章当然到这里还没结束,但是后面都是锦上添花的东西,感兴趣的话可以继续看。
二、实现符合习惯的弹出功能
ComboBox自带StaysOpenOnEdit选项,能够在用户输入时保持下拉框弹出,便于用户直观看到搜索结果。但是它有两个问题:
1.它是Stays而不是Change,也就是说如果用户输入时下拉框是弹出的,那么输入行为不会导致下拉框收回;但是如果输入时下拉框还没有弹出,它并不会自动弹出。
2.当我们选中其中一项,然后想要删除某些字来重新搜索时,它并不会响应,或者说选中后的删除并不被认为是一种Edit行为。如果想要自定义ComboBox的弹出行为,就需要进行一点小小的修改:
<ComboBox ItemsSource="{Binding FilterItems}"
Text="{Binding SearchText}"
IsEditable="True" IsTextSearchEnable="False"
x:Name ="CBName"
IsDropDownOpen="{Binding IsDrop}"
GotFocus="GotFocus"/><!--加上这三个-->
首先,我们给ComboBox的IsDropDownOpen属性绑定一个bool变量,便于我们控制它自己展开;同时,当我们点击ComboBox使其获得焦点时(在这之前它的下拉框应当是收回的),执行GotFocus函数,使其自动展开,从而不需要我们输入前还得点一下下拉按钮让它展开。有了这两个就可以定义出符合使用习惯的弹出行为。先给出代码:
privateList<T> ItemsSource;publicList<T> FilterItems {get;set;}privatestring mSearchText;publicstring SearchText
{get{return mSearchText自动弹窗代码;}set{
mSearchText=value;
FilterItems=ItemsSource.Where(x=>x.Contains(value)).ToList();
IsDrop =true;}}publicbool IsDrop {get;set}publicvoidGotFocus(object sender,RoutedEv
entArgs e){
CBName.IsDropDownOpen =true;}
这样,在我们点击ComboBox准备搜索时,以及输入中、删除时,下拉框都会保持弹出。当然,在我们选中其中一项的时候下拉框会自动收回,此时如果我们继续输入或者删除,下拉框也会重新弹出。不过,此时代码会有一个小bug,后面再介绍。
三、为ComboBox添加占位符,实现提示功能
有了GotFocus,再加上LostFocus,我们就可以实现在输入框为空、失去焦点时添加占位符提示用户输入,获得焦点时清空占位符供用户输入。下面是代码:
<ComboBox ItemsSource="{Binding FilterItems}"
Text="{Binding SearchText}"
IsEditable="True" IsTextSearchEnable="False"
x:Name ="CBName"
IsDropDownOpen="{Binding IsDrop}"
GotFocus="GotFocus"
LostFocus="LostFocus"/><!--加上这一个-->
publicvoidGotFocus(object sender,RoutedEventArgs e){
CBName.IsDropDownOpen =true;if(CBName.Text.Equals("占位符")) CBName.Text="";}publicvoidLostFocus(object sender,RoutedEventArgs e){
CBName.IsDropDownOpen =false;if(CBName.Text.IsNullOrEmpty()) CBName.Text="占位符";}
这样,当焦点离开ComboBox的时候,如果文本为空,则会出现占位符进行提示,再次点击时占位符就会被自动清空。不过需要注意的时,这两个事件在程序一开始并不会被触发,仅靠上面的代码,在程序刚开始运行时会发现ComboBox为空,而且没有占位符,所以需要给mSearchText(而不是SearchText,避免不正确的更新)一个初始值"占位符"即可。不过,
我们可以看到,当失去焦点时,LostFocus会将下拉框合上,而对CBName.Text的赋值又会在SearchText里面将其(试图)展开,从结果而言(在我的电脑上,win10,.NET Framework 4.6.2,Visual Studio 2017)实际上并没有展开。原因尚不清楚,大家可以自己验证看看。不过这一问题会带来一个小问题,即"占位符"的存在可能导致SearchText的更新不正确,需要我们修改一下它的更新逻辑:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论