ContentPresenter.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. using System;
  2. using Avalonia.Controls.Primitives;
  3. using Avalonia.Controls.Templates;
  4. using Avalonia.Controls.Utils;
  5. using Avalonia.Data;
  6. using Avalonia.Input;
  7. using Avalonia.Layout;
  8. using Avalonia.LogicalTree;
  9. using Avalonia.Media;
  10. using Avalonia.Metadata;
  11. namespace Avalonia.Controls.Presenters
  12. {
  13. /// <summary>
  14. /// Presents a single item of data inside a <see cref="TemplatedControl"/> template.
  15. /// </summary>
  16. public class ContentPresenter : Control, IContentPresenter
  17. {
  18. /// <summary>
  19. /// Defines the <see cref="Background"/> property.
  20. /// </summary>
  21. public static readonly StyledProperty<IBrush> BackgroundProperty =
  22. Border.BackgroundProperty.AddOwner<ContentPresenter>();
  23. /// <summary>
  24. /// Defines the <see cref="BorderBrush"/> property.
  25. /// </summary>
  26. public static readonly StyledProperty<IBrush> BorderBrushProperty =
  27. Border.BorderBrushProperty.AddOwner<ContentPresenter>();
  28. /// <summary>
  29. /// Defines the <see cref="BorderThickness"/> property.
  30. /// </summary>
  31. public static readonly StyledProperty<Thickness> BorderThicknessProperty =
  32. Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
  33. /// <summary>
  34. /// Defines the <see cref="CornerRadius"/> property.
  35. /// </summary>
  36. public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
  37. Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
  38. /// <summary>
  39. /// Defines the <see cref="BoxShadow"/> property.
  40. /// </summary>
  41. public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
  42. Border.BoxShadowProperty.AddOwner<ContentPresenter>();
  43. /// <summary>
  44. /// Defines the <see cref="Child"/> property.
  45. /// </summary>
  46. public static readonly DirectProperty<ContentPresenter, IControl> ChildProperty =
  47. AvaloniaProperty.RegisterDirect<ContentPresenter, IControl>(
  48. nameof(Child),
  49. o => o.Child);
  50. /// <summary>
  51. /// Defines the <see cref="Content"/> property.
  52. /// </summary>
  53. public static readonly StyledProperty<object> ContentProperty =
  54. ContentControl.ContentProperty.AddOwner<ContentPresenter>();
  55. /// <summary>
  56. /// Defines the <see cref="ContentTemplate"/> property.
  57. /// </summary>
  58. public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
  59. ContentControl.ContentTemplateProperty.AddOwner<ContentPresenter>();
  60. /// <summary>
  61. /// Defines the <see cref="HorizontalContentAlignment"/> property.
  62. /// </summary>
  63. public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
  64. ContentControl.HorizontalContentAlignmentProperty.AddOwner<ContentPresenter>();
  65. /// <summary>
  66. /// Defines the <see cref="VerticalContentAlignment"/> property.
  67. /// </summary>
  68. public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
  69. ContentControl.VerticalContentAlignmentProperty.AddOwner<ContentPresenter>();
  70. /// <summary>
  71. /// Defines the <see cref="Padding"/> property.
  72. /// </summary>
  73. public static readonly StyledProperty<Thickness> PaddingProperty =
  74. Decorator.PaddingProperty.AddOwner<ContentPresenter>();
  75. private IControl _child;
  76. private bool _createdChild;
  77. private IRecyclingDataTemplate _recyclingDataTemplate;
  78. private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
  79. /// <summary>
  80. /// Initializes static members of the <see cref="ContentPresenter"/> class.
  81. /// </summary>
  82. static ContentPresenter()
  83. {
  84. AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
  85. AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
  86. AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
  87. ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
  88. ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
  89. TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
  90. }
  91. /// <summary>
  92. /// Gets or sets a brush with which to paint the background.
  93. /// </summary>
  94. public IBrush Background
  95. {
  96. get { return GetValue(BackgroundProperty); }
  97. set { SetValue(BackgroundProperty, value); }
  98. }
  99. /// <summary>
  100. /// Gets or sets a brush with which to paint the border.
  101. /// </summary>
  102. public IBrush BorderBrush
  103. {
  104. get { return GetValue(BorderBrushProperty); }
  105. set { SetValue(BorderBrushProperty, value); }
  106. }
  107. /// <summary>
  108. /// Gets or sets the thickness of the border.
  109. /// </summary>
  110. public Thickness BorderThickness
  111. {
  112. get { return GetValue(BorderThicknessProperty); }
  113. set { SetValue(BorderThicknessProperty, value); }
  114. }
  115. /// <summary>
  116. /// Gets or sets the radius of the border rounded corners.
  117. /// </summary>
  118. public CornerRadius CornerRadius
  119. {
  120. get { return GetValue(CornerRadiusProperty); }
  121. set { SetValue(CornerRadiusProperty, value); }
  122. }
  123. /// <summary>
  124. /// Gets or sets the box shadow effect parameters
  125. /// </summary>
  126. public BoxShadows BoxShadow
  127. {
  128. get => GetValue(BoxShadowProperty);
  129. set => SetValue(BoxShadowProperty, value);
  130. }
  131. /// <summary>
  132. /// Gets the control displayed by the presenter.
  133. /// </summary>
  134. public IControl Child
  135. {
  136. get { return _child; }
  137. private set { SetAndRaise(ChildProperty, ref _child, value); }
  138. }
  139. /// <summary>
  140. /// Gets or sets the content to be displayed by the presenter.
  141. /// </summary>
  142. [DependsOn(nameof(ContentTemplate))]
  143. public object Content
  144. {
  145. get { return GetValue(ContentProperty); }
  146. set { SetValue(ContentProperty, value); }
  147. }
  148. /// <summary>
  149. /// Gets or sets the data template used to display the content of the control.
  150. /// </summary>
  151. public IDataTemplate ContentTemplate
  152. {
  153. get { return GetValue(ContentTemplateProperty); }
  154. set { SetValue(ContentTemplateProperty, value); }
  155. }
  156. /// <summary>
  157. /// Gets or sets the horizontal alignment of the content within the border the control.
  158. /// </summary>
  159. public HorizontalAlignment HorizontalContentAlignment
  160. {
  161. get { return GetValue(HorizontalContentAlignmentProperty); }
  162. set { SetValue(HorizontalContentAlignmentProperty, value); }
  163. }
  164. /// <summary>
  165. /// Gets or sets the vertical alignment of the content within the border of the control.
  166. /// </summary>
  167. public VerticalAlignment VerticalContentAlignment
  168. {
  169. get { return GetValue(VerticalContentAlignmentProperty); }
  170. set { SetValue(VerticalContentAlignmentProperty, value); }
  171. }
  172. /// <summary>
  173. /// Gets or sets the space between the border and the <see cref="Child"/> control.
  174. /// </summary>
  175. public Thickness Padding
  176. {
  177. get { return GetValue(PaddingProperty); }
  178. set { SetValue(PaddingProperty, value); }
  179. }
  180. /// <summary>
  181. /// Gets the host content control.
  182. /// </summary>
  183. internal IContentPresenterHost Host { get; private set; }
  184. /// <inheritdoc/>
  185. public sealed override void ApplyTemplate()
  186. {
  187. if (!_createdChild && ((ILogical)this).IsAttachedToLogicalTree)
  188. {
  189. UpdateChild();
  190. }
  191. }
  192. /// <summary>
  193. /// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
  194. /// </summary>
  195. /// <remarks>
  196. /// Usually the <see cref="Child"/> control is created automatically when
  197. /// <see cref="ApplyTemplate"/> is called; however for this to happen, the control needs to
  198. /// be attached to a logical tree (if the control is not attached to the logical tree, it
  199. /// is reasonable to expect that the DataTemplates needed for the child are not yet
  200. /// available). This method forces the <see cref="Child"/> control's creation at any point,
  201. /// and is particularly useful in unit tests.
  202. /// </remarks>
  203. public void UpdateChild()
  204. {
  205. var content = Content;
  206. var oldChild = Child;
  207. var newChild = CreateChild();
  208. var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
  209. // Remove the old child if we're not recycling it.
  210. if (newChild != oldChild)
  211. {
  212. if (oldChild != null)
  213. {
  214. VisualChildren.Remove(oldChild);
  215. logicalChildren.Remove(oldChild);
  216. ((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent);
  217. }
  218. }
  219. // Set the DataContext if the data isn't a control.
  220. if (!(content is IControl))
  221. {
  222. DataContext = content;
  223. }
  224. else
  225. {
  226. ClearValue(DataContextProperty);
  227. }
  228. // Update the Child.
  229. if (newChild == null)
  230. {
  231. Child = null;
  232. }
  233. else if (newChild != oldChild)
  234. {
  235. ((ISetInheritanceParent)newChild).SetParent(this);
  236. Child = newChild;
  237. if (!logicalChildren.Contains(newChild))
  238. {
  239. logicalChildren.Add(newChild);
  240. }
  241. VisualChildren.Add(newChild);
  242. }
  243. _createdChild = true;
  244. }
  245. /// <inheritdoc/>
  246. protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
  247. {
  248. base.OnAttachedToLogicalTree(e);
  249. _recyclingDataTemplate = null;
  250. _createdChild = false;
  251. InvalidateMeasure();
  252. }
  253. /// <inheritdoc/>
  254. public override void Render(DrawingContext context)
  255. {
  256. _borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
  257. BoxShadow);
  258. }
  259. /// <summary>
  260. /// Creates the child control.
  261. /// </summary>
  262. /// <returns>The child control or null.</returns>
  263. protected virtual IControl CreateChild()
  264. {
  265. var content = Content;
  266. var oldChild = Child;
  267. var newChild = content as IControl;
  268. if (content != null && newChild == null)
  269. {
  270. var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
  271. if (dataTemplate is IRecyclingDataTemplate rdt)
  272. {
  273. var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
  274. newChild = rdt.Build(content, toRecycle);
  275. _recyclingDataTemplate = rdt;
  276. }
  277. else
  278. {
  279. newChild = dataTemplate.Build(content);
  280. _recyclingDataTemplate = null;
  281. }
  282. }
  283. else
  284. {
  285. _recyclingDataTemplate = null;
  286. }
  287. return newChild;
  288. }
  289. /// <inheritdoc/>
  290. protected override Size MeasureOverride(Size availableSize)
  291. {
  292. return LayoutHelper.MeasureChild(Child, availableSize, Padding, BorderThickness);
  293. }
  294. /// <inheritdoc/>
  295. protected override Size ArrangeOverride(Size finalSize)
  296. {
  297. return ArrangeOverrideImpl(finalSize, new Vector());
  298. }
  299. internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
  300. {
  301. if (Child == null) return finalSize;
  302. var padding = Padding + BorderThickness;
  303. var horizontalContentAlignment = HorizontalContentAlignment;
  304. var verticalContentAlignment = VerticalContentAlignment;
  305. var useLayoutRounding = UseLayoutRounding;
  306. var availableSize = finalSize;
  307. var sizeForChild = availableSize;
  308. var scale = GetLayoutScale();
  309. var originX = offset.X;
  310. var originY = offset.Y;
  311. if (horizontalContentAlignment != HorizontalAlignment.Stretch)
  312. {
  313. sizeForChild = sizeForChild.WithWidth(Math.Min(sizeForChild.Width, DesiredSize.Width));
  314. }
  315. if (verticalContentAlignment != VerticalAlignment.Stretch)
  316. {
  317. sizeForChild = sizeForChild.WithHeight(Math.Min(sizeForChild.Height, DesiredSize.Height));
  318. }
  319. if (useLayoutRounding)
  320. {
  321. sizeForChild = LayoutHelper.RoundLayoutSize(sizeForChild, scale, scale);
  322. availableSize = LayoutHelper.RoundLayoutSize(availableSize, scale, scale);
  323. }
  324. switch (horizontalContentAlignment)
  325. {
  326. case HorizontalAlignment.Center:
  327. originX += (availableSize.Width - sizeForChild.Width) / 2;
  328. break;
  329. case HorizontalAlignment.Right:
  330. originX += availableSize.Width - sizeForChild.Width;
  331. break;
  332. }
  333. switch (verticalContentAlignment)
  334. {
  335. case VerticalAlignment.Center:
  336. originY += (availableSize.Height - sizeForChild.Height) / 2;
  337. break;
  338. case VerticalAlignment.Bottom:
  339. originY += availableSize.Height - sizeForChild.Height;
  340. break;
  341. }
  342. if (useLayoutRounding)
  343. {
  344. originX = LayoutHelper.RoundLayoutValue(originX, scale);
  345. originY = LayoutHelper.RoundLayoutValue(originY, scale);
  346. }
  347. var boundsForChild =
  348. new Rect(originX, originY, sizeForChild.Width, sizeForChild.Height).Deflate(padding);
  349. Child.Arrange(boundsForChild);
  350. return finalSize;
  351. }
  352. /// <summary>
  353. /// Called when the <see cref="Content"/> property changes.
  354. /// </summary>
  355. /// <param name="e">The event args.</param>
  356. private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
  357. {
  358. _createdChild = false;
  359. if (((ILogical)this).IsAttachedToLogicalTree)
  360. {
  361. UpdateChild();
  362. }
  363. else if (Child != null)
  364. {
  365. VisualChildren.Remove(Child);
  366. LogicalChildren.Remove(Child);
  367. ((ISetInheritanceParent)Child).SetParent(Child.Parent);
  368. Child = null;
  369. _recyclingDataTemplate = null;
  370. }
  371. InvalidateMeasure();
  372. }
  373. private double GetLayoutScale()
  374. {
  375. var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
  376. if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
  377. {
  378. throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
  379. }
  380. return result;
  381. }
  382. private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
  383. {
  384. var host = e.NewValue as IContentPresenterHost;
  385. Host = host?.RegisterContentPresenter(this) == true ? host : null;
  386. }
  387. }
  388. }