У меня есть ComboBox
, чье ItemsSource
является ListCollectionView
, которое реализует фильтрацию и группировку.Если я укажу GroupStyle
для ComboBox
в xaml, каждый раз, когда я впервые выбираю элемент из списка ComboBoxItems
, я получаю исключение ArgumentOutOfRange
:
"Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index"
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.get_Item(Int32 index)
at MS.Internal.Data.CollectionViewGroupInternal.Clear()
at System.Windows.Data.ListCollectionView.PrepareShaping()
at System.Windows.Data.ListCollectionView.PrepareLocalArray()
at System.Windows.Data.ListCollectionView.RefreshOverride()
at System.Windows.Data.CollectionView.RefreshInternal()
at System.Windows.Data.CollectionView.Refresh()
at kcplane.ViewModel.ShipViewModel.set_ComboSearchText(String value)
Выбороднако все еще действителен, и изменение выбора распознается (например, запускает повторную сортировку элементов в смежном ComboBox
, значения которого зависят от этого выбора).
Если функция поиска Iреализовано, используется для поиска выбора, тогда больше нет проблем.
Я пытаюсь сгруппировать ComboBoxItems
и отфильтровать их с помощью пользовательской функции.Чтобы обеспечить немного контекста для объектов ListCollectionView
и сделать код более понятным, ComboBox
предоставляет выбор кораблей, и эти корабли сгруппированы по их типу (линкор / разрушитель и т. Д.).Ввод ComboBox
запускает функцию поиска, которая находит корабли в зависимости от того, содержит ли их название текст поиска.
До сих пор я обнаружил обсуждения исключений ArgumentOutOfRange
/ IndexOutOfRange
, относящихся к ICollectionView
s в целом, но не все относятся к C #, не говоря уже о wpf. Этот пост от social.microsoft.com кажется наиболее похожим, хотя и относится к сортировке.Но это сводится к той же ошибке (ListCollectionView
пытается PrepareShaping()
, и терпит неудачу).В этом случае проблема, как мне кажется, заключалась в попытке отсортировать элементы из другого потока при одновременном изменении исходной коллекции.Здесь все, что я делаю, это применяю фильтр и добавляю описание группы, поэтому я верю, что Windows должна иметь возможность обрабатывать оба сразу, когда вызывается Refresh()
, чтобы обновить ListCollectionView
.
XAML (ShipView.xaml)
<ComboBox Name="ShipCombo" Grid.Row="0" Margin="0,0,0,2"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
ItemsSource="{Binding AvailableShipsCV}"
IsSynchronizedWithCurrentItem="True"
IsEditable="True"
StaysOpenOnEdit="True"
IsTextSearchEnabled="False"
Text="{Binding ComboSearchText, Mode=TwoWay}"
DisplayMemberPath="Name">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
</ComboBox.ItemContainerStyle>
<!-- If the section below is taken out, no errors will appear -->
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
ViewModel (ShipViewModel.cs)
public class ShipViewModel : INotifyPropertyChanged
{
#region Fields
//...
private string comboSearchText;
#endregion
#region Constructors
public ShipViewModel(List<BaseShip> AvailableShips = null, List<BaseEquipment> AvailableEquipment = null)
{
AvailableShipsCV = (ListCollectionView)new CollectionViewSource { Source = AvailableShips }.View;
AvailableShipsCV.Filter = ShipCollectionViewFilter;
AvailableShipsCV.GroupDescriptions.Add(new PropertyGroupDescription("Type")); //There is a property called "Type" in the BaseShip object
AvailableShipsCV.CurrentChanged += AvailableShipsCV_CurrentChanged;
//...
}
#endregion Constructors
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Events
#region Properties
public ListCollectionView AvailableShipsCV { get; private set; }
//...
public string ComboSearchText
{
get
{
return comboSearchText;
}
set
{
comboSearchText = value;
/////////////////////////////////////
// This line triggers the error if //
// GroupStyle is defined in xaml //
/////////////////////////////////////
AvailableShipsCV.Refresh();
OnPropertyChanged("ComboSearchText");
}
}
#endregion Properties
#region Methods
public void OnPropertyChanged(string PropertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
// Delegate for filter
private bool ShipCollectionViewFilter(object item)
{
BaseShip ship = item as BaseShip;
//If the search string is less than three letters long, or if it is empty, then do not filter
if (ComboSearchText == null || ComboSearchText?.Length < 3) return true;
return ship.Name.Contains(ComboSearchText);
}
#endregion Methods
}
BaseShip Object (KancolleObjects.cs)
public class BaseShip
{
#region Fields
//...
#endregion Fields
#region Constructors
public BaseShip()
{
//...
}
//...
public BaseShip(string[] entries):this()
{
//The name gets stored here, and is never changed
Name = entries[1];
//...
//Same with the type, here.
ShipType_Short shipType;
if(!Enum.TryParse(entries[4].Split('/')[1], out ShipType))
{
Debug.WriteLine($"Did not manage to get ship type for ship {Name}.");
//This message was never output, so no problems here (every BaseShip in the ListCollectionView has a valid type).
}
}
#endregion Constructors
#region Properties
//...
public string Name { get; }
//...
public ShipType_Short Type { get; }
#endregion Properties
#region Methods
//...
public override string ToString()
{
return Name;
}
// Because ItemContainerStyle is set, DisplayMemberPath
// cannot be set in the xaml. This means we have to rely
// on the default binding for text, which is ToString()
#endregion Methods
#region Constants
public static BaseShip Empty => new BaseShip();
#endregion Constants
}
public enum ShipType_Short
{
//...
}
Я ожидаю, что отфильтрованные элементы будут по-прежнему сортироваться по типу при поиске нужного элемента, и я делаю, но не раньше, чем выдаю исключение ArgumentOutOfRange
, которое яприходится обращаться.