// 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 Avalonia.Controls.Templates; using Avalonia.Interactivity; using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; 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 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 => x.OnTemplateChanged); } /// /// 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 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.Verbose(LogArea.Control, this, "Creating control template"); var child = template.Build(this); var nameScope = new NameScope(); NameScope.SetNameScope((Control)child, nameScope); ApplyTemplatedParent(child); RegisterNames(child, nameScope); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); OnTemplateApplied(new TemplateAppliedEventArgs(nameScope)); } _appliedTemplate = template; } } /// protected override IControl GetTemplateFocusTarget() { foreach (Control child in this.GetTemplateChildren()) { if (GetIsTemplateFocusTarget(child)) { return child; } } return this; } /// 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); } /// /// Called when the control's template is applied. /// /// The event args. protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e) { RaiseEvent(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); foreach (var child in control.LogicalChildren) { if (child is IControl c) { ApplyTemplatedParent(c); } } } /// /// Registers each control with its name scope. /// /// The control. /// The name scope. private void RegisterNames(IControl control, INameScope nameScope) { if (control.Name != null) { nameScope.Register(control.Name, control); } if (control.TemplatedParent == this) { foreach (IControl child in control.GetLogicalChildren()) { RegisterNames(child, nameScope); } } } } }