GridSplitter.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. // This source file is adapted from the Windows Presentation Foundation project.
  2. // (https://github.com/dotnet/wpf/)
  3. //
  4. // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
  5. using System;
  6. using System.Diagnostics;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Primitives;
  9. using Avalonia.Input;
  10. using Avalonia.Interactivity;
  11. using Avalonia.Layout;
  12. using Avalonia.Media;
  13. using Avalonia.Utilities;
  14. namespace Avalonia.Controls
  15. {
  16. /// <summary>
  17. /// Enum to indicate whether GridSplitter resizes Columns or Rows.
  18. /// </summary>
  19. public enum GridResizeDirection
  20. {
  21. /// <summary>
  22. /// Determines whether to resize rows or columns based on its Alignment and
  23. /// width compared to height.
  24. /// </summary>
  25. Auto,
  26. /// <summary>
  27. /// Resize columns when dragging Splitter.
  28. /// </summary>
  29. Columns,
  30. /// <summary>
  31. /// Resize rows when dragging Splitter.
  32. /// </summary>
  33. Rows
  34. }
  35. /// <summary>
  36. /// Enum to indicate what Columns or Rows the GridSplitter resizes.
  37. /// </summary>
  38. public enum GridResizeBehavior
  39. {
  40. /// <summary>
  41. /// Determine which columns or rows to resize based on its Alignment.
  42. /// </summary>
  43. BasedOnAlignment,
  44. /// <summary>
  45. /// Resize the current and next Columns or Rows.
  46. /// </summary>
  47. CurrentAndNext,
  48. /// <summary>
  49. /// Resize the previous and current Columns or Rows.
  50. /// </summary>
  51. PreviousAndCurrent,
  52. /// <summary>
  53. /// Resize the previous and next Columns or Rows.
  54. /// </summary>
  55. PreviousAndNext
  56. }
  57. /// <summary>
  58. /// Represents the control that redistributes space between columns or rows of a Grid control.
  59. /// </summary>
  60. public class GridSplitter : Thumb
  61. {
  62. /// <summary>
  63. /// Defines the <see cref="ResizeDirection"/> property.
  64. /// </summary>
  65. public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty =
  66. AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection));
  67. /// <summary>
  68. /// Defines the <see cref="ResizeBehavior"/> property.
  69. /// </summary>
  70. public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty =
  71. AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior));
  72. /// <summary>
  73. /// Defines the <see cref="ShowsPreview"/> property.
  74. /// </summary>
  75. public static readonly AvaloniaProperty<bool> ShowsPreviewProperty =
  76. AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview));
  77. /// <summary>
  78. /// Defines the <see cref="KeyboardIncrement"/> property.
  79. /// </summary>
  80. public static readonly AvaloniaProperty<double> KeyboardIncrementProperty =
  81. AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d);
  82. /// <summary>
  83. /// Defines the <see cref="DragIncrement"/> property.
  84. /// </summary>
  85. public static readonly AvaloniaProperty<double> DragIncrementProperty =
  86. AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d);
  87. /// <summary>
  88. /// Defines the <see cref="PreviewContent"/> property.
  89. /// </summary>
  90. public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty =
  91. AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent));
  92. private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast);
  93. private static readonly Cursor s_rowSplitterCursor = new Cursor(StandardCursorType.SizeNorthSouth);
  94. private ResizeData _resizeData;
  95. /// <summary>
  96. /// Indicates whether the Splitter resizes the Columns, Rows, or Both.
  97. /// </summary>
  98. public GridResizeDirection ResizeDirection
  99. {
  100. get => GetValue(ResizeDirectionProperty);
  101. set => SetValue(ResizeDirectionProperty, value);
  102. }
  103. /// <summary>
  104. /// Indicates which Columns or Rows the Splitter resizes.
  105. /// </summary>
  106. public GridResizeBehavior ResizeBehavior
  107. {
  108. get => GetValue(ResizeBehaviorProperty);
  109. set => SetValue(ResizeBehaviorProperty, value);
  110. }
  111. /// <summary>
  112. /// Indicates whether to Preview the column resizing without updating layout.
  113. /// </summary>
  114. public bool ShowsPreview
  115. {
  116. get => GetValue(ShowsPreviewProperty);
  117. set => SetValue(ShowsPreviewProperty, value);
  118. }
  119. /// <summary>
  120. /// The Distance to move the splitter when pressing the keyboard arrow keys.
  121. /// </summary>
  122. public double KeyboardIncrement
  123. {
  124. get => GetValue(KeyboardIncrementProperty);
  125. set => SetValue(KeyboardIncrementProperty, value);
  126. }
  127. /// <summary>
  128. /// Restricts splitter to move a multiple of the specified units.
  129. /// </summary>
  130. public double DragIncrement
  131. {
  132. get => GetValue(DragIncrementProperty);
  133. set => SetValue(DragIncrementProperty, value);
  134. }
  135. /// <summary>
  136. /// Gets or sets content that will be shown when <see cref="ShowsPreview"/> is enabled and user starts resize operation.
  137. /// </summary>
  138. public ITemplate<IControl> PreviewContent
  139. {
  140. get => GetValue(PreviewContentProperty);
  141. set => SetValue(PreviewContentProperty, value);
  142. }
  143. /// <summary>
  144. /// Converts BasedOnAlignment direction to Rows, Columns, or Both depending on its width/height.
  145. /// </summary>
  146. internal GridResizeDirection GetEffectiveResizeDirection()
  147. {
  148. GridResizeDirection direction = ResizeDirection;
  149. if (direction != GridResizeDirection.Auto)
  150. {
  151. return direction;
  152. }
  153. // When HorizontalAlignment is Left, Right or Center, resize Columns.
  154. if (HorizontalAlignment != HorizontalAlignment.Stretch)
  155. {
  156. direction = GridResizeDirection.Columns;
  157. }
  158. else if (VerticalAlignment != VerticalAlignment.Stretch)
  159. {
  160. direction = GridResizeDirection.Rows;
  161. }
  162. else if (Bounds.Width <= Bounds.Height) // Fall back to Width vs Height.
  163. {
  164. direction = GridResizeDirection.Columns;
  165. }
  166. else
  167. {
  168. direction = GridResizeDirection.Rows;
  169. }
  170. return direction;
  171. }
  172. /// <summary>
  173. /// Convert BasedOnAlignment to Next/Prev/Both depending on alignment and Direction.
  174. /// </summary>
  175. private GridResizeBehavior GetEffectiveResizeBehavior(GridResizeDirection direction)
  176. {
  177. GridResizeBehavior resizeBehavior = ResizeBehavior;
  178. if (resizeBehavior == GridResizeBehavior.BasedOnAlignment)
  179. {
  180. if (direction == GridResizeDirection.Columns)
  181. {
  182. switch (HorizontalAlignment)
  183. {
  184. case HorizontalAlignment.Left:
  185. resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
  186. break;
  187. case HorizontalAlignment.Right:
  188. resizeBehavior = GridResizeBehavior.CurrentAndNext;
  189. break;
  190. default:
  191. resizeBehavior = GridResizeBehavior.PreviousAndNext;
  192. break;
  193. }
  194. }
  195. else
  196. {
  197. switch (VerticalAlignment)
  198. {
  199. case VerticalAlignment.Top:
  200. resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
  201. break;
  202. case VerticalAlignment.Bottom:
  203. resizeBehavior = GridResizeBehavior.CurrentAndNext;
  204. break;
  205. default:
  206. resizeBehavior = GridResizeBehavior.PreviousAndNext;
  207. break;
  208. }
  209. }
  210. }
  211. return resizeBehavior;
  212. }
  213. /// <summary>
  214. /// Removes preview adorner from the grid.
  215. /// </summary>
  216. private void RemovePreviewAdorner()
  217. {
  218. if (_resizeData.Adorner != null)
  219. {
  220. AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
  221. layer.Children.Remove(_resizeData.Adorner);
  222. }
  223. }
  224. /// <summary>
  225. /// Initialize the data needed for resizing.
  226. /// </summary>
  227. private void InitializeData(bool showsPreview)
  228. {
  229. // If not in a grid or can't resize, do nothing.
  230. if (Parent is Grid grid)
  231. {
  232. GridResizeDirection resizeDirection = GetEffectiveResizeDirection();
  233. // Setup data used for resizing.
  234. _resizeData = new ResizeData
  235. {
  236. Grid = grid,
  237. ShowsPreview = showsPreview,
  238. ResizeDirection = resizeDirection,
  239. SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
  240. ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection)
  241. };
  242. // Store the rows and columns to resize on drag events.
  243. if (!SetupDefinitionsToResize())
  244. {
  245. // Unable to resize, clear data.
  246. _resizeData = null;
  247. return;
  248. }
  249. // Setup the preview in the adorner if ShowsPreview is true.
  250. SetupPreviewAdorner();
  251. }
  252. }
  253. /// <summary>
  254. /// Returns true if GridSplitter can resize rows/columns.
  255. /// </summary>
  256. private bool SetupDefinitionsToResize()
  257. {
  258. int gridSpan = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ?
  259. Grid.ColumnSpanProperty :
  260. Grid.RowSpanProperty);
  261. if (gridSpan == 1)
  262. {
  263. var splitterIndex = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ?
  264. Grid.ColumnProperty :
  265. Grid.RowProperty);
  266. // Select the columns based on behavior.
  267. int index1, index2;
  268. switch (_resizeData.ResizeBehavior)
  269. {
  270. case GridResizeBehavior.PreviousAndCurrent:
  271. // Get current and previous.
  272. index1 = splitterIndex - 1;
  273. index2 = splitterIndex;
  274. break;
  275. case GridResizeBehavior.CurrentAndNext:
  276. // Get current and next.
  277. index1 = splitterIndex;
  278. index2 = splitterIndex + 1;
  279. break;
  280. default: // GridResizeBehavior.PreviousAndNext.
  281. // Get previous and next.
  282. index1 = splitterIndex - 1;
  283. index2 = splitterIndex + 1;
  284. break;
  285. }
  286. // Get count of rows/columns in the resize direction.
  287. int count = _resizeData.ResizeDirection == GridResizeDirection.Columns ?
  288. _resizeData.Grid.ColumnDefinitions.Count :
  289. _resizeData.Grid.RowDefinitions.Count;
  290. if (index1 >= 0 && index2 < count)
  291. {
  292. _resizeData.SplitterIndex = splitterIndex;
  293. _resizeData.Definition1Index = index1;
  294. _resizeData.Definition1 = GetGridDefinition(_resizeData.Grid, index1, _resizeData.ResizeDirection);
  295. _resizeData.OriginalDefinition1Length =
  296. _resizeData.Definition1.UserSizeValueCache; // Save Size if user cancels.
  297. _resizeData.OriginalDefinition1ActualLength = GetActualLength(_resizeData.Definition1);
  298. _resizeData.Definition2Index = index2;
  299. _resizeData.Definition2 = GetGridDefinition(_resizeData.Grid, index2, _resizeData.ResizeDirection);
  300. _resizeData.OriginalDefinition2Length =
  301. _resizeData.Definition2.UserSizeValueCache; // Save Size if user cancels.
  302. _resizeData.OriginalDefinition2ActualLength = GetActualLength(_resizeData.Definition2);
  303. // Determine how to resize the columns.
  304. bool isStar1 = IsStar(_resizeData.Definition1);
  305. bool isStar2 = IsStar(_resizeData.Definition2);
  306. if (isStar1 && isStar2)
  307. {
  308. // If they are both stars, resize both.
  309. _resizeData.SplitBehavior = SplitBehavior.Split;
  310. }
  311. else
  312. {
  313. // One column is fixed width, resize the first one that is fixed.
  314. _resizeData.SplitBehavior = !isStar1 ? SplitBehavior.Resize1 : SplitBehavior.Resize2;
  315. }
  316. return true;
  317. }
  318. }
  319. return false;
  320. }
  321. /// <summary>
  322. /// Create the preview adorner and add it to the adorner layer.
  323. /// </summary>
  324. private void SetupPreviewAdorner()
  325. {
  326. if (_resizeData.ShowsPreview)
  327. {
  328. // Get the adorner layer and add an adorner to it.
  329. var adornerLayer = AdornerLayer.GetAdornerLayer(_resizeData.Grid);
  330. var previewContent = PreviewContent;
  331. // Can't display preview.
  332. if (adornerLayer == null)
  333. {
  334. return;
  335. }
  336. IControl builtPreviewContent = previewContent?.Build();
  337. _resizeData.Adorner = new PreviewAdorner(builtPreviewContent);
  338. AdornerLayer.SetAdornedElement(_resizeData.Adorner, this);
  339. adornerLayer.Children.Add(_resizeData.Adorner);
  340. // Get constraints on preview's translation.
  341. GetDeltaConstraints(out _resizeData.MinChange, out _resizeData.MaxChange);
  342. }
  343. }
  344. protected override void OnPointerEnter(PointerEventArgs e)
  345. {
  346. base.OnPointerEnter(e);
  347. GridResizeDirection direction = GetEffectiveResizeDirection();
  348. switch (direction)
  349. {
  350. case GridResizeDirection.Columns:
  351. Cursor = s_columnSplitterCursor;
  352. break;
  353. case GridResizeDirection.Rows:
  354. Cursor = s_rowSplitterCursor;
  355. break;
  356. }
  357. }
  358. protected override void OnLostFocus(RoutedEventArgs e)
  359. {
  360. base.OnLostFocus(e);
  361. if (_resizeData != null)
  362. {
  363. CancelResize();
  364. }
  365. }
  366. protected override void OnDragStarted(VectorEventArgs e)
  367. {
  368. base.OnDragStarted(e);
  369. Debug.Assert(_resizeData == null, "_resizeData is not null, DragCompleted was not called");
  370. InitializeData(ShowsPreview);
  371. }
  372. protected override void OnDragDelta(VectorEventArgs e)
  373. {
  374. base.OnDragDelta(e);
  375. if (_resizeData != null)
  376. {
  377. double horizontalChange = e.Vector.X;
  378. double verticalChange = e.Vector.Y;
  379. // Round change to nearest multiple of DragIncrement.
  380. double dragIncrement = DragIncrement;
  381. horizontalChange = Math.Round(horizontalChange / dragIncrement) * dragIncrement;
  382. verticalChange = Math.Round(verticalChange / dragIncrement) * dragIncrement;
  383. if (_resizeData.ShowsPreview)
  384. {
  385. // Set the Translation of the Adorner to the distance from the thumb.
  386. if (_resizeData.ResizeDirection == GridResizeDirection.Columns)
  387. {
  388. _resizeData.Adorner.OffsetX = Math.Min(
  389. Math.Max(horizontalChange, _resizeData.MinChange),
  390. _resizeData.MaxChange);
  391. }
  392. else
  393. {
  394. _resizeData.Adorner.OffsetY = Math.Min(
  395. Math.Max(verticalChange, _resizeData.MinChange),
  396. _resizeData.MaxChange);
  397. }
  398. }
  399. else
  400. {
  401. // Directly update the grid.
  402. MoveSplitter(horizontalChange, verticalChange);
  403. }
  404. }
  405. }
  406. protected override void OnDragCompleted(VectorEventArgs e)
  407. {
  408. base.OnDragCompleted(e);
  409. if (_resizeData != null)
  410. {
  411. if (_resizeData.ShowsPreview)
  412. {
  413. // Update the grid.
  414. MoveSplitter(_resizeData.Adorner.OffsetX, _resizeData.Adorner.OffsetY);
  415. RemovePreviewAdorner();
  416. }
  417. _resizeData = null;
  418. }
  419. }
  420. protected override void OnKeyDown(KeyEventArgs e)
  421. {
  422. Key key = e.Key;
  423. switch (key)
  424. {
  425. case Key.Escape:
  426. if (_resizeData != null)
  427. {
  428. CancelResize();
  429. e.Handled = true;
  430. }
  431. break;
  432. case Key.Left:
  433. e.Handled = KeyboardMoveSplitter(-KeyboardIncrement, 0);
  434. break;
  435. case Key.Right:
  436. e.Handled = KeyboardMoveSplitter(KeyboardIncrement, 0);
  437. break;
  438. case Key.Up:
  439. e.Handled = KeyboardMoveSplitter(0, -KeyboardIncrement);
  440. break;
  441. case Key.Down:
  442. e.Handled = KeyboardMoveSplitter(0, KeyboardIncrement);
  443. break;
  444. }
  445. }
  446. /// <summary>
  447. /// Cancels the resize operation.
  448. /// </summary>
  449. private void CancelResize()
  450. {
  451. // Restore original column/row lengths.
  452. if (_resizeData.ShowsPreview)
  453. {
  454. RemovePreviewAdorner();
  455. }
  456. else // Reset the columns/rows lengths to the saved values.
  457. {
  458. SetDefinitionLength(_resizeData.Definition1, _resizeData.OriginalDefinition1Length);
  459. SetDefinitionLength(_resizeData.Definition2, _resizeData.OriginalDefinition2Length);
  460. }
  461. _resizeData = null;
  462. }
  463. /// <summary>
  464. /// Returns true if the row/column has a star length.
  465. /// </summary>
  466. private static bool IsStar(DefinitionBase definition)
  467. {
  468. return definition.UserSizeValueCache.IsStar;
  469. }
  470. /// <summary>
  471. /// Gets Column or Row definition at index from grid based on resize direction.
  472. /// </summary>
  473. private static DefinitionBase GetGridDefinition(Grid grid, int index, GridResizeDirection direction)
  474. {
  475. return direction == GridResizeDirection.Columns ?
  476. (DefinitionBase)grid.ColumnDefinitions[index] :
  477. (DefinitionBase)grid.RowDefinitions[index];
  478. }
  479. /// <summary>
  480. /// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row.
  481. /// </summary>
  482. private double GetActualLength(DefinitionBase definition)
  483. {
  484. var column = definition as ColumnDefinition;
  485. return column?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
  486. }
  487. /// <summary>
  488. /// Gets Column or Row definition at index from grid based on resize direction.
  489. /// </summary>
  490. private static void SetDefinitionLength(DefinitionBase definition, GridLength length)
  491. {
  492. definition.SetValue(
  493. definition is ColumnDefinition ? ColumnDefinition.WidthProperty : RowDefinition.HeightProperty, length);
  494. }
  495. /// <summary>
  496. /// Get the minimum and maximum Delta can be given definition constraints (MinWidth/MaxWidth).
  497. /// </summary>
  498. private void GetDeltaConstraints(out double minDelta, out double maxDelta)
  499. {
  500. double definition1Len = GetActualLength(_resizeData.Definition1);
  501. double definition1Min = _resizeData.Definition1.UserMinSizeValueCache;
  502. double definition1Max = _resizeData.Definition1.UserMaxSizeValueCache;
  503. double definition2Len = GetActualLength(_resizeData.Definition2);
  504. double definition2Min = _resizeData.Definition2.UserMinSizeValueCache;
  505. double definition2Max = _resizeData.Definition2.UserMaxSizeValueCache;
  506. // Set MinWidths to be greater than width of splitter.
  507. if (_resizeData.SplitterIndex == _resizeData.Definition1Index)
  508. {
  509. definition1Min = Math.Max(definition1Min, _resizeData.SplitterLength);
  510. }
  511. else if (_resizeData.SplitterIndex == _resizeData.Definition2Index)
  512. {
  513. definition2Min = Math.Max(definition2Min, _resizeData.SplitterLength);
  514. }
  515. if (_resizeData.SplitBehavior == SplitBehavior.Split)
  516. {
  517. // Determine the minimum and maximum the columns can be resized.
  518. minDelta = -Math.Min(definition1Len - definition1Min, definition2Max - definition2Len);
  519. maxDelta = Math.Min(definition1Max - definition1Len, definition2Len - definition2Min);
  520. }
  521. else if (_resizeData.SplitBehavior == SplitBehavior.Resize1)
  522. {
  523. minDelta = definition1Min - definition1Len;
  524. maxDelta = definition1Max - definition1Len;
  525. }
  526. else
  527. {
  528. minDelta = definition2Len - definition2Max;
  529. maxDelta = definition2Len - definition2Min;
  530. }
  531. }
  532. /// <summary>
  533. /// Sets the length of definition1 and definition2.
  534. /// </summary>
  535. private void SetLengths(double definition1Pixels, double definition2Pixels)
  536. {
  537. // For the case where both definition1 and 2 are stars, update all star values to match their current pixel values.
  538. if (_resizeData.SplitBehavior == SplitBehavior.Split)
  539. {
  540. var definitions = _resizeData.ResizeDirection == GridResizeDirection.Columns ?
  541. (IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.ColumnDefinitions :
  542. (IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.RowDefinitions;
  543. var definitionsCount = definitions.Count;
  544. for (var i = 0; i < definitionsCount; i++)
  545. {
  546. DefinitionBase definition = definitions[i];
  547. // For each definition, if it is a star, set is value to ActualLength in stars
  548. // This makes 1 star == 1 pixel in length
  549. if (i == _resizeData.Definition1Index)
  550. {
  551. SetDefinitionLength(definition, new GridLength(definition1Pixels, GridUnitType.Star));
  552. }
  553. else if (i == _resizeData.Definition2Index)
  554. {
  555. SetDefinitionLength(definition, new GridLength(definition2Pixels, GridUnitType.Star));
  556. }
  557. else if (IsStar(definition))
  558. {
  559. SetDefinitionLength(definition, new GridLength(GetActualLength(definition), GridUnitType.Star));
  560. }
  561. }
  562. }
  563. else if (_resizeData.SplitBehavior == SplitBehavior.Resize1)
  564. {
  565. SetDefinitionLength(_resizeData.Definition1, new GridLength(definition1Pixels));
  566. }
  567. else
  568. {
  569. SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels));
  570. }
  571. }
  572. /// <summary>
  573. /// Move the splitter by the given Delta's in the horizontal and vertical directions.
  574. /// </summary>
  575. private void MoveSplitter(double horizontalChange, double verticalChange)
  576. {
  577. Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter");
  578. // Calculate the offset to adjust the splitter.
  579. var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange;
  580. DefinitionBase definition1 = _resizeData.Definition1;
  581. DefinitionBase definition2 = _resizeData.Definition2;
  582. if (definition1 != null && definition2 != null)
  583. {
  584. double actualLength1 = GetActualLength(definition1);
  585. double actualLength2 = GetActualLength(definition2);
  586. // When splitting, Check to see if the total pixels spanned by the definitions
  587. // is the same asbefore starting resize. If not cancel the drag
  588. if (_resizeData.SplitBehavior == SplitBehavior.Split &&
  589. !MathUtilities.AreClose(
  590. actualLength1 + actualLength2,
  591. _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength))
  592. {
  593. CancelResize();
  594. return;
  595. }
  596. GetDeltaConstraints(out var min, out var max);
  597. // Constrain Delta to Min/MaxWidth of columns
  598. delta = Math.Min(Math.Max(delta, min), max);
  599. double definition1LengthNew = actualLength1 + delta;
  600. double definition2LengthNew = actualLength1 + actualLength2 - definition1LengthNew;
  601. SetLengths(definition1LengthNew, definition2LengthNew);
  602. }
  603. }
  604. /// <summary>
  605. /// Move the splitter using the Keyboard (Don't show preview).
  606. /// </summary>
  607. private bool KeyboardMoveSplitter(double horizontalChange, double verticalChange)
  608. {
  609. // If moving with the mouse, ignore keyboard motion.
  610. if (_resizeData != null)
  611. {
  612. return false; // Don't handle the event.
  613. }
  614. // Don't show preview.
  615. InitializeData(false);
  616. // Check that we are actually able to resize.
  617. if (_resizeData == null)
  618. {
  619. return false; // Don't handle the event.
  620. }
  621. MoveSplitter(horizontalChange, verticalChange);
  622. _resizeData = null;
  623. return true;
  624. }
  625. /// <summary>
  626. /// This adorner draws the preview for the <see cref="GridSplitter"/>.
  627. /// It also positions the adorner.
  628. /// </summary>
  629. private sealed class PreviewAdorner : Decorator
  630. {
  631. private readonly TranslateTransform _translation;
  632. private readonly Decorator _decorator;
  633. public PreviewAdorner(IControl previewControl)
  634. {
  635. // Add a decorator to perform translations.
  636. _translation = new TranslateTransform();
  637. _decorator = new Decorator
  638. {
  639. Child = previewControl,
  640. RenderTransform = _translation
  641. };
  642. Child = _decorator;
  643. }
  644. /// <summary>
  645. /// The Preview's Offset in the X direction from the GridSplitter.
  646. /// </summary>
  647. public double OffsetX
  648. {
  649. get => _translation.X;
  650. set => _translation.X = value;
  651. }
  652. /// <summary>
  653. /// The Preview's Offset in the Y direction from the GridSplitter.
  654. /// </summary>
  655. public double OffsetY
  656. {
  657. get => _translation.Y;
  658. set => _translation.Y = value;
  659. }
  660. protected override Size ArrangeOverride(Size finalSize)
  661. {
  662. // Adorners always get clipped to the owner control. In this case we want
  663. // to constrain size to the splitter size but draw on top of the parent grid.
  664. Clip = null;
  665. return base.ArrangeOverride(finalSize);
  666. }
  667. }
  668. /// <summary>
  669. /// <see cref="GridSplitter"/> has special Behavior when columns are fixed.
  670. /// If the left column is fixed, splitter will only resize that column.
  671. /// Else if the right column is fixed, splitter will only resize the right column.
  672. /// </summary>
  673. private enum SplitBehavior
  674. {
  675. /// <summary>
  676. /// Both columns/rows are star lengths.
  677. /// </summary>
  678. Split,
  679. /// <summary>
  680. /// Resize 1 only.
  681. /// </summary>
  682. Resize1,
  683. /// <summary>
  684. /// Resize 2 only.
  685. /// </summary>
  686. Resize2
  687. }
  688. /// <summary>
  689. /// Stores data during the resizing operation.
  690. /// </summary>
  691. private class ResizeData
  692. {
  693. public bool ShowsPreview;
  694. public PreviewAdorner Adorner;
  695. // The constraints to keep the Preview within valid ranges.
  696. public double MinChange;
  697. public double MaxChange;
  698. // The grid to Resize.
  699. public Grid Grid;
  700. // Cache of Resize Direction and Behavior.
  701. public GridResizeDirection ResizeDirection;
  702. public GridResizeBehavior ResizeBehavior;
  703. // The columns/rows to resize.
  704. public DefinitionBase Definition1;
  705. public DefinitionBase Definition2;
  706. // Are the columns/rows star lengths.
  707. public SplitBehavior SplitBehavior;
  708. // The index of the splitter.
  709. public int SplitterIndex;
  710. // The indices of the columns/rows.
  711. public int Definition1Index;
  712. public int Definition2Index;
  713. // The original lengths of Definition1 and Definition2 (to restore lengths if user cancels resize).
  714. public GridLength OriginalDefinition1Length;
  715. public GridLength OriginalDefinition2Length;
  716. public double OriginalDefinition1ActualLength;
  717. public double OriginalDefinition2ActualLength;
  718. // The minimum of Width/Height of Splitter. Used to ensure splitter
  719. // isn't hidden by resizing a row/column smaller than the splitter.
  720. public double SplitterLength;
  721. }
  722. }
  723. }