Browse Source

Merge remote-tracking branch 'origin/logical-tree'

pull/39/head
Steven Kirk 12 years ago
parent
commit
ec5ff82d6f
  1. 2
      Perspex.Animation/PropertyTransitions.cs
  2. 15
      Perspex.Base/Collections/IPerspexList.cs
  3. 12
      Perspex.Base/Collections/PerspexList.cs
  4. 93
      Perspex.Base/Collections/PerspexReadOnlyListView.cs
  5. 113
      Perspex.Base/Collections/SingleItemPerspexList.cs
  6. 5
      Perspex.Base/Perspex.Base.csproj
  7. 141
      Perspex.Controls.UnitTests/ContentControlTests.cs
  8. 114
      Perspex.Controls.UnitTests/DecoratorTests.cs
  9. 294
      Perspex.Controls.UnitTests/ItemsControlTests.cs
  10. 82
      Perspex.Controls.UnitTests/PanelTests.cs
  11. 3
      Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  12. 1
      Perspex.Controls.UnitTests/TemplatedControlTests.cs
  13. 2
      Perspex.Controls/ColumnDefinitions.cs
  14. 52
      Perspex.Controls/ContentControl.cs
  15. 36
      Perspex.Controls/Control.cs
  16. 1
      Perspex.Controls/ControlExtensions.cs
  17. 1
      Perspex.Controls/DataTemplateExtensions.cs
  18. 25
      Perspex.Controls/Decorator.cs
  19. 1
      Perspex.Controls/GridSplitter.cs
  20. 43
      Perspex.Controls/ItemsControl.cs
  21. 27
      Perspex.Controls/Panel.cs
  22. 47
      Perspex.Controls/Presenters/ContentPresenter.cs
  23. 2
      Perspex.Controls/Presenters/ItemsPresenter.cs
  24. 1
      Perspex.Controls/Presenters/ScrollContentPresenter.cs
  25. 1
      Perspex.Controls/Presenters/TextPresenter.cs
  26. 1
      Perspex.Controls/Primitives/SelectingItemsControl.cs
  27. 20
      Perspex.Controls/Primitives/TabStrip.cs
  28. 5
      Perspex.Controls/Primitives/TemplatedControl.cs
  29. 1
      Perspex.Controls/RadioButton.cs
  30. 48
      Perspex.Controls/TabControl.cs
  31. 1
      Perspex.Controls/TreeViewItem.cs
  32. 69
      Perspex.Diagnostics/DevTools.cs
  33. 2
      Perspex.Diagnostics/Perspex.Diagnostics.csproj
  34. 10
      Perspex.Diagnostics/ViewModels/ControlDetails.cs
  35. 29
      Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs
  36. 63
      Perspex.Diagnostics/ViewModels/TreeNode.cs
  37. 41
      Perspex.Diagnostics/ViewModels/VisualTreeNode.cs
  38. 1
      Perspex.Input/FocusManager.cs
  39. 1
      Perspex.Input/InputElement.cs
  40. 1
      Perspex.Input/InputManager.cs
  41. 1
      Perspex.Input/MouseDevice.cs
  42. 1
      Perspex.Interactivity/Interactive.cs
  43. 1
      Perspex.Layout/LayoutManager.cs
  44. 1
      Perspex.Layout/Layoutable.cs
  45. 1
      Perspex.SceneGraph.UnitTests/VisualTests.cs
  46. 26
      Perspex.SceneGraph/ILogical.cs
  47. 3
      Perspex.SceneGraph/Perspex.SceneGraph.csproj
  48. 1
      Perspex.SceneGraph/Visual.cs
  49. 2
      Perspex.SceneGraph/VisualTree/VisualExtensions.cs
  50. 96
      Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  51. 5
      Perspex.Styling/Selectors.cs
  52. 1
      Perspex.Styling/Styler.cs
  53. 1
      Perspex.Themes.Default/ButtonStyle.cs
  54. 1
      Perspex.Themes.Default/CheckBoxStyle.cs
  55. 3
      Perspex.Themes.Default/ContentControlStyle.cs
  56. 1
      Perspex.Themes.Default/RadioButtonStyle.cs
  57. 2
      Perspex.Themes.Default/ScrollViewerStyle.cs
  58. 1
      Perspex.Themes.Default/TabControlStyle.cs
  59. 1
      Perspex.Themes.Default/TabItemStyle.cs
  60. 1
      Perspex.Themes.Default/TabStripStyle.cs
  61. 1
      Perspex.Themes.Default/ToggleButtonStyle.cs
  62. 1
      Perspex.Themes.Default/WindowStyle.cs

