Browse Source

Started reworking reparenting controls.

pull/72/merge
Steven Kirk 11 years ago
parent
commit
38e85a3f36
  1. 9
      Perspex.Base/Collections/IPerspexList.cs
  2. 11
      Perspex.Controls/ContentControl.cs
  3. 29
      Perspex.Controls/Control.cs
  4. 2
      Perspex.Controls/Deck.cs
  5. 16
      Perspex.Controls/Decorator.cs
  6. 10
      Perspex.Controls/DropDown.cs
  7. 26
      Perspex.Controls/IItemsPanel.cs
  8. 28
      Perspex.Controls/IReparentingControl.cs
  9. 2
      Perspex.Controls/ISetLogicalParent.cs
  10. 22
      Perspex.Controls/ItemsControl.cs
  11. 36
      Perspex.Controls/Panel.cs
  12. 2
      Perspex.Controls/Perspex.Controls.csproj
  13. 14
      Perspex.Controls/Popup.cs
  14. 3
      Perspex.Controls/PopupRoot.cs
  15. 73
      Perspex.Controls/Presenters/ContentPresenter.cs
  16. 20
      Perspex.Controls/Presenters/DeckPresenter.cs
  17. 4
      Perspex.Controls/Presenters/IPresenter.cs
  18. 11
      Perspex.Controls/Presenters/ItemsPresenter.cs
  19. 2
      Perspex.Controls/Presenters/TextPresenter.cs
  20. 10
      Perspex.Controls/TabControl.cs
  21. 100
      Tests/Perspex.Controls.UnitTests/BorderTests.cs
  22. 37
      Tests/Perspex.Controls.UnitTests/ContentControlTests.cs
  23. 13
      Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs
  24. 5
      Tests/Perspex.Controls.UnitTests/DecoratorTests.cs
  25. 26
      Tests/Perspex.Controls.UnitTests/PanelTests.cs

9
Perspex.Base/Collections/IPerspexList.cs

@ -11,5 +11,14 @@ namespace Perspex.Collections
public interface IPerspexList<T> : IList<T>, IList, IPerspexReadOnlyList<T> public interface IPerspexList<T> : IList<T>, IList, IPerspexReadOnlyList<T>
{ {
new int Count { get; }
void AddRange(IEnumerable<T> items);
new void Clear();
void InsertRange(int index, IEnumerable<T> items);
void RemoveAll(IEnumerable<T> items);
} }
} }

11
Perspex.Controls/ContentControl.cs

@ -13,7 +13,7 @@ namespace Perspex.Controls
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Layout; using Perspex.Layout;
public class ContentControl : TemplatedControl, IContentControl, ILogical public class ContentControl : TemplatedControl, IContentControl
{ {
public static readonly PerspexProperty<object> ContentProperty = public static readonly PerspexProperty<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>("Content"); PerspexProperty.Register<ContentControl, object>("Content");
@ -24,8 +24,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty = public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, VerticalAlignment>("VerticalContentAlignment"); PerspexProperty.Register<ContentControl, VerticalAlignment>("VerticalContentAlignment");
private PerspexReadOnlyListView<ILogical> logicalChildren = new PerspexReadOnlyListView<ILogical>();
public ContentControl() public ContentControl()
{ {
} }
@ -54,18 +52,13 @@ namespace Perspex.Controls
set { this.SetValue(VerticalContentAlignmentProperty, value); } set { this.SetValue(VerticalContentAlignmentProperty, value); }
} }
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChildren; }
}
protected override void OnTemplateApplied() protected override void OnTemplateApplied()
{ {
// We allow ContentControls without ContentPresenters in the template. This can be // 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 // 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. // have a ContentPresenter in the visual tree for that.
this.Presenter = this.FindTemplateChild<ContentPresenter>("contentPresenter"); this.Presenter = this.FindTemplateChild<ContentPresenter>("contentPresenter");
this.logicalChildren.Source = ((ILogical)this.Presenter)?.LogicalChildren; ((IReparentingControl)this.Presenter)?.ReparentLogicalChildren(this, this.LogicalChildren);
} }
} }
} }

29
Perspex.Controls/Control.cs

