using System; using System.Diagnostics; using System.Globalization; using Avalonia.Controls; using Avalonia.Reactive; namespace Avalonia.Xaml.Interactivity; /// /// A base class for behaviors, implementing the basic plumbing of . /// public abstract class StyledElementBehavior : StyledElement, IBehavior, IBehaviorEventsHandler { private IDisposable? _dataContextDisposable; /// /// Identifies the avalonia property. /// public static readonly StyledProperty IsEnabledProperty = AvaloniaProperty.Register(nameof(IsEnabled), defaultValue: true); /// /// Gets the to which the behavior is attached. /// public AvaloniaObject? AssociatedObject { get; private set; } /// /// Gets the to which this behavior is attached. /// public StyledElement? AssociatedStyledElement => AssociatedObject as StyledElement; /// /// Gets or sets a value indicating whether this instance is enabled. /// /// true if this instance is enabled; otherwise, false. public bool IsEnabled { get => GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } /// /// Attaches the behavior to the specified . /// /// The to which to attach. /// is null. public void Attach(AvaloniaObject? associatedObject) { if (Equals(associatedObject, AssociatedObject)) { return; } if (AssociatedObject is not null) { throw new InvalidOperationException(string.Format( CultureInfo.CurrentCulture, "An instance of a behavior cannot be attached to more than one object at a time.")); } Debug.Assert(associatedObject is not null, "Cannot attach the behavior to a null object."); AssociatedObject = associatedObject ?? throw new ArgumentNullException(nameof(associatedObject)); _dataContextDisposable = SynchronizeDataContext(associatedObject); OnAttached(); } /// /// Detaches the behaviors from the . /// public void Detach() { OnDetaching(); _dataContextDisposable?.Dispose(); AssociatedObject = null; } /// /// Called after the behavior is attached to the . /// /// /// Override this to hook up functionality to the /// protected virtual void OnAttached() { } /// /// Called when the behavior is being detached from its . /// /// /// Override this to unhook functionality from the /// protected virtual void OnDetaching() { } void IBehaviorEventsHandler.AttachedToVisualTreeEventHandler() { AttachBehaviorToLogicalTree(); OnAttachedToVisualTree(); } void IBehaviorEventsHandler.DetachedFromVisualTreeEventHandler() { DetachBehaviorFromLogicalTree(); OnDetachedFromVisualTree(); } void IBehaviorEventsHandler.AttachedToLogicalTreeEventHandler() { AttachBehaviorToLogicalTree(); OnAttachedToLogicalTree(); } void IBehaviorEventsHandler.DetachedFromLogicalTreeEventHandler() { DetachBehaviorFromLogicalTree(); OnDetachedFromLogicalTree(); } void IBehaviorEventsHandler.LoadedEventHandler() => OnLoaded(); void IBehaviorEventsHandler.UnloadedEventHandler() => OnUnloaded(); /// /// Called after the is attached to the visual tree. /// /// /// Invoked only when the is of type . /// protected virtual void OnAttachedToVisualTree() { } /// /// Called when the is being detached from the visual tree. /// /// /// Invoked only when the is of type . /// protected virtual void OnDetachedFromVisualTree() { } /// /// Called after the is attached to the logical tree. /// /// /// Invoked only when the is of type . /// protected virtual void OnAttachedToLogicalTree() { } /// /// Called when the is being detached from the logical tree. /// /// /// Invoked only when the is of type . /// protected virtual void OnDetachedFromLogicalTree() { } /// /// Called after the is loaded. /// /// /// Invoked only when the is of type . /// protected virtual void OnLoaded() { } /// /// Called when the is unloaded. /// /// /// Invoked only when the is of type . /// protected virtual void OnUnloaded() { } private void AttachBehaviorToLogicalTree() { if (AssociatedObject is not StyledElement styledElement) { return; } if (styledElement.Parent is not null) { ((ISetLogicalParent)this).SetParent(null); ((ISetLogicalParent)this).SetParent(styledElement); } } private void DetachBehaviorFromLogicalTree() { ((ISetLogicalParent)this).SetParent(null); } private IDisposable? SynchronizeDataContext(AvaloniaObject associatedObject) { if (associatedObject is StyledElement styledElement) { return styledElement .GetObservable(DataContextProperty) .Subscribe(new AnonymousObserver(x => { SetCurrentValue(DataContextProperty, x); })); } return default; } }