// (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.Data; using Avalonia.Interactivity; using Avalonia.VisualTree; using Avalonia.Collections; using Avalonia.Utilities; using System; using System.ComponentModel; using System.Linq; using System.Diagnostics; using Avalonia.Controls.Templates; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Markup.Xaml.MarkupExtensions; namespace Avalonia.Controls { public abstract class DataGridColumn : AvaloniaObject { internal const int DATAGRIDCOLUMN_maximumWidth = 65536; private const bool DATAGRIDCOLUMN_defaultIsReadOnly = false; private DataGridLength? _width; // Null by default, null means inherit the Width from the DataGrid private bool? _isReadOnly; private double? _maxWidth; private double? _minWidth; private bool _settingWidthInternally; private int _displayIndexWithFiller; private object _header; private IDataTemplate _headerTemplate; private DataGridColumnHeader _headerCell; private IControl _editingElement; private ICellEditBinding _editBinding; private IBinding _clipboardContentBinding; private readonly Classes _cellStyleClasses = new Classes(); /// /// Initializes a new instance of the class. /// protected internal DataGridColumn() { _displayIndexWithFiller = -1; IsInitialDesiredWidthDetermined = false; InheritsWidth = true; } internal DataGrid OwningGrid { get; set; } internal int Index { get; set; } internal bool? CanUserReorderInternal { get; set; } internal bool? CanUserResizeInternal { get; set; } internal bool? CanUserSortInternal { get; set; } internal bool ActualCanUserResize { get { if (OwningGrid == null || OwningGrid.CanUserResizeColumns == false || this is DataGridFillerColumn) { return false; } return CanUserResizeInternal ?? true; } } // MaxWidth from local setting or DataGrid setting internal double ActualMaxWidth { get { return _maxWidth ?? OwningGrid?.MaxColumnWidth ?? double.PositiveInfinity; } } // MinWidth from local setting or DataGrid setting internal double ActualMinWidth { get { double minWidth = _minWidth ?? OwningGrid?.MinColumnWidth ?? 0; if (Width.IsStar) { return Math.Max(DataGrid.DATAGRID_minimumStarColumnWidth, minWidth); } return minWidth; } } internal bool DisplayIndexHasChanged { get; set; } internal int DisplayIndexWithFiller { get { return _displayIndexWithFiller; } set { _displayIndexWithFiller = value; } } internal bool HasHeaderCell { get { return _headerCell != null; } } internal DataGridColumnHeader HeaderCell { get { if (_headerCell == null) { _headerCell = CreateHeader(); } return _headerCell; } } /// /// Tracks whether or not this column inherits its Width value from the DataGrid. /// internal bool InheritsWidth { get; private set; } /// /// When a column is initially added, we won't know its initial desired value /// until all rows have been measured. We use this variable to track whether or /// not the column has been fully measured. /// internal bool IsInitialDesiredWidthDetermined { get; set; } internal double LayoutRoundedWidth { get; private set; } internal ICellEditBinding CellEditBinding { get => _editBinding; } /// /// Defines the property. /// public static StyledProperty IsVisibleProperty = Control.IsVisibleProperty.AddOwner(); /// /// Determines whether or not this column is visible. /// public bool IsVisible { get => GetValue(IsVisibleProperty); set => SetValue(IsVisibleProperty, value); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == IsVisibleProperty) { OwningGrid?.OnColumnVisibleStateChanging(this); var isVisible = change.GetNewValue(); if (_headerCell != null) { _headerCell.IsVisible = isVisible; } OwningGrid?.OnColumnVisibleStateChanged(this); NotifyPropertyChanged(change.Property.Name); } } /// /// Actual visible width after Width, MinWidth, and MaxWidth setting at the Column level and DataGrid level /// have been taken into account /// public double ActualWidth { get { if (OwningGrid == null || double.IsNaN(Width.DisplayValue)) { return ActualMinWidth; } return Width.DisplayValue; } } /// /// Gets or sets a value that indicates whether the user can change the column display position by /// dragging the column header. /// /// /// true if the user can drag the column header to a new position; otherwise, false. The default is the current property value. /// public bool CanUserReorder { get { return CanUserReorderInternal ?? OwningGrid?.CanUserReorderColumns ?? DataGrid.DATAGRID_defaultCanUserResizeColumns; } set { CanUserReorderInternal = value; } } /// /// Gets or sets a value that indicates whether the user can adjust the column width using the mouse. /// /// /// true if the user can resize the column; false if the user cannot resize the column. The default is the current property value. /// public bool CanUserResize { get { return CanUserResizeInternal ?? OwningGrid?.CanUserResizeColumns ?? DataGrid.DATAGRID_defaultCanUserResizeColumns; } set { CanUserResizeInternal = value; OwningGrid?.OnColumnCanUserResizeChanged(this); } } /// /// Gets or sets a value that indicates whether the user can sort the column by clicking the column header. /// /// /// true if the user can sort the column; false if the user cannot sort the column. The default is the current property value. /// public bool CanUserSort { get { if (CanUserSortInternal.HasValue) { return CanUserSortInternal.Value; } else if (OwningGrid != null) { string propertyPath = GetSortPropertyName(); Type propertyType = OwningGrid.DataConnection.DataType.GetNestedPropertyType(propertyPath); // if the type is nullable, then we will compare the non-nullable type if (TypeHelper.IsNullableType(propertyType)) { propertyType = TypeHelper.GetNonNullableType(propertyType); } // return whether or not the property type can be compared return (typeof(IComparable).IsAssignableFrom(propertyType)) ? true : false; } else { return DataGrid.DATAGRID_defaultCanUserSortColumns; } } set { CanUserSortInternal = value; } } /// /// Gets or sets the display position of the column relative to the other columns in the . /// /// /// The zero-based position of the column as it is displayed in the associated . The default is the index of the corresponding in the collection. /// /// /// When setting this property, the specified value is less than -1 or equal to . /// /// -or- /// /// When setting this property on a column in a , the specified value is less than zero or greater than or equal to the number of columns in the . /// /// /// When setting this property, the is already making adjustments. For example, this exception is thrown when you attempt to set in a event handler. /// /// -or- /// /// When setting this property, the specified value would result in a frozen column being displayed in the range of unfrozen columns, or an unfrozen column being displayed in the range of frozen columns. /// public int DisplayIndex { get { if (OwningGrid != null && OwningGrid.ColumnsInternal.RowGroupSpacerColumn.IsRepresented) { return _displayIndexWithFiller - 1; } else { return _displayIndexWithFiller; } } set { if (value == Int32.MaxValue) { throw DataGridError.DataGrid.ValueMustBeLessThan(nameof(value), nameof(DisplayIndex), Int32.MaxValue); } if (OwningGrid != null) { if (OwningGrid.ColumnsInternal.RowGroupSpacerColumn.IsRepresented) { value++; } if (_displayIndexWithFiller != value) { if (value < 0 || value >= OwningGrid.ColumnsItemsInternal.Count) { throw DataGridError.DataGrid.ValueMustBeBetween(nameof(value), nameof(DisplayIndex), 0, true, OwningGrid.Columns.Count, false); } // Will throw an error if a visible frozen column is placed inside a non-frozen area or vice-versa. OwningGrid.OnColumnDisplayIndexChanging(this, value); _displayIndexWithFiller = value; try { OwningGrid.InDisplayIndexAdjustments = true; OwningGrid.OnColumnDisplayIndexChanged(this); OwningGrid.OnColumnDisplayIndexChanged_PostNotification(); } finally { OwningGrid.InDisplayIndexAdjustments = false; } } } else { if (value < -1) { throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo(nameof(value), nameof(DisplayIndex), -1); } _displayIndexWithFiller = value; } } } public Classes CellStyleClasses { get => _cellStyleClasses; set { if(_cellStyleClasses != value) { _cellStyleClasses.Replace(value); } } } /// /// Backing field for Header property /// public static readonly DirectProperty HeaderProperty = AvaloniaProperty.RegisterDirect( nameof(Header), o => o.Header, (o, v) => o.Header = v); /// /// Gets or sets the content /// public object Header { get { return _header; } set { SetAndRaise(HeaderProperty, ref _header, value); } } /// /// Backing field for Header property /// public static readonly DirectProperty HeaderTemplateProperty = AvaloniaProperty.RegisterDirect( nameof(HeaderTemplate), o => o.HeaderTemplate, (o, v) => o.HeaderTemplate = v); /// /// Gets or sets an for the /// public IDataTemplate HeaderTemplate { get { return _headerTemplate; } set { SetAndRaise(HeaderTemplateProperty, ref _headerTemplate, value); } } public bool IsAutoGenerated { get; internal set; } public bool IsFrozen { get; internal set; } public virtual bool IsReadOnly { get { if (OwningGrid == null) { return _isReadOnly ?? DATAGRIDCOLUMN_defaultIsReadOnly; } if (_isReadOnly != null) { return _isReadOnly.Value || OwningGrid.IsReadOnly; } return OwningGrid.GetColumnReadOnlyState(this, DATAGRIDCOLUMN_defaultIsReadOnly); } set { if (value != _isReadOnly) { OwningGrid?.OnColumnReadOnlyStateChanging(this, value); _isReadOnly = value; } } } public double MaxWidth { get { return _maxWidth ?? double.PositiveInfinity; } set { if (value < 0) { throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo("value", "MaxWidth", 0); } if (value < ActualMinWidth) { throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo("value", "MaxWidth", "MinWidth"); } if (!_maxWidth.HasValue || _maxWidth.Value != value) { double oldValue = ActualMaxWidth; _maxWidth = value; if (OwningGrid != null && OwningGrid.ColumnsInternal != null) { OwningGrid.OnColumnMaxWidthChanged(this, oldValue); } } } } public double MinWidth { get { return _minWidth ?? 0; } set { if (double.IsNaN(value)) { throw DataGridError.DataGrid.ValueCannotBeSetToNAN("MinWidth"); } if (value < 0) { throw DataGridError.DataGrid.ValueMustBeGreaterThanOrEqualTo("value", "MinWidth", 0); } if (double.IsPositiveInfinity(value)) { throw DataGridError.DataGrid.ValueCannotBeSetToInfinity("MinWidth"); } if (value > ActualMaxWidth) { throw DataGridError.DataGrid.ValueMustBeLessThanOrEqualTo("value", "MinWidth", "MaxWidth"); } if (!_minWidth.HasValue || _minWidth.Value != value) { double oldValue = ActualMinWidth; _minWidth = value; if (OwningGrid != null && OwningGrid.ColumnsInternal != null) { OwningGrid.OnColumnMinWidthChanged(this, oldValue); } } } } public DataGridLength Width { get { return _width ?? OwningGrid?.ColumnWidth ?? // We don't have a good choice here because we don't want to make this property nullable, see DevDiv Bugs 196581 DataGridLength.Auto; } set { if (!_width.HasValue || _width.Value != value) { if (!_settingWidthInternally) { InheritsWidth = false; } if (OwningGrid != null) { DataGridLength width = CoerceWidth(value); if (width.IsStar != Width.IsStar) { // If a column has changed either from or to a star value, we want to recalculate all // star column widths. They are recalculated during Measure based off what the value we set here. SetWidthInternalNoCallback(width); IsInitialDesiredWidthDetermined = false; OwningGrid.OnColumnWidthChanged(this); } else { // If a column width's value is simply changing, we resize it (to the right only). Resize(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue, false); } } else { SetWidthInternalNoCallback(value); } } } } /// /// The binding that will be used to get or set cell content for the clipboard. /// public virtual IBinding ClipboardContentBinding { get { return _clipboardContentBinding; } set { _clipboardContentBinding = value; } } /// /// Gets the value of a cell according to the the specified binding. /// /// The item associated with a cell. /// The binding to get the value of. /// The resultant cell value. internal object GetCellValue(object item, IBinding binding) { Debug.Assert(OwningGrid != null); object content = null; if (binding != null) { OwningGrid.ClipboardContentControl.DataContext = item; var sub = OwningGrid.ClipboardContentControl.Bind(ContentControl.ContentProperty, binding); content = OwningGrid.ClipboardContentControl.GetValue(ContentControl.ContentProperty); sub.Dispose(); } return content; } public IControl GetCellContent(DataGridRow dataGridRow) { Contract.Requires(dataGridRow != null); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); } if (dataGridRow.OwningGrid == OwningGrid) { DataGridCell dataGridCell = dataGridRow.Cells[Index]; if (dataGridCell != null) { return dataGridCell.Content as IControl; } } return null; } public IControl GetCellContent(object dataItem) { Contract.Requires(dataItem != null); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); } DataGridRow dataGridRow = OwningGrid.GetRowFromItem(dataItem); if (dataGridRow == null) { return null; } return GetCellContent(dataGridRow); } /// /// Returns the column which contains the given element /// /// element contained in a column /// Column that contains the element, or null if not found /// public static DataGridColumn GetColumnContainingElement(IControl element) { // Walk up the tree to find the DataGridCell or DataGridColumnHeader that contains the element IVisual parent = element; while (parent != null) { if (parent is DataGridCell cell) { return cell.OwningColumn; } if (parent is DataGridColumnHeader columnHeader) { return columnHeader.OwningColumn; } parent = parent.GetVisualParent(); } return null; } /// /// Clears the current sort direction /// public void ClearSort() { //InvokeProcessSort is already validating if sorting is possible _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier()); } /// /// Switches the current state of sort direction /// public void Sort() { //InvokeProcessSort is already validating if sorting is possible _headerCell?.InvokeProcessSort(Input.KeyModifiers.None); } /// /// Changes the sort direction of this column /// /// New sort direction public void Sort(ListSortDirection direction) { //InvokeProcessSort is already validating if sorting is possible _headerCell?.InvokeProcessSort(Input.KeyModifiers.None, direction); } /// /// When overridden in a derived class, causes the column cell being edited to revert to the unedited value. /// /// /// The element that the column displays for a cell in editing mode. /// /// /// The previous, unedited value in the cell being edited. /// protected virtual void CancelCellEdit(IControl editingElement, object uneditedValue) { } /// /// When overridden in a derived class, gets an editing element that is bound to the column's property value. /// /// /// The cell that will contain the generated element. /// /// /// The data item represented by the row that contains the intended cell. /// /// When the method returns, contains the applied binding. /// /// A new editing element that is bound to the column's property value. /// protected abstract IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding); /// /// When overridden in a derived class, gets a read-only element that is bound to the column's /// property value. /// /// /// The cell that will contain the generated element. /// /// /// The data item represented by the row that contains the intended cell. /// /// /// A new, read-only element that is bound to the column's property value. /// protected abstract IControl GenerateElement(DataGridCell cell, object dataItem); /// /// Called by a specific column type when one of its properties changed, /// and its current cells need to be updated. /// /// Indicates which property changed and caused this call protected void NotifyPropertyChanged(string propertyName) { OwningGrid?.RefreshColumnElements(this, propertyName); } /// /// When overridden in a derived class, called when a cell in the column enters editing mode. /// /// /// The element that the column displays for a cell in editing mode. /// /// /// Information about the user gesture that is causing a cell to enter editing mode. /// /// /// The unedited value. /// protected abstract object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs); /// /// Called by the DataGrid control when a column asked for its /// elements to be refreshed, typically because one of its properties changed. /// /// Indicates the element that needs to be refreshed /// Indicates which property changed and caused this call protected internal virtual void RefreshCellContent(IControl element, string propertyName) { } internal void CancelCellEditInternal(IControl editingElement, object uneditedValue) { CancelCellEdit(editingElement, uneditedValue); } /// /// Coerces a DataGridLength to a valid value. If any value components are double.NaN, this method /// coerces them to a proper initial value. For star columns, the desired width is calculated based /// on the rest of the star columns. For pixel widths, the desired value is based on the pixel value. /// For auto widths, the desired value is initialized as the column's minimum width. /// /// The DataGridLength to coerce. /// The resultant (coerced) DataGridLength. internal DataGridLength CoerceWidth(DataGridLength width) { double desiredValue = width.DesiredValue; if (double.IsNaN(desiredValue)) { if (width.IsStar && OwningGrid != null && OwningGrid.ColumnsInternal != null) { double totalStarValues = 0; double totalStarDesiredValues = 0; double totalNonStarDisplayWidths = 0; foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != this && !double.IsNaN(c.Width.DesiredValue))) { if (column.Width.IsStar) { totalStarValues += column.Width.Value; totalStarDesiredValues += column.Width.DesiredValue; } else { totalNonStarDisplayWidths += column.ActualWidth; } } if (totalStarValues == 0) { // Compute the new star column's desired value based on the available space if there are no other visible star columns desiredValue = Math.Max(ActualMinWidth, OwningGrid.CellsWidth - totalNonStarDisplayWidths); } else { // Otherwise, compute its desired value based on those of other visible star columns desiredValue = totalStarDesiredValues * width.Value / totalStarValues; } } else if (width.IsAbsolute) { desiredValue = width.Value; } else { desiredValue = ActualMinWidth; } } double displayValue = width.DisplayValue; if (double.IsNaN(displayValue)) { displayValue = desiredValue; } displayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); return new DataGridLength(width.Value, width.UnitType, desiredValue, displayValue); } /// /// If the DataGrid is using layout rounding, the pixel snapping will force all widths to /// whole numbers. Since the column widths aren't visual elements, they don't go through the normal /// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some /// pixel gaps and/or overlaps between columns. /// /// internal void ComputeLayoutRoundedWidth(double leftEdge) { if (OwningGrid != null && OwningGrid.UseLayoutRounding) { var scale = LayoutHelper.GetLayoutScale(HeaderCell); var roundSize = LayoutHelper.RoundLayoutSizeUp(new Size(leftEdge + ActualWidth, 1), scale, scale); LayoutRoundedWidth = roundSize.Width - leftEdge; } else { LayoutRoundedWidth = ActualWidth; } } //TODO Styles internal virtual DataGridColumnHeader CreateHeader() { var result = new DataGridColumnHeader { OwningColumn = this }; result[!ContentControl.ContentProperty] = this[!HeaderProperty]; result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; //result.EnsureStyle(null); return result; } /// /// Ensures that this column's width has been coerced to a valid value. /// internal void EnsureWidth() { SetWidthInternalNoCallback(CoerceWidth(Width)); } internal IControl GenerateElementInternal(DataGridCell cell, object dataItem) { return GenerateElement(cell, dataItem); } internal object PrepareCellForEditInternal(IControl editingElement, RoutedEventArgs editingEventArgs) { var result = PrepareCellForEdit(editingElement, editingEventArgs); editingElement.Focus(); return result; } /// /// Attempts to resize the column's width to the desired DisplayValue, but limits the final size /// to the column's minimum and maximum values. If star sizing is being used, then the column /// can only decrease in size by the amount that the columns after it can increase in size. /// Likewise, the column can only increase in size if other columns can spare the width. /// /// The new Value. /// The new UnitType. /// The new DesiredValue. /// The new DisplayValue. /// Whether or not this resize was initiated by a user action. internal void Resize(double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue, bool userInitiated) { double newValue = value; double newDesiredValue = desiredValue; double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); DataGridLengthUnitType newUnitType = unitType; int starColumnsCount = 0; double totalDisplayWidth = 0; foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns()) { column.EnsureWidth(); totalDisplayWidth += column.ActualWidth; starColumnsCount += (column != this && column.Width.IsStar) ? 1 : 0; } bool hasInfiniteAvailableWidth = !OwningGrid.RowsPresenterAvailableSize.HasValue || double.IsPositiveInfinity(OwningGrid.RowsPresenterAvailableSize.Value.Width); // If we're using star sizing, we can only resize the column as much as the columns to the // right will allow (i.e. until they hit their max or min widths). if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (unitType == DataGridLengthUnitType.Star && Width.IsStar && userInitiated))) { double limitedDisplayValue = Width.DisplayValue; double availableIncrease = Math.Max(0, OwningGrid.CellsWidth - totalDisplayWidth); double desiredChange = newDisplayValue - Width.DisplayValue; if (desiredChange > availableIncrease) { // The desired change is greater than the amount of available space, // so we need to decrease the widths of columns to the right to make room. desiredChange -= availableIncrease; double actualChange = desiredChange + OwningGrid.DecreaseColumnWidths(DisplayIndex + 1, -desiredChange, userInitiated); limitedDisplayValue += availableIncrease + actualChange; } else if (desiredChange > 0) { // The desired change is positive but less than the amount of available space, // so there's no need to decrease the widths of columns to the right. limitedDisplayValue += desiredChange; } else { // The desired change is negative, so we need to increase the widths of columns to the right. limitedDisplayValue += desiredChange + OwningGrid.IncreaseColumnWidths(DisplayIndex + 1, -desiredChange, userInitiated); } if (ActualCanUserResize || (Width.IsStar && !userInitiated)) { newDisplayValue = limitedDisplayValue; } } if (userInitiated) { newDesiredValue = newDisplayValue; if (!Width.IsStar) { InheritsWidth = false; newValue = newDisplayValue; newUnitType = DataGridLengthUnitType.Pixel; } else if (starColumnsCount > 0 && !hasInfiniteAvailableWidth) { // Recalculate star weight of this column based on the new desired value InheritsWidth = false; newValue = (Width.Value * newDisplayValue) / ActualWidth; } } DataGridLength oldWidth = Width; SetWidthInternalNoCallback(new DataGridLength(Math.Min(double.MaxValue, newValue), newUnitType, newDesiredValue, newDisplayValue)); if (Width != oldWidth) { OwningGrid.OnColumnWidthChanged(this); } } /// /// Sets the column's Width to a new DataGridLength with a different DesiredValue. /// /// The new DesiredValue. internal void SetWidthDesiredValue(double desiredValue) { SetWidthInternalNoCallback(new DataGridLength(Width.Value, Width.UnitType, desiredValue, Width.DisplayValue)); } /// /// Sets the column's Width to a new DataGridLength with a different DisplayValue. /// /// The new DisplayValue. internal void SetWidthDisplayValue(double displayValue) { SetWidthInternalNoCallback(new DataGridLength(Width.Value, Width.UnitType, Width.DesiredValue, displayValue)); } /// /// Set the column's Width without breaking inheritance. /// /// The new Width. internal void SetWidthInternal(DataGridLength width) { bool originalValue = _settingWidthInternally; _settingWidthInternally = true; try { Width = width; } finally { _settingWidthInternally = originalValue; } } /// /// Sets the column's Width directly, without any callback effects. /// /// The new Width. internal void SetWidthInternalNoCallback(DataGridLength width) { _width = width; } /// /// Set the column's star value. Whenever the star value changes, width inheritance is broken. /// /// The new star value. internal void SetWidthStarValue(double value) { InheritsWidth = false; SetWidthInternalNoCallback(new DataGridLength(value, Width.UnitType, Width.DesiredValue, Width.DisplayValue)); } //TODO Binding internal IControl GenerateEditingElementInternal(DataGridCell cell, object dataItem) { if (_editingElement == null) { _editingElement = GenerateEditingElement(cell, dataItem, out _editBinding); } return _editingElement; } /// /// Clears the cached editing element. /// //TODO Binding internal void RemoveEditingElement() { _editingElement = null; } /// /// Holds the name of the member to use for sorting, if not using the default. /// public string SortMemberPath { get; set; } /// /// Holds a Comparer to use for sorting, if not using the default. /// public System.Collections.IComparer CustomSortComparer { get; set; } /// /// We get the sort description from the data source. We don't worry whether we can modify sort -- perhaps the sort description /// describes an unchangeable sort that exists on the data. /// internal DataGridSortDescription GetSortDescription() { if (OwningGrid != null && OwningGrid.DataConnection != null && OwningGrid.DataConnection.SortDescriptions != null) { if(CustomSortComparer != null) { return OwningGrid.DataConnection.SortDescriptions .OfType() .FirstOrDefault(s => s.SourceComparer == CustomSortComparer); } string propertyName = GetSortPropertyName(); return OwningGrid.DataConnection.SortDescriptions.FirstOrDefault(s => s.HasPropertyPath && s.PropertyPath == propertyName); } return null; } internal string GetSortPropertyName() { string result = SortMemberPath; if (String.IsNullOrEmpty(result)) { if (this is DataGridBoundColumn boundColumn) { if (boundColumn.Binding is Binding binding) { result = binding.Path; } else if (boundColumn.Binding is CompiledBindingExtension compiledBinding) { result = compiledBinding.Path.ToString(); } } } return result; } } }