// 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);
}
}
}
}
}