Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.Controls.DataGrid/DataGridRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}
}
Expand Down
35 changes: 22 additions & 13 deletions src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
{
Expand Down
Loading