From 38e85a3f36cc434b76d8a6274a390ecbda972489 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jul 2015 07:55:46 +0200 Subject: [PATCH] Started reworking reparenting controls. --- Perspex.Base/Collections/IPerspexList.cs | 9 ++ Perspex.Controls/ContentControl.cs | 11 +- Perspex.Controls/Control.cs | 29 ++++- Perspex.Controls/Deck.cs | 2 +- Perspex.Controls/Decorator.cs | 16 +-- Perspex.Controls/DropDown.cs | 10 +- Perspex.Controls/IItemsPanel.cs | 26 ----- Perspex.Controls/IReparentingControl.cs | 28 +++++ Perspex.Controls/ISetLogicalParent.cs | 2 +- Perspex.Controls/ItemsControl.cs | 22 +--- Perspex.Controls/Panel.cs | 36 ++++--- Perspex.Controls/Perspex.Controls.csproj | 2 +- Perspex.Controls/Popup.cs | 14 +-- Perspex.Controls/PopupRoot.cs | 3 +- .../Presenters/ContentPresenter.cs | 73 +++++++++---- Perspex.Controls/Presenters/DeckPresenter.cs | 20 +++- Perspex.Controls/Presenters/IPresenter.cs | 4 +- Perspex.Controls/Presenters/ItemsPresenter.cs | 11 +- Perspex.Controls/Presenters/TextPresenter.cs | 2 +- Perspex.Controls/TabControl.cs | 10 +- .../Perspex.Controls.UnitTests/BorderTests.cs | 100 ------------------ .../ContentControlTests.cs | 37 ++++--- .../ContentPresenterTests.cs | 13 +-- .../DecoratorTests.cs | 5 +- .../Perspex.Controls.UnitTests/PanelTests.cs | 26 +++++ 25 files changed, 228 insertions(+), 283 deletions(-) delete mode 100644 Perspex.Controls/IItemsPanel.cs create mode 100644 Perspex.Controls/IReparentingControl.cs diff --git a/Perspex.Base/Collections/IPerspexList.cs b/Perspex.Base/Collections/IPerspexList.cs index 1aaf7d827e..34c6dfe5bc 100644 --- a/Perspex.Base/Collections/IPerspexList.cs +++ b/Perspex.Base/Collections/IPerspexList.cs @@ -11,5 +11,14 @@ namespace Perspex.Collections public interface IPerspexList : IList, IList, IPerspexReadOnlyList { + new int Count { get; } + + void AddRange(IEnumerable items); + + new void Clear(); + + void InsertRange(int index, IEnumerable items); + + void RemoveAll(IEnumerable items); } } \ No newline at end of file diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index 79ac1321c0..e3211721f0 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -13,7 +13,7 @@ namespace Perspex.Controls using Perspex.Controls.Templates; using Perspex.Layout; - public class ContentControl : TemplatedControl, IContentControl, ILogical + public class ContentControl : TemplatedControl, IContentControl { public static readonly PerspexProperty ContentProperty = PerspexProperty.Register("Content"); @@ -24,8 +24,6 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register("VerticalContentAlignment"); - private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); - public ContentControl() { } @@ -54,18 +52,13 @@ namespace Perspex.Controls set { this.SetValue(VerticalContentAlignmentProperty, value); } } - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChildren; } - } - protected override void OnTemplateApplied() { // 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"); - this.logicalChildren.Source = ((ILogical)this.Presenter)?.LogicalChildren; + ((IReparentingControl)this.Presenter)?.ReparentLogicalChildren(this, this.LogicalChildren); } } } diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 3b3e641062..8505717480 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -80,6 +80,8 @@ namespace Perspex.Controls private string id; + private IPerspexList logicalChildren; + private Styles styles; /// @@ -267,7 +269,7 @@ namespace Perspex.Controls /// IPerspexReadOnlyList ILogical.LogicalChildren { - get { return EmptyChildren; } + get { return this.LogicalChildren; } } /// @@ -284,6 +286,22 @@ namespace Perspex.Controls get { return this.GetType(); } } + /// + /// Gets the control's logical children. + /// + protected IPerspexList LogicalChildren + { + get + { + if (this.logicalChildren == null) + { + this.logicalChildren = new PerspexList(); + } + + return this.logicalChildren; + } + } + /// /// Tries to being the control into view. /// @@ -312,7 +330,7 @@ namespace Perspex.Controls /// Sets the control's logical parent. /// /// The parent. - void ISetLogicalParent.SetParent(IControl parent) + void ISetLogicalParent.SetParent(ILogical parent) { var old = this.Parent; @@ -324,13 +342,11 @@ namespace Perspex.Controls this.SetValue(ParentProperty, parent); } - [Obsolete("Obsolete this: use properties instead")] protected static void PseudoClass(PerspexProperty property, string className) { PseudoClass(property, x => x, className); } - [Obsolete("Obsolete this: use properties instead")] protected static void PseudoClass( PerspexProperty property, Func selector, @@ -411,5 +427,10 @@ namespace Perspex.Controls IStyler styler = Locator.Current.GetService(); styler.ApplyStyles(this); } + + protected void RedirectLogicalChildren(IPerspexList collection) + { + this.logicalChildren = collection; + } } } diff --git a/Perspex.Controls/Deck.cs b/Perspex.Controls/Deck.cs index 286eff7736..db29b89909 100644 --- a/Perspex.Controls/Deck.cs +++ b/Perspex.Controls/Deck.cs @@ -23,7 +23,7 @@ namespace Perspex.Controls PerspexProperty.Register("Transition"); /// - /// The default value of for . + /// The default value of for . /// private static readonly ItemsPanelTemplate PanelTemplate = new ItemsPanelTemplate(() => new Panel()); diff --git a/Perspex.Controls/Decorator.cs b/Perspex.Controls/Decorator.cs index bccf348d0e..fe67e47789 100644 --- a/Perspex.Controls/Decorator.cs +++ b/Perspex.Controls/Decorator.cs @@ -11,7 +11,7 @@ namespace Perspex.Controls /// /// Base class for controls which decorate a single child control. /// - public class Decorator : Control, IVisual, ILogical + public class Decorator : Control { /// /// Defines the property. @@ -25,8 +25,6 @@ namespace Perspex.Controls public static readonly PerspexProperty PaddingProperty = PerspexProperty.Register(nameof(Padding)); - private PerspexSingleItemList logicalChild = new PerspexSingleItemList(); - /// /// Initializes static members of the class. /// @@ -53,14 +51,6 @@ namespace Perspex.Controls set { this.SetValue(PaddingProperty, value); } } - /// - /// Gets the logical children of the control. - /// - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChild; } - } - /// protected override Size MeasureOverride(Size availableSize) { @@ -103,16 +93,16 @@ namespace Perspex.Controls if (oldChild != null) { ((ISetLogicalParent)oldChild).SetParent(null); + this.LogicalChildren.Clear(); this.RemoveVisualChild(oldChild); } if (newChild != null) { this.AddVisualChild(newChild); + this.LogicalChildren.Add(newChild); ((ISetLogicalParent)newChild).SetParent(this); } - - this.logicalChild.SingleItem = newChild; } } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index 6897088982..bdc7d9b588 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -14,7 +14,7 @@ namespace Perspex.Controls using Perspex.Input; using Perspex.Layout; - public class DropDown : SelectingItemsControl, IContentControl, ILogical + public class DropDown : SelectingItemsControl, IContentControl { public static readonly PerspexProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); @@ -28,8 +28,6 @@ namespace Perspex.Controls public static readonly PerspexProperty IsDropDownOpenProperty = PerspexProperty.Register("IsDropDownOpen"); - private PerspexReadOnlyListView logicalChildren = new PerspexReadOnlyListView(); - public DropDown() { this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent); @@ -60,11 +58,6 @@ namespace Perspex.Controls set { this.SetValue(IsDropDownOpenProperty, value); } } - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChildren; } - } - protected override void OnPointerPressed(PointerPressEventArgs e) { if (!this.IsDropDownOpen) @@ -76,7 +69,6 @@ namespace Perspex.Controls protected override void OnTemplateApplied() { var container = this.GetTemplateChild("container"); - this.logicalChildren.Source = ((ILogical)container).LogicalChildren; } private void SetContentParent(Tuple change) diff --git a/Perspex.Controls/IItemsPanel.cs b/Perspex.Controls/IItemsPanel.cs deleted file mode 100644 index 6d655b1ab3..0000000000 --- a/Perspex.Controls/IItemsPanel.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright 2014 MIT Licence. See licence.md for more information. -// -// ----------------------------------------------------------------------- - -namespace Perspex.Controls -{ - /// - /// Interface used by to set logical ownership of the panel's - /// children. - /// - /// - /// needs to set the logical parent of each of its items to itself. - /// To do this, it uses this interface to instruct the panel that instead of setting the - /// logical parent for each child to the panel itself, it should set it to that of - /// . - /// - public interface IItemsPanel - { - /// - /// Gets or sets the logical parent that should be set on children of the panel. - /// - ILogical ChildLogicalParent { get; set; } - } -} diff --git a/Perspex.Controls/IReparentingControl.cs b/Perspex.Controls/IReparentingControl.cs new file mode 100644 index 0000000000..eb7472b491 --- /dev/null +++ b/Perspex.Controls/IReparentingControl.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using Perspex.Collections; + + /// + /// A control that can make its visual children the logical children of another control. + /// + public interface IReparentingControl : IControl + { + /// + /// Requests that the visual children of the control use another control as their logical + /// parent. + /// + /// + /// The logical parent for the visual children of the control. + /// + /// + /// The collection to modify. + /// + void ReparentLogicalChildren(ILogical logicalParent, IPerspexList children); + } +} \ No newline at end of file diff --git a/Perspex.Controls/ISetLogicalParent.cs b/Perspex.Controls/ISetLogicalParent.cs index 4c91aa6bdd..00ed8bbf46 100644 --- a/Perspex.Controls/ISetLogicalParent.cs +++ b/Perspex.Controls/ISetLogicalParent.cs @@ -18,6 +18,6 @@ namespace Perspex.Controls /// Sets the control's parent. /// /// The parent. - void SetParent(IControl parent); + void SetParent(ILogical parent); } } \ No newline at end of file diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index 1fbfbacbf3..fb83aeab64 100644 --- a/Perspex.Controls/ItemsControl.cs +++ b/Perspex.Controls/ItemsControl.cs @@ -22,7 +22,7 @@ namespace Perspex.Controls /// /// Displays a collection of items. /// - public class ItemsControl : TemplatedControl, ILogical + public class ItemsControl : TemplatedControl { /// /// The default value for the property. @@ -45,9 +45,6 @@ namespace Perspex.Controls private IItemContainerGenerator itemContainerGenerator; - private PerspexReadOnlyListView logicalChildren = - new PerspexReadOnlyListView(x => (ILogical)x); - private IItemsPresenter presenter; /// @@ -113,22 +110,7 @@ namespace Perspex.Controls protected set { this.presenter = value; - this.logicalChildren.Source = ((IVisual)value?.Panel)?.VisualChildren; - } - } - - /// - /// Gets the logical children of the control. - /// - /// - /// The logical children of an are the item containers. - /// - IPerspexReadOnlyList ILogical.LogicalChildren - { - get - { - this.ApplyTemplate(); - return this.logicalChildren; + (value as IReparentingControl)?.ReparentLogicalChildren(this, this.LogicalChildren); } } diff --git a/Perspex.Controls/Panel.cs b/Perspex.Controls/Panel.cs index 1ca094f296..db2b41e1ff 100644 --- a/Perspex.Controls/Panel.cs +++ b/Perspex.Controls/Panel.cs @@ -19,7 +19,7 @@ namespace Perspex.Controls /// Controls can be added to a by adding them to its /// collection. All children are layed out to fill the panel. /// - public class Panel : Control, ILogical, IItemsPanel + public class Panel : Control, IReparentingControl { private Controls children; @@ -82,25 +82,28 @@ namespace Perspex.Controls } /// - /// Gets the logical children of the control. + /// Requests that the visual children of the panel use another control as their logical + /// parent. /// - IPerspexReadOnlyList ILogical.LogicalChildren + /// + /// The logical parent for the visual children of the panel. + /// + /// + /// The collection to modify. + /// + void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) { - get { return this.children; } - } + Contract.Requires(logicalParent != null); + Contract.Requires(children != null); - /// - ILogical IItemsPanel.ChildLogicalParent - { - get - { - return this.childLogicalParent; - } + this.childLogicalParent = logicalParent; + this.RedirectLogicalChildren(children); - set + foreach (var control in this.Children) { - this.childLogicalParent = value; - this.SetLogicalParent(this.Children); + ((ISetLogicalParent)control).SetParent(null); + ((ISetLogicalParent)control).SetParent((IControl)logicalParent); + children.Add(control); } } @@ -156,12 +159,14 @@ namespace Perspex.Controls controls = e.NewItems.OfType().ToList(); this.SetLogicalParent(controls); this.AddVisualChildren(e.NewItems.OfType()); + this.LogicalChildren.InsertRange(e.NewStartingIndex, controls); this.OnChildrenAdded(controls); break; case NotifyCollectionChangedAction.Remove: controls = e.OldItems.OfType().ToList(); this.ClearLogicalParent(e.OldItems.OfType()); + this.LogicalChildren.RemoveAll(controls); this.RemoveVisualChildren(e.OldItems.OfType()); this.OnChildrenRemoved(controls); break; @@ -169,6 +174,7 @@ namespace Perspex.Controls case NotifyCollectionChangedAction.Reset: controls = e.OldItems.OfType().ToList(); this.ClearLogicalParent(controls); + this.LogicalChildren.Clear(); this.ClearVisualChildren(); this.AddVisualChildren(this.children); this.OnChildrenAdded(controls); diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index c7e5ef8680..4e28263fed 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -52,7 +52,7 @@ - + diff --git a/Perspex.Controls/Popup.cs b/Perspex.Controls/Popup.cs index 41cdc126b9..e73d9b5b55 100644 --- a/Perspex.Controls/Popup.cs +++ b/Perspex.Controls/Popup.cs @@ -7,8 +7,8 @@ namespace Perspex.Controls { using System; - using Perspex.Interactivity; using Perspex.Collections; + using Perspex.Interactivity; using Perspex.Rendering; using Perspex.VisualTree; using Splat; @@ -16,7 +16,7 @@ namespace Perspex.Controls /// /// Displays a popup window. /// - public class Popup : Control, ILogical, IVisualTreeHost + public class Popup : Control, IVisualTreeHost { /// /// Defines the property. @@ -160,14 +160,6 @@ namespace Perspex.Controls set { this.SetValue(StaysOpenProperty, value); } } - /// - /// Gets the logical children of the popup. - /// - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChild; } - } - /// /// Gets the root of the popup window. /// @@ -297,7 +289,7 @@ namespace Perspex.Controls /// The popup's position in screen coordinates. private Point GetPosition() { - var target = this.PlacementTarget ?? this.GetVisualParent(); + var target = this.PlacementTarget ?? this.GetVisualParent(); Point point; if (target != null) diff --git a/Perspex.Controls/PopupRoot.cs b/Perspex.Controls/PopupRoot.cs index 34768b703b..8a3891f9e4 100644 --- a/Perspex.Controls/PopupRoot.cs +++ b/Perspex.Controls/PopupRoot.cs @@ -7,11 +7,12 @@ namespace Perspex.Controls { using System; + using Collections; using Perspex.Interactivity; using Perspex.Media; using Perspex.Platform; - using Splat; using Perspex.VisualTree; + using Splat; /// /// The root window of a . diff --git a/Perspex.Controls/Presenters/ContentPresenter.cs b/Perspex.Controls/Presenters/ContentPresenter.cs index 64c56082ac..9b18f6321f 100644 --- a/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/Perspex.Controls/Presenters/ContentPresenter.cs @@ -15,36 +15,47 @@ namespace Perspex.Controls.Presenters using Perspex.Controls.Templates; using Perspex.Media; - public class ContentPresenter : Control, IVisual, ILogical, IPresenter + /// + /// Presents a single item of data inside a template. + /// + public class ContentPresenter : Control, IPresenter { + /// + /// Defines the property. + /// public static readonly PerspexProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); private bool createdChild; - private PerspexSingleItemList logicalChild = new PerspexSingleItemList(); + private ILogical logicalParent; - public ContentPresenter() + /// + /// Initializes static members of the class. + /// + static ContentPresenter() { - this.GetObservable(ContentProperty).Skip(1).Subscribe(this.ContentChanged); + ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); } - public Control Child + /// + /// Gets the control displayed by the presenter. + /// + public IControl Child { - get { return (Control)this.logicalChild.SingleItem; } + get { return (Control)this.LogicalChildren.SingleOrDefault(); } } + /// + /// Gets or sets the content to be displayed by the presenter. + /// public object Content { get { return this.GetValue(ContentProperty); } set { this.SetValue(ContentProperty, value); } } - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChild; } - } - + /// public override sealed void ApplyTemplate() { if (!this.createdChild) @@ -53,11 +64,27 @@ namespace Perspex.Controls.Presenters } } + /// + void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) + { + if (this.Child != null) + { + ((ISetLogicalParent)this.Child).SetParent(null); + ((ISetLogicalParent)this.Child).SetParent(logicalParent); + children.Add(this.Child); + } + + this.logicalParent = logicalParent; + this.RedirectLogicalChildren(children); + } + + /// protected override Size MeasureCore(Size availableSize) { return base.MeasureCore(availableSize); } + /// protected override Size MeasureOverride(Size availableSize) { var child = this.Child; @@ -71,27 +98,30 @@ namespace Perspex.Controls.Presenters return new Size(); } - private void ContentChanged(object content) + /// + /// Called when the property changes. + /// + /// The event args. + private void ContentChanged(PerspexPropertyChangedEventArgs e) { this.createdChild = false; this.InvalidateMeasure(); } + /// + /// Creates the control from the . + /// private void CreateChild() { - Control result = null; + IControl result = null; object content = this.Content; this.ClearVisualChildren(); if (content != null) { - result = (Control)this.MaterializeDataTemplate(content); - - if (result.Parent == null) - { - ((ISetLogicalParent)result).SetParent(this.TemplatedParent as Control); - } + result = this.MaterializeDataTemplate(content); + ((ISetLogicalParent)result).SetParent(this.logicalParent ?? this); var templatedParent = this.TemplatedParent as TemplatedControl; @@ -100,11 +130,12 @@ namespace Perspex.Controls.Presenters templatedParent = templatedParent.TemplatedParent as TemplatedControl; } - result.TemplatedParent = templatedParent; + ((Control)result).TemplatedParent = templatedParent; this.AddVisualChild(result); } - this.logicalChild.SingleItem = result; + this.LogicalChildren.Clear(); + this.LogicalChildren.Add(result); this.createdChild = true; } } diff --git a/Perspex.Controls/Presenters/DeckPresenter.cs b/Perspex.Controls/Presenters/DeckPresenter.cs index 1c8ba52c18..e24ea4f024 100644 --- a/Perspex.Controls/Presenters/DeckPresenter.cs +++ b/Perspex.Controls/Presenters/DeckPresenter.cs @@ -12,6 +12,7 @@ namespace Perspex.Controls.Presenters using System.Reactive.Linq; using System.Threading.Tasks; using Perspex.Animation; + using Collections; using Perspex.Controls.Generators; using Perspex.Controls.Primitives; using Perspex.Controls.Utils; @@ -20,7 +21,7 @@ namespace Perspex.Controls.Presenters /// /// Displays pages inside an . /// - public class DeckPresenter : Control, IItemsPresenter, ITemplatedControl + public class DeckPresenter : Control, IItemsPresenter { /// /// Defines the property. @@ -131,6 +132,14 @@ namespace Perspex.Controls.Presenters set { this.SetValue(TransitionProperty, value); } } + Panel IItemsPresenter.Panel + { + get + { + throw new NotImplementedException(); + } + } + /// public override sealed void ApplyTemplate() { @@ -140,6 +149,12 @@ namespace Perspex.Controls.Presenters } } + /// + void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) + { + throw new NotImplementedException(); + } + /// /// Creates the . /// @@ -150,8 +165,7 @@ namespace Perspex.Controls.Presenters if (this.ItemsPanel != null) { this.Panel = this.ItemsPanel.Build(); - this.Panel.TemplatedParent = this; - ((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical; + this.Panel.TemplatedParent = this.TemplatedParent; this.AddVisualChild(this.Panel); this.createdPanel = true; var task = this.MoveToPage(-1, this.SelectedIndex); diff --git a/Perspex.Controls/Presenters/IPresenter.cs b/Perspex.Controls/Presenters/IPresenter.cs index 43a0987858..ddc3d39589 100644 --- a/Perspex.Controls/Presenters/IPresenter.cs +++ b/Perspex.Controls/Presenters/IPresenter.cs @@ -9,7 +9,7 @@ namespace Perspex.Controls.Presenters using Perspex.Controls.Primitives; /// - /// Interface for presenters such as and + /// Interface for presenters such as and /// . /// /// @@ -18,7 +18,7 @@ namespace Perspex.Controls.Presenters /// of a then that signals that the visual child /// of the presenter is not a part of the template. /// - public interface IPresenter : IVisual, INamed + public interface IPresenter : IControl, INamed, IReparentingControl { } } diff --git a/Perspex.Controls/Presenters/ItemsPresenter.cs b/Perspex.Controls/Presenters/ItemsPresenter.cs index 2b16212e1c..87b4ab3e25 100644 --- a/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls.Presenters using System; using System.Collections; using System.Collections.Specialized; + using Collections; using Perspex.Controls.Generators; using Perspex.Input; using Perspex.Styling; @@ -16,7 +17,7 @@ namespace Perspex.Controls.Presenters /// /// Displays items inside an . /// - public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl + public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl, IReparentingControl { /// /// Defines the property. @@ -109,6 +110,13 @@ namespace Perspex.Controls.Presenters } } + /// + public void ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) + { + this.ApplyTemplate(); + ((IReparentingControl)this.Panel).ReparentLogicalChildren(logicalParent, children); + } + /// protected override Size MeasureOverride(Size availableSize) { @@ -133,7 +141,6 @@ namespace Perspex.Controls.Presenters this.Panel = this.ItemsPanel.Build(); this.Panel.TemplatedParent = this; KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this)); - ((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical; this.AddVisualChild(this.Panel); this.createdPanel = true; this.CreateItemsAndListenForChanges(this.Items); diff --git a/Perspex.Controls/Presenters/TextPresenter.cs b/Perspex.Controls/Presenters/TextPresenter.cs index f6ff4abd46..c384d74eec 100644 --- a/Perspex.Controls/Presenters/TextPresenter.cs +++ b/Perspex.Controls/Presenters/TextPresenter.cs @@ -13,7 +13,7 @@ namespace Perspex.Controls.Presenters using Perspex.Threading; using Perspex.VisualTree; - public class TextPresenter : TextBlock, IPresenter + public class TextPresenter : TextBlock { public static readonly PerspexProperty CaretIndexProperty = TextBox.CaretIndexProperty.AddOwner(); diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index 4500640a88..0ade29b5c8 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -15,7 +15,7 @@ namespace Perspex.Controls /// /// A tab control that displays a tab strip along with the content of the selected tab. /// - public class TabControl : SelectingItemsControl, ILogical + public class TabControl : SelectingItemsControl { /// /// Defines the property. @@ -82,14 +82,6 @@ namespace Perspex.Controls set { this.SetValue(TransitionProperty, value); } } - /// - /// Gets the logical children of the control. - /// - IPerspexReadOnlyList ILogical.LogicalChildren - { - get { return this.logicalChildren; } - } - /// protected override void OnKeyDown(KeyEventArgs e) { diff --git a/Tests/Perspex.Controls.UnitTests/BorderTests.cs b/Tests/Perspex.Controls.UnitTests/BorderTests.cs index fc0e89354c..15251ecd9f 100644 --- a/Tests/Perspex.Controls.UnitTests/BorderTests.cs +++ b/Tests/Perspex.Controls.UnitTests/BorderTests.cs @@ -6,110 +6,10 @@ namespace Perspex.Controls.UnitTests { - using System.Collections.Specialized; - using System.Linq; using Xunit; public class BorderTests { - [Fact] - public void Setting_Content_Should_Set_Child_Controls_Parent() - { - var target = new Border(); - var child = new Control(); - - target.Child = child; - - Assert.Equal(child.Parent, target); - Assert.Equal(((ILogical)child).LogicalParent, target); - } - - [Fact] - public void Clearing_Content_Should_Clear_Child_Controls_Parent() - { - var target = new Border(); - var child = new Control(); - - target.Child = child; - target.Child = null; - - Assert.Null(child.Parent); - Assert.Null(((ILogical)child).LogicalParent); - } - - [Fact] - public void Content_Control_Should_Appear_In_LogicalChildren() - { - var target = new Border(); - var child = new Control(); - - target.Child = child; - - Assert.Equal(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); - } - - [Fact] - public void Clearing_Content_Should_Remove_From_LogicalChildren() - { - var target = new Border(); - var child = new Control(); - - target.Child = child; - target.Child = null; - - Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); - } - - [Fact] - public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() - { - var target = new Border(); - var child = new Control(); - var called = false; - - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => - called = e.Action == NotifyCollectionChangedAction.Add; - - target.Child = child; - - Assert.True(called); - } - - [Fact] - public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() - { - var target = new Border(); - var child = new Control(); - var called = false; - - target.Child = child; - - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => - called = e.Action == NotifyCollectionChangedAction.Remove; - - target.Child = null; - - Assert.True(called); - } - - [Fact] - public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() - { - var target = new Border(); - var child1 = new Control(); - var child2 = new Control(); - var called = false; - - target.Child = child1; - - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => - called = e.Action == NotifyCollectionChangedAction.Replace; - - target.Child = child2; - - Assert.True(called); - } - [Fact] public void Measure_Should_Return_BorderThickness_Plus_Padding_When_No_Child_Present() { diff --git a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs index 0aa6149636..9bd7003bde 100644 --- a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -14,6 +14,7 @@ namespace Perspex.Controls.UnitTests using Perspex.Controls.Presenters; using Perspex.Controls.Templates; using Perspex.Layout; + using LogicalTree; using Perspex.Platform; using Perspex.Styling; using Perspex.VisualTree; @@ -180,30 +181,28 @@ namespace Perspex.Controls.UnitTests target.Content = null; - // Need to call ApplyTemplate on presenter for LogocalChildren to be updated. - var presenter = target.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); - presenter.ApplyTemplate(); + // Need to call ApplyTemplate on presenter for LogicalChildren to be updated. + target.Presenter.ApplyTemplate(); - Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + Assert.Empty(target.GetLogicalChildren()); } [Fact] public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() { - var contentControl = new ContentControl(); + var target = new ContentControl(); var child = new Control(); var called = false; - ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = e.Action == NotifyCollectionChangedAction.Add; - contentControl.Template = this.GetTemplate(); - contentControl.Content = child; - contentControl.ApplyTemplate(); + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); - // Need to call ApplyTemplate on presenter for CollectionChanged to be called. - var presenter = contentControl.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); - presenter.ApplyTemplate(); + // Need to call ApplyTemplate on presenter for LogicalChildren to be updated. + target.Presenter.ApplyTemplate(); Assert.True(called); } @@ -211,21 +210,21 @@ namespace Perspex.Controls.UnitTests [Fact] public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() { - var contentControl = new ContentControl(); + var target = new ContentControl(); var child = new Control(); var called = false; - contentControl.Template = this.GetTemplate(); - contentControl.Content = child; - contentControl.ApplyTemplate(); + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); - ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = e.Action == NotifyCollectionChangedAction.Remove; - contentControl.Content = null; + target.Content = null; // Need to call ApplyTemplate on presenter for CollectionChanged to be called. - var presenter = contentControl.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); + var presenter = target.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); presenter.ApplyTemplate(); Assert.True(called); diff --git a/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs b/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs index c5bdedd656..23a75d3125 100644 --- a/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs +++ b/Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs @@ -6,20 +6,10 @@ 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 @@ -74,8 +64,7 @@ namespace Perspex.Controls.UnitTests target.Content = child; target.ApplyTemplate(); - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => - called = e.Action == NotifyCollectionChangedAction.Remove; + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; target.Content = null; target.ApplyTemplate(); diff --git a/Tests/Perspex.Controls.UnitTests/DecoratorTests.cs b/Tests/Perspex.Controls.UnitTests/DecoratorTests.cs index 7ba6b713aa..a1b960e297 100644 --- a/Tests/Perspex.Controls.UnitTests/DecoratorTests.cs +++ b/Tests/Perspex.Controls.UnitTests/DecoratorTests.cs @@ -67,7 +67,7 @@ namespace Perspex.Controls.UnitTests var child = new Control(); var called = false; - ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => + ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => called = e.Action == NotifyCollectionChangedAction.Add; decorator.Child = child; @@ -102,8 +102,7 @@ namespace Perspex.Controls.UnitTests decorator.Child = child1; - ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => - called = e.Action == NotifyCollectionChangedAction.Replace; + ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => called = true; decorator.Child = child2; diff --git a/Tests/Perspex.Controls.UnitTests/PanelTests.cs b/Tests/Perspex.Controls.UnitTests/PanelTests.cs index 2c1bb8cc3f..0b177743e5 100644 --- a/Tests/Perspex.Controls.UnitTests/PanelTests.cs +++ b/Tests/Perspex.Controls.UnitTests/PanelTests.cs @@ -7,6 +7,7 @@ namespace Perspex.Controls.UnitTests { using System.Linq; + using Collections; using Xunit; public class PanelTests @@ -106,5 +107,30 @@ namespace Perspex.Controls.UnitTests Assert.Equal(new Control[0], panel.Children); Assert.Equal(new ILogical[0], ((ILogical)panel).LogicalChildren.ToList()); } + + [Fact] + public void Should_Be_Able_To_Reparent_Child_Controls() + { + var target = new Panel(); + var parent = new TestReparent(); + var control1 = new Control(); + var control2 = new Control(); + + target.Children.Add(control1); + ((IReparentingControl)target).ReparentLogicalChildren(parent, parent.LogicalChildren); + target.Children.Add(control2); + + Assert.Equal(new[] { control1, control2 }, parent.LogicalChildren); + Assert.Equal(parent, target.Children[0].Parent); + Assert.Equal(parent, target.Children[1].Parent); + } + + private class TestReparent : Panel + { + public new IPerspexList LogicalChildren + { + get { return base.LogicalChildren; } + } + } } }