@ -80,6 +80,8 @@ namespace Perspex.Controls
private string id; private string id;
private IPerspexList<ILogical> logicalChildren;
private Styles styles; private Styles styles;
/// <summary> /// <summary>
@ -267,7 +269,7 @@ namespace Perspex.Controls
/// </summary> /// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{ {
get { return EmptyChildren; } get { return this.LogicalChildren; }
} }
/// <summary> /// <summary>
@ -284,6 +286,22 @@ namespace Perspex.Controls
get { return this.GetType(); } get { return this.GetType(); }
} }
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IPerspexList<ILogical> LogicalChildren
{
get
{
if (this.logicalChildren == null)
{
this.logicalChildren = new PerspexList<ILogical>();
}
return this.logicalChildren;
}
}
/// <summary> /// <summary>
/// Tries to being the control into view. /// Tries to being the control into view.
/// </summary> /// </summary>
@ -312,7 +330,7 @@ namespace Perspex.Controls
/// Sets the control's logical parent. /// Sets the control's logical parent.
/// </summary> /// </summary>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
void ISetLogicalParent.SetParent(IControl parent) void ISetLogicalParent.SetParent(ILogical parent)
{ {
var old = this.Parent; var old = this.Parent;
@ -324,13 +342,11 @@ namespace Perspex.Controls
this.SetValue(ParentProperty, parent); this.SetValue(ParentProperty, parent);
} }
[Obsolete("Obsolete this: use properties instead")]
protected static void PseudoClass(PerspexProperty<bool> property, string className) protected static void PseudoClass(PerspexProperty<bool> property, string className)
{ {
PseudoClass(property, x => x, className); PseudoClass(property, x => x, className);
} }
[Obsolete("Obsolete this: use properties instead")]
protected static void PseudoClass<T>( protected static void PseudoClass<T>(
PerspexProperty<T> property, PerspexProperty<T> property,
Func<T, bool> selector, Func<T, bool> selector,
@ -411,5 +427,10 @@ namespace Perspex.Controls
IStyler styler = Locator.Current.GetService<IStyler>(); IStyler styler = Locator.Current.GetService<IStyler>();
styler.ApplyStyles(this); styler.ApplyStyles(this);
} }
protected void RedirectLogicalChildren(IPerspexList<ILogical> collection)
{
this.logicalChildren = collection;
}
} }
} }

2
Perspex.Controls/Deck.cs

@ -23,7 +23,7 @@ namespace Perspex.Controls
PerspexProperty.Register<Deck, IPageTransition>("Transition"); PerspexProperty.Register<Deck, IPageTransition>("Transition");
/// <summary> /// <summary>
/// The default value of <see cref="IItemsPanel"/> for <see cref="Deck"/>. /// The default value of <see cref="IReparentingControl"/> for <see cref="Deck"/>.
/// </summary> /// </summary>
private static readonly ItemsPanelTemplate PanelTemplate = private static readonly ItemsPanelTemplate PanelTemplate =
new ItemsPanelTemplate(() => new Panel()); new ItemsPanelTemplate(() => new Panel());

16
Perspex.Controls/Decorator.cs

@ -11,7 +11,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Base class for controls which decorate a single child control. /// Base class for controls which decorate a single child control.
/// </summary> /// </summary>
public class Decorator : Control, IVisual, ILogical public class Decorator : Control
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Child"/> property. /// Defines the <see cref="Child"/> property.
@ -25,8 +25,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<Thickness> PaddingProperty = public static readonly PerspexProperty<Thickness> PaddingProperty =
PerspexProperty.Register<Decorator, Thickness>(nameof(Padding)); PerspexProperty.Register<Decorator, Thickness>(nameof(Padding));
private PerspexSingleItemList<ILogical> logicalChild = new PerspexSingleItemList<ILogical>();
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Decorator"/> class. /// Initializes static members of the <see cref="Decorator"/> class.
/// </summary> /// </summary>
@ -53,14 +51,6 @@ namespace Perspex.Controls
set { this.SetValue(PaddingProperty, value); } set { this.SetValue(PaddingProperty, value); }
} }
/// <summary>
/// Gets the logical children of the control.
/// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChild; }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
@ -103,16 +93,16 @@ namespace Perspex.Controls
if (oldChild != null) if (oldChild != null)
{ {
((ISetLogicalParent)oldChild).SetParent(null); ((ISetLogicalParent)oldChild).SetParent(null);
this.LogicalChildren.Clear();
this.RemoveVisualChild(oldChild); this.RemoveVisualChild(oldChild);
} }
if (newChild != null) if (newChild != null)
{ {
this.AddVisualChild(newChild); this.AddVisualChild(newChild);
this.LogicalChildren.Add(newChild);
((ISetLogicalParent)newChild).SetParent(this); ((ISetLogicalParent)newChild).SetParent(this);
} }
this.logicalChild.SingleItem = newChild;
} }
} }
} }

