DataGridColumnHeader.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5. using Avalonia.Controls.Primitives;
  6. using Avalonia.Data;
  7. using Avalonia.Input;
  8. using Avalonia.Collections;
  9. using Avalonia.Media;
  10. using System.ComponentModel;
  11. using System.Diagnostics;
  12. using Avalonia.Utilities;
  13. using System;
  14. using Avalonia.Controls.Utils;
  15. namespace Avalonia.Controls
  16. {
  17. /// <summary>
  18. /// Represents an individual <see cref="T:Avalonia.Controls.DataGrid" /> column header.
  19. /// </summary>
  20. public class DataGridColumnHeader : ContentControl
  21. {
  22. private enum DragMode
  23. {
  24. None = 0,
  25. MouseDown = 1,
  26. Drag = 2,
  27. Resize = 3,
  28. Reorder = 4
  29. }
  30. private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5;
  31. private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1;
  32. private bool _areHandlersSuspended;
  33. private static DragMode _dragMode;
  34. private static Point? _lastMousePositionHeaders;
  35. private static Cursor _originalCursor;
  36. private static double _originalHorizontalOffset;
  37. private static double _originalWidth;
  38. private bool _desiredSeparatorVisibility;
  39. private static Point? _dragStart;
  40. private static DataGridColumn _dragColumn;
  41. private static double _frozenColumnsWidth;
  42. private static Lazy<Cursor> _resizeCursor = new Lazy<Cursor>(() => new Cursor(StandardCursorType.SizeWestEast));
  43. public static readonly StyledProperty<IBrush> SeparatorBrushProperty =
  44. AvaloniaProperty.Register<DataGridColumnHeader, IBrush>(nameof(SeparatorBrush));
  45. public IBrush SeparatorBrush
  46. {
  47. get { return GetValue(SeparatorBrushProperty); }
  48. set { SetValue(SeparatorBrushProperty, value); }
  49. }
  50. public static readonly StyledProperty<bool> AreSeparatorsVisibleProperty =
  51. AvaloniaProperty.Register<DataGridColumnHeader, bool>(
  52. nameof(AreSeparatorsVisible),
  53. defaultValue: true);
  54. public bool AreSeparatorsVisible
  55. {
  56. get { return GetValue(AreSeparatorsVisibleProperty); }
  57. set { SetValue(AreSeparatorsVisibleProperty, value); }
  58. }
  59. static DataGridColumnHeader()
  60. {
  61. AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x,e) => x.OnAreSeparatorsVisibleChanged(e));
  62. }
  63. /// <summary>
  64. /// Initializes a new instance of the <see cref="T:Avalonia.Controls.Primitives.DataGridColumnHeader" /> class.
  65. /// </summary>
  66. //TODO Implement
  67. public DataGridColumnHeader()
  68. {
  69. PointerPressed += DataGridColumnHeader_PointerPressed;
  70. PointerReleased += DataGridColumnHeader_PointerReleased;
  71. PointerMoved += DataGridColumnHeader_PointerMove;
  72. PointerEnter += DataGridColumnHeader_PointerEnter;
  73. PointerLeave += DataGridColumnHeader_PointerLeave;
  74. }
  75. private void OnAreSeparatorsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
  76. {
  77. if (!_areHandlersSuspended)
  78. {
  79. _desiredSeparatorVisibility = (bool)e.NewValue;
  80. if (OwningGrid != null)
  81. {
  82. UpdateSeparatorVisibility(OwningGrid.ColumnsInternal.LastVisibleColumn);
  83. }
  84. else
  85. {
  86. UpdateSeparatorVisibility(null);
  87. }
  88. }
  89. }
  90. internal DataGridColumn OwningColumn
  91. {
  92. get;
  93. set;
  94. }
  95. internal DataGrid OwningGrid => OwningColumn?.OwningGrid;
  96. internal int ColumnIndex
  97. {
  98. get
  99. {
  100. if (OwningColumn == null)
  101. {
  102. return -1;
  103. }
  104. return OwningColumn.Index;
  105. }
  106. }
  107. internal ListSortDirection? CurrentSortingState
  108. {
  109. get;
  110. private set;
  111. }
  112. private bool IsMouseOver
  113. {
  114. get;
  115. set;
  116. }
  117. private bool IsPressed
  118. {
  119. get;
  120. set;
  121. }
  122. private void SetValueNoCallback<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
  123. {
  124. _areHandlersSuspended = true;
  125. try
  126. {
  127. SetValue(property, value, priority);
  128. }
  129. finally
  130. {
  131. _areHandlersSuspended = false;
  132. }
  133. }
  134. //TODO Implement
  135. internal void ApplyState()
  136. {
  137. CurrentSortingState = null;
  138. if (OwningGrid != null
  139. && OwningGrid.DataConnection != null
  140. && OwningGrid.DataConnection.AllowSort)
  141. {
  142. var sort = OwningColumn.GetSortDescription();
  143. if(sort != null)
  144. {
  145. CurrentSortingState = sort.Descending ? ListSortDirection.Descending : ListSortDirection.Ascending;
  146. }
  147. }
  148. PseudoClasses.Set(":sortascending",
  149. CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Ascending);
  150. PseudoClasses.Set(":sortdescending",
  151. CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Descending);
  152. }
  153. internal void UpdateSeparatorVisibility(DataGridColumn lastVisibleColumn)
  154. {
  155. bool newVisibility = _desiredSeparatorVisibility;
  156. // Collapse separator for the last column if there is no filler column
  157. if (OwningColumn != null &&
  158. OwningGrid != null &&
  159. _desiredSeparatorVisibility &&
  160. OwningColumn == lastVisibleColumn &&
  161. !OwningGrid.ColumnsInternal.FillerColumn.IsActive)
  162. {
  163. newVisibility = false;
  164. }
  165. // Update the public property if it has changed
  166. if (AreSeparatorsVisible != newVisibility)
  167. {
  168. SetValueNoCallback(AreSeparatorsVisibleProperty, newVisibility);
  169. }
  170. }
  171. internal void OnMouseLeftButtonUp_Click(InputModifiers inputModifiers, ref bool handled)
  172. {
  173. // completed a click without dragging, so we're sorting
  174. InvokeProcessSort(inputModifiers);
  175. handled = true;
  176. }
  177. internal void InvokeProcessSort(InputModifiers inputModifiers)
  178. {
  179. Debug.Assert(OwningGrid != null);
  180. if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(inputModifiers)))
  181. {
  182. return;
  183. }
  184. if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
  185. {
  186. Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(inputModifiers));
  187. }
  188. }
  189. //TODO GroupSorting
  190. internal void ProcessSort(InputModifiers inputModifiers)
  191. {
  192. // if we can sort:
  193. // - DataConnection.AllowSort is true, and
  194. // - AllowUserToSortColumns and CanSort are true, and
  195. // - OwningColumn is bound, and
  196. // - SortDescriptionsCollection exists, and
  197. // - the column's data type is comparable
  198. // then try to sort
  199. if (OwningColumn != null
  200. && OwningGrid != null
  201. && OwningGrid.EditingRow == null
  202. && OwningColumn != OwningGrid.ColumnsInternal.FillerColumn
  203. && OwningGrid.DataConnection.AllowSort
  204. && OwningGrid.CanUserSortColumns
  205. && OwningColumn.CanUserSort
  206. && OwningGrid.DataConnection.SortDescriptions != null)
  207. {
  208. DataGrid owningGrid = OwningGrid;
  209. DataGridSortDescription newSort;
  210. KeyboardHelper.GetMetaKeyState(inputModifiers, out bool ctrl, out bool shift);
  211. DataGridSortDescription sort = OwningColumn.GetSortDescription();
  212. IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;
  213. Debug.Assert(collectionView != null);
  214. using (collectionView.DeferRefresh())
  215. {
  216. // if shift is held down, we multi-sort, therefore if it isn't, we'll clear the sorts beforehand
  217. if (!shift || owningGrid.DataConnection.SortDescriptions.Count == 0)
  218. {
  219. owningGrid.DataConnection.SortDescriptions.Clear();
  220. }
  221. if (sort != null)
  222. {
  223. newSort = sort.SwitchSortDirection();
  224. // changing direction should not affect sort order, so we replace this column's
  225. // sort description instead of just adding it to the end of the collection
  226. int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
  227. if (oldIndex >= 0)
  228. {
  229. owningGrid.DataConnection.SortDescriptions.Remove(sort);
  230. owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
  231. }
  232. else
  233. {
  234. owningGrid.DataConnection.SortDescriptions.Add(newSort);
  235. }
  236. }
  237. else
  238. {
  239. string propertyName = OwningColumn.GetSortPropertyName();
  240. // no-opt if we couldn't find a property to sort on
  241. if (string.IsNullOrEmpty(propertyName))
  242. {
  243. return;
  244. }
  245. newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
  246. owningGrid.DataConnection.SortDescriptions.Add(newSort);
  247. }
  248. }
  249. }
  250. }
  251. private bool CanReorderColumn(DataGridColumn column)
  252. {
  253. return OwningGrid.CanUserReorderColumns
  254. && !(column is DataGridFillerColumn)
  255. && (column.CanUserReorderInternal.HasValue && column.CanUserReorderInternal.Value || !column.CanUserReorderInternal.HasValue);
  256. }
  257. /// <summary>
  258. /// Determines whether a column can be resized by dragging the border of its header. If star sizing
  259. /// is being used, there are special conditions that can prevent a column from being resized:
  260. /// 1. The column is the last visible column.
  261. /// 2. All columns are constrained by either their maximum or minimum values.
  262. /// </summary>
  263. /// <param name="column">Column to check.</param>
  264. /// <returns>Whether or not the column can be resized by dragging its header.</returns>
  265. private static bool CanResizeColumn(DataGridColumn column)
  266. {
  267. if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing &&
  268. (column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !DoubleUtil.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
  269. {
  270. return false;
  271. }
  272. return column.ActualCanUserResize;
  273. }
  274. private static bool TrySetResizeColumn(DataGridColumn column)
  275. {
  276. // If datagrid.CanUserResizeColumns == false, then the column can still override it
  277. if (CanResizeColumn(column))
  278. {
  279. _dragColumn = column;
  280. _dragMode = DragMode.Resize;
  281. return true;
  282. }
  283. return false;
  284. }
  285. //TODO DragDrop
  286. internal void OnMouseLeftButtonDown(ref bool handled, PointerEventArgs args, Point mousePosition)
  287. {
  288. IsPressed = true;
  289. if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
  290. {
  291. args.Device.Capture(this);
  292. _dragMode = DragMode.MouseDown;
  293. _frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth();
  294. _lastMousePositionHeaders = this.Translate(OwningGrid.ColumnHeaders, mousePosition);
  295. double distanceFromLeft = mousePosition.X;
  296. double distanceFromRight = Bounds.Width - distanceFromLeft;
  297. DataGridColumn currentColumn = OwningColumn;
  298. DataGridColumn previousColumn = null;
  299. if (!(OwningColumn is DataGridFillerColumn))
  300. {
  301. previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn);
  302. }
  303. if (_dragMode == DragMode.MouseDown && _dragColumn == null && (distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth))
  304. {
  305. handled = TrySetResizeColumn(currentColumn);
  306. }
  307. else if (_dragMode == DragMode.MouseDown && _dragColumn == null && distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null)
  308. {
  309. handled = TrySetResizeColumn(previousColumn);
  310. }
  311. if (_dragMode == DragMode.Resize && _dragColumn != null)
  312. {
  313. _dragStart = _lastMousePositionHeaders;
  314. _originalWidth = _dragColumn.ActualWidth;
  315. _originalHorizontalOffset = OwningGrid.HorizontalOffset;
  316. handled = true;
  317. }
  318. }
  319. }
  320. //TODO DragEvents
  321. //TODO MouseCapture
  322. internal void OnMouseLeftButtonUp(ref bool handled, PointerEventArgs args, Point mousePosition, Point mousePositionHeaders)
  323. {
  324. IsPressed = false;
  325. if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
  326. {
  327. if (_dragMode == DragMode.MouseDown)
  328. {
  329. OnMouseLeftButtonUp_Click(args.InputModifiers, ref handled);
  330. }
  331. else if (_dragMode == DragMode.Reorder)
  332. {
  333. // Find header we're hovering over
  334. int targetIndex = GetReorderingTargetDisplayIndex(mousePositionHeaders);
  335. if (((!OwningColumn.IsFrozen && targetIndex >= OwningGrid.FrozenColumnCount)
  336. || (OwningColumn.IsFrozen && targetIndex < OwningGrid.FrozenColumnCount)))
  337. {
  338. OwningColumn.DisplayIndex = targetIndex;
  339. DataGridColumnEventArgs ea = new DataGridColumnEventArgs(OwningColumn);
  340. OwningGrid.OnColumnReordered(ea);
  341. }
  342. }
  343. SetDragCursor(mousePosition);
  344. // Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture
  345. args.Device.Capture(null);
  346. OnLostMouseCapture();
  347. _dragMode = DragMode.None;
  348. handled = true;
  349. }
  350. }
  351. //TODO DragEvents
  352. internal void OnMouseMove(ref bool handled, Point mousePosition, Point mousePositionHeaders)
  353. {
  354. if (handled || OwningGrid == null || OwningGrid.ColumnHeaders == null)
  355. {
  356. return;
  357. }
  358. Debug.Assert(OwningGrid.Parent is InputElement);
  359. double distanceFromLeft = mousePosition.X;
  360. double distanceFromRight = Bounds.Width - distanceFromLeft;
  361. OnMouseMove_Resize(ref handled, mousePositionHeaders);
  362. OnMouseMove_Reorder(ref handled, mousePosition, mousePositionHeaders, distanceFromLeft, distanceFromRight);
  363. // if we still haven't done anything about moving the mouse while
  364. // the button is down, we remember that we're dragging, but we don't
  365. // claim to have actually handled the event
  366. if (_dragMode == DragMode.MouseDown)
  367. {
  368. _dragMode = DragMode.Drag;
  369. }
  370. _lastMousePositionHeaders = mousePositionHeaders;
  371. SetDragCursor(mousePosition);
  372. }
  373. private void DataGridColumnHeader_PointerEnter(object sender, PointerEventArgs e)
  374. {
  375. if (!IsEnabled)
  376. {
  377. return;
  378. }
  379. Point mousePosition = e.GetPosition(this);
  380. OnMouseEnter(mousePosition);
  381. ApplyState();
  382. }
  383. private void DataGridColumnHeader_PointerLeave(object sender, PointerEventArgs e)
  384. {
  385. if (!IsEnabled)
  386. {
  387. return;
  388. }
  389. OnMouseLeave();
  390. ApplyState();
  391. }
  392. private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e)
  393. {
  394. if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left)
  395. {
  396. return;
  397. }
  398. Point mousePosition = e.GetPosition(this);
  399. bool handled = e.Handled;
  400. OnMouseLeftButtonDown(ref handled, e, mousePosition);
  401. e.Handled = handled;
  402. ApplyState();
  403. }
  404. private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e)
  405. {
  406. if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left)
  407. {
  408. return;
  409. }
  410. Point mousePosition = e.GetPosition(this);
  411. Point mousePositionHeaders = e.GetPosition(OwningGrid.ColumnHeaders);
  412. bool handled = e.Handled;
  413. OnMouseLeftButtonUp(ref handled, e, mousePosition, mousePositionHeaders);
  414. e.Handled = handled;
  415. ApplyState();
  416. }
  417. private void DataGridColumnHeader_PointerMove(object sender, PointerEventArgs e)
  418. {
  419. if (OwningGrid == null || !IsEnabled)
  420. {
  421. return;
  422. }
  423. Point mousePosition = e.GetPosition(this);
  424. Point mousePositionHeaders = e.GetPosition(OwningGrid.ColumnHeaders);
  425. bool handled = false;
  426. OnMouseMove(ref handled, mousePosition, mousePositionHeaders);
  427. }
  428. /// <summary>
  429. /// Returns the column against whose top-left the reordering caret should be positioned
  430. /// </summary>
  431. /// <param name="mousePositionHeaders">Mouse position within the ColumnHeadersPresenter</param>
  432. /// <param name="scroll">Whether or not to scroll horizontally when a column is dragged out of bounds</param>
  433. /// <param name="scrollAmount">If scroll is true, returns the horizontal amount that was scrolled</param>
  434. /// <returns></returns>
  435. private DataGridColumn GetReorderingTargetColumn(Point mousePositionHeaders, bool scroll, out double scrollAmount)
  436. {
  437. scrollAmount = 0;
  438. double leftEdge = OwningGrid.ColumnsInternal.RowGroupSpacerColumn.IsRepresented ? OwningGrid.ColumnsInternal.RowGroupSpacerColumn.ActualWidth : 0;
  439. double rightEdge = OwningGrid.CellsWidth;
  440. if (OwningColumn.IsFrozen)
  441. {
  442. rightEdge = Math.Min(rightEdge, _frozenColumnsWidth);
  443. }
  444. else if (OwningGrid.FrozenColumnCount > 0)
  445. {
  446. leftEdge = _frozenColumnsWidth;
  447. }
  448. if (mousePositionHeaders.X < leftEdge)
  449. {
  450. if (scroll &&
  451. OwningGrid.HorizontalScrollBar != null &&
  452. OwningGrid.HorizontalScrollBar.IsVisible &&
  453. OwningGrid.HorizontalScrollBar.Value > 0)
  454. {
  455. double newVal = mousePositionHeaders.X - leftEdge;
  456. scrollAmount = Math.Min(newVal, OwningGrid.HorizontalScrollBar.Value);
  457. OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value);
  458. }
  459. mousePositionHeaders = mousePositionHeaders.WithX(leftEdge);
  460. }
  461. else if (mousePositionHeaders.X >= rightEdge)
  462. {
  463. if (scroll &&
  464. OwningGrid.HorizontalScrollBar != null &&
  465. OwningGrid.HorizontalScrollBar.IsVisible &&
  466. OwningGrid.HorizontalScrollBar.Value < OwningGrid.HorizontalScrollBar.Maximum)
  467. {
  468. double newVal = mousePositionHeaders.X - rightEdge;
  469. scrollAmount = Math.Min(newVal, OwningGrid.HorizontalScrollBar.Maximum - OwningGrid.HorizontalScrollBar.Value);
  470. OwningGrid.UpdateHorizontalOffset(scrollAmount + OwningGrid.HorizontalScrollBar.Value);
  471. }
  472. mousePositionHeaders = mousePositionHeaders.WithX(rightEdge - 1);
  473. }
  474. foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns())
  475. {
  476. Point mousePosition = OwningGrid.ColumnHeaders.Translate(column.HeaderCell, mousePositionHeaders);
  477. double columnMiddle = column.HeaderCell.Bounds.Width / 2;
  478. if (mousePosition.X >= 0 && mousePosition.X <= columnMiddle)
  479. {
  480. return column;
  481. }
  482. else if (mousePosition.X > columnMiddle && mousePosition.X < column.HeaderCell.Bounds.Width)
  483. {
  484. return OwningGrid.ColumnsInternal.GetNextVisibleColumn(column);
  485. }
  486. }
  487. return null;
  488. }
  489. /// <summary>
  490. /// Returns the display index to set the column to
  491. /// </summary>
  492. /// <param name="mousePositionHeaders">Mouse position relative to the column headers presenter</param>
  493. /// <returns></returns>
  494. private int GetReorderingTargetDisplayIndex(Point mousePositionHeaders)
  495. {
  496. DataGridColumn targetColumn = GetReorderingTargetColumn(mousePositionHeaders, false /*scroll*/, out double scrollAmount);
  497. if (targetColumn != null)
  498. {
  499. return targetColumn.DisplayIndex > OwningColumn.DisplayIndex ? targetColumn.DisplayIndex - 1 : targetColumn.DisplayIndex;
  500. }
  501. else
  502. {
  503. return OwningGrid.Columns.Count - 1;
  504. }
  505. }
  506. /// <summary>
  507. /// Returns true if the mouse is
  508. /// - to the left of the element, or within the left half of the element
  509. /// and
  510. /// - within the vertical range of the element, or ignoreVertical == true
  511. /// </summary>
  512. /// <param name="mousePosition"></param>
  513. /// <param name="element"></param>
  514. /// <param name="ignoreVertical"></param>
  515. /// <returns></returns>
  516. private bool IsReorderTargeted(Point mousePosition, Control element, bool ignoreVertical)
  517. {
  518. Point position = this.Translate(element, mousePosition);
  519. return (position.X < 0 || (position.X >= 0 && position.X <= element.Bounds.Width / 2))
  520. && (ignoreVertical || (position.Y >= 0 && position.Y <= element.Bounds.Height));
  521. }
  522. /// <summary>
  523. /// Resets the static DataGridColumnHeader properties when a header loses mouse capture
  524. /// </summary>
  525. private void OnLostMouseCapture()
  526. {
  527. // When we stop interacting with the column headers, we need to reset the drag mode
  528. // and close any popups if they are open.
  529. if (_dragColumn != null && _dragColumn.HeaderCell != null)
  530. {
  531. _dragColumn.HeaderCell.Cursor = _originalCursor;
  532. }
  533. _dragMode = DragMode.None;
  534. _dragColumn = null;
  535. _dragStart = null;
  536. _lastMousePositionHeaders = null;
  537. if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
  538. {
  539. OwningGrid.ColumnHeaders.DragColumn = null;
  540. OwningGrid.ColumnHeaders.DragIndicator = null;
  541. OwningGrid.ColumnHeaders.DropLocationIndicator = null;
  542. }
  543. }
  544. /// <summary>
  545. /// Sets up the DataGridColumnHeader for the MouseEnter event
  546. /// </summary>
  547. /// <param name="mousePosition">mouse position relative to the DataGridColumnHeader</param>
  548. private void OnMouseEnter(Point mousePosition)
  549. {
  550. IsMouseOver = true;
  551. SetDragCursor(mousePosition);
  552. }
  553. /// <summary>
  554. /// Sets up the DataGridColumnHeader for the MouseLeave event
  555. /// </summary>
  556. private void OnMouseLeave()
  557. {
  558. IsMouseOver = false;
  559. }
  560. //TODO Styles DragIndicator
  561. private void OnMouseMove_BeginReorder(Point mousePosition)
  562. {
  563. DataGridColumnHeader dragIndicator = new DataGridColumnHeader
  564. {
  565. OwningColumn = OwningColumn,
  566. IsEnabled = false,
  567. Content = Content,
  568. ContentTemplate = ContentTemplate
  569. };
  570. dragIndicator.PseudoClasses.Add(":dragIndicator");
  571. IControl dropLocationIndicator = OwningGrid.DropLocationIndicatorTemplate?.Build();
  572. // If the user didn't style the dropLocationIndicator's Height, default to the column header's height
  573. if (dropLocationIndicator != null && double.IsNaN(dropLocationIndicator.Height) && dropLocationIndicator is Control element)
  574. {
  575. element.Height = Bounds.Height;
  576. }
  577. // pass the caret's data template to the user for modification
  578. DataGridColumnReorderingEventArgs columnReorderingEventArgs = new DataGridColumnReorderingEventArgs(OwningColumn)
  579. {
  580. DropLocationIndicator = dropLocationIndicator,
  581. DragIndicator = dragIndicator
  582. };
  583. OwningGrid.OnColumnReordering(columnReorderingEventArgs);
  584. if (columnReorderingEventArgs.Cancel)
  585. {
  586. return;
  587. }
  588. // The user didn't cancel, so prepare for the reorder
  589. _dragColumn = OwningColumn;
  590. _dragMode = DragMode.Reorder;
  591. _dragStart = mousePosition;
  592. // Display the reordering thumb
  593. OwningGrid.ColumnHeaders.DragColumn = OwningColumn;
  594. OwningGrid.ColumnHeaders.DragIndicator = columnReorderingEventArgs.DragIndicator;
  595. OwningGrid.ColumnHeaders.DropLocationIndicator = columnReorderingEventArgs.DropLocationIndicator;
  596. // If the user didn't style the dragIndicator's Width, default it to the column header's width
  597. if (double.IsNaN(dragIndicator.Width))
  598. {
  599. dragIndicator.Width = Bounds.Width;
  600. }
  601. }
  602. //TODO DragEvents
  603. private void OnMouseMove_Reorder(ref bool handled, Point mousePosition, Point mousePositionHeaders, double distanceFromLeft, double distanceFromRight)
  604. {
  605. if (handled)
  606. {
  607. return;
  608. }
  609. //handle entry into reorder mode
  610. if (_dragMode == DragMode.MouseDown && _dragColumn == null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth))
  611. {
  612. handled = CanReorderColumn(OwningColumn);
  613. if (handled)
  614. {
  615. OnMouseMove_BeginReorder(mousePosition);
  616. }
  617. }
  618. //handle reorder mode (eg, positioning of the popup)
  619. if (_dragMode == DragMode.Reorder && OwningGrid.ColumnHeaders.DragIndicator != null)
  620. {
  621. // Find header we're hovering over
  622. DataGridColumn targetColumn = GetReorderingTargetColumn(mousePositionHeaders, !OwningColumn.IsFrozen /*scroll*/, out double scrollAmount);
  623. OwningGrid.ColumnHeaders.DragIndicatorOffset = mousePosition.X - _dragStart.Value.X + scrollAmount;
  624. OwningGrid.ColumnHeaders.InvalidateArrange();
  625. if (OwningGrid.ColumnHeaders.DropLocationIndicator != null)
  626. {
  627. Point targetPosition = new Point(0, 0);
  628. if (targetColumn == null || targetColumn == OwningGrid.ColumnsInternal.FillerColumn || targetColumn.IsFrozen != OwningColumn.IsFrozen)
  629. {
  630. targetColumn =
  631. OwningGrid.ColumnsInternal.GetLastColumn(
  632. isVisible: true,
  633. isFrozen: OwningColumn.IsFrozen,
  634. isReadOnly: null);
  635. targetPosition = targetColumn.HeaderCell.Translate(OwningGrid.ColumnHeaders, targetPosition);
  636. targetPosition = targetPosition.WithX(targetPosition.X + targetColumn.ActualWidth);
  637. }
  638. else
  639. {
  640. targetPosition = targetColumn.HeaderCell.Translate(OwningGrid.ColumnHeaders, targetPosition);
  641. }
  642. OwningGrid.ColumnHeaders.DropLocationIndicatorOffset = targetPosition.X - scrollAmount;
  643. }
  644. handled = true;
  645. }
  646. }
  647. private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders)
  648. {
  649. if (handled)
  650. {
  651. return;
  652. }
  653. if (_dragMode == DragMode.Resize && _dragColumn != null && _dragStart.HasValue)
  654. {
  655. // resize column
  656. double mouseDelta = mousePositionHeaders.X - _dragStart.Value.X;
  657. double desiredWidth = _originalWidth + mouseDelta;
  658. desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth));
  659. _dragColumn.Resize(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth, true);
  660. OwningGrid.UpdateHorizontalOffset(_originalHorizontalOffset);
  661. handled = true;
  662. }
  663. }
  664. private void SetDragCursor(Point mousePosition)
  665. {
  666. if (_dragMode != DragMode.None || OwningGrid == null || OwningColumn == null)
  667. {
  668. return;
  669. }
  670. // set mouse if we can resize column
  671. double distanceFromLeft = mousePosition.X;
  672. double distanceFromRight = Bounds.Width - distanceFromLeft;
  673. DataGridColumn currentColumn = OwningColumn;
  674. DataGridColumn previousColumn = null;
  675. if (!(OwningColumn is DataGridFillerColumn))
  676. {
  677. previousColumn = OwningGrid.ColumnsInternal.GetPreviousVisibleNonFillerColumn(currentColumn);
  678. }
  679. if ((distanceFromRight <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && currentColumn != null && CanResizeColumn(currentColumn)) ||
  680. (distanceFromLeft <= DATAGRIDCOLUMNHEADER_resizeRegionWidth && previousColumn != null && CanResizeColumn(previousColumn)))
  681. {
  682. var resizeCursor = _resizeCursor.Value;
  683. if (Cursor != resizeCursor)
  684. {
  685. _originalCursor = Cursor;
  686. Cursor = resizeCursor;
  687. }
  688. }
  689. else
  690. {
  691. Cursor = _originalCursor;
  692. }
  693. }
  694. }
  695. }