2
Perspex.Animation/PropertyTransitions.cs

@ -4,6 +4,8 @@
// </copyright>
// -----------------------------------------------------------------------
using Perspex.Collections;
namespace Perspex.Animation
{
using Perspex.Collections;

15
Perspex.Base/Collections/IPerspexList.cs

@ -0,0 +1,15 @@
// -----------------------------------------------------------------------
// <copyright file="IPerspexList.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Collections
{
using System.Collections;
using System.Collections.Generic;
public interface IPerspexList<T> : IList<T>, IList, IReadOnlyPerspexList<T>
{
}
}

12
Perspex.Base/Collections/PerspexList.cs

@ -13,7 +13,17 @@ namespace Perspex.Collections
using System.ComponentModel;
using System.Linq;
public class PerspexList<T> : IList<T>, IList, IReadOnlyPerspexList<T>, INotifyCollectionChanged, INotifyPropertyChanged
/// <summary>
/// A notifying list.
/// </summary>
/// <typeparam name="T">The type of the list items.</typeparam>
/// <remarks>
/// PerspexList is similar to <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>
/// except that when the <see cref="Clear"/> method is called, it notifies with a
/// <see cref="NotifyCollectionChangedAction.Remove"/> action, passing the items that were
/// removed.
/// </remarks>
public class PerspexList<T> : IPerspexList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
private List<T> inner;

93
Perspex.Base/Collections/PerspexReadOnlyListView.cs

@ -0,0 +1,93 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexReadOnlyListView.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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<TIn, TOut> : IReadOnlyPerspexList<TOut>, IDisposable
{
private IReadOnlyPerspexList<TIn> inner;
private Func<TIn, TOut> convert;
public PerspexReadOnlyListView(
IReadOnlyPerspexList<TIn> inner,
Func<TIn, TOut> 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<TOut> GetEnumerator()
{
return this.inner.Select(convert).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private IList<TOut> ConvertList(IList list)
{
return list.Cast<TIn>().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);
}
}
}
}

113
Perspex.Base/Collections/SingleItemPerspexList.cs

@ -0,0 +1,113 @@
// -----------------------------------------------------------------------
// <copyright file="PerspexList.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Collections
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
/// <summary>
/// Implements the <see cref="IReadOnlyPerspexList{T}"/> interface for single items.
/// </summary>
/// <typeparam name="T">The type of the single item.</typeparam>
/// <remarks>
/// 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
/// <see cref="IReadOnlyPerspexList{T}"/> interface. This class facilitates that
/// without creating an actual <see cref="PerspexList{T}"/>.
/// </remarks>
public class SingleItemPerspexList<T> : IReadOnlyPerspexList<T> 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<T> GetEnumerator()
{
return Enumerable.Repeat(this.item, this.Count).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}

5
Perspex.Base/Perspex.Base.csproj

@ -35,13 +35,16 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Binding.cs" />
<Compile Include="Collections\IPerspexList.cs" />
<Compile Include="Collections\PerspexReadOnlyListView.cs" />
<Compile Include="Collections\PerspexList.cs" />
<Compile Include="Collections\SingleItemPerspexList.cs" />
<Compile Include="Contract.cs" />
<Compile Include="Diagnostics\PerspexPropertyValue.cs" />
<Compile Include="IDescription.cs" />
<Compile Include="Collections\PerspexListExtensions.cs" />
<Compile Include="IObservableDescription.cs" />
<Compile Include="Collections\IReadOnlyPerspexList.cs" />
<Compile Include="Collections\PerspexList.cs" />
<Compile Include="PerspexObject.cs" />
<Compile Include="PerspexProperty.cs" />
<Compile Include="PerspexPropertyChangedEventArgs.cs" />