10
Perspex.Controls/DropDown.cs

@ -14,7 +14,7 @@ namespace Perspex.Controls
using Perspex.Input; using Perspex.Input;
using Perspex.Layout; using Perspex.Layout;
public class DropDown : SelectingItemsControl, IContentControl, ILogical public class DropDown : SelectingItemsControl, IContentControl
{ {
public static readonly PerspexProperty<object> ContentProperty = public static readonly PerspexProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<DropDown>(); ContentControl.ContentProperty.AddOwner<DropDown>();
@ -28,8 +28,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<bool> IsDropDownOpenProperty = public static readonly PerspexProperty<bool> IsDropDownOpenProperty =
PerspexProperty.Register<DropDown, bool>("IsDropDownOpen"); PerspexProperty.Register<DropDown, bool>("IsDropDownOpen");
private PerspexReadOnlyListView<ILogical> logicalChildren = new PerspexReadOnlyListView<ILogical>();
public DropDown() public DropDown()
{ {
this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent); this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent);
@ -60,11 +58,6 @@ namespace Perspex.Controls
set { this.SetValue(IsDropDownOpenProperty, value); } set { this.SetValue(IsDropDownOpenProperty, value); }
} }
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChildren; }
}
protected override void OnPointerPressed(PointerPressEventArgs e) protected override void OnPointerPressed(PointerPressEventArgs e)
{ {
if (!this.IsDropDownOpen) if (!this.IsDropDownOpen)
@ -76,7 +69,6 @@ namespace Perspex.Controls
protected override void OnTemplateApplied() protected override void OnTemplateApplied()
{ {
var container = this.GetTemplateChild<Panel>("container"); var container = this.GetTemplateChild<Panel>("container");
this.logicalChildren.Source = ((ILogical)container).LogicalChildren;
} }
private void SetContentParent(Tuple<object, object> change) private void SetContentParent(Tuple<object, object> change)

26
Perspex.Controls/IItemsPanel.cs

@ -1,26 +0,0 @@
// -----------------------------------------------------------------------
// <copyright file="IItemsPanel.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
/// <summary>
/// Interface used by <see cref="ItemsControl"/> to set logical ownership of the panel's
/// children.
/// </summary>
/// <remarks>
/// <see cref="ItemsControl"/> 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
/// <see cref="ChildLogicalParent"/>.
/// </remarks>
public interface IItemsPanel
{
/// <summary>
/// Gets or sets the logical parent that should be set on children of the panel.
/// </summary>
ILogical ChildLogicalParent { get; set; }
}
}

28
Perspex.Controls/IReparentingControl.cs

@ -0,0 +1,28 @@
// -----------------------------------------------------------------------
// <copyright file="IReparentingControl.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls
{
using Perspex.Collections;
/// <summary>
/// A control that can make its visual children the logical children of another control.
/// </summary>
public interface IReparentingControl : IControl
{
/// <summary>
/// Requests that the visual children of the control use another control as their logical
/// parent.
/// </summary>
/// <param name="logicalParent">
/// The logical parent for the visual children of the control.
/// </param>
/// <param name="children">
/// The <see cref="ILogical.LogicalChildren"/> collection to modify.
/// </param>
void ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> children);
}
}

2
Perspex.Controls/ISetLogicalParent.cs

@ -18,6 +18,6 @@ namespace Perspex.Controls
/// Sets the control's parent. /// Sets the control's parent.
/// </summary> /// </summary>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
void SetParent(IControl parent); void SetParent(ILogical parent);
} }
} }

22
Perspex.Controls/ItemsControl.cs

