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>
{
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.Layout;
public class ContentControl : TemplatedControl, IContentControl, ILogical
public class ContentControl : TemplatedControl, IContentControl
{
public static readonly PerspexProperty<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>("Content");
@ -24,8 +24,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, VerticalAlignment>("VerticalContentAlignment");
private PerspexReadOnlyListView<ILogical> logicalChildren = new PerspexReadOnlyListView<ILogical>();
public ContentControl()
{
}
@ -54,18 +52,13 @@ namespace Perspex.Controls
set { this.SetValue(VerticalContentAlignmentProperty, value); }
}
IPerspexReadOnlyList<ILogical> 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>("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 IPerspexList<ILogical> logicalChildren;
private Styles styles;
/// <summary>
@ -267,7 +269,7 @@ namespace Perspex.Controls
/// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return EmptyChildren; }
get { return this.LogicalChildren; }
}
/// <summary>
@ -284,6 +286,22 @@ namespace Perspex.Controls
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>
/// Tries to being the control into view.
/// </summary>
@ -312,7 +330,7 @@ namespace Perspex.Controls
/// Sets the control's logical parent.
/// </summary>
/// <param name="parent">The parent.</param>
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<bool> property, string className)
{
PseudoClass(property, x => x, className);
}
[Obsolete("Obsolete this: use properties instead")]
protected static void PseudoClass<T>(
PerspexProperty<T> property,
Func<T, bool> selector,
@ -411,5 +427,10 @@ namespace Perspex.Controls
IStyler styler = Locator.Current.GetService<IStyler>();
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");
/// <summary>
/// The default value of <see cref="IItemsPanel"/> for <see cref="Deck"/>.
/// The default value of <see cref="IReparentingControl"/> for <see cref="Deck"/>.
/// </summary>
private static readonly ItemsPanelTemplate PanelTemplate =
new ItemsPanelTemplate(() => new Panel());

16
Perspex.Controls/Decorator.cs

@ -11,7 +11,7 @@ namespace Perspex.Controls
/// <summary>
/// Base class for controls which decorate a single child control.
/// </summary>
public class Decorator : Control, IVisual, ILogical
public class Decorator : Control
{
/// <summary>
/// Defines the <see cref="Child"/> property.
@ -25,8 +25,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<Thickness> PaddingProperty =
PerspexProperty.Register<Decorator, Thickness>(nameof(Padding));
private PerspexSingleItemList<ILogical> logicalChild = new PerspexSingleItemList<ILogical>();
/// <summary>
/// Initializes static members of the <see cref="Decorator"/> class.
/// </summary>
@ -53,14 +51,6 @@ namespace Perspex.Controls
set { this.SetValue(PaddingProperty, value); }
}
/// <summary>
/// Gets the logical children of the control.
/// </summary>
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChild; }
}
/// <inheritdoc/>
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;
}
}
}

