Search code examples
c#wpfxamlcombobox

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


In my WPF window app I a have a combo box which gets populated on the app is launched & I also wanted to enable a suggestappend type feature for the combo box (using this). But when I try to type something on the combo box I get the error : System.InvalidOperationException: Items collection must be empty before using ItemsSource.

How do I get rid of this? I want to both select from drop down list or start typing in combo box and then select item from there on window load.

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" />

Solution

  • Using ICollectionView.Filter would work like this:

    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;
    
        }
    
      }
    }
    

    since the EditableTextBox has its own logic, w/o styling it, the code-behind have some weird elements, like moving the Caret, etc...