@ -22,7 +22,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Displays a collection of items. /// Displays a collection of items.
/// </summary> /// </summary>
public class ItemsControl : TemplatedControl, ILogical public class ItemsControl : TemplatedControl
{ {
/// <summary> /// <summary>
/// The default value for the <see cref="ItemsPanel"/> property. /// The default value for the <see cref="ItemsPanel"/> property.
@ -45,9 +45,6 @@ namespace Perspex.Controls
private IItemContainerGenerator itemContainerGenerator; private IItemContainerGenerator itemContainerGenerator;
private PerspexReadOnlyListView<IVisual, ILogical> logicalChildren =
new PerspexReadOnlyListView<IVisual, ILogical>(x => (ILogical)x);
private IItemsPresenter presenter; private IItemsPresenter presenter;
/// <summary> /// <summary>
@ -113,22 +110,7 @@ namespace Perspex.Controls
protected set protected set
{ {
this.presenter = value; this.presenter = value;
this.logicalChildren.Source = ((IVisual)value?.Panel)?.VisualChildren; (value as IReparentingControl)?.ReparentLogicalChildren(this, this.LogicalChildren);
}
}
/// <summary>
/// Gets the logical children of the control.
/// </summary>
/// <remarks>
/// The logical children of an <see cref="ItemsControl"/> are the item containers.
/// </remarks>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get
{
this.ApplyTemplate();
return this.logicalChildren;
} }
} }

36
Perspex.Controls/Panel.cs

@ -19,7 +19,7 @@ namespace Perspex.Controls
/// Controls can be added to a <see cref="Panel"/> by adding them to its <see cref="Children"/> /// Controls can be added to a <see cref="Panel"/> by adding them to its <see cref="Children"/>
/// collection. All children are layed out to fill the panel. /// collection. All children are layed out to fill the panel.
/// </remarks> /// </remarks>
public class Panel : Control, ILogical, IItemsPanel public class Panel : Control, IReparentingControl
{ {
private Controls children; private Controls children;
@ -82,25 +82,28 @@ namespace Perspex.Controls
} }
/// <summary> /// <summary>
/// Gets the logical children of the control. /// Requests that the visual children of the panel use another control as their logical
/// parent.
/// </summary> /// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren /// <param name="logicalParent">
/// The logical parent for the visual children of the panel.
/// </param>
/// <param name="children">
/// The <see cref="ILogical.LogicalChildren"/> collection to modify.
/// </param>
void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> children)
{ {
get { return this.children; } Contract.Requires<ArgumentNullException>(logicalParent != null);
} Contract.Requires<ArgumentNullException>(children != null);
/// <inheritdoc/> this.childLogicalParent = logicalParent;
ILogical IItemsPanel.ChildLogicalParent this.RedirectLogicalChildren(children);
{
get
{
return this.childLogicalParent;
}
set foreach (var control in this.Children)
{ {
this.childLogicalParent = value; ((ISetLogicalParent)control).SetParent(null);
this.SetLogicalParent(this.Children); ((ISetLogicalParent)control).SetParent((IControl)logicalParent);
children.Add(control);
} }
} }
@ -156,12 +159,14 @@ namespace Perspex.Controls
controls = e.NewItems.OfType<Control>().ToList(); controls = e.NewItems.OfType<Control>().ToList();
this.SetLogicalParent(controls); this.SetLogicalParent(controls);
this.AddVisualChildren(e.NewItems.OfType<Visual>()); this.AddVisualChildren(e.NewItems.OfType<Visual>());
this.LogicalChildren.InsertRange(e.NewStartingIndex, controls);
this.OnChildrenAdded(controls); this.OnChildrenAdded(controls);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType<Control>().ToList(); controls = e.OldItems.OfType<Control>().ToList();
this.ClearLogicalParent(e.OldItems.OfType<Control>()); this.ClearLogicalParent(e.OldItems.OfType<Control>());
this.LogicalChildren.RemoveAll(controls);
this.RemoveVisualChildren(e.OldItems.OfType<Visual>()); this.RemoveVisualChildren(e.OldItems.OfType<Visual>());
this.OnChildrenRemoved(controls); this.OnChildrenRemoved(controls);
break; break;
@ -169,6 +174,7 @@ namespace Perspex.Controls
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
controls = e.OldItems.OfType<Control>().ToList(); controls = e.OldItems.OfType<Control>().ToList();
this.ClearLogicalParent(controls); this.ClearLogicalParent(controls);
this.LogicalChildren.Clear();
this.ClearVisualChildren(); this.ClearVisualChildren();
this.AddVisualChildren(this.children); this.AddVisualChildren(this.children);
this.OnChildrenAdded(controls); this.OnChildrenAdded(controls);

2
Perspex.Controls/Perspex.Controls.csproj

