Browse Source

Allow TabControl to reparent children of its Deck.

pull/72/merge
Steven Kirk 11 years ago
parent
commit
07aa92e1b7
  1. 57
      Perspex.Controls/ContentControl.cs
  2. 1
      Perspex.Controls/DropDown.cs
  3. 31
      Perspex.Controls/IReparentingHost.cs
  4. 33
      Perspex.Controls/ItemsControl.cs
  5. 2
      Perspex.Controls/Perspex.Controls.csproj
  6. 47
      Perspex.Controls/Presenters/ContentPresenter.cs
  7. 27
      Perspex.Controls/Presenters/DeckPresenter.cs
  8. 25
      Perspex.Controls/Presenters/IContentPresenter.cs
  9. 2
      Perspex.Controls/Presenters/IPresenter.cs
  10. 24
      Perspex.Controls/Presenters/ItemsPresenter.cs
  11. 22
      Perspex.Controls/TabControl.cs
  12. 29
      Perspex.Controls/Templates/TemplateExtensions.cs
  13. 2
      Perspex.Themes.Default/TabStripStyle.cs
  14. 3
      Tests/Perspex.Controls.UnitTests/ContentControlTests.cs
  15. 28
      Tests/Perspex.Controls.UnitTests/TabControlTests.cs

57
Perspex.Controls/ContentControl.cs

@ -1,6 +1,6 @@
// -----------------------------------------------------------------------
// <copyright file="ContentControl.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
@ -13,52 +13,97 @@ namespace Perspex.Controls
using Perspex.Controls.Templates;
using Perspex.Layout;
public class ContentControl : TemplatedControl, IContentControl
/// <summary>
/// Displays <see cref="Content"/> according to a <see cref="DataTemplate"/>.
/// </summary>
public class ContentControl : TemplatedControl, IContentControl, IReparentingHost
{
/// <summary>
/// Defines the <see cref="Content"/> property.
/// </summary>
public static readonly PerspexProperty<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>("Content");
PerspexProperty.Register<ContentControl, object>(nameof(Content));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly PerspexProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, HorizontalAlignment>("HorizontalContentAlignment");
PerspexProperty.Register<ContentControl, HorizontalAlignment>(nameof(HorizontalContentAlignment));
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, VerticalAlignment>("VerticalContentAlignment");
PerspexProperty.Register<ContentControl, VerticalAlignment>(nameof(VerticalContentAlignment));
/// <summary>
/// Initializes a new instance of the <see cref="ContentControl"/> class.
/// </summary>
public ContentControl()
{
}
/// <summary>
/// Gets or sets the content to display.
/// </summary>
public object Content
{
get { return this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
/// <summary>
/// Gets the presenter from the control's template.
/// </summary>
public ContentPresenter Presenter
{
get;
private set;
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get { return this.GetValue(HorizontalContentAlignmentProperty); }
set { this.SetValue(HorizontalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get { return this.GetValue(VerticalContentAlignmentProperty); }
set { this.SetValue(VerticalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets a writeable logical children collection from the host.
/// </summary>
IPerspexList<ILogical> IReparentingHost.LogicalChildren => this.LogicalChildren;
/// <summary>
/// Asks the control whether it wants to reparent the logical children of the specified
/// control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>
/// True if the control wants to reparent its logical children otherwise false.
/// </returns>
bool IReparentingHost.WillReparentChildrenOf(IControl control)
{
return control is IContentPresenter && control.TemplatedParent == this;
}
/// <inheritdoc/>
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");
((IReparentingControl)this.Presenter)?.ReparentLogicalChildren(this, this.LogicalChildren);
}
}
}

1
Perspex.Controls/DropDown.cs

@ -69,7 +69,6 @@ namespace Perspex.Controls
protected override void OnTemplateApplied()
{
var container = this.GetTemplateChild<Panel>("container");
((IReparentingControl)container).ReparentLogicalChildren(this, this.LogicalChildren);
}
private void SetContentParent(Tuple<object, object> change)

31
Perspex.Controls/IReparentingHost.cs

@ -0,0 +1,31 @@
// -----------------------------------------------------------------------
// <copyright file="IReparentingHost.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 use the visual children of another control as its logical children.
/// </summary>
public interface IReparentingHost : ILogical
{
/// <summary>
/// Gets a writeable logical children collection from the host.
/// </summary>
new IPerspexList<ILogical> LogicalChildren { get; }
/// <summary>
/// Asks the control whether it wants to reparent the logical children of the specified
/// control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>
/// True if the control wants to reparent its logical children otherwise false.
/// </returns>
bool WillReparentChildrenOf(IControl control);
}
}

33
Perspex.Controls/ItemsControl.cs

@ -22,7 +22,7 @@ namespace Perspex.Controls
/// <summary>
/// Displays a collection of items.
/// </summary>
public class ItemsControl : TemplatedControl
public class ItemsControl : TemplatedControl, IReparentingHost
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
@ -45,8 +45,6 @@ namespace Perspex.Controls
private IItemContainerGenerator itemContainerGenerator;
private IItemsPresenter presenter;
/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
/// </summary>
@ -102,16 +100,27 @@ namespace Perspex.Controls
/// </summary>
public IItemsPresenter Presenter
{
get
{
return this.presenter;
}
get;
set;
}
protected set
{
this.presenter = value;
(value as IReparentingControl)?.ReparentLogicalChildren(this, this.LogicalChildren);
}
/// <inheritdoc/>
IPerspexList<ILogical> IReparentingHost.LogicalChildren
{
get { return this.LogicalChildren; }
}
/// <summary>
/// Asks the control whether it wants to reparent the logical children of the specified
/// control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>
/// True if the control wants to reparent its logical children otherwise false.
/// </returns>
bool IReparentingHost.WillReparentChildrenOf(IControl control)
{
return control is IItemsPresenter && control.TemplatedParent == this;
}
/// <summary>

2
Perspex.Controls/Perspex.Controls.csproj

@ -39,10 +39,12 @@
<Compile Include="Generators\ITreeItemContainerGenerator.cs" />
<Compile Include="Generators\ItemContainers.cs" />
<Compile Include="IControl.cs" />
<Compile Include="IReparentingHost.cs" />
<Compile Include="ISetLogicalParent.cs" />
<Compile Include="MenuItemAccessKeyHandler.cs" />
<Compile Include="Mixins\SelectableMixin.cs" />
<Compile Include="Parsers\GridLengthsParser.cs" />
<Compile Include="Presenters\IContentPresenter.cs" />
<Compile Include="Primitives\AccessText.cs" />
<Compile Include="Border.cs" />
<Compile Include="GlobalSuppressions.cs" />

47
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;
/// <summary>
/// Presents a single item of data inside a <see cref="TemplatedControl"/> template.
/// </summary>
public class ContentPresenter : Control, IPresenter
public class ContentPresenter : Control, IContentPresenter
{
/// <summary>
/// Defines the <see cref="Content"/> property.
@ -25,8 +21,6 @@ namespace Perspex.Controls.Presenters
private bool createdChild;
private ILogical logicalParent;
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
/// </summary>
@ -40,7 +34,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public IControl Child
{
get { return (Control)this.LogicalChildren.SingleOrDefault(); }
get;
private set;
}
/// <summary>
@ -61,20 +56,6 @@ 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)
{
@ -110,23 +91,29 @@ namespace Perspex.Controls.Presenters
/// </summary>
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;

27
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;
/// <summary>
@ -140,28 +141,28 @@ namespace Perspex.Controls.Presenters
}
}
/// <inheritdoc/>
void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList<ILogical> children)
{
this.ApplyTemplate();
((IReparentingControl)this.Panel).ReparentLogicalChildren(logicalParent, children);
}
/// <summary>
/// Creates the <see cref="Panel"/>.
/// </summary>
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);
}
/// <summary>

