每列的WPF DataGrid CustomSort

时间:2013-08-08 09:42:32

标签: c# .net wpf datagrid collectionviewsource

我有一个绑定到CollectionViewSource的WPF DataGrid,它封装了一个ObservableCollection。此CollectionViewSource有两个主要目标:

1)按T的特定属性对每个项目进行分组。我在GroupDescription中使用ValueConverter来获取我想要的分组行为。

2)通过a)主要对组名称(如上定义)和b)各个组项目对网格进行排序。我是通过将自定义IComparer附加到CollectionViewSource的'CustomSort'属性来实现的。

这在大多数情况下都很有效,但是只要单击列标题,就会覆盖排序逻辑。我不想禁用排序,但是我想知道是否可以为特定列分配自定义排序顺序?

为了使事情更加清晰,假设用户点击“ColumnA” - 此时,我的CustomSorter封装的排序逻辑被覆盖,DataGrid现在按该属性排序。我宁愿颠倒CustomSorter的逻辑,而不是按所选属性进行排序。

10 个答案:

答案 0 :(得分:26)

我创建了一些处理此问题的附加属性。我希望这对某人来说很方便!

首先 - 定向比较器的简单接口。这扩展了IComparer,但又为我们提供了一个属性(SortDirection)。您的实现应该使用它来确定元素的正确排序(否则会丢失)。

public interface ICustomSorter : IComparer
{
    ListSortDirection SortDirection { get; set; }
}

接下来是附加行为 - 这做了两件事:1)告诉网格使用自定义排序逻辑(AllowCustomSort = true)和b)使我们能够在每列级别设置此逻辑。

public class CustomSortBehaviour
{
    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehavior));

    public static ICustomSorter GetCustomSorter(DataGridColumn gridColumn)
    {
        return (ICustomSorter)gridColumn.GetValue(CustomSorterProperty);
    }

    public static void SetCustomSorter(DataGridColumn gridColumn, ICustomSorter value)
    {
        gridColumn.SetValue(CustomSorterProperty, value);
    }

    public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool),
        typeof(CustomSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));

    public static bool GetAllowCustomSort(DataGrid grid)
    {
        return (bool)grid.GetValue(AllowCustomSortProperty);
    }

    public static void SetAllowCustomSort(DataGrid grid, bool value)
    {
        grid.SetValue(AllowCustomSortProperty, value);
    }

    private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var existing = d as DataGrid;
        if (existing == null) return;

        var oldAllow = (bool)e.OldValue;
        var newAllow = (bool)e.NewValue;

        if (!oldAllow && newAllow)
        {
            existing.Sorting += HandleCustomSorting;
        }
        else
        {
            existing.Sorting -= HandleCustomSorting;
        }
    }

    private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
    {
        var dataGrid = sender as DataGrid;
        if (dataGrid == null || !GetAllowCustomSort(dataGrid)) return;

        var listColView = dataGrid.ItemsSource as ListCollectionView;
        if (listColView == null)
            throw new Exception("The DataGrid's ItemsSource property must be of type, ListCollectionView");

        // Sanity check
        var sorter = GetCustomSorter(e.Column);
        if (sorter == null) return;

        // The guts.
        e.Handled = true;

        var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
                            ? ListSortDirection.Ascending
                            : ListSortDirection.Descending;

        e.Column.SortDirection = sorter.SortDirection = direction;
        listColView.CustomSort = sorter;
    }
}

要使用它,请在XAML中实现ICustomComparer(带无参数构造函数):

<UserControl.Resources>
    <converters:MyComparer x:Key="MyComparer"/>
    <!-- add more if you need them -->
</UserControl.Resources>
<DataGrid behaviours:CustomSortBehaviour.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:CustomSortBehaviour.CustomSorter="{StaticResource MyComparer}" />
    </DataGrid.Columns>
</DataGrid>

答案 1 :(得分:4)

trilson86给出的答案非常好。但是,两个DependencyProperty声明中的第三个参数不正确。而不是DataGrid和DataGridColumn,它们应该是CustomSortBehaviour,因此:

public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort", 
        typeof(bool),
        typeof(CustomSortBehaviour), // <- Here
        new UIPropertyMetadata(false, OnAllowCustomSortChanged));

    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", 
        typeof(ICustomSorter), 
        typeof(CustomSortBehaviour));  // <- Here