@ -52,7 +52,7 @@
<Compile Include="AdornerTemplate.cs" /> <Compile Include="AdornerTemplate.cs" />
<Compile Include="DropDown.cs" /> <Compile Include="DropDown.cs" />
<Compile Include="IContentControl.cs" /> <Compile Include="IContentControl.cs" />
<Compile Include="IItemsPanel.cs" /> <Compile Include="IReparentingControl.cs" />
<Compile Include="INamed.cs" /> <Compile Include="INamed.cs" />
<Compile Include="INavigablePanel.cs" /> <Compile Include="INavigablePanel.cs" />
<Compile Include="Deck.cs" /> <Compile Include="Deck.cs" />

14
Perspex.Controls/Popup.cs

@ -7,8 +7,8 @@
namespace Perspex.Controls namespace Perspex.Controls
{ {
using System; using System;
using Perspex.Interactivity;
using Perspex.Collections; using Perspex.Collections;
using Perspex.Interactivity;
using Perspex.Rendering; using Perspex.Rendering;
using Perspex.VisualTree; using Perspex.VisualTree;
using Splat; using Splat;
@ -16,7 +16,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Displays a popup window. /// Displays a popup window.
/// </summary> /// </summary>
public class Popup : Control, ILogical, IVisualTreeHost public class Popup : Control, IVisualTreeHost
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Child"/> property. /// Defines the <see cref="Child"/> property.
@ -160,14 +160,6 @@ namespace Perspex.Controls
set { this.SetValue(StaysOpenProperty, value); } set { this.SetValue(StaysOpenProperty, value); }
} }
/// <summary>
/// Gets the logical children of the popup.
/// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChild; }
}
/// <summary> /// <summary>
/// Gets the root of the popup window. /// Gets the root of the popup window.
/// </summary> /// </summary>
@ -297,7 +289,7 @@ namespace Perspex.Controls
/// <returns>The popup's position in screen coordinates.</returns> /// <returns>The popup's position in screen coordinates.</returns>
private Point GetPosition() private Point GetPosition()
{ {
var target = this.PlacementTarget ?? this.GetVisualParent<Control>(); var target = this.PlacementTarget ?? this.GetVisualParent<Control>();
Point point; Point point;
if (target != null) if (target != null)

3
Perspex.Controls/PopupRoot.cs

@ -7,11 +7,12 @@
namespace Perspex.Controls namespace Perspex.Controls
{ {
using System; using System;
using Collections;
using Perspex.Interactivity; using Perspex.Interactivity;
using Perspex.Media; using Perspex.Media;
using Perspex.Platform; using Perspex.Platform;
using Splat;
using Perspex.VisualTree; using Perspex.VisualTree;
using Splat;
/// <summary> /// <summary>
/// The root window of a <see cref="Popup"/>. /// The root window of a <see cref="Popup"/>.

73
Perspex.Controls/Presenters/ContentPresenter.cs

@ -15,36 +15,47 @@ namespace Perspex.Controls.Presenters
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Media; using Perspex.Media;
public class ContentPresenter : Control, IVisual, ILogical, IPresenter /// <summary>
/// Presents a single item of data inside a <see cref="TemplatedControl"/> template.
/// </summary>
public class ContentPresenter : Control, IPresenter
{ {
/// <summary>
/// Defines the <see cref="Content"/> property.
/// </summary>
public static readonly PerspexProperty<object> ContentProperty = public static readonly PerspexProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<ContentPresenter>(); ContentControl.ContentProperty.AddOwner<ContentPresenter>();
private bool createdChild; private bool createdChild;
private PerspexSingleItemList<ILogical> logicalChild = new PerspexSingleItemList<ILogical>(); private ILogical logicalParent;
public ContentPresenter() /// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
/// </summary>
static ContentPresenter()
{ {
this.GetObservable(ContentProperty).Skip(1).Subscribe(this.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
} }
public Control Child /// <summary>
/// Gets the control displayed by the presenter.
/// </summary>
public IControl Child
{ {
get { return (Control)this.logicalChild.SingleItem; } get { return (Control)this.LogicalChildren.SingleOrDefault(); }
} }
/// <summary>
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
public object Content public object Content
{ {
get { return this.GetValue(ContentProperty); } get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); } set { this.SetValue(ContentProperty, value); }
} }
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren /// <inheritdoc/>
{
get { return this.logicalChild; }
}
public override sealed void ApplyTemplate() public override sealed void ApplyTemplate()
{ {
if (!this.createdChild) if (!this.createdChild)
@ -53,11 +64,27 @@ namespace Perspex.Controls.Presenters
} }
} }
/// <inheritdoc/>
void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> 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);
}
/// <inheritdoc/>
protected override Size MeasureCore(Size availableSize) protected override Size MeasureCore(Size availableSize)
{ {
return base.MeasureCore(availableSize); return base.MeasureCore(availableSize);
} }
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var child = this.Child; var child = this.Child;
@ -71,27 +98,30 @@ namespace Perspex.Controls.Presenters
return new Size(); return new Size();
} }
private void ContentChanged(object content) /// <summary>
/// Called when the <see cref="Content"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ContentChanged(PerspexPropertyChangedEventArgs e)
{ {
this.createdChild = false; this.createdChild = false;
this.InvalidateMeasure(); this.InvalidateMeasure();
} }
/// <summary>
/// Creates the <see cref="Child"/> control from the <see cref="Content"/>.
/// </summary>
private void CreateChild() private void CreateChild()
{ {
Control result = null; IControl result = null;
object content = this.Content; object content = this.Content;
this.ClearVisualChildren(); this.ClearVisualChildren();
if (content != null) if (content != null)
{ {
result = (Control)this.MaterializeDataTemplate(content); result = this.MaterializeDataTemplate(content);
((ISetLogicalParent)result).SetParent(this.logicalParent ?? this);
if (result.Parent == null)
{
((ISetLogicalParent)result).SetParent(this.TemplatedParent as Control);
}
var templatedParent = this.TemplatedParent as TemplatedControl; var templatedParent = this.TemplatedParent as TemplatedControl;
@ -100,11 +130,12 @@ namespace Perspex.Controls.Presenters
templatedParent = templatedParent.TemplatedParent as TemplatedControl; templatedParent = templatedParent.TemplatedParent as TemplatedControl;
} }
result.TemplatedParent = templatedParent; ((Control)result).TemplatedParent = templatedParent;
this.AddVisualChild(result); this.AddVisualChild(result);
} }
this.logicalChild.SingleItem = result; this.LogicalChildren.Clear();
this.LogicalChildren.Add(result);
this.createdChild = true; this.createdChild = true;
} }
} }

