// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Reactive.Linq; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.VisualTree; namespace Avalonia.Controls.Presenters { /// /// Presents a single item of data inside a template. /// public class ContentPresenter : Control, IContentPresenter { /// /// Defines the property. /// public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); /// /// Defines the property. /// public static readonly AvaloniaProperty BorderBrushProperty = Border.BorderBrushProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(); /// /// Defines the property. /// public static readonly DirectProperty ChildProperty = AvaloniaProperty.RegisterDirect( nameof(Child), o => o.Child); /// /// Defines the property. /// public static readonly StyledProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty ContentTemplateProperty = ContentControl.ContentTemplateProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty HorizontalContentAlignmentProperty = ContentControl.HorizontalContentAlignmentProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty VerticalContentAlignmentProperty = ContentControl.VerticalContentAlignmentProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PaddingProperty = Border.PaddingProperty.AddOwner(); private IControl _child; private bool _createdChild; private IDataTemplate _dataTemplate; /// /// Initializes static members of the class. /// static ContentPresenter() { ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); } /// /// Initializes a new instance of the class. /// public ContentPresenter() { } /// /// Gets or sets a brush with which to paint the background. /// public IBrush Background { get { return GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } } /// /// Gets or sets a brush with which to paint the border. /// public IBrush BorderBrush { get { return GetValue(BorderBrushProperty); } set { SetValue(BorderBrushProperty, value); } } /// /// Gets or sets the thickness of the border. /// public double BorderThickness { get { return GetValue(BorderThicknessProperty); } set { SetValue(BorderThicknessProperty, value); } } /// /// Gets the control displayed by the presenter. /// public IControl Child { get { return _child; } private set { SetAndRaise(ChildProperty, ref _child, value); } } /// /// Gets or sets the content to be displayed by the presenter. /// public object Content { get { return GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } /// /// Gets or sets the data template used to display the content of the control. /// public IDataTemplate ContentTemplate { get { return GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } } /// /// Gets or sets the radius of the border rounded corners. /// public float CornerRadius { get { return GetValue(CornerRadiusProperty); } set { SetValue(CornerRadiusProperty, value); } } /// /// Gets or sets the horizontal alignment of the content within the control. /// public HorizontalAlignment HorizontalContentAlignment { get { return GetValue(HorizontalContentAlignmentProperty); } set { SetValue(HorizontalContentAlignmentProperty, value); } } /// /// Gets or sets the vertical alignment of the content within the control. /// public VerticalAlignment VerticalContentAlignment { get { return GetValue(VerticalContentAlignmentProperty); } set { SetValue(VerticalContentAlignmentProperty, value); } } /// /// Gets or sets the padding to place around the control. /// public Thickness Padding { get { return GetValue(PaddingProperty); } set { SetValue(PaddingProperty, value); } } /// public override sealed void ApplyTemplate() { if (!_createdChild && ((ILogical)this).IsAttachedToLogicalTree) { UpdateChild(); } } /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); _dataTemplate = null; } /// /// Updates the control based on the control's . /// /// /// Usually the control is created automatically when /// is called; however for this to happen, the control needs to /// be attached to a logical tree (if the control is not attached to the logical tree, it /// is reasonable to expect that the DataTemplates needed for the child are not yet /// available). This method forces the control's creation at any point, /// and is particularly useful in unit tests. /// public void UpdateChild() { var content = Content; var oldChild = Child; var newChild = CreateChild(); // Remove the old child if we're not recycling it. if (oldChild != null && newChild != oldChild) { VisualChildren.Remove(oldChild); } // Set the DataContext if the data isn't a control. if (!(content is IControl)) { DataContext = content; } else { ClearValue(DataContextProperty); } // Update the Child. if (newChild == null) { Child = null; } else if (newChild != oldChild) { ((ISetInheritanceParent)newChild).SetParent(this); Child = newChild; if (oldChild?.Parent == this) { LogicalChildren.Remove(oldChild); } if (newChild.Parent == null) { var templatedLogicalParent = TemplatedParent as ILogical; if (templatedLogicalParent != null) { ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent); } else { LogicalChildren.Add(newChild); } } VisualChildren.Add(newChild); } _createdChild = true; } /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); _createdChild = false; InvalidateMeasure(); } /// public override void Render(DrawingContext context) { var background = Background; var borderBrush = BorderBrush; var borderThickness = BorderThickness; var cornerRadius = CornerRadius; var rect = new Rect(Bounds.Size).Deflate(BorderThickness); if (background != null) { context.FillRectangle(background, rect, cornerRadius); } if (borderBrush != null && borderThickness > 0) { context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius); } } /// /// Creates the child control. /// /// The child control or null. protected virtual IControl CreateChild() { var content = Content; var oldChild = Child; var newChild = content as IControl; if (content != null && newChild == null) { var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default; // We have content and it isn't a control, so if the new data template is the same // as the old data template, try to recycle the existing child control to display // the new data. if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling) { newChild = oldChild; } else { _dataTemplate = dataTemplate; newChild = _dataTemplate.Build(content); // Give the new control its own name scope. if (newChild is Control controlResult) { NameScope.SetNameScope(controlResult, new NameScope()); } } } else { _dataTemplate = null; } return newChild; } /// protected override Size MeasureOverride(Size availableSize) { var child = Child; var padding = Padding + new Thickness(BorderThickness); if (child != null) { child.Measure(availableSize.Deflate(padding)); return child.DesiredSize.Inflate(padding); } else { return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top); } } /// protected override Size ArrangeOverride(Size finalSize) { var child = Child; if (child != null) { var padding = Padding + new Thickness(BorderThickness); var sizeMinusPadding = finalSize.Deflate(padding); var size = sizeMinusPadding; var horizontalAlignment = HorizontalContentAlignment; var verticalAlignment = VerticalContentAlignment; var originX = padding.Left; var originY = padding.Top; if (horizontalAlignment != HorizontalAlignment.Stretch) { size = size.WithWidth(child.DesiredSize.Width); } if (verticalAlignment != VerticalAlignment.Stretch) { size = size.WithHeight(child.DesiredSize.Height); } switch (horizontalAlignment) { case HorizontalAlignment.Stretch: case HorizontalAlignment.Center: originX += (sizeMinusPadding.Width - size.Width) / 2; break; case HorizontalAlignment.Right: originX = size.Width - child.DesiredSize.Width; break; } switch (verticalAlignment) { case VerticalAlignment.Stretch: case VerticalAlignment.Center: originY += (sizeMinusPadding.Height - size.Height) / 2; break; case VerticalAlignment.Bottom: originY = size.Height - child.DesiredSize.Height; break; } child.Arrange(new Rect(originX, originY, size.Width, size.Height)); } return finalSize; } /// /// Called when the property changes. /// /// The event args. private void ContentChanged(AvaloniaPropertyChangedEventArgs e) { _createdChild = false; if (((ILogical)this).IsAttachedToLogicalTree) { UpdateChild(); } else if (Child != null) { VisualChildren.Remove(Child); LogicalChildren.Remove(Child); Child = null; _dataTemplate = null; } InvalidateMeasure(); } private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) { (e.NewValue as IContentPresenterHost)?.RegisterContentPresenter(this); } } }