我一直收到一条警告,说已经注册了AllowCustomSort属性。一个小小的研究让我得到答案here

无论如何,这是一个很好的答案,谢谢。

答案 2 :(得分:4)

这是一种方式:

using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

public static class DataGridSort
{
    public static readonly DependencyProperty ComparerProperty = DependencyProperty.RegisterAttached(
        "Comparer",
        typeof(IComparer),
        typeof(DataGridSort),
        new PropertyMetadata(
            default(IComparer),
            OnComparerChanged));

    private static readonly DependencyProperty ColumnComparerProperty = DependencyProperty.RegisterAttached(
        "ColumnComparer",
        typeof(ColumnComparer),
        typeof(DataGridSort),
        new PropertyMetadata(default(ColumnComparer)));

    private static readonly DependencyProperty PreviousComparerProperty = DependencyProperty.RegisterAttached(
        "PreviousComparer",
        typeof(IComparer),
        typeof(DataGridSort),
        new PropertyMetadata(default(IComparer)));

    public static readonly DependencyProperty UseCustomSortProperty = DependencyProperty.RegisterAttached(
        "UseCustomSort",
        typeof(bool),
        typeof(DataGridSort),
        new PropertyMetadata(default(bool), OnUseCustomSortChanged));

    public static void SetComparer(DataGridColumn element, IComparer value)
    {
        element.SetValue(ComparerProperty, value);
    }

    public static IComparer GetComparer(DataGridColumn element)
    {
        return (IComparer)element.GetValue(ComparerProperty);
    }

    public static void SetUseCustomSort(DependencyObject element, bool value)
    {
        element.SetValue(UseCustomSortProperty, value);
    }

    public static bool GetUseCustomSort(DependencyObject element)
    {
        return (bool)element.GetValue(UseCustomSortProperty);
    }

    private static void OnComparerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var column = (DataGridColumn)d;
        var columnComparer = new ColumnComparer((IComparer)e.NewValue, column);
        column.SetValue(ColumnComparerProperty, columnComparer);
    }

    private static void OnUseCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = (DataGrid)d;
        if ((bool)e.NewValue)
        {
            WeakEventManager<DataGrid, DataGridSortingEventArgs>.AddHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
        }
        else
        {
            WeakEventManager<DataGrid, DataGridSortingEventArgs>.RemoveHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
        }
    }

    private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
    {
        var column = e.Column;
        var columnComparer = (ColumnComparer)column.GetValue(ColumnComparerProperty);
        var dataGrid = (DataGrid)sender;
        var view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;
        if (view == null)
        {
            return;
        }
        if (columnComparer == null)
        {
            view.CustomSort = (IComparer)dataGrid.GetValue(PreviousComparerProperty);
        }
        else
        {
            if (!(view.CustomSort is ColumnComparer))
            {
                dataGrid.SetValue(PreviousComparerProperty, view.CustomSort);
            }

            switch (column.SortDirection)
            {
                case ListSortDirection.Ascending:
                    column.SortDirection = ListSortDirection.Descending;
                    view.CustomSort = columnComparer.Descending;
                    break;
                case null:
                case ListSortDirection.Descending:
                    column.SortDirection = ListSortDirection.Ascending;
                    view.CustomSort = columnComparer.Ascending;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            e.Handled = true;
        }
    }

    private class ColumnComparer : IComparer
    {
        private readonly IComparer valueComparer;
        private readonly DataGridColumn column;
        private readonly InvertedComparer inverted;

        public ColumnComparer(IComparer valueComparer, DataGridColumn column)
        {
            this.valueComparer = valueComparer;
            this.column = column;
            inverted = new InvertedComparer(this);
        }

        public IComparer Ascending => this;

        public IComparer Descending => inverted;

        int IComparer.Compare(object x, object y)
        {
            if (x == y)
            {
                return 0;
            }

            if (x == null)
            {
                return -1;
            }

            if (y == null)
            {
                return 1;
            }

            // this can perhaps be a bit slow
            // Not adding caching yet.
            var xProp = x.GetType().GetProperty(column.SortMemberPath);
            var xValue = xProp.GetValue(x);
            var yProp = x.GetType().GetProperty(column.SortMemberPath);
            var yValue = yProp.GetValue(y);
            return valueComparer.Compare(xValue, yValue);
        }

        private class InvertedComparer : IComparer
        {
            private readonly IComparer comparer;

            public InvertedComparer(IComparer comparer)
            {
                this.comparer = comparer;
            }

            public int Compare(object x, object y)
            {
                return comparer.Compare(y, x);
            }
        }
    }
}