20
Perspex.Controls/Presenters/DeckPresenter.cs

@ -12,6 +12,7 @@ namespace Perspex.Controls.Presenters
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Perspex.Animation; using Perspex.Animation;
using Collections;
using Perspex.Controls.Generators; using Perspex.Controls.Generators;
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
using Perspex.Controls.Utils; using Perspex.Controls.Utils;
@ -20,7 +21,7 @@ namespace Perspex.Controls.Presenters
/// <summary> /// <summary>
/// Displays pages inside an <see cref="ItemsControl"/>. /// Displays pages inside an <see cref="ItemsControl"/>.
/// </summary> /// </summary>
public class DeckPresenter : Control, IItemsPresenter, ITemplatedControl public class DeckPresenter : Control, IItemsPresenter
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Items"/> property. /// Defines the <see cref="Items"/> property.
@ -131,6 +132,14 @@ namespace Perspex.Controls.Presenters
set { this.SetValue(TransitionProperty, value); } set { this.SetValue(TransitionProperty, value); }
} }
Panel IItemsPresenter.Panel
{
get
{
throw new NotImplementedException();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public override sealed void ApplyTemplate() public override sealed void ApplyTemplate()
{ {
@ -140,6 +149,12 @@ namespace Perspex.Controls.Presenters
} }
} }
/// <inheritdoc/>
void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> children)
{
throw new NotImplementedException();
}
/// <summary> /// <summary>
/// Creates the <see cref="Panel"/>. /// Creates the <see cref="Panel"/>.
/// </summary> /// </summary>
@ -150,8 +165,7 @@ namespace Perspex.Controls.Presenters
if (this.ItemsPanel != null) if (this.ItemsPanel != null)
{ {
this.Panel = this.ItemsPanel.Build(); this.Panel = this.ItemsPanel.Build();
this.Panel.TemplatedParent = this; this.Panel.TemplatedParent = this.TemplatedParent;
((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical;
this.AddVisualChild(this.Panel); this.AddVisualChild(this.Panel);
this.createdPanel = true; this.createdPanel = true;
var task = this.MoveToPage(-1, this.SelectedIndex); var task = this.MoveToPage(-1, this.SelectedIndex);

4
Perspex.Controls/Presenters/IPresenter.cs

@ -9,7 +9,7 @@ namespace Perspex.Controls.Presenters
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
/// <summary> /// <summary>
/// Interface for presenters such as <see cref="ContentPresenter"/> and /// Interface for presenters such as <see cref="ContentPresenter"/> and
/// <see cref="ItemsPresenter"/>. /// <see cref="ItemsPresenter"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
@ -18,7 +18,7 @@ namespace Perspex.Controls.Presenters
/// of a <see cref="TemplatedControl"/> then that signals that the visual child /// of a <see cref="TemplatedControl"/> then that signals that the visual child
/// of the presenter is not a part of the template. /// of the presenter is not a part of the template.
/// </remarks> /// </remarks>
public interface IPresenter : IVisual, INamed public interface IPresenter : IControl, INamed, IReparentingControl
{ {
} }
} }

11
Perspex.Controls/Presenters/ItemsPresenter.cs

@ -9,6 +9,7 @@ namespace Perspex.Controls.Presenters
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using Collections;
using Perspex.Controls.Generators; using Perspex.Controls.Generators;
using Perspex.Input; using Perspex.Input;
using Perspex.Styling; using Perspex.Styling;
@ -16,7 +17,7 @@ namespace Perspex.Controls.Presenters
/// <summary> /// <summary>
/// Displays items inside an <see cref="ItemsControl"/>. /// Displays items inside an <see cref="ItemsControl"/>.
/// </summary> /// </summary>
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl, IReparentingControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Items"/> property. /// Defines the <see cref="Items"/> property.
@ -109,6 +110,13 @@ namespace Perspex.Controls.Presenters
} }
} }
/// <inheritdoc/>
public void ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> children)
{
this.ApplyTemplate();
((IReparentingControl)this.Panel).ReparentLogicalChildren(logicalParent, children);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
@ -133,7 +141,6 @@ namespace Perspex.Controls.Presenters
this.Panel = this.ItemsPanel.Build(); this.Panel = this.ItemsPanel.Build();
this.Panel.TemplatedParent = this; this.Panel.TemplatedParent = this;
KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this)); KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this));
((IItemsPanel)this.Panel).ChildLogicalParent = this.TemplatedParent as ILogical;
this.AddVisualChild(this.Panel); this.AddVisualChild(this.Panel);
this.createdPanel = true; this.createdPanel = true;
this.CreateItemsAndListenForChanges(this.Items); this.CreateItemsAndListenForChanges(this.Items);

