Преглед на файлове

Merge pull request #4687 from AvaloniaUI/feature/data-grid-custom-sorting

Feature/data grid custom sorting
Max Katz преди 5 години
родител
ревизия
ca498f8f9b

+ 32 - 3
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@@ -1,8 +1,12 @@
+using System.Collections;
 using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 using ControlCatalog.Models;
 using Avalonia.Collections;
+using Avalonia.Data;
 
 namespace ControlCatalog.Pages
 {
@@ -11,12 +15,22 @@ namespace ControlCatalog.Pages
         public DataGridPage()
         {
             this.InitializeComponent();
+
+            var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer());
+            var collectionView1 = new DataGridCollectionView(Countries.All);
+            collectionView1.SortDescriptions.Add(dataGridSortDescription);
             var dg1 = this.FindControl<DataGrid>("dataGrid1");
             dg1.IsReadOnly = true;
             dg1.LoadingRow += Dg1_LoadingRow;
-            var collectionView1 = new DataGridCollectionView(Countries.All);
-            //collectionView.GroupDescriptions.Add(new PathGroupDescription("Region"));
-
+            dg1.Sorting += (s, a) =>
+            {
+                var property = ((a.Column as DataGridBoundColumn)?.Binding as Binding).Path;
+                if (property == dataGridSortDescription.PropertyPath
+                    && !collectionView1.SortDescriptions.Contains(dataGridSortDescription))
+                {
+                    collectionView1.SortDescriptions.Add(dataGridSortDescription);
+                }
+            };
             dg1.Items = collectionView1;
 
             var dg2 = this.FindControl<DataGrid>("dataGridGrouping");
@@ -53,5 +67,20 @@ namespace ControlCatalog.Pages
         {
             AvaloniaXamlLoader.Load(this);
         }
+
+        private class ReversedStringComparer : IComparer<object>, IComparer
+        {
+            public int Compare(object x, object y)
+            {
+                if (x is string left && y is string right)
+                {
+                    var reversedLeft = new string(left.Reverse().ToArray());
+                    var reversedRight = new string(right.Reverse().ToArray());
+                    return reversedLeft.CompareTo(reversedRight);
+                }
+
+                return Comparer.Default.Compare(x, y);
+            }
+        }
     }
 }

+ 35 - 19
src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs

@@ -1,19 +1,21 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Globalization;
 using System.Linq;
-using System.Text;
-using Avalonia.Controls;
 using Avalonia.Controls.Utils;
-using Avalonia.Utilities;
 
 namespace Avalonia.Collections
 {
     public abstract class DataGridSortDescription
     {
         public virtual string PropertyPath => null;
-        public virtual bool Descending => false;
+
+        [Obsolete("Use Direction property to read or override sorting direction.")]
+        public virtual bool Descending => Direction == ListSortDirection.Descending;
+
+        public virtual ListSortDirection Direction => ListSortDirection.Ascending;
         public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath);
         public abstract IComparer<object> Comparer { get; }
 
@@ -26,7 +28,7 @@ namespace Avalonia.Collections
             return seq.ThenBy(o => o, Comparer);
         }
 
-        internal virtual DataGridSortDescription SwitchSortDirection()
+        public virtual DataGridSortDescription SwitchSortDirection()
         {
             return this;
         }
