diff --git a/Perspex.Animation/PropertyTransitions.cs b/Perspex.Animation/PropertyTransitions.cs index ff902d816d..bf251404d4 100644 --- a/Perspex.Animation/PropertyTransitions.cs +++ b/Perspex.Animation/PropertyTransitions.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Collections; + namespace Perspex.Animation { using Perspex.Collections; diff --git a/Perspex.Base/Collections/IPerspexList.cs b/Perspex.Base/Collections/IPerspexList.cs new file mode 100644 index 0000000000..6d958f07fb --- /dev/null +++ b/Perspex.Base/Collections/IPerspexList.cs @@ -0,0 +1,15 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Collections +{ + using System.Collections; + using System.Collections.Generic; + + public interface IPerspexList : IList, IList, IReadOnlyPerspexList + { + } +} \ No newline at end of file diff --git a/Perspex.Base/Collections/PerspexList.cs b/Perspex.Base/Collections/PerspexList.cs index 8107202560..b0775bd4a9 100644 --- a/Perspex.Base/Collections/PerspexList.cs +++ b/Perspex.Base/Collections/PerspexList.cs @@ -13,7 +13,17 @@ namespace Perspex.Collections using System.ComponentModel; using System.Linq; - public class PerspexList : IList, IList, IReadOnlyPerspexList, INotifyCollectionChanged, INotifyPropertyChanged + /// + /// A notifying list. + /// + /// The type of the list items. + /// + /// PerspexList is similar to + /// except that when the method is called, it notifies with a + /// action, passing the items that were + /// removed. + /// + public class PerspexList : IPerspexList, INotifyCollectionChanged, INotifyPropertyChanged { private List inner; diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs new file mode 100644 index 0000000000..dd5f8af61b --- /dev/null +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -0,0 +1,93 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Collections +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Linq; + + public class PerspexReadOnlyListView : IReadOnlyPerspexList, IDisposable + { + private IReadOnlyPerspexList inner; + + private Func convert; + + public PerspexReadOnlyListView( + IReadOnlyPerspexList inner, + Func convert) + { + this.inner = inner; + this.convert = convert; + this.inner.CollectionChanged += this.InnerCollectionChanged; + } + + public TOut this[int index] + { + get { return this.convert(this.inner[index]); } + } + + public int Count + { + get { return this.inner.Count; } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public void Dispose() + { + this.inner.CollectionChanged -= this.InnerCollectionChanged; + } + + public IEnumerator GetEnumerator() + { + return this.inner.Select(convert).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + private IList ConvertList(IList list) + { + return list.Cast().Select(this.convert).ToList(); + } + + private void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + NotifyCollectionChangedEventArgs ev; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + this.ConvertList(e.NewItems), + e.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Remove: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + this.ConvertList(e.OldItems), + e.NewStartingIndex); + break; + default: + throw new NotSupportedException("Action not yet implemented."); + } + + this.CollectionChanged(this, ev); + } + } + } +} diff --git a/Perspex.Base/Collections/SingleItemPerspexList.cs b/Perspex.Base/Collections/SingleItemPerspexList.cs new file mode 100644 index 0000000000..5af1e68caa --- /dev/null +++ b/Perspex.Base/Collections/SingleItemPerspexList.cs @@ -0,0 +1,113 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Collections +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + using System.Linq; + + /// + /// Implements the interface for single items. + /// + /// The type of the single item. + /// + /// Classes such as Border can only ever have a single logical child, but they need to + /// implement a list of logical children in their ILogical.LogicalChildren property using the + /// interface. This class facilitates that + /// without creating an actual . + /// + public class SingleItemPerspexList : IReadOnlyPerspexList where T : class + { + private T item; + + public SingleItemPerspexList() + { + } + + public SingleItemPerspexList(T item) + { + this.item = item; + } + + public T this[int index] + { + get + { + if (index < 0 || index >= this.Count) + { + throw new ArgumentOutOfRangeException(); + } + + return item; + } + } + + public int Count + { + get { return this.item != null ? 1 : 0; } + } + + public T SingleItem + { + get + { + return this.item; + } + + set + { + NotifyCollectionChangedEventArgs e = null; + bool countChanged = false; + + if (value == null && this.item != null ) + { + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, this.item, 0); + this.item = null; + countChanged = true; + } + else if (value != null && this.item == null) + { + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, this.item, 0); + this.item = value; + countChanged = true; + } + else + { + e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, this.item, 0); + this.item = value; + } + + if (e != null && this.CollectionChanged != null) + { + this.CollectionChanged(this, e); + } + + if (countChanged && this.PropertyChanged != null) + { + this.PropertyChanged(this, new PropertyChangedEventArgs("Count")); + } + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public IEnumerator GetEnumerator() + { + return Enumerable.Repeat(this.item, this.Count).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index 1f1414e13d..6959c65d9d 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -35,13 +35,16 @@ + + + + - diff --git a/Perspex.Controls.UnitTests/ContentControlTests.cs b/Perspex.Controls.UnitTests/ContentControlTests.cs index 67dc838d4f..9eee0c3fd6 100644 --- a/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/Perspex.Controls.UnitTests/ContentControlTests.cs @@ -7,6 +7,7 @@ namespace Perspex.Controls.UnitTests { using System; + using System.Collections.Specialized; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -15,6 +16,7 @@ namespace Perspex.Controls.UnitTests using Perspex.Layout; using Perspex.Platform; using Perspex.Styling; + using Perspex.VisualTree; using Ploeh.AutoFixture; using Ploeh.AutoFixture.AutoMoq; using Splat; @@ -56,7 +58,7 @@ namespace Perspex.Controls.UnitTests target.Template = this.GetTemplate(); root.Content = target; - this.ApplyTemplate(target); + target.ApplyTemplate(); styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); @@ -73,7 +75,7 @@ namespace Perspex.Controls.UnitTests target.Template = this.GetTemplate(); target.Content = child; - this.ApplyTemplate(target); + target.ApplyTemplate(); var contentPresenter = child.GetVisualParent(); Assert.AreEqual(target, contentPresenter.TemplatedParent); @@ -87,14 +89,142 @@ namespace Perspex.Controls.UnitTests target.Template = this.GetTemplate(); target.Content = child; - this.ApplyTemplate(target); + target.ApplyTemplate(); Assert.IsNull(child.TemplatedParent); } - private void ApplyTemplate(ILayoutable control) + [TestMethod] + public void Setting_Content_Should_Set_Child_Controls_Parent() + { + var target = new ContentControl(); + var child = new Control(); + + target.Content = child; + + Assert.AreEqual(child.Parent, target); + Assert.AreEqual(((ILogical)child).LogicalParent, target); + } + + [TestMethod] + public void Clearing_Content_Should_Clear_Child_Controls_Parent() + { + var target = new ContentControl(); + var child = new Control(); + + target.Content = child; + target.Content = null; + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Setting_Content_To_Control_Should_Make_Control_Appear_In_LogicalChildren() + { + var target = new ContentControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Content = child; + target.ApplyTemplate(); + + CollectionAssert.AreEqual(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Content_To_String_Should_Make_TextBlock_Appear_In_LogicalChildren() + { + var target = new ContentControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Content = "Foo"; + target.ApplyTemplate(); + + var logical = (ILogical)target; + Assert.AreEqual(1, logical.LogicalChildren.Count); + Assert.IsInstanceOfType(logical.LogicalChildren[0], typeof(TextBlock)); + } + + [TestMethod] + public void Clearing_Content_Should_Remove_From_LogicalChildren() + { + var contentControl = new ContentControl(); + var child = new Control(); + + contentControl.Content = child; + contentControl.Content = null; + + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)contentControl).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() { - control.Measure(new Size(100, 100)); + var contentControl = new ContentControl(); + var child = new Control(); + var called = false; + + ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + contentControl.Template = this.GetTemplate(); + contentControl.Content = child; + contentControl.ApplyTemplate(); + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var contentControl = new ContentControl(); + var child = new Control(); + var called = false; + + contentControl.Template = this.GetTemplate(); + contentControl.Content = child; + contentControl.ApplyTemplate(); + + ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + contentControl.Content = null; + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var contentControl = new ContentControl(); + var child1 = new Control(); + var child2 = new Control(); + var called = false; + + contentControl.Template = this.GetTemplate(); + contentControl.Content = child1; + contentControl.ApplyTemplate(); + + ((ILogical)contentControl).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Replace; + + contentControl.Content = child2; + + // Need to call ApplyTemplate on presenter for CollectionChanged to be called. + var presenter = contentControl.GetTemplateControls().Single(x => x.Id == "contentPresenter"); + presenter.ApplyTemplate(); + + Assert.IsTrue(called); } private ControlTemplate GetTemplate() @@ -106,6 +236,7 @@ namespace Perspex.Controls.UnitTests Background = new Perspex.Media.SolidColorBrush(0xffffffff), Content = new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], } }; diff --git a/Perspex.Controls.UnitTests/DecoratorTests.cs b/Perspex.Controls.UnitTests/DecoratorTests.cs new file mode 100644 index 0000000000..ae6a82d98e --- /dev/null +++ b/Perspex.Controls.UnitTests/DecoratorTests.cs @@ -0,0 +1,114 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System.Collections.Specialized; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DecoratorTests + { + [TestMethod] + public void Setting_Content_Should_Set_Child_Controls_Parent() + { + var decorator = new Decorator(); + var child = new Control(); + + decorator.Content = child; + + Assert.AreEqual(child.Parent, decorator); + Assert.AreEqual(((ILogical)child).LogicalParent, decorator); + } + + [TestMethod] + public void Clearing_Content_Should_Clear_Child_Controls_Parent() + { + var decorator = new Decorator(); + var child = new Control(); + + decorator.Content = child; + decorator.Content = null; + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Content_Control_Should_Appear_In_LogicalChildren() + { + var decorator = new Decorator(); + var child = new Control(); + + decorator.Content = child; + + CollectionAssert.AreEqual(new[] { child }, ((ILogical)decorator).LogicalChildren.ToList()); + } + + [TestMethod] + public void Clearing_Content_Should_Remove_From_LogicalChildren() + { + var decorator = new Decorator(); + var child = new Control(); + + decorator.Content = child; + decorator.Content = null; + + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)decorator).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var decorator = new Decorator(); + var child = new Control(); + var called = false; + + ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + decorator.Content = child; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Clearing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var decorator = new Decorator(); + var child = new Control(); + var called = false; + + decorator.Content = child; + + ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + decorator.Content = null; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Changing_Content_Should_Fire_LogicalChildren_CollectionChanged() + { + var decorator = new Decorator(); + var child1 = new Control(); + var child2 = new Control(); + var called = false; + + decorator.Content = child1; + + ((ILogical)decorator).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Replace; + + decorator.Content = child2; + + Assert.IsTrue(called); + } + } +} diff --git a/Perspex.Controls.UnitTests/ItemsControlTests.cs b/Perspex.Controls.UnitTests/ItemsControlTests.cs new file mode 100644 index 0000000000..494d226ee8 --- /dev/null +++ b/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -0,0 +1,294 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System; + using System.Collections.Specialized; + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Perspex.Collections; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Layout; + using Perspex.Platform; + using Perspex.Styling; + using Perspex.VisualTree; + using Ploeh.AutoFixture; + using Ploeh.AutoFixture.AutoMoq; + using Splat; + + [TestClass] + public class ItemsControlTests + { + [TestMethod] + public void Template_Should_Be_Instantiated() + { + using (var ctx = this.RegisterServices()) + { + var target = new ItemsControl(); + target.Items = new[] { "Foo" }; + target.Template = this.GetTemplate(); + + target.Measure(new Size(100, 100)); + + var child = ((IVisual)target).VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(Border)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(ItemsPresenter)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(StackPanel)); + child = child.VisualChildren.Single(); + Assert.IsInstanceOfType(child, typeof(TextBlock)); + } + } + + [TestMethod] + public void Templated_Children_Should_Be_Styled() + { + using (var ctx = this.RegisterServices()) + { + var root = new TestRoot(); + var target = new ItemsControl(); + var styler = new Mock(); + + Locator.CurrentMutable.Register(() => styler.Object, typeof(IStyler)); + target.Items = new[] { "Foo" }; + target.Template = this.GetTemplate(); + root.Content = target; + + target.ApplyTemplate(); + + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + } + } + + [TestMethod] + public void ItemsPresenter_And_Panel_Should_Have_TemplatedParent_Set() + { + var target = new ItemsControl(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var presenter = target.GetTemplateControls().OfType().Single(); + var panel = target.GetTemplateControls().OfType().Single(); + + Assert.AreEqual(target, presenter.TemplatedParent); + Assert.AreEqual(target, panel.TemplatedParent); + } + + [TestMethod] + public void Item_Should_Have_TemplatedParent_Set_To_Null() + { + var target = new ItemsControl(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var panel = target.GetTemplateControls().OfType().Single(); + var item = (TextBlock)panel.GetVisualChildren().First(); + + Assert.IsNull(item.TemplatedParent); + } + + [TestMethod] + public void Control_Item_Should_Set_Control_Parent() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + Assert.AreEqual(target, child.Parent); + Assert.AreEqual(target, ((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Clearing_Control_Item_Should_Clear_Child_Controls_Parent() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + target.Items = null; + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Control_Item_Should_Make_Control_Appear_In_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + CollectionAssert.AreEqual(new[] { child }, ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void String_Item_Should_Make_TextBlock_Appear_In_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + + var logical = (ILogical)target; + Assert.AreEqual(1, logical.LogicalChildren.Count); + Assert.IsInstanceOfType(logical.LogicalChildren[0], typeof(TextBlock)); + } + + [TestMethod] + public void Setting_Items_To_Null_Should_Remove_LogicalChildren() + { + var target = new ItemsControl(); + var child = new Control(); + + target.Template = this.GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Items = null; + + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)target).LogicalChildren.ToList()); + } + + [TestMethod] + public void Setting_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + target.Items = new[] { child }; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Setting_Items_To_Null_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + target.Items = null; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Changing_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var child = new Control(); + var called = false; + + target.Template = this.GetTemplate(); + target.Items = new[] { child }; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; + + target.Items = new[] { "Foo" }; + + Assert.IsTrue(called); + } + + [TestMethod] + public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var items = new PerspexList { "Foo" }; + var called = false; + + target.Template = this.GetTemplate(); + target.Items = items; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Add; + + items.Add("Bar"); + + Assert.IsTrue(called); + } + + [TestMethod] + public void Removing_Items_Should_Fire_LogicalChildren_CollectionChanged() + { + var target = new ItemsControl(); + var items = new PerspexList { "Foo", "Bar" }; + var called = false; + + target.Template = this.GetTemplate(); + target.Items = items; + target.ApplyTemplate(); + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => + called = e.Action == NotifyCollectionChangedAction.Remove; + + items.Remove("Bar"); + + Assert.IsTrue(called); + } + + private ControlTemplate GetTemplate() + { + return ControlTemplate.Create(parent => + { + return new Border + { + Background = new Perspex.Media.SolidColorBrush(0xffffffff), + Content = new ItemsPresenter + { + Id = "itemsPresenter", + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + } + }; + }); + } + + private IDisposable RegisterServices() + { + var result = Locator.CurrentMutable.WithResolver(); + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var renderInterface = fixture.Create(); + Locator.CurrentMutable.RegisterConstant(renderInterface, typeof(IPlatformRenderInterface)); + return result; + } + } +} diff --git a/Perspex.Controls.UnitTests/PanelTests.cs b/Perspex.Controls.UnitTests/PanelTests.cs new file mode 100644 index 0000000000..f4976e9399 --- /dev/null +++ b/Perspex.Controls.UnitTests/PanelTests.cs @@ -0,0 +1,82 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.UnitTests +{ + using System.Linq; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PanelTests + { + [TestMethod] + public void Adding_Control_To_Panel_Should_Set_Child_Controls_Parent() + { + var panel = new Panel(); + var child = new Control(); + + panel.Children.Add(child); + + Assert.AreEqual(child.Parent, panel); + Assert.AreEqual(((ILogical)child).LogicalParent, panel); + } + + [TestMethod] + public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent() + { + var panel = new Panel(); + var child = new Control(); + + panel.Children.Add(child); + panel.Children.Remove(child); + + Assert.IsNull(child.Parent); + Assert.IsNull(((ILogical)child).LogicalParent); + } + + [TestMethod] + public void Clearing_Panel_Children_Should_Clear_Child_Controls_Parent() + { + var panel = new Panel(); + var child1 = new Control(); + var child2 = new Control(); + + panel.Children.Add(child1); + panel.Children.Add(child2); + panel.Children.Clear(); + + Assert.IsNull(child1.Parent); + Assert.IsNull(((ILogical)child1).LogicalParent); + Assert.IsNull(child2.Parent); + Assert.IsNull(((ILogical)child2).LogicalParent); + } + + [TestMethod] + public void Child_Control_Should_Appear_In_Panel_Children() + { + var panel = new Panel(); + var child = new Control(); + + panel.Children.Add(child); + + CollectionAssert.AreEqual(new[] { child }, panel.Children); + CollectionAssert.AreEqual(new[] { child }, ((ILogical)panel).LogicalChildren.ToList()); + } + + [TestMethod] + public void Removing_Child_Control_Should_Remove_From_Panel_Children() + { + var panel = new Panel(); + var child = new Control(); + + panel.Children.Add(child); + panel.Children.Remove(child); + + CollectionAssert.AreEqual(new Control[0], panel.Children); + CollectionAssert.AreEqual(new ILogical[0], ((ILogical)panel).LogicalChildren.ToList()); + } + } +} diff --git a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 4eea508951..35214ff6b9 100644 --- a/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -64,7 +64,10 @@ + + + diff --git a/Perspex.Controls.UnitTests/TemplatedControlTests.cs b/Perspex.Controls.UnitTests/TemplatedControlTests.cs index b143b819d1..510b99cf99 100644 --- a/Perspex.Controls.UnitTests/TemplatedControlTests.cs +++ b/Perspex.Controls.UnitTests/TemplatedControlTests.cs @@ -14,6 +14,7 @@ namespace Perspex.Controls.UnitTests using Microsoft.VisualStudio.TestTools.UnitTesting; using Perspex.Controls; using Perspex.Controls.Primitives; + using Perspex.VisualTree; [TestClass] public class TemplatedControlTests diff --git a/Perspex.Controls/ColumnDefinitions.cs b/Perspex.Controls/ColumnDefinitions.cs index f8c9928eb8..636db00c36 100644 --- a/Perspex.Controls/ColumnDefinitions.cs +++ b/Perspex.Controls/ColumnDefinitions.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Collections; + namespace Perspex.Controls { using Perspex.Collections; diff --git a/Perspex.Controls/ContentControl.cs b/Perspex.Controls/ContentControl.cs index ad4bb2f6d2..2beec51b93 100644 --- a/Perspex.Controls/ContentControl.cs +++ b/Perspex.Controls/ContentControl.cs @@ -6,10 +6,13 @@ namespace Perspex.Controls { + using System; + using Perspex.Collections; + using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Layout; - public class ContentControl : TemplatedControl + public class ContentControl : TemplatedControl, ILogical { public static readonly PerspexProperty ContentProperty = PerspexProperty.Register("Content"); @@ -20,12 +23,42 @@ namespace Perspex.Controls public static readonly PerspexProperty VerticalContentAlignmentProperty = PerspexProperty.Register("VerticalContentAlignment"); + private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); + + private ContentPresenter presenter; + + private IDisposable presenterSubscription; + + public ContentControl() + { + this.GetObservableWithHistory(ContentProperty).Subscribe(x => + { + var control1 = x.Item1 as Control; + var control2 = x.Item2 as Control; + + if (control1 != null) + { + control1.Parent = null; + } + + if (control2 != null) + { + control2.Parent = this; + } + }); + } + public object Content { get { return this.GetValue(ContentProperty); } set { this.SetValue(ContentProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } + } + public HorizontalAlignment HorizontalContentAlignment { get { return this.GetValue(HorizontalContentAlignmentProperty); } @@ -37,5 +70,22 @@ namespace Perspex.Controls get { return this.GetValue(VerticalContentAlignmentProperty); } set { this.SetValue(VerticalContentAlignmentProperty, value); } } + + protected override void OnTemplateApplied() + { + if (this.presenterSubscription != null) + { + this.presenterSubscription.Dispose(); + this.presenterSubscription = null; + } + + this.presenter = this.FindTemplateChild("contentPresenter"); + + if (this.presenter != null) + { + this.presenterSubscription = this.presenter.ChildObservable + .Subscribe(x => this.logicalChild.SingleItem = x); + } + } } } diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index f7b60daf9d..e364b7519d 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -7,8 +7,10 @@ namespace Perspex.Controls { using System; + using System.Collections.Generic; + using System.Linq; using System.Reactive.Linq; - using Perspex.Animation; + using Perspex.Collections; using Perspex.Input; using Perspex.Interactivity; using Perspex.Media; @@ -16,7 +18,7 @@ namespace Perspex.Controls using Perspex.Styling; using Splat; - public class Control : InputElement, IStyleable, IStyleHost + public class Control : InputElement, ILogical, IStyleable, IStyleHost { public static readonly PerspexProperty BorderBrushProperty = PerspexProperty.Register("BorderBrush"); @@ -27,12 +29,20 @@ namespace Perspex.Controls public static readonly PerspexProperty ForegroundProperty = PerspexProperty.Register("Foreground", new SolidColorBrush(0xff000000), inherits: true); + public static readonly PerspexProperty ParentProperty = + PerspexProperty.Register("Parent"); + + public static readonly PerspexProperty TagProperty = + PerspexProperty.Register("Tag"); + public static readonly PerspexProperty TemplatedParentProperty = PerspexProperty.Register("TemplatedParent", inherits: true); public static readonly RoutedEvent RequestBringIntoViewEvent = RoutedEvent.Register("RequestBringIntoView", RoutingStrategy.Bubble); + private static readonly IReadOnlyPerspexList EmptyChildren = new SingleItemPerspexList(); + private Classes classes = new Classes(); private DataTemplates dataTemplates; @@ -143,12 +153,34 @@ namespace Perspex.Controls } } + public Control Parent + { + get { return this.GetValue(ParentProperty); } + internal set { this.SetValue(ParentProperty, value); } + } + + public object Tag + { + get { return this.GetValue(TagProperty); } + set { this.SetValue(TagProperty, value); } + } + public ITemplatedControl TemplatedParent { get { return this.GetValue(TemplatedParentProperty); } internal set { this.SetValue(TemplatedParentProperty, value); } } + ILogical ILogical.LogicalParent + { + get { return this.Parent; } + } + + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return EmptyChildren; } + } + public void BringIntoView() { this.BringIntoView(new Rect(this.ActualSize)); diff --git a/Perspex.Controls/ControlExtensions.cs b/Perspex.Controls/ControlExtensions.cs index 437aff1983..e1b400365a 100644 --- a/Perspex.Controls/ControlExtensions.cs +++ b/Perspex.Controls/ControlExtensions.cs @@ -11,6 +11,7 @@ namespace Perspex.Controls using System.Linq; using Perspex.Controls; using Perspex.Styling; + using Perspex.VisualTree; public static class ControlExtensions { diff --git a/Perspex.Controls/DataTemplateExtensions.cs b/Perspex.Controls/DataTemplateExtensions.cs index 48e6c51c82..576aa3beb2 100644 --- a/Perspex.Controls/DataTemplateExtensions.cs +++ b/Perspex.Controls/DataTemplateExtensions.cs @@ -7,6 +7,7 @@ namespace Perspex.Controls { using System.Linq; + using Perspex.VisualTree; using Splat; public static class DataTemplateExtensions diff --git a/Perspex.Controls/Decorator.cs b/Perspex.Controls/Decorator.cs index 231fe7b820..1ae8711fcd 100644 --- a/Perspex.Controls/Decorator.cs +++ b/Perspex.Controls/Decorator.cs @@ -10,9 +10,10 @@ namespace Perspex.Controls using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; + using Perspex.Collections; using Perspex.Layout; - public class Decorator : Control, IVisual + public class Decorator : Control, IVisual, ILogical { public static readonly PerspexProperty ContentProperty = PerspexProperty.Register("Content"); @@ -20,16 +21,25 @@ namespace Perspex.Controls public static readonly PerspexProperty PaddingProperty = PerspexProperty.Register("Padding"); + private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); + public Decorator() { - this.GetObservable(ContentProperty).Subscribe(x => + this.GetObservableWithHistory(ContentProperty).Subscribe(x => { - this.ClearVisualChildren(); + if (x.Item1 != null) + { + this.RemoveVisualChild(x.Item1); + x.Item1.Parent = null; + } - if (x != null) + if (x.Item2 != null) { - this.AddVisualChild(x); + this.AddVisualChild(x.Item2); + x.Item2.Parent = this; } + + this.logicalChild.SingleItem = x.Item2; }); } @@ -45,6 +55,11 @@ namespace Perspex.Controls set { this.SetValue(PaddingProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } + } + protected override Size ArrangeOverride(Size finalSize) { Control content = this.Content; diff --git a/Perspex.Controls/GridSplitter.cs b/Perspex.Controls/GridSplitter.cs index 99e713d6d9..07391d8305 100644 --- a/Perspex.Controls/GridSplitter.cs +++ b/Perspex.Controls/GridSplitter.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls using System; using Perspex.Controls.Primitives; using Perspex.Input; + using Perspex.VisualTree; public class GridSplitter : Thumb { diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index 1d8d92b8e4..7767904df4 100644 --- a/Perspex.Controls/ItemsControl.cs +++ b/Perspex.Controls/ItemsControl.cs @@ -11,10 +11,13 @@ namespace Perspex.Controls using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; + using Perspex.Collections; using Perspex.Controls.Generators; + using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; + using Perspex.VisualTree; - public class ItemsControl : TemplatedControl + public class ItemsControl : TemplatedControl, ILogical { [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Needs to be before or a NullReferenceException is thrown.")] private static readonly ItemsPanelTemplate DefaultPanel = @@ -28,6 +31,10 @@ namespace Perspex.Controls private ItemContainerGenerator itemContainerGenerator; + private PerspexReadOnlyListView logicalChildren; + + private ItemsPresenter presenter; + public ItemsControl() { this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged); @@ -58,11 +65,45 @@ namespace Perspex.Controls set { this.SetValue(ItemsPanelProperty, value); } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get + { + if (this.logicalChildren == null) + { + this.logicalChildren = new PerspexReadOnlyListView( + new PerspexList(), + x => (ILogical)x); + } + + return this.logicalChildren; + } + } + protected virtual ItemContainerGenerator CreateItemContainerGenerator() { return new ItemContainerGenerator(this); } + protected override void OnTemplateApplied() + { + if (this.logicalChildren != null) + { + this.logicalChildren.Dispose(); + this.logicalChildren = null; + } + + this.presenter = this.FindTemplateChild("itemsPresenter"); + + if (this.presenter != null) + { + var panel = (IVisual)this.presenter.GetVisualChildren().Single(); + this.logicalChildren = new PerspexReadOnlyListView( + panel.VisualChildren, + x => (ILogical)x); + } + } + private void ItemsChanged(Tuple value) { INotifyPropertyChanged inpc = value.Item1 as INotifyPropertyChanged; diff --git a/Perspex.Controls/Panel.cs b/Perspex.Controls/Panel.cs index 147831e3bf..4a1db10ac6 100644 --- a/Perspex.Controls/Panel.cs +++ b/Perspex.Controls/Panel.cs @@ -9,11 +9,12 @@ namespace Perspex.Controls using System; using System.Collections.Specialized; using System.Linq; + using Perspex.Collections; /// /// Base class for controls that can contain multiple children. /// - public class Panel : Control + public class Panel : Control, ILogical { private Controls children; @@ -54,17 +55,41 @@ namespace Perspex.Controls } } + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.children; } + } + private void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) { + var logicalParent = (Control)this; + + while (logicalParent.TemplatedParent != null) + { + logicalParent = (Control)logicalParent.TemplatedParent; + } + // TODO: Handle Move and Replace. switch (e.Action) { case NotifyCollectionChangedAction.Add: this.AddVisualChildren(e.NewItems.OfType()); + + foreach (var child in e.NewItems.OfType()) + { + child.Parent = logicalParent; + } + break; case NotifyCollectionChangedAction.Remove: this.RemoveVisualChildren(e.OldItems.OfType()); + + foreach (var child in e.OldItems.OfType()) + { + child.Parent = null; + } + break; case NotifyCollectionChangedAction.Reset: diff --git a/Perspex.Controls/Presenters/ContentPresenter.cs b/Perspex.Controls/Presenters/ContentPresenter.cs index 617489ad67..c0340567c3 100644 --- a/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/Perspex.Controls/Presenters/ContentPresenter.cs @@ -9,8 +9,8 @@ namespace Perspex.Controls.Presenters using System; using System.Linq; using System.Reactive.Linq; + using System.Reactive.Subjects; using Perspex.Controls.Primitives; - using Perspex.Layout; public class ContentPresenter : Control, IVisual { @@ -19,11 +19,44 @@ namespace Perspex.Controls.Presenters private bool createdChild; + private Control child; + + private BehaviorSubject childObservable = new BehaviorSubject(null); + public ContentPresenter() { this.GetObservable(ContentProperty).Skip(1).Subscribe(this.ContentChanged); } + public Control Child + { + get + { + return this.child; + } + + private set + { + if (this.child != value) + { + this.ClearVisualChildren(); + this.child = value; + + if (value != null) + { + this.AddVisualChild(value); + } + + this.childObservable.OnNext(value); + } + } + } + + public IObservable ChildObservable + { + get { return this.childObservable; } + } + public object Content { get { return this.GetValue(ContentProperty); } @@ -45,12 +78,10 @@ namespace Perspex.Controls.Presenters protected override Size MeasureOverride(Size availableSize) { - Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; - - if (child != null) + if (this.child != null) { - child.Measure(availableSize); - return child.DesiredSize.Value; + this.child.Measure(availableSize); + return this.child.DesiredSize.Value; } return new Size(); @@ -64,13 +95,13 @@ namespace Perspex.Controls.Presenters private void CreateChild() { + Control result = null; object content = this.Content; this.ClearVisualChildren(); if (content != null) { - Control result; if (content is Control) { @@ -101,9 +132,9 @@ namespace Perspex.Controls.Presenters } result.TemplatedParent = foo; - this.AddVisualChild(result); } + this.Child = result; this.createdChild = true; } } diff --git a/Perspex.Controls/Presenters/ItemsPresenter.cs b/Perspex.Controls/Presenters/ItemsPresenter.cs index 8830122696..b2de51a674 100644 --- a/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -26,7 +26,7 @@ namespace Perspex.Controls.Presenters public ItemsPresenter() { - this.GetObservableWithHistory(ItemsProperty).Skip(1).Subscribe(this.ItemsChanged); + this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged); } public IEnumerable Items diff --git a/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 8a795d5199..01f092f0c8 100644 --- a/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -10,6 +10,7 @@ namespace Perspex.Controls.Presenters using System.Linq; using Perspex.Input; using Perspex.Layout; + using Perspex.VisualTree; public class ScrollContentPresenter : ContentPresenter { diff --git a/Perspex.Controls/Presenters/TextPresenter.cs b/Perspex.Controls/Presenters/TextPresenter.cs index d0abc73530..6a1af946fe 100644 --- a/Perspex.Controls/Presenters/TextPresenter.cs +++ b/Perspex.Controls/Presenters/TextPresenter.cs @@ -15,6 +15,7 @@ namespace Perspex.Controls using Perspex.Input; using Perspex.Media; using Perspex.Threading; + using Perspex.VisualTree; public class TextPresenter : TextBlock { diff --git a/Perspex.Controls/Primitives/SelectingItemsControl.cs b/Perspex.Controls/Primitives/SelectingItemsControl.cs index 6390b17f36..f918fd56f1 100644 --- a/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -12,6 +12,7 @@ namespace Perspex.Controls.Primitives using Perspex.Controls.Presenters; using Perspex.Input; using Perspex.Interactivity; + using Perspex.VisualTree; public class SelectingItemsControl : ItemsControl { diff --git a/Perspex.Controls/Primitives/TabStrip.cs b/Perspex.Controls/Primitives/TabStrip.cs index 03d3709a75..91b13da1c6 100644 --- a/Perspex.Controls/Primitives/TabStrip.cs +++ b/Perspex.Controls/Primitives/TabStrip.cs @@ -7,14 +7,15 @@ namespace Perspex.Controls.Primitives { using System; - using System.Collections; using System.Linq; + using System.Reactive.Linq; using Perspex.Controls.Generators; - using Perspex.Controls.Presenters; - using Perspex.Input; public class TabStrip : SelectingItemsControl { + public static readonly PerspexProperty SelectedTabProperty = + TabControl.SelectedTabProperty.AddOwner(); + private static readonly ItemsPanelTemplate PanelTemplate = new ItemsPanelTemplate( () => new StackPanel()); @@ -23,6 +24,19 @@ namespace Perspex.Controls.Primitives ItemsPanelProperty.OverrideDefaultValue(typeof(TabStrip), PanelTemplate); } + public TabStrip() + { + this.Bind( + SelectedTabProperty, + this.GetObservable(SelectedItemProperty).Select(x => x as TabItem)); + } + + public TabItem SelectedTab + { + get { return this.GetValue(SelectedTabProperty); } + private set { this.SetValue(SelectedTabProperty, value); } + } + protected override ItemContainerGenerator CreateItemContainerGenerator() { TabControl tabControl = this.TemplatedParent as TabControl; diff --git a/Perspex.Controls/Primitives/TemplatedControl.cs b/Perspex.Controls/Primitives/TemplatedControl.cs index 29752b7049..547d7cd605 100644 --- a/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/Perspex.Controls/Primitives/TemplatedControl.cs @@ -10,6 +10,7 @@ namespace Perspex.Controls.Primitives using System.Linq; using Perspex.Media; using Perspex.Styling; + using Perspex.VisualTree; using Splat; public class TemplatedControl : Control, ITemplatedControl @@ -137,9 +138,7 @@ namespace Perspex.Controls.Primitives protected T FindTemplateChild(string id) where T : Control { - return this.GetTemplateControls() - .Where(x => x.TemplatedParent == this) - .OfType().FirstOrDefault(x => x.Id == id); + return (T)this.GetTemplateControls().SingleOrDefault(x => x.Id == id); } protected T GetTemplateChild(string id) where T : Control diff --git a/Perspex.Controls/RadioButton.cs b/Perspex.Controls/RadioButton.cs index 33c9bfa298..a88c0d5e3e 100644 --- a/Perspex.Controls/RadioButton.cs +++ b/Perspex.Controls/RadioButton.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls using System; using System.Linq; using Perspex.Controls.Primitives; + using Perspex.VisualTree; public class RadioButton : ToggleButton { diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index 5e71dd7b7f..eb9cb0eb11 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -9,16 +9,27 @@ namespace Perspex.Controls using System; using System.Linq; using System.Reactive.Linq; + using Perspex.Collections; using Perspex.Controls.Generators; + using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; - public class TabControl : SelectingItemsControl + public class TabControl : SelectingItemsControl, ILogical { public static readonly PerspexProperty SelectedContentProperty = PerspexProperty.Register("SelectedContent"); + public static readonly PerspexProperty SelectedTabProperty = + PerspexProperty.Register("SelectedTab"); + private TabStrip tabStrip; + private ContentPresenter presenter; + + private IDisposable presenterSubscription; + + private SingleItemPerspexList logicalChild = new SingleItemPerspexList(); + public TabControl() { this.GetObservable(SelectedItemProperty).Subscribe(x => @@ -27,6 +38,27 @@ namespace Perspex.Controls object content = (c != null) ? c.Content : null; this.SetValue(SelectedContentProperty, content); }); + + this.Bind( + SelectedTabProperty, + this.GetObservable(SelectedItemProperty).Select(x => x as TabItem)); + } + + public object SelectedContent + { + get { return this.GetValue(SelectedContentProperty); } + set { this.SetValue(SelectedContentProperty, value); } + } + + public TabItem SelectedTab + { + get { return this.GetValue(SelectedTabProperty); } + private set { this.SetValue(SelectedTabProperty, value); } + } + + IReadOnlyPerspexList ILogical.LogicalChildren + { + get { return this.logicalChild; } } protected override ItemContainerGenerator CreateItemContainerGenerator() @@ -36,6 +68,20 @@ namespace Perspex.Controls protected override void OnTemplateApplied() { + if (this.presenterSubscription != null) + { + this.presenterSubscription.Dispose(); + this.presenterSubscription = null; + } + + this.presenter = this.FindTemplateChild("contentPresenter"); + + if (this.presenter != null) + { + this.presenterSubscription = this.presenter.ChildObservable + .Subscribe(x => this.logicalChild.SingleItem = x); + } + this.tabStrip = this.GetTemplateControls().OfType().FirstOrDefault(); this.BindTwoWay(TabControl.SelectedItemProperty, this.tabStrip, TabControl.SelectedItemProperty); } diff --git a/Perspex.Controls/TreeViewItem.cs b/Perspex.Controls/TreeViewItem.cs index c0c028818f..51d0673f02 100644 --- a/Perspex.Controls/TreeViewItem.cs +++ b/Perspex.Controls/TreeViewItem.cs @@ -10,6 +10,7 @@ namespace Perspex.Controls using System.Linq; using Perspex.Controls.Generators; using Perspex.Controls.Primitives; + using Perspex.VisualTree; public class TreeViewItem : HeaderedItemsControl, ISelectable { diff --git a/Perspex.Diagnostics/DevTools.cs b/Perspex.Diagnostics/DevTools.cs index 38abb63506..bf8576a2a8 100644 --- a/Perspex.Diagnostics/DevTools.cs +++ b/Perspex.Diagnostics/DevTools.cs @@ -10,6 +10,7 @@ namespace Perspex.Diagnostics using System.Reactive.Disposables; using System.Reactive.Linq; using Perspex.Controls; + using Perspex.Controls.Primitives; using Perspex.Diagnostics.ViewModels; using Perspex.Input; using ReactiveUI; @@ -21,23 +22,43 @@ namespace Perspex.Diagnostics public DevTools() { - var treeView = new TreeView + var treePane = new Grid { - DataTemplates = new DataTemplates + RowDefinitions = new RowDefinitions { - new TreeDataTemplate(GetHeader, x => x.Children), + new RowDefinition(GridLength.Auto), + new RowDefinition(new GridLength(1, GridUnitType.Star)), }, - [!TreeView.ItemsProperty] = this[!DevTools.RootProperty].Select(x => + Children = new Controls { - if (x != null) + (var tabStrip = new TabStrip { - return new[] { new VisualTreeNode((IVisual)x) }; - } - else + Items = new[] + { + new TabItem + { + Header = "Logical Tree", + IsSelected = true, + [!TabItem.TagProperty] = this[!RootProperty].Select(x => LogicalTreeNode.Create(x)), + }, + new TabItem + { + Header = "Visual Tree", + [!TabItem.TagProperty] = this[!RootProperty].Select(x => VisualTreeNode.Create(x)), + } + }, + }), + (var treeView = new TreeView { - return null; - } - }), + DataTemplates = new DataTemplates + { + new TreeDataTemplate(GetHeader, x => x.Children), + new TreeDataTemplate(GetHeader, x => x.Children), + }, + [!TreeView.ItemsProperty] = tabStrip.WhenAnyValue(x => x.SelectedTab.Tag), + [Grid.RowProperty] = 1, + }) + } }; var detailsView = new ContentControl @@ -48,8 +69,8 @@ namespace Perspex.Diagnostics }, [!ContentControl.ContentProperty] = treeView[!TreeView.SelectedItemProperty] .Where(x => x != null) - .Cast() - .Select(x => new ControlDetails(x.Visual)), + .Cast() + .Select(x => new ControlDetails(x.Control)), [Grid.ColumnProperty] = 2, }; @@ -69,7 +90,7 @@ namespace Perspex.Diagnostics }, Children = new Controls { - treeView, + treePane, splitter, detailsView, } @@ -127,6 +148,26 @@ namespace Perspex.Diagnostics }; } + private static Control GetHeader(LogicalTreeNode node) + { + return new StackPanel + { + Orientation = Orientation.Horizontal, + Gap = 8, + Children = new Controls + { + new TextBlock + { + Text = node.Type, + }, + new TextBlock + { + [!TextBlock.TextProperty] = node.WhenAnyValue(x => x.Classes), + } + } + }; + } + private static Control GetHeader(VisualTreeNode node) { return new StackPanel diff --git a/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/Perspex.Diagnostics/Perspex.Diagnostics.csproj index c33de242fd..53948a659a 100644 --- a/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -76,6 +76,8 @@ + + diff --git a/Perspex.Diagnostics/ViewModels/ControlDetails.cs b/Perspex.Diagnostics/ViewModels/ControlDetails.cs index f6fa82fdb4..7d1d9ff301 100644 --- a/Perspex.Diagnostics/ViewModels/ControlDetails.cs +++ b/Perspex.Diagnostics/ViewModels/ControlDetails.cs @@ -8,18 +8,16 @@ namespace Perspex.Diagnostics.ViewModels { using System.Collections.Generic; using System.Linq; - using Perspex.Styling; + using Perspex.Controls; using ReactiveUI; internal class ControlDetails : ReactiveObject { - public ControlDetails(IVisual visual) + public ControlDetails(Control control) { - PerspexObject po = visual as PerspexObject; - - if (po != null) + if (control != null) { - this.Properties = po.GetAllValues() + this.Properties = control.GetAllValues() .Select(x => new PropertyDetails(x)) .OrderBy(x => x.Name); } diff --git a/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs b/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs new file mode 100644 index 0000000000..cf77931eb2 --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System; + using Perspex.Controls; + using ReactiveUI; + + internal class LogicalTreeNode : TreeNode + { + private string classes; + + public LogicalTreeNode(ILogical logical) + : base((Control)logical) + { + this.Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x)); + } + + public static LogicalTreeNode[] Create(object control) + { + var logical = control as ILogical; + return logical != null ? new[] { new LogicalTreeNode(logical) } : null; + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/TreeNode.cs b/Perspex.Diagnostics/ViewModels/TreeNode.cs new file mode 100644 index 0000000000..c0ecfbb395 --- /dev/null +++ b/Perspex.Diagnostics/ViewModels/TreeNode.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Diagnostics.ViewModels +{ + using System; + using System.Reactive; + using System.Reactive.Linq; + using Perspex.Controls; + using ReactiveUI; + + internal class TreeNode : ReactiveObject + { + private string classes; + + public TreeNode(Control control) + { + this.Control = control; + this.Type = control.GetType().Name; + + control.Classes.Changed.Select(_ => Unit.Default) + .StartWith(Unit.Default) + .Subscribe(_ => + { + if (control.Classes.Count > 0) + { + this.Classes = "(" + string.Join(" ", control.Classes) + ")"; + } + else + { + this.Classes = string.Empty; + } + }); + } + + public IReactiveDerivedList Children + { + get; + protected set; + } + + public string Classes + { + get { return this.classes; } + private set { this.RaiseAndSetIfChanged(ref this.classes, value); } + } + + public string Type + { + get; + private set; + } + + public Control Control + { + get; + private set; + } + } +} diff --git a/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs b/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs index cd96c02f8a..e76983e2c0 100644 --- a/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs +++ b/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs @@ -6,55 +6,30 @@ namespace Perspex.Diagnostics.ViewModels { - using System; - using System.Reactive; - using System.Reactive.Linq; using Perspex.Controls; - using Perspex.Styling; using ReactiveUI; - internal class VisualTreeNode : ReactiveObject + internal class VisualTreeNode : TreeNode { private string classes; public VisualTreeNode(IVisual visual) + : base((Control)visual) { this.Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x)); - this.Type = visual.GetType().Name; - this.Visual = visual; - Control control = visual as Control; - - if (control != null) + if (this.Control != null) { - this.IsInTemplate = control.TemplatedParent != null; - - control.Classes.Changed.Select(_ => Unit.Default).StartWith(Unit.Default).Subscribe(_ => - { - if (control.Classes.Count > 0) - { - this.Classes = "(" + string.Join(" ", control.Classes) + ")"; - } - else - { - this.Classes = string.Empty; - } - }); + this.IsInTemplate = this.Control.TemplatedParent != null; } } - public IReactiveDerivedList Children { get; private set; } + public bool IsInTemplate { get; private set; } - public string Classes + public static VisualTreeNode[] Create(object control) { - get { return this.classes; } - private set { this.RaiseAndSetIfChanged(ref this.classes, value); } + var visual = control as IVisual; + return visual != null ? new[] { new VisualTreeNode(visual) } : null; } - - public bool IsInTemplate { get; private set; } - - public string Type { get; private set; } - - public IVisual Visual { get; private set; } } } diff --git a/Perspex.Input/FocusManager.cs b/Perspex.Input/FocusManager.cs index 1cbb401975..ccf9be29a4 100644 --- a/Perspex.Input/FocusManager.cs +++ b/Perspex.Input/FocusManager.cs @@ -10,6 +10,7 @@ namespace Perspex.Input using System.Collections.Generic; using System.Linq; using Perspex.Interactivity; + using Perspex.VisualTree; using Splat; public class FocusManager : IFocusManager diff --git a/Perspex.Input/InputElement.cs b/Perspex.Input/InputElement.cs index 99b1c1c349..2c3a12c612 100644 --- a/Perspex.Input/InputElement.cs +++ b/Perspex.Input/InputElement.cs @@ -10,6 +10,7 @@ namespace Perspex.Input using System.Linq; using Perspex.Interactivity; using Perspex.Rendering; + using Perspex.VisualTree; public class InputElement : Interactive, IInputElement { diff --git a/Perspex.Input/InputManager.cs b/Perspex.Input/InputManager.cs index 16086d421c..f7e9297ade 100644 --- a/Perspex.Input/InputManager.cs +++ b/Perspex.Input/InputManager.cs @@ -11,6 +11,7 @@ namespace Perspex.Input using System.Linq; using System.Reactive.Subjects; using Perspex.Input.Raw; + using Perspex.VisualTree; public class InputManager : IInputManager { diff --git a/Perspex.Input/MouseDevice.cs b/Perspex.Input/MouseDevice.cs index b0808ca8e4..dd2bc1b1b2 100644 --- a/Perspex.Input/MouseDevice.cs +++ b/Perspex.Input/MouseDevice.cs @@ -12,6 +12,7 @@ namespace Perspex.Input using Perspex.Input.Raw; using Perspex.Interactivity; using Perspex.Platform; + using Perspex.VisualTree; using Splat; public abstract class MouseDevice : IMouseDevice diff --git a/Perspex.Interactivity/Interactive.cs b/Perspex.Interactivity/Interactive.cs index 509ffcedd5..c4bf1762e5 100644 --- a/Perspex.Interactivity/Interactive.cs +++ b/Perspex.Interactivity/Interactive.cs @@ -12,6 +12,7 @@ namespace Perspex.Interactivity using System.Reactive; using System.Reactive.Linq; using Perspex.Layout; + using Perspex.VisualTree; public class Interactive : Layoutable, IInteractive { diff --git a/Perspex.Layout/LayoutManager.cs b/Perspex.Layout/LayoutManager.cs index d02c9edc8a..7fff4cb3a0 100644 --- a/Perspex.Layout/LayoutManager.cs +++ b/Perspex.Layout/LayoutManager.cs @@ -10,6 +10,7 @@ namespace Perspex.Layout using System.Reactive; using System.Reactive.Subjects; using NGenerics.DataStructures.General; + using Perspex.VisualTree; /// /// Manages measuring and arranging of controls. diff --git a/Perspex.Layout/Layoutable.cs b/Perspex.Layout/Layoutable.cs index 69f1277388..f41b07b618 100644 --- a/Perspex.Layout/Layoutable.cs +++ b/Perspex.Layout/Layoutable.cs @@ -8,6 +8,7 @@ namespace Perspex.Layout { using System; using System.Linq; + using Perspex.VisualTree; using Splat; public enum HorizontalAlignment diff --git a/Perspex.SceneGraph.UnitTests/VisualTests.cs b/Perspex.SceneGraph.UnitTests/VisualTests.cs index a7e0755a47..37a307d054 100644 --- a/Perspex.SceneGraph.UnitTests/VisualTests.cs +++ b/Perspex.SceneGraph.UnitTests/VisualTests.cs @@ -8,6 +8,7 @@ namespace Perspex.SceneGraph.UnitTests { using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Perspex.VisualTree; [TestClass] public class VisualTests diff --git a/Perspex.SceneGraph/ILogical.cs b/Perspex.SceneGraph/ILogical.cs new file mode 100644 index 0000000000..154a54e0a1 --- /dev/null +++ b/Perspex.SceneGraph/ILogical.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using Perspex.Collections; + + /// + /// Represents a node in the logical tree. + /// + public interface ILogical + { + /// + /// Gets the logical parent. + /// + ILogical LogicalParent { get; } + + /// + /// Gets the logical children. + /// + IReadOnlyPerspexList LogicalChildren { get; } + } +} diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj index c3ee00e99c..3760967f12 100644 --- a/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -45,6 +45,7 @@ + @@ -91,7 +92,7 @@ - + diff --git a/Perspex.SceneGraph/Visual.cs b/Perspex.SceneGraph/Visual.cs index 2f9351fc12..5d9900e010 100644 --- a/Perspex.SceneGraph/Visual.cs +++ b/Perspex.SceneGraph/Visual.cs @@ -15,6 +15,7 @@ namespace Perspex using Perspex.Collections; using Perspex.Media; using Perspex.Rendering; + using Perspex.VisualTree; using Splat; public class Visual : Animatable, IVisual diff --git a/Perspex.SceneGraph/VisualExtensions.cs b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs similarity index 99% rename from Perspex.SceneGraph/VisualExtensions.cs rename to Perspex.SceneGraph/VisualTree/VisualExtensions.cs index d7452ed012..f21bc1ec96 100644 --- a/Perspex.SceneGraph/VisualExtensions.cs +++ b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------- -namespace Perspex +namespace Perspex.VisualTree { using System; using System.Collections.Generic; diff --git a/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 245a7f573b..073dad0717 100644 --- a/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -11,6 +11,7 @@ namespace Perspex.Styling.UnitTests using System.Reactive.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using Perspex.Collections; using Perspex.Styling; [TestClass] @@ -19,67 +20,64 @@ namespace Perspex.Styling.UnitTests [TestMethod] public void Descendent_Matches_Control_When_It_Is_Child_OfType() { - Assert.Inconclusive("Need to implement logical tree."); - ////var parent = new Mock(); - ////var child = new Mock(); - - ////child.Setup(x => x.LogicalParent).Returns(parent.Object); + var parent = new Mock(); + var child = new Mock(); + var childStyleable = child.As(); - ////var selector = new Selector().OfType(parent.Object.GetType()).Descendent().OfType(child.Object.GetType()); + child.Setup(x => x.LogicalParent).Returns(parent.Object); - ////Assert.IsTrue(ActivatorValue(selector, child.Object)); + var selector = new Selector().OfType(parent.Object.GetType()).Descendent().OfType(child.Object.GetType()); + + Assert.IsTrue(ActivatorValue(selector, childStyleable.Object)); } [TestMethod] public void Descendent_Matches_Control_When_It_Is_Descendent_OfType() { - Assert.Inconclusive("Need to implement logical tree."); - ////var grandparent = new Mock(); - ////var parent = new Mock(); - ////var child = new Mock(); + var grandparent = new Mock(); + var parent = new Mock(); + var child = new Mock(); - ////parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); - ////child.Setup(x => x.LogicalParent).Returns(parent.Object); + parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); + child.Setup(x => x.LogicalParent).Returns(parent.Object); - ////var selector = new Selector().OfType(grandparent.Object.GetType()).Descendent().OfType(child.Object.GetType()); + var selector = new Selector().OfType(grandparent.Object.GetType()).Descendent().OfType(child.Object.GetType()); - ////Assert.IsTrue(ActivatorValue(selector, child.Object)); + Assert.IsTrue(ActivatorValue(selector, child.Object)); } [TestMethod] public void Descendent_Matches_Control_When_It_Is_Descendent_OfType_And_Class() { - Assert.Inconclusive("Need to implement logical tree."); - ////var grandparent = new Mock(); - ////var parent = new Mock(); - ////var child = new Mock(); + var grandparent = new Mock(); + var parent = new Mock(); + var child = new Mock(); - ////grandparent.Setup(x => x.Classes).Returns(new Classes("foo")); - ////parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); - ////parent.Setup(x => x.Classes).Returns(new Classes()); - ////child.Setup(x => x.LogicalParent).Returns(parent.Object); + grandparent.Setup(x => x.Classes).Returns(new Classes("foo")); + parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); + parent.Setup(x => x.Classes).Returns(new Classes()); + child.Setup(x => x.LogicalParent).Returns(parent.Object); - ////var selector = new Selector().OfType(grandparent.Object.GetType()).Class("foo").Descendent().OfType(child.Object.GetType()); + var selector = new Selector().OfType(grandparent.Object.GetType()).Class("foo").Descendent().OfType(child.Object.GetType()); - ////Assert.IsTrue(ActivatorValue(selector, child.Object)); + Assert.IsTrue(ActivatorValue(selector, child.Object)); } [TestMethod] public void Descendent_Doesnt_Match_Control_When_It_Is_Descendent_OfType_But_Wrong_Class() { - Assert.Inconclusive("Need to implement logical tree."); - ////var grandparent = new Mock(); - ////var parent = new Mock(); - ////var child = new Mock(); + var grandparent = new Mock(); + var parent = new Mock(); + var child = new Mock(); - ////grandparent.Setup(x => x.Classes).Returns(new Classes("bar")); - ////parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); - ////parent.Setup(x => x.Classes).Returns(new Classes("foo")); - ////child.Setup(x => x.LogicalParent).Returns(parent.Object); + grandparent.Setup(x => x.Classes).Returns(new Classes("bar")); + parent.Setup(x => x.LogicalParent).Returns(grandparent.Object); + parent.Setup(x => x.Classes).Returns(new Classes("foo")); + child.Setup(x => x.LogicalParent).Returns(parent.Object); - ////var selector = new Selector().OfType().Class("foo").Descendent().OfType(); + var selector = new Selector().OfType().Class("foo").Descendent().OfType(); - ////Assert.IsFalse(ActivatorValue(selector, child.Object)); + Assert.IsFalse(ActivatorValue(selector, child.Object)); } private static bool ActivatorValue(Selector selector, IStyleable control) @@ -87,16 +85,26 @@ namespace Perspex.Styling.UnitTests return selector.GetActivator(control).Take(1).ToEnumerable().Single(); } - ////public abstract class TestLogical1 : TestLogical - ////{ - ////} + public abstract class TestLogical : ILogical, IStyleable + { + public abstract Classes Classes { get; } + public abstract string Id { get; } + public abstract IReadOnlyPerspexList LogicalChildren { get; } + public abstract ILogical LogicalParent { get; } + public abstract ITemplatedControl TemplatedParent { get; } + public abstract IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue); + } + + public abstract class TestLogical1 : TestLogical + { + } - ////public abstract class TestLogical2 : TestLogical - ////{ - ////} + public abstract class TestLogical2 : TestLogical + { + } - ////public abstract class TestLogical3 : TestLogical - ////{ - ////} + public abstract class TestLogical3 : TestLogical + { + } } } diff --git a/Perspex.Styling/Selectors.cs b/Perspex.Styling/Selectors.cs index 157b2e3921..120529b5c0 100644 --- a/Perspex.Styling/Selectors.cs +++ b/Perspex.Styling/Selectors.cs @@ -33,13 +33,12 @@ namespace Perspex.Styling SelectorString = " ", GetObservable = control => { - // TODO: This needs to traverse the logical tree, not the visual. - IVisual c = (IVisual)control; + ILogical c = (ILogical)control; List> descendentMatches = new List>(); while (c != null) { - c = c.VisualParent; + c = c.LogicalParent; if (c is IStyleable) { diff --git a/Perspex.Styling/Styler.cs b/Perspex.Styling/Styler.cs index 8ededfe417..263cc33d4a 100644 --- a/Perspex.Styling/Styler.cs +++ b/Perspex.Styling/Styler.cs @@ -8,6 +8,7 @@ namespace Perspex.Styling { using System; using System.Linq; + using Perspex.VisualTree; using Splat; public class Styler : IStyler diff --git a/Perspex.Themes.Default/ButtonStyle.cs b/Perspex.Themes.Default/ButtonStyle.cs index 925d9c1ae6..bc64c8f5f1 100644 --- a/Perspex.Themes.Default/ButtonStyle.cs +++ b/Perspex.Themes.Default/ButtonStyle.cs @@ -78,6 +78,7 @@ namespace Perspex.Themes.Default Padding = new Thickness(3), Content = new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~Button.ContentProperty], [~ContentPresenter.HorizontalAlignmentProperty] = control[~Button.HorizontalContentAlignmentProperty], [~ContentPresenter.VerticalAlignmentProperty] = control[~Button.VerticalContentAlignmentProperty], diff --git a/Perspex.Themes.Default/CheckBoxStyle.cs b/Perspex.Themes.Default/CheckBoxStyle.cs index b55de93331..2c8c9ed48b 100644 --- a/Perspex.Themes.Default/CheckBoxStyle.cs +++ b/Perspex.Themes.Default/CheckBoxStyle.cs @@ -83,6 +83,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + Id = "contentPresenter", Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~CheckBox.ContentProperty], diff --git a/Perspex.Themes.Default/ContentControlStyle.cs b/Perspex.Themes.Default/ContentControlStyle.cs index 8afb594a5e..4060c3bd44 100644 --- a/Perspex.Themes.Default/ContentControlStyle.cs +++ b/Perspex.Themes.Default/ContentControlStyle.cs @@ -31,7 +31,8 @@ namespace Perspex.Themes.Default private Control Template(ContentControl control) { return new ContentPresenter - { + { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], }; } diff --git a/Perspex.Themes.Default/RadioButtonStyle.cs b/Perspex.Themes.Default/RadioButtonStyle.cs index 1f374df041..0393f4ea86 100644 --- a/Perspex.Themes.Default/RadioButtonStyle.cs +++ b/Perspex.Themes.Default/RadioButtonStyle.cs @@ -82,6 +82,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + Id = "contentPresenter", Margin = new Thickness(4, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~RadioButton.ContentProperty], diff --git a/Perspex.Themes.Default/ScrollViewerStyle.cs b/Perspex.Themes.Default/ScrollViewerStyle.cs index a1d51c9024..7a14e1cd65 100644 --- a/Perspex.Themes.Default/ScrollViewerStyle.cs +++ b/Perspex.Themes.Default/ScrollViewerStyle.cs @@ -47,7 +47,7 @@ namespace Perspex.Themes.Default { new ScrollContentPresenter { - Id = "presenter", + Id = "contentPresenter", [~ScrollContentPresenter.ContentProperty] = control[~ScrollViewer.ContentProperty], [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], diff --git a/Perspex.Themes.Default/TabControlStyle.cs b/Perspex.Themes.Default/TabControlStyle.cs index b902e46a75..b3b919374a 100644 --- a/Perspex.Themes.Default/TabControlStyle.cs +++ b/Perspex.Themes.Default/TabControlStyle.cs @@ -46,6 +46,7 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~TabControl.SelectedContentProperty], [Grid.RowProperty] = 1, } diff --git a/Perspex.Themes.Default/TabItemStyle.cs b/Perspex.Themes.Default/TabItemStyle.cs index eac19be1b9..b3dcba8f76 100644 --- a/Perspex.Themes.Default/TabItemStyle.cs +++ b/Perspex.Themes.Default/TabItemStyle.cs @@ -41,6 +41,7 @@ namespace Perspex.Themes.Default { return new ContentPresenter { + Id = "headerPresenter", [~ContentPresenter.ContentProperty] = control[~TabItem.HeaderProperty], }; } diff --git a/Perspex.Themes.Default/TabStripStyle.cs b/Perspex.Themes.Default/TabStripStyle.cs index 6a46bee554..eed4683886 100644 --- a/Perspex.Themes.Default/TabStripStyle.cs +++ b/Perspex.Themes.Default/TabStripStyle.cs @@ -40,6 +40,7 @@ namespace Perspex.Themes.Default { return new ItemsPresenter { + Id = "itemsPresenter", [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], }; diff --git a/Perspex.Themes.Default/ToggleButtonStyle.cs b/Perspex.Themes.Default/ToggleButtonStyle.cs index a8bd8d7155..1106030ea2 100644 --- a/Perspex.Themes.Default/ToggleButtonStyle.cs +++ b/Perspex.Themes.Default/ToggleButtonStyle.cs @@ -93,6 +93,7 @@ namespace Perspex.Themes.Default Padding = new Thickness(3), Content = new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~ToggleButton.ContentProperty], [~ContentPresenter.HorizontalAlignmentProperty] = control[~ToggleButton.HorizontalContentAlignmentProperty], [~ContentPresenter.VerticalAlignmentProperty] = control[~ToggleButton.VerticalContentAlignmentProperty], diff --git a/Perspex.Themes.Default/WindowStyle.cs b/Perspex.Themes.Default/WindowStyle.cs index 1b0f749e56..a6380b5017 100644 --- a/Perspex.Themes.Default/WindowStyle.cs +++ b/Perspex.Themes.Default/WindowStyle.cs @@ -37,6 +37,7 @@ namespace Perspex.Themes.Default [~Border.BackgroundProperty] = control[~Window.BackgroundProperty], Content = new ContentPresenter { + Id = "contentPresenter", [~ContentPresenter.ContentProperty] = control[~Window.ContentProperty], } };