2
Perspex.Controls/Presenters/TextPresenter.cs

@ -13,7 +13,7 @@ namespace Perspex.Controls.Presenters
using Perspex.Threading; using Perspex.Threading;
using Perspex.VisualTree; using Perspex.VisualTree;
public class TextPresenter : TextBlock, IPresenter public class TextPresenter : TextBlock
{ {
public static readonly PerspexProperty<int> CaretIndexProperty = public static readonly PerspexProperty<int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>(); TextBox.CaretIndexProperty.AddOwner<TextPresenter>();

10
Perspex.Controls/TabControl.cs

@ -15,7 +15,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// A tab control that displays a tab strip along with the content of the selected tab. /// A tab control that displays a tab strip along with the content of the selected tab.
/// </summary> /// </summary>
public class TabControl : SelectingItemsControl, ILogical public class TabControl : SelectingItemsControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="SelectedContent"/> property. /// Defines the <see cref="SelectedContent"/> property.
@ -82,14 +82,6 @@ namespace Perspex.Controls
set { this.SetValue(TransitionProperty, value); } set { this.SetValue(TransitionProperty, value); }
} }
/// <summary>
/// Gets the logical children of the control.
/// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChildren; }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {

100
Tests/Perspex.Controls.UnitTests/BorderTests.cs

