WPF DataGrid CustomSort for each Column

后端 未结 10 1422
庸人自扰
庸人自扰 2020-12-01 01:56

I have a WPF DataGrid bound to a CollectionViewSource that encapsulates an ObservableCollection. This CollectionViewSource has two main objectives:

1) To group each

相关标签:
10条回答
  • 2020-12-01 02:08

    I changed @trilson86's answer so that you need only one custom sorter-class for the whole DataGrid.

    First the Interface:

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

    Next the Bevaviour-class which defines the CustomSorterProperty in such way that you can use it directly on the DataGrid, not on the DateGridRow. In HandleCustomSorting() the property SortMemberPath of the CustomSorter ist filled with the actual value from the clicked column, you can use this value in your CustomSorter to sort against the desired column.

    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
    }
    

    You can use it in XAML like this:

    <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>
    
    0 讨论(0)
  • 2020-12-01 02:12

    The answer given by trilson86 is excellent. However, the third parameter in the two DependencyProperty declarations is incorrect. Instead of DataGrid and DataGridColumn, they should be CustomSortBehaviour, as such:

    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
    

    I kept getting a warning that the AllowCustomSort property was already registered. A little research led me to the answer here.

    At any rate, it's an excellent answer, so thank you.

    0 讨论(0)
  • 2020-12-01 02:13

    I created a couple of attached properties which handle this issue. I hope this comes in handy for someone!

    First - a simple interface for your directionalised comparer. This extends IComparer but gives us one more property (SortDirection). Your implementation should use this to determine the correct ordering of elements (which would otherwise have been lost).

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

    Next is the attached behavior - this does two things: 1) tells the grid to use custom sort logic (AllowCustomSort=true) and b) gives us the ability to set this logic at a per-column level.

    public class CustomSortBehaviour
    {
        public static readonly DependencyProperty CustomSorterProperty =
            DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehaviour));
    
        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(CustomSortBehaviour), 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;
        }
    }
    

    To use it, implement an ICustomComparer (with a parameterless constructor) and in your XAML:

    <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>
    
    0 讨论(0)
  • 2020-12-01 02:14

    I've learned a lot from this question, and here I share a way of doing it.
    This method does not modify xaml while conforming to mvvm.
    I need to load the file path to DataGrid, which can be sorted like Windows Explorer.
    there have some files:

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

    Note that I have sorted by file name by Windows Explorer.
    I think you've found that the file sort used by Explorer is not a simple string sort.
    It uses the win32 api StrCmpLogicalW in shlwapi.dll
    We need to implement the IComparable(Non-generic) interface for the sorted properties.
    For less code, I used Prism.Mvvm.BindableBase, an INotifyPropertyChanged implementation.
    Code like this:

        /// <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 code:

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

    In summary, I encapsulated the original string properties,
    You can also encapsulate your custom properties for sorting,
    just override ToString for display and implemented CompareTo for sorting.

    0 讨论(0)
  • 2020-12-01 02:17

    here is some extention to @trilson86 ICustomeSorter

    that make the Sorter more generic to use

    NumericComparer based on this resource

    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; }
    }
    
    0 讨论(0)
  • 2020-12-01 02:21

    Here is one way:

    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);
                }
            }
        }
    }
    

    Usage:

    <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>
    
    0 讨论(0)
提交回复
热议问题