25
Perspex.Controls/Presenters/IContentPresenter.cs

@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="IContentPresenter.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls.Presenters
{
/// <summary>
/// Interface for controls that present a single item of data inside a
/// <see cref="TemplatedControl"/> template.
/// </summary>
public interface IContentPresenter : IPresenter
{
/// <summary>
/// Gets the control displayed by the presenter.
/// </summary>
IControl Child { get; }
/// <summary>
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
object Content { get; set; }
}
}

2
Perspex.Controls/Presenters/IPresenter.cs

@ -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 : IControl, INamed, IReparentingControl
public interface IPresenter : IControl, INamed
{
}
}

24
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;
/// <summary>
/// Displays items inside an <see cref="ItemsControl"/>.
/// </summary>
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl, IReparentingControl
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl
{
/// <summary>
/// Defines the <see cref="Items"/> property.
@ -110,13 +110,6 @@ 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)
{
@ -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);
}

22
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;
/// <summary>
/// A tab control that displays a tab strip along with the content of the selected tab.
/// </summary>
public class TabControl : SelectingItemsControl
public class TabControl : SelectingItemsControl, IReparentingHost
{
/// <summary>
/// Defines the <see cref="SelectedContent"/> property.
@ -70,15 +70,17 @@ namespace Perspex.Controls
set { this.SetValue(TransitionProperty, value); }
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
/// <summary>
/// Asks the control whether it wants to reparent the logical children of the specified
/// control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>
/// True if the control wants to reparent its logical children otherwise false.
/// </returns>
bool IReparentingHost.WillReparentChildrenOf(IControl control)
{
base.OnTemplateApplied();
var deck = this.GetTemplateChild<Deck>("deck");
((IReparentingControl)deck.Presenter.Panel).ReparentLogicalChildren(
this,
this.LogicalChildren);
return control is DeckPresenter;
}
/// <summary>

29
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<IReparentingHost>();
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<T>(this ITemplatedControl control, string id) where T : INamed
{
return control.GetTemplateChildren().OfType<T>().SingleOrDefault(x => x.Name == id);

2
Perspex.Themes.Default/TabStripStyle.cs

@ -26,7 +26,7 @@ namespace Perspex.Themes.Default
new Setter(TabStrip.TemplateProperty, new ControlTemplate<TabStrip>(this.Template)),
},
},
new Style(x => x.OfType<TabStrip>().Template().OfType<ItemsPresenter>().Template().OfType<StackPanel>())
new Style(x => x.OfType<TabStrip>().Template().OfType<StackPanel>())
{
Setters = new[]
{

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

28
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<TabControl>(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<TabItem>().ElementAt(1);
Assert.Same(target.SelectedTab, target.SelectedItem);
Assert.Equal("bar", target.SelectedContent);
}
[Fact]

Loading…
Cancel
Save