C# WPF ComboBox 使用箭头键选择问题

问题描述 投票:0回答:1

我在我的 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 中搜索一样

c# wpf mvvm combobox
1个回答
0
投票

事情并不像你想象的那么简单。当您更改

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;
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.