100 changed files with 1925 additions and 1277 deletions
@ -0,0 +1,45 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Perspex.Controls.Generators |
|||
{ |
|||
/// <summary>
|
|||
/// Holds information about an item container generated by an
|
|||
/// <see cref="IItemContainerGenerator"/>.
|
|||
/// </summary>
|
|||
public class ItemContainer |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemContainer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="container">The container control.</param>
|
|||
/// <param name="item">The item that the container represents.</param>
|
|||
/// <param name="index">
|
|||
/// The index of the item in the <see cref="ItemsControl.Items"/> collection.
|
|||
/// </param>
|
|||
public ItemContainer(IControl container, object item, int index) |
|||
{ |
|||
ContainerControl = container; |
|||
Item = item; |
|||
Index = index; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the container control.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This will be null if <see cref="Item"/> is null.
|
|||
/// </remarks>
|
|||
public IControl ContainerControl { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the item that the container represents.
|
|||
/// </summary>
|
|||
public object Item { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the index of the item in the <see cref="ItemsControl.Items"/> collection.
|
|||
/// </summary>
|
|||
public int Index { get; } |
|||
} |
|||
} |
|||
@ -1,34 +1,38 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Perspex.Controls.Generators |
|||
{ |
|||
/// <summary>
|
|||
/// Holds details about a set of item containers in an <see cref="IItemContainerGenerator"/>.
|
|||
/// Provides details for the <see cref="IItemContainerGenerator.Materialized"/>
|
|||
/// and <see cref="IItemContainerGenerator.Dematerialized"/> events.
|
|||
/// </summary>
|
|||
public class ItemContainers |
|||
public class ItemContainerEventArgs : EventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemContainers"/> class.
|
|||
/// Initializes a new instance of the <see cref="ItemContainerEventArgs"/> class.
|
|||
/// </summary>
|
|||
/// <param name="startingIndex">The index of the first container in the source items.</param>
|
|||
/// <param name="containers">The containers.</param>
|
|||
public ItemContainers(int startingIndex, IList<IControl> containers) |
|||
public ItemContainerEventArgs( |
|||
int startingIndex, |
|||
IList<ItemContainer> containers) |
|||
{ |
|||
StartingIndex = startingIndex; |
|||
Items = containers; |
|||
Containers = containers; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the index of the first container in the source items.
|
|||
/// Gets the containers.
|
|||
/// </summary>
|
|||
public int StartingIndex { get; } |
|||
public IList<ItemContainer> Containers { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the containers. May contain null entries.
|
|||
/// Gets the index of the first container in the source items.
|
|||
/// </summary>
|
|||
public IList<IControl> Items { get; } |
|||
public int StartingIndex { get; } |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Collections; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
/// <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); |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Collections; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
/// <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); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Perspex.Styling; |
|||
|
|||
namespace Perspex.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the event arguments for the <see cref="Control.AttachedToLogicalTree"/> and
|
|||
/// <see cref="Control.DetachedFromLogicalTree"/> events.
|
|||
/// </summary>
|
|||
public class LogicalTreeAttachmentEventArgs : EventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LogicalTreeAttachmentEventArgs"/> class.
|
|||
/// </summary>
|
|||
/// <param name="root">The root of the logical tree.</param>
|
|||
public LogicalTreeAttachmentEventArgs(IStyleHost root) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(root != null); |
|||
|
|||
Root = root; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the root of the logical tree that the control is being attached to or detached from.
|
|||
/// </summary>
|
|||
public IStyleHost Root { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using Perspex.Collections; |
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Interactivity; |
|||
|
|||
namespace Perspex.Controls.Mixins |
|||
{ |
|||
/// <summary>
|
|||
/// Adds content control functionality to control classes.
|
|||
/// </summary>
|
|||
/// <para>
|
|||
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
|
|||
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
|
|||
/// updates keeps the control's logical children in sync with the content being displayed by
|
|||
/// the control.
|
|||
/// </para>
|
|||
public class ContentControlMixin |
|||
{ |
|||
private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions = |
|||
new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() => |
|||
new ConditionalWeakTable<TemplatedControl, IDisposable>()); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SelectableMixin"/> class.
|
|||
/// </summary>
|
|||
/// <typeparam name="TControl">The control type.</typeparam>
|
|||
/// <param name="content">The content property.</param>
|
|||
/// <param name="logicalChildrenSelector">
|
|||
/// Given an control of <typeparamref name="TControl"/> should return the control's
|
|||
/// logical children collection.
|
|||
/// </param>
|
|||
/// <param name="presenterName">
|
|||
/// The name of the content presenter in the control's template.
|
|||
/// </param>
|
|||
public static void Attach<TControl>( |
|||
PerspexProperty content, |
|||
Func<TControl, IPerspexList<ILogical>> logicalChildrenSelector, |
|||
string presenterName = "PART_ContentPresenter") |
|||
where TControl : TemplatedControl |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(content != null); |
|||
Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null); |
|||
|
|||
EventHandler<RoutedEventArgs> templateApplied = (s, ev) => |
|||
{ |
|||
var sender = s as TControl; |
|||
|
|||
if (sender != null) |
|||
{ |
|||
var e = (TemplateAppliedEventArgs)ev; |
|||
var presenter = (IControl)e.NameScope.Find(presenterName); |
|||
|
|||
if (presenter != null) |
|||
{ |
|||
var logicalChildren = logicalChildrenSelector(sender); |
|||
var subscription = presenter |
|||
.GetObservable(ContentPresenter.ChildProperty) |
|||
.Subscribe(child => UpdateLogicalChild( |
|||
logicalChildren, |
|||
logicalChildren.FirstOrDefault(), |
|||
child)); |
|||
subscriptions.Value.Add(sender, subscription); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
TemplatedControl.TemplateAppliedEvent.AddClassHandler( |
|||
typeof(TControl), |
|||
templateApplied, |
|||
RoutingStrategies.Direct); |
|||
|
|||
content.Changed.Subscribe(e => |
|||
{ |
|||
var sender = e.Sender as TControl; |
|||
|
|||
if (sender != null) |
|||
{ |
|||
var logicalChildren = logicalChildrenSelector(sender); |
|||
UpdateLogicalChild(logicalChildren, e.OldValue, e.NewValue); |
|||
} |
|||
}); |
|||
|
|||
TemplatedControl.TemplateProperty.Changed.Subscribe(e => |
|||
{ |
|||
var sender = e.Sender as TControl; |
|||
|
|||
if (sender != null) |
|||
{ |
|||
IDisposable subscription; |
|||
|
|||
if (subscriptions.Value.TryGetValue(sender, out subscription)) |
|||
{ |
|||
subscription.Dispose(); |
|||
subscriptions.Value.Remove(sender); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static event EventHandler<TemplateAppliedEventArgs> TemplateApplied; |
|||
|
|||
private static void OnTemplateApplied(object sender, RoutedEventArgs e) |
|||
{ |
|||
TemplateApplied?.Invoke(sender, (TemplateAppliedEventArgs)e); |
|||
} |
|||
|
|||
private static void UpdateLogicalChild( |
|||
IPerspexList<ILogical> logicalChildren, |
|||
object oldValue, |
|||
object newValue) |
|||
{ |
|||
if (oldValue != newValue) |
|||
{ |
|||
var logical = oldValue as ILogical; |
|||
|
|||
if (logical != null) |
|||
{ |
|||
logicalChildren.Remove(logical); |
|||
} |
|||
|
|||
logical = newValue as ILogical; |
|||
|
|||
if (logical != null) |
|||
{ |
|||
logicalChildren.Add(logical); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,17 +1,58 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Perspex.Controls.Mixins; |
|||
using Perspex.Controls.Presenters; |
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an <see cref="ItemsControl"/> with a related header.
|
|||
/// </summary>
|
|||
public class HeaderedItemsControl : ItemsControl |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<object> HeaderProperty = |
|||
HeaderedContentControl.HeaderProperty.AddOwner<HeaderedItemsControl>(); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ContentControl"/> class.
|
|||
/// </summary>
|
|||
static HeaderedItemsControl() |
|||
{ |
|||
ContentControlMixin.Attach<HeaderedItemsControl>( |
|||
HeaderProperty, |
|||
x => x.LogicalChildren, |
|||
"PART_HeaderPresenter"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content of the control's header.
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get { return GetValue(HeaderProperty); } |
|||
set { SetValue(HeaderProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the header presenter from the control's template.
|
|||
/// </summary>
|
|||
public ContentPresenter HeaderPresenter |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnTemplateApplied(e); |
|||
HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Perspex.Controls.Mixins; |
|||
using Perspex.Controls.Presenters; |
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="SelectingItemsControl"/> with a related header.
|
|||
/// </summary>
|
|||
public class HeaderedSelectingItemsControl : SelectingItemsControl |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<object> HeaderProperty = |
|||
HeaderedContentControl.HeaderProperty.AddOwner<HeaderedSelectingItemsControl>(); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ContentControl"/> class.
|
|||
/// </summary>
|
|||
static HeaderedSelectingItemsControl() |
|||
{ |
|||
ContentControlMixin.Attach<HeaderedSelectingItemsControl>( |
|||
HeaderProperty, |
|||
x => x.LogicalChildren, |
|||
"PART_HeaderPresenter"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content of the control's header.
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get { return GetValue(HeaderProperty); } |
|||
set { SetValue(HeaderProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the header presenter from the control's template.
|
|||
/// </summary>
|
|||
public ContentPresenter HeaderPresenter |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnTemplateApplied(e); |
|||
HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a tab in a <see cref="TabStrip"/>.
|
|||
/// </summary>
|
|||
public class TabStripItem : ListBoxItem |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Interactivity; |
|||
|
|||
namespace Perspex.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the details of the <see cref="TemplatedControl.TemplateApplied"/> event.
|
|||
/// </summary>
|
|||
public class TemplateAppliedEventArgs : RoutedEventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TemplateAppliedEventArgs"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nameScope">The applied template's name scope.</param>
|
|||
public TemplateAppliedEventArgs(INameScope nameScope) |
|||
: base(TemplatedControl.TemplateAppliedEvent) |
|||
{ |
|||
NameScope = nameScope; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the name scope of the applied template.
|
|||
/// </summary>
|
|||
public INameScope NameScope { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Perspex.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// Denotes the root <see cref="IStyleHost"/> in a tree.
|
|||
/// </summary>
|
|||
public interface IStyleRoot : IStyleHost |
|||
{ |
|||
} |
|||
} |
|||
@ -1,14 +1,16 @@ |
|||
<Styles xmlns="https://github.com/perspex"> |
|||
<Style Selector="TabItem"> |
|||
<Style Selector="TabStripItem"> |
|||
<Setter Property="FontSize" Value="16"/> |
|||
<Setter Property="Foreground" Value="Gray"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="PART_HeaderPresenter" Content="{TemplateBinding Header}"/> |
|||
<ContentPresenter Name="PART_ContentPresenter" |
|||
Content="{TemplateBinding Content}" |
|||
Margin="{TemplateBinding Padding}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="TabItem:selected"> |
|||
<Style Selector="TabStripItem:selected"> |
|||
<Setter Property="Foreground" Value="Black"/> |
|||
</Style> |
|||
</Styles> |
|||
@ -1,89 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Specialized; |
|||
using System.Linq; |
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.LogicalTree; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class ContentPresenterTests |
|||
{ |
|||
[Fact] |
|||
public void Setting_Content_Should_Make_Control_Appear_In_LogicalChildren() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Control(); |
|||
|
|||
target.Content = child; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Equal(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Clearing_Content_Should_Remove_From_LogicalChildren() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Control(); |
|||
|
|||
target.Content = child; |
|||
target.ApplyTemplate(); |
|||
target.Content = null; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Clearing_Content_Clear_Childs_Parent() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Control(); |
|||
|
|||
target.Content = child; |
|||
target.ApplyTemplate(); |
|||
target.Content = null; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Null(child.Parent); |
|||
Assert.Null(child.GetLogicalParent()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Control(); |
|||
var called = false; |
|||
|
|||
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => |
|||
called = e.Action == NotifyCollectionChangedAction.Add; |
|||
|
|||
target.Content = child; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.True(called); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Control(); |
|||
var called = false; |
|||
|
|||
target.Content = child; |
|||
target.ApplyTemplate(); |
|||
|
|||
((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; |
|||
|
|||
target.Content = null; |
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.True(called); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
internal static class EnumerableExtensions |
|||
{ |
|||
public static IEnumerable<T> Do<T>(this IEnumerable<T> items, Action<T> action) |
|||
{ |
|||
foreach (var i in items) |
|||
{ |
|||
action(i); |
|||
yield return i; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.Controls.Primitives; |
|||
using Perspex.Controls.Templates; |
|||
using Perspex.LogicalTree; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests |
|||
{ |
|||
public class HeaderedItemsControlTests |
|||
{ |
|||
[Fact] |
|||
public void Control_Header_Should_Be_Logical_Child_Before_ApplyTemplate() |
|||
{ |
|||
var target = new HeaderedItemsControl |
|||
{ |
|||
Template = GetTemplate(), |
|||
}; |
|||
|
|||
var child = new Control(); |
|||
target.Header = child; |
|||
|
|||
Assert.Equal(child.Parent, target); |
|||
Assert.Equal(child.GetLogicalParent(), target); |
|||
Assert.Equal(new[] { child }, target.GetLogicalChildren()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate() |
|||
{ |
|||
var target = new HeaderedItemsControl |
|||
{ |
|||
Template = GetTemplate(), |
|||
}; |
|||
|
|||
target.Header = "Foo"; |
|||
target.ApplyTemplate(); |
|||
|
|||
var child = target.HeaderPresenter.Child; |
|||
|
|||
Assert.NotNull(child); |
|||
Assert.Equal(target, child.Parent); |
|||
Assert.Equal(target, child.GetLogicalParent()); |
|||
Assert.Equal(new[] { child }, target.GetLogicalChildren()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Clearing_Content_Should_Clear_Logical_Child() |
|||
{ |
|||
var target = new HeaderedItemsControl(); |
|||
var child = new Control(); |
|||
|
|||
target.Header = child; |
|||
target.Header = null; |
|||
|
|||
Assert.Null(child.Parent); |
|||
Assert.Null(child.GetLogicalParent()); |
|||
Assert.Empty(target.GetLogicalChildren()); |
|||
} |
|||
|
|||
private FuncControlTemplate GetTemplate() |
|||
{ |
|||
return new FuncControlTemplate<HeaderedItemsControl>(parent => |
|||
{ |
|||
return new Border |
|||
{ |
|||
Child = new ContentPresenter |
|||
{ |
|||
Name = "PART_HeaderPresenter", |
|||
[~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty], |
|||
} |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Controls.Presenters; |
|||
using Perspex.Controls.Templates; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Controls.UnitTests.Presenters |
|||
{ |
|||
public class ContentPresenterTests |
|||
{ |
|||
[Fact] |
|||
public void Setting_Content_To_Control_Should_Set_Child() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
var child = new Border(); |
|||
|
|||
target.Content = child; |
|||
|
|||
// Child should not update until ApplyTemplate called.
|
|||
Assert.Null(target.Child); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.Equal(child, target.Child); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Content_To_String_Should_Create_TextBlock() |
|||
{ |
|||
var target = new ContentPresenter(); |
|||
|
|||
target.Content = "Foo"; |
|||
|
|||
// Child should not update until ApplyTemplate called.
|
|||
Assert.Null(target.Child); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.IsType<TextBlock>(target.Child); |
|||
Assert.Equal("Foo", ((TextBlock)target.Child).Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_To_Logical_Tree_Should_Reevaluate_DataTemplates() |
|||
{ |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = "Foo", |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
Assert.IsType<TextBlock>(target.Child); |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
DataTemplates = new DataTemplates |
|||
{ |
|||
new FuncDataTemplate<string>(x => new Decorator()), |
|||
}, |
|||
}; |
|||
|
|||
root.Child = target; |
|||
target.ApplyTemplate(); |
|||
Assert.IsType<Decorator>(target.Child); |
|||
} |
|||
} |
|||
} |
|||
@ -1,124 +0,0 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using JetBrains.dotMemoryUnit; |
|||
using Perspex.Controls; |
|||
using Perspex.Styling; |
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace Perspex.LeakTests |
|||
{ |
|||
[DotMemoryUnit(FailIfRunWithoutSupport = false)] |
|||
public class StyleTests |
|||
{ |
|||
public StyleTests(ITestOutputHelper atr) |
|||
{ |
|||
TestApp.Initialize(); |
|||
DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StyleActivator_Should_Be_Released() |
|||
{ |
|||
Func<Window> run = () => |
|||
{ |
|||
var window = new Window |
|||
{ |
|||
Styles = new Styles |
|||
{ |
|||
new Style(x => x.OfType<Canvas>().Class("foo")) |
|||
{ |
|||
Setters = new[] |
|||
{ |
|||
new Setter(Canvas.WidthProperty, 100), |
|||
} |
|||
} |
|||
}, |
|||
Content = new Canvas |
|||
{ |
|||
Classes = new Classes("foo"), |
|||
} |
|||
}; |
|||
|
|||
// Do a layout and make sure that styled Canvas gets added to visual tree.
|
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
Assert.IsType<Canvas>(window.Presenter.Child); |
|||
Assert.Equal(100, (window.Presenter.Child).Width); |
|||
|
|||
// Clear the content and ensure the Canvas is removed.
|
|||
window.Content = null; |
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
Assert.Null(window.Presenter.Child); |
|||
|
|||
return window; |
|||
}; |
|||
|
|||
var result = run(); |
|||
|
|||
dotMemory.Check(memory => |
|||
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<StyleActivator>()).ObjectsCount)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_Carousel_SelectedIndex_Should_Not_Leak_StyleActivators() |
|||
{ |
|||
Func<Window> run = () => |
|||
{ |
|||
Carousel target; |
|||
|
|||
var window = new Window |
|||
{ |
|||
Styles = new Styles |
|||
{ |
|||
new Style(x => x.OfType<ContentControl>().Class("foo")) |
|||
{ |
|||
Setters = new[] |
|||
{ |
|||
new Setter(Visual.OpacityProperty, 0.5), |
|||
} |
|||
} |
|||
}, |
|||
Content = target = new Carousel |
|||
{ |
|||
Items = new[] |
|||
{ |
|||
new ContentControl |
|||
{ |
|||
Name = "item1", |
|||
Classes = new Classes("foo"), |
|||
Content = "item1", |
|||
}, |
|||
new ContentControl |
|||
{ |
|||
Name = "item2", |
|||
Classes = new Classes("foo"), |
|||
Content = "item2", |
|||
}, |
|||
} |
|||
} |
|||
}; |
|||
|
|||
// Do a layout and make sure that Carousel gets added to visual tree.
|
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
Assert.IsType<Carousel>(window.Presenter.Child); |
|||
|
|||
target.SelectedIndex = 1; |
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
target.SelectedIndex = 0; |
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
target.SelectedIndex = 1; |
|||
window.LayoutManager.ExecuteLayoutPass(); |
|||
|
|||
return window; |
|||
}; |
|||
|
|||
var result = run(); |
|||
|
|||
dotMemory.Check(memory => |
|||
Assert.Equal(1, memory.GetObjects(where => where.Type.Is<StyleActivator>()).ObjectsCount)); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue