我似乎找不到直接的方法来实现将文本输入过滤到WPF组合框中的项目列表中。
通过将IsTextSearchEnabled设置为true,comboBox下拉列表将跳转到第一个匹配项目的任何内容。我需要的是将列表过滤到与文本字符串匹配的任何内容(例如,如果我专注于我的组合框并键入'abc',我希望看到ItemsSource集合中的所有项目以(或最好包含)开头)'abc'作为下拉列表的成员。)
我怀疑它有所不同,但我的显示项目模板化为复杂类型的属性:
<ComboBox x:Name="DiagnosisComboBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3"
ItemsSource="{Binding Path = ApacheDxList,
UpdateSourceTrigger=PropertyChanged,
Mode=OneWay}"
IsTextSearchEnabled="True"
ItemTemplate="{StaticResource DxDescriptionTemplate}"
SelectedValue="{Binding Path = SelectedEncounterDetails.Diagnosis,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
感谢。
答案 0 :(得分:6)
我几天前刚使用此网站代码的修改版本执行此操作:Credit where credit is due
我的完整代码如下:
using System.Collections;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace MyControls
{
public class FilteredComboBox : ComboBox
{
private string oldFilter = string.Empty;
private string currentFilter = string.Empty;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
case Key.Enter:
IsDropDownOpen = false;
break;
case Key.Escape:
IsDropDownOpen = false;
SelectedIndex = -1;
Text = currentFilter;
break;
default:
if (e.Key == Key.Down) IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
break;
}
// Cache text
oldFilter = Text;
}
protected override void OnKeyUp(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
break;
case Key.Tab:
case Key.Enter:
ClearFilter();
break;
default:
if (Text != oldFilter)
{
RefreshFilter();
IsDropDownOpen = true;
EditableTextBox.SelectionStart = int.MaxValue;
}
base.OnKeyUp(e);
currentFilter = Text;
break;
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
ClearFilter();
var temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
private void ClearFilter()
{
currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (Text.Length == 0) return true;
return value.ToString().ToLower().Contains(Text.ToLower());
}
}
}
WPF应该是这样的:
<MyControls:FilteredComboBox ItemsSource="{Binding MyItemsSource}"
SelectedItem="{Binding MySelectedItem}"
DisplayMemberPath="Name"
IsEditable="True"
IsTextSearchEnabled="False"
StaysOpenOnEdit="True">
<MyControls:FilteredComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizationMode="Recycling" />
</ItemsPanelTemplate>
</MyControls:FilteredComboBox.ItemsPanel>
</MyControls:FilteredComboBox>
这里要注意一些事项。您会注意到FilterItem实现在对象上执行ToString()。这意味着您要显示的对象的属性应该在object.ToString()实现中返回。 (或者已经是一个字符串)换句话说就是这样:
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
public override string ToString()
{
return Name;
}
}
如果这不能满足您的需求,我想您可以获得DisplayMemberPath的值并使用反射来获取属性以使用它,但这样会慢一点,所以除非必要,否则我不建议这样做。 / p>
此实现也不会阻止用户在ComboBox的TextBox部分中键入他们喜欢的内容。如果他们输入了一些愚蠢的东西,那么SelectedItem将恢复为NULL,所以要准备好在你的代码中处理它。
此外,如果您有很多项目,我强烈建议您使用VirtualizingStackPanel,就像上面的示例一样,因为它在加载时间方面有很大差异
答案 1 :(得分:1)
您可以尝试https://www.nuget.org/packages/THEFilteredComboBox/并提供反馈。我计划尽可能多地获得反馈并创建完美的滤波组合框,我们都错过了WPF。
答案 2 :(得分:1)
以下是修复错误的代码,并添加了在输入文本与其匹配时自动选择项目的功能。
using S = struct { int x; }
答案 3 :(得分:1)
基于this answer,我添加了:
InputSource
属性将用户输入限制为OnlyValuesInList
中提供的值的功能。SelectionEffectivelyChanged
事件仅在用户离开控件或按Enter时触发,如从标准ComboBox过滤SelectionChanged
事件时触发多次。EffectivelySelectedItem
属性仅在用户离开控件或按Enter时才会更改,就像从标准ComboBox过滤SelectedItem
项的过程中更改一样。public class FilterableComboBox : ComboBox
{
/// <summary>
/// If true, on lost focus or enter key pressed, checks the text in the combobox. If the text is not present
/// in the list, it leaves it blank.
/// </summary>
public bool OnlyValuesInList {
get => (bool)GetValue(OnlyValuesInListProperty);
set => SetValue(OnlyValuesInListProperty, value);
}
public static readonly DependencyProperty OnlyValuesInListProperty =
DependencyProperty.Register(nameof(OnlyValuesInList), typeof(bool), typeof(FilterableComboBox));
/// <summary>
/// Selected item, changes only on lost focus or enter key pressed
/// </summary>
public object EffectivelySelectedItem {
get => (bool)GetValue(EffectivelySelectedItemProperty);
set => SetValue(EffectivelySelectedItemProperty, value);
}
public static readonly DependencyProperty EffectivelySelectedItemProperty =
DependencyProperty.Register(nameof(EffectivelySelectedItem), typeof(object), typeof(FilterableComboBox));
private string CurrentFilter = string.Empty;
private bool TextBoxFreezed;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
private UserChange<bool> IsDropDownOpenUC;
/// <summary>
/// Triggers on lost focus or enter key pressed, if the selected item changed since the last time focus was lost or enter was pressed.
/// </summary>
public event Action<FilterableComboBox, object> SelectionEffectivelyChanged;
public FilterableComboBox()
{
IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);
DropDownOpened += FilteredComboBox_DropDownOpened;
IsEditable = true;
IsTextSearchEnabled = true;
StaysOpenOnEdit = true;
IsReadOnly = false;
Loaded += (s, e) => {
if (EditableTextBox != null)
new TextBoxBaseUserChangeTracker(EditableTextBox).UserTextChanged += FilteredComboBox_UserTextChange;
};
SelectionChanged += (_, __) => shouldTriggerSelectedItemChanged = true;
SelectionEffectivelyChanged += (_, o) => EffectivelySelectedItem = o;
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Down && !IsDropDownOpen) {
IsDropDownOpen = true;
e.Handled = true;
}
else if (e.Key == Key.Escape) {
ClearFilter();
Text = "";
IsDropDownOpen = true;
}
else if (e.Key == Key.Enter || e.Key == Key.Tab) {
CheckSelectedItem();
TriggerSelectedItemChanged();
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnPreviewLostKeyboardFocus(e);
CheckSelectedItem();
if ((e.OldFocus == this || e.OldFocus == EditableTextBox) && e.NewFocus != this && e.NewFocus != EditableTextBox)
TriggerSelectedItemChanged();
}
private void CheckSelectedItem()
{
if (OnlyValuesInList)
Text = SelectedItem?.ToString() ?? "";
}
private bool shouldTriggerSelectedItemChanged = false;
private void TriggerSelectedItemChanged()
{
if (shouldTriggerSelectedItemChanged) {
SelectionEffectivelyChanged?.Invoke(this, SelectedItem);
shouldTriggerSelectedItemChanged = false;
}
}
public void ClearFilter()
{
if (string.IsNullOrEmpty(CurrentFilter)) return;
CurrentFilter = "";
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
}
private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)
{
if (IsDropDownOpenUC.IsUserChange)
ClearFilter();
}
private void FilteredComboBox_UserTextChange(object sender, EventArgs e)
{
if (TextBoxFreezed) return;
var tb = EditableTextBox;
if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();
else
CurrentFilter = tb.Text.ToLower();
RefreshFilter();
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null) {
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null) {
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
FreezTextBoxState(() => {
var isDropDownOpen = IsDropDownOpen;
//always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()
IsDropDownOpenUC.Set(false);
view.Refresh();
if (!string.IsNullOrEmpty(CurrentFilter) || isDropDownOpen)
IsDropDownOpenUC.Set(true);
if (SelectedItem == null) {
foreach (var itm in ItemsSource)
if (itm.ToString() == Text) {
SelectedItem = itm;
break;
}
}
});
}
private void FreezTextBoxState(Action action)
{
TextBoxFreezed = true;
var tb = EditableTextBox;
var text = Text;
var selStart = tb.SelectionStart;
var selLen = tb.SelectionLength;
action();
Text = text;
tb.SelectionStart = selStart;
tb.SelectionLength = selLen;
TextBoxFreezed = false;
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (CurrentFilter.Length == 0) return true;
return value.ToString().ToLower().Contains(CurrentFilter);
}
private class TextBoxBaseUserChangeTracker
{
private bool IsTextInput { get; set; }
public TextBoxBase TextBoxBase { get; set; }
private List<Key> PressedKeys = new List<Key>();
public event EventHandler UserTextChanged;
private string LastText;
public TextBoxBaseUserChangeTracker(TextBoxBase textBoxBase)
{
TextBoxBase = textBoxBase;
LastText = TextBoxBase.ToString();
textBoxBase.PreviewTextInput += (s, e) => {
IsTextInput = true;
};
textBoxBase.TextChanged += (s, e) => {
var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBoxBase.ToString();
IsTextInput = false;
LastText = TextBoxBase.ToString();
if (isUserChange)
UserTextChanged?.Invoke(this, e);
};
textBoxBase.PreviewKeyDown += (s, e) => {
switch (e.Key) {
case Key.Back:
case Key.Space:
if (!PressedKeys.Contains(e.Key))
PressedKeys.Add(e.Key);
break;
}
if (e.Key == Key.Back) {
var textBox = textBoxBase as TextBox;
if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length) {
textBox.SelectionStart--;
textBox.SelectionLength++;
e.Handled = true;
UserTextChanged?.Invoke(this, e);
}
}
};
textBoxBase.PreviewKeyUp += (s, e) => {
if (PressedKeys.Contains(e.Key))
PressedKeys.Remove(e.Key);
};
textBoxBase.LostFocus += (s, e) => {
PressedKeys.Clear();
IsTextInput = false;
};
}
}
private class UserChange<T>
{
private Action<T> action;
public bool IsUserChange { get; private set; } = true;
public UserChange(Action<T> action)
{
this.action = action;
}
public void Set(T val)
{
try {
IsUserChange = false;
action(val);
}
finally {
IsUserChange = true;
}
}
}
}
答案 4 :(得分:0)
这是我的看法。一种不同的方法,一种是我为自己制定的方法,另一种是我正在使用的方法。它与IsTextSearchEnabled =“ true”一起使用。我刚刚完成它,所以可能会有一些错误。
public class TextBoxBaseUserChangeTracker
{
private bool IsTextInput { get; set; }
public TextBoxBase TextBox { get; set; }
private List<Key> PressedKeys = new List<Key>();
public event EventHandler UserTextChanged;
private string LastText;
public TextBoxBaseUserChangeTracker(TextBoxBase textBox)
{
TextBox = textBox;
LastText = TextBox.ToString();
textBox.PreviewTextInput += (s, e) =>
{
IsTextInput = true;
};
textBox.TextChanged += (s, e) =>
{
var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBox.ToString();
IsTextInput = false;
LastText = TextBox.ToString();
if (isUserChange)
UserTextChanged?.Invoke(this, e);
};
textBox.PreviewKeyDown += (s, e) =>
{
switch (e.Key)
{
case Key.Back:
case Key.Space:
case Key.Delete:
if (!PressedKeys.Contains(e.Key))
PressedKeys.Add(e.Key);
break;
}
};
textBox.PreviewKeyUp += (s, e) =>
{
if (PressedKeys.Contains(e.Key))
PressedKeys.Remove(e.Key);
};
textBox.LostFocus += (s, e) =>
{
PressedKeys.Clear();
IsTextInput = false;
};
}
}
public static class ExtensionMethods
{
#region DependencyObject
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return parentObject.FindParent<T>();
}
#endregion
#region TextBoxBase
public static TextBoxBaseUserChangeTracker TrackUserChange(this TextBoxBase textBox)
{
return new TextBoxBaseUserChangeTracker(textBox);
}
#endregion
}
public class UserChange<T>
{
private Action<T> action;
private bool isUserChange = true;
public bool IsUserChange
{
get
{
return isUserChange;
}
}
public UserChange(Action<T> action)
{
this.action = action;
}
public void Set(T val)
{
try
{
isUserChange = false;
action(val);
}
finally
{
isUserChange = true;
}
}
}
public class FilteredComboBox : ComboBox
{
// private string oldFilter = string.Empty;
private string CurrentFilter = string.Empty;
private bool TextBoxFreezed;
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
private UserChange<bool> IsDropDownOpenUC;
public FilteredComboBox()
{
IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);
DropDownOpened += FilteredComboBox_DropDownOpened;
Loaded += (s, e) =>
{
if (EditableTextBox != null)
{
EditableTextBox.TrackUserChange().UserTextChanged += FilteredComboBox_UserTextChange;
}
};
}
public void ClearFilter()
{
if (string.IsNullOrEmpty(CurrentFilter)) return;
CurrentFilter = "";
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
}
private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)
{
//if user opens the drop down show all items
if (IsDropDownOpenUC.IsUserChange)
ClearFilter();
}
private void FilteredComboBox_UserTextChange(object sender, EventArgs e)
{
if (TextBoxFreezed) return;
var tb = EditableTextBox;
if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();
else
CurrentFilter = tb.Text.ToLower();
RefreshFilter();
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterItem;
}
if (oldValue != null)
{
var view = CollectionViewSource.GetDefaultView(oldValue);
if (view != null) view.Filter -= FilterItem;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void RefreshFilter()
{
if (ItemsSource == null) return;
var view = CollectionViewSource.GetDefaultView(ItemsSource);
FreezTextBoxState(() =>
{
var isDropDownOpen = IsDropDownOpen;
//always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()
IsDropDownOpenUC.Set(false);
view.Refresh();
if (!string.IsNullOrEmpty(CurrentFilter) || isDropDownOpen)
IsDropDownOpenUC.Set(true);
if (SelectedItem == null)
{
foreach (var itm in ItemsSource)
{
if (itm.ToString() == Text)
{
SelectedItem = itm;
break;
}
}
}
});
}
private void FreezTextBoxState(Action action)
{
TextBoxFreezed = true;
var tb = EditableTextBox;
var text = Text;
var selStart = tb.SelectionStart;
var selLen = tb.SelectionLength;
action();
Text = text;
tb.SelectionStart = selStart;
tb.SelectionLength = selLen;
TextBoxFreezed = false;
}
private bool FilterItem(object value)
{
if (value == null) return false;
if (CurrentFilter.Length == 0) return true;
return value.ToString().ToLower().Contains(CurrentFilter);
}
}
Xaml:
<local:FilteredComboBox ItemsSource="{Binding List}" IsEditable="True" IsTextSearchEnabled="true" StaysOpenOnEdit="True" x:Name="cmItems" SelectionChanged="CmItems_SelectionChanged">
</local:FilteredComboBox>
答案 5 :(得分:-1)
听起来你正在寻找的东西类似于自动完成的文本框,它在类似于组合框弹出窗口的弹出窗口中提供完成建议。
您可能会发现此CodeProject文章很有用: