我想有一个基于ComboBox的搜索框,允许用户在一个大集合中选择一个项目。
Classic ComboBox with IsTextSearchEnabled="true">对我来说基本没问题,但是有一些问题。
- DataBase大约有5000多个条目,所以当组合框的列表框试图向我显示所有条目时,程序开始滞后。这就是为什么我想只显示一些条目。我不想看到任何东西,直到用户输入至少3个字符。
的想法是:
if (searchBox.Text.Length < 3) return;
else show only 10 (or less) entries starts with searchBox.Text.
- 另一个问题是,当用户输入3+符号时,我需要列表框自动打开。改变 IsDropDownOpen 从代码导致错误的ComboBox行为(就像它只是不工作或当我输入第二个字符第一个消失)。
总而言之,它应该像谷歌搜索栏。
最后我不能为这个程序使用任何第三方包或dll。
- 这个特性叫做UI虚拟化,并且已经实现了。但是,虽然默认情况下为
ListBox
启用了此功能,但必须显式地为ComboBox
(以及其他ItemsControl
子类型,如TreeView
)启用UI虚拟化。这使控件能够仅加载可见的项。
通常,UI虚拟化需要一个ScrollViewer
和一个VirtualizationPanel
。要为ComboBox
启用UI虚拟化,您只需要替换托管项目的默认Panel
(默认为StackPanel
)。ComboBox
已经包含了一个ScrollViewer
来承载它的内容:
<ComboBox>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
-
ComboBox
是一个相当复杂的控件。要更改其行为,我通常建议扩展控件以创建自定义ComboBox
。除了ComboBox.IsEditable
之外,您还应该将ComboBox.StaysOpenOnEdit
设置为true
。
下面的示例向您展示了如何实现在输入第三个字符后打开下拉菜单的解决方案:
MainWindow.xaml
<ComboBox IsEditable="True"
StaysOpenOnEdit="True"
PreviewTextInput="OnComboBoxPreviewTextInput"
LostFocus="OnComboBoxLostFocus" />
MainWindow.xaml.cs
private StringBuilder SearchInput { get; } = new StringBuilder();
private const int ThresholdInputTextLength = 3;
private void OnComboBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox.IsDropDownOpen)
{
return;
}
this.SearchInput.Append(e.Text);
if (this.SearchInput.Length >= ThresholdInputTextLength)
{
if (TryFindVisualChildElementByName(comboBox, "PART_EditableTextBox", out TextBox editTextBox))
{
var currentSuggestion = editTextBox.Text;
comboBox.IsDropDownOpen = true;
Dispatcher.InvokeAsync(() =>
{
editTextBox.Text = currentSuggestion;
int selectionStartIndex = this.SearchInput.Length;
int selectionLength = currentSuggestion.Length - this.SearchInput.Length;
editTextBox.Select(selectionStartIndex, selectionLength);
this.SearchInput.Clear();
}, DispatcherPriority.Input);
}
}
}
private void OnComboBoxLostFocus(object sender, EventArgs e)
=> this.SearchInput.Clear();
public static bool TryFindVisualChildElementByName<TChild>(
DependencyObject parent,
string childElementName,
out TChild resultElement) where TChild : FrameworkElement
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild frameworkElement)
{
if (string.IsNullOrWhiteSpace(childElementName) || frameworkElement.Name.Equals(
childElementName,
StringComparison.OrdinalIgnoreCase))
{
resultElement = frameworkElement;
return true;
}
}
if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
{
return true;
}
}
return false;
}