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
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],
}
};