当分组 属性 是字符串列表时,按组名对 ListView 中的分组进行排序
Sorting grouping in a ListView by group name when the grouping property is a list of string
我正在开发其他人用 C# (MVVM) WPF 开发的应用程序。
在此应用程序中,一些分组是在 ListView 中的字符串列表 属性 上进行的,在此 属性 上的分组似乎有效,但问题是 在 ListView.
中,组未按组名称 的字母顺序排序
例如:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePackages);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("SupportedOperatingSystems");
view.GroupDescriptions.Add(groupDescription);
view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending));
属性“SupportedOperatingSystems”是一个字符串列表。
行:view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending)); 抛出异常:
System.InvalidOperationException: 'Failed to compare two elements in the array.'
感谢您的帮助:)
将您的代码更改为:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePackages.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("SupportedOperatingSystems");
view.GroupDescriptions.Add(groupDescription);
view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending));
SortDescription 仅指定有关 属性 排序依据和排序顺序的信息。
使用此信息,CollectionView 按此 属性 对项目进行排序。
但是对于排序,用于排序的类型 属性 必须实现 IComparable 接口。
如果此接口不存在,则会引发您指定的错误。
据我所知,没有 .Net 集合具有此接口的实现。
为了解决这个问题,你需要将原始集合的元素反映到视图的附加类型中。
在此类型中,添加一个 属性,原始元素的集合将反映在转换为具有 IComparable 实现的类型中。
不知道你的实现细节,我不能给你具体的实现代码。
因此,我展示了一个非常简单的例子:
using System;
using System.Collections.Generic;
namespace SortDescriptionList
{
public class ListItem
{
public IReadOnlyList<string> Strings { get; }
public string ViewStrings => string.Join(", ", Strings);
public ListItem(params string[] strings)
=> Strings = Array.AsReadOnly(strings);
}
}
using System;
using System.Collections.Generic;
namespace SortDescriptionList
{
public class StringCollection : List<string>, IComparable, IComparable<IReadOnlyList<string>>
{
public int CompareTo(IReadOnlyList<string> other)
{
if (other == null)
return 1;
var minCount = Count;
if (minCount > other.Count)
minCount = other.Count;
if (minCount > 0)
{
for (int i = 0; i < minCount; i++)
{
var comp = string.Compare(this[i], other[i]);
if (comp != 0)
return comp;
}
}
return Count.CompareTo(other.Count);
}
public int CompareTo(object obj)
=> CompareTo(obj as IReadOnlyList<string>);
}
}
namespace SortDescriptionList
{
public class ListItemView
{
public StringCollection StringsView { get; }
public string ViewStrings => Source.ViewStrings;
public ListItem Source { get; }
public ListItemView(ListItem source)
{
Source = source;
StringsView = new StringCollection();
StringsView.AddRange(source.Strings);
}
}
}
using System.Collections.Generic;
using System.Linq;
namespace SortDescriptionList
{
public class ViewModel
{
public List<ListItem> ListItems { get; }
= new List<ListItem>()
{
new ListItem("First"),
new ListItem("Second"),
new ListItem("Third"),
new ListItem("Fourth", "Fifth")
};
public List<ListItemView> ListItemsView { get; }
public ViewModel()
{
ListItemsView = new List<ListItemView>(ListItems.Select(item => new ListItemView(item)));
}
}
}
<Window x:Class="SortDescriptionList.SortListWindow"
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:SortDescriptionList"
mc:Ignorable="d"
Title="SortListWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:ViewModel/>
</FrameworkElement.DataContext>
<UniformGrid Columns="2">
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding ListItems}"/>
<DataGrid x:Name="dataGridView"
ItemsSource="{Binding ListItemsView}"/>
</UniformGrid>
</Window>
左列显示具有原始元素的 DataGrid。
并且排序的点击仅在“ViewStrings”列可用,因为不可能为具有常规集合类型的“Strings”属性创建 SortDescription。
在反射集合的右列中,您可以启用按“StringsView”列排序。
由于此 属性 的类型不再是常规列表,而是实现 IComparable 接口的自定义 StringCollection,因此,您可以为其创建 SortDescription。
补充回答
Here is the project as zip file
我无法准确判断这在实践中需要多少,但你需要的实现相当复杂且不灵活。
您可以按照在其元素中的 SupportedPlatforms 集合中指定的组对 OcAvailablePackages 集合的元素进行排序。
但是由于您需要排序的不是SupportedPlatforms集合中元素的顺序,而是SupportedPlatforms元素的排序顺序,所以您也需要先对它们进行排序。
为了实现这样的排序,我不得不想出一个相当复杂的类型:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Test_Grouping
{
public class CompareReadOnlyList<T> : ICollection, IReadOnlyList<T>, IComparable, IComparable<IReadOnlyList<T>>
where T : IComparable, IComparable<T>
{
private readonly T[] array;
public int Count => array.Length;
public bool IsReadOnly => true;
public object SyncRoot => null;
public bool IsSynchronized => false;
public T this[int index] => array[index];
public CompareReadOnlyList(IEnumerable<T> ts)
{
array = ts.ToArray();
Array.Sort(array);
}
public CompareReadOnlyList(params T[] ts)
{
array = (T[])ts.Clone();
Array.Sort(array);
}
public int CompareTo(IReadOnlyList<T> other)
{
if (other == null)
return 1;
int minCount = Count;
if (minCount > other.Count)
{
minCount = other.Count;
}
if (minCount > 0)
{
for (int i = 0; i < minCount; i++)
{
int comp = Compare(this[i], other[i]);
if (comp != 0)
{
return comp;
}
}
}
return Count.CompareTo(other.Count);
}
public int CompareTo(object obj)
{
return CompareTo(obj as IReadOnlyList<T>);
}
public int Compare(T x, T y)
{
return x?.CompareTo(y) ?? -(y?.CompareTo(x) ?? 0);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)array).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return array.GetEnumerator();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
}
}
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
namespace Test_Grouping
{
public class Package : ViewModelBase
{
#region Properties
private string _name;
public string Name
{
get => _name;
set => Set(ref _name, value);
}
private CompareReadOnlyList<string> _supportedPlatforms ;
public CompareReadOnlyList<string> SupportedPlatforms
{
get => _supportedPlatforms;
set => Set(ref _supportedPlatforms, value);
}
#endregion
#region Constructors
public Package() { }
public Package(string name) => Name = name;
public Package(string name, IEnumerable<string> vs)
: this(name)
=> SupportedPlatforms = new CompareReadOnlyList<string>(vs);
public Package(string name, params string[] vs)
: this(name, (IEnumerable<string>)vs)
{ }
#endregion
#region Methods
#endregion
}
}
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Test_Grouping
{
public class MainWindowVM : ViewModelBase
{
#region Properties
private ObservableCollection<Package> _ocAvailablePackages = new ObservableCollection<Package>();
public ObservableCollection<Package> OcAvailablePackages
{
get => _ocAvailablePackages;
set => Set(ref _ocAvailablePackages, value);
}
private Package _objSelectedPackage;
public Package ObjSelectedPackage
{
get => _objSelectedPackage;
set => Set(ref _objSelectedPackage, value);
}
#endregion
#region Constructors
public MainWindowVM()
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
return;
}
//MessageBox.Show("test");
OcAvailablePackages.Add(new Package("Package 1", "Platform 2"));
OcAvailablePackages.Add(new Package("Package 2", "Platform 2", "Platform 4"));
OcAvailablePackages.Add(new Package("Package 3", "Platform 2", "Platform 1", "Platform 3"));
OcAvailablePackages.Add(new Package("Package 4", "Platform 3", "Platform 1", "Platform 4"));
OcAvailablePackages.Add(new Package("Package 5", "Platform 2", "Platform 3"));
OcAvailablePackages.Add(new Package("Package 6", "Platform 4", "Platform 1", "Platform 3"));
//Group
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(OcAvailablePackages);
view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(Package.SupportedPlatforms)));
//The next code row raises an exception (due to List<string>) :
view.SortDescriptions.Add(new SortDescription(nameof(Package.SupportedPlatforms), ListSortDirection.Ascending));
}
#endregion
#region Methods
#endregion
}
}
但我真的不喜欢这个实现。
首先,我不确定它是否会始终正常工作。
我无法想出一个值的组合来支持这个假设,但我认为是。
其次,这不会对组内的项目进行排序。
由于首先对一般集合进行排序,然后根据元素集合中明确指定的组对其进行分组,这些组是虚拟的。
虽然 ListView 显示了两打行,但实际上有六个项目,所以仍然是相同的数字。
我将通过一个额外的类型来实现,该类型表示一个包,每个组都明确分解为单独的元素:
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
using System.Linq;
namespace Test_Grouping
{
public class PackageRow : ViewModelBase
{
public Package Package { get; }
public string Platform => Package.SupportedPlatforms[index];
private readonly int index;
public string Name => Package.Name;
private PackageRow(Package package, string platform)
{
Package = package;
index = package.SupportedPlatforms.TakeWhile(p => p != platform).Count();
}
public static IEnumerable<PackageRow> CreatePackageRows(Package package)
=> package.SupportedPlatforms.Select(platform => new PackageRow(package, platform));
public static void AddInList(IList<PackageRow> packages, Package package)
{
foreach (var pv in CreatePackageRows(package))
{
packages.Add(pv);
}
}
public static void RemoveInList(IList<PackageRow> packages, Package package)
{
for (int i = packages.Count - 1; i >= 0; i--)
{
if (packages[i].Package == package)
{
packages.RemoveAt(i);
}
}
}
}
}
foreach (var pck in OcAvailablePackages)
{
PackageRow.AddInList(Packages, pck);
}
//Group
ListCollectionView viewGr = (ListCollectionView)CollectionViewSource.GetDefaultView(Packages);
viewGr.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PackageRow.Platform)));
//The next code row raises an exception (due to List<string>) :
viewGr.SortDescriptions.Add(new SortDescription(nameof(PackageRow.Platform), ListSortDirection.Ascending));
viewGr.SortDescriptions.Add(new SortDescription(nameof(PackageRow.Name), ListSortDirection.Ascending));
}
public ObservableCollection<PackageRow> Packages { get; }
= new ObservableCollection<PackageRow>();
归档更改:Test_Grouping(Le Zvince).7z
应用程序屏幕截图。
左列是使用 属性 类型中的自定义比较器实现的输出。
右 - 将原始元素分成几个,每个组(平台)一个。这种情况下不需要修改原来的Package类型。
我正在开发其他人用 C# (MVVM) WPF 开发的应用程序。
在此应用程序中,一些分组是在 ListView 中的字符串列表 属性 上进行的,在此 属性 上的分组似乎有效,但问题是 在 ListView.
中,组未按组名称 的字母顺序排序例如:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePackages);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("SupportedOperatingSystems");
view.GroupDescriptions.Add(groupDescription);
view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending));
属性“SupportedOperatingSystems”是一个字符串列表。
行:view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending)); 抛出异常: System.InvalidOperationException: 'Failed to compare two elements in the array.'
感谢您的帮助:)
将您的代码更改为:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePackages.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("SupportedOperatingSystems");
view.GroupDescriptions.Add(groupDescription);
view.SortDescriptions.Add(new SortDescription("SupportedOperatingSystems", ListSortDirection.Ascending));
SortDescription 仅指定有关 属性 排序依据和排序顺序的信息。
使用此信息,CollectionView 按此 属性 对项目进行排序。
但是对于排序,用于排序的类型 属性 必须实现 IComparable 接口。
如果此接口不存在,则会引发您指定的错误。
据我所知,没有 .Net 集合具有此接口的实现。
为了解决这个问题,你需要将原始集合的元素反映到视图的附加类型中。
在此类型中,添加一个 属性,原始元素的集合将反映在转换为具有 IComparable 实现的类型中。
不知道你的实现细节,我不能给你具体的实现代码。
因此,我展示了一个非常简单的例子:
using System;
using System.Collections.Generic;
namespace SortDescriptionList
{
public class ListItem
{
public IReadOnlyList<string> Strings { get; }
public string ViewStrings => string.Join(", ", Strings);
public ListItem(params string[] strings)
=> Strings = Array.AsReadOnly(strings);
}
}
using System;
using System.Collections.Generic;
namespace SortDescriptionList
{
public class StringCollection : List<string>, IComparable, IComparable<IReadOnlyList<string>>
{
public int CompareTo(IReadOnlyList<string> other)
{
if (other == null)
return 1;
var minCount = Count;
if (minCount > other.Count)
minCount = other.Count;
if (minCount > 0)
{
for (int i = 0; i < minCount; i++)
{
var comp = string.Compare(this[i], other[i]);
if (comp != 0)
return comp;
}
}
return Count.CompareTo(other.Count);
}
public int CompareTo(object obj)
=> CompareTo(obj as IReadOnlyList<string>);
}
}
namespace SortDescriptionList
{
public class ListItemView
{
public StringCollection StringsView { get; }
public string ViewStrings => Source.ViewStrings;
public ListItem Source { get; }
public ListItemView(ListItem source)
{
Source = source;
StringsView = new StringCollection();
StringsView.AddRange(source.Strings);
}
}
}
using System.Collections.Generic;
using System.Linq;
namespace SortDescriptionList
{
public class ViewModel
{
public List<ListItem> ListItems { get; }
= new List<ListItem>()
{
new ListItem("First"),
new ListItem("Second"),
new ListItem("Third"),
new ListItem("Fourth", "Fifth")
};
public List<ListItemView> ListItemsView { get; }
public ViewModel()
{
ListItemsView = new List<ListItemView>(ListItems.Select(item => new ListItemView(item)));
}
}
}
<Window x:Class="SortDescriptionList.SortListWindow"
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:SortDescriptionList"
mc:Ignorable="d"
Title="SortListWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:ViewModel/>
</FrameworkElement.DataContext>
<UniformGrid Columns="2">
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding ListItems}"/>
<DataGrid x:Name="dataGridView"
ItemsSource="{Binding ListItemsView}"/>
</UniformGrid>
</Window>
左列显示具有原始元素的 DataGrid。
并且排序的点击仅在“ViewStrings”列可用,因为不可能为具有常规集合类型的“Strings”属性创建 SortDescription。
在反射集合的右列中,您可以启用按“StringsView”列排序。
由于此 属性 的类型不再是常规列表,而是实现 IComparable 接口的自定义 StringCollection,因此,您可以为其创建 SortDescription。
补充回答
Here is the project as zip file
我无法准确判断这在实践中需要多少,但你需要的实现相当复杂且不灵活。
您可以按照在其元素中的 SupportedPlatforms 集合中指定的组对 OcAvailablePackages 集合的元素进行排序。
但是由于您需要排序的不是SupportedPlatforms集合中元素的顺序,而是SupportedPlatforms元素的排序顺序,所以您也需要先对它们进行排序。
为了实现这样的排序,我不得不想出一个相当复杂的类型:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Test_Grouping
{
public class CompareReadOnlyList<T> : ICollection, IReadOnlyList<T>, IComparable, IComparable<IReadOnlyList<T>>
where T : IComparable, IComparable<T>
{
private readonly T[] array;
public int Count => array.Length;
public bool IsReadOnly => true;
public object SyncRoot => null;
public bool IsSynchronized => false;
public T this[int index] => array[index];
public CompareReadOnlyList(IEnumerable<T> ts)
{
array = ts.ToArray();
Array.Sort(array);
}
public CompareReadOnlyList(params T[] ts)
{
array = (T[])ts.Clone();
Array.Sort(array);
}
public int CompareTo(IReadOnlyList<T> other)
{
if (other == null)
return 1;
int minCount = Count;
if (minCount > other.Count)
{
minCount = other.Count;
}
if (minCount > 0)
{
for (int i = 0; i < minCount; i++)
{
int comp = Compare(this[i], other[i]);
if (comp != 0)
{
return comp;
}
}
}
return Count.CompareTo(other.Count);
}
public int CompareTo(object obj)
{
return CompareTo(obj as IReadOnlyList<T>);
}
public int Compare(T x, T y)
{
return x?.CompareTo(y) ?? -(y?.CompareTo(x) ?? 0);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)array).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return array.GetEnumerator();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
}
}
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
namespace Test_Grouping
{
public class Package : ViewModelBase
{
#region Properties
private string _name;
public string Name
{
get => _name;
set => Set(ref _name, value);
}
private CompareReadOnlyList<string> _supportedPlatforms ;
public CompareReadOnlyList<string> SupportedPlatforms
{
get => _supportedPlatforms;
set => Set(ref _supportedPlatforms, value);
}
#endregion
#region Constructors
public Package() { }
public Package(string name) => Name = name;
public Package(string name, IEnumerable<string> vs)
: this(name)
=> SupportedPlatforms = new CompareReadOnlyList<string>(vs);
public Package(string name, params string[] vs)
: this(name, (IEnumerable<string>)vs)
{ }
#endregion
#region Methods
#endregion
}
}
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Test_Grouping
{
public class MainWindowVM : ViewModelBase
{
#region Properties
private ObservableCollection<Package> _ocAvailablePackages = new ObservableCollection<Package>();
public ObservableCollection<Package> OcAvailablePackages
{
get => _ocAvailablePackages;
set => Set(ref _ocAvailablePackages, value);
}
private Package _objSelectedPackage;
public Package ObjSelectedPackage
{
get => _objSelectedPackage;
set => Set(ref _objSelectedPackage, value);
}
#endregion
#region Constructors
public MainWindowVM()
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
return;
}
//MessageBox.Show("test");
OcAvailablePackages.Add(new Package("Package 1", "Platform 2"));
OcAvailablePackages.Add(new Package("Package 2", "Platform 2", "Platform 4"));
OcAvailablePackages.Add(new Package("Package 3", "Platform 2", "Platform 1", "Platform 3"));
OcAvailablePackages.Add(new Package("Package 4", "Platform 3", "Platform 1", "Platform 4"));
OcAvailablePackages.Add(new Package("Package 5", "Platform 2", "Platform 3"));
OcAvailablePackages.Add(new Package("Package 6", "Platform 4", "Platform 1", "Platform 3"));
//Group
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(OcAvailablePackages);
view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(Package.SupportedPlatforms)));
//The next code row raises an exception (due to List<string>) :
view.SortDescriptions.Add(new SortDescription(nameof(Package.SupportedPlatforms), ListSortDirection.Ascending));
}
#endregion
#region Methods
#endregion
}
}
但我真的不喜欢这个实现。
首先,我不确定它是否会始终正常工作。
我无法想出一个值的组合来支持这个假设,但我认为是。
其次,这不会对组内的项目进行排序。
由于首先对一般集合进行排序,然后根据元素集合中明确指定的组对其进行分组,这些组是虚拟的。
虽然 ListView 显示了两打行,但实际上有六个项目,所以仍然是相同的数字。
我将通过一个额外的类型来实现,该类型表示一个包,每个组都明确分解为单独的元素:
using Infomil.ZSYS.WSUS.Mvvm;
using System.Collections.Generic;
using System.Linq;
namespace Test_Grouping
{
public class PackageRow : ViewModelBase
{
public Package Package { get; }
public string Platform => Package.SupportedPlatforms[index];
private readonly int index;
public string Name => Package.Name;
private PackageRow(Package package, string platform)
{
Package = package;
index = package.SupportedPlatforms.TakeWhile(p => p != platform).Count();
}
public static IEnumerable<PackageRow> CreatePackageRows(Package package)
=> package.SupportedPlatforms.Select(platform => new PackageRow(package, platform));
public static void AddInList(IList<PackageRow> packages, Package package)
{
foreach (var pv in CreatePackageRows(package))
{
packages.Add(pv);
}
}
public static void RemoveInList(IList<PackageRow> packages, Package package)
{
for (int i = packages.Count - 1; i >= 0; i--)
{
if (packages[i].Package == package)
{
packages.RemoveAt(i);
}
}
}
}
}
foreach (var pck in OcAvailablePackages)
{
PackageRow.AddInList(Packages, pck);
}
//Group
ListCollectionView viewGr = (ListCollectionView)CollectionViewSource.GetDefaultView(Packages);
viewGr.GroupDescriptions.Add(new PropertyGroupDescription(nameof(PackageRow.Platform)));
//The next code row raises an exception (due to List<string>) :
viewGr.SortDescriptions.Add(new SortDescription(nameof(PackageRow.Platform), ListSortDirection.Ascending));
viewGr.SortDescriptions.Add(new SortDescription(nameof(PackageRow.Name), ListSortDirection.Ascending));
}
public ObservableCollection<PackageRow> Packages { get; }
= new ObservableCollection<PackageRow>();
归档更改:Test_Grouping(Le Zvince).7z
应用程序屏幕截图。
左列是使用 属性 类型中的自定义比较器实现的输出。
右 - 将原始元素分成几个,每个组(平台)一个。这种情况下不需要修改原来的Package类型。