我在我的 C# WPF MVVM 应用程序中创建了一个 UserControl。当我在组合框过滤产品显示中输入内容时,假设下拉列表中显示 8 个过滤产品,问题从这里开始,如果我尝试通过箭头键在这些过滤产品之间导航,它会自动选择列表中的第一项。 查看代码
<UserControl ...>
<Grid>
<ComboBox Width="300" Height="25"
ItemsSource="{Binding FilteredProducts}"
SelectedItem="{Binding SelectedProduct}"
Text="{Binding SearchText ,Mode=TwoWay}"
DisplayMemberPath="DisplayName"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
IsEditable="True">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsDropDownOpen" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</UserControl>
查看模型代码
public TestDropDownViewModel()
{
ProductList = new List<Products>...;
FilteredProducts = ProductList;
}
[ObservableProperty]
private List<Products> _productList;
[ObservableProperty]
private Products _selectedProduct;
private string _searchText = "";
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
OnPropertyChanged();
FilteredProducts = ProductList
.Where(product => product.DisplayName.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
.ToList();
}
}
private List<Products> _filteredProducts = new List<Products>();
public List<Products> FilteredProducts
{
get { return _filteredProducts; }
set
{
_filteredProducts = value;
OnPropertyChanged();
}
}
和型号代码
public partial class Products : ObservableObject
{
[ObservableProperty]
private string _code = "";
[ObservableProperty]
private string _title = "";
public string DisplayName => Code + " - " + Title;
}
过滤后的产品列表显示为我输入的内容 当我按下箭头键时,就会发生这种情况 我希望如果我输入某些内容并列表显示一些项目,我应该能够通过向上或向下箭头键从此列表中进行选择。就像我们在 Google.com 中搜索一样
事情并不像你想象的那么简单。当您更改
ComboBox.Text
属性值时,您会过滤项目。现在,使用箭头键导航还将选择项目并更改 ComboBox.Text
属性的值,这会触发过滤器,从而删除所有搜索结果。除非您有重复的条目满足基于所选项目的过滤器,否则您只能使用箭头键选择单个项目。
您当然不想清除搜索/过滤结果。
解决方案是在按下导航键时暂停过滤,并在按下导航键时恢复过滤。
您还应该正确实施集合过滤。您当前的实施非常昂贵,并且会对性能产生重大影响。
第一条规则是在绑定到集合时使用
ObservableCollection
(或任何其他实现 INotifyCollectionChanged
的集合)。然后,不要替换这个集合来更新它。替换集合是昂贵的,因为 ItemsControl
必须执行完整的布局过程来渲染自身。相反,清除/删除旧项目并添加/插入新项目。现在 ItemsControl
只需更新更改的项目,而不是所有项目。
接下来,您应该始终使用源集合的默认
ICollectionView
或 CollectionViewSource
来过滤集合。
要暂停过滤,您应该将
Binding.UpdateSourceTrigger
绑定的 ComboBox.Text
设置为 UpdateSourceTrigger.Explicit
。
如果您仍然遇到光标键导航问题,您可以自行处理选择。在这种情况下,我建议将
ComboBox.IsSynchronizedWithCurrentItem
设置为 true
,然后使用集合视图导航项目。
代码的改进版本和原始问题的解决方案可能如下所示:
TestDropDownViewModel.cs
class TestDropDownViewModel : ObservableObject
{
public TestDropDownViewModel()
{
ProductList = new ObservableCollection<Products>...;
}
[ObservableProperty]
// Use an ObservableCollection to improve rendering performance
private ObservableCollection<Products> _productList;
[ObservableProperty]
private Products _selectedProduct;
private string _searchText = "";
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
OnPropertyChanged();
FilterProducts();
}
}
// Filtering using the ICollectionView to improve rendering performance
private void FilterProducts()
{
var defaultCollectionView = CollectionViewSource.GetDefaultView(this.ProductList);
defaultCollectionView.Filter =
item => ((Products)item).DisplayName.Contains(this.SearchText, StringComparison.OrdinalIgnoreCase);
}
}
MainWindow.xaml
<ComboBox ItemsSource="{Binding ProductList}"
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
PreviewKeyUp="ComboBox_PreviewKeyUp"
IsSynchronizedWithCurrentItem="True"
... >
</ComboBox>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Related code is optional in case you experience cursor key navigation issues.
// For example, when you are not able to select items using the arrow down key
// while in the text box of the ComboBox then use this event handling code.
//
// Because the ComboBox marks cursor key related instance events
// as handled internally we must handle these events on class level.
EventManager.RegisterClassHandler(typeof(ComboBox), PreviewKeyDownEvent, new KeyEventHandler(OnComboBoxPreviewKeyDown));
}
// Mandatory: use to prevent cursor key navigation from triggering the filter
private void ComboBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
var comboBox = (ComboBox)sender;
if (e.Key is not Key.Down
and not Key.Up
and not Key.Left
and not Key.Right)
{
comboBox.GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
}
// Optional: manually handle cursor kkey selection in case the ComboBox
// has issues (introduced by the source collection filtering)
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
var comboBox = (ComboBox)sender;
switch (e.Key)
{
case Key.Down:
_ = comboBox.Items.MoveCurrentToNext();
e.Handled = true;
break;
case Key.Up:
_ = comboBox.Items.MoveCurrentToPrevious();
e.Handled = true;
break;
}
}
}