@@ -105,7 +107,7 @@ namespace Avalonia.Collections
 
         private class DataGridPathSortDescription : DataGridSortDescription
         {
-            private readonly bool _descending;
+            private readonly ListSortDirection _direction;
             private readonly string _propertyPath;
             private readonly Lazy<CultureSensitiveComparer> _cultureSensitiveComparer;
             private readonly Lazy<IComparer<object>> _comparer;
@@ -118,7 +120,7 @@ namespace Avalonia.Collections
                 {
                     if (_internalComparerTyped == null && _internalComparer != null)
                     {
-                        if (_internalComparerTyped is IComparer<object> c)
+                        if (_internalComparer is IComparer<object> c)
                             _internalComparerTyped = c;
                         else
                             _internalComparerTyped = Comparer<object>.Create((x, y) => _internalComparer.Compare(x, y));
@@ -130,19 +132,20 @@ namespace Avalonia.Collections
 
             public override string PropertyPath => _propertyPath;
             public override IComparer<object> Comparer => _comparer.Value;
-            public override bool Descending => _descending;
+            public override ListSortDirection Direction => _direction;
 
-            public DataGridPathSortDescription(string propertyPath, bool descending, CultureInfo culture)
+            public DataGridPathSortDescription(string propertyPath, ListSortDirection direction, IComparer internalComparer, CultureInfo culture)
             {
                 _propertyPath = propertyPath;
-                _descending = descending;
+                _direction = direction;
                 _cultureSensitiveComparer = new Lazy<CultureSensitiveComparer>(() => new CultureSensitiveComparer(culture ?? CultureInfo.CurrentCulture));
+                _internalComparer = internalComparer;
                 _comparer = new Lazy<IComparer<object>>(() => Comparer<object>.Create((x, y) => Compare(x, y)));
             }
-            private DataGridPathSortDescription(DataGridPathSortDescription inner, bool descending)
+            private DataGridPathSortDescription(DataGridPathSortDescription inner, ListSortDirection direction)
             {
                 _propertyPath = inner._propertyPath;
-                _descending = descending;
+                _direction = direction;
                 _propertyType = inner._propertyType;
                 _cultureSensitiveComparer = inner._cultureSensitiveComparer;
                 _internalComparer = inner._internalComparer;
@@ -201,7 +204,7 @@ namespace Avalonia.Collections
 
                 result = _internalComparer?.Compare(v1, v2) ?? 0;
 
-                if (_descending)
+                if (Direction == ListSortDirection.Descending)
                     return -result;
                 else
                     return result;
@@ -218,7 +221,7 @@ namespace Avalonia.Collections
             }
             public override IOrderedEnumerable<object> OrderBy(IEnumerable<object> seq)
             {
-                if(_descending)
+                if (Direction == ListSortDirection.Descending)
                 {
                     return seq.OrderByDescending(o => GetValue(o), InternalComparer);
                 }
@@ -229,7 +232,7 @@ namespace Avalonia.Collections
             }
             public override IOrderedEnumerable<object> ThenBy(IOrderedEnumerable<object> seq)
             {
-                if (_descending)
+                if (Direction == ListSortDirection.Descending)
                 {
                     return seq.ThenByDescending(o => GetValue(o), InternalComparer);
                 }
@@ -239,15 +242,28 @@ namespace Avalonia.Collections
                 }
             }
 
-            internal override DataGridSortDescription SwitchSortDirection()
+            public override DataGridSortDescription SwitchSortDirection()
             {
-                return new DataGridPathSortDescription(this, !_descending);
+                var newDirection = _direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
+                return new DataGridPathSortDescription(this, newDirection);
             }
         }
 
-        public static DataGridSortDescription FromPath(string propertyPath, bool descending = false, CultureInfo culture = null)
+        public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction = ListSortDirection.Ascending, CultureInfo culture = null)
+        {
+            return new DataGridPathSortDescription(propertyPath, direction, null, culture);
+        }
+
+
+        [Obsolete("Use overload taking a ListSortDirection.")]
+        public static DataGridSortDescription FromPath(string propertyPath, bool descending, CultureInfo culture = null)
+        {
+            return new DataGridPathSortDescription(propertyPath, descending ? ListSortDirection.Descending : ListSortDirection.Ascending, null, culture);
+        }
+
+        public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer)
         {
-            return new DataGridPathSortDescription(propertyPath, descending, culture);
+            return new DataGridPathSortDescription(propertyPath, direction, comparer, null);
         }
     }
 

+ 5 - 0
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -1229,6 +1229,11 @@ namespace Avalonia.Controls
             remove { AddHandler(SelectionChangedEvent, value); }
         }
 
