基于文本输入的WPF组合框动态过滤
我似乎找不到一种直接的方法来实现将文本输入过滤到WPF组合框中的项目列表中。基于文本输入的WPF组合框动态过滤,wpf,combobox,Wpf,Combobox,我似乎找不到一种直接的方法来实现将文本输入过滤到WPF组合框中的项目列表中。 通过将IsTextSearchEnabled设置为true,comboBox下拉列表将跳转到第一个匹配项所在的位置。我需要的是将列表过滤到与文本字符串匹配的任何内容(例如,如果我关注组合框并键入“abc”,我希望看到ItemsSource集合中以“abc”开头(或最好包含“abc”)的所有项目作为下拉列表的成员) 我怀疑这有什么不同,但我的显示项被模板化为复杂类型的属性: <ComboBox x:Name="Di
通过将IsTextSearchEnabled设置为true,comboBox下拉列表将跳转到第一个匹配项所在的位置。我需要的是将列表过滤到与文本字符串匹配的任何内容(例如,如果我关注组合框并键入“abc”,我希望看到ItemsSource集合中以“abc”开头(或最好包含“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}"/>
谢谢。听起来你真正想要的是一个类似于自动完成文本框的东西,它在一个类似于组合框的弹出窗口中提供完成建议 您可能会发现这篇CodeProject文章很有用:
您可以尝试给出反馈。我计划获得尽可能多的反馈,并创建我们在WPF中都错过的完美过滤组合框。我几天前刚刚使用此站点的代码修改版本完成了此操作: 我的完整代码如下:
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>
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的值并使用反射来获取属性以使用它,但这会更慢,因此除非必要,否则我不建议这样做
此外,此实现不会阻止用户在组合框的文本框部分键入他们喜欢的内容。如果他们在那里输入了一些愚蠢的东西,SelectedItem将恢复为NULL,所以请准备在代码中处理它
另外,如果您有许多物品,我强烈建议您使用上面的示例中的VirtualzingStackPanel,因为它会大大缩短加载时间Kelly的答案非常好。但是,有一个小错误,如果您在列表中选择一个项目(突出显示输入文本),然后按BackSpace,则输入文本将恢复为所选项目,并且组合框的SelectedItem属性仍然是您以前选择的项目 下面是修复错误的代码,并添加了在输入文本匹配时自动选择项目的功能
using System.Collections;
using System.Diagnostics;
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)
{
var temp = Text;
RefreshFilter(); //RefreshFilter will change Text property
Text = temp;
if (SelectedIndex != -1 && Text != Items[SelectedIndex].ToString())
{
SelectedIndex = -1; //Clear selection. This line will also clear Text property
Text = temp;
}
IsDropDownOpen = true;
EditableTextBox.SelectionStart = int.MaxValue;
}
//automatically select the item when the input text matches it
for (int i = 0; i < Items.Count; i++)
{
if (Text == Items[i].ToString())
SelectedIndex = i;
}
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());
}
}
}
使用系统集合;
使用系统诊断;
使用System.Windows.Controls;
使用System.Windows.Data;
使用System.Windows.Input;
命名空间MyControls
{
公共类FilteredComboBox:ComboBox
{
私有字符串oldFilter=string.Empty;
私有字符串currentFilter=string.Empty;
受保护的文本框EditableTextBox=>GetTemplateChild(“PART_EditableTextBox”)作为文本框;
已更改受保护的重写资源(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;
}
基本资源更改(旧值、新值);
}
PreviewKeyDown(KeyEventArgs e)上受保护的覆盖无效
{
开关(电子钥匙)
{
案例键。选项卡:
大小写键。输入:
IsDropDownOpen=false;
打破
箱子钥匙。逃生:
IsDropDownOpen=false;
SelectedIndex=-1;
Text=当前过滤器;
打破
违约:
如果(e.Key==Key.Down)IsDropDownOpen=true;
基于预览的向下(e);
打破
}
//缓存文本
oldFilter=文本;
}
受保护的覆盖无效OnKeyUp(KeyEventArgs e)
{
开关(电子钥匙)
{
case Key.Up:
case Key.Down:
打破
案例键。选项卡:
大小写键。输入:
ClearFilter();
打破
违约:
如果(文本!=旧过滤器)
{
var-temp=文本;
RefreshFilter();//RefreshFilter将更改文本属性
文本=温度;
if(SelectedIndex!=-1&&Text!=Items[SelectedIndex].ToString())
{
SelectedIndex=-1;//清除选择。此行还将清除文本属性
文本=温度;
}
IsDropDownOpen=true;
EditableTextBox.SelectionStart=int.MaxValue;
}
//当输入文本与项目匹配时自动选择该项目
对于(int i=0;i 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);
}
}
<local:FilteredComboBox ItemsSource="{Binding List}" IsEditable="True" IsTextSearchEnabled="true" StaysOpenOnEdit="True" x:Name="cmItems" SelectionChanged="CmItems_SelectionChanged">
</local:FilteredComboBox>