Browse Source

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

Feature/data grid custom sorting
pull/4694/head
Max Katz 5 years ago
committed by GitHub
parent
commit
ca498f8f9b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  2. 54
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  3. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  4. 2
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  5. 97
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  6. 5
      src/Avalonia.Controls.DataGrid/DataGridColumns.cs
  7. 4
      src/Avalonia.Controls.DataGrid/EventArgs.cs
  8. 9
      tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs

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

54
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
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.

2
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

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

97
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
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

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

9
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();

Loading…
Cancel
Save