当分组 属性 是字符串列表时,按组名对 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类型。