From 3d89fd922a8ffe88296a4d3b47b556f05ab88a6a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 Mar 2014 12:30:05 +0100 Subject: [PATCH] Fixed up logical tree stuff. --- .../Controls/ContentControlTests.cs | 50 +++++++++++ Perspex/Controls/ContentControl.cs | 25 +++++- Perspex/Controls/Control.cs | 25 +++++- Perspex/Controls/Decorator.cs | 14 ++- Perspex/Controls/LogicalChildren.cs | 86 +++++++++++++++++++ Perspex/Controls/Panel.cs | 36 ++++++++ Perspex/Visual.cs | 12 --- 7 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 Perspex/Controls/LogicalChildren.cs create mode 100644 Perspex/Controls/Panel.cs diff --git a/Perspex.UnitTests/Controls/ContentControlTests.cs b/Perspex.UnitTests/Controls/ContentControlTests.cs index f862edfa53..985757d189 100644 --- a/Perspex.UnitTests/Controls/ContentControlTests.cs +++ b/Perspex.UnitTests/Controls/ContentControlTests.cs @@ -56,6 +56,56 @@ namespace Perspex.UnitTests.Controls } } + [TestMethod] + public void Setting_Content_To_Control_Should_Set_Parent() + { + var target = new ContentControl(); + var child = new Border(); + + target.Content = child; + + Assert.AreEqual(child.Parent, target); + Assert.AreEqual(((IVisual)child).VisualParent, target); + Assert.AreEqual(((ILogical)child).LogicalParent, target); + } + + [TestMethod] + public void Setting_Content_To_Control_Should_Set_Logical_Child() + { + var target = new ContentControl(); + var child = new Border(); + + target.Content = child; + + Assert.AreEqual(child, ((ILogical)target).LogicalChildren.Single()); + } + + [TestMethod] + public void Removing_Control_From_Content_Should_Clear_Parent() + { + var target = new ContentControl(); + var child = new Border(); + + target.Content = child; + target.Content = "foo"; + + Assert.IsNull(child.Parent); + Assert.IsNull(((IVisual)child).VisualParent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Removing_Control_From_Content_Should_Clear_Logical_Child() + { + var target = new ContentControl(); + var child = new Border(); + + target.Content = child; + target.Content = "foo"; + + Assert.IsFalse(((ILogical)target).LogicalChildren.Any()); + } + private void ApplyTemplate(IVisual visual) { foreach (IVisual child in visual.VisualChildren) diff --git a/Perspex/Controls/ContentControl.cs b/Perspex/Controls/ContentControl.cs index 186313e982..ece79b91f1 100644 --- a/Perspex/Controls/ContentControl.cs +++ b/Perspex/Controls/ContentControl.cs @@ -10,13 +10,27 @@ namespace Perspex.Controls using System.Collections.Generic; using System.Linq; - public class ContentControl : TemplatedControl + public class ContentControl : TemplatedControl, ILogical { public static readonly PerspexProperty ContentProperty = PerspexProperty.Register("Content"); public ContentControl() { + this.GetObservableWithHistory(ContentProperty).Subscribe(x => + { + if (x.Item1 is Control) + { + ((IVisual)x.Item1).VisualParent = null; + ((ILogical)x.Item1).LogicalParent = null; + } + + if (x.Item2 is Control) + { + ((IVisual)x.Item2).VisualParent = this; + ((ILogical)x.Item2).LogicalParent = this; + } + }); } public object Content @@ -25,6 +39,15 @@ namespace Perspex.Controls set { this.SetValue(ContentProperty, value); } } + IEnumerable ILogical.LogicalChildren + { + get + { + ILogical logicalChild = this.Content as ILogical; + return Enumerable.Repeat(logicalChild, logicalChild != null ? 1 : 0); + } + } + protected override Size ArrangeContent(Size finalSize) { Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index f376edcb49..05a7b76afb 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -33,7 +33,7 @@ namespace Perspex.Controls Bottom, } - public class Control : Interactive, ILayoutable, IStyleable, IStyled + public class Control : Interactive, ILayoutable, ILogical, IStyleable, IStyled { public static readonly PerspexProperty BackgroundProperty = PerspexProperty.Register("Background", inherits: true); @@ -56,6 +56,9 @@ namespace Perspex.Controls public static readonly PerspexProperty MarginProperty = PerspexProperty.Register("Margin"); + public static readonly ReadOnlyPerspexProperty ParentProperty = + new ReadOnlyPerspexProperty(ParentPropertyRW); + public static readonly PerspexProperty VerticalAlignmentProperty = PerspexProperty.Register("VerticalAlignment"); @@ -65,6 +68,9 @@ namespace Perspex.Controls public static readonly RoutedEvent MouseLeftButtonUpEvent = RoutedEvent.Register("MouseLeftButtonUp", RoutingStrategy.Bubble); + internal static readonly PerspexProperty ParentPropertyRW = + PerspexProperty.Register("Parent"); + private Classes classes; private string id; @@ -208,6 +214,12 @@ namespace Perspex.Controls set { this.SetValue(MarginProperty, value); } } + public Control Parent + { + get { return this.GetValue(ParentPropertyRW); } + protected set { this.SetValue(ParentPropertyRW, value); } + } + public Styles Styles { get @@ -238,6 +250,17 @@ namespace Perspex.Controls set { this.SetValue(VerticalAlignmentProperty, value); } } + ILogical ILogical.LogicalParent + { + get { return this.Parent; } + set { this.Parent = (Control)value; } + } + + IEnumerable ILogical.LogicalChildren + { + get { return Enumerable.Empty(); } + } + public ILayoutRoot GetLayoutRoot() { return this.GetVisualAncestorOrSelf(); diff --git a/Perspex/Controls/Decorator.cs b/Perspex/Controls/Decorator.cs index c9d9fd56f2..a4157fc422 100644 --- a/Perspex/Controls/Decorator.cs +++ b/Perspex/Controls/Decorator.cs @@ -21,12 +21,18 @@ namespace Perspex.Controls public Decorator() { - // TODO: Unset old content's visual parent. - this.GetObservable(ContentProperty).OfType().Subscribe(x => + this.GetObservableWithHistory(ContentProperty).Subscribe(x => { - if (x != null) + if (x.Item1 != null) { - x.VisualParent = this; + ((IVisual)x.Item1).VisualParent = null; + ((ILogical)x.Item1).LogicalParent = null; + } + + if (x.Item2 != null) + { + ((IVisual)x.Item2).VisualParent = this; + ((ILogical)x.Item2).LogicalParent = this; } }); } diff --git a/Perspex/Controls/LogicalChildren.cs b/Perspex/Controls/LogicalChildren.cs new file mode 100644 index 0000000000..dc49a9e5aa --- /dev/null +++ b/Perspex/Controls/LogicalChildren.cs @@ -0,0 +1,86 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.Linq; + + /// + /// Manages parenting for a collection of logical child controls. + /// + /// The type of the controls. + /// + /// Unfortunately, because of ObservableCollection's handling of clear (the cleared items + /// aren't passed to the CollectionChanged event) we have to hold two lists of child controls: + /// the ones in Panel.Children and the ones here - held in case the ObservableCollection + /// gets cleared. It's either that or write a proper PerspexList which is too much work + /// for now. + /// + internal class LogicalChildren where T : class, ILogical, IVisual + { + private T parent; + + private List inner = new List(); + + public LogicalChildren(T parent, INotifyCollectionChanged childrenCollection) + { + this.parent = parent; + childrenCollection.CollectionChanged += CollectionChanged; + } + + private void Add(IEnumerable items) + { + foreach (T item in items) + { + this.inner.Add(item); + item.LogicalParent = this.parent; + item.VisualParent = this.parent; + } + } + + private void Remove(IEnumerable items) + { + foreach (T item in items) + { + this.inner.Remove(item); + item.LogicalParent = null; + item.VisualParent = null; + } + } + + private void Reset(IEnumerable newState) + { + this.Add(newState.Except(this.inner)); + this.Remove(this.inner.Except(newState)); + } + + private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + this.Add(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + this.Remove(e.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Replace: + this.Remove(e.OldItems.Cast()); + this.Add(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + this.Reset((IEnumerable)sender); + break; + } + } + } +} diff --git a/Perspex/Controls/Panel.cs b/Perspex/Controls/Panel.cs new file mode 100644 index 0000000000..6b4e748376 --- /dev/null +++ b/Perspex/Controls/Panel.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Collections.Specialized; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + /// + /// Base class for controls that can contain multiple children. + /// + public class Panel : Control + { + private LogicalChildren logicalChildren; + + public Panel() + { + this.Children = new ObservableCollection(); + this.logicalChildren = new LogicalChildren(this, this.Children); + } + + public ObservableCollection Children + { + get; + private set; + } + } +} diff --git a/Perspex/Visual.cs b/Perspex/Visual.cs index 1344e056fc..f88c0e85e9 100644 --- a/Perspex/Visual.cs +++ b/Perspex/Visual.cs @@ -15,12 +15,6 @@ namespace Perspex public abstract class Visual : PerspexObject, IVisual { - public static readonly ReadOnlyPerspexProperty ParentProperty = - new ReadOnlyPerspexProperty(ParentPropertyRW); - - internal static readonly PerspexProperty ParentPropertyRW = - PerspexProperty.Register("Parent"); - private IVisual visualParent; public Rect Bounds @@ -29,12 +23,6 @@ namespace Perspex protected set; } - public Control Parent - { - get { return this.GetValue(ParentPropertyRW); } - protected set { this.SetValue(ParentPropertyRW, value); } - } - IEnumerable IVisual.ExistingVisualChildren { get { return Enumerable.Empty(); }