mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-28 14:20:47 +08:00
Add DataGridDisplayData
This commit is contained in:
@@ -3,11 +3,321 @@
|
||||
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
|
||||
// All other rights reserved.
|
||||
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal class DataGridDisplayData
|
||||
{
|
||||
public int FirstDisplayedScrollingCol { get; set; }
|
||||
public int FirstScrollingSlot { get; set; }
|
||||
public int LastScrollingSlot { get; set; }
|
||||
public int LastTotallyDisplayedScrollingCol { get; set; }
|
||||
public int NumDisplayedScrollingElements => _scrollingElements.Count;
|
||||
public int NumTotallyDisplayedScrollingElements { get; set; }
|
||||
internal double PendingVerticalScrollHeight { get; set; }
|
||||
|
||||
private Stack<DataGridRow> _fullyRecycledRows; // list of Rows that have been fully recycled (Collapsed)
|
||||
private int _headScrollingElements; // index of the row in _scrollingRows that is the first displayed row
|
||||
private DataGrid _owner;
|
||||
private Stack<DataGridRow> _recyclableRows; // list of Rows which have not been fully recycled (avoids Measure in several cases)
|
||||
private List<Control> _scrollingElements; // circular list of displayed elements
|
||||
private Stack<DataGridRowGroupHeader> _fullyRecycledGroupHeaders; // list of GroupHeaders that have been fully recycled (Collapsed)
|
||||
private Stack<DataGridRowGroupHeader> _recyclableGroupHeaders; // list of GroupHeaders which have not been fully recycled (avoids Measure in several cases)
|
||||
|
||||
public DataGridDisplayData(DataGrid owner)
|
||||
{
|
||||
_owner = owner;
|
||||
FirstDisplayedScrollingCol = -1;
|
||||
LastTotallyDisplayedScrollingCol = -1;
|
||||
|
||||
_scrollingElements = new List<Control>();
|
||||
_recyclableRows = new Stack<DataGridRow>();
|
||||
_fullyRecycledRows = new Stack<DataGridRow>();
|
||||
_recyclableGroupHeaders = new Stack<DataGridRowGroupHeader>();
|
||||
_fullyRecycledGroupHeaders = new Stack<DataGridRowGroupHeader>();
|
||||
}
|
||||
|
||||
internal void AddRecyclableRow(DataGridRow row)
|
||||
{
|
||||
Debug.Assert(!_recyclableRows.Contains(row));
|
||||
row.DetachFromDataGrid(true);
|
||||
_recyclableRows.Push(row);
|
||||
}
|
||||
|
||||
internal DataGridRowGroupHeader? GetUsedGroupHeader()
|
||||
{
|
||||
if (_recyclableGroupHeaders.Count > 0)
|
||||
{
|
||||
return _recyclableGroupHeaders.Pop();
|
||||
}
|
||||
if (_fullyRecycledGroupHeaders.Count > 0)
|
||||
{
|
||||
// For fully recycled rows, we need to set the Visibility back to Visible
|
||||
DataGridRowGroupHeader groupHeader = _fullyRecycledGroupHeaders.Pop();
|
||||
groupHeader.IsVisible = true;
|
||||
return groupHeader;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void AddRecyclableRowGroupHeader(DataGridRowGroupHeader groupHeader)
|
||||
{
|
||||
Debug.Assert(!_recyclableGroupHeaders.Contains(groupHeader));
|
||||
groupHeader.IsRecycled = true;
|
||||
_recyclableGroupHeaders.Push(groupHeader);
|
||||
}
|
||||
|
||||
internal void ClearElements(bool recycle)
|
||||
{
|
||||
ResetSlotIndexes();
|
||||
if (recycle)
|
||||
{
|
||||
foreach (Control element in _scrollingElements)
|
||||
{
|
||||
if (element is DataGridRow row)
|
||||
{
|
||||
if (row.IsRecyclable)
|
||||
{
|
||||
AddRecyclableRow(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
row.Clip = new RectangleGeometry();
|
||||
}
|
||||
}
|
||||
else if (element is DataGridRowGroupHeader groupHeader)
|
||||
{
|
||||
AddRecyclableRowGroupHeader(groupHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_recyclableRows.Clear();
|
||||
_fullyRecycledRows.Clear();
|
||||
_recyclableGroupHeaders.Clear();
|
||||
_fullyRecycledGroupHeaders.Clear();
|
||||
}
|
||||
_scrollingElements.Clear();
|
||||
}
|
||||
|
||||
internal void CorrectSlotsAfterDeletion(int slot, bool wasCollapsed)
|
||||
{
|
||||
if (wasCollapsed)
|
||||
{
|
||||
if (slot > FirstScrollingSlot)
|
||||
{
|
||||
LastScrollingSlot--;
|
||||
}
|
||||
}
|
||||
else if (_owner.IsSlotVisible(slot))
|
||||
{
|
||||
UnloadScrollingElement(slot, true /*updateSlotInformation*/, true /*wasDeleted*/);
|
||||
}
|
||||
// This cannot be an else condition because if there are 2 rows left, and you delete the first one
|
||||
// then these indexes need to be updated as well
|
||||
if (slot < FirstScrollingSlot)
|
||||
{
|
||||
FirstScrollingSlot--;
|
||||
LastScrollingSlot--;
|
||||
}
|
||||
}
|
||||
|
||||
internal void CorrectSlotsAfterInsertion(int slot, Control? element, bool isCollapsed)
|
||||
{
|
||||
if (slot < FirstScrollingSlot)
|
||||
{
|
||||
// The row was inserted above our viewport, just update our indexes
|
||||
FirstScrollingSlot++;
|
||||
LastScrollingSlot++;
|
||||
}
|
||||
else if (isCollapsed && (slot <= LastScrollingSlot))
|
||||
{
|
||||
LastScrollingSlot++;
|
||||
}
|
||||
else if ((_owner.GetPreviousVisibleSlot(slot) <= LastScrollingSlot) || (LastScrollingSlot == -1))
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
// The row was inserted in our viewport, add it as a scrolling row
|
||||
LoadScrollingSlot(slot, element, true /*updateSlotInformation*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetCircularListIndex(int slot, bool wrap)
|
||||
{
|
||||
int index = slot - FirstScrollingSlot - _headScrollingElements - _owner.GetCollapsedSlotCount(FirstScrollingSlot, slot);
|
||||
return wrap ? index % _scrollingElements.Count : index;
|
||||
}
|
||||
|
||||
internal void FullyRecycleElements()
|
||||
{
|
||||
// Fully recycle Recyclable rows and transfer them to Recycled rows
|
||||
while (_recyclableRows.Count > 0)
|
||||
{
|
||||
DataGridRow row = _recyclableRows.Pop();
|
||||
row.IsVisible = false;
|
||||
Debug.Assert(!_fullyRecycledRows.Contains(row));
|
||||
_fullyRecycledRows.Push(row);
|
||||
}
|
||||
// Fully recycle Recyclable GroupHeaders and transfer them to Recycled GroupHeaders
|
||||
while (_recyclableGroupHeaders.Count > 0)
|
||||
{
|
||||
DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop();
|
||||
groupHeader.IsVisible = false;
|
||||
Debug.Assert(!_fullyRecycledGroupHeaders.Contains(groupHeader));
|
||||
_fullyRecycledGroupHeaders.Push(groupHeader);
|
||||
}
|
||||
}
|
||||
|
||||
internal Control GetDisplayedElement(int slot)
|
||||
{
|
||||
Debug.Assert(slot >= FirstScrollingSlot);
|
||||
Debug.Assert(slot <= LastScrollingSlot);
|
||||
|
||||
return _scrollingElements[GetCircularListIndex(slot, true /*wrap*/)];
|
||||
}
|
||||
|
||||
internal DataGridRow? GetDisplayedRow(int rowIndex)
|
||||
{
|
||||
return GetDisplayedElement(_owner.SlotFromRowIndex(rowIndex)) as DataGridRow;
|
||||
}
|
||||
|
||||
// Returns an enumeration of the displayed scrolling rows in order starting with the FirstDisplayedScrollingRow
|
||||
internal IEnumerable<Control> GetScrollingElements()
|
||||
{
|
||||
return GetScrollingElements(null);
|
||||
}
|
||||
|
||||
internal IEnumerable<Control> GetScrollingElements(Predicate<object>? filter)
|
||||
{
|
||||
for (int i = 0; i < _scrollingElements.Count; i++)
|
||||
{
|
||||
Control element = _scrollingElements[(_headScrollingElements + i) % _scrollingElements.Count];
|
||||
if (filter == null || filter(element))
|
||||
{
|
||||
// _scrollingRows is a circular list that wraps
|
||||
yield return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<Control> GetScrollingRows()
|
||||
{
|
||||
return GetScrollingElements(element => element is DataGridRow);
|
||||
}
|
||||
|
||||
internal DataGridRow? GetUsedRow()
|
||||
{
|
||||
if (_recyclableRows.Count > 0)
|
||||
{
|
||||
return _recyclableRows.Pop();
|
||||
}
|
||||
if (_fullyRecycledRows.Count > 0)
|
||||
{
|
||||
// For fully recycled rows, we need to set the Visibility back to Visible
|
||||
DataGridRow row = _fullyRecycledRows.Pop();
|
||||
row.IsVisible = true;
|
||||
return row;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tracks the row at index rowIndex as a scrolling row
|
||||
internal void LoadScrollingSlot(int slot, Control element, bool updateSlotInformation)
|
||||
{
|
||||
if (_scrollingElements.Count == 0)
|
||||
{
|
||||
SetScrollingSlots(slot);
|
||||
_scrollingElements.Add(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The slot should be adjacent to the other slots being displayed
|
||||
Debug.Assert(slot >= _owner.GetPreviousVisibleSlot(FirstScrollingSlot) && slot <= _owner.GetNextVisibleSlot(LastScrollingSlot));
|
||||
if (updateSlotInformation)
|
||||
{
|
||||
if (slot < FirstScrollingSlot)
|
||||
{
|
||||
FirstScrollingSlot = slot;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastScrollingSlot = _owner.GetNextVisibleSlot(LastScrollingSlot);
|
||||
}
|
||||
}
|
||||
int insertIndex = GetCircularListIndex(slot, false /*wrap*/);
|
||||
if (insertIndex > _scrollingElements.Count)
|
||||
{
|
||||
// We need to wrap around from the bottom to the top of our circular list; as a result the head of the list moves forward
|
||||
insertIndex -= _scrollingElements.Count;
|
||||
_headScrollingElements++;
|
||||
}
|
||||
_scrollingElements.Insert(insertIndex, element);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSlotIndexes()
|
||||
{
|
||||
SetScrollingSlots(-1);
|
||||
NumTotallyDisplayedScrollingElements = 0;
|
||||
_headScrollingElements = 0;
|
||||
}
|
||||
|
||||
private void SetScrollingSlots(int newValue)
|
||||
{
|
||||
FirstScrollingSlot = newValue;
|
||||
LastScrollingSlot = newValue;
|
||||
}
|
||||
|
||||
// Stops tracking the element at the given slot as a scrolling element
|
||||
internal void UnloadScrollingElement(int slot, bool updateSlotInformation, bool wasDeleted)
|
||||
{
|
||||
Debug.Assert(_owner.IsSlotVisible(slot));
|
||||
int elementIndex = GetCircularListIndex(slot, false /*wrap*/);
|
||||
if (elementIndex > _scrollingElements.Count)
|
||||
{
|
||||
// We need to wrap around from the top to the bottom of our circular list
|
||||
elementIndex -= _scrollingElements.Count;
|
||||
_headScrollingElements--;
|
||||
}
|
||||
_scrollingElements.RemoveAt(elementIndex);
|
||||
|
||||
if (updateSlotInformation)
|
||||
{
|
||||
if (slot == FirstScrollingSlot && !wasDeleted)
|
||||
{
|
||||
FirstScrollingSlot = _owner.GetNextVisibleSlot(FirstScrollingSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
LastScrollingSlot = _owner.GetPreviousVisibleSlot(LastScrollingSlot);
|
||||
}
|
||||
if (LastScrollingSlot < FirstScrollingSlot)
|
||||
{
|
||||
ResetSlotIndexes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
internal void PrintDisplay()
|
||||
{
|
||||
foreach (Control element in GetScrollingElements())
|
||||
{
|
||||
if (element is DataGridRow row)
|
||||
{
|
||||
Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Slot: {0} Row: {1} ", row.Slot, row.Index));
|
||||
}
|
||||
else if (element is DataGridRowGroupHeader groupHeader)
|
||||
{
|
||||
Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Slot: {0} GroupHeader: {1}", groupHeader.RowGroupInfo?.Slot, groupHeader.RowGroupInfo?.CollectionViewGroup?.Key));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user