+        /// <summary>
+        /// Occurs when the <see cref="DataGridColumn"/> sorting request is triggered.
+        /// </summary>
+        public event EventHandler<DataGridColumnEventArgs> Sorting;
+
         /// <summary>
         /// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" /> 
         /// object becomes available for reuse.

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@@ -1047,4 +1047,4 @@ namespace Avalonia.Controls
 
     }
 
-}
+}

+ 52 - 45
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -161,13 +161,14 @@ namespace Avalonia.Controls
                 var sort = OwningColumn.GetSortDescription();
                 if (sort != null)
                 {
-                    CurrentSortingState = sort.Descending ? ListSortDirection.Descending : ListSortDirection.Ascending;
+                    CurrentSortingState = sort.Direction;
                 }
             }
+
             PseudoClasses.Set(":sortascending",
-                CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Ascending);
+                CurrentSortingState == ListSortDirection.Ascending);
             PseudoClasses.Set(":sortdescending",
-                CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Descending);
+                CurrentSortingState == ListSortDirection.Descending);
         }
 
         internal void UpdateSeparatorVisibility(DataGridColumn lastVisibleColumn)
@@ -215,70 +216,76 @@ namespace Avalonia.Controls
         internal void ProcessSort(KeyModifiers keyModifiers)
         {
             // if we can sort:
-            //  - DataConnection.AllowSort is true, and
             //  - AllowUserToSortColumns and CanSort are true, and
-            //  - OwningColumn is bound, and
-            //  - SortDescriptionsCollection exists, and
-            //  - the column's data type is comparable
+            //  - OwningColumn is bound
             // then try to sort
             if (OwningColumn != null
                 && OwningGrid != null
                 && OwningGrid.EditingRow == null
                 && OwningColumn != OwningGrid.ColumnsInternal.FillerColumn
-                && OwningGrid.DataConnection.AllowSort
                 && OwningGrid.CanUserSortColumns
-                && OwningColumn.CanUserSort
-                && OwningGrid.DataConnection.SortDescriptions != null)
+                && OwningColumn.CanUserSort)
             {
-                DataGrid owningGrid = OwningGrid;
+                var ea = new DataGridColumnEventArgs(OwningColumn);
+                OwningGrid.OnColumnSorting(ea);
 
-                DataGridSortDescription newSort;
+                if (!ea.Handled && OwningGrid.DataConnection.AllowSort && OwningGrid.DataConnection.SortDescriptions != null)
+                {
+                    // - DataConnection.AllowSort is true, and
+                    // - SortDescriptionsCollection exists, and
+                    // - the column's data type is comparable
 
-                KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift);
+                    DataGrid owningGrid = OwningGrid;
+                    DataGridSortDescription newSort;
 
-                DataGridSortDescription sort = OwningColumn.GetSortDescription();
-                IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;
-                Debug.Assert(collectionView != null);
-                using (collectionView.DeferRefresh())
-                {
-                    // if shift is held down, we multi-sort, therefore if it isn't, we'll clear the sorts beforehand
-                    if (!shift || owningGrid.DataConnection.SortDescriptions.Count == 0)
-                    {
-                        owningGrid.DataConnection.SortDescriptions.Clear();
-                    }
+                    KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift);
+
+                    DataGridSortDescription sort = OwningColumn.GetSortDescription();
+                    IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;
+                    Debug.Assert(collectionView != null);
 
-                    // if ctrl is held down, we only clear the sort directions
-                    if (!ctrl)
+                    using (collectionView.DeferRefresh())
                     {
-                        if (sort != null)
+                        // if shift is held down, we multi-sort, therefore if it isn't, we'll clear the sorts beforehand
+                        if (!shift || owningGrid.DataConnection.SortDescriptions.Count == 0)
                         {
-                            newSort = sort.SwitchSortDirection();
+                            owningGrid.DataConnection.SortDescriptions.Clear();
+                        }
 
-                            // changing direction should not affect sort order, so we replace this column's
-                            // sort description instead of just adding it to the end of the collection
-                            int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
-                            if (oldIndex >= 0)
+                        // if ctrl is held down, we only clear the sort directions
+                        if (!ctrl)
+                        {
+                            if (sort != null)
                             {
-                                owningGrid.DataConnection.SortDescriptions.Remove(sort);
-                                owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
+                                newSort = sort.SwitchSortDirection();
+
+                                // changing direction should not affect sort order, so we replace this column's
+                                // sort description instead of just adding it to the end of the collection
+                                int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
+                                if (oldIndex >= 0)
+                                {
+                                    owningGrid.DataConnection.SortDescriptions.Remove(sort);
+                                    owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
+                                }
+                                else
+                                {
+                                    owningGrid.DataConnection.SortDescriptions.Add(newSort);
+                                }
                             }
                             else
                             {
+                                string propertyName = OwningColumn.GetSortPropertyName();
+                                // no-opt if we couldn't find a property to sort on
+                                if (string.IsNullOrEmpty(propertyName))
+                                {
+                                    return;
+                                }
+
+                                newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
+
                                 owningGrid.DataConnection.SortDescriptions.Add(newSort);
                             }
                         }
-                        else
-                        {
-                            string propertyName = OwningColumn.GetSortPropertyName();
-                            // no-opt if we couldn't find a property to sort on
-                            if (string.IsNullOrEmpty(propertyName))
-                            {
-                                return;
-                            }
-
-                            newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
-                            owningGrid.DataConnection.SortDescriptions.Add(newSort);
-                        }
                     }
                 }
             }

