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.
|
// 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.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
|
|
||||
namespace Perspex.Controls.Generators |
namespace Perspex.Controls.Generators |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
public class ItemContainers |
public class ItemContainerEventArgs : EventArgs |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemContainers"/> class.
|
/// Initializes a new instance of the <see cref="ItemContainerEventArgs"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="startingIndex">The index of the first container in the source items.</param>
|
/// <param name="startingIndex">The index of the first container in the source items.</param>
|
||||
/// <param name="containers">The containers.</param>
|
/// <param name="containers">The containers.</param>
|
||||
public ItemContainers(int startingIndex, IList<IControl> containers) |
public ItemContainerEventArgs( |
||||
|
int startingIndex, |
||||
|
IList<ItemContainer> containers) |
||||
{ |
{ |
||||
StartingIndex = startingIndex; |
StartingIndex = startingIndex; |
||||
Items = containers; |
Containers = containers; |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the index of the first container in the source items.
|
/// Gets the containers.
|
||||
/// </summary>
|
/// </summary>
|
||||
public int StartingIndex { get; } |
public IList<ItemContainer> Containers { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the containers. May contain null entries.
|
/// Gets the index of the first container in the source items.
|
||||
/// </summary>
|
/// </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.
|
// 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.
|
// 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 |
namespace Perspex.Controls.Primitives |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an <see cref="ItemsControl"/> with a related header.
|
||||
|
/// </summary>
|
||||
public class HeaderedItemsControl : ItemsControl |
public class HeaderedItemsControl : ItemsControl |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Header"/> property.
|
||||
|
/// </summary>
|
||||
public static readonly PerspexProperty<object> HeaderProperty = |
public static readonly PerspexProperty<object> HeaderProperty = |
||||
HeaderedContentControl.HeaderProperty.AddOwner<HeaderedItemsControl>(); |
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 |
public object Header |
||||
{ |
{ |
||||
get { return GetValue(HeaderProperty); } |
get { return GetValue(HeaderProperty); } |
||||
set { SetValue(HeaderProperty, value); } |
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"> |
<Styles xmlns="https://github.com/perspex"> |
||||
<Style Selector="TabItem"> |
<Style Selector="TabStripItem"> |
||||
<Setter Property="FontSize" Value="16"/> |
<Setter Property="FontSize" Value="16"/> |
||||
<Setter Property="Foreground" Value="Gray"/> |
<Setter Property="Foreground" Value="Gray"/> |
||||
<Setter Property="Template"> |
<Setter Property="Template"> |
||||
<ControlTemplate> |
<ControlTemplate> |
||||
<ContentPresenter Name="PART_HeaderPresenter" Content="{TemplateBinding Header}"/> |
<ContentPresenter Name="PART_ContentPresenter" |
||||
|
Content="{TemplateBinding Content}" |
||||
|
Margin="{TemplateBinding Padding}"/> |
||||
</ControlTemplate> |
</ControlTemplate> |
||||
</Setter> |
</Setter> |
||||
</Style> |
</Style> |
||||
<Style Selector="TabItem:selected"> |
<Style Selector="TabStripItem:selected"> |
||||
<Setter Property="Foreground" Value="Black"/> |
<Setter Property="Foreground" Value="Black"/> |
||||
</Style> |
</Style> |
||||
</Styles> |
</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