35 changed files with 416 additions and 432 deletions
@ -1,166 +0,0 @@ |
|||
// 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 |
|||
{ |
|||
/// <summary>
|
|||
/// Adds content control functionality to control classes.
|
|||
/// </summary>
|
|||
/// <para>
|
|||
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
|
|||
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
|
|||
/// keeps the control's logical children in sync with the content being displayed by the
|
|||
/// control.
|
|||
/// </para>
|
|||
public class ContentControlMixin |
|||
{ |
|||
private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions = |
|||
new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() => |
|||
new ConditionalWeakTable<TemplatedControl, IDisposable>()); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SelectableMixin"/> class.
|
|||
/// </summary>
|
|||
/// <typeparam name="TControl">The control type.</typeparam>
|
|||
/// <param name="content">The content property.</param>
|
|||
/// <param name="logicalChildrenSelector">
|
|||
/// Given an control of <typeparamref name="TControl"/> should return the control's
|
|||
/// logical children collection.
|
|||
/// </param>
|
|||
/// <param name="presenterName">
|
|||
/// The name of the content presenter in the control's template.
|
|||
/// </param>
|
|||
public static void Attach<TControl>( |
|||
AvaloniaProperty content, |
|||
Func<TControl, IAvaloniaList<ILogical>> logicalChildrenSelector, |
|||
string presenterName = "PART_ContentPresenter") |
|||
where TControl : TemplatedControl |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(content != null); |
|||
Contract.Requires<ArgumentNullException>(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<ILogical> 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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,107 +0,0 @@ |
|||
// 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.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls.Mixins; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.LogicalTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests.Mixins |
|||
{ |
|||
public class ContentControlMixinTests |
|||
{ |
|||
[Fact] |
|||
public void Multiple_Mixin_Usages_Should_Not_Throw() |
|||
{ |
|||
var target = new TestControl() |
|||
{ |
|||
Template = new FuncControlTemplate((_, scope) => new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope), |
|||
new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope) |
|||
} |
|||
}) |
|||
}; |
|||
|
|||
|
|||
var ex = Record.Exception(() => target.ApplyTemplate()); |
|||
|
|||
Assert.Null(ex); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Replacing_Template_Releases_Events() |
|||
{ |
|||
var p1 = new ContentPresenter { Name = "Content_1_Presenter" }; |
|||
var p2 = new ContentPresenter { Name = "Content_2_Presenter" }; |
|||
var target = new TestControl |
|||
{ |
|||
Template = new FuncControlTemplate((_, scope) => new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
p1.RegisterInNameScope(scope), |
|||
p2.RegisterInNameScope(scope) |
|||
} |
|||
}) |
|||
}; |
|||
target.ApplyTemplate(); |
|||
|
|||
Control tc; |
|||
|
|||
p1.Content = tc = new Control(); |
|||
p1.UpdateChild(); |
|||
Assert.Contains(tc, target.GetLogicalChildren()); |
|||
|
|||
p2.Content = tc = new Control(); |
|||
p2.UpdateChild(); |
|||
Assert.Contains(tc, target.GetLogicalChildren()); |
|||
|
|||
target.Template = null; |
|||
|
|||
p1.Content = tc = new Control(); |
|||
p1.UpdateChild(); |
|||
Assert.DoesNotContain(tc, target.GetLogicalChildren()); |
|||
|
|||
p2.Content = tc = new Control(); |
|||
p2.UpdateChild(); |
|||
Assert.DoesNotContain(tc, target.GetLogicalChildren()); |
|||
|
|||
} |
|||
|
|||
private class TestControl : TemplatedControl |
|||
{ |
|||
public static readonly StyledProperty<object> Content1Property = |
|||
AvaloniaProperty.Register<TestControl, object>(nameof(Content1)); |
|||
|
|||
public static readonly StyledProperty<object> Content2Property = |
|||
AvaloniaProperty.Register<TestControl, object>(nameof(Content2)); |
|||
|
|||
static TestControl() |
|||
{ |
|||
ContentControlMixin.Attach<TestControl>(Content1Property, x => x.LogicalChildren, "Content_1_Presenter"); |
|||
ContentControlMixin.Attach<TestControl>(Content2Property, x => x.LogicalChildren, "Content_2_Presenter"); |
|||
} |
|||
|
|||
public object Content1 |
|||
{ |
|||
get { return GetValue(Content1Property); } |
|||
set { SetValue(Content1Property, value); } |
|||
} |
|||
|
|||
public object Content2 |
|||
{ |
|||
get { return GetValue(Content2Property); } |
|||
set { SetValue(Content2Property, value); } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue