WPF组合框搜索打字速度

WPF Combobox search typing speed

我有一个 WPF 组合框

<ComboBox BorderThickness="0" Name="cmb_songs_head" HorizontalAlignment="Right" SelectedItem="{Binding Path=T.SelectedSong, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=T.SelectedSet.Songs, UpdateSourceTrigger=PropertyChanged}" />

当我选择组合框并键入时,它会从下拉列表中进行选择 - 这正是我想要的。例如:

Hall
Hold
Hollow
Lead

所以当我键入 H 时,第一项被选中,Ho 选择第二项,Holl 选择第三项。

但是,我的程序的用户抱怨他们通常打字太慢,所以他们最终输入 Hol 选择 Hold,然后输入 l,选择 Lead;而不是将其视为 Hollow.

的一个输入

有什么办法可以延长单词之间的超时时间吗?

绑定ComboBox.SelectedItem时可以设置Binding.Delay

以下示例将绑定的延迟设置为1500ms。在延迟结束之前发生的绑定目标或源的每次更改都将重置延迟计时器:

<ComboBox Name="cmb_songs_head" 
          StaysOpenOnEdit="True"
          IsEditable="True"
          SelectedItem="{Binding T.SelectedSong, Delay=1500}"
          ItemsSource="{Binding T.SelectedSet.Songs}" />

备注

可以简化绑定以增强可读性:
ComboBox.SelectedItem 默认绑定 TwoWay
UpdateSourceTrigger.PropertyChangedItemsControl 属性的默认触发器。


更新

这是默认的搜索行为。通过键入,匹配项目被搜索并且匹配实际上是 selected。

由于匹配立即分配给 ComboBox.SelectedItem,它可能会对 select 不匹配的内容产生不必要的副作用。尤其是select离子触发操作时。

如果您想自动select最接近的匹配或提出建议,我建议改用集合过滤。

我会写一个附加行为,它监听 ComboBox.PreviewTextInputTextBoxBase.PreviewKeyUp 事件来处理过滤。
以下示例改为在代码隐藏中处理此事件,并假定 ComboBox 项类型为 stringBinding.Delay设置为5s:

查看

<ComboBox ItemsSource="{Binding T.SelectedSet.Songs}" 
          SelectedItem="{Binding T.SelectedSong, Delay=5000}"
          StaysOpenOnEdit="True" 
          IsEditable="True" 
          TextBoxBase.PreviewKeyUp="EditTextBox_OnPreviewKeyUp"
          PreviewTextInput="ComboBox_OnPreviewTextInput" />

代码隐藏

private void EditTextBox _OnPreviewKeyUp(object sender, KeyEventArgs e)
{
  var editTextBox = e.OriginalSource as TextBox;
  var comboBox = sender as ComboBox;

  switch (e.Key)
  {
    case Key.Back:
    {
      MainWindow.FilterComboBoxItemsSource(sender as ComboBox, editTextBox.Text, editTextBox);
      int selectionStart = comboBox.SelectedItem == null
        ? editTextBox.CaretIndex
        : Math.Max(0, editTextBox.SelectionStart - 1);
      int selectionLength = comboBox.SelectedItem == null 
        ? 0 
        : editTextBox.Text.Length - selectionStart;
      editTextBox.Select(selectionStart, selectionLength);
      break;
    }
    case Key.Space:
    {
      MainWindow.FilterComboBoxItemsSource(sender as ComboBox, editTextBox.Text, editTextBox);
      break;
    }
    case Key.Delete:
    {
      int currentCaretIndex = editTextBox.CaretIndex;
      MainWindow.FilterComboBoxItemsSource(sender as ComboBox, editTextBox.Text, editTextBox);
      editTextBox.CaretIndex = currentCaretIndex;
      break;
    }
  }
}

private void ComboBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
  e.Handled = true;

  var editTextBox = e.OriginalSource as TextBox;
  string oldText = editTextBox.Text.Substring(0, editTextBox.SelectionStart);
  string newText = oldText + e.Text;

  FilterComboBoxItemsSource(sender as ComboBox, newText, editTextBox);
}

private void FilterComboBoxItemsSource(ComboBox comboBox, string predicateText, TextBox editTextBox)
{
  ICollectionView collectionView = CollectionViewSource.GetDefaultView(comboBox.ItemsSource);
  if (!string.IsNullOrWhiteSpace(predicateText) 
    && !collectionView.SourceCollection
      .Cast<string>()
      .Any(item => item.StartsWith(predicateText, StringComparison.OrdinalIgnoreCase)))
  {
    int oldCaretIndex = editTextBox.CaretIndex == editTextBox.Text.Length
      ? predicateText.Length
      : editTextBox.CaretIndex;
    editTextBox.Text = predicateText;
    editTextBox.CaretIndex = oldCaretIndex;
    return;
  }

  collectionView.Filter = item => (item as string).StartsWith(string.IsNullOrWhiteSpace(predicateText) 
    ? string.Empty 
    : predicateText, StringComparison.OrdinalIgnoreCase);

  collectionView.MoveCurrentToFirst();
  editTextBox.Text = collectionView.CurrentItem as string;
  editTextBox.Select(predicateText.Length, editTextBox.Text.Length - predicateText.Length);
}