csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
13 KiB
365 lines
13 KiB
// (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.Media;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
|
|
namespace Avalonia.Controls
|
|
{
|
|
internal class DataGridDisplayData
|
|
{
|
|
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;
|
|
|
|
ResetSlotIndexes();
|
|
FirstDisplayedScrollingCol = -1;
|
|
LastTotallyDisplayedScrollingCol = -1;
|
|
|
|
_scrollingElements = new List<Control>();
|
|
_recyclableRows = new Stack<DataGridRow>();
|
|
_fullyRecycledRows = new Stack<DataGridRow>();
|
|
_recyclableGroupHeaders = new Stack<DataGridRowGroupHeader>();
|
|
_fullyRecycledGroupHeaders = new Stack<DataGridRowGroupHeader>();
|
|
}
|
|
|
|
public int FirstDisplayedScrollingCol
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public int FirstScrollingSlot
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public int LastScrollingSlot
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public int LastTotallyDisplayedScrollingCol
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public int NumDisplayedScrollingElements
|
|
{
|
|
get
|
|
{
|
|
return _scrollingElements.Count;
|
|
}
|
|
}
|
|
|
|
public int NumTotallyDisplayedScrollingElements
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
internal double PendingVerticalScrollHeight
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
internal void AddRecylableRow(DataGridRow row)
|
|
{
|
|
Debug.Assert(!_recyclableRows.Contains(row));
|
|
row.DetachFromDataGrid(true);
|
|
_recyclableRows.Push(row);
|
|
}
|
|
|
|
internal DataGridRowGroupHeader GetUsedGroupHeader()
|
|
{
|
|
if (_recyclableGroupHeaders.Count > 0)
|
|
{
|
|
return _recyclableGroupHeaders.Pop();
|
|
}
|
|
else 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 AddRecylableRowGroupHeader(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)
|
|
{
|
|
AddRecylableRow(row);
|
|
}
|
|
else
|
|
{
|
|
row.Clip = new RectangleGeometry();
|
|
}
|
|
}
|
|
else if (element is DataGridRowGroupHeader groupHeader)
|
|
{
|
|
AddRecylableRowGroupHeader(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))
|
|
{
|
|
Debug.Assert(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 Recycleable rows and transfer them to Recycled rows
|
|
while (_recyclableRows.Count > 0)
|
|
{
|
|
DataGridRow row = _recyclableRows.Pop();
|
|
Debug.Assert(row != null);
|
|
row.IsVisible = false;
|
|
Debug.Assert(!_fullyRecycledRows.Contains(row));
|
|
_fullyRecycledRows.Push(row);
|
|
}
|
|
// Fully recycle Recycleable GroupHeaders and transfer them to Recycled GroupHeaders
|
|
while (_recyclableGroupHeaders.Count > 0)
|
|
{
|
|
DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop();
|
|
Debug.Assert(groupHeader != null);
|
|
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();
|
|
}
|
|
else 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
|
|
}
|
|
}
|
|
|