From a6b74db847506fd4f4d04aebe43d968b111a4acc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 16 Dec 2015 22:40:24 +0000 Subject: [PATCH] Merge branch 'logical-tree' into leaks --- samples/TestApplicationShared/GalleryStyle.cs | 21 +- samples/TestApplicationShared/MainWindow.cs | 1 - .../Perspex.Markup.Xaml/Data/Binding.cs | 2 - src/Perspex.Application/Application.cs | 7 +- src/Perspex.Base/Collections/PerspexList.cs | 5 +- src/Perspex.Controls/ContentControl.cs | 42 +--- src/Perspex.Controls/Control.cs | 223 +++++++++++++++--- src/Perspex.Controls/Decorator.cs | 4 +- src/Perspex.Controls/DropDown.cs | 4 +- .../Generators/IItemContainerGenerator.cs | 19 +- .../Generators/ItemContainer.cs | 45 ++++ ...ontainers.cs => ItemContainerEventArgs.cs} | 22 +- .../Generators/ItemContainerGenerator.cs | 77 +++--- .../Generators/ItemContainerGenerator`1.cs | 5 +- .../Generators/TreeItemContainerGenerator.cs | 10 +- src/Perspex.Controls/IReparentingControl.cs | 25 -- src/Perspex.Controls/IReparentingHost.cs | 28 --- src/Perspex.Controls/ItemsControl.cs | 193 +++++++++++++-- .../LogicalTreeAttachmentEventArgs.cs | 31 +++ src/Perspex.Controls/MenuItem.cs | 28 +-- .../Mixins/ContentControlMixin.cs | 136 +++++++++++ src/Perspex.Controls/Panel.cs | 79 ++----- src/Perspex.Controls/Perspex.Controls.csproj | 10 +- .../Presenters/CarouselPresenter.cs | 18 +- .../Presenters/ContentPresenter.cs | 30 +-- .../Presenters/ItemsPresenter.cs | 58 +++-- .../Primitives/AdornerDecorator.cs | 2 +- .../Primitives/HeaderedItemsControl.cs | 41 ++++ .../Primitives/HeaderedSelectingControl.cs | 58 +++++ src/Perspex.Controls/Primitives/PopupRoot.cs | 5 +- .../Primitives/SelectingItemsControl.cs | 113 +++------ src/Perspex.Controls/Primitives/TabStrip.cs | 40 +--- .../Primitives/TabStripItem.cs | 12 + .../Primitives/TemplateAppliedEventArgs.cs | 28 +++ .../Primitives/TemplatedControl.cs | 46 +++- src/Perspex.Controls/Primitives/Track.cs | 4 +- src/Perspex.Controls/ProgressBar.cs | 4 +- src/Perspex.Controls/TabControl.cs | 93 +++++--- .../Templates/TemplateExtensions.cs | 29 --- src/Perspex.Controls/TextBlock.cs | 6 +- src/Perspex.Controls/TextBox.cs | 4 +- src/Perspex.Controls/TopLevel.cs | 7 +- src/Perspex.SceneGraph/Visual.cs | 130 +++------- .../VisualTreeAttachmentEventArgs.cs | 2 + src/Perspex.Styling/ILogical.cs | 5 + src/Perspex.Styling/Perspex.Styling.csproj | 1 + src/Perspex.Styling/Styling/IGlobalStyles.cs | 6 +- src/Perspex.Styling/Styling/IStyleHost.cs | 2 +- src/Perspex.Styling/Styling/IStyleRoot.cs | 12 + src/Perspex.Styling/Styling/IStyleable.cs | 6 + src/Perspex.Styling/Styling/Selector.cs | 2 +- src/Perspex.Styling/Styling/Selectors.cs | 6 +- src/Perspex.Styling/Styling/Style.cs | 16 +- src/Perspex.Styling/Styling/StyleActivator.cs | 32 ++- src/Perspex.Styling/Styling/Styler.cs | 28 +-- src/Perspex.Themes.Default/Button.paml | 3 +- src/Perspex.Themes.Default/DefaultTheme.paml | 2 +- src/Perspex.Themes.Default/MenuItem.paml | 6 +- .../Perspex.Themes.Default.csproj | 2 +- src/Perspex.Themes.Default/TabControl.paml | 36 ++- src/Perspex.Themes.Default/TabStrip.paml | 6 +- .../{TabItem.paml => TabStripItem.paml} | 8 +- src/Perspex.Themes.Default/ToggleButton.paml | 3 +- src/Perspex.Themes.Default/TreeViewItem.paml | 2 +- .../ContentControlTests.cs | 73 +----- .../ContentPresenterTests.cs | 89 ------- .../ControlTests.cs | 110 ++++++++- .../ControlTests_NameScope.cs | 3 +- .../EnumerableExtensions.cs | 20 ++ .../Generators/ItemContainerGeneratorTests.cs | 26 +- .../ItemContainerGeneratorTypedTests.cs | 4 +- .../HeaderedItemsControlTests .cs | 78 ++++++ .../ItemsControlTests.cs | 58 +++-- .../ListBoxTests.cs | 35 ++- .../Perspex.Controls.UnitTests/PanelTests.cs | 17 -- .../Perspex.Controls.UnitTests.csproj | 4 +- .../Presenters/ContentPresenterTests.cs | 68 ++++++ .../Presenters/ItemsPresenterTests.cs | 3 +- .../Primitives/PopupTests.cs | 16 -- .../Primitives/TabStripTests.cs | 159 ++++++++----- .../Primitives/TemplatedControlTests.cs | 99 ++++++-- .../TabControlTests.cs | 174 +++++++++----- tests/Perspex.Controls.UnitTests/TestRoot.cs | 3 +- .../TestTemplatedControl.cs | 7 +- .../TreeViewTests.cs | 2 +- .../InteractiveTests.cs | 2 +- .../Perspex.LeakTests.csproj | 1 - tests/Perspex.LeakTests/StyleTests.cs | 124 ---------- tests/Perspex.Markup.UnitTests/TestRoot.cs | 3 +- .../PerspexPropertyConverterTest.cs | 3 + .../Perspex.Markup.Xaml.UnitTests/TestRoot.cs | 3 +- .../TestVisual.cs | 14 +- .../VisualTests.cs | 23 -- .../SelectorTests_Child.cs | 5 + .../SelectorTests_Descendent.cs | 5 + .../StyleActivatorTests.cs | 30 ++- tests/Perspex.Styling.UnitTests/StyleTests.cs | 2 +- .../TestControlBase.cs | 3 + tests/Perspex.Styling.UnitTests/TestRoot.cs | 2 +- .../TestTemplatedControl.cs | 3 + 100 files changed, 1925 insertions(+), 1277 deletions(-) create mode 100644 src/Perspex.Controls/Generators/ItemContainer.cs rename src/Perspex.Controls/Generators/{ItemContainers.cs => ItemContainerEventArgs.cs} (62%) delete mode 100644 src/Perspex.Controls/IReparentingControl.cs delete mode 100644 src/Perspex.Controls/IReparentingHost.cs create mode 100644 src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs create mode 100644 src/Perspex.Controls/Mixins/ContentControlMixin.cs create mode 100644 src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs create mode 100644 src/Perspex.Controls/Primitives/TabStripItem.cs create mode 100644 src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs create mode 100644 src/Perspex.Styling/Styling/IStyleRoot.cs rename src/Perspex.Themes.Default/{TabItem.paml => TabStripItem.paml} (53%) delete mode 100644 tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs create mode 100644 tests/Perspex.Controls.UnitTests/EnumerableExtensions.cs create mode 100644 tests/Perspex.Controls.UnitTests/HeaderedItemsControlTests .cs create mode 100644 tests/Perspex.Controls.UnitTests/Presenters/ContentPresenterTests.cs delete mode 100644 tests/Perspex.LeakTests/StyleTests.cs diff --git a/samples/TestApplicationShared/GalleryStyle.cs b/samples/TestApplicationShared/GalleryStyle.cs index a0e32b7740..ecac60c33a 100644 --- a/samples/TestApplicationShared/GalleryStyle.cs +++ b/samples/TestApplicationShared/GalleryStyle.cs @@ -27,15 +27,15 @@ namespace TestApplication } }, - new Style(s => s.Class(":container").OfType().Child().Child().Child().Child().Child().OfType()) + new Style(s => s.Class(":container").OfType().Child().Child().Child().Child().Child().OfType()) { Setters = new[] { - new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate (TabItemTemplate)), + new Setter (TemplatedControl.TemplateProperty, new FuncControlTemplate(TabStripItemTemplate)), } }, - new Style(s => s.Name("internalStrip").OfType().Child().OfType()) + new Style(s => s.Name("PART_TabStrip").OfType().Child().OfType()) { Setters = new[] { @@ -44,7 +44,7 @@ namespace TestApplication } }, - new Style(s => s.Name("internalStrip").OfType().Child().OfType().Class(":selected")) + new Style(s => s.Name("PART_TabStrip").OfType().Child().OfType().Class(":selected")) { Setters = new[] { @@ -55,7 +55,7 @@ namespace TestApplication }); } - public static Control TabItemTemplate(TabItem control) + public static Control TabStripItemTemplate(TabStripItem control) { return new ContentPresenter { @@ -72,8 +72,8 @@ namespace TestApplication } }) }, - Name = "headerPresenter", - [~ContentPresenter.ContentProperty] = control[~HeaderedContentControl.HeaderProperty], + Name = "PART_ContentPresenter", + [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], }; } @@ -96,9 +96,10 @@ namespace TestApplication { Content = new TabStrip { + Name = "PART_TabStrip", ItemsPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical, Gap = 4 }), Margin = new Thickness(0, 10, 0, 0), - Name = "internalStrip", + MemberSelector = TabControl.HeaderSelector, [!ItemsControl.ItemsProperty] = control[!ItemsControl.ItemsProperty], [!!SelectingItemsControl.SelectedItemProperty] = control[!!SelectingItemsControl.SelectedItemProperty], } @@ -106,8 +107,8 @@ namespace TestApplication }, new Carousel { - Name = "carousel", - MemberSelector = control.ContentSelector, + Name = "PART_Content", + MemberSelector = TabControl.ContentSelector, [~Carousel.TransitionProperty] = control[~TabControl.TransitionProperty], [!Carousel.ItemsProperty] = control[!ItemsControl.ItemsProperty], [!Carousel.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty], diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs index 2a0c736984..7e05c01a35 100644 --- a/samples/TestApplicationShared/MainWindow.cs +++ b/samples/TestApplicationShared/MainWindow.cs @@ -105,7 +105,6 @@ namespace TestApplication }; container.Classes.Add(":container"); - window.Show(); return window; diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index 20cab81984..00ed242855 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -246,8 +246,6 @@ namespace Perspex.Markup.Xaml.Data var update = target.GetObservable(Control.TemplatedParentProperty) .Skip(1) - .Where(x => x != null) - .Take(1) .Select(_ => Unit.Default); var result = new ExpressionObserver( diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 52d2574c70..177c337d04 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -32,7 +32,7 @@ namespace Perspex /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles + public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot { static Action _platformInitializationCallback; @@ -135,6 +135,11 @@ namespace Perspex protected set; } + /// + /// Gets the styling parent of the application, which is null. + /// + IStyleHost IStyleHost.StylingParent => null; + /// /// Runs the application's main loop until the is closed. /// diff --git a/src/Perspex.Base/Collections/PerspexList.cs b/src/Perspex.Base/Collections/PerspexList.cs index e2d829afe5..120f62d82f 100644 --- a/src/Perspex.Base/Collections/PerspexList.cs +++ b/src/Perspex.Base/Collections/PerspexList.cs @@ -52,7 +52,7 @@ namespace Perspex.Collections /// /// /// - public class PerspexList : IPerspexList, IList, INotifyCollectionChanged, INotifyPropertyChanged + public class PerspexList : IPerspexList, IList { private List _inner; @@ -150,7 +150,8 @@ namespace Perspex.Collections var e = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, value, - old); + old, + index); CollectionChanged(this, e); } } diff --git a/src/Perspex.Controls/ContentControl.cs b/src/Perspex.Controls/ContentControl.cs index c4c1c94e31..2899b58bcd 100644 --- a/src/Perspex.Controls/ContentControl.cs +++ b/src/Perspex.Controls/ContentControl.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Perspex.Collections; +using System.Linq; +using System.Reactive.Linq; +using Perspex.Controls.Mixins; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; @@ -14,7 +16,7 @@ namespace Perspex.Controls /// /// Displays according to a . /// - public class ContentControl : TemplatedControl, IContentControl, IReparentingHost + public class ContentControl : TemplatedControl, IContentControl { /// /// Defines the property. @@ -34,18 +36,14 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register(nameof(VerticalContentAlignment)); - /// - /// Initializes static members of the class. - /// - static ContentControl() - { - } + private IDisposable _presenterSubscription; /// - /// Initializes a new instance of the class. + /// Initializes static members of the class. /// - public ContentControl() + static ContentControl() { + ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren); } /// @@ -85,31 +83,15 @@ namespace Perspex.Controls set { SetValue(VerticalContentAlignmentProperty, value); } } - /// - /// Gets a writeable logical children collection from the host. - /// - IPerspexList IReparentingHost.LogicalChildren => LogicalChildren; - - /// - /// Asks the control whether it wants to reparent the logical children of the specified - /// control. - /// - /// The control. - /// - /// True if the control wants to reparent its logical children otherwise false. - /// - bool IReparentingHost.WillReparentChildrenOf(IControl control) - { - return control is IContentPresenter && control.TemplatedParent == this; - } - /// - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { + base.OnTemplateApplied(e); + // 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. - Presenter = nameScope.Find("PART_ContentPresenter"); + Presenter = e.NameScope.Find("PART_ContentPresenter"); } } } diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index e76be17149..ea20401142 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -2,12 +2,18 @@ // 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.Specialized; +using System.Linq; +using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Subjects; using Perspex.Collections; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.Input; using Perspex.Interactivity; +using Perspex.LogicalTree; using Perspex.Rendering; using Perspex.Styling; @@ -70,9 +76,11 @@ namespace Perspex.Controls private readonly Classes _classes = new Classes(); private DataTemplates _dataTemplates; private IControl _focusAdorner; + private bool _isAttachedToLogicalTree; private IPerspexList _logicalChildren; private INameScope _nameScope; private Styles _styles; + private Subject _styleDetach = new Subject(); /// /// Initializes static members of the class. @@ -93,6 +101,16 @@ namespace Perspex.Controls _nameScope = this as INameScope; } + /// + /// Raised when the control is attached to a rooted logical tree. + /// + public event EventHandler AttachedToLogicalTree; + + /// + /// Raised when the control is detached from a rooted logical tree. + /// + public event EventHandler DetachedFromLogicalTree; + /// /// Occurs when the property changes. /// @@ -206,6 +224,11 @@ namespace Perspex.Controls internal set { SetValue(TemplatedParentProperty, value); } } + /// + /// Gets a value indicating whether the element is attached to a rooted logical tree. + /// + bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; + /// /// Gets the control's logical parent. /// @@ -230,9 +253,10 @@ namespace Perspex.Controls /// Type IStyleable.StyleKey => GetType(); - /// - /// Gets the parent style host element. - /// + /// + IObservable IStyleable.StyleDetach => _styleDetach; + + /// IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; /// @@ -255,12 +279,32 @@ namespace Perspex.Controls if (_logicalChildren == null) { var list = new PerspexList(); - list.ResetBehavior = ResetBehavior.Remove; - _logicalChildren = list; + LogicalChildren = list; } return _logicalChildren; } + + set + { + Contract.Requires(value != null); + + if (_logicalChildren != value) + { + if (_logicalChildren != null) + { + _logicalChildren.CollectionChanged -= LogicalChildrenCollectionChanged; + } + } + + if (value is PerspexList) + { + ((PerspexList)value).ResetBehavior = ResetBehavior.Remove; + } + + _logicalChildren = value; + _logicalChildren.CollectionChanged += LogicalChildrenCollectionChanged; + } } /// @@ -278,7 +322,26 @@ namespace Perspex.Controls throw new InvalidOperationException("The Control already has a parent."); } - SetAndRaise(ParentProperty, ref _parent, (IControl)parent); + InheritanceParent = parent as PerspexObject; + _parent = (IControl)parent; + + var root = FindStyleRoot(old); + + if (root != null) + { + var e = new LogicalTreeAttachmentEventArgs(root); + OnDetachedFromLogicalTree(e); + } + + root = FindStyleRoot(this); + + if (root != null) + { + var e = new LogicalTreeAttachmentEventArgs(root); + OnAttachedToLogicalTree(e); + } + + RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue); } } @@ -328,6 +391,62 @@ namespace Perspex.Controls }); } + /// + /// Called when the control is added to a logical tree. + /// + /// The event args. + /// + /// It is vital that if you override this method you call the base implementation; + /// failing to do so will cause numerous features to not work as expected. + /// + protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (_nameScope == null) + { + _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope; + } + + if (Name != null) + { + _nameScope?.Register(Name, this); + } + + _isAttachedToLogicalTree = true; + PerspexLocator.Current.GetService()?.ApplyStyles(this); + AttachedToLogicalTree?.Invoke(this, e); + + foreach (var child in LogicalChildren.OfType()) + { + child.OnAttachedToLogicalTree(e); + } + } + + /// + /// Called when the control is removed from a logical tree. + /// + /// The event args. + /// + /// It is vital that if you override this method you call the base implementation; + /// failing to do so will cause numerous features to not work as expected. + /// + protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + if (Name != null) + { + _nameScope?.Unregister(Name); + } + + _isAttachedToLogicalTree = false; + _styleDetach.OnNext(Unit.Default); + this.TemplatedParent = null; + DetachedFromLogicalTree?.Invoke(this, e); + + foreach (var child in LogicalChildren.OfType()) + { + child.OnDetachedFromLogicalTree(e); + } + } + /// protected override void OnGotFocus(GotFocusEventArgs e) { @@ -373,35 +492,6 @@ namespace Perspex.Controls } } - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - if (_nameScope == null) - { - _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope; - } - - if (Name != null) - { - _nameScope?.Register(Name, this); - } - - PerspexLocator.Current.GetService()?.ApplyStyles(this); - } - - /// - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - - if (Name != null) - { - _nameScope?.Unregister(Name); - } - } - /// /// Called when the is changed and all subscribers to that change /// have been notified. @@ -417,7 +507,7 @@ namespace Perspex.Controls /// The logical children to use. protected void RedirectLogicalChildren(IPerspexList collection) { - _logicalChildren = collection; + LogicalChildren = collection; } /// @@ -439,5 +529,66 @@ namespace Perspex.Controls } } } + + private static IStyleRoot FindStyleRoot(IStyleHost e) + { + while (e != null) + { + var root = e as IStyleRoot; + + if (root != null && root.StylingParent == null) + { + return root; + } + + e = e.StylingParent; + } + + return null; + } + + private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Remove: + ClearLogicalParent(e.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Replace: + ClearLogicalParent(e.OldItems.Cast()); + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection"); + } + } + + private void SetLogicalParent(IEnumerable children) + { + foreach (var i in children) + { + if (i.LogicalParent == null) + { + ((ISetLogicalParent)i).SetParent(this); + } + } + } + + private void ClearLogicalParent(IEnumerable children) + { + foreach (var i in children) + { + if (i.LogicalParent == this) + { + ((ISetLogicalParent)i).SetParent(null); + } + } + } } } diff --git a/src/Perspex.Controls/Decorator.cs b/src/Perspex.Controls/Decorator.cs index f5da5a4114..3440845c46 100644 --- a/src/Perspex.Controls/Decorator.cs +++ b/src/Perspex.Controls/Decorator.cs @@ -88,13 +88,13 @@ namespace Perspex.Controls { ((ISetLogicalParent)oldChild).SetParent(null); LogicalChildren.Clear(); - RemoveVisualChild(oldChild); + VisualChildren.Remove(oldChild); } if (newChild != null) { ((ISetLogicalParent)newChild).SetParent(this); - AddVisualChild(newChild); + VisualChildren.Add(newChild); LogicalChildren.Add(newChild); } } diff --git a/src/Perspex.Controls/DropDown.cs b/src/Perspex.Controls/DropDown.cs index 31a44ad7d0..7995d48105 100644 --- a/src/Perspex.Controls/DropDown.cs +++ b/src/Perspex.Controls/DropDown.cs @@ -125,14 +125,14 @@ namespace Perspex.Controls base.OnPointerPressed(e); } - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { if (_popup != null) { _popup.Opened -= PopupOpened; } - _popup = nameScope.Get("PART_Popup"); + _popup = e.NameScope.Get("PART_Popup"); _popup.Opened += PopupOpened; } diff --git a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs index 7189d7a0c9..85628e3374 100644 --- a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs @@ -16,12 +16,17 @@ namespace Perspex.Controls.Generators /// /// Gets the currently realized containers. /// - IEnumerable Containers { get; } + IEnumerable Containers { get; } /// - /// Signalled whenever new containers are initialized. + /// Signalled whenever new containers are materialized. /// - IObservable ContainersInitialized { get; } + event EventHandler Materialized; + + /// + /// Event raised whenever containers are dematerialized. + /// + event EventHandler Dematerialized; /// /// Creates container controls for a collection of items. @@ -32,7 +37,7 @@ namespace Perspex.Controls.Generators /// The items. /// An optional member selector. /// The created controls. - IEnumerable Materialize( + IEnumerable Materialize( int startingIndex, IEnumerable items, IMemberSelector selector); @@ -45,7 +50,7 @@ namespace Perspex.Controls.Generators /// /// The the number of items to remove. /// The removed containers. - IEnumerable Dematerialize(int startingIndex, int count); + IEnumerable Dematerialize(int startingIndex, int count); /// /// Removes a set of created containers and updates the index of later containers to fill @@ -56,13 +61,13 @@ namespace Perspex.Controls.Generators /// /// The the number of items to remove. /// The removed containers. - IEnumerable RemoveRange(int startingIndex, int count); + IEnumerable RemoveRange(int startingIndex, int count); /// /// Clears all created containers and returns the removed controls. /// /// The removed controls. - IEnumerable Clear(); + IEnumerable Clear(); /// /// Gets the container control representing the item with the specified index. diff --git a/src/Perspex.Controls/Generators/ItemContainer.cs b/src/Perspex.Controls/Generators/ItemContainer.cs new file mode 100644 index 0000000000..86f3795106 --- /dev/null +++ b/src/Perspex.Controls/Generators/ItemContainer.cs @@ -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 +{ + /// + /// Holds information about an item container generated by an + /// . + /// + public class ItemContainer + { + /// + /// Initializes a new instance of the class. + /// + /// The container control. + /// The item that the container represents. + /// + /// The index of the item in the collection. + /// + public ItemContainer(IControl container, object item, int index) + { + ContainerControl = container; + Item = item; + Index = index; + } + + /// + /// Gets the container control. + /// + /// + /// This will be null if is null. + /// + public IControl ContainerControl { get; } + + /// + /// Gets the item that the container represents. + /// + public object Item { get; } + + /// + /// Gets the index of the item in the collection. + /// + public int Index { get; } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Generators/ItemContainers.cs b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs similarity index 62% rename from src/Perspex.Controls/Generators/ItemContainers.cs rename to src/Perspex.Controls/Generators/ItemContainerEventArgs.cs index 9eec0f1147..86e2b890d1 100644 --- a/src/Perspex.Controls/Generators/ItemContainers.cs +++ b/src/Perspex.Controls/Generators/ItemContainerEventArgs.cs @@ -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 { /// - /// Holds details about a set of item containers in an . + /// Provides details for the + /// and events. /// - public class ItemContainers + public class ItemContainerEventArgs : EventArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The index of the first container in the source items. /// The containers. - public ItemContainers(int startingIndex, IList containers) + public ItemContainerEventArgs( + int startingIndex, + IList containers) { StartingIndex = startingIndex; - Items = containers; + Containers = containers; } /// - /// Gets the index of the first container in the source items. + /// Gets the containers. /// - public int StartingIndex { get; } + public IList Containers { get; } /// - /// Gets the containers. May contain null entries. + /// Gets the index of the first container in the source items. /// - public IList Items { get; } + public int StartingIndex { get; } } } diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs index b8b57ddd59..2733de5770 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs @@ -15,9 +15,7 @@ namespace Perspex.Controls.Generators /// public class ItemContainerGenerator : IItemContainerGenerator { - private List _containers = new List(); - - private readonly Subject _containersInitialized = new Subject(); + private List _containers = new List(); /// /// Initializes a new instance of the class. @@ -31,10 +29,13 @@ namespace Perspex.Controls.Generators } /// - public IEnumerable Containers => _containers; + public IEnumerable Containers => _containers; + + /// + public event EventHandler Materialized; /// - public IObservable ContainersInitialized => _containersInitialized; + public event EventHandler Dematerialized; /// /// Gets the owner control. @@ -42,7 +43,7 @@ namespace Perspex.Controls.Generators public IControl Owner { get; } /// - public IEnumerable Materialize( + public IEnumerable Materialize( int startingIndex, IEnumerable items, IMemberSelector selector) @@ -50,25 +51,25 @@ namespace Perspex.Controls.Generators Contract.Requires(items != null); int index = startingIndex; - var result = new List(); + var result = new List(); foreach (var item in items) { var i = selector != null ? selector.Select(item) : item; - var container = CreateContainer(i); + var container = new ItemContainer(CreateContainer(i), item, index++); result.Add(container); } - AddContainers(startingIndex, result); - _containersInitialized.OnNext(new ItemContainers(startingIndex, result)); + AddContainers(result); + Materialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); return result.Where(x => x != null).ToList(); } /// - public virtual IEnumerable Dematerialize(int startingIndex, int count) + public virtual IEnumerable Dematerialize(int startingIndex, int count) { - var result = new List(); + var result = new List(); for (int i = startingIndex; i < startingIndex + count; ++i) { @@ -79,22 +80,31 @@ namespace Perspex.Controls.Generators } } + Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); + return result; } /// - public virtual IEnumerable RemoveRange(int startingIndex, int count) + public virtual IEnumerable RemoveRange(int startingIndex, int count) { var result = _containers.GetRange(startingIndex, count); _containers.RemoveRange(startingIndex, count); + Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); return result; } /// - public virtual IEnumerable Clear() + public virtual IEnumerable Clear() { var result = _containers; - _containers = new List(); + _containers = new List(); + + if (result.Count > 0) + { + Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result)); + } + return result; } @@ -103,7 +113,7 @@ namespace Perspex.Controls.Generators { if (index < _containers.Count) { - return _containers[index]; + return _containers[index]?.ContainerControl; } return null; @@ -112,7 +122,19 @@ namespace Perspex.Controls.Generators /// public int IndexFromContainer(IControl container) { - return _containers.IndexOf(container); + var index = 0; + + foreach (var i in _containers) + { + if (i?.ContainerControl == container) + { + return index; + } + + ++index; + } + + return -1; } /// @@ -135,33 +157,30 @@ namespace Perspex.Controls.Generators /// /// Adds a collection of containers to the index. /// - /// The starting index. - /// The container. - protected void AddContainers(int index, IList container) + /// The containers. + protected void AddContainers(IList containers) { - Contract.Requires(container != null); + Contract.Requires(containers != null); - foreach (var c in container) + foreach (var c in containers) { - while (_containers.Count < index) + while (_containers.Count < c.Index) { _containers.Add(null); } - if (_containers.Count == index) + if (_containers.Count == c.Index) { _containers.Add(c); } - else if (_containers[index] == null) + else if (_containers[c.Index] == null) { - _containers[index] = c; + _containers[c.Index] = c; } else { throw new InvalidOperationException("Container already created."); } - - ++index; } } @@ -171,7 +190,7 @@ namespace Perspex.Controls.Generators /// The first index. /// The number of elements in the range. /// The containers. - protected IEnumerable GetContainerRange(int index, int count) + protected IEnumerable GetContainerRange(int index, int count) { return _containers.GetRange(index, count); } diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs index dcb365b120..1a18c2d154 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs @@ -24,6 +24,9 @@ namespace Perspex.Controls.Generators PerspexProperty contentProperty) : base(owner) { + Contract.Requires(owner != null); + Contract.Requires(contentProperty != null); + ContentProperty = contentProperty; } @@ -48,7 +51,7 @@ namespace Perspex.Controls.Generators else { var result = new T(); - result.SetValue(ContentProperty, Owner.MaterializeDataTemplate(item)); + result.SetValue(ContentProperty, item); if (!(item is IControl)) { diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 802cd3866b..4caa53e3c1 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -120,13 +120,13 @@ namespace Perspex.Controls.Generators } } - public override IEnumerable Clear() + public override IEnumerable Clear() { ClearIndex(); return base.Clear(); } - public override IEnumerable Dematerialize(int startingIndex, int count) + public override IEnumerable Dematerialize(int startingIndex, int count) { RemoveFromIndex(GetContainerRange(startingIndex, count)); return base.Dematerialize(startingIndex, count); @@ -145,7 +145,7 @@ namespace Perspex.Controls.Generators } } - private void RemoveFromIndex(IEnumerable containers) + private void RemoveFromIndex(IEnumerable containers) { if (RootGenerator != null) { @@ -155,8 +155,8 @@ namespace Perspex.Controls.Generators { foreach (var container in containers) { - var item = _containerToItem[container]; - _containerToItem.Remove(container); + var item = _containerToItem[container.ContainerControl]; + _containerToItem.Remove(container.ContainerControl); _itemToContainer.Remove(item); } } diff --git a/src/Perspex.Controls/IReparentingControl.cs b/src/Perspex.Controls/IReparentingControl.cs deleted file mode 100644 index a7d4b46e64..0000000000 --- a/src/Perspex.Controls/IReparentingControl.cs +++ /dev/null @@ -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 -{ - /// - /// A control that can make its visual children the logical children of another control. - /// - public interface IReparentingControl : IControl - { - /// - /// Requests that the visual children of the control use another control as their logical - /// parent. - /// - /// - /// The logical parent for the visual children of the control. - /// - /// - /// The collection to modify. - /// - void ReparentLogicalChildren(ILogical logicalParent, IPerspexList children); - } -} \ No newline at end of file diff --git a/src/Perspex.Controls/IReparentingHost.cs b/src/Perspex.Controls/IReparentingHost.cs deleted file mode 100644 index 99dcf7d4f1..0000000000 --- a/src/Perspex.Controls/IReparentingHost.cs +++ /dev/null @@ -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 -{ - /// - /// A control that can use the visual children of another control as its logical children. - /// - public interface IReparentingHost : ILogical - { - /// - /// Gets a writeable logical children collection from the host. - /// - new IPerspexList LogicalChildren { get; } - - /// - /// Asks the control whether it wants to reparent the logical children of the specified - /// control. - /// - /// The control. - /// - /// True if the control wants to reparent its logical children otherwise false. - /// - bool WillReparentChildrenOf(IControl control); - } -} \ No newline at end of file diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 1fc232870e..017434bfac 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -3,7 +3,7 @@ using System; using System.Collections; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -21,7 +21,7 @@ namespace Perspex.Controls /// /// Displays a collection of items. /// - public class ItemsControl : TemplatedControl, IReparentingHost + public class ItemsControl : TemplatedControl { /// /// The default value for the property. @@ -78,6 +78,12 @@ namespace Perspex.Controls if (_itemContainerGenerator == null) { _itemContainerGenerator = CreateItemContainerGenerator(); + + if (_itemContainerGenerator != null) + { + _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); + _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); + } } return _itemContainerGenerator; @@ -118,38 +124,130 @@ namespace Perspex.Controls public IItemsPresenter Presenter { get; - set; + protected set; } - /// - IPerspexList IReparentingHost.LogicalChildren => LogicalChildren; + /// + /// Gets the item at the specified index in a collection. + /// + /// The collection. + /// The index. + /// The index of the item or -1 if the item was not found. + protected static object ElementAt(IEnumerable items, int index) + { + var typedItems = items?.Cast(); + + if (index != -1 && typedItems != null && index < typedItems.Count()) + { + return typedItems.ElementAt(index) ?? null; + } + else + { + return null; + } + } /// - /// Asks the control whether it wants to reparent the logical children of the specified - /// control. + /// Gets the index of an item in a collection. /// - /// The control. - /// - /// True if the control wants to reparent its logical children otherwise false. - /// - bool IReparentingHost.WillReparentChildrenOf(IControl control) + /// The collection. + /// The item. + /// The index of the item or -1 if the item was not found. + protected static int IndexOf(IEnumerable items, object item) { - return control is IItemsPresenter && control.TemplatedParent == this; + if (items != null && item != null) + { + var list = items as IList; + + if (list != null) + { + return list.IndexOf(item); + } + else + { + int index = 0; + + foreach (var i in items) + { + if (Equals(i, item)) + { + return index; + } + + ++index; + } + } + } + + return -1; } /// /// Creates the for the control. /// - /// An . + /// + /// An or null. + /// + /// + /// Certain controls such as don't actually create item + /// containers; however they want it to be ItemsControls so that they have an Items + /// property etc. In this case, a derived class can override this method to return null + /// in order to disable the creation of item containers. + /// protected virtual IItemContainerGenerator CreateItemContainerGenerator() { return new ItemContainerGenerator(this); } + /// + /// Called when new containers are materialized for the by its + /// . + /// + /// The details of the containers. + protected virtual void OnContainersMaterialized(ItemContainerEventArgs e) + { + var toAdd = new List(); + + foreach (var container in e.Containers) + { + // If the item is its own container, then it will be added to the logical tree when + // it was added to the Items collection. + if (container.ContainerControl != container.Item) + { + toAdd.Add(container.ContainerControl); + } + } + + LogicalChildren.AddRange(toAdd); + } + + /// + /// Called when containers are dematerialized for the by its + /// . + /// + /// The details of the containers. + protected virtual void OnContainersDematerialized(ItemContainerEventArgs e) + { + var toRemove = new List(); + + foreach (var container in e.Containers) + { + // If the item is its own container, then it will be removed from the logical tree + // when it is removed from the Items collection. + if (container.ContainerControl != container.Item) + { + toRemove.Add(container.ContainerControl); + } + } + + LogicalChildren.RemoveAll(toRemove); + } + /// - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - Presenter = nameScope.Find("PART_ItemsPresenter"); + base.OnTemplateApplied(e); + Presenter = e.NameScope.Find("PART_ItemsPresenter"); } /// @@ -176,7 +274,11 @@ namespace Perspex.Controls incc.CollectionChanged -= ItemsCollectionChanged; } + var oldValue = e.OldValue as IEnumerable; var newValue = e.NewValue as IEnumerable; + + RemoveControlItemsFromLogicalChildren(oldValue); + AddControlItemsToLogicalChildren(newValue); SubscribeToItems(newValue); } @@ -188,6 +290,17 @@ namespace Perspex.Controls /// The event args. protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + AddControlItemsToLogicalChildren(e.NewItems); + break; + + case NotifyCollectionChangedAction.Remove: + RemoveControlItemsFromLogicalChildren(e.OldItems); + break; + } + var collection = sender as ICollection; if (collection.Count == 0) @@ -200,6 +313,54 @@ namespace Perspex.Controls } } + /// + /// Given a collection of items, adds those that are controls to the logical children. + /// + /// The items. + private void AddControlItemsToLogicalChildren(IEnumerable items) + { + var toAdd = new List(); + + if (items != null) + { + foreach (var i in items) + { + var control = i as IControl; + + if (control != null && !LogicalChildren.Contains(control)) + { + toAdd.Add(control); + } + } + } + + LogicalChildren.AddRange(toAdd); + } + + /// + /// Given a collection of items, removes those that are controls to from logical children. + /// + /// The items. + private void RemoveControlItemsFromLogicalChildren(IEnumerable items) + { + var toRemove = new List(); + + if (items != null) + { + foreach (var i in items) + { + var control = i as IControl; + + if (control != null) + { + toRemove.Add(control); + } + } + } + + LogicalChildren.RemoveAll(toRemove); + } + /// /// Subscribes to an collection. /// diff --git a/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs b/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs new file mode 100644 index 0000000000..024942cf71 --- /dev/null +++ b/src/Perspex.Controls/LogicalTreeAttachmentEventArgs.cs @@ -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 +{ + /// + /// Holds the event arguments for the and + /// events. + /// + public class LogicalTreeAttachmentEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The root of the logical tree. + public LogicalTreeAttachmentEventArgs(IStyleHost root) + { + Contract.Requires(root != null); + + Root = root; + } + + /// + /// Gets the root of the logical tree that the control is being attached to or detached from. + /// + public IStyleHost Root { get; } + } +} diff --git a/src/Perspex.Controls/MenuItem.cs b/src/Perspex.Controls/MenuItem.cs index 7207bfa808..1a95b48a41 100644 --- a/src/Perspex.Controls/MenuItem.cs +++ b/src/Perspex.Controls/MenuItem.cs @@ -20,7 +20,7 @@ namespace Perspex.Controls /// /// A menu item control. /// - public class MenuItem : SelectingItemsControl, ISelectable + public class MenuItem : HeaderedSelectingItemsControl, ISelectable { /// /// Defines the property. @@ -40,12 +40,6 @@ namespace Perspex.Controls public static readonly PerspexProperty CommandParameterProperty = Button.CommandParameterProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly PerspexProperty HeaderProperty = - HeaderedItemsControl.HeaderProperty.AddOwner(); - /// /// Defines the property. /// @@ -136,7 +130,6 @@ namespace Perspex.Controls set { SetValue(CommandProperty, value); } } - /// /// Gets or sets an associated with this control /// @@ -156,15 +149,6 @@ namespace Perspex.Controls set { SetValue(CommandParameterProperty, value); } } - /// - /// Gets or sets the 's header. - /// - public object Header - { - get { return GetValue(HeaderProperty); } - set { SetValue(HeaderProperty, value); } - } - /// /// Gets or sets the icon that appears in a . /// @@ -370,14 +354,12 @@ namespace Perspex.Controls } } - /// - /// Called when the MenuItem's template has been applied. - /// - protected override void OnTemplateApplied(INameScope nameScope) + /// + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - base.OnTemplateApplied(nameScope); + base.OnTemplateApplied(e); - _popup = nameScope.Get("PART_Popup"); + _popup = e.NameScope.Get("PART_Popup"); _popup.DependencyResolver = DependencyResolver.Instance; _popup.PopupRootCreated += PopupRootCreated; _popup.Opened += PopupOpened; diff --git a/src/Perspex.Controls/Mixins/ContentControlMixin.cs b/src/Perspex.Controls/Mixins/ContentControlMixin.cs new file mode 100644 index 0000000000..65153ed0af --- /dev/null +++ b/src/Perspex.Controls/Mixins/ContentControlMixin.cs @@ -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 +{ + /// + /// Adds content control functionality to control classes. + /// + /// + /// The adds behavior to a control which acts as a content + /// control such as and . It + /// updates keeps the control's logical children in sync with the content being displayed by + /// the control. + /// + public class ContentControlMixin + { + private static Lazy> subscriptions = + new Lazy>(() => + new ConditionalWeakTable()); + + /// + /// Initializes a new instance of the class. + /// + /// The control type. + /// The content property. + /// + /// Given an control of should return the control's + /// logical children collection. + /// + /// + /// The name of the content presenter in the control's template. + /// + public static void Attach( + PerspexProperty content, + Func> logicalChildrenSelector, + string presenterName = "PART_ContentPresenter") + where TControl : TemplatedControl + { + Contract.Requires(content != null); + Contract.Requires(logicalChildrenSelector != null); + + EventHandler 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 TemplateApplied; + + private static void OnTemplateApplied(object sender, RoutedEventArgs e) + { + TemplateApplied?.Invoke(sender, (TemplateAppliedEventArgs)e); + } + + private static void UpdateLogicalChild( + IPerspexList 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); + } + } + } + } +} diff --git a/src/Perspex.Controls/Panel.cs b/src/Perspex.Controls/Panel.cs index 9adda8bb16..edb110f22d 100644 --- a/src/Perspex.Controls/Panel.cs +++ b/src/Perspex.Controls/Panel.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using Perspex.Collections; using Perspex.Media; using Perspex.Metadata; @@ -18,7 +17,7 @@ namespace Perspex.Controls /// Controls can be added to a by adding them to its /// collection. All children are layed out to fill the panel. /// - public class Panel : Control, IReparentingControl, IPanel + public class Panel : Control, IPanel { /// /// Defines the property. @@ -68,7 +67,7 @@ namespace Perspex.Controls { Contract.Requires(value != null); - ClearVisualChildren(); + VisualChildren.Clear(); _children.Clear(); _children.AddRange(value); } @@ -83,58 +82,6 @@ namespace Perspex.Controls set { SetValue(BackgroundProperty, value); } } - /// - /// Requests that the visual children of the panel use another control as their logical - /// parent. - /// - /// - /// The logical parent for the visual children of the panel. - /// - /// - /// The collection to modify. - /// - void IReparentingControl.ReparentLogicalChildren(ILogical logicalParent, IPerspexList children) - { - Contract.Requires(logicalParent != null); - Contract.Requires(children != null); - - _childLogicalParent = logicalParent; - RedirectLogicalChildren(children); - - foreach (var control in Children) - { - ((ISetLogicalParent)control).SetParent(null); - ((ISetLogicalParent)control).SetParent((IControl)logicalParent); - children.Add(control); - } - } - - /// - /// Clears for the specified controls. - /// - /// The controls. - private void ClearLogicalParent(IEnumerable controls) - { - foreach (var control in controls) - { - ((ISetLogicalParent)control).SetParent(null); - } - } - - /// - /// Sets for the specified controls. - /// - /// The controls. - private void SetLogicalParent(IEnumerable controls) - { - var parent = _childLogicalParent as Control; - - foreach (var control in controls) - { - ((ISetLogicalParent)control).SetParent(parent); - } - } - /// /// Called when the collection changes. /// @@ -144,29 +91,35 @@ namespace Perspex.Controls { List controls; - // TODO: Handle Replace. switch (e.Action) { case NotifyCollectionChangedAction.Add: controls = e.NewItems.OfType().ToList(); - SetLogicalParent(controls); - AddVisualChildren(e.NewItems.OfType()); LogicalChildren.InsertRange(e.NewStartingIndex, controls); + VisualChildren.AddRange(e.NewItems.OfType()); break; case NotifyCollectionChangedAction.Remove: controls = e.OldItems.OfType().ToList(); - ClearLogicalParent(e.OldItems.OfType()); LogicalChildren.RemoveAll(controls); - RemoveVisualChildren(e.OldItems.OfType()); + VisualChildren.RemoveAll(e.OldItems.OfType()); + break; + + case NotifyCollectionChangedAction.Replace: + for (var i = 0; i < e.OldItems.Count; ++i) + { + var index = i + e.OldStartingIndex; + var child = (IControl)e.NewItems[i]; + LogicalChildren[index] = child; + VisualChildren[index] = child; + } break; case NotifyCollectionChangedAction.Reset: controls = e.OldItems.OfType().ToList(); - ClearLogicalParent(controls); LogicalChildren.Clear(); - ClearVisualChildren(); - AddVisualChildren(_children); + VisualChildren.Clear(); + VisualChildren.AddRange(_children); break; } diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 31b23702a4..10215b7e87 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -44,23 +44,27 @@ + + + + + - + - @@ -72,7 +76,6 @@ - @@ -150,6 +153,7 @@ + diff --git a/src/Perspex.Controls/Presenters/CarouselPresenter.cs b/src/Perspex.Controls/Presenters/CarouselPresenter.cs index e130936d29..cee9a053db 100644 --- a/src/Perspex.Controls/Presenters/CarouselPresenter.cs +++ b/src/Perspex.Controls/Presenters/CarouselPresenter.cs @@ -162,20 +162,13 @@ namespace Perspex.Controls.Presenters /// private void CreatePanel() { - var logicalHost = this.FindReparentingHost(); - - ClearVisualChildren(); Panel = ItemsPanel.Build(); Panel.SetValue(TemplatedParentProperty, TemplatedParent); - AddVisualChild(Panel); - - if (logicalHost != null) - { - ((IReparentingControl)Panel).ReparentLogicalChildren( - logicalHost, - logicalHost.LogicalChildren); - } + LogicalChildren.Clear(); + VisualChildren.Clear(); + LogicalChildren.Add(Panel); + VisualChildren.Add(Panel); _createdPanel = true; var task = MoveToPage(-1, SelectedIndex); @@ -204,7 +197,8 @@ namespace Perspex.Controls.Presenters { var item = Items.Cast().ElementAt(toIndex); to = generator.ContainerFromIndex(toIndex) ?? - generator.Materialize(toIndex, new[] { item }, MemberSelector).FirstOrDefault(); + generator.Materialize(toIndex, new[] { item }, MemberSelector) + .FirstOrDefault()?.ContainerControl; if (to != null) { diff --git a/src/Perspex.Controls/Presenters/ContentPresenter.cs b/src/Perspex.Controls/Presenters/ContentPresenter.cs index 46a57dd613..0faa7fd783 100644 --- a/src/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ContentPresenter.cs @@ -63,6 +63,14 @@ namespace Perspex.Controls.Presenters } } + /// + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + _createdChild = false; + InvalidateMeasure(); + } + /// protected override Size MeasureCore(Size availableSize) { @@ -101,18 +109,12 @@ namespace Perspex.Controls.Presenters var old = Child; var content = Content; var result = this.MaterializeDataTemplate(content); - var logicalHost = this.FindReparentingHost(); - var logicalChildren = logicalHost?.LogicalChildren ?? LogicalChildren; if (old != null) { - ((ISetLogicalParent)old).SetParent(null); - logicalChildren.Remove(old); - ClearVisualChildren(); + VisualChildren.Remove(old); } - Child = result; - if (result != null) { if (!(content is IControl)) @@ -120,14 +122,12 @@ namespace Perspex.Controls.Presenters result.DataContext = content; } - if (result.Parent == null) - { - ((ISetLogicalParent)result).SetParent((ILogical)logicalHost ?? this); - } - - AddVisualChild(result); - logicalChildren.Remove(old); - logicalChildren.Add(result); + Child = result; + VisualChildren.Add(result); + } + else + { + Child = null; } _createdChild = true; diff --git a/src/Perspex.Controls/Presenters/ItemsPresenter.cs b/src/Perspex.Controls/Presenters/ItemsPresenter.cs index ecd74e7bdb..6f052af7f7 100644 --- a/src/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/src/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using Perspex.Controls.Generators; using Perspex.Controls.Templates; using Perspex.Input; @@ -150,7 +151,6 @@ namespace Perspex.Controls.Presenters /// private void CreatePanel() { - ClearVisualChildren(); Panel = ItemsPanel.Build(); Panel.SetValue(TemplatedParentProperty, TemplatedParent); @@ -161,16 +161,10 @@ namespace Perspex.Controls.Presenters KeyboardNavigationMode.Contained); } - AddVisualChild(Panel); - - var logicalHost = this.FindReparentingHost(); - - if (logicalHost != null) - { - ((IReparentingControl)Panel).ReparentLogicalChildren( - logicalHost, - logicalHost.LogicalChildren); - } + LogicalChildren.Clear(); + VisualChildren.Clear(); + LogicalChildren.Add(Panel); + VisualChildren.Add(Panel); KeyboardNavigation.SetTabNavigation( (InputElement)Panel, @@ -187,7 +181,7 @@ namespace Perspex.Controls.Presenters { if (items != null) { - Panel.Children.AddRange(ItemContainerGenerator.Materialize(0, Items, MemberSelector)); + AddContainers(ItemContainerGenerator.Materialize(0, Items, MemberSelector)); INotifyCollectionChanged incc = items as INotifyCollectionChanged; @@ -238,30 +232,28 @@ namespace Perspex.Controls.Presenters if (_createdPanel) { var generator = ItemContainerGenerator; - IEnumerable containers; // TODO: Handle Move and Replace etc. switch (e.Action) { case NotifyCollectionChangedAction.Add: - containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector); - Panel.Children.AddRange(containers); + AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector)); break; case NotifyCollectionChangedAction.Remove: - containers = generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count); - Panel.Children.RemoveAll(containers); + RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count)); break; case NotifyCollectionChangedAction.Replace: - generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count); - containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector); + RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count)); + var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector); + AddContainers(containers); var i = e.NewStartingIndex; foreach (var container in containers) { - Panel.Children[i++] = container; + Panel.Children[i++] = container.ContainerControl; } break; @@ -269,13 +261,35 @@ namespace Perspex.Controls.Presenters case NotifyCollectionChangedAction.Move: // TODO: Implement Move in a more efficient manner. case NotifyCollectionChangedAction.Reset: - Panel.Children.RemoveAll(generator.Clear()); - Panel.Children.AddRange(generator.Materialize(0, Items, MemberSelector)); + RemoveContainers(generator.Clear()); + AddContainers(generator.Materialize(0, Items, MemberSelector)); break; } InvalidateMeasure(); } } + + private void AddContainers(IEnumerable items) + { + foreach (var i in items) + { + if (i.ContainerControl != null) + { + this.Panel.Children.Add(i.ContainerControl); + } + } + } + + private void RemoveContainers(IEnumerable items) + { + foreach (var i in items) + { + if (i.ContainerControl != null) + { + this.Panel.Children.Remove(i.ContainerControl); + } + } + } } } diff --git a/src/Perspex.Controls/Primitives/AdornerDecorator.cs b/src/Perspex.Controls/Primitives/AdornerDecorator.cs index 6fa8ec81b3..218e7b8360 100644 --- a/src/Perspex.Controls/Primitives/AdornerDecorator.cs +++ b/src/Perspex.Controls/Primitives/AdornerDecorator.cs @@ -10,7 +10,7 @@ namespace Perspex.Controls.Primitives AdornerLayer = new AdornerLayer(); ((ISetLogicalParent)AdornerLayer).SetParent(this); AdornerLayer.ZIndex = int.MaxValue; - AddVisualChild(AdornerLayer); + VisualChildren.Add(AdornerLayer); } public AdornerLayer AdornerLayer diff --git a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs index cf58646d4b..5f543cae17 100644 --- a/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs +++ b/src/Perspex.Controls/Primitives/HeaderedItemsControl.cs @@ -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 { + /// + /// Represents an with a related header. + /// public class HeaderedItemsControl : ItemsControl { + /// + /// Defines the property. + /// public static readonly PerspexProperty HeaderProperty = HeaderedContentControl.HeaderProperty.AddOwner(); + /// + /// Initializes static members of the class. + /// + static HeaderedItemsControl() + { + ContentControlMixin.Attach( + HeaderProperty, + x => x.LogicalChildren, + "PART_HeaderPresenter"); + } + + /// + /// Gets or sets the content of the control's header. + /// public object Header { get { return GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } + + /// + /// Gets the header presenter from the control's template. + /// + public ContentPresenter HeaderPresenter + { + get; + private set; + } + + /// + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + { + base.OnTemplateApplied(e); + HeaderPresenter = e.NameScope.Find("PART_HeaderPresenter"); + } } } diff --git a/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs b/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs new file mode 100644 index 0000000000..9a6431c3e5 --- /dev/null +++ b/src/Perspex.Controls/Primitives/HeaderedSelectingControl.cs @@ -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 +{ + /// + /// Represents a with a related header. + /// + public class HeaderedSelectingItemsControl : SelectingItemsControl + { + /// + /// Defines the property. + /// + public static readonly PerspexProperty HeaderProperty = + HeaderedContentControl.HeaderProperty.AddOwner(); + + /// + /// Initializes static members of the class. + /// + static HeaderedSelectingItemsControl() + { + ContentControlMixin.Attach( + HeaderProperty, + x => x.LogicalChildren, + "PART_HeaderPresenter"); + } + + /// + /// Gets or sets the content of the control's header. + /// + public object Header + { + get { return GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// + /// Gets the header presenter from the control's template. + /// + public ContentPresenter HeaderPresenter + { + get; + private set; + } + + /// + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + { + base.OnTemplateApplied(e); + HeaderPresenter = e.NameScope.Find("PART_HeaderPresenter"); + } + } +} diff --git a/src/Perspex.Controls/Primitives/PopupRoot.cs b/src/Perspex.Controls/Primitives/PopupRoot.cs index b69af20c3f..8b659d6e91 100644 --- a/src/Perspex.Controls/Primitives/PopupRoot.cs +++ b/src/Perspex.Controls/Primitives/PopupRoot.cs @@ -44,7 +44,6 @@ namespace Perspex.Controls.Primitives public PopupRoot(IPerspexDependencyResolver dependencyResolver) : base(PlatformManager.CreatePopup(), dependencyResolver) { - GetObservable(ParentProperty).Subscribe(x => InheritanceParent = (PerspexObject)x); } /// @@ -94,9 +93,9 @@ namespace Perspex.Controls.Primitives } /// - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - base.OnTemplateApplied(nameScope); + base.OnTemplateApplied(e); if (Parent.TemplatedParent != null) { diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index df9f5b08d2..999766f8af 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -98,7 +98,6 @@ namespace Perspex.Controls.Primitives /// public SelectingItemsControl() { - ItemContainerGenerator.ContainersInitialized.Subscribe(ContainersInitialized); } /// @@ -286,6 +285,27 @@ namespace Perspex.Controls.Primitives } } + /// + protected override void OnContainersMaterialized(ItemContainerEventArgs e) + { + base.OnContainersMaterialized(e); + + var selectedIndex = SelectedIndex; + var selectedContainer = e.Containers + .FirstOrDefault(x => (x.ContainerControl as ISelectable)?.IsSelected == true); + + if (selectedContainer != null) + { + SelectedIndex = selectedContainer.Index; + } + else if (selectedIndex >= e.StartingIndex && + selectedIndex < e.StartingIndex + e.Containers.Count) + { + var container = e.Containers[selectedIndex - e.StartingIndex]; + MarkContainerSelected(container.ContainerControl, true); + } + } + /// protected override void OnDataContextChanged() { @@ -379,7 +399,7 @@ namespace Perspex.Controls.Primitives bool rangeModifier = false, bool toggleModifier = false) { - var index = ItemContainerGenerator.IndexFromContainer(container); + var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1; if (index != -1) { @@ -416,61 +436,6 @@ namespace Perspex.Controls.Primitives return false; } - /// - /// Gets the item at the specified index in a collection. - /// - /// The collection. - /// The index. - /// The index of the item or -1 if the item was not found. - private static object ElementAt(IEnumerable items, int index) - { - var typedItems = items?.Cast(); - - if (index != -1 && typedItems != null && index < typedItems.Count()) - { - return typedItems.ElementAt(index) ?? null; - } - else - { - return null; - } - } - - /// - /// Gets the index of an item in a collection. - /// - /// The collection. - /// The item. - /// The index of the item or -1 if the item was not found. - private static int IndexOf(IEnumerable items, object item) - { - if (items != null && item != null) - { - var list = items as IList; - - if (list != null) - { - return list.IndexOf(item); - } - else - { - int index = 0; - - foreach (var i in items) - { - if (Equals(i, item)) - { - return index; - } - - ++index; - } - } - } - - return -1; - } - /// /// Gets a range of items from an IEnumerable. /// @@ -523,27 +488,6 @@ namespace Perspex.Controls.Primitives } } - /// - /// Called when new containers are initialized by the . - /// - /// The containers. - private void ContainersInitialized(ItemContainers containers) - { - var selectedIndex = SelectedIndex; - var selectedContainer = containers.Items.OfType().FirstOrDefault(x => x.IsSelected); - - if (selectedContainer != null) - { - SelectedIndex = containers.Items.IndexOf((IControl)selectedContainer) + containers.StartingIndex; - } - else if (selectedIndex >= containers.StartingIndex && - selectedIndex < containers.StartingIndex + containers.Items.Count) - { - var container = containers.Items[selectedIndex - containers.StartingIndex]; - MarkContainerSelected(container, true); - } - } - /// /// Called when a container raises the . /// @@ -625,7 +569,7 @@ namespace Perspex.Controls.Primitives /// Whether the item should be selected or deselected. private void MarkItemSelected(int index, bool selected) { - var container = ItemContainerGenerator.ContainerFromIndex(index); + var container = ItemContainerGenerator?.ContainerFromIndex(index); if (container != null) { @@ -655,6 +599,8 @@ namespace Perspex.Controls.Primitives /// The event args. private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { + var generator = ItemContainerGenerator; + switch (e.Action) { case NotifyCollectionChangedAction.Add: @@ -680,11 +626,14 @@ namespace Perspex.Controls.Primitives break; case NotifyCollectionChangedAction.Reset: - foreach (var item in ItemContainerGenerator.Containers) + if (generator != null) { - if (item != null) + foreach (var item in generator.Containers) { - MarkContainerSelected(item, false); + if (item != null) + { + MarkContainerSelected(item.ContainerControl, false); + } } } diff --git a/src/Perspex.Controls/Primitives/TabStrip.cs b/src/Perspex.Controls/Primitives/TabStrip.cs index 5fc305c73a..1cfb4e0515 100644 --- a/src/Perspex.Controls/Primitives/TabStrip.cs +++ b/src/Perspex.Controls/Primitives/TabStrip.cs @@ -1,52 +1,26 @@ // 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.Reactive.Linq; using Perspex.Controls.Generators; +using Perspex.Controls.Templates; using Perspex.Input; namespace Perspex.Controls.Primitives { public class TabStrip : SelectingItemsControl { - public static readonly PerspexProperty SelectedTabProperty = - TabControl.SelectedTabProperty.AddOwner(); + private static IMemberSelector s_MemberSelector = new FuncMemberSelector(SelectHeader); static TabStrip() { + MemberSelectorProperty.OverrideDefaultValue(s_MemberSelector); SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false); } - public TabStrip() - { - GetObservable(SelectedItemProperty).Subscribe(x => SelectedTab = x as TabItem); - GetObservable(SelectedTabProperty).Subscribe(x => SelectedItem = x as TabItem); - } - - public TabItem SelectedTab - { - get { return GetValue(SelectedTabProperty); } - set { SetValue(SelectedTabProperty, value); } - } - protected override IItemContainerGenerator CreateItemContainerGenerator() { - TabControl tabControl = TemplatedParent as TabControl; - IItemContainerGenerator result; - - if (tabControl != null) - { - result = tabControl.ItemContainerGenerator; - } - else - { - result = new ItemContainerGenerator(this, TabItem.ContentProperty); - } - - return result; + return new ItemContainerGenerator(this, ContentControl.ContentProperty); } /// @@ -70,5 +44,11 @@ namespace Perspex.Controls.Primitives e.Handled = UpdateSelectionFromEventSource(e.Source); } } + + private static object SelectHeader(object o) + { + var headered = o as IHeadered; + return (headered != null) ? (headered.Header ?? string.Empty) : o; + } } } diff --git a/src/Perspex.Controls/Primitives/TabStripItem.cs b/src/Perspex.Controls/Primitives/TabStripItem.cs new file mode 100644 index 0000000000..5e6787c167 --- /dev/null +++ b/src/Perspex.Controls/Primitives/TabStripItem.cs @@ -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 +{ + /// + /// Represents a tab in a . + /// + public class TabStripItem : ListBoxItem + { + } +} diff --git a/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs b/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs new file mode 100644 index 0000000000..e921f40785 --- /dev/null +++ b/src/Perspex.Controls/Primitives/TemplateAppliedEventArgs.cs @@ -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 +{ + /// + /// Holds the details of the event. + /// + public class TemplateAppliedEventArgs : RoutedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The applied template's name scope. + public TemplateAppliedEventArgs(INameScope nameScope) + : base(TemplatedControl.TemplateAppliedEvent) + { + NameScope = nameScope; + } + + /// + /// Gets the name scope of the applied template. + /// + public INameScope NameScope { get; } + } +} diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 2955c733b8..87e2f03eeb 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reactive.Linq; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; +using Perspex.Interactivity; using Perspex.Media; using Perspex.Styling; using Perspex.VisualTree; @@ -73,6 +74,14 @@ namespace Perspex.Controls.Primitives public static readonly PerspexProperty TemplateProperty = PerspexProperty.Register("Template"); + /// + /// Defines the routed event. + /// + public static readonly RoutedEvent TemplateAppliedEvent = + RoutedEvent.Register( + "TemplateApplied", + RoutingStrategies.Direct); + private bool _templateApplied; private readonly ILogger _templateLog; @@ -98,6 +107,15 @@ namespace Perspex.Controls.Primitives }); } + /// + /// Raised when the control's template is applied. + /// + public event EventHandler TemplateApplied + { + add { AddHandler(TemplateAppliedEvent, value); } + remove { RemoveHandler(TemplateAppliedEvent, value); } + } + /// /// Gets or sets the brush used to draw the control's background. /// @@ -184,7 +202,7 @@ namespace Perspex.Controls.Primitives { if (!_templateApplied) { - ClearVisualChildren(); + VisualChildren.Clear(); if (Template != null) { @@ -194,18 +212,21 @@ namespace Perspex.Controls.Primitives var nameScope = new NameScope(); NameScope.SetNameScope((Control)child, nameScope); - // We need to call SetTemplatedParentAndApplyChildTemplates twice - once - // before the controls are added to the visual tree so that the logical - // tree can be set up before styling is applied. - ((ISetLogicalParent)child).SetParent(this); + // We need to call SetupTemplateControls twice: + // - Once before the controls are added to the visual/logical trees so that the + // TemplatedParent property is set and names are registered; if + // TemplatedParent is not set when the control is added to the logical tree, + // then styles with the /template/ selector won't match. + // - Once after the controls are added to the logical tree (and thus styled) to + // call ApplyTemplate on nested templated controls and register any of our + // templated children that appear as children of presenters in these nested + // templated child controls. SetupTemplateControls(child, nameScope); - - // And again after the controls are added to the visual tree, and have their - // styling and thus Template property set. - AddVisualChild((Visual)child); + VisualChildren.Add(child); + ((ISetLogicalParent)child).SetParent(this); SetupTemplateControls(child, nameScope); - OnTemplateApplied(nameScope); + OnTemplateApplied(new TemplateAppliedEventArgs(nameScope)); } _templateApplied = true; @@ -231,9 +252,10 @@ namespace Perspex.Controls.Primitives /// /// Called when the control's template is applied. /// - /// The template name scope. - protected virtual void OnTemplateApplied(INameScope nameScope) + /// The event args. + protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e) { + RaiseEvent(e); } /// diff --git a/src/Perspex.Controls/Primitives/Track.cs b/src/Perspex.Controls/Primitives/Track.cs index ae9a8d1c4a..4e343b65b3 100644 --- a/src/Perspex.Controls/Primitives/Track.cs +++ b/src/Perspex.Controls/Primitives/Track.cs @@ -45,12 +45,12 @@ namespace Perspex.Controls.Primitives val.Item1.DragDelta -= ThumbDragged; } - ClearVisualChildren(); + VisualChildren.Clear(); if (val.Item2 != null) { val.Item2.DragDelta += ThumbDragged; - AddVisualChild(val.Item2); + VisualChildren.Add(val.Item2); } }); } diff --git a/src/Perspex.Controls/ProgressBar.cs b/src/Perspex.Controls/ProgressBar.cs index 4bc1fdb9d2..0cb512d50e 100644 --- a/src/Perspex.Controls/ProgressBar.cs +++ b/src/Perspex.Controls/ProgressBar.cs @@ -26,9 +26,9 @@ namespace Perspex.Controls } /// - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - _indicator = nameScope.Get("PART_Indicator"); + _indicator = e.NameScope.Get("PART_Indicator"); UpdateIndicator(Bounds.Size); } diff --git a/src/Perspex.Controls/TabControl.cs b/src/Perspex.Controls/TabControl.cs index bff505497f..86ca837a4e 100644 --- a/src/Perspex.Controls/TabControl.cs +++ b/src/Perspex.Controls/TabControl.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Perspex.Animation; -using Perspex.Controls.Presenters; +using Perspex.Controls.Generators; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; @@ -11,23 +11,26 @@ namespace Perspex.Controls /// /// A tab control that displays a tab strip along with the content of the selected tab. /// - public class TabControl : SelectingItemsControl, IReparentingHost + public class TabControl : SelectingItemsControl { - /// - /// Defines the property. - /// - public static readonly PerspexProperty SelectedTabProperty = - PerspexProperty.Register("SelectedTab"); - /// /// Defines the property. /// public static readonly PerspexProperty TransitionProperty = - Carousel.TransitionProperty.AddOwner(); + Perspex.Controls.Carousel.TransitionProperty.AddOwner(); - private static readonly IMemberSelector s_contentSelector = + /// + /// Defines an that selects the content of a . + /// + public static readonly IMemberSelector ContentSelector = new FuncMemberSelector(SelectContent); + /// + /// Defines an that selects the header of a . + /// + public static readonly IMemberSelector HeaderSelector = + new FuncMemberSelector(SelectHeader); + /// /// Defines the property. /// @@ -41,22 +44,25 @@ namespace Perspex.Controls { SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); FocusableProperty.OverrideDefaultValue(false); - SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); AffectsMeasure(TabStripPlacementProperty); } /// - /// Gets an that selects the content of a . + /// Gets the pages portion of the 's template. /// - public IMemberSelector ContentSelector => s_contentSelector; + public IControl Pages + { + get; + private set; + } /// - /// Gets the as a . + /// Gets the tab strip portion of the 's template. /// - public TabItem SelectedTab + public IControl TabStrip { - get { return GetValue(SelectedTabProperty); } - private set { SetValue(SelectedTabProperty, value); } + get; + private set; } /// @@ -77,17 +83,21 @@ namespace Perspex.Controls set { SetValue(TabStripPlacementProperty, value); } } - /// - /// Asks the control whether it wants to reparent the logical children of the specified - /// control. - /// - /// The control. - /// - /// True if the control wants to reparent its logical children otherwise false. - /// - bool IReparentingHost.WillReparentChildrenOf(IControl control) + protected override IItemContainerGenerator CreateItemContainerGenerator() { - return control is CarouselPresenter; + // TabControl doesn't actually create items - instead its TabStrip and Carousel + // children create the items. However we want it to be a SelectingItemsControl + // so that it has the Items/SelectedItem etc properties. In this case, we can + // return a null ItemContainerGenerator to disable the creation of item containers. + return null; + } + + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + { + base.OnTemplateApplied(e); + + TabStrip = e.NameScope.Find("PART_TabStrip"); + Pages = e.NameScope.Find("PART_Content"); } /// @@ -110,14 +120,31 @@ namespace Perspex.Controls } /// - /// Called when the property changes. + /// Selects the header of a tab item. /// - /// The event args. - private void SelectedItemChanged(PerspexPropertyChangedEventArgs e) + /// The tab item. + /// The content. + private static object SelectHeader(object o) { - var item = e.NewValue as IContentControl; - var content = item?.Content ?? item; - SelectedTab = item as TabItem; + var headered = o as IHeadered; + var control = o as IControl; + + if (headered != null) + { + return headered.Header ?? string.Empty; + } + else if (control != null) + { + // Non-headered control items should result in TabStripItems with empty content. + // If a TabStrip is created with non IHeadered controls as its items, don't try to + // display the control in the TabStripItem: the content portion will also try to + // display this control, resulting in dual-parentage breakage. + return string.Empty; + } + else + { + return o; + } } } } diff --git a/src/Perspex.Controls/Templates/TemplateExtensions.cs b/src/Perspex.Controls/Templates/TemplateExtensions.cs index b8cd9e25c2..40672f873e 100644 --- a/src/Perspex.Controls/Templates/TemplateExtensions.cs +++ b/src/Perspex.Controls/Templates/TemplateExtensions.cs @@ -12,35 +12,6 @@ namespace Perspex.Controls.Templates { public static class TemplateExtensions { - public static IReparentingHost FindReparentingHost(this IControl control) - { - var tp = control.TemplatedParent; - var chain = new List(); - - 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 IEnumerable GetTemplateChildren(this ITemplatedControl control) { var visual = control as IVisual; diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index 1442bae1f2..ea749be84e 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -26,6 +26,7 @@ namespace Perspex.Controls public static readonly PerspexProperty FontFamilyProperty = PerspexProperty.RegisterAttached( nameof(FontFamily), + defaultValue: "Courier New", inherits: true); /// @@ -34,6 +35,7 @@ namespace Perspex.Controls public static readonly PerspexProperty FontSizeProperty = PerspexProperty.RegisterAttached( nameof(FontSize), + defaultValue: 12, inherits: true); /// @@ -343,8 +345,8 @@ namespace Perspex.Controls { var result = new FormattedText( Text ?? string.Empty, - FontFamily ?? "Arial", - FontSize > 0 ? FontSize : 12, + FontFamily, + FontSize, FontStyle, TextAlignment, FontWeight); diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 7eddecf0f7..0bfc8d2eeb 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -157,9 +157,9 @@ namespace Perspex.Controls set { SetValue(TextWrappingProperty, value); } } - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - _presenter = nameScope.Get("PART_TextPresenter"); + _presenter = e.NameScope.Get("PART_TextPresenter"); _presenter.Cursor = new Cursor(StandardCursorType.Ibeam); } diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index ca84f67de1..2f3799751c 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -24,7 +24,7 @@ namespace Perspex.Controls /// . It handles scheduling layout, styling and rendering as well as /// tracking the window and state. /// - public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable + public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot { /// /// Defines the property. @@ -204,6 +204,11 @@ namespace Perspex.Controls set { SetValue(AccessText.ShowAccessKeyProperty, value); } } + IStyleHost IStyleHost.StylingParent + { + get { return PerspexLocator.Current.GetService(); } + } + /// /// Whether an auto-size operation is in progress. /// diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 597528d0a7..e8fa579960 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -81,11 +81,6 @@ namespace Perspex /// private string _name; - /// - /// Holds the children of the visual. - /// - private readonly PerspexList _visualChildren; - /// /// The visual's bounds relative to its parent. /// @@ -128,9 +123,10 @@ namespace Perspex new PropertyEnricher("Id", GetHashCode()), }); - _visualChildren = new PerspexList(); - _visualChildren.ResetBehavior = ResetBehavior.Remove; - _visualChildren.CollectionChanged += VisualChildrenChanged; + var visualChildren = new PerspexList(); + visualChildren.ResetBehavior = ResetBehavior.Remove; + visualChildren.CollectionChanged += VisualChildrenChanged; + VisualChildren = visualChildren; } /// @@ -249,6 +245,15 @@ namespace Perspex set { SetValue(ZIndexProperty, value); } } + /// + /// Gets the control's visual children. + /// + protected IPerspexList VisualChildren + { + get; + private set; + } + /// /// Gets a value indicating whether this scene graph node is attached to a visual root. /// @@ -257,7 +262,7 @@ namespace Perspex /// /// Gets the scene graph node's child nodes. /// - IPerspexReadOnlyList IVisual.VisualChildren => _visualChildren; + IPerspexReadOnlyList IVisual.VisualChildren => VisualChildren; /// /// Gets the scene graph node's parent node. @@ -333,61 +338,6 @@ namespace Perspex property.Changed.Subscribe(AffectsRenderInvalidate); } - /// - /// Adds a visual child to the control. - /// - /// The child to add. - protected void AddVisualChild(IVisual visual) - { - Contract.Requires(visual != null); - - _visualChildren.Add(visual); - } - - /// - /// Adds visual children to the control. - /// - /// The children to add. - protected void AddVisualChildren(IEnumerable visuals) - { - Contract.Requires(visuals != null); - - _visualChildren.AddRange(visuals); - } - - /// - /// Removes all visual children from the control. - /// - protected void ClearVisualChildren() - { - _visualChildren.Clear(); - } - - /// - /// Removes a visual child from the control; - /// - /// The child to remove. - protected void RemoveVisualChild(IVisual visual) - { - Contract.Requires(visual != null); - - _visualChildren.Remove(visual); - } - - /// - /// Removes a visual children from the control; - /// - /// The children to remove. - protected void RemoveVisualChildren(IEnumerable visuals) - { - Contract.Requires(visuals != null); - - foreach (var v in visuals) - { - _visualChildren.Remove(v); - } - } - /// /// Called when the control is added to a visual tree. /// @@ -433,20 +383,6 @@ namespace Perspex (e.Sender as Visual)?.InvalidateVisual(); } - /// - /// Gets the event args for an or - /// event. - /// - /// - /// A if the visual currently has a root; - /// otherwise null. - /// - private VisualTreeAttachmentEventArgs GetAttachmentEventArgs() - { - var root = this.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); - return root != null ? new VisualTreeAttachmentEventArgs(root) : null; - } - /// /// Gets the visual offset from the specified ancestor. /// @@ -544,38 +480,28 @@ namespace Perspex } var old = _visualParent; + _visualParent = value; if (_isAttachedToVisualTree) { - var oldArgs = GetAttachmentEventArgs(); - - _visualParent = value; - - if (oldArgs != null) - { - NotifyDetachedFromVisualTree(oldArgs); - } - } - else - { - _visualParent = value; + var root = (this as IRenderRoot) ?? + old.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + var e = new VisualTreeAttachmentEventArgs(root); + NotifyDetachedFromVisualTree(e); } if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { - var newArgs = GetAttachmentEventArgs(); - - if (newArgs != null) - { - NotifyAttachedToVisualTree(newArgs); - } + var root = this.GetVisualAncestors().OfType().FirstOrDefault(); + var e = new VisualTreeAttachmentEventArgs(root); + NotifyAttachedToVisualTree(e); } RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue); } /// - /// Called when the collection changes. + /// Called when the collection changes. /// /// The sender. /// The event args. @@ -586,7 +512,6 @@ namespace Perspex case NotifyCollectionChangedAction.Add: foreach (Visual v in e.NewItems) { - v.InheritanceParent = this; v.SetVisualParent(this); } @@ -595,7 +520,6 @@ namespace Perspex case NotifyCollectionChangedAction.Remove: foreach (Visual v in e.OldItems) { - v.InheritanceParent = null; v.SetVisualParent(null); } @@ -616,9 +540,9 @@ namespace Perspex OnAttachedToVisualTree(e); - if (_visualChildren != null) + if (VisualChildren != null) { - foreach (Visual child in _visualChildren.OfType()) + foreach (Visual child in VisualChildren.OfType()) { child.NotifyAttachedToVisualTree(e); } @@ -637,9 +561,9 @@ namespace Perspex _isAttachedToVisualTree = false; OnDetachedFromVisualTree(e); - if (_visualChildren != null) + if (VisualChildren != null) { - foreach (Visual child in _visualChildren.OfType()) + foreach (Visual child in VisualChildren.OfType()) { child.NotifyDetachedFromVisualTree(e); } diff --git a/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs b/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs index b2df5a9a34..a6f1081ed3 100644 --- a/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs +++ b/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs @@ -18,6 +18,8 @@ namespace Perspex /// The root visual. public VisualTreeAttachmentEventArgs(IRenderRoot root) { + Contract.Requires(root != null); + Root = root; } diff --git a/src/Perspex.Styling/ILogical.cs b/src/Perspex.Styling/ILogical.cs index a62e309021..b1f5ded5b6 100644 --- a/src/Perspex.Styling/ILogical.cs +++ b/src/Perspex.Styling/ILogical.cs @@ -10,6 +10,11 @@ namespace Perspex /// public interface ILogical { + /// + /// Gets a value indicating whether the element is attached to a rooted logical tree. + /// + bool IsAttachedToLogicalTree { get; } + /// /// Gets the logical parent. /// diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index cb5367bc9a..2ad2057aa5 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -49,6 +49,7 @@ + diff --git a/src/Perspex.Styling/Styling/IGlobalStyles.cs b/src/Perspex.Styling/Styling/IGlobalStyles.cs index ef246f9b84..85cc9abaa7 100644 --- a/src/Perspex.Styling/Styling/IGlobalStyles.cs +++ b/src/Perspex.Styling/Styling/IGlobalStyles.cs @@ -3,8 +3,10 @@ namespace Perspex.Styling { - public interface IGlobalStyles + /// + /// Defines the style host that provides styles global to the application. + /// + public interface IGlobalStyles : IStyleRoot { - Styles Styles { get; } } } diff --git a/src/Perspex.Styling/Styling/IStyleHost.cs b/src/Perspex.Styling/Styling/IStyleHost.cs index 40cad25f63..9f434e1231 100644 --- a/src/Perspex.Styling/Styling/IStyleHost.cs +++ b/src/Perspex.Styling/Styling/IStyleHost.cs @@ -6,7 +6,7 @@ namespace Perspex.Styling /// /// Defines an element that has a collection. /// - public interface IStyleHost : IVisual + public interface IStyleHost { /// /// Gets the styles for the element. diff --git a/src/Perspex.Styling/Styling/IStyleRoot.cs b/src/Perspex.Styling/Styling/IStyleRoot.cs new file mode 100644 index 0000000000..9b3e1dcc70 --- /dev/null +++ b/src/Perspex.Styling/Styling/IStyleRoot.cs @@ -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 +{ + /// + /// Denotes the root in a tree. + /// + public interface IStyleRoot : IStyleHost + { + } +} diff --git a/src/Perspex.Styling/Styling/IStyleable.cs b/src/Perspex.Styling/Styling/IStyleable.cs index c6ebacaea7..e5a3f9db81 100644 --- a/src/Perspex.Styling/Styling/IStyleable.cs +++ b/src/Perspex.Styling/Styling/IStyleable.cs @@ -3,6 +3,7 @@ using System; using Perspex.Collections; +using System.Reactive; namespace Perspex.Styling { @@ -11,6 +12,11 @@ namespace Perspex.Styling /// public interface IStyleable : IObservablePropertyBag, INamed { + /// + /// Raised when the control's style should be removed. + /// + IObservable StyleDetach { get; } + /// /// Gets the list of classes for the control. /// diff --git a/src/Perspex.Styling/Styling/Selector.cs b/src/Perspex.Styling/Styling/Selector.cs index 06999744c1..b47a3e28fd 100644 --- a/src/Perspex.Styling/Styling/Selector.cs +++ b/src/Perspex.Styling/Styling/Selector.cs @@ -141,7 +141,7 @@ namespace Perspex.Styling if (inputs.Count > 0) { - return new SelectorMatch(new StyleActivator(inputs)); + return new SelectorMatch(StyleActivator.And(inputs)); } else { diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index 67da8fc8ca..7ed066f293 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/src/Perspex.Styling/Styling/Selectors.cs @@ -64,7 +64,7 @@ namespace Perspex.Styling { Contract.Requires(previous != null); - return new Selector(previous, x => MatchIs(x, type), type.Name, type); + return new Selector(previous, x => MatchIs(x, type), $":is({type.Name})", type); } /// @@ -218,9 +218,7 @@ namespace Perspex.Styling } } - return new SelectorMatch(new StyleActivator( - descendentMatches, - ActivatorMode.Or)); + return new SelectorMatch(StyleActivator.Or(descendentMatches)); } private static SelectorMatch MatchIs(IStyleable control, Type type) diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 112bc817a0..b1337b0233 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reactive.Linq; using Perspex.Metadata; @@ -13,6 +14,8 @@ namespace Perspex.Styling /// public class Style : IStyle { + private static readonly IObservable True = Observable.Never().StartWith(true); + /// /// Initializes a new instance of the class. /// @@ -56,17 +59,8 @@ namespace Perspex.Styling if (match.ImmediateResult != false) { - var visual = control as IVisual; - var activator = match.ObservableResult ?? - Observable.Never().StartWith(true); - - if (visual != null) - { - var detached = Observable.FromEventPattern( - x => visual.DetachedFromVisualTree += x, - x => visual.DetachedFromVisualTree -= x); - activator = activator.TakeUntil(detached); - } + var activator = (match.ObservableResult ?? True) + .TakeUntil(control.StyleDetach); foreach (var setter in Setters) { diff --git a/src/Perspex.Styling/Styling/StyleActivator.cs b/src/Perspex.Styling/Styling/StyleActivator.cs index aa71884530..463fc5bb78 100644 --- a/src/Perspex.Styling/Styling/StyleActivator.cs +++ b/src/Perspex.Styling/Styling/StyleActivator.cs @@ -15,30 +15,28 @@ namespace Perspex.Styling Or, } - public class StyleActivator : ObservableBase + public static class StyleActivator { - private readonly IObservable[] _inputs; - private readonly ActivatorMode _mode; - - public StyleActivator( - IList> inputs, - ActivatorMode mode = ActivatorMode.And) + public static IObservable And(IEnumerable> inputs) { - _inputs = inputs.ToArray(); - _mode = mode; - } + var sourceArray = inputs.Select(s => s.Publish().RefCount()).ToArray(); - protected override IDisposable SubscribeCore(IObserver observer) - { - return _inputs.CombineLatest() - .Select(Calculate) + var terminate = sourceArray + .ToObservable() + .SelectMany(x => x.LastAsync() + .Where(y => y == false)); + + return sourceArray + .CombineLatest(values => values.All(x => x)) .DistinctUntilChanged() - .Subscribe(observer); + .TakeUntil(terminate); } - private bool Calculate(IList values) + public static IObservable Or(IEnumerable> inputs) { - return _mode == ActivatorMode.And ? values.All(x => x) : values.Any(x => x); + return inputs.CombineLatest() + .Select(values => values.Any(x => x)) + .DistinctUntilChanged(); } } } diff --git a/src/Perspex.Styling/Styling/Styler.cs b/src/Perspex.Styling/Styling/Styler.cs index f2f8f56380..400af18252 100644 --- a/src/Perspex.Styling/Styling/Styler.cs +++ b/src/Perspex.Styling/Styling/Styler.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using Perspex.VisualTree; namespace Perspex.Styling { @@ -11,39 +9,27 @@ namespace Perspex.Styling { public void ApplyStyles(IStyleable control) { - IVisual visual = control as IVisual; - IStyleHost styleContainer = visual - .GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(); - IGlobalStyles global = PerspexLocator.Current.GetService(); + var styleHost = control as IStyleHost; - global?.Styles.Attach(control, null); - - if (styleContainer != null) + if (styleHost != null) { - ApplyStyles(control, styleContainer); + ApplyStyles(control, styleHost); } } - private void ApplyStyles(IStyleable control, IStyleHost container) + private void ApplyStyles(IStyleable control, IStyleHost styleHost) { Contract.Requires(control != null); - Contract.Requires(container != null); + Contract.Requires(styleHost != null); - var parentContainer = container.StylingParent; + var parentContainer = styleHost.StylingParent; if (parentContainer != null) { ApplyStyles(control, parentContainer); } - container.Styles.Attach(control, container); - } - - private IStyleHost GetParentContainer(IStyleHost container) - { - return container.GetVisualAncestors().OfType().FirstOrDefault(); + styleHost.Styles.Attach(control, styleHost); } } } diff --git a/src/Perspex.Themes.Default/Button.paml b/src/Perspex.Themes.Default/Button.paml index 256ac94df9..7cfd1e7f35 100644 --- a/src/Perspex.Themes.Default/Button.paml +++ b/src/Perspex.Themes.Default/Button.paml @@ -13,7 +13,8 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> - - + diff --git a/src/Perspex.Themes.Default/MenuItem.paml b/src/Perspex.Themes.Default/MenuItem.paml index 68d4602a92..26034cacbd 100644 --- a/src/Perspex.Themes.Default/MenuItem.paml +++ b/src/Perspex.Themes.Default/MenuItem.paml @@ -24,7 +24,8 @@ IsVisible="False" Margin="3" VerticalAlignment="Center"/> - @@ -78,7 +79,8 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> - diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index 2ec676592d..f17c80fe52 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -159,7 +159,7 @@ Designer - + Designer diff --git a/src/Perspex.Themes.Default/TabControl.paml b/src/Perspex.Themes.Default/TabControl.paml index 0809be9077..b678304a87 100644 --- a/src/Perspex.Themes.Default/TabControl.paml +++ b/src/Perspex.Themes.Default/TabControl.paml @@ -3,10 +3,13 @@ - - @@ -18,11 +21,14 @@ - - @@ -35,7 +41,9 @@ - @@ -45,8 +53,9 @@ - @@ -58,13 +67,16 @@ - - diff --git a/src/Perspex.Themes.Default/TabStrip.paml b/src/Perspex.Themes.Default/TabStrip.paml index a9df7c0ca7..fbc8aefd8d 100644 --- a/src/Perspex.Themes.Default/TabStrip.paml +++ b/src/Perspex.Themes.Default/TabStrip.paml @@ -2,7 +2,9 @@ - \ No newline at end of file diff --git a/src/Perspex.Themes.Default/TabItem.paml b/src/Perspex.Themes.Default/TabStripItem.paml similarity index 53% rename from src/Perspex.Themes.Default/TabItem.paml rename to src/Perspex.Themes.Default/TabStripItem.paml index 3781d32de7..08dd0bb932 100644 --- a/src/Perspex.Themes.Default/TabItem.paml +++ b/src/Perspex.Themes.Default/TabStripItem.paml @@ -1,14 +1,16 @@  - - \ No newline at end of file diff --git a/src/Perspex.Themes.Default/ToggleButton.paml b/src/Perspex.Themes.Default/ToggleButton.paml index cbb5341450..0311294db9 100644 --- a/src/Perspex.Themes.Default/ToggleButton.paml +++ b/src/Perspex.Themes.Default/ToggleButton.paml @@ -13,7 +13,8 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> - - diff --git a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/tests/Perspex.Controls.UnitTests/ContentControlTests.cs index ca47bf8571..ff0c993af6 100644 --- a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -90,7 +90,7 @@ namespace Perspex.Controls.UnitTests } [Fact] - public void Setting_Content_To_Control_Should_Set_Child_Controls_Parent() + public void Control_Content_Should_Be_Logical_Child_Before_ApplyTemplate() { var target = new ContentControl { @@ -99,14 +99,14 @@ namespace Perspex.Controls.UnitTests var child = new Control(); target.Content = child; - target.ApplyTemplate(); Assert.Equal(child.Parent, target); - Assert.Equal(((ILogical)child).LogicalParent, target); + Assert.Equal(child.GetLogicalParent(), target); + Assert.Equal(new[] { child }, target.GetLogicalChildren()); } [Fact] - public void Setting_Content_To_String_Should_Set_Child_Controls_Parent() + public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate() { var target = new ContentControl { @@ -118,12 +118,14 @@ namespace Perspex.Controls.UnitTests var child = target.Presenter.Child; - Assert.Equal(child.Parent, target); - Assert.Equal(((ILogical)child).LogicalParent, target); + 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_Child_Controls_Parent() + public void Clearing_Content_Should_Clear_Logical_Child() { var target = new ContentControl(); var child = new Control(); @@ -132,52 +134,7 @@ namespace Perspex.Controls.UnitTests target.Content = null; Assert.Null(child.Parent); - Assert.Null(((ILogical)child).LogicalParent); - } - - [Fact] - public void Setting_Content_To_Control_Should_Make_Control_Appear_In_LogicalChildren() - { - var target = new ContentControl(); - var child = new Control(); - - target.Template = GetTemplate(); - target.Content = child; - target.ApplyTemplate(); - - Assert.Equal(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); - } - - [Fact] - public void Setting_Content_To_String_Should_Make_TextBlock_Appear_In_LogicalChildren() - { - var target = new ContentControl(); - var child = new Control(); - - target.Template = GetTemplate(); - target.Content = "Foo"; - target.ApplyTemplate(); - - var logical = (ILogical)target; - Assert.Equal(1, logical.LogicalChildren.Count); - Assert.IsType(logical.LogicalChildren[0]); - } - - [Fact] - public void Clearing_Content_Should_Remove_From_LogicalChildren() - { - var target = new ContentControl(); - var child = new Control(); - - target.Template = GetTemplate(); - target.Content = child; - target.ApplyTemplate(); - - target.Content = null; - - // Need to call ApplyTemplate on presenter for LogicalChildren to be updated. - target.Presenter.ApplyTemplate(); - + Assert.Null(child.GetLogicalParent()); Assert.Empty(target.GetLogicalChildren()); } @@ -194,8 +151,6 @@ namespace Perspex.Controls.UnitTests target.Template = GetTemplate(); target.Content = child; target.ApplyTemplate(); - - // Need to call ApplyTemplate on presenter for LogicalChildren to be updated. target.Presenter.ApplyTemplate(); Assert.True(called); @@ -215,8 +170,6 @@ namespace Perspex.Controls.UnitTests ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; target.Content = null; - - // Need to call ApplyTemplate on presenter for CollectionChanged to be called. target.Presenter.ApplyTemplate(); Assert.True(called); @@ -237,8 +190,6 @@ namespace Perspex.Controls.UnitTests ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => called = true; contentControl.Content = child2; - - // Need to call ApplyTemplate on presenter for CollectionChanged to be called. contentControl.Presenter.ApplyTemplate(); Assert.True(called); @@ -261,7 +212,7 @@ namespace Perspex.Controls.UnitTests } [Fact] - public void DataContext_Should_Be_Set_For_Templated_Data() + public void DataContext_Should_Be_Set_For_DataTemplate_Created_Content() { var target = new ContentControl(); @@ -273,7 +224,7 @@ namespace Perspex.Controls.UnitTests } [Fact] - public void DataContext_Should_Not_Be_Set_For_Control_Data() + public void DataContext_Should_Not_Be_Set_For_Control_Content() { var target = new ContentControl(); diff --git a/tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs deleted file mode 100644 index 05b330872f..0000000000 --- a/tests/Perspex.Controls.UnitTests/ContentPresenterTests.cs +++ /dev/null @@ -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); - } - } -} diff --git a/tests/Perspex.Controls.UnitTests/ControlTests.cs b/tests/Perspex.Controls.UnitTests/ControlTests.cs index d86733de3d..152c175d20 100644 --- a/tests/Perspex.Controls.UnitTests/ControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ControlTests.cs @@ -2,6 +2,8 @@ // 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.Reactive.Linq; using Moq; using Perspex.Layout; using Perspex.Platform; @@ -22,24 +24,105 @@ namespace Perspex.Controls.UnitTests } [Fact] - public void Adding_Control_To_IRenderRoot_Should_Style_Control() + public void LogicalParent_Should_Be_Set_To_Parent() { - using (PerspexLocator.EnterScope()) + var parent = new Decorator(); + var target = new TestControl(); + + parent.Child = target; + + Assert.Equal(parent, target.InheritanceParent); + } + + [Fact] + public void LogicalParent_Should_Be_Cleared_When_Removed_From_Parent() + { + var parent = new Decorator(); + var target = new TestControl(); + + parent.Child = target; + parent.Child = null; + + Assert.Null(target.InheritanceParent); + } + + [Fact] + public void AttachedToLogicalParent_Should_Be_Called_When_Added_To_Tree() + { + var root = new TestRoot(); + var parent = new Border(); + var child = new Border(); + var grandchild = new Border(); + var parentRaised = false; + var childRaised = false; + var grandchildRaised = false; + + parent.AttachedToLogicalTree += (s, e) => parentRaised = true; + child.AttachedToLogicalTree += (s, e) => childRaised = true; + grandchild.AttachedToLogicalTree += (s, e) => grandchildRaised = true; + + parent.Child = child; + child.Child = grandchild; + + Assert.False(parentRaised); + Assert.False(childRaised); + Assert.False(grandchildRaised); + + root.Child = parent; + + Assert.True(parentRaised); + Assert.True(childRaised); + Assert.True(grandchildRaised); + } + + [Fact] + public void AttachedToLogicalParent_Should_Be_Called_Before_Parent_Change_Signalled() + { + var root = new TestRoot(); + var child = new Border(); + var raised = new List(); + + child.AttachedToLogicalTree += (s, e) => { - var root = new TestRoot(); - var target = new Control(); - var styler = new Mock(); + Assert.Equal(root, child.Parent); + raised.Add("attached"); + }; - PerspexLocator.CurrentMutable.Bind().ToConstant(styler.Object); + child.GetObservable(Control.ParentProperty).Skip(1).Subscribe(_ => raised.Add("parent")); - root.Child = target; + root.Child = child; - styler.Verify(x => x.ApplyStyles(target), Times.Once()); - } + Assert.Equal(new[] { "attached", "parent" }, raised); } [Fact] - public void Adding_Tree_To_ILayoutRoot_Should_Style_Controls() + public void DetachedToLogicalParent_Should_Be_Called_When_Removed_From_Tree() + { + var root = new TestRoot(); + var parent = new Border(); + var child = new Border(); + var grandchild = new Border(); + var parentRaised = false; + var childRaised = false; + var grandchildRaised = false; + + parent.Child = child; + child.Child = grandchild; + root.Child = parent; + + parent.DetachedFromLogicalTree += (s, e) => parentRaised = true; + child.DetachedFromLogicalTree += (s, e) => childRaised = true; + grandchild.DetachedFromLogicalTree += (s, e) => grandchildRaised = true; + + root.Child = null; + + Assert.True(parentRaised); + Assert.True(childRaised); + Assert.True(grandchildRaised); + } + + [Fact] + public void Adding_Tree_To_IStyleRoot_Should_Style_Controls() { using (PerspexLocator.EnterScope()) { @@ -64,7 +147,7 @@ namespace Perspex.Controls.UnitTests } } - private class TestRoot : Decorator, ILayoutRoot, IRenderRoot + private class TestRoot : Decorator, ILayoutRoot, IRenderRoot, IStyleRoot { public Size ClientSize { @@ -91,5 +174,10 @@ namespace Perspex.Controls.UnitTests throw new NotImplementedException(); } } + + private class TestControl : Control + { + public new PerspexObject InheritanceParent => base.InheritanceParent; + } } } diff --git a/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs b/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs index 2aa911b61a..ec5f657877 100644 --- a/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs +++ b/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs @@ -5,6 +5,7 @@ using System; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; using Perspex.Rendering; +using Perspex.Styling; using Xunit; namespace Perspex.Controls.UnitTests @@ -71,7 +72,7 @@ namespace Perspex.Controls.UnitTests Assert.Null(NameScope.GetNameScope(root.Presenter).Find("foo")); } - private class TestRoot : ContentControl, IRenderRoot, INameScope + private class TestRoot : ContentControl, IRenderRoot, INameScope, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); diff --git a/tests/Perspex.Controls.UnitTests/EnumerableExtensions.cs b/tests/Perspex.Controls.UnitTests/EnumerableExtensions.cs new file mode 100644 index 0000000000..92b7f9c815 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/EnumerableExtensions.cs @@ -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 Do(this IEnumerable items, Action action) + { + foreach (var i in items) + { + action(i); + yield return i; + } + } + } +} diff --git a/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index d8d2c6ba1f..7731155e71 100644 --- a/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs @@ -16,7 +16,11 @@ namespace Perspex.Controls.UnitTests.Generators var owner = new Decorator(); var target = new ItemContainerGenerator(owner); var containers = target.Materialize(0, items, null); - var result = containers.OfType().Select(x => x.Text).ToList(); + var result = containers + .Select(x => x.ContainerControl) + .OfType() + .Select(x => x.Text) + .ToList(); Assert.Equal(items, result); } @@ -29,9 +33,9 @@ namespace Perspex.Controls.UnitTests.Generators var target = new ItemContainerGenerator(owner); var containers = target.Materialize(0, items, null).ToList(); - Assert.Equal(containers[0], target.ContainerFromIndex(0)); - Assert.Equal(containers[1], target.ContainerFromIndex(1)); - Assert.Equal(containers[2], target.ContainerFromIndex(2)); + Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); + Assert.Equal(containers[1].ContainerControl, target.ContainerFromIndex(1)); + Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } [Fact] @@ -42,9 +46,9 @@ namespace Perspex.Controls.UnitTests.Generators var target = new ItemContainerGenerator(owner); var containers = target.Materialize(0, items, null).ToList(); - Assert.Equal(0, target.IndexFromContainer(containers[0])); - Assert.Equal(1, target.IndexFromContainer(containers[1])); - Assert.Equal(2, target.IndexFromContainer(containers[2])); + Assert.Equal(0, target.IndexFromContainer(containers[0].ContainerControl)); + Assert.Equal(1, target.IndexFromContainer(containers[1].ContainerControl)); + Assert.Equal(2, target.IndexFromContainer(containers[2].ContainerControl)); } [Fact] @@ -57,9 +61,9 @@ namespace Perspex.Controls.UnitTests.Generators target.Dematerialize(1, 1); - Assert.Equal(containers[0], target.ContainerFromIndex(0)); + Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); Assert.Equal(null, target.ContainerFromIndex(1)); - Assert.Equal(containers[2], target.ContainerFromIndex(2)); + Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } [Fact] @@ -85,8 +89,8 @@ namespace Perspex.Controls.UnitTests.Generators var removed = target.RemoveRange(1, 1).Single(); - Assert.Equal(containers[0], target.ContainerFromIndex(0)); - Assert.Equal(containers[2], target.ContainerFromIndex(1)); + Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); + Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(1)); Assert.Equal(containers[1], removed); } } diff --git a/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs b/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs index f6b0ff2eae..e59a1e0256 100644 --- a/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs +++ b/tests/Perspex.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs @@ -17,10 +17,10 @@ namespace Perspex.Controls.UnitTests.Generators var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty); var containers = target.Materialize(0, items, null); var result = containers + .Select(x => x.ContainerControl) .OfType() .Select(x => x.Content) - .OfType() - .Select(x => x.Text).ToList(); + .ToList(); Assert.Equal(items, result); } diff --git a/tests/Perspex.Controls.UnitTests/HeaderedItemsControlTests .cs b/tests/Perspex.Controls.UnitTests/HeaderedItemsControlTests .cs new file mode 100644 index 0000000000..3eb4db00de --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/HeaderedItemsControlTests .cs @@ -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(parent => + { + return new Border + { + Child = new ContentPresenter + { + Name = "PART_HeaderPresenter", + [~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty], + } + }; + }); + } + } +} diff --git a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs b/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs index e066866a05..9cc960a5c2 100644 --- a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -7,7 +7,6 @@ using Perspex.Collections; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; using Perspex.LogicalTree; -using Perspex.Styling; using Perspex.VisualTree; using Xunit; @@ -24,10 +23,7 @@ namespace Perspex.Controls.UnitTests target.Items = new[] { "Foo" }; target.ApplyTemplate(); - var presenter = target.GetTemplateChildren().OfType().Single(); - var panel = target.GetTemplateChildren().OfType().Single(); - - Assert.Equal(target, panel.TemplatedParent); + Assert.Equal(target, target.Presenter.Panel.TemplatedParent); } [Fact] @@ -39,29 +35,57 @@ namespace Perspex.Controls.UnitTests target.Items = new[] { "Foo" }; target.ApplyTemplate(); - var presenter = target.GetTemplateChildren().OfType().Single(); - var panel = target.GetTemplateChildren().OfType().Single(); - var item = (TextBlock)panel.GetVisualChildren().First(); + var item = (TextBlock)target.Presenter.Panel.GetVisualChildren().First(); Assert.Null(item.TemplatedParent); } [Fact] - public void Control_Item_Should_Have_Parent_Set() + public void Control_Item_Should_Be_Logical_Child_Before_ApplyTemplate() { var target = new ItemsControl(); var child = new Control(); target.Template = GetTemplate(); target.Items = new[] { child }; - target.ApplyTemplate(); - Assert.Equal(target, child.Parent); - Assert.Equal(target, ((ILogical)child).LogicalParent); + Assert.Equal(child.Parent, target); + Assert.Equal(child.GetLogicalParent(), target); + Assert.Equal(new[] { child }, target.GetLogicalChildren()); + } + + [Fact] + public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate() + { + var target = new ItemsControl(); + var child = new Control(); + var items = new PerspexList(child); + + target.Template = GetTemplate(); + target.Items = items; + items.RemoveAt(0); + + Assert.Null(child.Parent); + Assert.Null(child.GetLogicalParent()); + Assert.Empty(target.GetLogicalChildren()); } [Fact] - public void Clearing_Control_Item_Should_Clear_Child_Controls_Parent() + public void Clearing_Items_Should_Clear_Child_Controls_Parent_Before_ApplyTemplate() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = GetTemplate(); + target.Items = new[] { child }; + target.Items = null; + + Assert.Null(child.Parent); + Assert.Null(((ILogical)child).LogicalParent); + } + + [Fact] + public void Clearing_Items_Should_Clear_Child_Controls_Parent() { var target = new ItemsControl(); var child = new Control(); @@ -83,9 +107,13 @@ namespace Perspex.Controls.UnitTests target.Template = GetTemplate(); target.Items = new[] { child }; + + // Should appear both before and after applying template. + Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren()); + target.ApplyTemplate(); - Assert.Equal(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren()); } [Fact] @@ -114,7 +142,7 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); target.Items = null; - Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + Assert.Equal(new ILogical[0], target.GetLogicalChildren()); } [Fact] diff --git a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs index 0f1a60b902..08c445e8df 100644 --- a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs @@ -14,7 +14,31 @@ namespace Perspex.Controls.UnitTests public class ListBoxTests { [Fact] - public void LogicalChildren_Should_Be_Set() + public void ListBoxItem_Containers_Should_Be_Generated() + { + var items = new[] { "Foo", "Bar", "Baz " }; + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = items, + }; + + target.ApplyTemplate(); + + var text = target.Presenter.Panel.Children + .OfType() + .Do(x => x.Template = ListBoxItemTemplate()) + .Do(x => x.ApplyTemplate()) + .Select(x => x.Presenter.Child) + .OfType() + .Select(x => x.Text) + .ToList(); + + Assert.Equal(items, text); + } + + [Fact] + public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items() { var target = new ListBox { @@ -103,6 +127,15 @@ namespace Perspex.Controls.UnitTests }; } + private FuncControlTemplate ListBoxItemTemplate() + { + return new FuncControlTemplate(parent => new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty], + }); + } + private Control CreateScrollViewerTemplate(ITemplatedControl parent) { return new ScrollContentPresenter diff --git a/tests/Perspex.Controls.UnitTests/PanelTests.cs b/tests/Perspex.Controls.UnitTests/PanelTests.cs index 877f7039fc..68f6075e2e 100644 --- a/tests/Perspex.Controls.UnitTests/PanelTests.cs +++ b/tests/Perspex.Controls.UnitTests/PanelTests.cs @@ -118,23 +118,6 @@ namespace Perspex.Controls.UnitTests Assert.Equal(new ILogical[0], panel.GetLogicalChildren()); } - [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 LogicalChildren => base.LogicalChildren; diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 745556a791..49ca24dba4 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -81,11 +81,13 @@ + + - + diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ContentPresenterTests.cs new file mode 100644 index 0000000000..3bc0465dc4 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/Presenters/ContentPresenterTests.cs @@ -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(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(target.Child); + + var root = new TestRoot + { + DataTemplates = new DataTemplates + { + new FuncDataTemplate(x => new Decorator()), + }, + }; + + root.Child = target; + target.ApplyTemplate(); + Assert.IsType(target.Child); + } + } +} diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index db9e6d3aec..b5fc0a0869 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -206,7 +206,8 @@ namespace Perspex.Controls.UnitTests.Presenters target.ApplyTemplate(); - Assert.Equal(panel, target.Panel); + Assert.Same(panel, target.Panel); + Assert.Same(target, target.Panel.Parent); } [Fact] diff --git a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs index 172d2320f1..4751ffbfde 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs @@ -183,21 +183,6 @@ namespace Perspex.Controls.UnitTests.Primitives } } - [Fact] - public void PopupRoot_Should_Have_Child_As_LogicalChild() - { - using (CreateServices()) - { - var target = new Popup(); - var child = new Control(); - - target.Child = child; - target.Open(); - - Assert.Equal(new[] { child }, target.PopupRoot.GetLogicalChildren()); - } - } - [Fact] public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent() { @@ -263,7 +248,6 @@ namespace Perspex.Controls.UnitTests.Primitives var globalStyles = new Mock(); globalStyles.Setup(x => x.Styles).Returns(styles); - PerspexLocator.CurrentMutable .Bind().ToTransient() .Bind().ToFunc(() => globalStyles.Object) diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs index d20b15be23..6ae55f73c2 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs @@ -3,9 +3,11 @@ using System.Collections.ObjectModel; using System.Linq; +using Moq; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; +using Perspex.LogicalTree; using Xunit; namespace Perspex.Controls.UnitTests.Primitives @@ -13,114 +15,144 @@ namespace Perspex.Controls.UnitTests.Primitives public class TabStripTests { [Fact] - public void First_Tab_Should_Be_Selected_By_Default() + public void Header_Of_IHeadered_Items_Should_Be_Used() { + var items = new[] + { + Mock.Of(x => x.Header == "foo"), + Mock.Of(x => x.Header == "bar"), + }; + var target = new TabStrip { Template = new FuncControlTemplate(CreateTabStripTemplate), - Items = new[] - { - new TabItem - { - Name = "first" - }, - new TabItem - { - Name = "second" - }, - } + Items = items, }; target.ApplyTemplate(); - Assert.Equal(0, target.SelectedIndex); - Assert.Equal(target.Items.Cast().First(), target.SelectedItem); - Assert.Equal(target.Items.Cast().First(), target.SelectedTab); + var result = target.GetLogicalChildren() + .OfType() + .Select(x => x.Content) + .ToList(); + + Assert.Equal(new[] { "foo", "bar" }, result); } [Fact] - public void Setting_SelectedItem_Should_Set_SelectedTab() + public void Data_Of_Non_IHeadered_Items_Should_Be_Used() { + var items = new[] + { + "foo", + "bar" + }; + var target = new TabStrip { Template = new FuncControlTemplate(CreateTabStripTemplate), - Items = new[] - { - new TabItem - { - Name = "first" - }, - new TabItem - { - Name = "second" - }, - } + Items = items, }; target.ApplyTemplate(); - target.SelectedItem = target.Items.Cast().ElementAt(1); - Assert.Same(target.SelectedTab, target.SelectedItem); + var result = target.GetLogicalChildren() + .OfType() + .Select(x => x.Content) + .ToList(); + + Assert.Equal(new[] { "foo", "bar" }, result); } [Fact] - public void Setting_SelectedTab_Should_Set_SelectedItem() + public void First_Tab_Should_Be_Selected_By_Default() { + var items = new[] + { + new TabItem + { + Name = "first" + }, + new TabItem + { + Name = "second" + }, + }; + var target = new TabStrip { Template = new FuncControlTemplate(CreateTabStripTemplate), - Items = new[] + Items = items, + }; + + target.ApplyTemplate(); + + Assert.Equal(0, target.SelectedIndex); + Assert.Same(items[0], target.SelectedItem); + } + + [Fact] + public void Setting_SelectedItem_Should_Set_Selection() + { + var items = new[] + { + new TabItem + { + Name = "first" + }, + new TabItem { - new TabItem - { - Name = "first" - }, - new TabItem - { - Name = "second" - }, - } + Name = "second" + }, + }; + + var target = new TabStrip + { + Template = new FuncControlTemplate(CreateTabStripTemplate), + Items = items, + SelectedItem = items[1], }; target.ApplyTemplate(); - target.SelectedTab = target.Items.Cast().ElementAt(1); - Assert.Same(target.SelectedItem, target.SelectedTab); + Assert.Equal(1, target.SelectedIndex); + Assert.Same(items[1], target.SelectedItem); } [Fact] public void Removing_Selected_Should_Select_Next() { - var list = new ObservableCollection() + var items = new ObservableCollection() + { + new TabItem + { + Name = "first" + }, + new TabItem { - new TabItem - { - Name = "first" - }, - new TabItem - { - Name = "second" - }, - new TabItem - { - Name = "3rd" - }, - }; + Name = "second" + }, + new TabItem + { + Name = "3rd" + }, + }; var target = new TabStrip { Template = new FuncControlTemplate(CreateTabStripTemplate), - Items = list + Items = items }; target.ApplyTemplate(); - target.SelectedTab = list[1]; - Assert.Same(list[1], target.SelectedTab); - list.RemoveAt(1); + target.SelectedItem = items[1]; + Assert.Same(items[1], target.SelectedItem); + items.RemoveAt(1); // Assert for former element [2] now [1] == "3rd" - Assert.Same(list[1], target.SelectedTab); - Assert.Same("3rd", target.SelectedTab.Name); + Assert.Equal(1, target.SelectedIndex); + Assert.Same(items[1], target.SelectedItem); + Assert.Same("3rd", ((TabItem)target.SelectedItem).Name); } private Control CreateTabStripTemplate(TabStrip parent) @@ -128,7 +160,8 @@ namespace Perspex.Controls.UnitTests.Primitives return new ItemsPresenter { Name = "itemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + [!ItemsPresenter.ItemsProperty] = parent[!ItemsControl.ItemsProperty], + [!ItemsPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], }; } } diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index d0f0547854..40d8e1c0c0 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -118,27 +118,6 @@ namespace Perspex.Controls.UnitTests.Primitives Assert.Equal(target, child.GetLogicalParent()); } - [Fact] - public void Templated_Child_Should_Have_ApplyTemplate_Called_With_Logical_Then_Visual_Parent() - { - var target = new TemplatedControl - { - Template = new FuncControlTemplate(_ => new ApplyTemplateTracker()) - }; - - target.ApplyTemplate(); - - var child = (ApplyTemplateTracker)target.GetVisualChildren().Single(); - - Assert.Equal( - new[] - { - new Tuple(null, target), - new Tuple(target, target), - }, - child.Invocations); - } - [Fact] public void Nested_TemplatedControls_Should_Be_Expanded_And_Have_Correct_TemplatedParent() { @@ -186,6 +165,76 @@ namespace Perspex.Controls.UnitTests.Primitives templatedParents); } + + + [Fact] + public void Nested_TemplatedControls_Should_Register_With_Correct_NameScope() + { + var target = new ContentControl + { + Template = new FuncControlTemplate(ScrollingContentControlTemplate), + Content = "foo" + }; + + target.ApplyTemplate(); + + var border = target.GetVisualChildren().FirstOrDefault(); + Assert.IsType(border); + var scrollViewer = border.GetVisualChildren().FirstOrDefault(); + Assert.IsType(scrollViewer); + var scrollContentPresenter = scrollViewer.GetVisualChildren().FirstOrDefault(); + Assert.IsType(scrollContentPresenter); + var contentPresenter = scrollContentPresenter.GetVisualChildren().FirstOrDefault(); + Assert.IsType(contentPresenter); + + var borderNs = NameScope.GetNameScope((Control)border); + var scrollContentPresenterNs = NameScope.GetNameScope((Control)scrollContentPresenter); + Assert.NotNull(borderNs); + Assert.Same(scrollViewer, borderNs.Find("ScrollViewer")); + Assert.Same(contentPresenter, borderNs.Find("PART_ContentPresenter")); + Assert.Same(scrollContentPresenter, scrollContentPresenterNs.Find("PART_ContentPresenter")); + } + + [Fact] + public void ApplyTemplate_Should_Raise_TemplateApplied() + { + var target = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => new Decorator()) + }; + + var raised = false; + + target.TemplateApplied += (s, e) => + { + Assert.Equal(TemplatedControl.TemplateAppliedEvent, e.RoutedEvent); + Assert.Same(target, e.Source); + Assert.NotNull(e.NameScope); + raised = true; + }; + + target.ApplyTemplate(); + + Assert.True(raised); + } + + private static IControl ScrollingContentControlTemplate(ContentControl control) + { + return new Border + { + Child = new ScrollViewer + { + Template = new FuncControlTemplate(ScrollViewerTemplate), + Name = "ScrollViewer", + Content = new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = control[!ContentControl.ContentProperty], + } + } + }; + } + private static IControl ItemsControlTemplate(ItemsControl control) { return new Border @@ -195,9 +244,9 @@ namespace Perspex.Controls.UnitTests.Primitives Template = new FuncControlTemplate(ScrollViewerTemplate), Content = new ItemsPresenter { - Name = "itemsPresenter", - [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], - [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], + Name = "PART_ItemsPresenter", + [!ItemsPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], + [!ItemsPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], } } }; @@ -207,7 +256,7 @@ namespace Perspex.Controls.UnitTests.Primitives { var result = new ScrollContentPresenter { - Name = "contentPresenter", + Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], }; diff --git a/tests/Perspex.Controls.UnitTests/TabControlTests.cs b/tests/Perspex.Controls.UnitTests/TabControlTests.cs index 541c1c8cd8..0f3172e715 100644 --- a/tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/TabControlTests.cs @@ -7,6 +7,7 @@ using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.LogicalTree; +using Perspex.Styling; using Xunit; namespace Perspex.Controls.UnitTests @@ -37,60 +38,34 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); + Assert.Equal(0, target.SelectedIndex); Assert.Equal(selected, target.SelectedItem); - Assert.Equal(selected, target.SelectedTab); } [Fact] - public void Setting_SelectedItem_Should_Set_SelectedTab() + public void Logical_Children_Should_Be_TabItems() { - var target = new TabControl + var items = new[] { - Template = new FuncControlTemplate(CreateTabControlTemplate), - Items = new[] + new TabItem { - new TabItem - { - Name = "first", - Content = "foo", - }, - new TabItem - { - Name = "second", - Content = "bar", - }, - } + Content = "foo" + }, + new TabItem + { + Content = "bar" + }, }; - target.ApplyTemplate(); - target.SelectedItem = target.Items.Cast().ElementAt(1); - - Assert.Same(target.SelectedTab, target.SelectedItem); - } - - [Fact] - public void Logical_Child_Should_Be_Selected_Tab_Content() - { var target = new TabControl { Template = new FuncControlTemplate(CreateTabControlTemplate), - Items = new[] - { - new TabItem - { - Content = "foo" - }, - new TabItem - { - Content = "bar" - }, - }, + Items = items, }; + Assert.Equal(items, target.GetLogicalChildren()); target.ApplyTemplate(); - - Assert.Equal(1, target.GetLogicalChildren().Count()); - Assert.Equal("foo", ((TextBlock)target.GetLogicalChildren().First()).Text); + Assert.Equal(items, target.GetLogicalChildren()); } [Fact] @@ -127,7 +102,60 @@ namespace Perspex.Controls.UnitTests // compare with former [2] now [1] == "3rd" Assert.Same(collection[1], target.SelectedItem); - Assert.Same(target.SelectedTab, target.SelectedItem); + } + + + [Fact] + public void TabItem_Templates_Should_Be_Set_Before_TabItem_ApplyTemplate() + { + var collection = new[] + { + new TabItem + { + Name = "first", + Content = "foo", + }, + new TabItem + { + Name = "second", + Content = "bar", + }, + new TabItem + { + Name = "3rd", + Content = "barf", + }, + }; + + var template = new FuncControlTemplate(x => new Decorator()); + + using (PerspexLocator.EnterScope()) + { + PerspexLocator.CurrentMutable.Bind().ToConstant(new Styler()); + + var root = new TestRoot + { + Styles = new Styles + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(TemplatedControl.TemplateProperty, template) + } + } + }, + Child = new TabControl + { + Template = new FuncControlTemplate(CreateTabControlTemplate), + Items = collection, + } + }; + } + + Assert.Same(collection[0].Template, template); + Assert.Same(collection[1].Template, template); + Assert.Same(collection[2].Template, template); } [Fact] @@ -155,26 +183,62 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); - var dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; + var carousel = (Carousel)target.Pages; + + var dataContext = ((TextBlock)carousel.Presenter.Panel.GetLogicalChildren().Single()).DataContext; Assert.Equal(items[0], dataContext); target.SelectedIndex = 1; - dataContext = ((Button)target.GetLogicalChildren().Single()).DataContext; + dataContext = ((Button)carousel.Presenter.Panel.GetLogicalChildren().Single()).DataContext; Assert.Equal(items[1], dataContext); target.SelectedIndex = 2; - dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; + dataContext = ((TextBlock)carousel.Presenter.Panel.GetLogicalChildren().Single()).DataContext; Assert.Equal("Base", dataContext); target.SelectedIndex = 3; - dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; + dataContext = ((TextBlock)carousel.Presenter.Panel.GetLogicalChildren().Single()).DataContext; Assert.Equal("Qux", dataContext); target.SelectedIndex = 4; - dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext; + dataContext = ((TextBlock)carousel.Presenter.Panel.GetLogicalChildren().Single()).DataContext; Assert.Equal("Base", dataContext); } + /// + /// Non-headered control items should result in TabStripItems with empty content. + /// + /// + /// If a TabStrip is created with non IHeadered controls as its items, don't try to + /// display the control in the TabStripItem: if the TabStrip is part of a TabControl + /// then *that* will also try to display the control, resulting in dual-parentage + /// breakage. + /// + [Fact] + public void Non_IHeadered_Control_Items_Should_Be_Ignored() + { + var items = new[] + { + new TextBlock { Text = "foo" }, + new TextBlock { Text = "bar" }, + }; + + var target = new TabControl + { + Template = new FuncControlTemplate(CreateTabControlTemplate), + Items = items, + }; + + target.ApplyTemplate(); + + var result = target.TabStrip.GetLogicalChildren() + .OfType() + .Select(x => x.Content) + .ToList(); + + Assert.Equal(new object[] { string.Empty, string.Empty }, result); + } + private Control CreateTabControlTemplate(TabControl parent) { return new StackPanel @@ -183,18 +247,19 @@ namespace Perspex.Controls.UnitTests { new TabStrip { - Name = "tabStrip", + Name = "PART_TabStrip", Template = new FuncControlTemplate(CreateTabStripTemplate), - [!ItemsControl.ItemsProperty] = parent[!ItemsControl.ItemsProperty], - [!!TabStrip.SelectedTabProperty] = parent[!!TabControl.SelectedTabProperty] + MemberSelector = TabControl.HeaderSelector, + [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty] }, new Carousel { - Name = "carousel", + Name = "PART_Content", Template = new FuncControlTemplate(CreateCarouselTemplate), - MemberSelector = parent.ContentSelector, - [!ItemsControl.ItemsProperty] = parent[!ItemsControl.ItemsProperty], - [!SelectingItemsControl.SelectedItemProperty] = parent[!SelectingItemsControl.SelectedItemProperty], + MemberSelector = TabControl.ContentSelector, + [!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty], } } }; @@ -204,8 +269,9 @@ namespace Perspex.Controls.UnitTests { return new ItemsPresenter { - Name = "itemsPresenter", + Name = "PART_ItemsPresenter", [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + [!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], }; } @@ -213,7 +279,7 @@ namespace Perspex.Controls.UnitTests { return new CarouselPresenter { - Name = "itemsPresenter", + Name = "PART_ItemsPresenter", [!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], [!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], [!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty], diff --git a/tests/Perspex.Controls.UnitTests/TestRoot.cs b/tests/Perspex.Controls.UnitTests/TestRoot.cs index 10973f7c9c..ea72005719 100644 --- a/tests/Perspex.Controls.UnitTests/TestRoot.cs +++ b/tests/Perspex.Controls.UnitTests/TestRoot.cs @@ -6,10 +6,11 @@ using Moq; using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; +using Perspex.Styling; namespace Perspex.Controls.UnitTests { - internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot + internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot, IStyleRoot { public Size ClientSize => new Size(100, 100); diff --git a/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs index 8cbf1dca3f..56f2ca8b3b 100644 --- a/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs @@ -9,13 +9,14 @@ namespace Perspex.Controls.UnitTests { public bool OnTemplateAppliedCalled { get; private set; } - public new void AddVisualChild(IVisual visual) + public void AddVisualChild(IVisual visual) { - base.AddVisualChild(visual); + VisualChildren.Add(visual); } - protected override void OnTemplateApplied(INameScope nameScope) + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { + base.OnTemplateApplied(e); OnTemplateAppliedCalled = true; } } diff --git a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs index 1e8ffc7a48..06337d9412 100644 --- a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs @@ -42,7 +42,7 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); - var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single(); + var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; var header = (TextBlock)container.Header; Assert.Equal("Root", header.Text); } diff --git a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs index 3a3d47947a..d7c937f597 100644 --- a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs +++ b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs @@ -349,7 +349,7 @@ namespace Perspex.Interactivity.UnitTests set { - AddVisualChildren(value.Cast()); + VisualChildren.AddRange(value.Cast()); } } diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj index ba2450f8f9..b2d67554c3 100644 --- a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -88,7 +88,6 @@ - diff --git a/tests/Perspex.LeakTests/StyleTests.cs b/tests/Perspex.LeakTests/StyleTests.cs deleted file mode 100644 index ae74048ae9..0000000000 --- a/tests/Perspex.LeakTests/StyleTests.cs +++ /dev/null @@ -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 run = () => - { - var window = new Window - { - Styles = new Styles - { - new Style(x => x.OfType().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(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()).ObjectsCount)); - } - - [Fact] - public void Changing_Carousel_SelectedIndex_Should_Not_Leak_StyleActivators() - { - Func run = () => - { - Carousel target; - - var window = new Window - { - Styles = new Styles - { - new Style(x => x.OfType().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(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()).ObjectsCount)); - } - } -} diff --git a/tests/Perspex.Markup.UnitTests/TestRoot.cs b/tests/Perspex.Markup.UnitTests/TestRoot.cs index 3649493f0c..26ac0ed0da 100644 --- a/tests/Perspex.Markup.UnitTests/TestRoot.cs +++ b/tests/Perspex.Markup.UnitTests/TestRoot.cs @@ -5,10 +5,11 @@ using System; using Perspex.Controls; using Perspex.Platform; using Perspex.Rendering; +using Perspex.Styling; namespace Perspex.Markup.UnitTests { - public class TestRoot : Decorator, IRenderRoot, INameScope + public class TestRoot : Decorator, IRenderRoot, INameScope, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs index f1565fb457..270b6d3d9b 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive; using Moq; using OmniXaml; using OmniXaml.ObjectAssembler.Commands; @@ -95,6 +96,8 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters { get { throw new NotImplementedException(); } } + + IObservable IStyleable.StyleDetach { get; } } private class AttachedOwner diff --git a/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs b/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs index b9db948265..e172e2ccb7 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs @@ -5,10 +5,11 @@ using System; using Perspex.Controls; using Perspex.Platform; using Perspex.Rendering; +using Perspex.Styling; namespace Perspex.Markup.Xaml.UnitTests { - public class TestRoot : Decorator, IRenderRoot, INameScope + public class TestRoot : Decorator, IRenderRoot, INameScope, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); diff --git a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs index 88f47d7876..21a4427fbc 100644 --- a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs +++ b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs @@ -20,8 +20,6 @@ namespace Perspex.SceneGraph.UnitTests public class TestVisual : Visual { - public new PerspexObject InheritanceParent => base.InheritanceParent; - public IVisual Child { get @@ -33,34 +31,34 @@ namespace Perspex.SceneGraph.UnitTests { if (Child != null) { - RemoveVisualChild(Child); + VisualChildren.Remove(Child); } if (value != null) { - AddVisualChild(value); + VisualChildren.Add(value); } } } public void AddChild(Visual v) { - AddVisualChild(v); + VisualChildren.Add(v); } public void AddChildren(IEnumerable v) { - AddVisualChildren(v); + VisualChildren.AddRange(v); } public void RemoveChild(Visual v) { - RemoveVisualChild(v); + VisualChildren.Remove(v); } public void ClearChildren() { - ClearVisualChildren(); + VisualChildren.Clear(); } } } diff --git a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs index f4785b66c1..00d8c01d0d 100644 --- a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs +++ b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs @@ -23,17 +23,6 @@ namespace Perspex.SceneGraph.UnitTests Assert.Equal(target, child.GetVisualParent()); } - [Fact] - public void Added_Child_Should_Have_InheritanceParent_Set() - { - var target = new TestVisual(); - var child = new TestVisual(); - - target.AddChild(child); - - Assert.Equal(target, child.InheritanceParent); - } - [Fact] public void Added_Child_Should_Notify_VisualParent_Changed() { @@ -60,18 +49,6 @@ namespace Perspex.SceneGraph.UnitTests Assert.Null(child.GetVisualParent()); } - [Fact] - public void Removed_Child_Should_Have_InheritanceParent_Cleared() - { - var target = new TestVisual(); - var child = new TestVisual(); - - target.AddChild(child); - target.RemoveChild(child); - - Assert.Null(child.InheritanceParent); - } - [Fact] public void Clearing_Children_Should_Clear_VisualParent() { diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 89419b0b17..8a579223b3 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; @@ -83,6 +84,8 @@ namespace Perspex.Styling.UnitTests public string Name { get; set; } + public bool IsAttachedToLogicalTree { get; } + public IPerspexReadOnlyList LogicalChildren { get; set; } public ILogical LogicalParent { get; set; } @@ -91,6 +94,8 @@ namespace Perspex.Styling.UnitTests public ITemplatedControl TemplatedParent { get; } + IObservable IStyleable.StyleDetach { get; } + public IPropertyBag InheritanceParent { get diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 80c3e4f370..f7ce2beaa9 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; @@ -113,6 +114,8 @@ namespace Perspex.Styling.UnitTests public string Name { get; set; } + public bool IsAttachedToLogicalTree { get; } + public IPerspexReadOnlyList LogicalChildren { get; set; } public ILogical LogicalParent { get; set; } @@ -131,6 +134,8 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; + IObservable IStyleable.StyleDetach { get; } + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); diff --git a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs index 3b444ec77f..9669904af6 100644 --- a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs +++ b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs @@ -17,7 +17,7 @@ namespace Perspex.Styling.UnitTests { var scheduler = new TestScheduler(); var source = scheduler.CreateColdObservable(); - var target = new StyleActivator(new[] { source }, ActivatorMode.And); + var target = StyleActivator.And(new[] { source }); Assert.Equal(0, source.Subscriptions.Count); target.Subscribe(_ => { }); @@ -29,7 +29,7 @@ namespace Perspex.Styling.UnitTests { var scheduler = new TestScheduler(); var source = scheduler.CreateColdObservable(); - var target = new StyleActivator(new[] { source }, ActivatorMode.And); + var target = StyleActivator.And(new[] { source }); var dispose = target.Subscribe(_ => { }); Assert.Equal(1, source.Subscriptions.Count); @@ -44,7 +44,7 @@ namespace Perspex.Styling.UnitTests public void Activator_And_Should_Follow_Single_Input() { var inputs = new[] { new TestSubject(false) }; - var target = new StyleActivator(inputs, ActivatorMode.And); + var target = StyleActivator.And(inputs); var result = new TestObserver(); target.Subscribe(result); @@ -68,7 +68,7 @@ namespace Perspex.Styling.UnitTests new TestSubject(false), new TestSubject(true), }; - var target = new StyleActivator(inputs, ActivatorMode.And); + var target = StyleActivator.And(inputs); var result = new TestObserver(); target.Subscribe(result); @@ -85,7 +85,7 @@ namespace Perspex.Styling.UnitTests } [Fact] - public void Activator_And_Should_Not_Unsubscribe_All_When_Input_Completes_On_True() + public void Activator_And_Should_Complete_When_Input_Completes_On_False() { var inputs = new[] { @@ -93,24 +93,22 @@ namespace Perspex.Styling.UnitTests new TestSubject(false), new TestSubject(true), }; - var target = new StyleActivator(inputs, ActivatorMode.And); + var target = StyleActivator.And(inputs); var result = new TestObserver(); + var completed = false; - target.Subscribe(result); - Assert.False(result.GetValue()); - inputs[0].OnNext(true); + target.Subscribe(_ => { }, () => completed = true); + inputs[0].OnNext(false); inputs[0].OnCompleted(); - Assert.Equal(0, inputs[0].SubscriberCount); - Assert.Equal(1, inputs[1].SubscriberCount); - Assert.Equal(1, inputs[2].SubscriberCount); + Assert.True(completed); } [Fact] public void Activator_Or_Should_Follow_Single_Input() { var inputs = new[] { new TestSubject(false) }; - var target = new StyleActivator(inputs, ActivatorMode.Or); + var target = StyleActivator.Or(inputs); var result = new TestObserver(); target.Subscribe(result); @@ -134,7 +132,7 @@ namespace Perspex.Styling.UnitTests new TestSubject(false), new TestSubject(true), }; - var target = new StyleActivator(inputs, ActivatorMode.Or); + var target = StyleActivator.Or(inputs); var result = new TestObserver(); target.Subscribe(result); @@ -158,7 +156,7 @@ namespace Perspex.Styling.UnitTests new TestSubject(false), new TestSubject(true), }; - var target = new StyleActivator(inputs, ActivatorMode.Or); + var target = StyleActivator.Or(inputs); var result = new TestObserver(); target.Subscribe(result); @@ -180,7 +178,7 @@ namespace Perspex.Styling.UnitTests Observable.Return(false), }; - var target = new StyleActivator(inputs, ActivatorMode.Or); + var target = StyleActivator.Or(inputs); var completed = false; target.Subscribe(_ => { }, () => completed = true); diff --git a/tests/Perspex.Styling.UnitTests/StyleTests.cs b/tests/Perspex.Styling.UnitTests/StyleTests.cs index c484389cbf..f915f4169c 100644 --- a/tests/Perspex.Styling.UnitTests/StyleTests.cs +++ b/tests/Perspex.Styling.UnitTests/StyleTests.cs @@ -188,7 +188,7 @@ namespace Perspex.Styling.UnitTests } [Fact] - public void Style_Should_Detach_When_Removed_From_Visual_Tree() + public void Style_Should_Detach_When_Removed_From_Logical_Tree() { Border border; diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index 64a0b67b2c..82ab942a09 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive; using System.Reactive.Subjects; using Perspex.Collections; using Perspex.Controls; @@ -40,6 +41,8 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; + IObservable IStyleable.StyleDetach { get; } + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); diff --git a/tests/Perspex.Styling.UnitTests/TestRoot.cs b/tests/Perspex.Styling.UnitTests/TestRoot.cs index 2f613e4e51..3223a033c6 100644 --- a/tests/Perspex.Styling.UnitTests/TestRoot.cs +++ b/tests/Perspex.Styling.UnitTests/TestRoot.cs @@ -10,7 +10,7 @@ using Perspex.Rendering; namespace Perspex.Styling.UnitTests { - internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot + internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot, IStyleRoot { public Size ClientSize => new Size(100, 100); diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index f8d576d4de..1348b1d662 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Reactive; using System.Reactive.Subjects; using Perspex.Collections; using Perspex.Controls; @@ -46,6 +47,8 @@ namespace Perspex.Styling.UnitTests IPerspexReadOnlyList IStyleable.Classes => Classes; + IObservable IStyleable.StyleDetach { get; } + public IObservable GetObservable(PerspexProperty property) { throw new NotImplementedException();