From 7a8a9123c3ada5ecd03211882961993f0fac76be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 23 Jan 2015 19:30:39 +0100 Subject: [PATCH] Implement ILogical on DropDown. --- Perspex.Controls.UnitTests/DropDownTests.cs | 254 ++++++++++++++++++ .../Perspex.Controls.UnitTests.csproj | 1 + Perspex.Controls/ContentControl.cs | 42 +-- Perspex.Controls/DropDown.cs | 53 +++- 4 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 Perspex.Controls.UnitTests/DropDownTests.cs diff --git a/Perspex.Controls.UnitTests/DropDownTests.cs b/Perspex.Controls.UnitTests/DropDownTests.cs new file mode 100644 index 0000000000..40a582821a --- /dev/null +++ b/Perspex.Controls.UnitTests/DropDownTests.cs @@ -0,0 +1,254 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System; + using System.Collections.Specialized; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Platform; + using Perspex.Styling; + using Perspex.VisualTree; + using Ploeh.AutoFixture; + using Ploeh.AutoFixture.AutoMoq; + using Splat; + + [TestClass] + public class DropDownTests + { + [TestMethod] + public void Template_Should_Be_Instantiated() + { + using (var ctx = this.RegisterServices()) + { + var target = new DropDown(); + target.Content = "Foo"; + target.Template = this.GetTemplate(); + + target.Measure(new Size(100, 100)); + + var child = ((IVisual)target).VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(Border)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(ContentPresenter)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(TextBlock)); + } + } + + [TestMethod] + public void Templated_Children_Should_Be_Styled() + { + using (var ctx = this.RegisterServices()) + { + var root = new TestRoot(); + var target = new DropDown(); + var styler = new Mock(); + + Locator.CurrentMutable.Register(() => styler.Object, typeof(IStyler)); + target.Content = "Foo"; + target.Template = this.GetTemplate(); + root.Content = target; + + target.ApplyTemplate(); + + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + } + } + + [TestMethod] + public void ContentPresenter_Should_Have_TemplatedParent_Set() + { + var target = new DropDown(); + var child = new Border(); + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + var contentPresenter = child.GetVisualParent(); + Assert.AreEqual(target, contentPresenter.TemplatedParent); + } + + [TestMethod] + public void Content_Should_Have_TemplatedParent_Set_To_Null() + { + var target = new DropDown(); + var child = new Border(); + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + Assert.IsNull(child.TemplatedParent); + } + + [TestMethod] + public void Setting_Content_Should_Set_Child_Controls_Parent() + { + var target = new DropDown(); + var child = new Control(); + + target.Content = child; + + Assert.AreEqual(child.Parent, target); + Assert.AreEqual(((ILogical)child).LogicalParent, target); + } + + [TestMethod] + public void Clearing_Content_Should_Clear_Child_Controls_Parent() + { + var target = new DropDown(); + var child = new Control(); + + target.Content = child; + target.Content = null; + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Setting_Content_To_Control_Should_Make_Control_Appear_In_LogicalChildren() + { + var target = new DropDown(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + CollectionAssert.AreEqual(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Content_To_String_Should_Make_TextBlock_Appear_In_LogicalChildren() + { + var target = new DropDown(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Content = "Foo"; + target.ApplyTemplate(); + + var logical = (ILogical)target; + Assert.AreEqual(1, logical.LogicalChildren.Count); + Assert.IsInstanceOfType(logical.LogicalChildren[0], typeof(TextBlock)); + } + + [TestMethod] + public void Clearing_Content_Should_Remove_From_LogicalChildren() + { + var target = new DropDown(); + var child = new Control(); + + target.Content = child; + target.Content = null; + + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new DropDown(); + var child = new Control(); + var called = false; + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = target.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new DropDown(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + target.Content = null; + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = target.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new DropDown(); + var child1 = new Control(); + var child2 = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Content = child1; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Replace; + + target.Content = child2; + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = target.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); + } + + private ControlTemplate GetTemplate() + { + return ControlTemplate.Create(parent => + { + return new Border + { + Background = new Perspex.Media.SolidColorBrush(0xffffffff), + Content = new ContentPresenter + { + Id = "contentPresenter", + [~ContentPresenter.ContentProperty] = parent[~DropDown.ContentProperty], + } + }; + }); + } + + private IDisposable RegisterServices() + { + var result = Locator.CurrentMutable.WithResolver(); + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var renderInterface = fixture.Create(); + Locator.CurrentMutable.RegisterConstant(renderInterface, typeof(IPlatformRenderInterface)); + return result; + } + } +} diff --git a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 35214ff6b9..f06e046e4e 100644 --- a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -64,6 +64,7 @@ + diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index 7b515b4a7e..94670d9b64 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -31,21 +31,7 @@ namespace Perspex.Controls public ContentControl() { - this.GetObservableWithHistory(ContentProperty).Subscribe(x => - { - var control1 = x.Item1 as Control; - var control2 = x.Item2 as Control; - - if (control1 != null) - { - control1.Parent = null; - } - - if (control2 != null) - { - control2.Parent = this; - } - }); + this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent); } public object Content @@ -54,11 +40,6 @@ namespace Perspex.Controls set { this.SetValue(ContentProperty, value); } } - IReadOnlyPerspexList ILogical.LogicalChildren - { - get { return this.logicalChild; } - } - public HorizontalAlignment HorizontalContentAlignment { get { return this.GetValue(HorizontalContentAlignmentProperty); } @@ -71,6 +52,11 @@ namespace Perspex.Controls set { this.SetValue(VerticalContentAlignmentProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } + } + protected override void OnTemplateApplied() { if (this.presenterSubscription != null) @@ -87,5 +73,21 @@ namespace Perspex.Controls .Subscribe(x => this.logicalChild.SingleItem = x); } } + + private void SetContentParent(Tuple change) + { + var control1 = change.Item1 as Control; + var control2 = change.Item2 as Control; + + if (control1 != null) + { + control1.Parent = null; + } + + if (control2 != null) + { + control2.Parent = this; + } + } } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index c2151c31f0..45d0b13648 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -7,10 +7,12 @@ namespace Perspex.Controls { using System; + using Perspex.Collections; + using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Layout; - public class DropDown : SelectingItemsControl, IContentControl + public class DropDown : SelectingItemsControl, IContentControl, ILogical { public static readonly PerspexProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); @@ -24,6 +26,17 @@ namespace Perspex.Controls public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.Register("IsDropDownOpen"); + private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); + + private ContentPresenter presenter; + + private IDisposable presenterSubscription; + + public DropDown() + { + this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent); + } + public object Content { get { return this.GetValue(ContentProperty); } @@ -41,5 +54,43 @@ namespace Perspex.Controls get { return this.GetValue(VerticalContentAlignmentProperty); } set { this.SetValue(VerticalContentAlignmentProperty, value); } } + + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } + } + + protected override void OnTemplateApplied() + { + if (this.presenterSubscription != null) + { + this.presenterSubscription.Dispose(); + this.presenterSubscription = null; + } + + this.presenter = this.FindTemplateChild("contentPresenter"); + + if (this.presenter != null) + { + this.presenterSubscription = this.presenter.ChildObservable + .Subscribe(x => this.logicalChild.SingleItem = x); + } + } + + private void SetContentParent(Tuple change) + { + var control1 = change.Item1 as Control; + var control2 = change.Item2 as Control; + + if (control1 != null) + { + control1.Parent = null; + } + + if (control2 != null) + { + control2.Parent = this; + } + } } }