【发布时间】:2019-05-20 09:01:43
【问题描述】:
我有一个ComboBox,其ItemsSource 是一个实现过滤和分组的ListCollectionView。如果我在 xaml 中为 ComboBox 指定 GroupStyle,任何时候我第一次从 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 中的项目重新排序,其值取决于该选择)。
如果我实现的搜索功能用于查找选择,那么没有进一步的问题。
我正在尝试对 ComboBoxItems 进行分组并使用自定义函数对其进行过滤。为了为ListCollectionView 对象提供一些上下文,并使代码更易于理解,ComboBox 公开了一系列舰船,这些舰船按其类型(战舰/驱逐舰等)分组。输入ComboBox 会触发一个搜索功能,该功能会根据船只名称是否包含搜索文本来查找船只。
到目前为止,我发现关于ArgumentOutOfRange/IndexOutOfRange 异常的讨论通常与ICollectionViews 相关,但并非所有都与C# 相关,更不用说wpf。 This post from social.microsoft.com 似乎是最相似的,尽管与排序有关。但它归结为同样的错误(ListCollectionView 尝试PrepareShaping(),但失败了)。在那种情况下,我认为问题在于尝试对来自另一个线程的项目进行排序,同时也更改源集合。我在这里所做的只是应用过滤器并添加组描述,所以我相信当调用Refresh() 来刷新ListCollectionView 时,Windows 应该能够同时处理这两者。
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 对象 (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
{
//...
}
当我搜索我想要的项目时,我希望过滤后的项目仍然根据类型进行排序,我这样做了,但在给我一个我必须处理的ArgumentOutOfRangeexception 之前不是这样。
【问题讨论】:
标签: c# wpf wpf-controls