// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System;
using System.Diagnostics;
using Avalonia.Reactive;
namespace Avalonia.Controls
{
///
/// Represents a row.
///
[TemplatePart(DATAGRIDROW_elementBottomGridLine, typeof(Rectangle))]
[TemplatePart(DATAGRIDROW_elementCells, typeof(DataGridCellsPresenter))]
[TemplatePart(DATAGRIDROW_elementDetails, typeof(DataGridDetailsPresenter))]
[TemplatePart(DATAGRIDROW_elementRoot, typeof(Panel))]
[TemplatePart(DATAGRIDROW_elementRowHeader, typeof(DataGridRowHeader))]
[PseudoClasses(":selected", ":editing", ":invalid")]
public class DataGridRow : TemplatedControl
{
private const byte DATAGRIDROW_defaultMinHeight = 0;
internal const int DATAGRIDROW_maximumHeight = 65536;
internal const double DATAGRIDROW_minimumHeight = 0;
private const string DATAGRIDROW_elementBottomGridLine = "PART_BottomGridLine";
private const string DATAGRIDROW_elementCells = "PART_CellsPresenter";
private const string DATAGRIDROW_elementDetails = "PART_DetailsPresenter";
internal const string DATAGRIDROW_elementRoot = "PART_Root";
internal const string DATAGRIDROW_elementRowHeader = "PART_RowHeader";
private DataGridCellsPresenter _cellsElement;
private DataGridCell _fillerCell;
private DataGridRowHeader _headerElement;
private double _lastHorizontalOffset;
private int? _mouseOverColumnIndex;
private bool _isValid = true;
private Rectangle _bottomGridLine;
private bool _areHandlersSuspended;
// In the case where Details scales vertically when it's arranged at a different width, we
// get the wrong height measurement so we need to check it again after arrange
private bool _checkDetailsContentHeight;
// Optimal height of the details based on the Element created by the DataTemplate
private double _detailsDesiredHeight;
private bool _detailsLoaded;
private bool _detailsVisibilityNotificationPending;
private Control _detailsContent;
private IDisposable _detailsContentSizeSubscription;
private DataGridDetailsPresenter _detailsElement;
// Locally cache whether or not details are visible so we don't run redundant storyboards
// The Details Template that is actually applied to the Row
private IDataTemplate _appliedDetailsTemplate;
private bool? _appliedDetailsVisibility;
///
/// Identifies the Header dependency property.
///
public static readonly StyledProperty HeaderProperty =
AvaloniaProperty.Register(nameof(Header));
///
/// Gets or sets the row header.
///
public object Header
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DirectProperty IsValidProperty =
AvaloniaProperty.RegisterDirect(
nameof(IsValid),
o => o.IsValid);
///
/// Gets a value that indicates whether the data in a row is valid.
///
public bool IsValid
{
get { return _isValid; }
internal set { SetAndRaise(IsValidProperty, ref _isValid, value); }
}
public static readonly StyledProperty DetailsTemplateProperty =
AvaloniaProperty.Register(nameof(DetailsTemplate));
///
/// Gets or sets the template that is used to display the details section of the row.
///
public IDataTemplate DetailsTemplate
{
get { return GetValue(DetailsTemplateProperty); }
set { SetValue(DetailsTemplateProperty, value); }
}
public static readonly StyledProperty AreDetailsVisibleProperty =
AvaloniaProperty.Register(nameof(AreDetailsVisible));
///
/// Gets or sets a value that indicates when the details section of the row is displayed.
///
public bool AreDetailsVisible
{
get { return GetValue(AreDetailsVisibleProperty); }
set { SetValue(AreDetailsVisibleProperty, value); }
}
static DataGridRow()
{
HeaderProperty.Changed.AddClassHandler((x, e) => x.OnHeaderChanged(e));
DetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnDetailsTemplateChanged(e));
AreDetailsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreDetailsVisibleChanged(e));
PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
}
///
/// Initializes a new instance of the class.
///
public DataGridRow()
{
MinHeight = DATAGRIDROW_defaultMinHeight;
Index = -1;
IsValid = true;
Slot = -1;
_mouseOverColumnIndex = null;
_detailsDesiredHeight = double.NaN;
_detailsLoaded = false;
_appliedDetailsVisibility = false;
Cells = new DataGridCellCollection(this);
Cells.CellAdded += DataGridCellCollection_CellAdded;
Cells.CellRemoved += DataGridCellCollection_CellRemoved;
}
private void SetValueNoCallback(AvaloniaProperty property, T value, BindingPriority priority = BindingPriority.LocalValue)
{
_areHandlersSuspended = true;
try
{
SetValue(property, value, priority);
}
finally
{
_areHandlersSuspended = false;
}
}
private void OnHeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_headerElement != null)
{
_headerElement.Content = e.NewValue;
}
}
private void OnDetailsTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{
var oldValue = (IDataTemplate)e.OldValue;
var newValue = (IDataTemplate)e.NewValue;
if (!_areHandlersSuspended && OwningGrid != null)
{
IDataTemplate actualDetailsTemplate(IDataTemplate template) => (template ?? OwningGrid.RowDetailsTemplate);
// We don't always want to apply the new Template because they might have set the same one
// we inherited from the DataGrid
if (actualDetailsTemplate(newValue) != actualDetailsTemplate(oldValue))
{
ApplyDetailsTemplate(initializeDetailsPreferredHeight: false);
}
}
}
private void OnAreDetailsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
{
if (OwningGrid == null)
{
throw DataGridError.DataGrid.NoOwningGrid(this.GetType());
}
if (Index == -1)
{
throw DataGridError.DataGridRow.InvalidRowIndexCannotCompleteOperation();
}
var newValue = (bool)e.NewValue;
OwningGrid.OnRowDetailsVisibilityPropertyChanged(Index, newValue);
SetDetailsVisibilityInternal(newValue, raiseNotification: true, animate: true);
}
}
internal DataGrid OwningGrid
{
get;
set;
}
///
/// Index of the row
///
internal int Index
{
get;
set;
}
internal double ActualBottomGridLineHeight
{
get
{
if (_bottomGridLine != null && OwningGrid != null && OwningGrid.AreRowBottomGridLinesRequired)
{
// Unfortunately, _bottomGridLine has no size yet so we can't get its actualheight
return DataGrid.HorizontalGridLinesThickness;
}
return 0;
}
}
internal DataGridCellCollection Cells
{
get;
private set;
}
internal DataGridCell FillerCell
{
get
{
if (_fillerCell == null)
{
_fillerCell = new DataGridCell
{
IsVisible = false,
OwningRow = this
};
if (OwningGrid.CellTheme is {} cellTheme)
{
_fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template);
}
if (_cellsElement != null)
{
_cellsElement.Children.Add(_fillerCell);
}
}
return _fillerCell;
}
}
internal bool HasBottomGridLine
{
get
{
return _bottomGridLine != null;
}
}
internal bool HasHeaderCell
{
get
{
return _headerElement != null;
}
}
internal DataGridRowHeader HeaderCell
{
get
{
return _headerElement;
}
}
internal bool IsEditing => OwningGrid != null && OwningGrid.EditingRow == this;
///
/// Layout when template is applied
///
internal bool IsLayoutDelayed
{
get;
private set;
}
internal bool IsMouseOver
{
get
{
return OwningGrid != null && OwningGrid.MouseOverRowIndex == Index;
}
set
{
if (OwningGrid != null && value != IsMouseOver)
{
if (value)
{
OwningGrid.MouseOverRowIndex = Index;
}
else
{
OwningGrid.MouseOverRowIndex = null;
}
}
}
}
internal bool IsRecycled
{
get;
private set;
}
internal bool IsRecyclable
{
get
{
if (OwningGrid != null)
{
return OwningGrid.IsRowRecyclable(this);
}
return true;
}
}
internal bool IsSelected
{
get
{
if (OwningGrid == null || Slot == -1)
{
// The Slot can be -1 if we're about to reuse or recycle this row, but the layout cycle has not
// passed so we don't know the outcome yet. We don't care whether or not it's selected in this case
return false;
}
return OwningGrid.GetRowSelection(Slot);
}
}
internal int? MouseOverColumnIndex
{
get
{
return _mouseOverColumnIndex;
}
set
{
if (_mouseOverColumnIndex != value)
{
DataGridCell oldMouseOverCell = null;
if (_mouseOverColumnIndex != null && OwningGrid.IsSlotVisible(Slot))
{
if (_mouseOverColumnIndex > -1)
{
oldMouseOverCell = Cells[_mouseOverColumnIndex.Value];
}
}
_mouseOverColumnIndex = value;
if (oldMouseOverCell != null && IsVisible)
{
oldMouseOverCell.UpdatePseudoClasses();
}
if (_mouseOverColumnIndex != null && OwningGrid != null && OwningGrid.IsSlotVisible(Slot))
{
if (_mouseOverColumnIndex > -1)
{
Cells[_mouseOverColumnIndex.Value].UpdatePseudoClasses();
}
}
}
}
}
internal Panel RootElement
{
get;
private set;
}
internal int Slot
{
get;
set;
}
// Height that the row will eventually end up at after a possible details animation has completed
internal double TargetHeight
{
get
{
if (!double.IsNaN(Height))
{
return Height;
}
else if (_detailsElement != null && _appliedDetailsVisibility == true && _appliedDetailsTemplate != null)
{
Debug.Assert(!double.IsNaN(_detailsElement.ContentHeight));
Debug.Assert(!double.IsNaN(_detailsDesiredHeight));
return DesiredSize.Height + _detailsDesiredHeight - _detailsElement.ContentHeight;
}
else
{
return DesiredSize.Height;
}
}
}
///
/// Returns the index of the current row.
///
///
/// The index of the current row.
///
public int GetIndex()
{
return Index;
}
///
/// Returns the row which contains the given element
///
/// element contained in a row
/// Row that contains the element, or null if not found
///
public static DataGridRow GetRowContainingElement(Control element)
{
// Walk up the tree to find the DataGridRow that contains the element
Visual parent = element;
DataGridRow row = parent as DataGridRow;
while ((parent != null) && (row == null))
{
parent = parent.GetVisualParent();
row = parent as DataGridRow;
}
return row;
}
///
/// Arranges the content of the .
///
///
/// The actual size used by the .
///
///
/// The final area within the parent that this element should use to arrange itself and its children.
///
protected override Size ArrangeOverride(Size finalSize)
{
if (OwningGrid == null)
{
return base.ArrangeOverride(finalSize);
}
// If the DataGrid was scrolled horizontally after our last Arrange, we need to make sure
// the Cells and Details are Arranged again
if (_lastHorizontalOffset != OwningGrid.HorizontalOffset)
{
_lastHorizontalOffset = OwningGrid.HorizontalOffset;
InvalidateHorizontalArrange();
}
Size size = base.ArrangeOverride(finalSize);
if (_checkDetailsContentHeight)
{
_checkDetailsContentHeight = false;
EnsureDetailsContentHeight();
}
if (RootElement != null)
{
foreach (Control child in RootElement.Children)
{
if (DataGridFrozenGrid.GetIsFrozen(child))
{
TranslateTransform transform = new TranslateTransform();
// Automatic layout rounding doesn't apply to transforms so we need to Round this
transform.X = Math.Round(OwningGrid.HorizontalOffset);
child.RenderTransform = transform;
}
}
}
if (_bottomGridLine != null)
{
RectangleGeometry gridlineClipGeometry = new RectangleGeometry();
gridlineClipGeometry.Rect = new Rect(OwningGrid.HorizontalOffset, 0, Math.Max(0, DesiredSize.Width - OwningGrid.HorizontalOffset), _bottomGridLine.DesiredSize.Height);
_bottomGridLine.Clip = gridlineClipGeometry;
}
return size;
}
///
/// Measures the children of a to
/// prepare for arranging them during the pass.
///
///
/// The available size that this element can give to child elements. Indicates an upper limit that child elements should not exceed.
///
///
/// The size that the determines it needs during layout, based on its calculations of child object allocated sizes.
///
protected override Size MeasureOverride(Size availableSize)
{
if (OwningGrid == null)
{
return base.MeasureOverride(availableSize);
}
//Allow the DataGrid specific components to adjust themselves based on new values
if (_headerElement != null)
{
_headerElement.InvalidateMeasure();
}
if (_cellsElement != null)
{
_cellsElement.InvalidateMeasure();
}
if (_detailsElement != null)
{
_detailsElement.InvalidateMeasure();
}
Size desiredSize = base.MeasureOverride(availableSize);
return desiredSize.WithWidth(Math.Max(desiredSize.Width, OwningGrid.CellsWidth));
}
///
/// Builds the visual tree for the column header when a new template is applied.
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
RootElement = e.NameScope.Find(DATAGRIDROW_elementRoot);
if (RootElement != null)
{
UpdatePseudoClasses();
}
bool updateVerticalScrollBar = false;
if (_cellsElement != null)
{
// If we're applying a new template, we want to remove the cells from the previous _cellsElement
_cellsElement.Children.Clear();
updateVerticalScrollBar = true;
}
_cellsElement = e.NameScope.Find(DATAGRIDROW_elementCells);
if (_cellsElement != null)
{
_cellsElement.OwningRow = this;
// Cells that were already added before the Template was applied need to
// be added to the Canvas
if (Cells.Count > 0)
{
foreach (DataGridCell cell in Cells)
{
_cellsElement.Children.Add(cell);
}
}
}
_detailsElement = e.NameScope.Find(DATAGRIDROW_elementDetails);
if (_detailsElement != null && OwningGrid != null)
{
_detailsElement.OwningRow = this;
if (ActualDetailsVisibility && ActualDetailsTemplate != null && _appliedDetailsTemplate == null)
{
// Apply the DetailsTemplate now that the row template is applied.
SetDetailsVisibilityInternal(ActualDetailsVisibility, raiseNotification: _detailsVisibilityNotificationPending, animate: false);
_detailsVisibilityNotificationPending = false;
}
}
_bottomGridLine = e.NameScope.Find(DATAGRIDROW_elementBottomGridLine);
EnsureGridLines();
_headerElement = e.NameScope.Find(DATAGRIDROW_elementRowHeader);
if (_headerElement != null)
{
_headerElement.Owner = this;
if (Header != null)
{
_headerElement.Content = Header;
}
EnsureHeaderStyleAndVisibility(null);
}
//The height of this row might have changed after applying a new style, so fix the vertical scroll bar
if (OwningGrid != null && updateVerticalScrollBar)
{
OwningGrid.UpdateVerticalScrollBar();
}
}
protected override void OnPointerEntered(PointerEventArgs e)
{
base.OnPointerEntered(e);
IsMouseOver = true;
}
protected override void OnPointerExited(PointerEventArgs e)
{
IsMouseOver = false;
base.OnPointerExited(e);
}
internal void ApplyCellsState()
{
foreach (DataGridCell dataGridCell in Cells)
{
dataGridCell.UpdatePseudoClasses();
}
}
internal void ApplyHeaderStatus()
{
if (_headerElement != null && OwningGrid.AreRowHeadersVisible)
{
_headerElement.UpdatePseudoClasses();
}
}
internal void UpdatePseudoClasses()
{
if (RootElement != null && OwningGrid != null && IsVisible)
{
PseudoClasses.Set(":selected", IsSelected);
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
}
}
//TODO Animation
internal void DetachFromDataGrid(bool recycle)
{
UnloadDetailsTemplate(recycle);
if (recycle)
{
IsRecycled = true;
if (_cellsElement != null)
{
_cellsElement.Recycle();
}
_checkDetailsContentHeight = false;
// Clear out the old Details cache so it won't be reused for other data
//_detailsDesiredHeight = double.NaN;
if (_detailsElement != null)
{
_detailsElement.ClearValue(DataGridDetailsPresenter.ContentHeightProperty);
}
}
Slot = -1;
}
internal void InvalidateCellsIndex()
{
_cellsElement?.InvalidateChildIndex();
}
internal void EnsureFillerVisibility()
{
if (_cellsElement != null)
{
_cellsElement.EnsureFillerVisibility();
}
}
internal void EnsureGridLines()
{
if (OwningGrid != null)
{
if (_bottomGridLine != null)
{
// It looks like setting Visibility sometimes has side effects so make sure the value is actually
// different before setting it
bool newVisibility = OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Horizontal || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All;
if (newVisibility != _bottomGridLine.IsVisible)
{
_bottomGridLine.IsVisible = newVisibility;
}
_bottomGridLine.Fill = OwningGrid.HorizontalGridLinesBrush;
}
foreach (DataGridCell cell in Cells)
{
cell.EnsureGridLine(OwningGrid.ColumnsInternal.LastVisibleColumn);
}
}
}
internal void EnsureHeaderStyleAndVisibility(Styling.Style previousStyle)
{
if (_headerElement != null && OwningGrid != null)
{
_headerElement.IsVisible = OwningGrid.AreRowHeadersVisible;
}
}
internal void EnsureHeaderVisibility()
{
if (_headerElement != null && OwningGrid != null)
{
_headerElement.IsVisible = OwningGrid.AreRowHeadersVisible;
}
}
internal void InvalidateHorizontalArrange()
{
if (_cellsElement != null)
{
_cellsElement.InvalidateArrange();
}
if (_detailsElement != null)
{
_detailsElement.InvalidateArrange();
}
}
internal void InvalidateDesiredHeight()
{
_cellsElement?.InvalidateDesiredHeight();
}
internal void ResetGridLine()
{
_bottomGridLine = null;
}
private void DataGridCellCollection_CellAdded(object sender, DataGridCellEventArgs e)
{
_cellsElement?.Children.Add(e.Cell);
}
private void DataGridCellCollection_CellRemoved(object sender, DataGridCellEventArgs e)
{
_cellsElement?.Children.Remove(e.Cell);
}
private void DataGridRow_PointerPressed(PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}
if (OwningGrid != null)
{
OwningGrid.IsDoubleClickRecordsClickOnCall(this);
if (OwningGrid.UpdatedStateOnMouseLeftButtonDown)
{
OwningGrid.UpdatedStateOnMouseLeftButtonDown = false;
}
else
{
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, -1, Slot, false);
}
}
}
private void OnRowDetailsChanged()
{
OwningGrid?.OnRowDetailsChanged();
}
// Returns the actual template that should be sued for Details: either explicity set on this row
// or inherited from the DataGrid
private IDataTemplate ActualDetailsTemplate
{
get
{
Debug.Assert(OwningGrid != null);
return DetailsTemplate ?? OwningGrid.RowDetailsTemplate;
}
}
private bool ActualDetailsVisibility
{
get
{
if (OwningGrid == null)
{
throw DataGridError.DataGrid.NoOwningGrid(GetType());
}
if (Index == -1)
{
throw DataGridError.DataGridRow.InvalidRowIndexCannotCompleteOperation();
}
return OwningGrid.GetRowDetailsVisibility(Index);
}
}
private void UnloadDetailsTemplate(bool recycle)
{
if (_detailsElement != null)
{
if (_detailsContent != null)
{
if (_detailsLoaded)
{
OwningGrid.OnUnloadingRowDetails(this, _detailsContent);
}
_detailsContent.DataContext = null;
if (!recycle)
{
_detailsContentSizeSubscription?.Dispose();
_detailsContentSizeSubscription = null;
_detailsContent = null;
}
}
if (!recycle)
{
_detailsElement.Children.Clear();
}
_detailsElement.ContentHeight = 0;
}
if (!recycle)
{
_appliedDetailsTemplate = null;
SetValueNoCallback(DetailsTemplateProperty, null);
}
_detailsLoaded = false;
_appliedDetailsVisibility = null;
SetValueNoCallback(AreDetailsVisibleProperty, false);
}
//TODO Animation
internal void EnsureDetailsContentHeight()
{
if ((_detailsElement != null)
&& (_detailsContent != null)
&& (double.IsNaN(_detailsContent.Height))
&& (AreDetailsVisible)
&& (!double.IsNaN(_detailsDesiredHeight))
&& !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
&& Slot != -1)
{
_detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height;
if (true)
{
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
}
// Makes sure the _detailsDesiredHeight is initialized. We need to measure it to know what
// height we want to animate to. Subsequently, we just update that height in response to SizeChanged
private void EnsureDetailsDesiredHeight()
{
Debug.Assert(_detailsElement != null && OwningGrid != null);
if (_detailsContent != null)
{
Debug.Assert(_detailsElement.Children.Contains(_detailsContent));
_detailsContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_detailsDesiredHeight = _detailsContent.DesiredSize.Height;
}
else
{
_detailsDesiredHeight = 0;
}
}
//TODO Cleanup
double? _previousDetailsHeight = null;
//TODO Animation
private void DetailsContent_HeightChanged(double newValue)
{
if (_previousDetailsHeight.HasValue)
{
var oldValue = _previousDetailsHeight.Value;
_previousDetailsHeight = newValue;
if (newValue != oldValue && newValue != _detailsDesiredHeight)
{
if (AreDetailsVisible && _appliedDetailsTemplate != null)
{
// Update the new desired height for RowDetails
_detailsDesiredHeight = newValue;
_detailsElement.ContentHeight = newValue;
// Calling this when details are not visible invalidates during layout when we have no work
// to do. In certain scenarios, this could cause a layout cycle
OnRowDetailsChanged();
}
}
}
else
{
_previousDetailsHeight = newValue;
}
}
private void DetailsContent_SizeChanged(Rect newValue)
{
DetailsContent_HeightChanged(newValue.Height);
}
private void DetailsContent_MarginChanged(Thickness newValue)
{
if (_detailsContent != null)
DetailsContent_SizeChanged(_detailsContent.Bounds.Inflate(newValue));
}
private void DetailsContent_LayoutUpdated(object sender, EventArgs e)
{
if (_detailsContent != null)
{
var margin = _detailsContent.Margin;
var height = _detailsContent.DesiredSize.Height + margin.Top + margin.Bottom;
DetailsContent_HeightChanged(height);
}
}
//TODO Animation
// Sets AreDetailsVisible on the row and animates if necessary
internal void SetDetailsVisibilityInternal(bool isVisible, bool raiseNotification, bool animate)
{
Debug.Assert(OwningGrid != null);
Debug.Assert(Index != -1);
if (_appliedDetailsVisibility != isVisible)
{
if (_detailsElement == null)
{
if (raiseNotification)
{
_detailsVisibilityNotificationPending = true;
}
return;
}
_appliedDetailsVisibility = isVisible;
SetValueNoCallback(AreDetailsVisibleProperty, isVisible);
// Applies a new DetailsTemplate only if it has changed either here or at the DataGrid level
ApplyDetailsTemplate(initializeDetailsPreferredHeight: true);
// no template to show
if (_appliedDetailsTemplate == null)
{
if (_detailsElement.ContentHeight > 0)
{
_detailsElement.ContentHeight = 0;
}
return;
}
if (AreDetailsVisible)
{
// Set the details height directly
_detailsElement.ContentHeight = _detailsDesiredHeight;
_checkDetailsContentHeight = true;
}
else
{
_detailsElement.ContentHeight = 0;
}
OnRowDetailsChanged();
if (raiseNotification)
{
OwningGrid.OnRowDetailsVisibilityChanged(new DataGridRowDetailsEventArgs(this, _detailsContent));
}
}
}
internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
{
if (_detailsElement != null && AreDetailsVisible)
{
IDataTemplate oldDetailsTemplate = _appliedDetailsTemplate;
if (ActualDetailsTemplate != null && ActualDetailsTemplate != _appliedDetailsTemplate)
{
if (_detailsContent != null)
{
_detailsContentSizeSubscription?.Dispose();
_detailsContentSizeSubscription = null;
if (_detailsLoaded)
{
OwningGrid.OnUnloadingRowDetails(this, _detailsContent);
_detailsLoaded = false;
}
}
_detailsElement.Children.Clear();
_detailsContent = ActualDetailsTemplate.Build(DataContext);
_appliedDetailsTemplate = ActualDetailsTemplate;
if (_detailsContent != null)
{
if (_detailsContent is Layout.Layoutable layoutableContent)
{
layoutableContent.LayoutUpdated += DetailsContent_LayoutUpdated;
_detailsContentSizeSubscription = new CompositeDisposable(2)
{
Disposable.Create(() => layoutableContent.LayoutUpdated -= DetailsContent_LayoutUpdated),
_detailsContent.GetObservable(MarginProperty).Subscribe(DetailsContent_MarginChanged)
};
}
else
{
_detailsContentSizeSubscription =
_detailsContent.GetObservable(MarginProperty)
.Subscribe(DetailsContent_MarginChanged);
}
_detailsElement.Children.Add(_detailsContent);
}
}
if (_detailsContent != null && !_detailsLoaded)
{
_detailsLoaded = true;
_detailsContent.DataContext = DataContext;
OwningGrid.OnLoadingRowDetails(this, _detailsContent);
}
if (initializeDetailsPreferredHeight && double.IsNaN(_detailsDesiredHeight) &&
_appliedDetailsTemplate != null && _detailsElement.Children.Count > 0)
{
EnsureDetailsDesiredHeight();
}
else if (oldDetailsTemplate == null)
{
_detailsDesiredHeight = double.NaN;
EnsureDetailsDesiredHeight();
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == DataContextProperty)
{
var owner = OwningGrid;
if (owner != null && this.IsRecycled)
{
var columns = owner.ColumnsItemsInternal;
var nc = columns.Count;
for (int ci = 0; ci < nc; ci++)
{
if (columns[ci] is DataGridTemplateColumn column)
{
column.RefreshCellContent((Control)this.Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate));
}
}
}
}
base.OnPropertyChanged(change);
}
}
}