DataGridCellsPresenter.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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.Media;
  6. using Avalonia.Utilities;
  7. using System;
  8. using System.Diagnostics;
  9. using Avalonia.Controls;
  10. using Avalonia.Controls.Utils;
  11. namespace Avalonia.Controls.Primitives
  12. {
  13. /// <summary>
  14. /// Used within the template of a <see cref="T:Avalonia.Controls.DataGrid" />
  15. /// to specify the location in the control's visual tree where the cells are to be added.
  16. /// </summary>
  17. public sealed class DataGridCellsPresenter : Panel
  18. {
  19. private double _fillerLeftEdge;
  20. // The desired height needs to be cached due to column virtualization; otherwise, the cells
  21. // would grow and shrink as the DataGrid scrolls horizontally
  22. private double DesiredHeight
  23. {
  24. get;
  25. set;
  26. }
  27. private DataGrid OwningGrid
  28. {
  29. get
  30. {
  31. return OwningRow?.OwningGrid;
  32. }
  33. }
  34. internal DataGridRow OwningRow
  35. {
  36. get;
  37. set;
  38. }
  39. /// <summary>
  40. /// Arranges the content of the <see cref="T:Avalonia.Controls.Primitives.DataGridCellsPresenter" />.
  41. /// </summary>
  42. /// <returns>
  43. /// The actual size used by the <see cref="T:Avalonia.Controls.Primitives.DataGridCellsPresenter" />.
  44. /// </returns>
  45. /// <param name="finalSize">
  46. /// The final area within the parent that this element should use to arrange itself and its children.
  47. /// </param>
  48. protected override Size ArrangeOverride(Size finalSize)
  49. {
  50. if (OwningGrid == null)
  51. {
  52. return base.ArrangeOverride(finalSize);
  53. }
  54. if (OwningGrid.AutoSizingColumns)
  55. {
  56. // When we initially load an auto-column, we have to wait for all the rows to be measured
  57. // before we know its final desired size. We need to trigger a new round of measures now
  58. // that the final sizes have been calculated.
  59. OwningGrid.AutoSizingColumns = false;
  60. return base.ArrangeOverride(finalSize);
  61. }
  62. double frozenLeftEdge = 0;
  63. double scrollingLeftEdge = -OwningGrid.HorizontalOffset;
  64. double cellLeftEdge;
  65. foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
  66. {
  67. DataGridCell cell = OwningRow.Cells[column.Index];
  68. Debug.Assert(cell.OwningColumn == column);
  69. Debug.Assert(column.IsVisible);
  70. if (column.IsFrozen)
  71. {
  72. cellLeftEdge = frozenLeftEdge;
  73. // This can happen before or after clipping because frozen cells aren't clipped
  74. frozenLeftEdge += column.ActualWidth;
  75. }
  76. else
  77. {
  78. cellLeftEdge = scrollingLeftEdge;
  79. }
  80. if (cell.IsVisible)
  81. {
  82. cell.Arrange(new Rect(cellLeftEdge, 0, column.LayoutRoundedWidth, finalSize.Height));
  83. EnsureCellClip(cell, column.ActualWidth, finalSize.Height, frozenLeftEdge, scrollingLeftEdge);
  84. }
  85. scrollingLeftEdge += column.ActualWidth;
  86. column.IsInitialDesiredWidthDetermined = true;
  87. }
  88. _fillerLeftEdge = scrollingLeftEdge;
  89. OwningRow.FillerCell.Arrange(new Rect(_fillerLeftEdge, 0, OwningGrid.ColumnsInternal.FillerColumn.FillerWidth, finalSize.Height));
  90. return finalSize;
  91. }
  92. private static void EnsureCellClip(DataGridCell cell, double width, double height, double frozenLeftEdge, double cellLeftEdge)
  93. {
  94. // Clip the cell only if it's scrolled under frozen columns. Unfortunately, we need to clip in this case
  95. // because cells could be transparent
  96. if (!cell.OwningColumn.IsFrozen && frozenLeftEdge > cellLeftEdge)
  97. {
  98. RectangleGeometry rg = new RectangleGeometry();
  99. double xClip = Math.Round(Math.Min(width, frozenLeftEdge - cellLeftEdge));
  100. rg.Rect = new Rect(xClip, 0, Math.Max(0, width - xClip), height);
  101. cell.Clip = rg;
  102. }
  103. else
  104. {
  105. cell.Clip = null;
  106. }
  107. }
  108. private static void EnsureCellDisplay(DataGridCell cell, bool displayColumn)
  109. {
  110. if (cell.IsCurrent)
  111. {
  112. if (displayColumn)
  113. {
  114. cell.IsVisible = true;
  115. cell.Clip = null;
  116. }
  117. else
  118. {
  119. // Clip
  120. RectangleGeometry rg = new RectangleGeometry();
  121. rg.Rect = Rect.Empty;
  122. cell.Clip = rg;
  123. }
  124. }
  125. else
  126. {
  127. cell.IsVisible = displayColumn;
  128. }
  129. }
  130. internal void EnsureFillerVisibility()
  131. {
  132. DataGridFillerColumn fillerColumn = OwningGrid.ColumnsInternal.FillerColumn;
  133. bool newVisibility = fillerColumn.IsActive;
  134. if (OwningRow.FillerCell.IsVisible != newVisibility)
  135. {
  136. OwningRow.FillerCell.IsVisible = newVisibility;
  137. if (newVisibility)
  138. {
  139. OwningRow.FillerCell.Arrange(new Rect(_fillerLeftEdge, 0, fillerColumn.FillerWidth, Bounds.Height));
  140. }
  141. }
  142. // This must be done after the Filler visibility is determined. This also must be done
  143. // regardless of whether or not the filler visibility actually changed values because
  144. // we could scroll in a cell that didn't have EnsureGridLine called yet
  145. DataGridColumn lastVisibleColumn = OwningGrid.ColumnsInternal.LastVisibleColumn;
  146. if (lastVisibleColumn != null)
  147. {
  148. DataGridCell cell = OwningRow.Cells[lastVisibleColumn.Index];
  149. cell.EnsureGridLine(lastVisibleColumn);
  150. }
  151. }
  152. /// <summary>
  153. /// Measures the children of a <see cref="T:Avalonia.Controls.Primitives.DataGridCellsPresenter" /> to
  154. /// prepare for arranging them during the <see cref="M:System.Windows.FrameworkElement.ArrangeOverride(System.Windows.Size)" /> pass.
  155. /// </summary>
  156. /// <param name="availableSize">
  157. /// The available size that this element can give to child elements. Indicates an upper limit that child elements should not exceed.
  158. /// </param>
  159. /// <returns>
  160. /// The size that the <see cref="T:Avalonia.Controls.Primitives.DataGridCellsPresenter" /> determines it needs during layout, based on its calculations of child object allocated sizes.
  161. /// </returns>
  162. protected override Size MeasureOverride(Size availableSize)
  163. {
  164. if (OwningGrid == null)
  165. {
  166. return base.MeasureOverride(availableSize);
  167. }
  168. bool autoSizeHeight;
  169. double measureHeight;
  170. if (double.IsNaN(OwningGrid.RowHeight))
  171. {
  172. // No explicit height values were set so we can autosize
  173. autoSizeHeight = true;
  174. measureHeight = double.PositiveInfinity;
  175. }
  176. else
  177. {
  178. DesiredHeight = OwningGrid.RowHeight;
  179. measureHeight = DesiredHeight;
  180. autoSizeHeight = false;
  181. }
  182. double frozenLeftEdge = 0;
  183. double totalDisplayWidth = 0;
  184. double scrollingLeftEdge = -OwningGrid.HorizontalOffset;
  185. OwningGrid.ColumnsInternal.EnsureVisibleEdgedColumnsWidth();
  186. DataGridColumn lastVisibleColumn = OwningGrid.ColumnsInternal.LastVisibleColumn;
  187. foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
  188. {
  189. DataGridCell cell = OwningRow.Cells[column.Index];
  190. // Measure the entire first row to make the horizontal scrollbar more accurate
  191. bool shouldDisplayCell = ShouldDisplayCell(column, frozenLeftEdge, scrollingLeftEdge) || OwningRow.Index == 0;
  192. EnsureCellDisplay(cell, shouldDisplayCell);
  193. if (shouldDisplayCell)
  194. {
  195. DataGridLength columnWidth = column.Width;
  196. bool autoGrowWidth = columnWidth.IsSizeToCells || columnWidth.IsAuto;
  197. if (column != lastVisibleColumn)
  198. {
  199. cell.EnsureGridLine(lastVisibleColumn);
  200. }
  201. // If we're not using star sizing or the current column can't be resized,
  202. // then just set the display width according to the column's desired width
  203. if (!OwningGrid.UsesStarSizing || (!column.ActualCanUserResize && !column.Width.IsStar))
  204. {
  205. // In the edge-case where we're given infinite width and we have star columns, the
  206. // star columns grow to their predefined limit of 10,000 (or their MaxWidth)
  207. double newDisplayWidth = column.Width.IsStar ?
  208. Math.Min(column.ActualMaxWidth, DataGrid.DATAGRID_maximumStarColumnWidth) :
  209. Math.Max(column.ActualMinWidth, Math.Min(column.ActualMaxWidth, column.Width.DesiredValue));
  210. column.SetWidthDisplayValue(newDisplayWidth);
  211. }
  212. // If we're auto-growing the column based on the cell content, we want to measure it at its maximum value
  213. if (autoGrowWidth)
  214. {
  215. cell.Measure(new Size(column.ActualMaxWidth, measureHeight));
  216. OwningGrid.AutoSizeColumn(column, cell.DesiredSize.Width);
  217. column.ComputeLayoutRoundedWidth(totalDisplayWidth);
  218. }
  219. else if (!OwningGrid.UsesStarSizing)
  220. {
  221. column.ComputeLayoutRoundedWidth(scrollingLeftEdge);
  222. cell.Measure(new Size(column.LayoutRoundedWidth, measureHeight));
  223. }
  224. // We need to track the largest height in order to auto-size
  225. if (autoSizeHeight)
  226. {
  227. DesiredHeight = Math.Max(DesiredHeight, cell.DesiredSize.Height);
  228. }
  229. }
  230. if (column.IsFrozen)
  231. {
  232. frozenLeftEdge += column.ActualWidth;
  233. }
  234. scrollingLeftEdge += column.ActualWidth;
  235. totalDisplayWidth += column.ActualWidth;
  236. }
  237. // If we're using star sizing (and we're not waiting for an auto-column to finish growing)
  238. // then we will resize all the columns to fit the available space.
  239. if (OwningGrid.UsesStarSizing && !OwningGrid.AutoSizingColumns)
  240. {
  241. double adjustment = OwningGrid.CellsWidth - totalDisplayWidth;
  242. totalDisplayWidth += adjustment - OwningGrid.AdjustColumnWidths(0, adjustment, false);
  243. // Since we didn't know the final widths of the columns until we resized,
  244. // we waited until now to measure each cell
  245. double leftEdge = 0;
  246. foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetVisibleColumns())
  247. {
  248. DataGridCell cell = OwningRow.Cells[column.Index];
  249. column.ComputeLayoutRoundedWidth(leftEdge);
  250. cell.Measure(new Size(column.LayoutRoundedWidth, measureHeight));
  251. if (autoSizeHeight)
  252. {
  253. DesiredHeight = Math.Max(DesiredHeight, cell.DesiredSize.Height);
  254. }
  255. leftEdge += column.ActualWidth;
  256. }
  257. }
  258. // Measure FillerCell, we're doing it unconditionally here because we don't know if we'll need the filler
  259. // column and we don't want to cause another Measure if we do
  260. OwningRow.FillerCell.Measure(new Size(double.PositiveInfinity, DesiredHeight));
  261. OwningGrid.ColumnsInternal.EnsureVisibleEdgedColumnsWidth();
  262. return new Size(OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, DesiredHeight);
  263. }
  264. internal void Recycle()
  265. {
  266. // Clear out the cached desired height so it is not reused for other rows
  267. DesiredHeight = 0;
  268. }
  269. private bool ShouldDisplayCell(DataGridColumn column, double frozenLeftEdge, double scrollingLeftEdge)
  270. {
  271. if (!column.IsVisible)
  272. {
  273. return false;
  274. }
  275. scrollingLeftEdge += OwningGrid.HorizontalAdjustment;
  276. double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge;
  277. double rightEdge = leftEdge + column.ActualWidth;
  278. return
  279. DoubleUtil.GreaterThan(rightEdge, 0) &&
  280. DoubleUtil.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
  281. DoubleUtil.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
  282. }
  283. }
  284. }