// 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.Linq; using System.Reactive.Disposables; using System.Runtime.CompilerServices; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; using Avalonia.LogicalTree; namespace Avalonia.Controls.Mixins { /// /// Adds content control functionality to control classes. /// /// /// The adds behavior to a control which acts as a content /// control such as and . It /// keeps the control's logical children in sync with the content being displayed by the /// control. /// public class ContentControlMixin { private static Lazy> subscriptions = new Lazy>(() => new ConditionalWeakTable()); /// /// Initializes a new instance of the class. /// /// The control type. /// The content property. /// /// Given an control of should return the control's /// logical children collection. /// /// /// The name of the content presenter in the control's template. /// public static void Attach( AvaloniaProperty content, Func> logicalChildrenSelector, string presenterName = "PART_ContentPresenter") where TControl : TemplatedControl { Contract.Requires(content != null); Contract.Requires(logicalChildrenSelector != null); void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e) { if (s is IControl sender && sender?.TemplatedParent is TControl parent) { UpdateLogicalChild( sender, logicalChildrenSelector(parent), e.OldValue, null); } } void TemplateApplied(object s, RoutedEventArgs ev) { if (s is TControl sender) { var e = (TemplateAppliedEventArgs)ev; var presenter = e.NameScope.Find(presenterName) as IContentPresenter; if (presenter != null) { presenter.ApplyTemplate(); var logicalChildren = logicalChildrenSelector(sender); var subscription = new CompositeDisposable(); presenter.ChildChanging += ChildChanging; subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging)); subscription.Add(presenter .GetPropertyChangedObservable(ContentPresenter.ChildProperty) .Subscribe(c => UpdateLogicalChild( sender, logicalChildren, null, c.NewValue))); UpdateLogicalChild( sender, logicalChildren, null, presenter.GetValue(ContentPresenter.ChildProperty)); if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription)) { subscription = new CompositeDisposable(previousSubscription, subscription); subscriptions.Value.Remove(sender); } subscriptions.Value.Add(sender, subscription); } } } TemplatedControl.TemplateAppliedEvent.AddClassHandler( typeof(TControl), TemplateApplied, RoutingStrategies.Direct); content.Changed.Subscribe(e => { if (e.Sender is TControl sender) { var logicalChildren = logicalChildrenSelector(sender); UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue); } }); Control.TemplatedParentProperty.Changed.Subscribe(e => { if (e.Sender is TControl sender) { var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl; logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent); } }); TemplatedControl.TemplateProperty.Changed.Subscribe(e => { if (e.Sender is TControl sender) { if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription)) { subscription.Dispose(); subscriptions.Value.Remove(sender); } } }); } private static void UpdateLogicalChild( IControl control, IAvaloniaList logicalChildren, object oldValue, object newValue) { if (oldValue != newValue) { if (oldValue is IControl child) { logicalChildren.Remove(child); ((ISetInheritanceParent)child).SetParent(child.Parent); } child = newValue as IControl; if (child != null && !logicalChildren.Contains(child)) { child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent); logicalChildren.Add(child); } } } } }