Files
AtomUI/src/AtomUI.Controls.DataGrid/DataGridColumnCollection.cs
T
2025-07-13 22:13:41 +08:00

521 lines
21 KiB
C#

// (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 System.Collections.ObjectModel;
using System.Diagnostics;
namespace AtomUI.Controls;
internal class DataGridColumnCollection : ObservableCollection<DataGridColumn>
{
internal int AutoGeneratedColumnCount { get; set; }
internal List<int> DisplayIndexMap { get; set; }
internal DataGridFillerColumn? FillerColumn { get; private set; }
internal DataGridColumn? FirstColumn => GetFirstColumn(null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
internal DataGridColumn? FirstVisibleColumn => GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
internal DataGridColumn? FirstVisibleNonFillerColumn
{
get
{
DataGridColumn? dataGridColumn = FirstVisibleColumn;
if (dataGridColumn != null && dataGridColumn == RowGroupSpacerColumn)
{
dataGridColumn = GetNextVisibleColumn(dataGridColumn);
}
return dataGridColumn;
}
}
internal DataGridColumn? FirstVisibleWritableColumn => GetFirstColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
internal DataGridColumn? FirstVisibleScrollingColumn => GetFirstColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
internal List<DataGridColumn> ItemsInternal { get; private set; }
internal DataGridColumn? LastVisibleColumn => GetLastColumn(true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
internal DataGridColumn? LastVisibleScrollingColumn => GetLastColumn(true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
internal DataGridColumn? LastVisibleWritableColumn => GetLastColumn(true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
internal DataGridFillerColumn? RowGroupSpacerColumn { get; private set; }
internal int VisibleColumnCount { get; private set; }
internal double VisibleEdgedColumnsWidth { get; private set; }
/// <summary>
/// The number of star columns that are currently visible.
/// NOTE: Requires that EnsureVisibleEdgedColumnsWidth has been called.
/// </summary>
internal int VisibleStarColumnCount { get; private set; }
internal int AutogeneratedColumnCount { get; set; }
private readonly DataGrid _owningGrid;
public DataGridColumnCollection(DataGrid owningGrid)
{
_owningGrid = owningGrid;
DisplayIndexMap = new List<int>();
ItemsInternal = new List<DataGridColumn>();
FillerColumn = new DataGridFillerColumn(owningGrid);
RowGroupSpacerColumn = new DataGridFillerColumn(owningGrid);
}
protected override void ClearItems()
{
try
{
_owningGrid.NoCurrentCellChangeCount++;
if (ItemsInternal.Count > 0)
{
if (_owningGrid.InDisplayIndexAdjustments)
{
// We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
}
_owningGrid.HandleClearingColumns();
for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
{
// Detach the column...
ItemsInternal[columnIndex].OwningGrid = null;
}
ItemsInternal.Clear();
DisplayIndexMap.Clear();
AutogeneratedColumnCount = 0;
_owningGrid.HandleColumnCollectionChangedPreNotification(false /*columnsGrew*/);
base.ClearItems();
VisibleEdgedColumnsWidth = 0;
_owningGrid.HandleColumnCollectionChangedPostNotification(false /*columnsGrew*/);
}
}
finally
{
_owningGrid.NoCurrentCellChangeCount--;
}
}
protected override void InsertItem(int columnIndex, DataGridColumn dataGridColumn)
{
try
{
_owningGrid.NoCurrentCellChangeCount++;
if (_owningGrid.InDisplayIndexAdjustments)
{
// We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
}
if (dataGridColumn == null)
{
throw new ArgumentNullException(nameof(dataGridColumn));
}
int columnIndexWithFiller = columnIndex;
if (dataGridColumn != RowGroupSpacerColumn && RowGroupSpacerColumn != null && RowGroupSpacerColumn.IsRepresented)
{
columnIndexWithFiller++;
}
// get the new current cell coordinates
DataGridCellCoordinates newCurrentCellCoordinates = _owningGrid.HandleInsertingColumn(columnIndex, dataGridColumn);
// insert the column into our internal list
ItemsInternal.Insert(columnIndexWithFiller, dataGridColumn);
dataGridColumn.Index = columnIndexWithFiller;
dataGridColumn.OwningGrid = _owningGrid;
dataGridColumn.RemoveEditingElement();
if (dataGridColumn.IsVisible)
{
VisibleEdgedColumnsWidth += dataGridColumn.ActualWidth;
}
// continue with the base insert
_owningGrid.HandleInsertedColumnPreNotification(dataGridColumn);
_owningGrid.HandleColumnCollectionChangedPreNotification(true /*columnsGrew*/);
if (dataGridColumn != RowGroupSpacerColumn)
{
base.InsertItem(columnIndex, dataGridColumn);
}
_owningGrid.HandleInsertedColumnPostNotification(newCurrentCellCoordinates, dataGridColumn.DisplayIndex);
_owningGrid.HandleColumnCollectionChangedPostNotification(true /*columnsGrew*/);
}
finally
{
_owningGrid.NoCurrentCellChangeCount--;
}
}
internal DataGridColumn? GetFirstColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
{
Debug.Assert(ItemsInternal != null);
Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
int index = 0;
while (index < DisplayIndexMap.Count)
{
DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
Debug.Assert(dataGridColumn != null);
if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
(isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
(isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
{
return dataGridColumn;
}
index++;
}
return null;
}
internal DataGridColumn? GetLastColumn(bool? isVisible, bool? isFrozen, bool? isReadOnly)
{
Debug.Assert(ItemsInternal != null);
Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
int index = DisplayIndexMap.Count - 1;
while (index >= 0)
{
DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
Debug.Assert(dataGridColumn != null);
if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
(isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
(isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
{
return dataGridColumn;
}
index--;
}
return null;
}
/// <summary>
/// In addition to ensuring that column widths are valid, method updates the values of
/// VisibleEdgedColumnsWidth and VisibleStarColumnCount.
/// </summary>
internal void EnsureVisibleEdgedColumnsWidth()
{
VisibleStarColumnCount = 0;
VisibleEdgedColumnsWidth = 0;
VisibleColumnCount = 0;
Debug.Assert(ItemsInternal != null);
for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
{
var item = ItemsInternal[columnIndex];
if (item.IsVisible)
{
VisibleColumnCount++;
item.EnsureWidth();
if (item.Width.IsStar)
{
VisibleStarColumnCount++;
}
VisibleEdgedColumnsWidth += item.ActualWidth;
}
}
}
internal DataGridColumn? GetColumnAtDisplayIndex(int displayIndex)
{
Debug.Assert(ItemsInternal != null);
if (displayIndex < 0 || displayIndex >= ItemsInternal.Count || displayIndex >= DisplayIndexMap.Count)
{
return null;
}
int columnIndex = DisplayIndexMap[displayIndex];
return ItemsInternal[columnIndex];
}
internal int GetColumnCount(bool isVisible, bool isFrozen, int fromColumnIndex, int toColumnIndex)
{
Debug.Assert(ItemsInternal != null);
int columnCount = 0;
DataGridColumn? dataGridColumn = ItemsInternal[fromColumnIndex];
// TODO 需要审查
while (dataGridColumn != null && dataGridColumn != ItemsInternal[toColumnIndex])
{
dataGridColumn = GetNextColumn(dataGridColumn, isVisible, isFrozen, null /*isReadOnly*/);
columnCount++;
}
return columnCount;
}
internal IEnumerable<DataGridColumn> GetDisplayedColumns()
{
Debug.Assert(ItemsInternal != null);
foreach (int columnIndex in DisplayIndexMap)
{
yield return ItemsInternal[columnIndex];
}
}
internal int GetDisplayedColumnCount()
{
Debug.Assert(ItemsInternal != null);
return DisplayIndexMap.Count;
}
/// <summary>
/// Returns an enumeration of all columns that meet the criteria of the filter predicate.
/// </summary>
/// <param name="filter">Criteria for inclusion.</param>
/// <returns>Columns that meet the criteria, in ascending DisplayIndex order.</returns>
internal IEnumerable<DataGridColumn> GetDisplayedColumns(Predicate<DataGridColumn> filter)
{
Debug.Assert(ItemsInternal != null);
Debug.Assert(filter != null);
Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
foreach (int columnIndex in DisplayIndexMap)
{
DataGridColumn column = ItemsInternal[columnIndex];
if (filter(column))
{
yield return column;
}
}
}
/// <summary>
/// Returns an enumeration of all columns that meet the criteria of the filter predicate.
/// The columns are returned in the order specified by the reverse flag.
/// </summary>
/// <param name="reverse">Whether or not to return the columns in descending DisplayIndex order.</param>
/// <param name="filter">Criteria for inclusion.</param>
/// <returns>Columns that meet the criteria, in the order specified by the reverse flag.</returns>
internal IEnumerable<DataGridColumn> GetDisplayedColumns(bool reverse, Predicate<DataGridColumn> filter)
{
return reverse ? GetDisplayedColumnsReverse(filter) : GetDisplayedColumns(filter);
}
/// <summary>
/// Returns an enumeration of all columns that meet the criteria of the filter predicate.
/// The columns are returned in descending DisplayIndex order.
/// </summary>
/// <param name="filter">Criteria for inclusion.</param>
/// <returns>Columns that meet the criteria, in descending DisplayIndex order.</returns>
internal IEnumerable<DataGridColumn> GetDisplayedColumnsReverse(Predicate<DataGridColumn> filter)
{
Debug.Assert(ItemsInternal != null);
for (int displayIndex = DisplayIndexMap.Count - 1; displayIndex >= 0; displayIndex--)
{
DataGridColumn column = ItemsInternal[DisplayIndexMap[displayIndex]];
if (filter(column))
{
yield return column;
}
}
}
internal DataGridColumn? GetNextColumn(DataGridColumn dataGridColumnStart)
{
return GetNextColumn(dataGridColumnStart, null /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
}
internal DataGridColumn? GetNextColumn(DataGridColumn dataGridColumnStart,
bool? isVisible, bool? isFrozen, bool? isReadOnly)
{
Debug.Assert(ItemsInternal != null);
Debug.Assert(dataGridColumnStart != null);
Debug.Assert(ItemsInternal.Contains(dataGridColumnStart));
Debug.Assert(ItemsInternal.Count == DisplayIndexMap.Count);
int index = dataGridColumnStart.DisplayIndexWithFiller + 1;
while (index < DisplayIndexMap.Count)
{
DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
Debug.Assert(dataGridColumn != null);
if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
(isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
(isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
{
return dataGridColumn;
}
index++;
}
return null;
}
internal DataGridColumn? GetNextVisibleColumn(DataGridColumn dataGridColumnStart)
{
return GetNextColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
}
internal DataGridColumn? GetNextVisibleFrozenColumn(DataGridColumn dataGridColumnStart)
{
return GetNextColumn(dataGridColumnStart, true /*isVisible*/, true /*isFrozen*/, null /*isReadOnly*/);
}
internal DataGridColumn? GetNextVisibleWritableColumn(DataGridColumn dataGridColumnStart)
{
return GetNextColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
}
internal DataGridColumn? GetPreviousColumn(DataGridColumn dataGridColumnStart,
bool? isVisible, bool? isFrozen, bool? isReadOnly)
{
int index = dataGridColumnStart.DisplayIndexWithFiller - 1;
while (index >= 0)
{
DataGridColumn? dataGridColumn = GetColumnAtDisplayIndex(index);
Debug.Assert(dataGridColumn != null);
if ((isVisible == null || (dataGridColumn.IsVisible) == isVisible) &&
(isFrozen == null || dataGridColumn.IsFrozen == isFrozen) &&
(isReadOnly == null || dataGridColumn.IsReadOnly == isReadOnly))
{
return dataGridColumn;
}
index--;
}
return null;
}
internal DataGridColumn? GetPreviousVisibleNonFillerColumn(DataGridColumn dataGridColumnStart)
{
DataGridColumn? column = GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, null /*isReadOnly*/);
return (column is DataGridFillerColumn) ? null : column;
}
internal DataGridColumn? GetPreviousVisibleScrollingColumn(DataGridColumn dataGridColumnStart)
{
return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, false /*isFrozen*/, null /*isReadOnly*/);
}
internal DataGridColumn? GetPreviousVisibleWritableColumn(DataGridColumn dataGridColumnStart)
{
return GetPreviousColumn(dataGridColumnStart, true /*isVisible*/, null /*isFrozen*/, false /*isReadOnly*/);
}
internal int GetVisibleColumnCount(int fromColumnIndex, int toColumnIndex)
{
Debug.Assert(ItemsInternal != null);
int columnCount = 0;
DataGridColumn? dataGridColumn = ItemsInternal[fromColumnIndex];
while (dataGridColumn != null && dataGridColumn != ItemsInternal[toColumnIndex])
{
dataGridColumn = GetNextVisibleColumn(dataGridColumn);
columnCount++;
}
return columnCount;
}
internal IEnumerable<DataGridColumn> GetVisibleColumns()
{
Predicate<DataGridColumn> filter = column => column.IsVisible;
return GetDisplayedColumns(filter);
}
internal IEnumerable<DataGridColumn> GetVisibleFrozenColumns()
{
Predicate<DataGridColumn> filter = column => column.IsVisible && column.IsFrozen;
return GetDisplayedColumns(filter);
}
internal double GetVisibleFrozenEdgedColumnsWidth()
{
Debug.Assert(ItemsInternal != null);
double visibleFrozenColumnsWidth = 0;
for (int columnIndex = 0; columnIndex < ItemsInternal.Count; columnIndex++)
{
if (ItemsInternal[columnIndex].IsVisible && ItemsInternal[columnIndex].IsFrozen)
{
visibleFrozenColumnsWidth += ItemsInternal[columnIndex].ActualWidth;
}
}
return visibleFrozenColumnsWidth;
}
internal IEnumerable<DataGridColumn> GetVisibleScrollingColumns()
{
Predicate<DataGridColumn> filter = column => column.IsVisible && !column.IsFrozen;
return GetDisplayedColumns(filter);
}
private void RemoveItemPrivate(int columnIndex, bool isSpacer)
{
try
{
_owningGrid.NoCurrentCellChangeCount++;
if (_owningGrid.InDisplayIndexAdjustments)
{
// We are within columns display indexes adjustments. We do not allow changing the column collection while adjusting display indexes.
throw DataGridError.DataGrid.CannotChangeColumnCollectionWhileAdjustingDisplayIndexes();
}
int columnIndexWithFiller = columnIndex;
if (!isSpacer && RowGroupSpacerColumn != null && RowGroupSpacerColumn.IsRepresented)
{
columnIndexWithFiller++;
}
Debug.Assert(ItemsInternal != null);
DataGridColumn dataGridColumn = ItemsInternal[columnIndexWithFiller];
DataGridCellCoordinates newCurrentCellCoordinates = _owningGrid.OnRemovingColumn(dataGridColumn);
ItemsInternal.RemoveAt(columnIndexWithFiller);
if (dataGridColumn.IsVisible)
{
VisibleEdgedColumnsWidth -= dataGridColumn.ActualWidth;
}
dataGridColumn.OwningGrid = null;
dataGridColumn.RemoveEditingElement();
// continue with the base remove
_owningGrid.HandleRemovedColumnPreNotification(dataGridColumn);
_owningGrid.HandleColumnCollectionChangedPreNotification(false /*columnsGrew*/);
if (!isSpacer)
{
base.RemoveItem(columnIndex);
}
_owningGrid.HandleRemovedColumnPostNotification(newCurrentCellCoordinates);
_owningGrid.HandleColumnCollectionChangedPostNotification(false /*columnsGrew*/);
}
finally
{
_owningGrid.NoCurrentCellChangeCount--;
}
}
protected override void RemoveItem(int columnIndex)
{
RemoveItemPrivate(columnIndex, false /*isSpacer*/);
}
protected override void SetItem(int columnIndex, DataGridColumn dataGridColumn)
{
RemoveItem(columnIndex);
InsertItem(columnIndex, dataGridColumn);
}
internal bool DisplayInOrder(int columnIndex1, int columnIndex2)
{
int displayIndex1 = ItemsInternal[columnIndex1].DisplayIndexWithFiller;
int displayIndex2 = ItemsInternal[columnIndex2].DisplayIndexWithFiller;
return displayIndex1 < displayIndex2;
}
internal bool EnsureRowGrouping(bool rowGrouping)
{
// The insert below could cause the first column to be added. That causes a refresh
// which re-enters method so instead of checking RowGroupSpacerColumn.IsRepresented,
// we need to check to see if it's actually in our collection instead.
bool spacerRepresented = (ItemsInternal.Count > 0) && (ItemsInternal[0] == RowGroupSpacerColumn);
Debug.Assert(RowGroupSpacerColumn != null);
if (rowGrouping && !spacerRepresented)
{
Insert(0, RowGroupSpacerColumn);
RowGroupSpacerColumn.IsRepresented = true;
return true;
}
if (!rowGrouping && spacerRepresented)
{
// We need to set IsRepresented to false before removing the RowGroupSpacerColumn
// otherwise, we'll remove the column after it
RowGroupSpacerColumn.IsRepresented = false;
RemoveItemPrivate(0, true /*isSpacer*/);
return true;
}
return false;
}
}