diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index e3211721f0..a740a22b2b 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -1,6 +1,6 @@ // ----------------------------------------------------------------------- // -// Copyright 2014 MIT Licence. See licence.md for more information. +// Copyright 2015 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- @@ -13,52 +13,97 @@ namespace Perspex.Controls using Perspex.Controls.Templates; using Perspex.Layout; - public class ContentControl : TemplatedControl, IContentControl + /// + /// Displays according to a . + /// + public class ContentControl : TemplatedControl, IContentControl, IReparentingHost { + /// + /// Defines the property. + /// public static readonly PerspexProperty ContentProperty = - PerspexProperty.Register("Content"); + PerspexProperty.Register(nameof(Content)); + /// + /// Defines the property. + /// public static readonly PerspexProperty HorizontalContentAlignmentProperty = - PerspexProperty.Register("HorizontalContentAlignment"); + PerspexProperty.Register(nameof(HorizontalContentAlignment)); + /// + /// Defines the property. + /// public static readonly PerspexProperty VerticalContentAlignmentProperty = - PerspexProperty.Register("VerticalContentAlignment"); + PerspexProperty.Register(nameof(VerticalContentAlignment)); + /// + /// Initializes a new instance of the class. + /// public ContentControl() { } + /// + /// Gets or sets the content to display. + /// public object Content { get { return this.GetValue(ContentProperty); } set { this.SetValue(ContentProperty, value); } } + /// + /// Gets the presenter from the control's template. + /// public ContentPresenter Presenter { get; private set; } + /// + /// Gets or sets the horizontal alignment of the content within the control. + /// public HorizontalAlignment HorizontalContentAlignment { get { return this.GetValue(HorizontalContentAlignmentProperty); } set { this.SetValue(HorizontalContentAlignmentProperty, value); } } + /// + /// Gets or sets the vertical alignment of the content within the control. + /// public VerticalAlignment VerticalContentAlignment { get { return this.GetValue(VerticalContentAlignmentProperty); } set { this.SetValue(VerticalContentAlignmentProperty, value); } } + /// + /// Gets a writeable logical children collection from the host. + /// + IPerspexList IReparentingHost.LogicalChildren => this.LogicalChildren; + + /// + /// Asks the control whether it wants to reparent the logical children of the specified + /// control. + /// + /// The control. + /// + /// True if the control wants to reparent its logical children otherwise false. + /// + bool IReparentingHost.WillReparentChildrenOf(IControl control) + { + return control is IContentPresenter && control.TemplatedParent == this; + } + + /// 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"); - ((IReparentingControl)this.Presenter)?.ReparentLogicalChildren(this, this.LogicalChildren); } } } diff --git a/Perspex.Controls/DropDown.cs b/Perspex.Controls/DropDown.cs index a4d736b9b4..bdc7d9b588 100644 --- a/Perspex.Controls/DropDown.cs +++ b/Perspex.Controls/DropDown.cs @@ -69,7 +69,6 @@ namespace Perspex.Controls protected override void OnTemplateApplied() { var container = this.GetTemplateChild("container"); - ((IReparentingControl)container).ReparentLogicalChildren(this, this.LogicalChildren); } private void SetContentParent(Tuple change) diff --git a/Perspex.Controls/IReparentingHost.cs b/Perspex.Controls/IReparentingHost.cs new file mode 100644 index 0000000000..7f0b00a242 --- /dev/null +++ b/Perspex.Controls/IReparentingHost.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using Perspex.Collections; + + /// + /// A control that can use the visual children of another control as its logical children. + /// + public interface IReparentingHost : ILogical + { + /// + /// Gets a writeable logical children collection from the host. + /// + new IPerspexList LogicalChildren { get; } + + /// + /// Asks the control whether it wants to reparent the logical children of the specified + /// control. + /// + /// The control. + /// + /// True if the control wants to reparent its logical children otherwise false. + /// + bool WillReparentChildrenOf(IControl control); + } +} \ No newline at end of file diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index fb83aeab64..4dab49dd98 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 + public class ItemsControl : TemplatedControl, IReparentingHost { /// /// The default value for the property. @@ -45,8 +45,6 @@ namespace Perspex.Controls private IItemContainerGenerator itemContainerGenerator; - private IItemsPresenter presenter; - /// /// Initializes static members of the class. /// @@ -102,16 +100,27 @@ namespace Perspex.Controls /// public IItemsPresenter Presenter { - get - { - return this.presenter; - } + get; + set; + } - protected set - { - this.presenter = value; - (value as IReparentingControl)?.ReparentLogicalChildren(this, this.LogicalChildren); - } + /// + IPerspexList IReparentingHost.LogicalChildren + { + get { return this.LogicalChildren; } + } + + /// + /// Asks the control whether it wants to reparent the logical children of the specified + /// control. + /// + /// The control. + /// + /// True if the control wants to reparent its logical children otherwise false. + /// + bool IReparentingHost.WillReparentChildrenOf(IControl control) + { + return control is IItemsPresenter && control.TemplatedParent == this; } /// diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index 907abbdef2..94392d744f 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -39,10 +39,12 @@ + + diff --git a/Perspex.Controls/Presenters/ContentPresenter.cs b/Perspex.Controls/Presenters/ContentPresenter.cs index 36385428d7..bb030530ec 100644 --- a/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/Perspex.Controls/Presenters/ContentPresenter.cs @@ -6,16 +6,12 @@ namespace Perspex.Controls.Presenters { - using System.Linq; - using System.Reactive.Linq; - using Perspex.Collections; - using Perspex.Controls.Primitives; using Perspex.Controls.Templates; /// /// Presents a single item of data inside a template. /// - public class ContentPresenter : Control, IPresenter + public class ContentPresenter : Control, IContentPresenter { /// /// Defines the property. @@ -25,8 +21,6 @@ namespace Perspex.Controls.Presenters private bool createdChild; - private ILogical logicalParent; - /// /// Initializes static members of the class. /// @@ -40,7 +34,8 @@ namespace Perspex.Controls.Presenters /// public IControl Child { - get { return (Control)this.LogicalChildren.SingleOrDefault(); } + get; + private set; } /// @@ -61,20 +56,6 @@ 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) { @@ -110,23 +91,29 @@ namespace Perspex.Controls.Presenters /// private void CreateChild() { - IControl result = null; - object content = this.Content; + var old = this.Child; + var content = this.Content; + var result = content != null ? this.MaterializeDataTemplate(content) : null; + var logicalHost = this.FindReparentingHost(); + var logicalChildren = logicalHost?.LogicalChildren ?? this.LogicalChildren; - this.LogicalChildren.Clear(); + logicalChildren.Remove(old); this.ClearVisualChildren(); - if (content != null) + this.Child = result; + + if (result != null) { - result = this.MaterializeDataTemplate(content); + this.AddVisualChild(result); if (result.Parent == null) { - ((ISetLogicalParent)result).SetParent(this.logicalParent ?? this); + ((ISetLogicalParent)result).SetParent((ILogical)logicalHost ?? this); + } - this.AddVisualChild(result); - this.LogicalChildren.Add(result); + logicalChildren.Remove(old); + logicalChildren.Add(result); } this.createdChild = true; diff --git a/Perspex.Controls/Presenters/DeckPresenter.cs b/Perspex.Controls/Presenters/DeckPresenter.cs index 5f25ffd332..b8a24c85e9 100644 --- a/Perspex.Controls/Presenters/DeckPresenter.cs +++ b/Perspex.Controls/Presenters/DeckPresenter.cs @@ -15,6 +15,7 @@ namespace Perspex.Controls.Presenters using Perspex.Collections; using Perspex.Controls.Generators; using Perspex.Controls.Primitives; + using Templates; using Perspex.Controls.Utils; /// @@ -140,28 +141,28 @@ namespace Perspex.Controls.Presenters } } - /// - void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) - { - this.ApplyTemplate(); - ((IReparentingControl)this.Panel).ReparentLogicalChildren(logicalParent, children); - } - /// /// Creates the . /// private void CreatePanel() { + var logicalHost = this.FindReparentingHost(); + this.ClearVisualChildren(); + this.Panel = this.ItemsPanel.Build(); + this.Panel.SetValue(TemplatedParentProperty, this.TemplatedParent); + + this.AddVisualChild(this.Panel); - if (this.ItemsPanel != null) + if (logicalHost != null) { - this.Panel = this.ItemsPanel.Build(); - this.Panel.TemplatedParent = this.TemplatedParent; - this.AddVisualChild(this.Panel); - this.createdPanel = true; - var task = this.MoveToPage(-1, this.SelectedIndex); + ((IReparentingControl)this.Panel).ReparentLogicalChildren( + logicalHost, + logicalHost.LogicalChildren); } + + this.createdPanel = true; + var task = this.MoveToPage(-1, this.SelectedIndex); } /// diff --git a/Perspex.Controls/Presenters/IContentPresenter.cs b/Perspex.Controls/Presenters/IContentPresenter.cs new file mode 100644 index 0000000000..b460fc124f --- /dev/null +++ b/Perspex.Controls/Presenters/IContentPresenter.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Presenters +{ + /// + /// Interface for controls that present a single item of data inside a + /// template. + /// + public interface IContentPresenter : IPresenter + { + /// + /// Gets the control displayed by the presenter. + /// + IControl Child { get; } + + /// + /// Gets or sets the content to be displayed by the presenter. + /// + object Content { get; set; } + } +} \ No newline at end of file diff --git a/Perspex.Controls/Presenters/IPresenter.cs b/Perspex.Controls/Presenters/IPresenter.cs index 1638ce8f13..3de04e2af4 100644 --- a/Perspex.Controls/Presenters/IPresenter.cs +++ b/Perspex.Controls/Presenters/IPresenter.cs @@ -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 : IControl, INamed, IReparentingControl + public interface IPresenter : IControl, INamed { } } diff --git a/Perspex.Controls/Presenters/ItemsPresenter.cs b/Perspex.Controls/Presenters/ItemsPresenter.cs index 432dd66d65..533f4af22e 100644 --- a/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -9,15 +9,15 @@ namespace Perspex.Controls.Presenters using System; using System.Collections; using System.Collections.Specialized; - using Collections; using Perspex.Controls.Generators; + using Templates; using Perspex.Input; using Perspex.Styling; /// /// Displays items inside an . /// - public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl, IReparentingControl + public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl { /// /// Defines the property. @@ -110,13 +110,6 @@ 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) { @@ -140,8 +133,19 @@ namespace Perspex.Controls.Presenters this.ClearVisualChildren(); this.Panel = this.ItemsPanel.Build(); this.Panel.SetValue(TemplatedParentProperty, this.TemplatedParent); - KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this)); + this.AddVisualChild(this.Panel); + + var logicalHost = this.FindReparentingHost(); + + if (logicalHost != null) + { + ((IReparentingControl)this.Panel).ReparentLogicalChildren( + logicalHost, + logicalHost.LogicalChildren); + } + + KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this)); this.createdPanel = true; this.CreateItemsAndListenForChanges(this.Items); } diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index e1baf59f09..77e686e889 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -7,13 +7,13 @@ namespace Perspex.Controls { using Perspex.Animation; + using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; - using Perspex.Controls.Templates; /// /// A tab control that displays a tab strip along with the content of the selected tab. /// - public class TabControl : SelectingItemsControl + public class TabControl : SelectingItemsControl, IReparentingHost { /// /// Defines the property. @@ -70,15 +70,17 @@ namespace Perspex.Controls set { this.SetValue(TransitionProperty, value); } } - /// - protected override void OnTemplateApplied() + /// + /// Asks the control whether it wants to reparent the logical children of the specified + /// control. + /// + /// The control. + /// + /// True if the control wants to reparent its logical children otherwise false. + /// + bool IReparentingHost.WillReparentChildrenOf(IControl control) { - base.OnTemplateApplied(); - - var deck = this.GetTemplateChild("deck"); - ((IReparentingControl)deck.Presenter.Panel).ReparentLogicalChildren( - this, - this.LogicalChildren); + return control is DeckPresenter; } /// diff --git a/Perspex.Controls/Templates/TemplateExtensions.cs b/Perspex.Controls/Templates/TemplateExtensions.cs index de77d7f73a..d53c36540d 100644 --- a/Perspex.Controls/Templates/TemplateExtensions.cs +++ b/Perspex.Controls/Templates/TemplateExtensions.cs @@ -15,6 +15,35 @@ namespace Perspex.Controls.Templates public static class TemplateExtensions { + public static IReparentingHost FindReparentingHost(this IControl control) + { + var tp = control.TemplatedParent; + var chain = new List(); + + while (tp != null) + { + var reparentingHost = tp as IReparentingHost; + var styleable = tp as IStyleable; + + if (reparentingHost != null) + { + chain.Add(reparentingHost); + } + + tp = styleable?.TemplatedParent ?? null; + } + + foreach (var reparenting in chain.AsEnumerable().Reverse()) + { + if (reparenting.WillReparentChildrenOf(control)) + { + return reparenting; + } + } + + return null; + } + public static T FindTemplateChild(this ITemplatedControl control, string id) where T : INamed { return control.GetTemplateChildren().OfType().SingleOrDefault(x => x.Name == id); diff --git a/Perspex.Themes.Default/TabStripStyle.cs b/Perspex.Themes.Default/TabStripStyle.cs index af01a74d21..ca233142bc 100644 --- a/Perspex.Themes.Default/TabStripStyle.cs +++ b/Perspex.Themes.Default/TabStripStyle.cs @@ -26,7 +26,7 @@ namespace Perspex.Themes.Default new Setter(TabStrip.TemplateProperty, new ControlTemplate(this.Template)), }, }, - new Style(x => x.OfType().Template().OfType().Template().OfType()) + new Style(x => x.OfType().Template().OfType()) { Setters = new[] { diff --git a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs index f8c9179a5f..c7a59ba444 100644 --- a/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -218,8 +218,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; diff --git a/Tests/Perspex.Controls.UnitTests/TabControlTests.cs b/Tests/Perspex.Controls.UnitTests/TabControlTests.cs index 96014ba7da..4f7f224d19 100644 --- a/Tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/TabControlTests.cs @@ -41,33 +41,6 @@ namespace Perspex.Controls.UnitTests Assert.Equal(selected, target.SelectedItem); Assert.Equal(selected, target.SelectedTab); - Assert.Equal("foo", target.SelectedContent); - } - - [Fact] - public void SelectedContent_Should_Initially_Be_First_Tab_Content() - { - var target = new TabControl - { - Template = new ControlTemplate(this.CreateTabControlTemplate), - Items = new[] - { - new TabItem - { - Name = "first", - Content = "foo", - }, - new TabItem - { - Name = "second", - Content = "bar", - }, - } - }; - - target.ApplyTemplate(); - - Assert.Equal("foo", target.SelectedContent); } [Fact] @@ -95,7 +68,6 @@ namespace Perspex.Controls.UnitTests target.SelectedItem = target.Items.Cast().ElementAt(1); Assert.Same(target.SelectedTab, target.SelectedItem); - Assert.Equal("bar", target.SelectedContent); } [Fact]