A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

212 lines
7.1 KiB

using System;
using System.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
/// Base class for Avalonia controls.
/// </summary>
/// <remarks>
/// The control class extends <see cref="InputElement"/> and adds the following features:
///
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// </remarks>
public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, ISetterValue
{
/// <summary>
/// Defines the <see cref="FocusAdorner"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IControl>?> FocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<IControl>?>(nameof(FocusAdorner));
/// <summary>
/// Defines the <see cref="Tag"/> property.
/// </summary>
public static readonly StyledProperty<object?> TagProperty =
AvaloniaProperty.Register<Control, object?>(nameof(Tag));
/// <summary>
/// Defines the <see cref="ContextMenu"/> property.
/// </summary>
public static readonly StyledProperty<ContextMenu?> ContextMenuProperty =
AvaloniaProperty.Register<Control, ContextMenu?>(nameof(ContextMenu));
/// <summary>
/// Defines the <see cref="ContextFlyout"/> property
/// </summary>
public static readonly StyledProperty<FlyoutBase?> ContextFlyoutProperty =
AvaloniaProperty.Register<Control, FlyoutBase?>(nameof(ContextFlyout));
/// <summary>
/// Event raised when an element wishes to be scrolled into view.
/// </summary>
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
/// <summary>
/// Gets or sets the control's focus adorner.
/// </summary>
public ITemplate<IControl>? FocusAdorner
{
get => GetValue(FocusAdornerProperty);
set => SetValue(FocusAdornerProperty, value);
}
/// <summary>
/// Gets or sets the data templates for the control.
/// </summary>
/// <remarks>
/// Each control may define data templates which are applied to the control itself and its
/// children.
/// </remarks>
public DataTemplates DataTemplates => _dataTemplates ??= new DataTemplates();
/// <summary>
/// Gets or sets a context menu to the control.
/// </summary>
public ContextMenu? ContextMenu
{
get => GetValue(ContextMenuProperty);
set => SetValue(ContextMenuProperty, value);
}
/// <summary>
/// Gets or sets a context flyout to the control
/// </summary>
public FlyoutBase? ContextFlyout
{
get => GetValue(ContextFlyoutProperty);
set => SetValue(ContextFlyoutProperty, value);
}
/// <summary>
/// Gets or sets a user-defined object attached to the control.
/// </summary>
public object? Tag
{
get => GetValue(TagProperty);
set => SetValue(TagProperty, value);
}
public new IControl? Parent => (IControl?)base.Parent;
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
if (setter is Setter s && s.Property == ContextFlyoutProperty)
{
return; // Allow ContextFlyout to not need wrapping in <Template>
}
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{
if (VisualRoot == null)
{
if (!IsInitialized)
{
foreach (var i in this.GetSelfAndVisualDescendants())
{
var c = i as IControl;
if (c?.IsInitialized == false && c is ISupportInitialize init)
{
init.BeginInit();
init.EndInit();
}
}
}
if (!IsArrangeValid)
{
Measure(Size.Infinity);
Arrange(new Rect(DesiredSize));
}
}
}
/// <summary>
/// Gets the element that receives the focus adorner.
/// </summary>
/// <returns>The control that receives the focus adorner.</returns>
protected virtual IControl? GetTemplateFocusTarget() => this;
/// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
InitializeIfNeeded();
}
/// <inheritdoc/>
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
if (IsFocused &&
(e.NavigationMethod == NavigationMethod.Tab ||
e.NavigationMethod == NavigationMethod.Directional))
{
var adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null)
{
if (_focusAdorner == null)
{
var template = GetValue(FocusAdornerProperty);
if (template != null)
{
_focusAdorner = template.Build();
}
}
if (_focusAdorner != null && GetTemplateFocusTarget() is Visual target)
{
AdornerLayer.SetAdornedElement((Visual)_focusAdorner, target);
adornerLayer.Children.Add(_focusAdorner);
}
}
}
}
/// <inheritdoc/>
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (_focusAdorner?.Parent != null)
{
var adornerLayer = (IPanel)_focusAdorner.Parent;
adornerLayer.Children.Remove(_focusAdorner);
_focusAdorner = null;
}
}
}
}