用法:

<DataGrid AutoGenerateColumns="False"
            ItemsSource="{Binding DataItems}"
            local:DataGridSort.UseCustomSort="True">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Key}"
                            Header="Key"
                            local:DataGridSort.Comparer="{x:Static local:StringLengthComparer.Default}" />
        <DataGridTextColumn Binding="{Binding Value}" Header="Value" />
    </DataGrid.Columns>
</DataGrid>

答案 3 :(得分:2)

我是通过覆盖OnSorting事件并自己实现它来完成的。

http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.onsorting.aspx

这基本上意味着重新排序ListCollectionView。

对不起它的答案不太深入。

答案 4 :(得分:1)

我更改了@ trilson86的回答,因此您只需要为整个DataGrid提供一个自定义排序器类。

首先是界面:

public interface ICustomSorter : IComparer
{
    ListSortDirection SortDirection { get; set; }

    string SortMemberPath { get; set; }
}

接下来,Bevaviour类以这样的方式定义CustomSorterProperty,您可以直接在DataGrid上使用它,而不是在DateGridRow上。在HandleCustomSorting()中,CustomSorter的属性SortMemberPath填充了单击列中的实际值,您可以在CustomSorter中使用此值对所需列进行排序。

public class CustomSortBehaviour
{
    #region Fields and Constants

    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", typeof (ICustomSorter), typeof (CustomSortBehaviour));

    public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort",
            typeof (bool),
            typeof (CustomSortBehaviour),
            new UIPropertyMetadata(false, OnAllowCustomSortChanged));



    #endregion

    #region public Methods

    public static bool GetAllowCustomSort(DataGrid grid)
    {
        return (bool) grid.GetValue(AllowCustomSortProperty);
    }


    public static ICustomSorter GetCustomSorter(DataGrid grid)
    {
        return (ICustomSorter)grid.GetValue(CustomSorterProperty);
    }

    public static void SetAllowCustomSort(DataGrid grid, bool value)
    {
        grid.SetValue(AllowCustomSortProperty, value);
    }


    public static void SetCustomSorter(DataGrid grid, ICustomSorter value)
    {
        grid.SetValue(CustomSorterProperty, value);
    }

    #endregion

    #region nonpublic Methods

    private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
    {
        var dataGrid = sender as DataGrid;
        if (dataGrid == null || !GetAllowCustomSort(dataGrid))
        {
            return;
        }

        var listColView = dataGrid.ItemsSource as ListCollectionView;
        if (listColView == null)
        {
            throw new Exception("The DataGrid's ItemsSource property must be of type, ListCollectionView");
        }

        // Sanity check
        var sorter = GetCustomSorter(dataGrid);
        if (sorter == null)
        {
            return;
        }

        // The guts.
        e.Handled = true;

        var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
            ? ListSortDirection.Ascending
            : ListSortDirection.Descending;

        e.Column.SortDirection = sorter.SortDirection = direction;
        sorter.SortMemberPath = e.Column.SortMemberPath;

        listColView.CustomSort = sorter;
    }

    private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var existing = d as DataGrid;
        if (existing == null)
        {
            return;
        }

        var oldAllow = (bool) e.OldValue;
        var newAllow = (bool) e.NewValue;

        if (!oldAllow && newAllow)
        {
            existing.Sorting += HandleCustomSorting;
        }
        else
        {
            existing.Sorting -= HandleCustomSorting;
        }
    }

    #endregion
}

您可以在XAML中使用它,如下所示:

<Window x:Class="..."
        xmlns:sorter="clr-namespace:...Sorting"
        ...
        >

    <Window.Resources>
        <sorter:CustomSorter x:Key="MySorter"/>
    </Window.Resources>

    <Grid>

        <DataGrid ItemsSource="{Binding ...}"
                  sorter:CustomSortBehaviour.AllowCustomSort="True"
                  sorter:CustomSortBehaviour.CustomSorter="{StaticResource MySorter}" >


            <DataGrid.Columns>
                <DataGridTextColumn Header="Column 1" Binding="{Binding Column1}"/>
                <DataGridTextColumn Header="Column 2" Binding="{Binding Column2}"/>
                <DataGridTextColumn Header="Column 3" Binding="{Binding Column3}"/>
            </DataGrid.Columns>

        </DataGrid>

    </Grid>
</Window>

答案 5 :(得分:1)

这个答案与trilson86的解决方案非常相似 - 它基于它 - 但它以一种方式解释SortMemberPath,使得传递给比较器的值是实际的值。列,而不是行。这有助于在分拣机上进行更多的重复使用。此外,它完全不需要自定义排序界面。

DataGridSortBehavior.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace YourNamespace
{
    public class DataGridSortBehavior 
    {
        public static IComparer GetSorter(DataGridColumn column)
        {
            return (IComparer)column.GetValue(SorterProperty);
        }

        public static void SetSorter(DataGridColumn column, IComparer value)
        {
            column.SetValue(SorterProperty, value);
        }

        public static bool GetAllowCustomSort(DataGrid grid)
        {
            return (bool)grid.GetValue(AllowCustomSortProperty);
        }

        public static void SetAllowCustomSort(DataGrid grid, bool value)
        {
            grid.SetValue(AllowCustomSortProperty, value);
        }

        public static readonly DependencyProperty SorterProperty = DependencyProperty.RegisterAttached("Sorter", typeof(IComparer), 
            typeof(DataGridSortBehavior));
        public static readonly DependencyProperty AllowCustomSortProperty = DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool), 
            typeof(DataGridSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));

        private static void OnAllowCustomSortChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var grid = (DataGrid)obj;

            bool oldAllow = (bool)e.OldValue;
            bool newAllow = (bool)e.NewValue;

            if (!oldAllow && newAllow)
            {
                grid.Sorting += HandleCustomSorting;
            }
            else
            {
                grid.Sorting -= HandleCustomSorting;
            }
        }

        public static bool ApplySort(DataGrid grid, DataGridColumn column)
        {
            IComparer sorter = GetSorter(column);
            if (sorter == null)
            {
                return false;
            }

            var listCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource) as ListCollectionView;
            if (listCollectionView == null)
            {
                throw new Exception("The ICollectionView associated with the DataGrid must be of type, ListCollectionView");
            }

            listCollectionView.CustomSort = new DataGridSortComparer(sorter, column.SortDirection ?? ListSortDirection.Ascending, column.SortMemberPath);
            return true;
        }

        private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
        {
            IComparer sorter = GetSorter(e.Column);
            if (sorter == null)
            {
                return;
            }

            var grid = (DataGrid)sender;
            e.Column.SortDirection = e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
            if (ApplySort(grid, e.Column))
            {
                e.Handled = true;
            }
        }

        private class DataGridSortComparer : IComparer
        {
            private IComparer comparer;
            private ListSortDirection sortDirection;
            private string propertyName;
            private PropertyInfo property;

            public DataGridSortComparer(IComparer comparer, ListSortDirection sortDirection, string propertyName)
            {
                this.comparer = comparer;
                this.sortDirection = sortDirection;
                this.propertyName = propertyName;
            }

            public int Compare(object x, object y)
            {
                PropertyInfo property = this.property ?? (this.property = x.GetType().GetProperty(propertyName));
                object value1 = property.GetValue(x);
                object value2 = property.GetValue(y);

                int result = comparer.Compare(value1, value2);
                if (sortDirection == ListSortDirection.Descending)
                {
                    result = -result;
                }
                return result;
            }
        }
    }
}

您的Xaml

这应该与trilson86的解决方案类似:

<UserControl.Resources>
    <converters:MyComparer x:Key="MyComparer"/>
</UserControl.Resources>
<DataGrid behaviours:DataGridSortBehavior.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:DataGridSortBehavior.Sorter="{StaticResource MyComparer}" />
    </DataGrid.Columns>
</DataGrid>

答案 6 :(得分:1)