@ -6,110 +6,10 @@
namespace Perspex.Controls.UnitTests namespace Perspex.Controls.UnitTests
{ {
using System.Collections.Specialized;
using System.Linq;
using Xunit; using Xunit;
public class BorderTests 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] [Fact]
public void Measure_Should_Return_BorderThickness_Plus_Padding_When_No_Child_Present() public void Measure_Should_Return_BorderThickness_Plus_Padding_When_No_Child_Present()
{ {

37
Tests/Perspex.Controls.UnitTests/ContentControlTests.cs

@ -14,6 +14,7 @@ namespace Perspex.Controls.UnitTests
using Perspex.Controls.Presenters; using Perspex.Controls.Presenters;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Layout; using Perspex.Layout;
using LogicalTree;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Styling; using Perspex.Styling;
using Perspex.VisualTree; using Perspex.VisualTree;
@ -180,30 +181,28 @@ namespace Perspex.Controls.UnitTests
target.Content = null; target.Content = null;
// Need to call ApplyTemplate on presenter for LogocalChildren to be updated. // Need to call ApplyTemplate on presenter for LogicalChildren to be updated.
var presenter = target.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); target.Presenter.ApplyTemplate();
presenter.ApplyTemplate();
Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); Assert.Empty(target.GetLogicalChildren());
} }
[Fact] [Fact]
public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged()
{ {
var contentControl = new ContentControl(); var target = new ContentControl();
var child = new Control(); var child = new Control();
var called = false; var called = false;
((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Add; called = e.Action == NotifyCollectionChangedAction.Add;
contentControl.Template = this.GetTemplate(); target.Template = this.GetTemplate();
contentControl.Content = child; target.Content = child;
contentControl.ApplyTemplate(); target.ApplyTemplate();
// Need to call ApplyTemplate on presenter for CollectionChanged to be called. // Need to call ApplyTemplate on presenter for LogicalChildren to be updated.
var presenter = contentControl.GetTemplateChildren().Single(x => x.Name == "contentPresenter"); target.Presenter.ApplyTemplate();
presenter.ApplyTemplate();
Assert.True(called); Assert.True(called);
} }
@ -211,21 +210,21 @@ namespace Perspex.Controls.UnitTests
[Fact] [Fact]
public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged()
{ {
var contentControl = new ContentControl(); var target = new ContentControl();
var child = new Control(); var child = new Control();
var called = false; var called = false;
contentControl.Template = this.GetTemplate(); target.Template = this.GetTemplate();
contentControl.Content = child; target.Content = child;
contentControl.ApplyTemplate(); target.ApplyTemplate();
((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Remove; called = e.Action == NotifyCollectionChangedAction.Remove;
contentControl.Content = null; target.Content = null;
// Need to call ApplyTemplate on presenter for CollectionChanged to be called. // 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(); presenter.ApplyTemplate();
Assert.True(called); Assert.True(called);

13
Tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs

@ -6,20 +6,10 @@
namespace Perspex.Controls.UnitTests namespace Perspex.Controls.UnitTests
{ {
using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Moq;
using Perspex.Controls; using Perspex.Controls;
using Perspex.Controls.Presenters; 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; using Xunit;
public class ContentPresenterTests public class ContentPresenterTests
@ -74,8 +64,7 @@ namespace Perspex.Controls.UnitTests
target.Content = child; target.Content = child;
target.ApplyTemplate(); target.ApplyTemplate();
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
called = e.Action == NotifyCollectionChangedAction.Remove;
target.Content = null; target.Content = null;
target.ApplyTemplate(); target.ApplyTemplate();

5
Tests/Perspex.Controls.UnitTests/DecoratorTests.cs

@ -67,7 +67,7 @@ namespace Perspex.Controls.UnitTests
var child = new Control(); var child = new Control();
var called = false; var called = false;
((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) =>
called = e.Action == NotifyCollectionChangedAction.Add; called = e.Action == NotifyCollectionChangedAction.Add;
decorator.Child = child; decorator.Child = child;
@ -102,8 +102,7 @@ namespace Perspex.Controls.UnitTests
decorator.Child = child1; decorator.Child = child1;
((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => called = true;
called = e.Action == NotifyCollectionChangedAction.Replace;
decorator.Child = child2; decorator.Child = child2;

26
Tests/Perspex.Controls.UnitTests/PanelTests.cs

@ -7,6 +7,7 @@
namespace Perspex.Controls.UnitTests namespace Perspex.Controls.UnitTests
{ {
using System.Linq; using System.Linq;
using Collections;
using Xunit; using Xunit;
public class PanelTests public class PanelTests
@ -106,5 +107,30 @@ namespace Perspex.Controls.UnitTests
Assert.Equal(new Control[0], panel.Children); Assert.Equal(new Control[0], panel.Children);
Assert.Equal(new ILogical[0], ((ILogical)panel).LogicalChildren.ToList()); 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<ILogical> LogicalChildren
{
get { return base.LogicalChildren; }
}
}
} }
} }

Loading…
Cancel
Save