// 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);
}
}
}