10
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<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<DropDown>();
@ -28,8 +28,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<bool> IsDropDownOpenProperty =
PerspexProperty.Register<DropDown, bool>("IsDropDownOpen");
private PerspexReadOnlyListView<ILogical> logicalChildren = new PerspexReadOnlyListView<ILogical>();
public DropDown()
{
this.GetObservableWithHistory(ContentProperty).Subscribe(this.SetContentParent);
@ -60,11 +58,6 @@ namespace Perspex.Controls
set { this.SetValue(IsDropDownOpenProperty, value); }
}
IPerspexReadOnlyList<ILogical> 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<Panel>("container");
this.logicalChildren.Source = ((ILogical)container).LogicalChildren;
}
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.
/// </summary>
/// <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>
/// Displays a collection of items.
/// </summary>
public class ItemsControl : TemplatedControl, ILogical
public class ItemsControl : TemplatedControl
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
@ -45,9 +45,6 @@ namespace Perspex.Controls
private IItemContainerGenerator itemContainerGenerator;
private PerspexReadOnlyListView<IVisual, ILogical> logicalChildren =
new PerspexReadOnlyListView<IVisual, ILogical>(x => (ILogical)x);
private IItemsPresenter presenter;
/// <summary>
@ -113,22 +110,7 @@ namespace Perspex.Controls
protected set
{
this.presenter = value;
this.logicalChildren.Source = ((IVisual)value?.Panel)?.VisualChildren;
}
}
/// <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;
(value as IReparentingControl)?.ReparentLogicalChildren(this, 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"/>
/// collection. All children are layed out to fill the panel.
/// </remarks>
public class Panel : Control, ILogical, IItemsPanel
public class Panel : Control, IReparentingControl
{
private Controls children;
@ -82,25 +82,28 @@ namespace Perspex.Controls
}
/// <summary>
/// Gets the logical children of the control.
/// Requests that the visual children of the panel use another control as their logical
/// parent.
/// </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/>
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<Control>().ToList();
this.SetLogicalParent(controls);
this.AddVisualChildren(e.NewItems.OfType<Visual>());
this.LogicalChildren.InsertRange(e.NewStartingIndex, controls);
this.OnChildrenAdded(controls);
break;
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType<Control>().ToList();
this.ClearLogicalParent(e.OldItems.OfType<Control>());
this.LogicalChildren.RemoveAll(controls);
this.RemoveVisualChildren(e.OldItems.OfType<Visual>());
this.OnChildrenRemoved(controls);
break;
@ -169,6 +174,7 @@ namespace Perspex.Controls
case NotifyCollectionChangedAction.Reset:
controls = e.OldItems.OfType<Control>().ToList();
this.ClearLogicalParent(controls);
this.LogicalChildren.Clear();
this.ClearVisualChildren();
this.AddVisualChildren(this.children);
this.OnChildrenAdded(controls);

2
Perspex.Controls/Perspex.Controls.csproj

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

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

3
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;
/// <summary>
/// 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.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 =
ContentControl.ContentProperty.AddOwner<ContentPresenter>();
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
{
get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
IPerspexReadOnlyList<ILogical> ILogical.LogicalChildren
{
get { return this.logicalChild; }
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
{
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)
{
return base.MeasureCore(availableSize);
}
/// <inheritdoc/>
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)
/// <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.InvalidateMeasure();
}
/// <summary>
/// Creates the <see cref="Child"/> control from the <see cref="Content"/>.
/// </summary>
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;
}
}

20
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
/// <summary>
/// Displays pages inside an <see cref="ItemsControl"/>.
/// </summary>
public class DeckPresenter : Control, IItemsPresenter, ITemplatedControl
public class DeckPresenter : Control, IItemsPresenter
{
/// <summary>
/// Defines the <see cref="Items"/> property.
@ -131,6 +132,14 @@ namespace Perspex.Controls.Presenters
set { this.SetValue(TransitionProperty, value); }
}
Panel IItemsPresenter.Panel
{
get
{
throw new NotImplementedException();
}
}
/// <inheritdoc/>
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>
/// Creates the <see cref="Panel"/>.
/// </summary>
@ -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);

4
Perspex.Controls/Presenters/IPresenter.cs

@ -9,7 +9,7 @@ namespace Perspex.Controls.Presenters
using Perspex.Controls.Primitives;
/// <summary>
/// Interface for presenters such as <see cref="ContentPresenter"/> and
/// Interface for presenters such as <see cref="ContentPresenter"/> and
/// <see cref="ItemsPresenter"/>.
/// </summary>
/// <remarks>
@ -18,7 +18,7 @@ namespace Perspex.Controls.Presenters
/// of a <see cref="TemplatedControl"/> then that signals that the visual child
/// of the presenter is not a part of the template.
/// </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.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
/// <summary>
/// Displays items inside an <see cref="ItemsControl"/>.
/// </summary>
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl, IReparentingControl
{
/// <summary>
/// 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/>
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);

2
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<int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>();

10
Perspex.Controls/TabControl.cs

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

100
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()
{

37
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);

13
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();

5
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;

26
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<ILogical> LogicalChildren
{
get { return base.LogicalChildren; }
}
}
}
}

Loading…
Cancel
Save