Fix DataGrid row reorder bug

This commit is contained in:
CHINBOY
2025-07-21 12:01:12 +08:00
parent bb02d1167f
commit a6bf551c7e
7 changed files with 100 additions and 112 deletions
@@ -1,7 +1,6 @@
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using AtomUI.Controls.Data;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
@@ -121,6 +120,11 @@ public sealed class DataGridRowReorderColumn : DataGridColumn
{
throw DataGridError.DataGridRow.RowReorderNotAllowedException();
}
// TODO 需否需要检查每一列的配置
if (owningGrid.CanUserFilterColumns || owningGrid.CanUserSortColumns)
{
throw DataGridError.DataGridRow.InvalidRowReorderPreConditionException();
}
owningGrid.PropertyChanged += HandleOwningGridItemsSourceChanged;
}
@@ -6,7 +6,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Threading;
namespace AtomUI.Controls;
@@ -26,9 +25,11 @@ internal class DataGridRowReorderHandle : TemplatedControl
private static Point? _dragStart;
private static double? _dragDelta;
private static DataGridRow? _dragRow;
private static DataGridRow? _prevDraggingOverRow;
private static DataGridRow? _currentDraggingOverRow;
private static Rect? _dragRowBounds;
private static int? _dragRowIndex;
private static int? _currentDraggingOverRowIndex;
private static Point? _lastMousePositionInPresenter;
private IconButton? _indicatorButton;
@@ -67,48 +68,45 @@ internal class DataGridRowReorderHandle : TemplatedControl
{
_indicatorButton.DisableTransitions();
}
if (_prevDraggingOverRow != null && _prevDraggingOverRow != OwningRow)
if (_currentDraggingOverRowIndex != null && _currentDraggingOverRowIndex != _dragRowIndex)
{
if (OwningGrid.CollectionView is IList collectionView)
{
var data = collectionView[OwningRow.Index];
if (data is not null)
if (_dragRowIndex != null)
{
collectionView.RemoveAt(OwningRow.Index);
collectionView.Insert(_prevDraggingOverRow.Index, data);
var data = collectionView[_dragRowIndex.Value];
if (data is not null)
{
collectionView.RemoveAt(_dragRowIndex.Value);
collectionView.Insert(_currentDraggingOverRowIndex.Value, data);
}
}
if (_indicatorButton != null)
{
_indicatorButton.EnableTransitions();
}
Dispatcher.UIThread.Post(() =>
{
OwningGrid.CollectionView.Refresh();
});
OwningGrid.CollectionView.Refresh();
}
}
OwningRow.Opacity = 1.0;
_dragDelta = null;
_dragRow = null;
_dragRowIndex = null;
_dragRowBounds = null;
_dragStart = null;
_lastMousePositionInPresenter = null;
_currentDraggingOverRow = null;
_prevDraggingOverRow = null;
rowsPresenter.DraggedRow = null;
_currentDraggingOverRowIndex = null;
rowsPresenter.DragRowOffset = 0.0;
rowsPresenter.InvalidateArrange();
}
private void HandlePointerMoved(object? sender, PointerEventArgs e)
{
if (OwningGrid == null || !IsEnabled || OwningGrid.ColumnHeaders == null)
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || OwningGrid == null || !IsEnabled || OwningGrid.ColumnHeaders == null)
{
return;
}
Point mousePositionPresenter = e.GetPosition(OwningGrid.RowsPresenter);
Debug.Assert(OwningGrid.Parent is InputElement);
HandleMouseMoveReorder(mousePositionPresenter);
}
@@ -122,38 +120,31 @@ internal class DataGridRowReorderHandle : TemplatedControl
var rowsPresenter = OwningGrid.RowsPresenter;
//handle entry into reorder mode
if (_dragRow == null && _lastMousePositionInPresenter != null)
if (_dragRowIndex == null && _lastMousePositionInPresenter != null)
{
HandleMouseMoveBeginReorder(mousePositionPresenter);
}
//handle reorder mode (eg, positioning of the popup)
if (rowsPresenter.DraggedRow != null)
if (rowsPresenter.DraggedRowIndex != null)
{
Debug.Assert(_dragStart != null);
// Find row we're hovering over
var targetRow = GetReorderingTargetRow(mousePositionPresenter, true, out double scrollAmount);
if (targetRow != _currentDraggingOverRow)
{
_prevDraggingOverRow = _currentDraggingOverRow;
_currentDraggingOverRow = targetRow;
if (_currentDraggingOverRow != null && _currentDraggingOverRow != _dragRow)
{
rowsPresenter.SwapOrderingChildren(OwningRow, _currentDraggingOverRow);
}
}
var targetRowIndex = GetReorderingTargetRow(mousePositionPresenter, true, out double scrollAmount);
_currentDraggingOverRowIndex = targetRowIndex;
if (_dragDelta == null)
{
_dragDelta = _dragRow?.Bounds.Y;
_dragDelta = _dragRowBounds?.Y;
}
var delta = _dragDelta.HasValue ? _dragDelta.Value : 0.0;
rowsPresenter.DragRowOffset = delta + mousePositionPresenter.Y - _dragStart.Value.Y + scrollAmount;
rowsPresenter.DraggedRowIndex = _dragRowIndex;
rowsPresenter.InvalidateArrange();
}
}
private DataGridRow? GetReorderingTargetRow(Point mousePositionPresenter, bool scroll, out double scrollAmount)
private int? GetReorderingTargetRow(Point mousePositionPresenter, bool scroll, out double scrollAmount)
{
scrollAmount = 0;
Debug.Assert(OwningGrid != null);
@@ -163,8 +154,8 @@ internal class DataGridRowReorderHandle : TemplatedControl
var rowsPresenter = OwningGrid.RowsPresenter;
double topEdge = rowsPresenter.Bounds.Top;
double bottomEdge = rowsPresenter.Bounds.Bottom;
double topEdge = 0;
double bottomEdge = rowsPresenter.DesiredSize.Height;
if (mousePositionPresenter.Y < topEdge)
{
@@ -187,23 +178,26 @@ internal class DataGridRowReorderHandle : TemplatedControl
OwningGrid.VerticalScrollBar.IsVisible &&
OwningGrid.VerticalScrollBar.Value < OwningGrid.VerticalScrollBar.Maximum)
{
double newVal = mousePositionPresenter.X - bottomEdge;
double newVal = mousePositionPresenter.Y - bottomEdge;
scrollAmount = Math.Min(newVal,
OwningGrid.VerticalScrollBar.Maximum - OwningGrid.VerticalScrollBar.Value);
OwningGrid.ScrollSlotsByHeight(scrollAmount);
if (scrollAmount > 0)
{
OwningGrid.ScrollSlotsByHeight(scrollAmount);
}
}
mousePositionPresenter = mousePositionPresenter.WithY(bottomEdge - 1);
}
foreach (Control child in rowsPresenter.Children)
foreach (Control child in OwningGrid.DisplayData.GetScrollingElements())
{
if (child is DataGridRow row)
{
Point mousePosition = OwningGrid.RowsPresenter.Translate(row, mousePositionPresenter);
if (mousePosition.Y >= 0 && mousePosition.Y <= row.Bounds.Height)
{
return row;
return row.Index;
}
}
}
@@ -225,13 +219,13 @@ internal class DataGridRowReorderHandle : TemplatedControl
}
// The user didn't cancel, so prepare for the reorder
_dragRow = OwningRow;
_dragStart = mousePosition;
_dragRow.Opacity = 0.0;
_dragRowIndex = OwningRow.Index;
_dragRowBounds = OwningRow.Bounds;
_dragStart = mousePosition;
Debug.Assert(OwningGrid.RowsPresenter != null);
// Display the reordering thumb
OwningGrid.RowsPresenter.DraggedRow = OwningRow;
OwningGrid.RowsPresenter.DraggedRowIndex = _dragRowIndex;
OwningGrid.RowsPresenter.NotifyAboutToDragging();
}
@@ -3863,7 +3863,7 @@ public partial class DataGrid
EditingRow = null;
}
private void ResetFocusedRow()
internal void ResetFocusedRow()
{
if (_focusedRow != null
&& _focusedRow != EditingRow
@@ -161,16 +161,24 @@ internal class DataGridDisplayData
{
DataGridRow row = _recyclableRows.Pop();
row.IsVisible = false;
Debug.Assert(!_fullyRecycledRows.Contains(row));
_fullyRecycledRows.Push(row);
// TODO 拖动的时候滚动翻页了会触发这个,暂时改成动态判断
// Debug.Assert(!_fullyRecycledRows.Contains(row));
if (!_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);
// TODO 拖动的时候滚动翻页了会触发这个,暂时改成动态判断
// Debug.Assert(!_fullyRecycledGroupHeaders.Contains(groupHeader));
if (!_fullyRecycledGroupHeaders.Contains(groupHeader))
{
_fullyRecycledGroupHeaders.Push(groupHeader);
}
}
}
@@ -122,14 +122,6 @@ internal static class DataGridError
}
public static class DataGridColumnHeader
{
public static NotSupportedException ContentDoesNotSupportUIElements()
{
return new NotSupportedException("Content does not support Controls; use ContentTemplate instead.");
}
}
public static class DataGridColumn
{
public static NotSupportedException SelectionModeException()
@@ -190,6 +182,11 @@ internal static class DataGridError
{
return new InvalidOperationException("Only one DataGridRowReorderColumn is allowed.");
}
public static InvalidOperationException InvalidRowReorderPreConditionException()
{
return new InvalidOperationException("Cannot start sorting or filtering while sorting is in progress.");
}
}
public static class DataGridSelectedItemsCollection
@@ -4,6 +4,7 @@
// All other rights reserved.
using System.Diagnostics;
using AtomUI.Controls.Data;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
@@ -21,9 +22,9 @@ namespace AtomUI.Controls;
public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
{
#region 内部属性定义
internal DataGridRow? DraggedRow { get; set; }
internal int? DraggedRowIndex { get; set; }
internal Double DragRowOffset { get; set; }
internal DataGrid? OwningGrid { get; set; }
@@ -31,7 +32,6 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
#endregion
private List<Control>? _orderingChildren;
private DataGridRow? _dragIndicator;
public DataGridRowsPresenter()
@@ -89,7 +89,16 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
double rowDesiredWidth = OwningGrid.RowHeadersDesiredWidth + OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth + OwningGrid.ColumnsInternal.FillerColumn.FillerWidth;
double topEdge = -OwningGrid.NegVerticalOffset;
foreach (Control element in GetScrollingElements())
var visibleRows = new List<Control>();
visibleRows.AddRange(OwningGrid.DisplayData.GetScrollingElements());
if (_dragIndicator != null)
{
visibleRows.Add(_dragIndicator);
}
foreach (Control element in visibleRows)
{
if (element is DataGridRow row)
{
@@ -98,15 +107,14 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
// Visibility for all filler cells needs to be set in one place. Setting it individually in
// each CellsPresenter causes an NxN layout cycle (see DevDiv Bugs 211557)
row.EnsureFillerVisibility();
if (_dragIndicator != row)
{
row.Arrange(new Rect(0, topEdge, rowDesiredWidth, element.DesiredSize.Height));
}
else
if (_dragIndicator == row)
{
row.Arrange(new Rect(0, DragRowOffset, rowDesiredWidth, element.DesiredSize.Height));
}
else
{
row.Arrange(new Rect(0, topEdge, rowDesiredWidth, element.DesiredSize.Height));
}
}
else if (element is DataGridRowGroupHeader groupHeader)
{
@@ -172,7 +180,14 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
double totalCellsWidth = OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth;
double headerWidth = 0;
foreach (Control element in GetScrollingElements())
var visibleRows = OwningGrid.DisplayData.GetScrollingElements().ToList();
if (_dragIndicator != null)
{
visibleRows.Add(_dragIndicator);
}
foreach (Control element in visibleRows)
{
DataGridRow? row = element as DataGridRow;
if (row != null)
@@ -213,37 +228,25 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
{
e.Handled = e.Handled || (OwningGrid?.UpdateScroll(-e.Delta) ?? false);
}
IEnumerable<Control> GetScrollingElements()
{
Debug.Assert(OwningGrid != null);
if (DraggedRow != null)
{
return _orderingChildren!;
}
return OwningGrid.DisplayData.GetScrollingElements();
}
internal void NotifyAboutToDragging()
{
Debug.Assert(OwningGrid != null);
Debug.Assert(DraggedRow != null);
_orderingChildren = new List<Control>();
_orderingChildren.AddRange(OwningGrid.DisplayData.GetScrollingElements());
_dragIndicator = OwningGrid.GetGeneratedGhostRow(DraggedRow.DataContext);
_dragIndicator.Index = DraggedRow.Index;
Debug.Assert(DraggedRowIndex.HasValue);
object? data = null;
if (OwningGrid.CollectionView is DataGridCollectionView collectionView)
{
data = collectionView.GetItemAt(DraggedRowIndex.Value);
}
_dragIndicator = OwningGrid.GetGeneratedGhostRow(data);
_dragIndicator.Index = DraggedRowIndex.Value;
_dragIndicator.IsDragging = true;
_dragIndicator.ZIndex = 1000;
_orderingChildren.Add(_dragIndicator);
LogicalChildren.Add(_dragIndicator);
VisualChildren.Add(_dragIndicator);
}
internal void NotifyDropped()
{
_orderingChildren?.Clear();
_orderingChildren = null;
if (_dragIndicator != null)
{
LogicalChildren.Remove(_dragIndicator);
@@ -253,25 +256,6 @@ public sealed class DataGridRowsPresenter : Panel, IChildIndexProvider
_dragIndicator = null;
}
public void SwapOrderingChildren(Control item1, Control item2)
{
if (_orderingChildren != null)
{
if (!_orderingChildren.Contains(item1))
{
throw new ArgumentOutOfRangeException(nameof(item1));
}
if (!_orderingChildren.Contains(item2))
{
throw new ArgumentOutOfRangeException(nameof(item2));
}
var index1 = _orderingChildren.IndexOf(item1);
var index2 = _orderingChildren.IndexOf(item2);
(_orderingChildren[index2], _orderingChildren[index1]) = (_orderingChildren[index1], _orderingChildren[index2]);
}
}
#if DEBUG
internal void PrintChildren()
{
@@ -62,6 +62,7 @@
<Setter Property="Background"
Value="{DynamicResource {x:Static atom:DataGridTokenKey.TableRowHoverBg}}" />
</Style>
<Setter Property="ZIndex" Value="1000" />
</Style>
<Style Selector="^[IsDragging=False]">