+ 5 - 0
src/Avalonia.Controls.DataGrid/DataGridColumns.cs

@@ -33,6 +33,11 @@ namespace Avalonia.Controls
             ColumnReordering?.Invoke(this, e);
         }
 
+        protected internal virtual void OnColumnSorting(DataGridColumnEventArgs e)
+        {
+            Sorting?.Invoke(this, e);
+        }
+
         /// <summary>
         /// Adjusts the widths of all columns with DisplayIndex >= displayIndex such that the total
         /// width is adjusted by the given amount, if possible.  If the total desired adjustment amount

+ 2 - 2
src/Avalonia.Controls.DataGrid/EventArgs.cs

@@ -289,7 +289,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// Provides data for <see cref="T:Avalonia.Controls.DataGrid" /> column-related events.
     /// </summary>
-    public class DataGridColumnEventArgs : EventArgs
+    public class DataGridColumnEventArgs : HandledEventArgs
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridColumnEventArgs" /> class.
@@ -566,4 +566,4 @@ namespace Avalonia.Controls
             private set;
         }
     }
-}
+}

+ 5 - 4
tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using System.Linq;
 using Avalonia.Collections;
 using Xunit;
@@ -18,7 +19,7 @@ namespace Avalonia.Controls.DataGrid.UnitTests.Collections
                 new Item("c", "c"),
             };
             var expectedResult = items.OrderBy(i => i.Prop1).ToList();
-            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), @descending: false);
+            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), ListSortDirection.Ascending);
             
             sortDescription.Initialize(typeof(Item));
             var result = sortDescription.OrderBy(items).ToList();
@@ -36,7 +37,7 @@ namespace Avalonia.Controls.DataGrid.UnitTests.Collections
                 new Item("c", "c"),
             };
             var expectedResult = items.OrderByDescending(i => i.Prop1).ToList();
-            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), @descending: true);
+            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), ListSortDirection.Descending);
             
             sortDescription.Initialize(typeof(Item));
             var result = sortDescription.OrderBy(items).ToList();
@@ -61,7 +62,7 @@ namespace Avalonia.Controls.DataGrid.UnitTests.Collections
                 new Item("a", "b"),
                 new Item("a", "c"),
             };
-            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), @descending: false);
+            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), ListSortDirection.Ascending);
             
             sortDescription.Initialize(typeof(Item));
             var result = sortDescription.ThenBy(items).ToList();
@@ -86,7 +87,7 @@ namespace Avalonia.Controls.DataGrid.UnitTests.Collections
                 new Item("a", "b"),
                 new Item("a", "a"),
             };
-            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), @descending: true);
+            var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), ListSortDirection.Descending);
             
             sortDescription.Initialize(typeof(Item));
             var result = sortDescription.ThenBy(items).ToList();