我从这个问题中学到了很多,在这里我分享了一种解决方法。
符合mvvm时,此方法不会修改xaml。
我需要将文件路径加载到DataGrid,可以将其排序为Windows资源管理器。
有一些文件:

E:\ Test \ Item_1.txt
E:\ Test \ Item_04.txt
E:\ Test \ Item_5.txt
E:\ Test \ Item_10.txt

注意,我已通过Windows资源管理器按文件名排序。
我认为您已经发现Explorer所使用的文件排序不是简单的字符串排序。
它使用 shlwapi.dll
中的win32 api StrCmpLogicalW 我们需要为已排序的属性实现 IComparable (非通用)接口。
为了减少代码量,我使用了Prism.Mvvm.BindableBase(一个INotifyPropertyChanged实现)。
像这样的代码:

    /// <summary>
    /// Data Model
    /// </summary>
    public class ListItemModel : BindableBase
    {

        private FilePath filePath;
        public FilePath FilePath
        {
            get { return filePath; }
            set { SetProperty(ref filePath, value); }
        }
        
        /// Other properties.
        /// ....
    }

    /// <summary>
    /// wrapper of filepath
    /// </summary>
    public class FilePath : IComparable
    {
        private string filePath;

        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
        private static extern int StrCmpLogicalW(string psz1, string psz2);

        public FilePath(string filePath)
        {
            this.filePath = filePath;
        }

        /// <summary>
        /// Implicit type conversion.
        /// </summary>
        /// <param name="x"></param>
        public static implicit operator string(FilePath x)
        {
            return x.filePath;
        }

        public static implicit operator FilePath(string x)
        {
            return new FilePath(x);
        }

        /// <summary>
        /// override for datagrid display.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return filePath;
        }

        /// <summary>
        /// Implement the interface IComparable for Custom sorting.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj is FilePath other)
                return StrCmpLogicalW(filePath, other.filePath);
            return 1;
        }
    }

XAML代码:

        <!-- Items is ObservableCollection<ListItemModel> -->        
        <DataGrid  ItemsSource="{Binding Items}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="File" Binding="{Binding FilePath}" />
            </DataGrid.Columns>
        </DataGrid>

总而言之,我封装了原始的字符串属性,
您还可以封装自定义属性以进行排序,
只需覆盖 ToString 进行显示,并实现 CompareTo 进行排序。

答案 7 :(得分:0)

这里有一些@ trilson86 ICustomeSorter

的延伸

使分拣机更具通用性

基于此资源的NumericComparer

http://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C

public class GenericNumericComparer : ICustomSorter
{
    private PropertyInfo _propertyInfo;
    private Type _objectType;

    public string SortMemberPath { get; set; }

    private readonly NumericComparer _comparer = new NumericComparer();

    public Type ObjectType
    {
        get { return _objectType; }
        set
        {
            _objectType = value;

            if (_objectType != null) _propertyInfo = ObjectType.GetProperty(SortMemberPath);
        }
    }

    private int CompareHelper(object x, object y)
    {
        if (_propertyInfo != null)
        {
            var value1 = _propertyInfo.GetValue(x);
            var value2 = _propertyInfo.GetValue(y);

            return _comparer.Compare(value1, value2);
        }

        return 0;
    }

    public int Compare(object x, object y)
    {
        var i = CompareHelper(x, y);

        if (SortDirection == ListSortDirection.Ascending)
            return i;

        return i*-1;
    }

    public ListSortDirection SortDirection { get; set; }
}

答案 8 :(得分:0)

DataGrid CustomSorting

<DataGrid attached:DataGridHelpers.UseCustomSort="True" ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn attached:DataGridHelpers.CustomSorterType="{x:Type comparers:StrLogicalComparer}" Binding="{Binding CodeText}" Header="Code"  />
        <DataGridTextColumn Header="Number" Binding="{Binding Number}" />
    </DataGrid.Columns>
</DataGrid>

支持嵌套属性

答案 9 :(得分:-2)

如果您以编程方式添加列,则可以使用它。

dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val1", System.ComponentModel.ListSortDirection.Descending));

&#34; VAL1&#34;这是我们添加的列的绑定路径,您还可以使用另一行作为第二种排序。喜欢这个。

        dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val2", System.ComponentModel.ListSortDirection.Ascending));