141
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<ContentControl>()), Times.Once());
styler.Verify(x => x.ApplyStyles(It.IsAny<Border>()), 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<ContentPresenter>();
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],
}
};

114
Perspex.Controls.UnitTests/DecoratorTests.cs

@ -0,0 +1,114 @@
// -----------------------------------------------------------------------
// <copyright file="DecoratorTests.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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);
}
}
}

294
Perspex.Controls.UnitTests/ItemsControlTests.cs

@ -0,0 +1,294 @@
// -----------------------------------------------------------------------
// <copyright file="ItemsControlTests.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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<IStyler>();
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<ItemsControl>()), Times.Once());
styler.Verify(x => x.ApplyStyles(It.IsAny<Border>()), Times.Once());
styler.Verify(x => x.ApplyStyles(It.IsAny<ItemsPresenter>()), Times.Once());
styler.Verify(x => x.ApplyStyles(It.IsAny<StackPanel>()), Times.Once());
styler.Verify(x => x.ApplyStyles(It.IsAny<TextBlock>()), 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<ItemsPresenter>().Single();
var panel = target.GetTemplateControls().OfType<StackPanel>().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<StackPanel>().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<string> { "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<string> { "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<ItemsControl>(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<IPlatformRenderInterface>();
Locator.CurrentMutable.RegisterConstant(renderInterface, typeof(IPlatformRenderInterface));
return result;
}
}
}

82
Perspex.Controls.UnitTests/PanelTests.cs

@ -0,0 +1,82 @@
// -----------------------------------------------------------------------
// <copyright file="PanelTests.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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());
}
}
}

3
Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -64,7 +64,10 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="ItemsControlTests.cs" />
<Compile Include="ContentControlTests.cs" />
<Compile Include="DecoratorTests.cs" />
<Compile Include="PanelTests.cs" />
<Compile Include="ControlTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TemplatedControlTests.cs" />

1
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

2
Perspex.Controls/ColumnDefinitions.cs

@ -4,6 +4,8 @@
// </copyright>
// -----------------------------------------------------------------------
using Perspex.Collections;
namespace Perspex.Controls
{
using Perspex.Collections;

52
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<object> ContentProperty =
PerspexProperty.Register<ContentControl, object>("Content");
@ -20,12 +23,42 @@ namespace Perspex.Controls
public static readonly PerspexProperty<VerticalAlignment> VerticalContentAlignmentProperty =
PerspexProperty.Register<ContentControl, VerticalAlignment>("VerticalContentAlignment");
private SingleItemPerspexList<ILogical> logicalChild = new SingleItemPerspexList<ILogical>();
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> 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>("contentPresenter");
if (this.presenter != null)
{
this.presenterSubscription = this.presenter.ChildObservable
.Subscribe(x => this.logicalChild.SingleItem = x);
}
}
}
}

