diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index 58def8bd32..4d12d275b8 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -94,7 +94,9 @@ namespace Perspex.Collections public IEnumerator GetEnumerator() { - return this.source.GetEnumerator(); + return (this.source != null) ? + this.source.GetEnumerator() : + Enumerable.Empty().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index e10a600a63..6b7c673538 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -24,11 +24,7 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register("VerticalContentAlignment"); - private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); - - private ContentPresenter presenter; - - private IDisposable presenterSubscription; + private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); public ContentControl() { @@ -55,26 +51,23 @@ namespace Perspex.Controls IReadOnlyPerspexList ILogical.LogicalChildren { - get { return this.logicalChild; } + get { return this.logicalChildren; } } protected override void OnTemplateApplied() { - if (this.presenterSubscription != null) - { - this.presenterSubscription.Dispose(); - this.presenterSubscription = null; - } - // We allow ContentControls without ContentPresenters in the template. This can be // useful for e.g. a simple ToggleButton that displays an image. There's no need to // have a ContentPresenter in the visual tree for that. - this.presenter = this.FindTemplateChild("contentPresenter"); + var presenter = this.FindTemplateChild("contentPresenter"); - if (this.presenter != null) + if (presenter != null) + { + this.logicalChildren.Source = ((ILogical)presenter).LogicalChildren; + } + else { - this.presenterSubscription = this.presenter.ChildObservable - .Subscribe(x => this.logicalChild.SingleItem = x); + this.logicalChildren.Source = null; } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index 1a36bd9b34..1d34a095a8 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -27,11 +27,7 @@ namespace Perspex.Controls public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.Register("IsDropDownOpen"); - private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); - - private ContentPresenter presenter; - - private IDisposable presenterSubscription; + private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); public DropDown() { @@ -65,26 +61,23 @@ namespace Perspex.Controls IReadOnlyPerspexList ILogical.LogicalChildren { - get { return this.logicalChild; } + get { return this.logicalChildren; } } protected override void OnTemplateApplied() { - if (this.presenterSubscription != null) + var presenter = this.FindTemplateChild("contentPresenter"); + + if (presenter != null) { - this.presenterSubscription.Dispose(); - this.presenterSubscription = null; + this.logicalChildren.Source = ((ILogical)presenter).LogicalChildren; } - - this.presenter = this.FindTemplateChild("contentPresenter"); - - if (this.presenter != null) + else { - this.presenterSubscription = this.presenter.ChildObservable - .Subscribe(x => this.logicalChild.SingleItem = x); + this.logicalChildren.Source = null; } } - + private void SetContentParent(Tuple change) { var control1 = change.Item1 as Control; diff --git a/Perspex.Controls/Presenters/ContentPresenter.cs b/Perspex.Controls/Presenters/ContentPresenter.cs index be4e74a782..da4db2fc04 100644 --- a/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/Perspex.Controls/Presenters/ContentPresenter.cs @@ -10,19 +10,18 @@ namespace Perspex.Controls.Presenters using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; + using Perspex.Collections; using Perspex.Controls.Primitives; using Perspex.Media; - public class ContentPresenter : Control, IVisual, IPresenter + public class ContentPresenter : Control, IVisual, ILogical, IPresenter { public static readonly PerspexProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); private bool createdChild; - private Control child; - - private BehaviorSubject childObservable = new BehaviorSubject(null); + private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); public ContentPresenter() { @@ -31,31 +30,7 @@ namespace Perspex.Controls.Presenters public Control Child { - get - { - return this.child; - } - - private set - { - if (this.child != value) - { - this.ClearVisualChildren(); - this.child = value; - - if (value != null) - { - this.AddVisualChild(value); - } - - this.childObservable.OnNext(value); - } - } - } - - public IObservable ChildObservable - { - get { return this.childObservable; } + get { return (Control)this.logicalChild.SingleItem; } } public object Content @@ -64,6 +39,11 @@ namespace Perspex.Controls.Presenters set { this.SetValue(ContentProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } + } + public override sealed void ApplyTemplate() { if (!this.createdChild) @@ -79,10 +59,12 @@ namespace Perspex.Controls.Presenters protected override Size MeasureOverride(Size availableSize) { - if (this.child != null) + var child = this.Child; + + if (child != null) { - this.child.Measure(availableSize); - return this.child.DesiredSize.Value; + child.Measure(availableSize); + return child.DesiredSize.Value; } return new Size(); @@ -124,17 +106,18 @@ namespace Perspex.Controls.Presenters } } - var foo = this.TemplatedParent as TemplatedControl; + var templatedParent = this.TemplatedParent as TemplatedControl; - if (foo != null) + if (templatedParent != null) { - foo = foo.TemplatedParent as TemplatedControl; + templatedParent = templatedParent.TemplatedParent as TemplatedControl; } - result.TemplatedParent = foo; + result.TemplatedParent = templatedParent; + this.AddVisualChild(result); } - this.Child = result; + this.logicalChild.SingleItem = result; this.createdChild = true; } } diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index b2973efc74..7f4c6cbe03 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -15,7 +15,7 @@ namespace Perspex.Controls using Perspex.Controls.Primitives; using Perspex.Controls.Templates; - public class TabControl : SelectingItemsControl + public class TabControl : SelectingItemsControl, ILogical { public static readonly PerspexProperty SelectedContentProperty = PerspexProperty.Register("SelectedContent"); @@ -50,6 +50,11 @@ namespace Perspex.Controls set { this.SetValue(SelectedTabProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChildren; } + } + protected override void OnTemplateApplied() { var presenter = this.GetTemplateChild("contentPresenter"); diff --git a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs index 6a186c183b..d773601f7d 100644 --- a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -150,13 +150,20 @@ namespace Perspex.Controls.UnitTests [Fact] public void Clearing_Content_Should_Remove_From_LogicalChildren() { - var contentControl = new ContentControl(); + var target = new ContentControl(); var child = new Control(); - contentControl.Content = child; - contentControl.Content = null; + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + target.Content = null; + + // Need to call ApplyTemplate on presenter for LogocalChildren to be updated. + var presenter = target.GetTemplateChildren().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); - Assert.Equal(new ILogical[0], ((ILogical)contentControl).LogicalChildren.ToList()); + Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); } [Fact] diff --git a/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs b/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs new file mode 100644 index 0000000000..c5bdedd656 --- /dev/null +++ b/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs @@ -0,0 +1,86 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System; + using System.Collections.Specialized; + using System.Linq; + using Moq; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Controls.Templates; + using Perspex.Layout; + using Perspex.Platform; + using Perspex.Styling; + using Perspex.VisualTree; + using Ploeh.AutoFixture; + using Ploeh.AutoFixture.AutoMoq; + using Splat; + using Xunit; + + public class ContentPresenterTests + { + [Fact] + public void Setting_Content_Should_Make_Control_Appear_In_LogicalChildren() + { + var target = new ContentPresenter(); + var child = new Control(); + + target.Content = child; + target.ApplyTemplate(); + + Assert.Equal(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + } + + [Fact] + public void Clearing_Content_Should_Remove_From_LogicalChildren() + { + var target = new ContentPresenter(); + var child = new Control(); + + target.Content = child; + target.Content = null; + + Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + } + + [Fact] + public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ContentPresenter(); + var child = new Control(); + var called = false; + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + target.Content = child; + target.ApplyTemplate(); + + Assert.True(called); + } + + [Fact] + public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ContentPresenter(); + var child = new Control(); + var called = false; + + target.Content = child; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + target.Content = null; + target.ApplyTemplate(); + + Assert.True(called); + } + } +} diff --git a/Tests/Perspex.Controls.UnitTests/DropDownTests.cs b/Tests/Perspex.Controls.UnitTests/DropDownTests.cs index 182a756790..42f6d7786f 100644 --- a/Tests/Perspex.Controls.UnitTests/DropDownTests.cs +++ b/Tests/Perspex.Controls.UnitTests/DropDownTests.cs @@ -154,6 +154,7 @@ namespace Perspex.Controls.UnitTests target.Content = child; target.Content = null; + target.ApplyTemplate(); Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); } diff --git a/Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 0ff2d44095..917b4c3418 100644 --- a/Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -79,6 +79,7 @@ + diff --git a/Tests/Perspex.Controls.UnitTests/TabControlTests.cs b/Tests/Perspex.Controls.UnitTests/TabControlTests.cs index 6e7ac6f2bc..f978443f0b 100644 --- a/Tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/TabControlTests.cs @@ -136,7 +136,7 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); Assert.Equal(1, target.GetLogicalChildren().Count()); - Assert.Equal("Foo", ((TabItem)target.GetLogicalChildren().First()).Id); + Assert.Equal("Foo", ((TextBlock)target.GetLogicalChildren().First()).Id); } private Control CreateTabControlTemplate(TabControl parent)