diff --git a/src/Avalonia.Controls.TreeDataGrid/FlatTreeDataGridSource.cs b/src/Avalonia.Controls.TreeDataGrid/FlatTreeDataGridSource.cs index 228a7515..faaa9183 100644 --- a/src/Avalonia.Controls.TreeDataGrid/FlatTreeDataGridSource.cs +++ b/src/Avalonia.Controls.TreeDataGrid/FlatTreeDataGridSource.cs @@ -135,29 +135,40 @@ void ITreeDataGridSource.DragDropRows( } } - bool ITreeDataGridSource.SortBy(IColumn? column, ListSortDirection direction) + public bool SortBy(IColumn? column, ListSortDirection direction) { - if (column is IColumn typedColumn) + if (column is IColumn typedColumn && + Columns.Contains(typedColumn)) { - if (!Columns.Contains(typedColumn)) - return true; - var comparer = typedColumn.GetComparison(direction); + if (comparer is null) + return false; + + Sort(comparer); + foreach (var c in Columns) + c.SortDirection = c == column ? direction : null; - if (comparer is not null) - { - _comparer = new FuncComparer(comparer); - _rows?.Sort(_comparer); - Sorted?.Invoke(); - foreach (var c in Columns) - c.SortDirection = c == column ? direction : null; - } return true; } return false; } + public void Sort(Comparison? comparison) + { + _comparer = comparison is not null ? new FuncComparer(comparison!) : null; + _rows?.Sort(_comparer); + Sorted?.Invoke(); + } + + public void Unsort() + { + Sort(null); + + foreach (var c in Columns) + c.SortDirection = null; + } + IEnumerable ITreeDataGridSource.GetModelChildren(object model) { return []; diff --git a/src/Avalonia.Controls.TreeDataGrid/HierarchicalTreeDataGridSource.cs b/src/Avalonia.Controls.TreeDataGrid/HierarchicalTreeDataGridSource.cs index ccb385d9..0d5b54f5 100644 --- a/src/Avalonia.Controls.TreeDataGrid/HierarchicalTreeDataGridSource.cs +++ b/src/Avalonia.Controls.TreeDataGrid/HierarchicalTreeDataGridSource.cs @@ -194,6 +194,15 @@ public void Sort(Comparison? comparison) { _comparison = comparison; _rows?.Sort(_comparison); + Sorted?.Invoke(); + } + + public void Unsort() + { + Sort(null); + + foreach (var c in Columns) + c.SortDirection = null; } IEnumerable? ITreeDataGridSource.GetModelChildren(object model) @@ -208,7 +217,6 @@ public bool SortBy(IColumn? column, ListSortDirection direction) columnBase.GetComparison(direction) is Comparison comparison) { Sort(comparison); - Sorted?.Invoke(); foreach (var c in Columns) c.SortDirection = c == column ? direction : null; return true; diff --git a/src/Avalonia.Controls.TreeDataGrid/ITreeDataGridSource.cs b/src/Avalonia.Controls.TreeDataGrid/ITreeDataGridSource.cs index dd2c48ac..a38a4e80 100644 --- a/src/Avalonia.Controls.TreeDataGrid/ITreeDataGridSource.cs +++ b/src/Avalonia.Controls.TreeDataGrid/ITreeDataGridSource.cs @@ -76,6 +76,11 @@ void DragDropRows( /// The sort direction. /// True if the sort could be performed; otherwise false. bool SortBy(IColumn column, ListSortDirection direction); + + /// + /// Removes any active sort and restores the source to its unsorted state. + /// + void Unsort(); } /// @@ -87,5 +92,13 @@ public interface ITreeDataGridSource : ITreeDataGridSource /// Gets or sets the items in the data source. /// new IEnumerable Items { get; set; } + + /// + /// Sorts the data source using the specified comparison. + /// + /// + /// A delegate that defines the item order. + /// + void Sort(Comparison? comparison); } } diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCellsPresenter.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCellsPresenter.cs index 7448138a..e0fcd898 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCellsPresenter.cs @@ -1,10 +1,8 @@ using System; -using System.Xml.Linq; using Avalonia.Controls.Models.TreeDataGrid; using Avalonia.Controls.Selection; using Avalonia.Layout; using Avalonia.LogicalTree; -using Avalonia.Media; using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives diff --git a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs index e4a56d55..9a971f61 100644 --- a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs +++ b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs @@ -766,6 +766,73 @@ public void Should_Preserve_Horizontal_ScrollBar_When_Rows_Removed() Assert.Equal(new(200, 0), headerScroll.Extent); } + [AvaloniaFact] + public void SortBy_Sorts_Display_And_Sets_ColumnIndicator() + { + var (target, items) = CreateTarget(); + + // Apply sort by ID descending + Assert.False(target.Source!.IsSorted); + var ok = target.Source!.SortBy(target.Columns![0], ListSortDirection.Descending); + Assert.True(ok); + Assert.True(target.Source.IsSorted); + Assert.Equal(ListSortDirection.Descending, target.Columns![0].SortDirection); + + // Verify rows are presented in descending order + var rows = target.Source!.Rows; + var sorted = items.OrderByDescending(x => x.Id).ToList(); + + for (var i = 0; i < sorted.Count && i < rows.Count; ++i) + { + var row = rows[i]; + Assert.Equal(sorted[i].Id, (row.Model as Model)?.Id); + } + } + + [AvaloniaFact] + public void Sort_Using_Comparison_Sorts_Display() + { + var (target, items) = CreateTarget(); + + var source = (FlatTreeDataGridSource)target.Source!; + Assert.False(source.IsSorted); + + source.Sort((x, y) => y.Id - x.Id); + + Assert.True(source.IsSorted); + + var sorted = items.OrderByDescending(x => x.Id).ToList(); + + for (var i = 0; i < sorted.Count && i < source.Rows.Count; ++i) + { + var row = source.Rows[i]; + Assert.Equal(sorted[i].Id, (row.Model as Model)?.Id); + } + } + + [AvaloniaFact] + public void Unsort_Restores_Original_Order() + { + var (target, items) = CreateTarget(); + + var source = (FlatTreeDataGridSource)target.Source!; + + // Apply and then clear sort + var ok = source.SortBy(source.Columns[0], ListSortDirection.Descending); + Assert.True(ok); + Assert.True(source.IsSorted); + + source.Unsort(); + Assert.False(source.IsSorted); + + // Verify original insertion order restored + for (var i = 0; i < items.Count && i < source.Rows.Count; ++i) + { + var row = (IRow)source.Rows[i]; + Assert.Equal(items[i].Id, ((Model)row.Model).Id); + } + } + private static void AssertRowIndexes(TreeDataGrid target, int firstRowIndex, int rowCount) { var presenter = target.RowsPresenter; diff --git a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Hierarchical.cs b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Hierarchical.cs index 181b56e2..b3c619b7 100644 --- a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Hierarchical.cs +++ b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Hierarchical.cs @@ -484,6 +484,67 @@ public void Should_Recycle_Focused_Cell_When_Row_Collapsed() Assert.Equal(-1, cell.RowIndex); } + [AvaloniaFact] + public void SortBy_Sorts_Roots_And_Sets_Indicator() + { + var (target, source) = CreateTarget(); + + Assert.False(source.IsSorted); + + var ok = source.SortBy(source.Columns[0], ListSortDirection.Descending); + Assert.True(ok); + Assert.True(source.IsSorted); + Assert.Equal(ListSortDirection.Descending, source.Columns[0].SortDirection); + + // Verify top-level rows are sorted by Id descending + var sorted = source.Items.OrderByDescending(x => x.Id).ToList(); + for (var i = 0; i < sorted.Count && i < source.Rows.Count; ++i) + { + var row = source.Rows[i]; + Assert.Equal(sorted[i].Id, (row.Model as Model)?.Id); + } + } + + [AvaloniaFact] + public void Sort_Using_Comparison_Sorts_Display() + { + var (target, source) = CreateTarget(); + + Assert.False(source.IsSorted); + + source.Sort((x, y) => y.Id - x.Id); + + Assert.True(source.IsSorted); + var sorted = source.Items.OrderByDescending(x => x.Id).ToList(); + + for (var i = 0; i < sorted.Count && i < source.Rows.Count; ++i) + { + var row = source.Rows[i]; + Assert.Equal(sorted[i].Id, (row.Model as Model)?.Id); + } + } + + [AvaloniaFact] + public void Unsort_Restores_Original_Order() + { + var (target, source) = CreateTarget(); + var original = source.Items.ToList(); + + var ok = source.SortBy(source.Columns[0], ListSortDirection.Descending); + Assert.True(ok); + Assert.True(source.IsSorted); + + source.Unsort(); + Assert.False(source.IsSorted); + + // Verify top-level rows have original sort order + for (var i = 0; i < original.Count && i < source.Rows.Count; ++i) + { + var row = source.Rows[i]; + Assert.Equal(original[i].Id, (row.Model as Model)?.Id); + } + } + private static (TreeDataGrid, HierarchicalTreeDataGridSource) CreateTarget( IEnumerable>? columns = null, bool runLayout = true) @@ -503,14 +564,14 @@ private static (TreeDataGrid, HierarchicalTreeDataGridSource) CreateTarge }, }; - columns ??= new IColumn[] - { + columns ??= + [ new HierarchicalExpanderColumn( new TextColumn("ID", x => x.Id), x => x.Children, x => true), new TextColumn("Title", x => x.Title), - }; + ]; var source = new HierarchicalTreeDataGridSource(items); source.Columns.AddRange(columns);