36
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<Brush> BorderBrushProperty =
PerspexProperty.Register<Control, Brush>("BorderBrush");
@ -27,12 +29,20 @@ namespace Perspex.Controls
public static readonly PerspexProperty<Brush> ForegroundProperty =
PerspexProperty.Register<Control, Brush>("Foreground", new SolidColorBrush(0xff000000), inherits: true);
public static readonly PerspexProperty<Control> ParentProperty =
PerspexProperty.Register<Control, Control>("Parent");
public static readonly PerspexProperty<object> TagProperty =
PerspexProperty.Register<Control, object>("Tag");
public static readonly PerspexProperty<ITemplatedControl> TemplatedParentProperty =
PerspexProperty.Register<Control, ITemplatedControl>("TemplatedParent", inherits: true);
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategy.Bubble);
private static readonly IReadOnlyPerspexList<ILogical> EmptyChildren = new SingleItemPerspexList<ILogical>();
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> ILogical.LogicalChildren
{
get { return EmptyChildren; }
}
public void BringIntoView()
{
this.BringIntoView(new Rect(this.ActualSize));

1
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
{

1
Perspex.Controls/DataTemplateExtensions.cs

@ -7,6 +7,7 @@
namespace Perspex.Controls
{
using System.Linq;
using Perspex.VisualTree;
using Splat;
public static class DataTemplateExtensions

25
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<Control> ContentProperty =
PerspexProperty.Register<Decorator, Control>("Content");
@ -20,16 +21,25 @@ namespace Perspex.Controls
public static readonly PerspexProperty<Thickness> PaddingProperty =
PerspexProperty.Register<Decorator, Thickness>("Padding");
private SingleItemPerspexList<ILogical> logicalChild = new SingleItemPerspexList<ILogical>();
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> ILogical.LogicalChildren
{
get { return this.logicalChild; }
}
protected override Size ArrangeOverride(Size finalSize)
{
Control content = this.Content;

1
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
{

43
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<IVisual, ILogical> 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> ILogical.LogicalChildren
{
get
{
if (this.logicalChildren == null)
{
this.logicalChildren = new PerspexReadOnlyListView<IVisual, ILogical>(
new PerspexList<IVisual>(),
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>("itemsPresenter");
if (this.presenter != null)
{
var panel = (IVisual)this.presenter.GetVisualChildren().Single();
this.logicalChildren = new PerspexReadOnlyListView<IVisual, ILogical>(
panel.VisualChildren,
x => (ILogical)x);
}
}
private void ItemsChanged(Tuple<IEnumerable, IEnumerable> value)
{
INotifyPropertyChanged inpc = value.Item1 as INotifyPropertyChanged;

27
Perspex.Controls/Panel.cs

@ -9,11 +9,12 @@ namespace Perspex.Controls
using System;
using System.Collections.Specialized;
using System.Linq;
using Perspex.Collections;
/// <summary>
/// Base class for controls that can contain multiple children.
/// </summary>
public class Panel : Control
public class Panel : Control, ILogical
{
private Controls children;
@ -54,17 +55,41 @@ namespace Perspex.Controls
}
}
IReadOnlyPerspexList<ILogical> 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<Visual>());
foreach (var child in e.NewItems.OfType<Control>())
{
child.Parent = logicalParent;
}
break;
case NotifyCollectionChangedAction.Remove:
this.RemoveVisualChildren(e.OldItems.OfType<Visual>());
foreach (var child in e.OldItems.OfType<Control>())
{
child.Parent = null;
}
break;
case NotifyCollectionChangedAction.Reset:

47
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<Control> childObservable = new BehaviorSubject<Control>(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<Control> 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;
}
}

2
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

1
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
{

1
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
{

1
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
{

20
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<TabItem> SelectedTabProperty =
TabControl.SelectedTabProperty.AddOwner<TabStrip>();
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;

5
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<T>(string id) where T : Control
{
return this.GetTemplateControls()
.Where(x => x.TemplatedParent == this)
.OfType<T>().FirstOrDefault(x => x.Id == id);
return (T)this.GetTemplateControls().SingleOrDefault(x => x.Id == id);
}
protected T GetTemplateChild<T>(string id) where T : Control

1
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
{

48
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<object> SelectedContentProperty =
PerspexProperty.Register<TabControl, object>("SelectedContent");
public static readonly PerspexProperty<TabItem> SelectedTabProperty =
PerspexProperty.Register<TabControl, TabItem>("SelectedTab");
private TabStrip tabStrip;
private ContentPresenter presenter;
private IDisposable presenterSubscription;
private SingleItemPerspexList<ILogical> logicalChild = new SingleItemPerspexList<ILogical>();
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> 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>("contentPresenter");
if (this.presenter != null)
{
this.presenterSubscription = this.presenter.ChildObservable
.Subscribe(x => this.logicalChild.SingleItem = x);
}
this.tabStrip = this.GetTemplateControls().OfType<TabStrip>().FirstOrDefault();
this.BindTwoWay(TabControl.SelectedItemProperty, this.tabStrip, TabControl.SelectedItemProperty);
}

1
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
{

69
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<VisualTreeNode>(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<LogicalTreeNode>(GetHeader, x => x.Children),
new TreeDataTemplate<VisualTreeNode>(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<VisualTreeNode>()
.Select(x => new ControlDetails(x.Visual)),
.Cast<TreeNode>()
.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

2
Perspex.Diagnostics/Perspex.Diagnostics.csproj

@ -76,6 +76,8 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\PropertyDetails.cs" />
<Compile Include="ViewModels\ControlDetails.cs" />
<Compile Include="ViewModels\LogicalTreeNode.cs" />
<Compile Include="ViewModels\TreeNode.cs" />
<Compile Include="ViewModels\VisualTreeNode.cs" />
</ItemGroup>
<ItemGroup>

10
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);
}

29
Perspex.Diagnostics/ViewModels/LogicalTreeNode.cs

@ -0,0 +1,29 @@
// -----------------------------------------------------------------------
// <copyright file="LogicalTreeNode.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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;
}
}
}

63
Perspex.Diagnostics/ViewModels/TreeNode.cs

@ -0,0 +1,63 @@
// -----------------------------------------------------------------------
// <copyright file="LogicalTreeNode.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
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<TreeNode> 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;
}
}
}

41
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<VisualTreeNode> 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; }
}
}

1
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

1
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
{

1
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
{

1
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

1
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
{

1
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;
/// <summary>
/// Manages measuring and arranging of controls.

1
Perspex.Layout/Layoutable.cs

@ -8,6 +8,7 @@ namespace Perspex.Layout
{
using System;
using System.Linq;
using Perspex.VisualTree;
using Splat;
public enum HorizontalAlignment

1
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

26
Perspex.SceneGraph/ILogical.cs

@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="ILogical.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
{
using Perspex.Collections;
/// <summary>
/// Represents a node in the logical tree.
/// </summary>
public interface ILogical
{
/// <summary>
/// Gets the logical parent.
/// </summary>
ILogical LogicalParent { get; }
/// <summary>
/// Gets the logical children.
/// </summary>
IReadOnlyPerspexList<ILogical> LogicalChildren { get; }
}
}

3
Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -45,6 +45,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="ILogical.cs" />
<Compile Include="Media\Brush.cs" />
<Compile Include="Media\Brushes.cs" />
<Compile Include="Media\Color.cs" />
@ -91,7 +92,7 @@
<Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" />
<Compile Include="Visual.cs" />
<Compile Include="VisualExtensions.cs" />
<Compile Include="VisualTree\VisualExtensions.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Splat">

1
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

2
Perspex.SceneGraph/VisualExtensions.cs → Perspex.SceneGraph/VisualTree/VisualExtensions.cs

@ -4,7 +4,7 @@
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex
namespace Perspex.VisualTree
{
using System;
using System.Collections.Generic;

96
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<TestLogical1>();
////var child = new Mock<TestLogical2>();
////child.Setup(x => x.LogicalParent).Returns(parent.Object);
var parent = new Mock<TestLogical1>();
var child = new Mock<TestLogical2>();
var childStyleable = child.As<IStyleable>();
////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<TestLogical1>();
////var parent = new Mock<TestLogical2>();
////var child = new Mock<TestLogical3>();
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
////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<TestLogical1>();
////var parent = new Mock<TestLogical2>();
////var child = new Mock<TestLogical3>();
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
////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<TestLogical1>();
////var parent = new Mock<TestLogical2>();
////var child = new Mock<TestLogical3>();
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
////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<TestLogical1>().Class("foo").Descendent().OfType<TestLogical3>();
var selector = new Selector().OfType<TestLogical1>().Class("foo").Descendent().OfType<TestLogical3>();
////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<ILogical> LogicalChildren { get; }
public abstract ILogical LogicalParent { get; }
public abstract ITemplatedControl TemplatedParent { get; }
public abstract IDisposable Bind(PerspexProperty property, IObservable<object> 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
{
}
}
}

5
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<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
while (c != null)
{
c = c.VisualParent;
c = c.LogicalParent;
if (c is IStyleable)
{

1
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

1
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],

1
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],

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

1
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],

2
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],

1
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,
}

1
Perspex.Themes.Default/TabItemStyle.cs

@ -41,6 +41,7 @@ namespace Perspex.Themes.Default
{
return new ContentPresenter
{
Id = "headerPresenter",
[~ContentPresenter.ContentProperty] = control[~TabItem.HeaderProperty],
};
}

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

1
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],

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

Loading…
Cancel
Save