一种方法是使用事件(包括MVVM的绑定)
<ComboBox TextBoxBase.TextChanged="ComboBox_TextChanged" />
然而运行时就会发现,这个事件在疯狂的触发,很频繁
在实际应用中,如果关联查询数据库,网络吞吐什么的,就会卡顿
另一种方法是使用IsTextSearchEnabled属性,在文本框敲键盘会自动选择相关项
<ComboBox IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" TextSearch.TextPath="Name" />
然而又有新的问题,选择项不会显示到文本框(文本框仍然是键盘敲的内容,当然还可能跟Framework版本有关),于是我们需要更深入
试验数据含 3 个项
<ComboBox TextBoxBase.TextChanged="ComboBox_TextChanged">
<ComboBoxItem>啊啊啊</ComboBoxItem>
<ComboBoxItem>哦哦哦</ComboBoxItem>
<ComboBoxItem>呃呃呃</ComboBoxItem>
</ComboBox>
试验一:绑定 TextChanged 和 SelectionChanged 调试
private void Combobox_TextChanged(object sender, TextChangedEventArgs e)
{
// e.OriginalSource == TextBox, e.Source == sender == Combobox
var tb = e.OriginalSource as TextBox;
var cb = e.Source as ComboBox;
var cs = e.Changes.ToArray();
int alen = -1, offs = -1, rlen = -1;
if (cs.Length > 0) { alen = cs[0].AddedLength; offs = cs[0].Offset; rlen = cs[0].RemovedLength; }
var str1 = TextSearch.GetText(cmbStudios);
System.Diagnostics.Debug.Print($"TextBox.Text={tb.Text},ComboBox.Text={cb.Text},TextSearch.Text={str1},Action={e.UndoAction}, Changes={cs.Length}: [0]={
{
{alen},{offs},{rlen}}}\r\n");
}
private void Combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var cb = e.Source as ComboBox;
System.Diagnostics.Debug.Print($"ComboBox.Text={cb.Text},SelectedItem={cb.SelectedItem}\r\n");
}
记录
TextBox.Text=a,ComboBox.Text=,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,0}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,1}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=None, Changes=0: [0]={-1,-1,-1}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,1}
ComboBox.Text=啊,SelectedItem=啊啊啊
TextBox.Text=啊啊啊,ComboBox.Text=啊啊啊,TextSearch.Text=,Action=Create, Changes=1: [0]={3,0,1}
也就是选择项目正常设置Text属性,但是现在我们要让Text改变自动选择项目
试验二:启用IsTextSearchEnabled属性
TextBox.Text=a,ComboBox.Text=,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,0}
ComboBox.Text=,SelectedItem=啊啊啊
TextBox.Text=啊,ComboBox.Text=啊啊啊,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,1}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=None, Changes=0: [0]={-1,-1,-1}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,1}
TextBox.Text=啊,ComboBox.Text=啊,TextSearch.Text=,Action=Create, Changes=1: [0]={1,0,1}
敲a一次,空格变成“啊”自动选择“啊啊啊”项目,然后就开始抽,完全看不到适当的介入时机
用过Win10的都知道,文件夹右上角的搜索框,能在输入法完成后才开始搜索,所以一定是可以实现的
换网,查英文资料发现Win11中有TextCompositionEnded事件,但是WPF中找不到,类似的TextCompositionManager外挂
实验三:启用TextComposition事件
<TextBox TextCompositionManager.TextInputStart="TextBox_TextInputStart" TextCompositionManager.TextInputUpdate="TextBox_TextInputUpdate" TextCompositionManager.TextInput="TextBox_TextInput" />
事件响应代码
private void TextBox_TextInputStart(object sender, TextCompositionEventArgs e)
{
var tc = e.TextComposition;
string text = $" CompositionText={tc.CompositionText},ControlText={tc.ControlText},ControlText={tc.SystemText},Text={tc.Text} ";
System.Diagnostics.Debug.Print($"TextInputStart() ControlText={e.ControlText},SystemText={e.SystemText},Text={e.Text},TextComposition={
{
{text}}}\r\n");
}
private void TextBox_TextInputUpdate(object sender, TextCompositionEventArgs e)
{
var tc = e.TextComposition;
string text = $" CompositionText={tc.CompositionText},ControlText={tc.ControlText},ControlText={tc.SystemText},Text={tc.Text} ";
System.Diagnostics.Debug.Print($"TextInputUpdate() ControlText={e.ControlText},SystemText={e.SystemText},Text={e.Text},TextComposition={
{
{text}}}\r\n");
}
private void TextBox_TextInput(object sender, TextCompositionEventArgs e)
{
var tc = e.TextComposition;
string text = $" CompositionText={tc.CompositionText},ControlText={tc.ControlText},ControlText={tc.SystemText},Text={tc.Text} ";
System.Diagnostics.Debug.Print($"TextInput() ControlText={e.ControlText},SystemText={e.SystemText},Text={e.Text},TextComposition={
{
{text}}}\r\n");
}
记录
TextInputStart() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=,ControlText=,ControlText=,Text= }
TextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=a,ControlText=,ControlText=,Text= }
TextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=啊,ControlText=,ControlText=,Text= }
没有TextInput事件!
英文资料显示,会依次触发 TextCompositionStarted、TextChanging、TextChanged、TextCompositionChanged、TextCompositionEnded 事件
思考是隧道事件的问题:
如上面提到的外挂,WPF中的控件其实都外挂的,而且还可以自己给现有的类添加属性、方法和事件(参考:Binding Property)
由于可以任意的排布所有的控件,因此事件的响应与传统的MFC控件就开始有差异
如图,如果鼠标点在2上,传统的事件会由2响应,容器1对此一无所知
这有什么问题?有没有问题就看你怎么看问题。
所谓路由事件是把响应规则扩展为3类:
1.直接事件:等同传统事件
2.冒泡事件:从內向外依次触发,直到Handled被设置
3.隧道事件:从外向内依次触发,直到Handled被设置,这样容器就有机会在内部控件之前做出响应,事件一般以Preview开头,常见的如多个带滚动条的控件互相包含
试验四:加入Preview事件
PreviewTextInputStart() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=,ControlText=,ControlText=,Text= }
TextInputStart() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=,ControlText=,ControlText=,Text= }
PreviewTextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=a,ControlText=,ControlText=,Text= }
TextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=a,ControlText=,ControlText=,Text= }
PreviewTextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=啊,ControlText=,ControlText=,Text= }
TextInputUpdate() ControlText=,SystemText=,Text=,TextComposition={ CompositionText=啊,ControlText=,ControlText=,Text= }
PreviewTextInput() ControlText=,SystemText=,Text=啊,TextComposition={ CompositionText=,ControlText=,ControlText=,Text=啊 }
所有的都可以不管,只看PreviewTextInput检查Text属性就是输入法敲出完整的文本时
策略响应的也就简单了
1.加入一个Timer
2.在TextInput事件启动计时器,其它如Start和Update时停止计时器
3.计时器响应执行过滤逻辑,然后停止计时器
当然这是Windows文件搜索框的逻辑,也就是你敲键盘很快的话,中途不会执行搜索,你也可以根据需要进行调整,比如Start也开始计时器,如果Update过一段时间未触发,一样执行逻辑