diff --git a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs
index 630bf58f9..fe97b78c6 100644
--- a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs
+++ b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumn.cs
@@ -113,7 +113,7 @@ public abstract class DataGridColumn : AvaloniaObject
internal bool HasHeaderCell => _headerCell != null;
- internal DataGridColumnHeader HeaderCell
+ internal DataGridColumnHeader? HeaderCell
{
get
{
@@ -852,7 +852,7 @@ public abstract class DataGridColumn : AvaloniaObject
{
if (OwningGrid != null && OwningGrid.UseLayoutRounding)
{
- var scale = LayoutHelper.GetLayoutScale(HeaderCell);
+ var scale = LayoutHelper.GetLayoutScale(HeaderCell!);
var roundSize = LayoutHelper.RoundLayoutSizeUp(new Size(leftEdge + ActualWidth, 1), scale, scale);
LayoutRoundedWidth = roundSize.Width - leftEdge;
}
diff --git a/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs
new file mode 100644
index 000000000..024ac5643
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/Colum/DataGridColumnHeader.cs
@@ -0,0 +1,854 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using AtomUI.Controls.Automation.Peers;
+using AtomUI.Controls.Collections;
+using AtomUI.Controls.Utils;
+using Avalonia;
+using Avalonia.Automation;
+using Avalonia.Automation.Peers;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Mixins;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Utilities;
+
+namespace AtomUI.Controls;
+
+///
+/// Represents an individual column header.
+///
+[PseudoClasses(":dragIndicator", ":pressed", ":sortascending", ":sortdescending")]
+public class DataGridColumnHeader : ContentControl
+{
+ private enum DragMode
+ {
+ None = 0,
+ MouseDown = 1,
+ Drag = 2,
+ Resize = 3,
+ Reorder = 4
+ }
+
+ private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5;
+ private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5;
+
+ private bool _areHandlersSuspended;
+ private static DragMode _dragMode;
+ private static Point? _lastMousePositionHeaders;
+ private static Cursor? _originalCursor;
+ private static double _originalHorizontalOffset;
+ private static double _originalWidth;
+ private bool _desiredSeparatorVisibility = true;
+ private static Point? _dragStart;
+ private static DataGridColumn? _dragColumn;
+ private static double _frozenColumnsWidth;
+ private static Lazy _resizeCursor = new Lazy(() => new Cursor(StandardCursorType.SizeWestEast));
+
+ public static readonly StyledProperty SeparatorBrushProperty =
+ AvaloniaProperty.Register(nameof(SeparatorBrush));
+
+ public IBrush SeparatorBrush
+ {
+ get => GetValue(SeparatorBrushProperty);
+ set => SetValue(SeparatorBrushProperty, value);
+ }
+
+ public static readonly StyledProperty AreSeparatorsVisibleProperty =
+ AvaloniaProperty.Register(
+ nameof(AreSeparatorsVisible),
+ defaultValue: true);
+
+ public bool AreSeparatorsVisible
+ {
+ get => GetValue(AreSeparatorsVisibleProperty);
+ set => SetValue(AreSeparatorsVisibleProperty, value);
+ }
+
+ static DataGridColumnHeader()
+ {
+ AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) =>
+ x.OnAreSeparatorsVisibleChanged(e));
+ PressedMixin.Attach();
+ IsTabStopProperty.OverrideDefaultValue(false);
+ AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior
+ .FromClip);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ //TODO Implement
+ public DataGridColumnHeader()
+ {
+ PointerPressed += DataGridColumnHeader_PointerPressed;
+ PointerReleased += DataGridColumnHeader_PointerReleased;
+ PointerMoved += DataGridColumnHeader_PointerMoved;
+ PointerEntered += DataGridColumnHeader_PointerEntered;
+ PointerExited += DataGridColumnHeader_PointerExited;
+ }
+
+ protected override AutomationPeer OnCreateAutomationPeer()
+ {
+ return new DataGridColumnHeaderAutomationPeer(this);
+ }
+
+ private void OnAreSeparatorsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (!_areHandlersSuspended)
+ {
+ _desiredSeparatorVisibility = e.NewValue != null && (bool)e.NewValue;
+ if (OwningGrid != null)
+ {
+ UpdateSeparatorVisibility(OwningGrid.ColumnsInternal.LastVisibleColumn);
+ }
+ else
+ {
+ UpdateSeparatorVisibility(null);
+ }
+ }
+ }
+
+ internal DataGridColumn? OwningColumn { get; set; }
+ internal DataGrid? OwningGrid => OwningColumn?.OwningGrid;
+
+ internal int ColumnIndex
+ {
+ get
+ {
+ if (OwningColumn == null)
+ {
+ return -1;
+ }
+
+ return OwningColumn.Index;
+ }
+ }
+
+ internal ListSortDirection? CurrentSortingState { get; private set; }
+
+ private bool IsMouseOver { get; set; }
+
+ private bool IsPressed { get; set; }
+
+ private void SetValueNoCallback(AvaloniaProperty property, T value,
+ BindingPriority priority = BindingPriority.LocalValue)
+ {
+ _areHandlersSuspended = true;
+ try
+ {
+ SetValue(property, value, priority);
+ }
+ finally
+ {
+ _areHandlersSuspended = false;
+ }
+ }
+
+ internal void UpdatePseudoClasses()
+ {
+ CurrentSortingState = null;
+ if (OwningGrid != null
+ && OwningGrid.DataConnection != null
+ && OwningGrid.DataConnection.AllowSort)
+ {
+ var sort = OwningColumn?.GetSortDescription();
+ if (sort != null)
+ {
+ CurrentSortingState = sort.Direction;
+ }
+ }
+
+ PseudoClasses.Set(":sortascending",
+ CurrentSortingState == ListSortDirection.Ascending);
+ PseudoClasses.Set(":sortdescending",
+ CurrentSortingState == ListSortDirection.Descending);
+ }
+
+ internal void UpdateSeparatorVisibility(DataGridColumn? lastVisibleColumn)
+ {
+ bool newVisibility = _desiredSeparatorVisibility;
+
+ // Collapse separator for the last column if there is no filler column
+ if (OwningColumn != null &&
+ OwningGrid != null &&
+ _desiredSeparatorVisibility &&
+ OwningColumn == lastVisibleColumn &&
+ !OwningGrid.ColumnsInternal.FillerColumn.IsActive)
+ {
+ newVisibility = false;
+ }
+
+ // Update the public property if it has changed
+ if (AreSeparatorsVisible != newVisibility)
+ {
+ SetValueNoCallback(AreSeparatorsVisibleProperty, newVisibility);
+ }
+ }
+
+ public event EventHandler? LeftClick;
+
+ internal void OnMouseLeftButtonUp_Click(KeyModifiers keyModifiers, ref bool handled)
+ {
+ LeftClick?.Invoke(this, keyModifiers);
+
+ // completed a click without dragging, so we're sorting
+ InvokeProcessSort(keyModifiers);
+ handled = true;
+ }
+
+ internal void InvokeProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
+ {
+ Debug.Assert(OwningGrid != null);
+ if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers, forcedDirection)))
+ {
+ return;
+ }
+
+ if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
+ {
+ Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers, forcedDirection));
+ }
+ }
+
+ //TODO GroupSorting
+ internal void ProcessSort(KeyModifiers keyModifiers, ListSortDirection? forcedDirection = null)
+ {
+ // if we can sort:
+ // - AllowUserToSortColumns and CanSort are true, and
+ // - OwningColumn is bound
+ // then try to sort
+ if (OwningColumn != null
+ && OwningGrid != null
+ && OwningGrid.EditingRow == null
+ && OwningColumn != OwningGrid.ColumnsInternal.FillerColumn
+ && OwningGrid.CanUserSortColumns
+ && OwningColumn.CanUserSort)
+ {
+ var ea = new DataGridColumnEventArgs(OwningColumn);
+ OwningGrid.OnColumnSorting(ea);
+
+ if (!ea.Handled && OwningGrid.DataConnection!.AllowSort &&
+ OwningGrid.DataConnection.SortDescriptions != null)
+ {
+ // - DataConnection.AllowSort is true, and
+ // - SortDescriptionsCollection exists, and
+ // - the column's data type is comparable
+
+ DataGrid owningGrid = OwningGrid;
+ DataGridSortDescription newSort;
+
+ KeyboardHelper.GetMetaKeyState(this, keyModifiers, out bool ctrl, out bool shift);
+
+ var sort = OwningColumn.GetSortDescription();
+ var collectionView = owningGrid.DataConnection.CollectionView;
+ Debug.Assert(collectionView != null);
+
+ using (collectionView.DeferRefresh())
+ {
+ // if shift is held down, we multi-sort, therefore if it isn't, we'll clear the sorts beforehand
+ if (!shift || owningGrid.DataConnection.SortDescriptions.Count == 0)
+ {
+ owningGrid.DataConnection.SortDescriptions.Clear();
+ }
+
+ // if ctrl is held down, we only clear the sort directions
+ if (!ctrl)
+ {
+ if (sort != null)
+ {
+ if (forcedDirection == null || sort.Direction != forcedDirection)
+ {
+ newSort = sort.SwitchSortDirection();
+ }
+ else
+ {
+ newSort = sort;
+ }
+
+ // changing direction should not affect sort order, so we replace this column's
+ // sort description instead of just adding it to the end of the collection
+ int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
+ if (oldIndex >= 0)
+ {
+ owningGrid.DataConnection.SortDescriptions.Remove(sort);
+ owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
+ }
+ else
+ {
+ owningGrid.DataConnection.SortDescriptions.Add(newSort);
+ }
+ }
+ else if (OwningColumn.CustomSortComparer != null)
+ {
+ newSort = forcedDirection != null
+ ? DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer,
+ forcedDirection.Value)
+ : DataGridSortDescription.FromComparer(OwningColumn.CustomSortComparer);
+
+ owningGrid.DataConnection.SortDescriptions.Add(newSort);
+ }
+ else
+ {
+ var propertyName = OwningColumn.GetSortPropertyName();
+ // no-opt if we couldn't find a property to sort on
+ if (string.IsNullOrEmpty(propertyName))
+ {
+ return;
+ }
+
+ newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
+ if (forcedDirection != null && newSort.Direction != forcedDirection)
+ {
+ newSort = newSort.SwitchSortDirection();
+ }
+
+ owningGrid.DataConnection.SortDescriptions.Add(newSort);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private bool CanReorderColumn(DataGridColumn column)
+ {
+ return OwningGrid != null && OwningGrid.CanUserReorderColumns
+ && !(column is DataGridFillerColumn)
+ && (column.CanUserReorderInternal.HasValue && column.CanUserReorderInternal.Value ||
+ !column.CanUserReorderInternal.HasValue);
+ }
+
+ ///
+ /// Determines whether a column can be resized by dragging the border of its header. If star sizing
+ /// is being used, there are special conditions that can prevent a column from being resized:
+ /// 1. The column is the last visible column.
+ /// 2. All columns are constrained by either their maximum or minimum values.
+ ///
+ /// Column to check.
+ /// Whether or not the column can be resized by dragging its header.
+ private static bool CanResizeColumn(DataGridColumn column)
+ {
+ if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null &&
+ column.OwningGrid.UsesStarSizing &&
+ (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column ||
+ !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth,
+ column.OwningGrid.CellsWidth)))
+ {
+ return false;
+ }
+
+ return column.ActualCanUserResize;
+ }
+
+ private static bool TrySetResizeColumn(DataGridColumn column)
+ {
+ // If datagrid.CanUserResizeColumns == false, then the column can still override it
+ if (CanResizeColumn(column))
+ {
+ _dragColumn = column;
+ _dragMode = DragMode.Resize;
+ return true;
+ }
+
+ return false;
+ }
+
+ //TODO DragDrop
+
+ internal void OnMouseLeftButtonDown(ref bool handled, PointerEventArgs args, Point mousePosition)
+ {
+ IsPressed = true;
+ if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
+ {
+ _dragMode = DragMode.MouseDown;
+ _frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth();
+ _lastMousePositionHeaders = this.Translate(OwningGrid.ColumnHeaders, mousePosition);
+
+ double distanceFromLeft = mousePosition.X;
+ double distanceFromRight = Bounds.Width - distanceFromLeft;
+ var currentColumn = OwningColumn;
+ DataGridColumn? previousColumn = null;
+ if (!(OwningColumn is DataGridFillerColumn))
+ {
+ previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn);
+ }
+
+ if (_dragMode == DragMode.MouseDown && _dragColumn == null &&
+ distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth &&
+ currentColumn != null)
+ {
+ handled = TrySetResizeColumn(currentColumn);
+ }
+ else if (_dragMode == DragMode.MouseDown && _dragColumn == null &&
+ distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null)
+ {
+ handled = TrySetResizeColumn(previousColumn);
+ }
+
+ if (_dragMode == DragMode.Resize && _dragColumn != null)
+ {
+ _dragStart = _lastMousePositionHeaders;
+ _originalWidth = _dragColumn.ActualWidth;
+ _originalHorizontalOffset = OwningGrid.HorizontalOffset;
+
+ handled = true;
+ }
+ }
+ }
+
+ //TODO DragEvents
+ //TODO MouseCapture
+ internal void OnMouseLeftButtonUp(ref bool handled, PointerEventArgs args, Point mousePosition,
+ Point mousePositionHeaders)
+ {
+ IsPressed = false;
+
+ if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
+ {
+ if (_dragMode == DragMode.MouseDown)
+ {
+ OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled);
+ }
+ else if (_dragMode == DragMode.Reorder)
+ {
+ // Find header we're hovering over
+ int targetIndex = GetReorderingTargetDisplayIndex(mousePositionHeaders);
+
+ if (OwningColumn is not null)
+ {
+ if ((!OwningColumn.IsFrozen && targetIndex >= OwningGrid.FrozenColumnCount)
+ || (OwningColumn.IsFrozen && targetIndex < OwningGrid.FrozenColumnCount))
+ {
+ OwningColumn.DisplayIndex = targetIndex;
+
+ var ea = new DataGridColumnEventArgs(OwningColumn);
+ OwningGrid.OnColumnReordered(ea);
+ }
+ }
+ }
+
+ SetDragCursor(mousePosition);
+
+ // Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture
+ args.Pointer.Capture(null);
+ OnLostMouseCapture();
+ _dragMode = DragMode.None;
+ handled = true;
+ }
+ }
+
+ //TODO DragEvents
+ internal void OnMouseMove(PointerEventArgs args, Point mousePosition, Point mousePositionHeaders)
+ {
+ var handled = args.Handled;
+ if (handled || OwningGrid == null || OwningGrid.ColumnHeaders == null)
+ {
+ return;
+ }
+
+ Debug.Assert(OwningGrid.Parent is InputElement);
+
+ OnMouseMove_Resize(ref handled, mousePositionHeaders);
+
+ OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders);
+
+ SetDragCursor(mousePosition);
+ }
+
+ private void DataGridColumnHeader_PointerEntered(object? sender, PointerEventArgs e)
+ {
+ if (!IsEnabled)
+ {
+ return;
+ }
+
+ Point mousePosition = e.GetPosition(this);
+ OnMouseEnter(mousePosition);
+ UpdatePseudoClasses();
+ }
+
+ private void DataGridColumnHeader_PointerExited(object? sender, PointerEventArgs e)
+ {
+ if (!IsEnabled)
+ {
+ return;
+ }
+
+ OnMouseLeave();
+ UpdatePseudoClasses();
+ }
+
+ private void DataGridColumnHeader_PointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (OwningColumn == null || e.Handled || !IsEnabled || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ {
+ return;
+ }
+
+ Point mousePosition = e.GetPosition(this);
+ bool handled = e.Handled;
+ OnMouseLeftButtonDown(ref handled, e, mousePosition);
+ e.Handled = handled;
+
+ UpdatePseudoClasses();
+ }
+
+ private void DataGridColumnHeader_PointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left)
+ {
+ return;
+ }
+
+ Point mousePosition = e.GetPosition(this);
+ Point mousePositionHeaders = e.GetPosition(OwningGrid!.ColumnHeaders);
+ bool handled = e.Handled;
+ OnMouseLeftButtonUp(ref handled, e, mousePosition, mousePositionHeaders);
+ e.Handled = handled;
+
+ UpdatePseudoClasses();
+ }
+
+ private void DataGridColumnHeader_PointerMoved(object? sender, PointerEventArgs e)
+ {
+ if (OwningGrid == null || !IsEnabled)
+ {
+ return;
+ }
+
+ Point mousePosition = e.GetPosition(this);
+ Point mousePositionHeaders = e.GetPosition(OwningGrid.ColumnHeaders);
+
+ OnMouseMove(e, mousePosition, mousePositionHeaders);
+ }
+
+ ///
+ /// Returns the column against whose top-left the reordering caret should be positioned
+ ///
+ /// Mouse position within the ColumnHeadersPresenter
+ /// Whether or not to scroll horizontally when a column is dragged out of bounds
+ /// If scroll is true, returns the horizontal amount that was scrolled
+ ///
+ private DataGridColumn? GetReorderingTargetColumn(Point mousePositionHeaders, bool scroll, out double scrollAmount)
+ {
+ scrollAmount = 0;
+ double leftEdge = OwningGrid?.ColumnsInternal.RowGroupSpacerColumn.IsRepresented
+ ? OwningGrid?.ColumnsInternal.RowGroupSpacerColumn.ActualWidth
+ : 0;
+ var rightEdge = OwningGrid!.CellsWidth;
+ if (OwningColumn!.IsFrozen)
+ {
+ rightEdge = Math.Min(rightEdge, _frozenColumnsWidth);
+ }
+ else if (OwningGrid.FrozenColumnCount > 0)
+ {
+ leftEdge = _frozenColumnsWidth;
+ }
+
+ if (mousePositionHeaders.X < leftEdge)
+ {
+ if (scroll &&
+ OwningGrid.HorizontalScrollBar != null &&
+ OwningGrid.HorizontalScrollBar.IsVisible &&
+ OwningGrid.HorizontalScrollBar.Value > 0)
+ {
+ double newVal = mousePositionHeaders.X - leftEdge;
+ scrollAmount = Math.Min(newVal, OwningGrid.HorizontalScrollBar.Value);
+ OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value);
+ }
+
+ mousePositionHeaders = mousePositionHeaders.WithX(leftEdge);
+ }
+ else if (mousePositionHeaders.X >= rightEdge)
+ {
+ if (scroll &&
+ OwningGrid.HorizontalScrollBar != null &&
+ OwningGrid.HorizontalScrollBar.IsVisible &&
+ OwningGrid.HorizontalScrollBar.Value < OwningGrid.HorizontalScrollBar.Maximum)
+ {
+ double newVal = mousePositionHeaders.X - rightEdge;
+ scrollAmount = Math.Min(newVal,
+ OwningGrid.HorizontalScrollBar.Maximum - OwningGrid.HorizontalScrollBar.Value);
+ OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value);
+ }
+
+ mousePositionHeaders = mousePositionHeaders.WithX(rightEdge - 1);
+ }
+
+ foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns())
+ {
+ Point mousePosition = OwningGrid.ColumnHeaders!.Translate(column.HeaderCell!, mousePositionHeaders);
+ double columnMiddle = column.HeaderCell!.Bounds.Width / 2;
+ if (mousePosition.X >= 0 && mousePosition.X <= columnMiddle)
+ {
+ return column;
+ }
+ else if (mousePosition.X > columnMiddle && mousePosition.X < column.HeaderCell.Bounds.Width)
+ {
+ return OwningGrid.ColumnsInternal.GetNextVisibleColumn(column);
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns the display index to set the column to
+ ///
+ /// Mouse position relative to the column headers presenter
+ ///
+ private int GetReorderingTargetDisplayIndex(Point mousePositionHeaders)
+ {
+ var targetColumn =
+ GetReorderingTargetColumn(mousePositionHeaders, false /*scroll*/, out double scrollAmount);
+ if (targetColumn != null)
+ {
+ return targetColumn.DisplayIndex > OwningColumn!.DisplayIndex
+ ? targetColumn.DisplayIndex - 1
+ : targetColumn.DisplayIndex;
+ }
+ else
+ {
+ return OwningGrid!.Columns.Count - 1;
+ }
+ }
+
+ ///
+ /// Returns true if the mouse is
+ /// - to the left of the element, or within the left half of the element
+ /// and
+ /// - within the vertical range of the element, or ignoreVertical == true
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool IsReorderTargeted(Point mousePosition, Control element, bool ignoreVertical)
+ {
+ Point position = this.Translate(element, mousePosition);
+
+ return (position.X < 0 || (position.X >= 0 && position.X <= element.Bounds.Width / 2))
+ && (ignoreVertical || (position.Y >= 0 && position.Y <= element.Bounds.Height));
+ }
+
+ ///
+ /// Resets the static DataGridColumnHeader properties when a header loses mouse capture
+ ///
+ private void OnLostMouseCapture()
+ {
+ // When we stop interacting with the column headers, we need to reset the drag mode
+ // and close any popups if they are open.
+
+ if (_dragColumn != null && _dragColumn.HeaderCell != null)
+ {
+ _dragColumn.HeaderCell.Cursor = _originalCursor;
+ }
+
+ _dragMode = DragMode.None;
+ _dragColumn = null;
+ _dragStart = null;
+ _lastMousePositionHeaders = null;
+
+ if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
+ {
+ OwningGrid.ColumnHeaders.DragColumn = null;
+ OwningGrid.ColumnHeaders.DragIndicator = null;
+ OwningGrid.ColumnHeaders.DropLocationIndicator = null;
+ }
+ }
+
+ ///
+ /// Sets up the DataGridColumnHeader for the MouseEnter event
+ ///
+ /// mouse position relative to the DataGridColumnHeader
+ private void OnMouseEnter(Point mousePosition)
+ {
+ IsMouseOver = true;
+ SetDragCursor(mousePosition);
+ }
+
+ ///
+ /// Sets up the DataGridColumnHeader for the MouseLeave event
+ ///
+ private void OnMouseLeave()
+ {
+ IsMouseOver = false;
+ }
+
+ private void OnMouseMove_BeginReorder(Point mousePosition)
+ {
+ var dragIndicator = new DataGridColumnHeader
+ {
+ OwningColumn = OwningColumn,
+ IsEnabled = false,
+ Content = Content,
+ ContentTemplate = ContentTemplate
+ };
+ if (OwningGrid!.ColumnHeaderTheme is { } columnHeaderTheme)
+ {
+ dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.Template);
+ }
+
+ dragIndicator.PseudoClasses.Add(":dragIndicator");
+
+ Control? dropLocationIndicator = OwningGrid?.DropLocationIndicatorTemplate.Build();
+
+ // If the user didn't style the dropLocationIndicator's Height, default to the column header's height
+ if (dropLocationIndicator != null && double.IsNaN(dropLocationIndicator.Height))
+ {
+ dropLocationIndicator.Height = Bounds.Height;
+ }
+
+ // pass the caret's data template to the user for modification
+ var columnReorderingEventArgs = new DataGridColumnReorderingEventArgs(OwningColumn!)
+ {
+ DropLocationIndicator = dropLocationIndicator,
+ DragIndicator = dragIndicator
+ };
+ OwningGrid!.OnColumnReordering(columnReorderingEventArgs);
+ if (columnReorderingEventArgs.Cancel)
+ {
+ return;
+ }
+
+ // The user didn't cancel, so prepare for the reorder
+ _dragColumn = OwningColumn;
+ _dragMode = DragMode.Reorder;
+ _dragStart = mousePosition;
+
+ // Display the reordering thumb
+ OwningGrid.ColumnHeaders!.DragColumn = OwningColumn;
+ OwningGrid.ColumnHeaders.DragIndicator = columnReorderingEventArgs.DragIndicator;
+ OwningGrid.ColumnHeaders.DropLocationIndicator = columnReorderingEventArgs.DropLocationIndicator;
+
+ // If the user didn't style the dragIndicator's Width, default it to the column header's width
+ if (double.IsNaN(dragIndicator.Width))
+ {
+ dragIndicator.Width = Bounds.Width;
+ }
+ }
+
+ //TODO DragEvents
+ private void OnMouseMove_Reorder(ref bool handled, Point mousePosition, Point mousePositionHeaders)
+ {
+ if (handled)
+ {
+ return;
+ }
+
+ //handle entry into reorder mode
+ if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null)
+ {
+ var distanceFromInitial = (Vector)(mousePositionHeaders - _lastMousePositionHeaders);
+ if (distanceFromInitial.Length > DATAGRIDCOLUMNHEADER_columnsDragTreshold)
+ {
+ handled = CanReorderColumn(OwningColumn!);
+
+ if (handled)
+ {
+ OnMouseMove_BeginReorder(mousePosition);
+ }
+ }
+ }
+
+ //handle reorder mode (eg, positioning of the popup)
+ if (_dragMode == DragMode.Reorder && OwningGrid?.ColumnHeaders?.DragIndicator != null)
+ {
+ // Find header we're hovering over
+ var targetColumn = GetReorderingTargetColumn(mousePositionHeaders, !OwningColumn!.IsFrozen /*scroll*/,
+ out double scrollAmount);
+
+ OwningGrid.ColumnHeaders.DragIndicatorOffset = mousePosition.X - _dragStart!.Value.X + scrollAmount;
+ OwningGrid.ColumnHeaders.InvalidateArrange();
+
+ if (OwningGrid.ColumnHeaders.DropLocationIndicator != null)
+ {
+ Point targetPosition = new Point(0, 0);
+ if (targetColumn == null || targetColumn == OwningGrid.ColumnsInternal.FillerColumn ||
+ targetColumn.IsFrozen != OwningColumn.IsFrozen)
+ {
+ targetColumn =
+ OwningGrid.ColumnsInternal.GetLastColumn(
+ isVisible: true,
+ isFrozen: OwningColumn.IsFrozen,
+ isReadOnly: null);
+ targetPosition = targetColumn.HeaderCell!.Translate(OwningGrid.ColumnHeaders, targetPosition);
+
+ targetPosition = targetPosition.WithX(targetPosition.X + targetColumn.ActualWidth);
+ }
+ else
+ {
+ targetPosition = targetColumn.HeaderCell!.Translate(OwningGrid.ColumnHeaders, targetPosition);
+ }
+
+ OwningGrid.ColumnHeaders.DropLocationIndicatorOffset = targetPosition.X - scrollAmount;
+ }
+
+ handled = true;
+ }
+ }
+
+ private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders)
+ {
+ if (handled)
+ {
+ return;
+ }
+
+ if (_dragMode == DragMode.Resize && _dragColumn != null && _dragStart.HasValue)
+ {
+ // resize column
+
+ double mouseDelta = mousePositionHeaders.X - _dragStart.Value.X;
+ double desiredWidth = _originalWidth + mouseDelta;
+
+ desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth));
+ _dragColumn.Resize(_dragColumn.Width,
+ new(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth),
+ true);
+
+ OwningGrid?.UpdateHorizontalOffset(_originalHorizontalOffset);
+
+ handled = true;
+ }
+ }
+
+ private void SetDragCursor(Point mousePosition)
+ {
+ if (_dragMode != DragMode.None || OwningGrid == null || OwningColumn == null)
+ {
+ return;
+ }
+
+ // set mouse if we can resize column
+
+ double distanceFromLeft = mousePosition.X;
+ double distanceFromRight = Bounds.Width - distanceFromLeft;
+ DataGridColumn? currentColumn = OwningColumn;
+ DataGridColumn? previousColumn = null;
+
+ if (!(OwningColumn is DataGridFillerColumn))
+ {
+ previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn);
+ }
+
+ if ((distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && currentColumn != null &&
+ CanResizeColumn(currentColumn)) ||
+ (distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null &&
+ CanResizeColumn(previousColumn)))
+ {
+ var resizeCursor = _resizeCursor.Value;
+ if (Cursor != resizeCursor)
+ {
+ _originalCursor = Cursor;
+ Cursor = resizeCursor;
+ }
+ }
+ else
+ {
+ Cursor = _originalCursor;
+ }
+ }
+}
\ No newline at end of file