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" />
CodePudding user response:
Add a local collection, let's say cBItems
private List<string> cBItems;
in your c'tor fill the List, instead of adding to cmb.Items
foreach (var element in party.OrderBy(a=>a.ToLowerInvariant()).Distinct())
{
cBItems.Add(element);
Names.Add(element);
}
and then set the ItemsSource
cmb.ItemsSource = cBItems;
so you manipulate the ItemsSource
only, and leave the Items
empty
Anyway, there are other options with CollectionView
, etc... but that's for MVVM... ;-)
CodePudding user response:
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...