From 7b9571ac7b89715bb94a912d012f06f7f5acd934 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 27 Nov 2024 15:27:17 +0100 Subject: [PATCH 1/2] Added failing test for #298. --- .../TreeDataGridTests_Flat.cs | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs index 083396e7..4f7cbd67 100644 --- a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs +++ b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs @@ -1,19 +1,15 @@ -using Avalonia.Collections; -using Avalonia.Controls.Models.TreeDataGrid; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Selection; -using Avalonia.Layout; -using Avalonia.LogicalTree; -using Avalonia.Styling; -using Avalonia.VisualTree; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using Avalonia.Controls.Embedding; -using Avalonia.Headless; +using Avalonia.Collections; +using Avalonia.Controls.Models.TreeDataGrid; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; using Avalonia.Headless.XUnit; +using Avalonia.Styling; using Avalonia.Threading; +using Avalonia.VisualTree; using Xunit; using Enumerable = System.Linq.Enumerable; @@ -381,6 +377,53 @@ public void Columns_Are_Correctly_Sized_After_Changing_Source() Assert.Equal(20, columns[2].ActualWidth); } + [AvaloniaFact(Timeout = 10000)] + public void Should_Correctly_Align_Columns_When_Vertically_Scrolling_With_First_Column_Unrealized() + { + // Issue #298 + static void AssertRealizedCells(TreeDataGrid target) + { + var rows = target.RowsPresenter!.GetVisualChildren().Cast(); + + foreach (var row in rows) + { + var cells = row.CellsPresenter!.GetRealizedElements() + .Cast() + .OrderBy(x => x.ColumnIndex) + .ToList(); + + Assert.Equal(3, cells.Count); + Assert.Equal(1, cells[0].ColumnIndex); + Assert.Equal(100, cells[0].Bounds.Left); + Assert.Equal(150, cells[1].Bounds.Left); + Assert.Equal(200, cells[2].Bounds.Left); + } + } + + var (target, items) = CreateTarget(columns: + [ + new TextColumn("ID", x => x.Id, width: new GridLength(100, GridUnitType.Pixel)), + new TextColumn("Title1", x => x.Title, width: new GridLength(50, GridUnitType.Pixel)), + new TextColumn("Title2", x => x.Title, width: new GridLength(50, GridUnitType.Pixel)), + new TextColumn("Title3", x => x.Title, width: new GridLength(50, GridUnitType.Pixel)), + ]); + + // Scroll horizontally and check that the realized cells are positioned correctly. + target.Scroll!.Offset = new Vector(120, 0); + target.UpdateLayout(); + AssertRealizedCells(target); + + // Scroll down a row and check that the realized cells are positioned correctly. + target.Scroll!.Offset = new Vector(120, 10); + target.UpdateLayout(); + AssertRealizedCells(target); + + // Now scroll back vertically and check once more. + target.Scroll!.Offset = new Vector(120, 0); + target.UpdateLayout(); + AssertRealizedCells(target); + } + public class RemoveItems { [AvaloniaFact(Timeout = 10000)] From 39aa9c1993a1fa8f6cbc91db49c3e5ccef773dc7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 27 Nov 2024 15:36:47 +0100 Subject: [PATCH 2/2] Get X position from Columns. For columnar layouts, there should usually be be no need to estimate the x position of the column start in the viewport, as this information can be queried from `IColumns`. Make a new `GetOrEstimateAnchorElementForViewport` virtual method and override its behavior for columnar presenters. Fixes #298 --- .../TreeDataGridColumnarPresenterBase.cs | 10 ++++++++++ .../Primitives/TreeDataGridPresenterBase.cs | 20 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs index 6700470a..6e1129e5 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridColumnarPresenterBase.cs @@ -23,6 +23,16 @@ protected sealed override Size GetInitialConstraint(Control element, int index, return new Size(Math.Min(availableSize.Width, column.MaxActualWidth), availableSize.Height); } + protected override (int index, double position) GetOrEstimateAnchorElementForViewport( + double viewportStart, + double viewportEnd, + int itemCount) + { + if (Columns?.GetColumnAt(viewportStart) is var (index, position) && index >= 0) + return (index, position); + return base.GetOrEstimateAnchorElementForViewport(viewportStart, viewportEnd, itemCount); + } + protected sealed override bool NeedsFinalMeasurePass(int firstIndex, IReadOnlyList elements) { var columns = Columns!; diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs index 483acddb..7cb9e7b0 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs @@ -397,6 +397,20 @@ protected override Size ArrangeOverride(Size finalSize) } } + protected virtual (int index, double position) GetOrEstimateAnchorElementForViewport( + double viewportStart, + double viewportEnd, + int itemCount) + { + Debug.Assert(_realizedElements is not null); + + return _realizedElements.GetOrEstimateAnchorElementForViewport( + viewportStart, + viewportEnd, + itemCount, + ref _lastEstimatedElementSizeU); + } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -539,11 +553,7 @@ private MeasureViewport CalculateMeasureViewport(IReadOnlyList items, Siz // Get or estimate the anchor element from which to start realization. var itemCount = items.Count; - var (anchorIndex, anchorU) = _realizedElements.GetOrEstimateAnchorElementForViewport( - viewportStart, - viewportEnd, - itemCount, - ref _lastEstimatedElementSizeU); + var (anchorIndex, anchorU) = GetOrEstimateAnchorElementForViewport(viewportStart, viewportEnd, itemCount); // Check if the anchor element is not within the currently realized elements. var disjunct = anchorIndex < _realizedElements.FirstIndex ||