using System;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Styling;
#nullable enable
namespace Avalonia.Controls.Primitives
{
///
/// A lookless control whose visual appearance is defined by its .
///
public class TemplatedControl : Control, ITemplatedControl
{
///
/// Defines the property.
///
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty BorderBrushProperty =
Border.BorderBrushProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty PaddingProperty =
Decorator.PaddingProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty TemplateProperty =
AvaloniaProperty.Register(nameof(Template));
///
/// Defines the IsTemplateFocusTarget attached property.
///
public static readonly AttachedProperty IsTemplateFocusTargetProperty =
AvaloniaProperty.RegisterAttached("IsTemplateFocusTarget");
///
/// Defines the routed event.
///
public static readonly RoutedEvent TemplateAppliedEvent =
RoutedEvent.Register(
"TemplateApplied",
RoutingStrategies.Direct);
private IControlTemplate? _appliedTemplate;
///
/// Initializes static members of the class.
///
static TemplatedControl()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
TemplateProperty.Changed.AddClassHandler((x, e) => x.OnTemplateChanged(e));
}
///
/// Raised when the control's template is applied.
///
public event EventHandler TemplateApplied
{
add { AddHandler(TemplateAppliedEvent, value); }
remove { RemoveHandler(TemplateAppliedEvent, value); }
}
///
/// Gets or sets the brush used to draw the control's background.
///
public IBrush? Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
///
/// Gets or sets the brush used to draw the control's border.
///
public IBrush? BorderBrush
{
get { return GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
}
///
/// Gets or sets the thickness of the control's border.
///
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
}
///
/// Gets or sets the radius of the border rounded corners.
///
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
///
/// Gets or sets the font family used to draw the control's text.
///
public FontFamily FontFamily
{
get { return GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
///
/// Gets or sets the size of the control's text in points.
///
public double FontSize
{
get { return GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
///
/// Gets or sets the font style used to draw the control's text.
///
public FontStyle FontStyle
{
get { return GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
///
/// Gets or sets the font weight used to draw the control's text.
///
public FontWeight FontWeight
{
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
///
/// Gets or sets the brush used to draw the control's text and other foreground elements.
///
public IBrush? Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
///
/// Gets or sets the padding placed between the border of the control and its content.
///
public Thickness Padding
{
get { return GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
///
/// Gets or sets the template that defines the control's appearance.
///
public IControlTemplate? Template
{
get { return GetValue(TemplateProperty); }
set { SetValue(TemplateProperty, value); }
}
///
/// Gets the value of the IsTemplateFocusTargetProperty attached property on a control.
///
/// The control.
/// The property value.
///
public static bool GetIsTemplateFocusTarget(Control control)
{
return control.GetValue(IsTemplateFocusTargetProperty);
}
///
/// Sets the value of the IsTemplateFocusTargetProperty attached property on a control.
///
/// The control.
/// The property value.
///
/// When a control is navigated to using the keyboard, a focus adorner is shown - usually
/// around the control itself. However if the TemplatedControl.IsTemplateFocusTarget
/// attached property is set to true on an element in the control template, then the focus
/// adorner will be shown around that control instead.
///
public static void SetIsTemplateFocusTarget(Control control, bool value)
{
control.SetValue(IsTemplateFocusTargetProperty, value);
}
///
public sealed override void ApplyTemplate()
{
var template = Template;
var logical = (ILogical)this;
// Apply the template if it is not the same as the template already applied - except
// for in the case that the template is null and we're not attached to the logical
// tree. In that case, the template has probably been cleared because the style setting
// the template has been detached, so we want to wait until it's re-attached to the
// logical tree as if it's re-attached to the same tree the template will be the same
// and we don't need to do anything.
if (_appliedTemplate != template && (template != null || logical.IsAttachedToLogicalTree))
{
if (VisualChildren.Count > 0)
{
foreach (var child in this.GetTemplateChildren())
{
child.SetValue(TemplatedParentProperty, null);
((ISetLogicalParent)child).SetParent(null);
}
VisualChildren.Clear();
}
if (template != null)
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
// Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null)
nameScope = new NameScope();
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
#pragma warning disable CS0618 // Type or member is obsolete
OnTemplateApplied(e);
#pragma warning restore CS0618 // Type or member is obsolete
RaiseEvent(e);
}
_appliedTemplate = template;
}
}
///
protected override IControl GetTemplateFocusTarget()
{
foreach (Control child in this.GetTemplateChildren())
{
if (GetIsTemplateFocusTarget(child))
{
return child;
}
}
return this;
}
protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{
var count = VisualChildren.Count;
for (var i = 0; i < count; ++i)
{
if (VisualChildren[i] is ILogical logical)
{
logical.NotifyResourcesChanged(e);
}
}
base.NotifyChildResourcesChanged(e);
}
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
///
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyDetachedFromLogicalTree(e);
}
base.OnDetachedFromLogicalTree(e);
}
protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e)
{
}
///
/// Called when the control's template is applied.
///
/// The event args.
[Obsolete("Use OnApplyTemplate")]
protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e)
{
}
///
/// Called when the property changes.
///
/// The event args.
protected virtual void OnTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{
InvalidateMeasure();
}
///
/// Sets the TemplatedParent property for the created template children.
///
/// The control.
private void ApplyTemplatedParent(IControl control)
{
control.SetValue(TemplatedParentProperty, this);
var children = control.LogicalChildren;
var count = children.Count;
for (var i = 0; i < count; i++)
{
if (children[i] is IControl child)
{
ApplyTemplatedParent(child);
}
}
}
}
}