diff --git a/src/Avalonia.Controls.DataGrid.UnitTests/Columns/DataGridTemplateColumnReuseTests.cs b/src/Avalonia.Controls.DataGrid.UnitTests/Columns/DataGridTemplateColumnReuseTests.cs index 724bc5d8..ee003ed2 100644 --- a/src/Avalonia.Controls.DataGrid.UnitTests/Columns/DataGridTemplateColumnReuseTests.cs +++ b/src/Avalonia.Controls.DataGrid.UnitTests/Columns/DataGridTemplateColumnReuseTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Wieslaw Soltes. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Headless.XUnit; @@ -50,6 +51,86 @@ public void ReuseCellContent_false_rebuilds_content() Assert.Equal(2, template.BuildCount); } + [AvaloniaFact] + public void Recycled_row_reuses_existing_content_when_enabled() + { + var template = new CountingTemplate(); + var column = new TestTemplateColumn + { + CellTemplate = template, + ReuseCellContent = true + }; + var grid = new DataGrid(); + grid.ColumnsInternal.Add(column); + + var row = new DataGridRow + { + OwningGrid = grid, + DataContext = new object(), + Index = 0, + Slot = 0 + }; + var cell = new DataGridCell + { + OwningColumn = column + }; + var initialContent = column.GenerateElementPublic(cell, row.DataContext); + cell.Content = initialContent; + row.Cells.Insert(column.Index, cell); + + var root = new Window + { + Content = grid + }; + root.Show(); + + try + { + grid.DisplayData.RecycleRow(row); + row.DataContext = new object(); + + Assert.Same(initialContent, cell.Content); + Assert.Equal(1, template.BuildCount); + } + finally + { + root.Close(); + } + } + + [AvaloniaFact] + public void Recycled_placeholder_content_is_replaced_when_row_becomes_data_row() + { + var displayTemplate = new CountingTemplate(); + var newRowTemplate = new CountingTemplate(); + var column = new TestTemplateColumn + { + CellTemplate = displayTemplate, + NewRowCellTemplate = newRowTemplate, + ReuseCellContent = true + }; + var row = new DataGridRow + { + DataContext = new object(), + IsPlaceholder = false, + RecycledIsPlaceholder = true + }; + var cell = new DataGridCell + { + OwningColumn = column, + OwningRow = row, + DataContext = row.DataContext + }; + var placeholderContent = column.GenerateElementPublic(cell, DataGridCollectionView.NewItemPlaceholder); + cell.Content = placeholderContent; + + column.RefreshCellContentForDataContextChange(cell); + + Assert.NotSame(placeholderContent, cell.Content); + Assert.Equal(1, newRowTemplate.BuildCount); + Assert.Equal(1, displayTemplate.BuildCount); + } + private sealed class TestTemplateColumn : DataGridTemplateColumn { public Control GenerateElementPublic(DataGridCell cell, object dataItem) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 501bfc72..d4b4784d 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -721,7 +721,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { if (column.Index >= 0 && column.Index < Cells.Count) { - column.RefreshCellContent((Control)Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate)); + column.RefreshCellContentForDataContextChange(Cells[column.Index]); } } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 1ad64beb..c86e4ab7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -112,26 +112,30 @@ public IDataTemplate CellEditingTemplate protected override void EndCellEdit() { - //the next call to generate element should not resuse the current content as we need to exit edit mode + // The next regular generation must replace the editing control with display content. _forceGenerateCellFromTemplate = true; base.EndCellEdit(); } protected override Control GenerateElement(DataGridCell cell, object dataItem) { - Control recycledContent = _forceGenerateCellFromTemplate ? null : cell.Content as Control; + var forceNewContent = _forceGenerateCellFromTemplate; + _forceGenerateCellFromTemplate = false; + return GenerateElementCore(cell, dataItem, forceNewContent); + } + + private Control GenerateElementCore(DataGridCell cell, object dataItem, bool forceNewContent) + { + Control recycledContent = forceNewContent ? null : cell.Content as Control; // A recycled row can briefly clear its DataContext while being re-templated; avoid invoking user templates with null. if (dataItem is null) { - _forceGenerateCellFromTemplate = false; return recycledContent ?? new Control(); } if (dataItem == DataGridCollectionView.NewItemPlaceholder) { - _forceGenerateCellFromTemplate = false; - if (NewRowCellTemplate != null) { if (NewRowCellTemplate is IRecyclingDataTemplate recyclingNewRowTemplate) @@ -147,12 +151,6 @@ protected override Control GenerateElement(DataGridCell cell, object dataItem) if (CellTemplate != null) { - if (_forceGenerateCellFromTemplate) - { - _forceGenerateCellFromTemplate = false; - return CellTemplate.Build(dataItem); - } - if (ReuseCellContent && recycledContent != null && CellTemplate is not IRecyclingDataTemplate) @@ -204,12 +202,23 @@ protected internal override void RefreshCellContent(Control element, string prop var cell = element?.Parent as DataGridCell; if(cell is not null && (propertyName == nameof(CellTemplate) || propertyName == nameof(NewRowCellTemplate))) { - _forceGenerateCellFromTemplate = true; - cell.Content = GenerateElement(cell, cell.DataContext); + cell.Content = GenerateElementCore(cell, cell.DataContext, forceNewContent: true); } base.RefreshCellContent(element, propertyName); } + + internal void RefreshCellContentForDataContextChange(DataGridCell cell) + { + if (cell is null) + { + return; + } + + DataGridRow row = cell.OwningRow; + bool placeholderStateChanged = row != null && row.RecycledIsPlaceholder != row.IsPlaceholder; + cell.Content = GenerateElementCore(cell, cell.DataContext, placeholderStateChanged); + } public override bool IsReadOnly {