WPF 组合框 - 尝试输入时显示错误

WPF combo box - error showing when trying to type in it

在我的 WPF window 应用程序中,我有一个组合框,它在应用程序启动时被填充,我还想为组合框 (using this) 启用 suggestappend 类型功能。但是,当我尝试在组合框中键入内容时,出现错误:System.InvalidOperationException: Items collection must be empty before using ItemsSource.

我该如何摆脱它?我想从下拉列表中 select 或开始在组合框中输入 select 项目,然后 window 加载 select 项目。

public partial class Window1 : Window
{
    List<string> Names= new List<string>();
    int lastRow = 0;
    
    string file_Bills=@"BILLS.xlsx";
    string file_CNDN=@"CN_DN.xlsx";
    string file_supp=@"suppliers.xlsx";
    
    public Window1()
    {
        InitializeComponent();
        ExcelPackage.LicenseContext =LicenseContext.NonCommercial;
        FillCombo();
    }
    
    public List<string> FillCombo()
    {
        using (ExcelPackage package = new ExcelPackage(new System.IO.FileInfo(file_Bills), false))
        {
            ExcelWorksheet mainSheet = package.Workbook.Worksheets.First();

            for (int i = 2; i <= mainSheet.Dimension.End.Row; i++)
            {
                if (!string.IsNullOrEmpty(mainSheet.Cells["A"+i].Text))
                {
                    lastRow =i;
                }
            }
            List<string> party = new List<string>();
            
            for (int row = 2; row <= lastRow; row++)
            {
                if (!string.IsNullOrEmpty(mainSheet.Cells[row, 1].Text))
                {
                    party.Add(mainSheet.Cells[row, 1].Text);
                }
            }
            
            
            foreach (var element in party.OrderBy(a=>a.ToLowerInvariant()).Distinct())
            {
                cmb.Items.Add(element);
                Names.Add(element);
            }

        }
        
        return Names;
    }
    
    //portion implementing the suggestappend like feature
    public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
    {
        if (depObj == null) return null;

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            var child = VisualTreeHelper.GetChild(depObj, i);

            var result = (child as T) ?? GetChildOfType<T>(child);
            if (result != null) return result;
        }
        return null;
    }
    
    private void PreviewTextInput_EnhanceComboSearch(object sender, TextCompositionEventArgs e)
    {
        ComboBox cmb = (ComboBox)sender;

        cmb.IsDropDownOpen = true;

        if (!string.IsNullOrEmpty(cmb.Text))
        {
            string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, e.Text);
            cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
        }
        else if (!string.IsNullOrEmpty(e.Text))
        {
            cmb.ItemsSource = Names.Where(s => s.IndexOf(e.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
        }
        else
        {
            cmb.ItemsSource = Names;
        }
    }
    
    private void Pasting_EnhanceComboSearch(object sender, DataObjectPastingEventArgs e)
    {
        ComboBox cmb = (ComboBox)sender;

        cmb.IsDropDownOpen = true;

        string pastedText = (string)e.DataObject.GetData(typeof(string));
        string fullText = cmb.Text.Insert(GetChildOfType<TextBox>(cmb).CaretIndex, pastedText);

        if (!string.IsNullOrEmpty(fullText))
        {
            cmb.ItemsSource = Names.Where(s => s.IndexOf(fullText, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
        }
        else
        {
            cmb.ItemsSource = Names;
        }
    }
    
    private void PreviewKeyUp_EnhanceComboSearch(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Back || e.Key == Key.Delete)
        {
            ComboBox cmb = (ComboBox)sender;

            cmb.IsDropDownOpen = true;

            if (!string.IsNullOrEmpty(cmb.Text))
            {
                cmb.ItemsSource = Names.Where(s => s.IndexOf(cmb.Text, StringComparison.InvariantCultureIgnoreCase) != -1).ToList();
            }
            else
            {
                cmb.ItemsSource = Names;
            }
        }
    }
}

XAML:

            <ComboBox
                IsTextSearchEnabled="False"
                PreviewTextInput="PreviewTextInput_EnhanceComboSearch"
                PreviewKeyUp="PreviewKeyUp_EnhanceComboSearch"
                DataObject.Pasting="Pasting_EnhanceComboSearch"
                IsEditable="True"
                Name="cmb"
                Grid.Column="0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                FontFamily="Segoe UI"
                FontStyle="Normal"
                FontSize="18"
                Margin="10 10 10 0"
                Height="35"
                MaxHeight="40"
                Width="300"
                MaxWidth="450" />

添加本地集合,比方说cBItems

 private List<string> cBItems;

在你的c'中填写列表,而不是添加到cmb.Items

      foreach (var element in party.OrderBy(a=>a.ToLowerInvariant()).Distinct())
        {
            cBItems.Add(element);
            Names.Add(element);
        }

然后设置ItemsSource

cmb.ItemsSource = cBItems;

所以你只操纵 ItemsSource,而让 Items 为空

无论如何,还有 CollectionView 等其他选项...但那是针对 MVVM...;-)

使用 ICollectionView.Filter 会像这样工作:

XAML:

<Window x:Class="WPFSandbox.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFSandbox"
        mc:Ignorable="d"
        Title="Window1"
        Height="450"
        Width="800">
  <Grid>
    <ComboBox x:Name="cmb"
              IsEditable="True"
              KeyUp="cmb_KeyUp"
              Height="25"
              VerticalAlignment="Top"
              IsTextSearchEnabled="False" 
              IsReadOnly="false"
              />
  </Grid>
</Window>

xaml.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace WPFSandbox
{
  public partial class Window1 : Window
  {
    private List<string> Items { get; }
    private ICollectionView view;

    public Window1()
    {
      InitializeComponent();
      Items = new List<string>()
      {
      "john",
      "john doe",
      "jane",
      "jane doe",
      "steve miller",
      "jane miller"
      };

      cmb.ItemsSource = Items;
      KeyboardNavigation.SetDirectionalNavigation(cmb, KeyboardNavigationMode.Cycle);
      view = CollectionViewSource.GetDefaultView(Items);
    }

    private void cmb_KeyUp(object sender, KeyEventArgs e)
    {
      var tb = cmb.Template.FindName("PART_EditableTextBox", cmb) as TextBox;
      var val = tb.Text;
      var empty = string.IsNullOrEmpty(tb.Text);

      var keysToIgnore = new Key[] { Key.Down, Key.Up, Key.Enter, Key.Left, Key.Right };

      if (keysToIgnore.Contains(e.Key))
      {
        return;
      }

      if (empty)
      {
        view.Filter = null;
      }
      else
      {
        view.Filter = (i) =>
        {
          var str = i.ToString();
          return str.ToLowerInvariant().Contains(tb.Text.ToLowerInvariant());
        };
      }

      cmb.IsDropDownOpen = true;

      tb.Text = val;
      tb.CaretIndex = tb.Text.Length;

    }

  }
}

由于 EditableTextBox 有自己的逻辑,w/o 对其进行样式设置,因此 code-behind 有一些奇怪的元素,例如移动插入符等...