Browse Source

Merge branch 'name-scope'

pull/325/head
Steven Kirk 11 years ago
parent
commit
335c8b239f
  1. 7
      samples/BindingTest/MainWindow.paml
  2. 30
      src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs
  3. 17
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  4. 22
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
  5. 143
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  6. 4
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  7. 6
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  8. 2
      src/Markup/Perspex.Markup.Xaml/OmniXAML
  9. 6
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  10. 2
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  11. 60
      src/Markup/Perspex.Markup/ControlLocator.cs
  12. 81
      src/Markup/Perspex.Markup/Data/ExpressionObserver.cs
  13. 21
      src/Markup/Perspex.Markup/Perspex.Markup.csproj
  14. 12
      src/Perspex.Controls/Button.cs
  15. 6
      src/Perspex.Controls/Carousel.cs
  16. 4
      src/Perspex.Controls/ContentControl.cs
  17. 36
      src/Perspex.Controls/Control.cs
  18. 31
      src/Perspex.Controls/ControlExtensions.cs
  19. 4
      src/Perspex.Controls/DropDown.cs
  20. 4
      src/Perspex.Controls/GridSplitter.cs
  21. 4
      src/Perspex.Controls/ItemsControl.cs
  22. 22
      src/Perspex.Controls/Menu.cs
  23. 6
      src/Perspex.Controls/MenuItem.cs
  24. 20
      src/Perspex.Controls/Primitives/AccessText.cs
  25. 20
      src/Perspex.Controls/Primitives/Popup.cs
  26. 4
      src/Perspex.Controls/Primitives/PopupRoot.cs
  27. 26
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  28. 4
      src/Perspex.Controls/ProgressBar.cs
  29. 21
      src/Perspex.Controls/Templates/TemplateExtensions.cs
  30. 4
      src/Perspex.Controls/TextBox.cs
  31. 4
      src/Perspex.Controls/TopLevel.cs
  32. 4
      src/Perspex.Controls/TreeViewItem.cs
  33. 36
      src/Perspex.Controls/Window.cs
  34. 8
      src/Perspex.Input/InputElement.cs
  35. 43
      src/Perspex.SceneGraph/INameScope.cs
  36. 8
      src/Perspex.SceneGraph/INamed.cs
  37. 11
      src/Perspex.SceneGraph/IVisual.cs
  38. 109
      src/Perspex.SceneGraph/NameScope.cs
  39. 19
      src/Perspex.SceneGraph/NameScopeEventArgs.cs
  40. 68
      src/Perspex.SceneGraph/NameScopeExtensions.cs
  41. 6
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  42. 169
      src/Perspex.SceneGraph/Visual.cs
  43. 36
      src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs
  44. 1
      src/Perspex.Styling/Perspex.Styling.csproj
  45. 3
      src/Perspex.Themes.Default/ScrollViewer.paml
  46. 4
      src/Perspex.Themes.Default/TextBox.paml
  47. 2
      tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs
  48. 7
      tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs
  49. 4
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  50. 23
      tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  51. 6
      tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs
  52. 32
      tests/Perspex.Controls.UnitTests/TabControlTests.cs
  53. 2
      tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs
  54. 122
      tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs
  55. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionNodeBuilderTests.cs
  56. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionNodeBuilderTests_Errors.cs
  57. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs
  58. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs
  59. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs
  60. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_PerspexProperty.cs
  61. 30
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs
  62. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs
  63. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs
  64. 2
      tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs
  65. 2
      tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs
  66. 57
      tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj
  67. 61
      tests/Perspex.Markup.UnitTests/TestRoot.cs
  68. 1
      tests/Perspex.Markup.UnitTests/packages.config
  69. 20
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
  70. 83
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  71. 6
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  72. 6
      tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs
  73. 2
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj
  74. 2
      tests/Perspex.Markup.Xaml.UnitTests/SamplePerspexObject.cs
  75. 61
      tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs
  76. 2
      tests/Perspex.Markup.Xaml.UnitTests/TypeProviderMock.cs
  77. 2
      tests/Perspex.Markup.Xaml.UnitTests/ViewModelMock.cs
  78. 55
      tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs
  79. 1
      tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
  80. 31
      tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
  81. 39
      tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
  82. 87
      tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

7
samples/BindingTest/MainWindow.paml

@ -6,7 +6,7 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel Margin="18" Gap="4" Width="200"> <StackPanel Margin="18" Gap="4" Width="200">
<TextBlock FontSize="16" Text="Simple Bindings"/> <TextBlock FontSize="16" Text="Simple Bindings"/>
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}"/> <TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/> <TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/> <TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/> <TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
@ -30,6 +30,11 @@
<TextBlock Text="{Binding Path=DoubleValue}"/> <TextBlock Text="{Binding Path=DoubleValue}"/>
<ProgressBar Maximum="10" Value="{Binding DoubleValue}"/> <ProgressBar Maximum="10" Value="{Binding DoubleValue}"/>
</StackPanel> </StackPanel>
<StackPanel Margin="18" Gap="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Binding Sources"/>
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
Text="{Binding #first.Text, Mode=TwoWay}"/>
</StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</TabItem> </TabItem>

30
src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs

@ -0,0 +1,30 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Xaml.Context
{
internal class NameScopeWrapper : OmniXaml.INameScope
{
private Perspex.INameScope _inner;
public NameScopeWrapper(Perspex.INameScope inner)
{
_inner = inner;
}
public object Find(string name)
{
return _inner.Find(name);
}
public void Register(string name, object scopedElement)
{
_inner.Register(name, scopedElement);
}
public void Unregister(string name)
{
_inner.Unregister(name);
}
}
}

17
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Glass; using Glass;
@ -11,21 +9,17 @@ using OmniXaml;
using OmniXaml.Builder; using OmniXaml.Builder;
using OmniXaml.TypeConversion; using OmniXaml.TypeConversion;
using OmniXaml.Typing; using OmniXaml.Typing;
using Perspex.Collections;
using Perspex.Controls; using Perspex.Controls;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input; using Perspex.Input;
using Perspex.Markup.Xaml.Templates;
using Perspex.Markup.Xaml.Converters; using Perspex.Markup.Xaml.Converters;
using Perspex.Markup.Xaml.Data;
using Perspex.Markup.Xaml.MarkupExtensions;
using Perspex.Media; using Perspex.Media;
using Perspex.Media.Imaging; using Perspex.Media.Imaging;
using Perspex.Metadata; using Perspex.Metadata;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Styling; using Perspex.Styling;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using System.Collections;
using Perspex.Collections;
namespace Perspex.Markup.Xaml.Context namespace Perspex.Markup.Xaml.Context
{ {
@ -48,9 +42,10 @@ namespace Perspex.Markup.Xaml.Context
var xamlNamespaceRegistry = CreateXamlNamespaceRegistry(); var xamlNamespaceRegistry = CreateXamlNamespaceRegistry();
var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider); var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider);
typeRepository.RegisterMetadata(new Metadata<Setter>().WithMemberDependency(x => x.Value, x => x.Property)); typeRepository.RegisterMetadata(new GenericMetadata<Visual>().WithRuntimeNameProperty(d => d.Name));
typeRepository.RegisterMetadata(new GenericMetadata<Setter>().WithMemberDependency(x => x.Value, x => x.Property));
typeRepository.RegisterMetadata( typeRepository.RegisterMetadata(
new Metadata<SelectingItemsControl>() new GenericMetadata<SelectingItemsControl>()
.WithMemberDependency(x => x.SelectedIndex, x => x.Items) .WithMemberDependency(x => x.SelectedIndex, x => x.Items)
.WithMemberDependency(x => x.SelectedItem, x => x.Items)); .WithMemberDependency(x => x.SelectedItem, x => x.Items));

22
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs

@ -18,6 +18,28 @@ namespace Perspex.Markup.Xaml.Context
{ {
} }
public override OmniXaml.INameScope GetNamescope(object instance)
{
var result = this.UnderlyingType as OmniXaml.INameScope;
if (result == null)
{
var visual = instance as Visual;
if (visual != null)
{
var perspexNs = (instance as Perspex.INameScope) ?? NameScope.GetNameScope(visual);
if (perspexNs != null)
{
result = new NameScopeWrapper(perspexNs);
}
}
}
return result;
}
protected override XamlMember LookupMember(string name) protected override XamlMember LookupMember(string name)
{ {
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider); return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider);

143
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using OmniXaml.TypeConversion;
using Perspex.Controls; using Perspex.Controls;
using Perspex.Markup.Data; using Perspex.Markup.Data;
@ -20,6 +19,11 @@ namespace Perspex.Markup.Xaml.Data
/// </summary> /// </summary>
public IValueConverter Converter { get; set; } public IValueConverter Converter { get; set; }
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string ElementName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the binding mode. /// Gets or sets the binding mode.
/// </summary> /// </summary>
@ -38,7 +42,7 @@ namespace Perspex.Markup.Xaml.Data
/// <summary> /// <summary>
/// Gets or sets the binding path. /// Gets or sets the binding path.
/// </summary> /// </summary>
public string SourcePropertyPath { get; set; } public string Path { get; set; }
/// <summary> /// <summary>
/// Applies the binding to a property on an instance. /// Applies the binding to a property on an instance.
@ -47,8 +51,11 @@ namespace Perspex.Markup.Xaml.Data
/// <param name="property">The target property.</param> /// <param name="property">The target property.</param>
public void Bind(IObservablePropertyBag instance, PerspexProperty property) public void Bind(IObservablePropertyBag instance, PerspexProperty property)
{ {
Contract.Requires<ArgumentNullException>(instance != null);
Contract.Requires<ArgumentNullException>(property != null);
var subject = CreateSubject( var subject = CreateSubject(
instance, instance,
property.PropertyType, property.PropertyType,
property == Control.DataContextProperty); property == Control.DataContextProperty);
@ -72,15 +79,33 @@ namespace Perspex.Markup.Xaml.Data
Type targetType, Type targetType,
bool targetIsDataContext = false) bool targetIsDataContext = false)
{ {
Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
ExpressionObserver observer; ExpressionObserver observer;
if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) if (pathInfo.ElementName != null || ElementName != null)
{ {
observer = CreateDataContextExpressionSubject(target, targetIsDataContext); observer = CreateElementSubject(
(IControl)target,
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContextSubject(
target,
pathInfo.Path,
targetIsDataContext);
} }
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{ {
observer = CreateTemplatedParentExpressionSubject(target); observer = CreateTemplatedParentSubject(
target,
pathInfo.Path);
} }
else else
{ {
@ -88,8 +113,8 @@ namespace Perspex.Markup.Xaml.Data
} }
return new ExpressionSubject( return new ExpressionSubject(
observer, observer,
targetType, targetType,
Converter ?? DefaultValueConverter.Instance); Converter ?? DefaultValueConverter.Instance);
} }
@ -101,6 +126,10 @@ namespace Perspex.Markup.Xaml.Data
/// <param name="subject">The binding subject.</param> /// <param name="subject">The binding subject.</param>
internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject) internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
{ {
Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(subject != null);
var mode = Mode == BindingMode.Default ? var mode = Mode == BindingMode.Default ?
property.DefaultBindingMode : Mode; property.DefaultBindingMode : Mode;
@ -117,7 +146,7 @@ namespace Perspex.Markup.Xaml.Data
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext => target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{ {
subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority)); subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
}); });
break; break;
case BindingMode.OneWayToSource: case BindingMode.OneWayToSource:
target.GetObservable(property).Subscribe(subject); target.GetObservable(property).Subscribe(subject);
@ -125,10 +154,60 @@ namespace Perspex.Markup.Xaml.Data
} }
} }
private ExpressionObserver CreateDataContextExpressionSubject( private static PathInfo ParsePath(string path)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = string.Empty;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else
{
result.Path = path;
}
return result;
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private ExpressionObserver CreateDataContextSubject(
IObservablePropertyBag target, IObservablePropertyBag target,
string path,
bool targetIsDataContext) bool targetIsDataContext)
{ {
Contract.Requires<ArgumentNullException>(target != null);
var dataContextHost = targetIsDataContext ? var dataContextHost = targetIsDataContext ?
target.InheritanceParent as IObservablePropertyBag : target; target.InheritanceParent as IObservablePropertyBag : target;
@ -136,7 +215,7 @@ namespace Perspex.Markup.Xaml.Data
{ {
var result = new ExpressionObserver( var result = new ExpressionObserver(
() => dataContextHost.GetValue(Control.DataContextProperty), () => dataContextHost.GetValue(Control.DataContextProperty),
GetExpression()); path);
dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x => dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
result.UpdateRoot()); result.UpdateRoot());
return result; return result;
@ -148,11 +227,15 @@ namespace Perspex.Markup.Xaml.Data
} }
} }
private ExpressionObserver CreateTemplatedParentExpressionSubject(IObservablePropertyBag target) private ExpressionObserver CreateTemplatedParentSubject(
IObservablePropertyBag target,
string path)
{ {
Contract.Requires<ArgumentNullException>(target != null);
var result = new ExpressionObserver( var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty), () => target.GetValue(Control.TemplatedParentProperty),
GetExpression()); path);
if (target.GetValue(Control.TemplatedParentProperty) == null) if (target.GetValue(Control.TemplatedParentProperty) == null)
{ {
@ -167,10 +250,38 @@ namespace Perspex.Markup.Xaml.Data
return result; return result;
} }
private string GetExpression() private ExpressionObserver CreateElementSubject(
IControl target,
string elementName,
string path)
{
Contract.Requires<ArgumentNullException>(target != null);
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path);
return result;
}
private IControl LookupNamedControl(IControl target)
{
Contract.Requires<ArgumentNullException>(target != null);
var nameScope = target.FindNameScope();
if (nameScope == null)
{
throw new InvalidOperationException(
"Could not find name scope for ElementName binding.");
}
return nameScope.Find<IControl>(ElementName);
}
private class PathInfo
{ {
return SourcePropertyPath == null || SourcePropertyPath == "." ? public string Path { get; set; }
string.Empty : SourcePropertyPath; public string ElementName { get; set; }
} }
} }
} }

4
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -22,12 +22,14 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
return new Binding return new Binding
{ {
Converter = Converter, Converter = Converter,
ElementName = ElementName,
Mode = Mode, Mode = Mode,
SourcePropertyPath = Path, Path = Path,
}; };
} }
public IValueConverter Converter { get; set; } public IValueConverter Converter { get; set; }
public string ElementName { get; set; }
public BindingMode Mode { get; set; } public BindingMode Mode { get; set; }
public string Path { get; set; } public string Path { get; set; }
} }

6
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -19,17 +19,19 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public override object ProvideValue(MarkupExtensionContext extensionContext) public override object ProvideValue(MarkupExtensionContext extensionContext)
{ {
return new Data.Binding return new Binding
{ {
Converter = Converter, Converter = Converter,
ElementName = ElementName,
Mode = Mode, Mode = Mode,
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
SourcePropertyPath = Path, Path = Path,
}; };
} }
public IValueConverter Converter { get; set; } public IValueConverter Converter { get; set; }
public string ElementName { get; set; }
public BindingMode Mode { get; set; } public BindingMode Mode { get; set; }
public string Path { get; set; } public string Path { get; set; }
} }

2
src/Markup/Perspex.Markup.Xaml/OmniXAML

@ -1 +1 @@
Subproject commit 55fc2abc715885d3686f8c8605f34714445cd72f Subproject commit 3e3b46ba66941da925092e2977003d0553cfc907

6
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -38,6 +38,7 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Context\NameScopeWrapper.cs" />
<Compile Include="Converters\PerspexListTypeConverter.cs" /> <Compile Include="Converters\PerspexListTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" /> <Compile Include="Converters\RelativeRectTypeConverter.cs" />
<Compile Include="Data\IXamlBinding.cs" /> <Compile Include="Data\IXamlBinding.cs" />
@ -185,7 +186,6 @@
<Compile Include="OmniXAML\Source\OmniXaml\Sequence.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Sequence.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TemplateHostingObjectAssembler.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TemplateHostingObjectAssembler.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeContext.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeContext.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeContextBuilder.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\BooleanConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\BooleanConverter.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\DoubleTypeConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\DoubleTypeConverter.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\IntTypeConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\IntTypeConverter.cs" />
@ -205,11 +205,15 @@
<Compile Include="OmniXAML\Source\OmniXaml\Typing\AttachableXamlMember.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\AttachableXamlMember.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\ClrNamespace.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\ClrNamespace.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\CoreTypes.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\CoreTypes.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\DependencyRegistration.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\DependencyRegistrations.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\GenericMetadata.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlMemberValuePlugin.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlMemberValuePlugin.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlNamespaceRegistry.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlNamespaceRegistry.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlTypeRepository.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlTypeRepository.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\MemberValuePlugin.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\MemberValuePlugin.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\Metadata.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\Metadata.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\MetadataProvider.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\MutableXamlMember.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\MutableXamlMember.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\Namespace.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\Namespace.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\PrefixRegistration.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Typing\PrefixRegistration.cs" />

2
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -35,7 +35,7 @@ namespace Perspex.Markup.Xaml.Templates
{ {
if (ItemsSource != null) if (ItemsSource != null)
{ {
var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath); var obs = new ExpressionObserver(item, ItemsSource.Path);
return obs.Take(1).Wait() as IEnumerable; return obs.Take(1).Wait() as IEnumerable;
} }

60
src/Markup/Perspex.Markup/ControlLocator.cs

@ -0,0 +1,60 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Controls;
namespace Perspex.Markup
{
/// <summary>
/// Locates controls relative to other controls.
/// </summary>
public static class ControlLocator
{
/// <summary>
/// Tracks a named control relative to another control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="name">The name of the control to find.</param>
public static IObservable<IControl> Track(IControl relativeTo, string name)
{
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
.Select(x => x.EventArgs.NameScope)
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
{
if (nameScope != null)
{
var registered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Registered += x,
x => nameScope.Registered -= x)
.Where(x => x.EventArgs.Name == name)
.Select(x => x.EventArgs.Element)
.OfType<IControl>();
var unregistered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Unregistered += x,
x => nameScope.Unregistered -= x);
return registered
.StartWith(nameScope.Find<IControl>(name))
.TakeUntil(unregistered);
}
else
{
return Observable.Return<IControl>(null);
}
}).Switch();
}
}
}

81
src/Markup/Perspex.Markup/Data/ExpressionObserver.cs

@ -26,9 +26,12 @@ namespace Perspex.Markup.Data
new InpcPropertyAccessorPlugin(), new InpcPropertyAccessorPlugin(),
}; };
private Func<object> _root; private readonly object _root;
private readonly Func<object> _rootGetter;
private readonly IObservable<object> _rootObservable;
private IDisposable _rootObserverSubscription;
private int _count; private int _count;
private ExpressionNode _node; private readonly ExpressionNode _node;
private ISubject<object> _empty; private ISubject<object> _empty;
/// <summary> /// <summary>
@ -37,21 +40,50 @@ namespace Perspex.Markup.Data
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
public ExpressionObserver(object root, string expression) public ExpressionObserver(object root, string expression)
: this(() => root, expression)
{ {
Contract.Requires<ArgumentNullException>(expression != null);
_root = root;
if (!string.IsNullOrWhiteSpace(expression))
{
_node = ExpressionNodeBuilder.Build(expression);
}
Expression = expression;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary> /// </summary>
/// <param name="root">A function which gets the root object.</param> /// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param> /// <param name="expression">The expression.</param>
public ExpressionObserver(Func<object> root, string expression) public ExpressionObserver(IObservable<object> rootObservable, string expression)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(rootObservable != null);
Contract.Requires<ArgumentNullException>(expression != null); Contract.Requires<ArgumentNullException>(expression != null);
_root = root; _rootObservable = rootObservable;
if (!string.IsNullOrWhiteSpace(expression))
{
_node = ExpressionNodeBuilder.Build(expression);
}
Expression = expression;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</param>
/// <param name="expression">The expression.</param>
public ExpressionObserver(Func<object> rootGetter, string expression)
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
Contract.Requires<ArgumentNullException>(expression != null);
_rootGetter = rootGetter;
if (!string.IsNullOrWhiteSpace(expression)) if (!string.IsNullOrWhiteSpace(expression))
{ {
@ -105,9 +137,13 @@ namespace Perspex.Markup.Data
{ {
return (Leaf as PropertyAccessorNode)?.PropertyType; return (Leaf as PropertyAccessorNode)?.PropertyType;
} }
else if(_rootGetter != null)
{
return _rootGetter()?.GetType();
}
else else
{ {
return _root()?.GetType(); return _root?.GetType();
} }
} }
finally finally
@ -134,19 +170,19 @@ namespace Perspex.Markup.Data
} }
/// <summary> /// <summary>
/// Causes the root object to be re-read. /// Causes the root object to be re-read from the root getter.
/// </summary> /// </summary>
public void UpdateRoot() public void UpdateRoot()
{ {
if (_count > 0) if (_count > 0 && _rootGetter != null)
{ {
if (_node != null) if (_node != null)
{ {
_node.Target = _root(); _node.Target = _rootGetter();
} }
else if (_empty != null) else if (_empty != null)
{ {
_empty.OnNext(_root()); _empty.OnNext(_rootGetter());
} }
} }
} }
@ -170,7 +206,7 @@ namespace Perspex.Markup.Data
{ {
if (_empty == null) if (_empty == null)
{ {
_empty = new BehaviorSubject<object>(_root()); _empty = new BehaviorSubject<object>(_rootGetter());
} }
return _empty.Subscribe(observer); return _empty.Subscribe(observer);
@ -181,7 +217,18 @@ namespace Perspex.Markup.Data
{ {
if (_count++ == 0 && _node != null) if (_count++ == 0 && _node != null)
{ {
_node.Target = _root(); if (_rootGetter != null)
{
_node.Target = _rootGetter();
}
else if (_rootObservable != null)
{
_rootObserverSubscription = _rootObservable.Subscribe(x => _node.Target = x);
}
else
{
_node.Target = _root;
}
} }
} }
@ -189,6 +236,12 @@ namespace Perspex.Markup.Data
{ {
if (--_count == 0 && _node != null) if (--_count == 0 && _node != null)
{ {
if (_rootObserverSubscription != null)
{
_rootObserverSubscription.Dispose();
_rootObserverSubscription = null;
}
_node.Target = null; _node.Target = null;
} }
} }

21
src/Markup/Perspex.Markup/Perspex.Markup.csproj

@ -42,6 +42,7 @@
<Compile Include="Data\ExpressionNodeBuilder.cs" /> <Compile Include="Data\ExpressionNodeBuilder.cs" />
<Compile Include="Data\ExpressionParseException.cs" /> <Compile Include="Data\ExpressionParseException.cs" />
<Compile Include="Data\ExpressionSubject.cs" /> <Compile Include="Data\ExpressionSubject.cs" />
<Compile Include="ControlLocator.cs" />
<Compile Include="Data\Plugins\PerspexPropertyAccessorPlugin.cs" /> <Compile Include="Data\Plugins\PerspexPropertyAccessorPlugin.cs" />
<Compile Include="Data\Plugins\InpcPropertyAccessorPlugin.cs" /> <Compile Include="Data\Plugins\InpcPropertyAccessorPlugin.cs" />
<Compile Include="Data\Plugins\IPropertyAccessor.cs" /> <Compile Include="Data\Plugins\IPropertyAccessor.cs" />
@ -91,10 +92,30 @@
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project> <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name> <Name>Perspex.Base</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.SceneGraph\Perspex.SceneGraph.csproj"> <ProjectReference Include="..\..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project> <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name> <Name>Perspex.SceneGraph</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.

12
src/Perspex.Controls/Button.cs

@ -147,13 +147,13 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
if (IsDefault) if (IsDefault)
{ {
var inputElement = root as IInputElement; var inputElement = e.Root as IInputElement;
if (inputElement != null) if (inputElement != null)
{ {
@ -198,13 +198,13 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(oldRoot); base.OnDetachedFromVisualTree(e);
if (IsDefault) if (IsDefault)
{ {
var inputElement = oldRoot as IInputElement; var inputElement = e.Root as IInputElement;
if (inputElement != null) if (inputElement != null)
{ {

6
src/Perspex.Controls/Carousel.cs

@ -55,11 +55,5 @@ namespace Perspex.Controls
{ {
// Ignore pointer presses. // Ignore pointer presses.
} }
/// <inheritdoc/>
protected override void OnTemplateApplied()
{
base.OnTemplateApplied();
}
} }
} }

4
src/Perspex.Controls/ContentControl.cs

@ -104,12 +104,12 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
// We allow ContentControls without ContentPresenters in the template. This can be // We allow ContentControls without ContentPresenters in the template. This can be
// useful for e.g. a simple ToggleButton that displays an image. There's no need to // useful for e.g. a simple ToggleButton that displays an image. There's no need to
// have a ContentPresenter in the visual tree for that. // have a ContentPresenter in the visual tree for that.
Presenter = this.FindTemplateChild<ContentPresenter>("PART_ContentPresenter"); Presenter = nameScope.Find<ContentPresenter>("PART_ContentPresenter");
} }
} }
} }

36
src/Perspex.Controls/Control.cs

@ -19,7 +19,6 @@ namespace Perspex.Controls
/// <remarks> /// <remarks>
/// The control class extends <see cref="InputElement"/> and adds the following features: /// The control class extends <see cref="InputElement"/> and adds the following features:
/// ///
/// - A <see cref="Name"/>.
/// - An inherited <see cref="DataContext"/>. /// - An inherited <see cref="DataContext"/>.
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control. /// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - A collection of class strings for custom styling. /// - A collection of class strings for custom styling.
@ -71,7 +70,6 @@ namespace Perspex.Controls
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates; private DataTemplates _dataTemplates;
private IControl _focusAdorner; private IControl _focusAdorner;
private string _name;
private IPerspexList<ILogical> _logicalChildren; private IPerspexList<ILogical> _logicalChildren;
private Styles _styles; private Styles _styles;
@ -165,36 +163,6 @@ namespace Perspex.Controls
} }
} }
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
/// <remarks>
/// A control's name is used to uniquely identify a control within the control's name
/// scope. Once a control is added to a visual tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (_name != null)
{
throw new InvalidOperationException("Name already set.");
}
if (((IVisual)this).VisualParent != null)
{
throw new InvalidOperationException("Cannot set Name : control already added to tree.");
}
_name = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the styles for the control. /// Gets or sets the styles for the control.
/// </summary> /// </summary>
@ -404,9 +372,9 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
IStyler styler = PerspexLocator.Current.GetService<IStyler>(); IStyler styler = PerspexLocator.Current.GetService<IStyler>();

31
src/Perspex.Controls/ControlExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved. // Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq; using System.Linq;
using Perspex.LogicalTree; using Perspex.LogicalTree;
using Perspex.Styling; using Perspex.Styling;
@ -39,17 +40,35 @@ namespace Perspex.Controls
} }
/// <summary> /// <summary>
/// Finds the named control in the specified control. /// Finds the named control in the scope of the specified control.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the control to find.</typeparam> /// <typeparam name="T">The type of the control to find.</typeparam>
/// <param name="control">The control.</param> /// <param name="control">The control to look in.</param>
/// <param name="name">The name of the control to find.</param> /// <param name="name">The name of the control to find.</param>
/// <returns>The control or null if not found.</returns> /// <returns>The control or null if not found.</returns>
public static T FindControl<T>(this IControl control, string name) where T : IControl public static T FindControl<T>(this IControl control, string name) where T : class, IControl
{
var nameScope = control.FindNameScope();
if (nameScope == null)
{
throw new InvalidOperationException("Could not find parent name scope.");
}
return nameScope.Find<T>(name);
}
/// <summary>
/// Finds the name scope for a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The control's name scope, or null if not found.</returns>
public static INameScope FindNameScope(this IControl control)
{ {
return control.GetLogicalDescendents() return control.GetSelfAndLogicalAncestors()
.OfType<T>() .OfType<Visual>()
.FirstOrDefault(x => x.Name == name); .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
} }
} }
} }

4
src/Perspex.Controls/DropDown.cs

@ -125,14 +125,14 @@ namespace Perspex.Controls
base.OnPointerPressed(e); base.OnPointerPressed(e);
} }
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
if (_popup != null) if (_popup != null)
{ {
_popup.Opened -= PopupOpened; _popup.Opened -= PopupOpened;
} }
_popup = this.GetTemplateChild<Popup>("PART_Popup"); _popup = nameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;
} }

4
src/Perspex.Controls/GridSplitter.cs

@ -34,9 +34,9 @@ namespace Perspex.Controls
} }
} }
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent<Grid>(); _grid = this.GetVisualParent<Grid>();
} }
} }

4
src/Perspex.Controls/ItemsControl.cs

@ -147,9 +147,9 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
Presenter = this.FindTemplateChild<IItemsPresenter>("PART_ItemsPresenter"); Presenter = nameScope.Find<IItemsPresenter>("PART_ItemsPresenter");
} }
/// <summary> /// <summary>

22
src/Perspex.Controls/Menu.cs

@ -96,15 +96,12 @@ namespace Perspex.Controls
IsOpen = true; IsOpen = true;
} }
/// <summary> /// <inheritdoc/>
/// Called when the <see cref="MenuItem"/> is attached to the visual tree. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
var topLevel = root as TopLevel; var topLevel = e.Root as TopLevel;
topLevel.Deactivated += Deactivated; topLevel.Deactivated += Deactivated;
@ -117,7 +114,7 @@ namespace Perspex.Controls
pointerPress, pointerPress,
Disposable.Create(() => topLevel.Deactivated -= Deactivated)); Disposable.Create(() => topLevel.Deactivated -= Deactivated));
var inputRoot = root as IInputRoot; var inputRoot = e.Root as IInputRoot;
if (inputRoot != null && inputRoot.AccessKeyHandler != null) if (inputRoot != null && inputRoot.AccessKeyHandler != null)
{ {
@ -125,13 +122,10 @@ namespace Perspex.Controls
} }
} }
/// <summary> /// <inheritdoc/>
/// Called when the <see cref="Menu"/> is detached from the visual tree. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="oldRoot">The root of the visual tree being detached from.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
{ {
base.OnDetachedFromVisualTree(oldRoot); base.OnDetachedFromVisualTree(e);
_subscription.Dispose(); _subscription.Dispose();
} }

6
src/Perspex.Controls/MenuItem.cs

@ -376,11 +376,11 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// Called when the MenuItem's template has been applied. /// Called when the MenuItem's template has been applied.
/// </summary> /// </summary>
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
base.OnTemplateApplied(); base.OnTemplateApplied(nameScope);
_popup = this.GetTemplateChild<Popup>("PART_Popup"); _popup = nameScope.Get<Popup>("PART_Popup");
_popup.DependencyResolver = DependencyResolver.Instance; _popup.DependencyResolver = DependencyResolver.Instance;
_popup.PopupRootCreated += PopupRootCreated; _popup.PopupRootCreated += PopupRootCreated;
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;

20
src/Perspex.Controls/Primitives/AccessText.cs

@ -108,14 +108,11 @@ namespace Perspex.Controls.Primitives
return result.WithHeight(result.Height + 1); return result.WithHeight(result.Height + 1);
} }
/// <summary> /// <inheritdoc/>
/// Called when the control is attached to a visual tree. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
_accessKeys = (root as IInputRoot)?.AccessKeyHandler; _accessKeys = (e.Root as IInputRoot)?.AccessKeyHandler;
if (_accessKeys != null && AccessKey != 0) if (_accessKeys != null && AccessKey != 0)
{ {
@ -123,13 +120,10 @@ namespace Perspex.Controls.Primitives
} }
} }
/// <summary> /// <inheritdoc/>
/// Called when the control is detached from a visual tree. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot root)
{ {
base.OnDetachedFromVisualTree(root); base.OnDetachedFromVisualTree(e);
if (_accessKeys != null && AccessKey != 0) if (_accessKeys != null && AccessKey != 0)
{ {

20
src/Perspex.Controls/Primitives/Popup.cs

@ -212,23 +212,17 @@ namespace Perspex.Controls.Primitives
return new Size(); return new Size();
} }
/// <summary> /// <inheritdoc/>
/// Called when the control is added to the visual tree. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="root">THe root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
_topLevel = root as TopLevel; _topLevel = e.Root as TopLevel;
} }
/// <summary> /// <inheritdoc/>
/// Called when the control is removed to the visual tree. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
/// </summary>
/// <param name="root">THe root of the visual tree.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot root)
{ {
base.OnDetachedFromVisualTree(root); base.OnDetachedFromVisualTree(e);
_topLevel = null; _topLevel = null;
} }

4
src/Perspex.Controls/Primitives/PopupRoot.cs

@ -94,9 +94,9 @@ namespace Perspex.Controls.Primitives
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
base.OnTemplateApplied(); base.OnTemplateApplied(nameScope);
if (Parent.TemplatedParent != null) if (Parent.TemplatedParent != null)
{ {

26
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -195,19 +195,21 @@ namespace Perspex.Controls.Primitives
_templateLog.Verbose("Creating control template"); _templateLog.Verbose("Creating control template");
var child = Template.Build(this); var child = Template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Visual)child, nameScope);
// We need to call SetTemplatedParentAndApplyChildTemplates twice - once // We need to call SetTemplatedParentAndApplyChildTemplates twice - once
// before the controls are added to the visual tree so that the logical // before the controls are added to the visual tree so that the logical
// tree can be set up before styling is applied. // tree can be set up before styling is applied.
((ISetLogicalParent)child).SetParent(this); ((ISetLogicalParent)child).SetParent(this);
SetTemplatedParentAndApplyChildTemplates(child); SetupTemplateControls(child, nameScope);
// And again after the controls are added to the visual tree, and have their // And again after the controls are added to the visual tree, and have their
// styling and thus Template property set. // styling and thus Template property set.
AddVisualChild((Visual)child); AddVisualChild((Visual)child);
SetTemplatedParentAndApplyChildTemplates(child); SetupTemplateControls(child, nameScope);
OnTemplateApplied(); OnTemplateApplied(nameScope);
} }
_templateApplied = true; _templateApplied = true;
@ -217,20 +219,30 @@ namespace Perspex.Controls.Primitives
/// <summary> /// <summary>
/// Called when the control's template is applied. /// Called when the control's template is applied.
/// </summary> /// </summary>
protected virtual void OnTemplateApplied() /// <param name="nameScope">The template name scope.</param>
protected virtual void OnTemplateApplied(INameScope nameScope)
{ {
} }
/// <summary> /// <summary>
/// Sets the TemplatedParent property for a control created from the control template and /// Sets the TemplatedParent property for a control created from the control template and
/// applies the templates of nested templated controls. /// applies the templates of nested templated controls. Also adds each control to its name
/// scope if it has a name.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
private void SetTemplatedParentAndApplyChildTemplates(IControl control) /// <param name="nameScope">The name scope.</param>
private void SetupTemplateControls(IControl control, INameScope nameScope)
{ {
// If control.TemplatedParent is null at this point, then the control is our templated
// child so set its TemplatedParent and register it with its name scope.
if (control.TemplatedParent == null) if (control.TemplatedParent == null)
{ {
control.SetValue(TemplatedParentProperty, this); control.SetValue(TemplatedParentProperty, this);
if (control.Name != null)
{
nameScope.Register(control.Name, control);
}
} }
control.ApplyTemplate(); control.ApplyTemplate();
@ -239,7 +251,7 @@ namespace Perspex.Controls.Primitives
{ {
foreach (IControl child in control.GetVisualChildren()) foreach (IControl child in control.GetVisualChildren())
{ {
SetTemplatedParentAndApplyChildTemplates(child); SetupTemplateControls(child, nameScope);
} }
} }
} }

4
src/Perspex.Controls/ProgressBar.cs

@ -26,9 +26,9 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
_indicator = this.GetTemplateChild<Border>("PART_Indicator"); _indicator = nameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size); UpdateIndicator(Bounds.Size);
} }

21
src/Perspex.Controls/Templates/TemplateExtensions.cs

@ -41,27 +41,6 @@ namespace Perspex.Controls.Templates
return null; return null;
} }
public static T FindTemplateChild<T>(this ITemplatedControl control, string name) where T : INamed
{
return control.GetTemplateChildren().OfType<T>().SingleOrDefault(x => x.Name == name);
}
public static T GetTemplateChild<T>(this ITemplatedControl control, string name) where T : INamed
{
var result = control.FindTemplateChild<T>(name);
if (result == null)
{
throw new InvalidOperationException(string.Format(
"Could not find template child '{0}' of type '{1}' in template for '{2}'.",
name,
typeof(T).FullName,
control.GetType().FullName));
}
return result;
}
public static IEnumerable<Control> GetTemplateChildren(this ITemplatedControl control) public static IEnumerable<Control> GetTemplateChildren(this ITemplatedControl control)
{ {
var visual = control as IVisual; var visual = control as IVisual;

4
src/Perspex.Controls/TextBox.cs

@ -136,9 +136,9 @@ namespace Perspex.Controls
set { SetValue(TextWrappingProperty, value); } set { SetValue(TextWrappingProperty, value); }
} }
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
_presenter = this.GetTemplateChild<TextPresenter>("PART_TextPresenter"); _presenter = nameScope.Get<TextPresenter>("PART_TextPresenter");
_presenter.Cursor = new Cursor(StandardCursorType.Ibeam); _presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
} }

4
src/Perspex.Controls/TopLevel.cs

@ -287,9 +287,9 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
throw new InvalidOperationException( throw new InvalidOperationException(
$"Control '{GetType().Name}' is a top level control and cannot be added as a child."); $"Control '{GetType().Name}' is a top level control and cannot be added as a child.");

4
src/Perspex.Controls/TreeViewItem.cs

@ -83,9 +83,9 @@ namespace Perspex.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
_treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault(); _treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
} }

36
src/Perspex.Controls/Window.cs

@ -41,7 +41,7 @@ namespace Perspex.Controls
/// <summary> /// <summary>
/// A top-level window. /// A top-level window.
/// </summary> /// </summary>
public class Window : TopLevel, IStyleable, IFocusScope public class Window : TopLevel, IStyleable, IFocusScope, INameScope
{ {
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -55,8 +55,8 @@ namespace Perspex.Controls
public static readonly PerspexProperty<string> TitleProperty = public static readonly PerspexProperty<string> TitleProperty =
PerspexProperty.Register<Window, string>(nameof(Title), "Window"); PerspexProperty.Register<Window, string>(nameof(Title), "Window");
private NameScope _nameScope = new NameScope();
private object _dialogResult; private object _dialogResult;
private Size _maxPlatformClientSize; private Size _maxPlatformClientSize;
/// <summary> /// <summary>
@ -77,6 +77,20 @@ namespace Perspex.Controls
_maxPlatformClientSize = this.PlatformImpl.MaxClientSize; _maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
} }
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
/// </summary> /// </summary>
@ -191,6 +205,24 @@ namespace Perspex.Controls
} }
} }
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{
_nameScope.Register(name, element);
}
/// <inheritdoc/>
object INameScope.Find(string name)
{
return _nameScope.Find(name);
}
/// <inheritdoc/>
void INameScope.Unregister(string name)
{
_nameScope.Unregister(name);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {

8
src/Perspex.Input/InputElement.cs

@ -348,9 +348,9 @@ namespace Perspex.Input
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(oldRoot); base.OnDetachedFromVisualTree(e);
if (IsFocused) if (IsFocused)
{ {
@ -359,9 +359,9 @@ namespace Perspex.Input
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(root); base.OnAttachedToVisualTree(e);
UpdateIsEnabledCore(); UpdateIsEnabledCore();
} }

43
src/Perspex.SceneGraph/INameScope.cs

@ -0,0 +1,43 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Perspex
{
/// <summary>
/// Defines a name scope.
/// </summary>
public interface INameScope
{
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Registers an element eith the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
void Register(string name, object element);
/// <summary>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
object Find(string name);
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
void Unregister(string name);
}
}

8
src/Perspex.Styling/Styling/INamed.cs → src/Perspex.SceneGraph/INamed.cs

@ -1,10 +1,16 @@
// Copyright (c) The Perspex Project. All rights reserved. // Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Styling namespace Perspex
{ {
/// <summary>
/// Interface for named elements.
/// </summary>
public interface INamed public interface INamed
{ {
/// <summary>
/// Gets the element name.
/// </summary>
string Name { get; } string Name { get; }
} }
} }

11
src/Perspex.SceneGraph/IVisual.cs

@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved. // Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Collections; using Perspex.Collections;
using Perspex.Media; using Perspex.Media;
using Perspex.VisualTree; using Perspex.VisualTree;
@ -20,6 +21,16 @@ namespace Perspex
/// </remarks> /// </remarks>
public interface IVisual public interface IVisual
{ {
/// <summary>
/// Raised when the control is attached to a rooted visual tree.
/// </summary>
event EventHandler<VisualTreeAttachmentEventArgs> AttachedToVisualTree;
/// <summary>
/// Raised when the control is detached from a rooted visual tree.
/// </summary>
event EventHandler<VisualTreeAttachmentEventArgs> DetachedFromVisualTree;
/// <summary> /// <summary>
/// Gets the bounds of the scene graph node relative to its parent. /// Gets the bounds of the scene graph node relative to its parent.
/// </summary> /// </summary>

109
src/Perspex.SceneGraph/NameScope.cs

@ -0,0 +1,109 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Perspex
{
/// <summary>
/// Implements a name scope.
/// </summary>
public class NameScope : INameScope
{
/// <summary>
/// Defines the NameScope attached property.
/// </summary>
public static readonly PerspexProperty<INameScope> NameScopeProperty =
PerspexProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Visual visual)
{
return visual.GetValue(NameScopeProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="value">The value to set.</param>
public static void SetNameScope(Visual visual, INameScope value)
{
visual.SetValue(NameScopeProperty, value);
}
/// <summary>
/// Registers an element with the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
public void Register(string name, object element)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(element != null);
object existing;
if (_inner.TryGetValue(name, out existing))
{
if (existing != element)
{
throw new ArgumentException($"Control with the name '{name}' already registered.");
}
}
else
{
_inner.Add(name, element);
Registered?.Invoke(this, new NameScopeEventArgs(name, element));
}
}
/// <summary>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
public object Find(string name)
{
Contract.Requires<ArgumentNullException>(name != null);
object result;
_inner.TryGetValue(name, out result);
return result;
}
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
public void Unregister(string name)
{
Contract.Requires<ArgumentNullException>(name != null);
object element;
if (_inner.TryGetValue(name, out element))
{
_inner.Remove(name);
Registered?.Invoke(this, new NameScopeEventArgs(name, element));
}
}
}
}

19
src/Perspex.SceneGraph/NameScopeEventArgs.cs

@ -0,0 +1,19 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Perspex
{
public class NameScopeEventArgs : EventArgs
{
public NameScopeEventArgs(string name, object element)
{
Name = name;
Element = element;
}
public string Name { get; }
public object Element { get; }
}
}

68
src/Perspex.SceneGraph/NameScopeExtensions.cs

@ -0,0 +1,68 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Perspex
{
/// <summary>
/// Extension methods for <see cref="INameScope"/>.
/// </summary>
public static class NameScopeExtensions
{
/// <summary>
/// Finds a named element in an <see cref="INameScope"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="nameScope">The name scope.</param>
/// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns>
public static T Find<T>(this INameScope nameScope, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(nameScope != null);
Contract.Requires<ArgumentNullException>(name != null);
var result = nameScope.Find(name);
if (result != null && !(result is T))
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
return (T)result;
}
/// <summary>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="nameScope">The name scope.</param>
/// <param name="name">The name.</param>
/// <returns>The named element.</returns>
public static T Get<T>(this INameScope nameScope, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(nameScope != null);
Contract.Requires<ArgumentNullException>(name != null);
var result = nameScope.Find(name);
if (result == null)
{
throw new KeyNotFoundException($"Could not find control '{name}'.");
}
if (!(result is T))
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
return (T)result;
}
}
}

6
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -55,6 +55,8 @@
<Compile Include="Animation\PageSlide.cs" /> <Compile Include="Animation\PageSlide.cs" />
<Compile Include="Animation\CrossFade.cs" /> <Compile Include="Animation\CrossFade.cs" />
<Compile Include="Animation\IPageTransition.cs" /> <Compile Include="Animation\IPageTransition.cs" />
<Compile Include="INamed.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="Matrix.cs" /> <Compile Include="Matrix.cs" />
<Compile Include="Media\AlignmentY.cs" /> <Compile Include="Media\AlignmentY.cs" />
<Compile Include="Media\AlignmentX.cs" /> <Compile Include="Media\AlignmentX.cs" />
@ -101,6 +103,9 @@
<Compile Include="Media\TileBrush.cs" /> <Compile Include="Media\TileBrush.cs" />
<Compile Include="Media\ImageBush.cs" /> <Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\VisualBrush.cs" /> <Compile Include="Media\VisualBrush.cs" />
<Compile Include="NameScopeEventArgs.cs" />
<Compile Include="NameScopeExtensions.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="RelativePoint.cs" /> <Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" /> <Compile Include="Platform\IFormattedTextImpl.cs" />
<Compile Include="Platform\IBitmapImpl.cs" /> <Compile Include="Platform\IBitmapImpl.cs" />
@ -122,6 +127,7 @@
<Compile Include="Thickness.cs" /> <Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" /> <Compile Include="Vector.cs" />
<Compile Include="Visual.cs" /> <Compile Include="Visual.cs" />
<Compile Include="VisualTreeAttachmentEventArgs.cs" />
<Compile Include="VisualTree\IHostedVisualTreeRoot.cs" /> <Compile Include="VisualTree\IHostedVisualTreeRoot.cs" />
<Compile Include="VisualTree\IVisualTreeHost.cs" /> <Compile Include="VisualTree\IVisualTreeHost.cs" />
<Compile Include="VisualTree\TransformedBounds.cs" /> <Compile Include="VisualTree\TransformedBounds.cs" />

169
src/Perspex.SceneGraph/Visual.cs

@ -26,7 +26,7 @@ namespace Perspex
/// To traverse the scene graph (aka Visual Tree), use the extension methods defined /// To traverse the scene graph (aka Visual Tree), use the extension methods defined
/// in <see cref="VisualExtensions"/>. /// in <see cref="VisualExtensions"/>.
/// </remarks> /// </remarks>
public class Visual : Animatable, IVisual public class Visual : Animatable, IVisual, INamed
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Bounds"/> property. /// Defines the <see cref="Bounds"/> property.
@ -76,6 +76,11 @@ namespace Perspex
public static readonly PerspexProperty<int> ZIndexProperty = public static readonly PerspexProperty<int> ZIndexProperty =
PerspexProperty.Register<Visual, int>(nameof(ZIndex)); PerspexProperty.Register<Visual, int>(nameof(ZIndex));
/// <summary>
/// The name of the visual, if any.
/// </summary>
private string _name;
/// <summary> /// <summary>
/// Holds the children of the visual. /// Holds the children of the visual.
/// </summary> /// </summary>
@ -128,6 +133,16 @@ namespace Perspex
_visualChildren.CollectionChanged += VisualChildrenChanged; _visualChildren.CollectionChanged += VisualChildrenChanged;
} }
/// <summary>
/// Raised when the control is attached to a rooted visual tree.
/// </summary>
public event EventHandler<VisualTreeAttachmentEventArgs> AttachedToVisualTree;
/// <summary>
/// Raised when the control is detached from a rooted visual tree.
/// </summary>
public event EventHandler<VisualTreeAttachmentEventArgs> DetachedFromVisualTree;
/// <summary> /// <summary>
/// Gets the bounds of the scene graph node relative to its parent. /// Gets the bounds of the scene graph node relative to its parent.
/// </summary> /// </summary>
@ -163,6 +178,36 @@ namespace Perspex
set { SetValue(IsVisibleProperty, value); } set { SetValue(IsVisibleProperty, value); }
} }
/// <summary>
/// Gets or sets the name of the visual.
/// </summary>
/// <remarks>
/// An element's name is used to uniquely identify a control within the control's name
/// scope. Once the element is added to a visual tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (value.Trim() == string.Empty)
{
throw new InvalidOperationException("Cannot set Name to empty string.");
}
if (_isAttachedToVisualTree)
{
throw new InvalidOperationException("Cannot set Name : control already added to tree.");
}
_name = value;
}
}
/// <summary> /// <summary>
/// Gets the opacity of the scene graph node. /// Gets the opacity of the scene graph node.
/// </summary> /// </summary>
@ -340,17 +385,19 @@ namespace Perspex
/// <summary> /// <summary>
/// Called when the control is added to a visual tree. /// Called when the control is added to a visual tree.
/// </summary> /// </summary>
/// <param name="root">The root of the visual tree.</param> /// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTree(IRenderRoot root) protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
AttachedToVisualTree?.Invoke(this, e);
} }
/// <summary> /// <summary>
/// Called when the control is removed from a visual tree. /// Called when the control is removed from a visual tree.
/// </summary> /// </summary>
/// <param name="root">The root of the visual tree.</param> /// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTree(IRenderRoot root) protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
DetachedFromVisualTree?.Invoke(this, e);
} }
/// <summary> /// <summary>
@ -367,6 +414,60 @@ namespace Perspex
} }
} }
/// <summary>
/// Gets the event args for an <see cref="AttachedToVisualTree"/> or
/// <see cref="DetachedFromVisualTree"/> event.
/// </summary>
/// <returns>
/// A <see cref="VisualTreeAttachmentEventArgs"/> if the visual currently has a root;
/// otherwise null.
/// </returns>
private VisualTreeAttachmentEventArgs GetAttachmentEventArgs()
{
var e = (IVisual)this;
IRenderRoot root = null;
INameScope nameScope = null;
while (e != null)
{
if (nameScope == null)
{
nameScope = e as INameScope ?? NameScope.GetNameScope((Visual)e);
}
root = e as IRenderRoot;
if (root != null)
{
return new VisualTreeAttachmentEventArgs(root, nameScope);
}
e = e.VisualParent;
}
return null;
}
/// <summary>
/// Gets the <see cref="VisualTreeAttachmentEventArgs"/> for this element based on the
/// parent's args.
/// </summary>
/// <param name="e">The parent args.</param>
/// <returns>The args for this element.</returns>
private VisualTreeAttachmentEventArgs GetAttachmentEventArgs(VisualTreeAttachmentEventArgs e)
{
var childNameScope = (this as INameScope) ?? NameScope.GetNameScope(this);
if (childNameScope != null)
{
return new VisualTreeAttachmentEventArgs(e.Root, childNameScope);
}
else
{
return e;
}
}
/// <summary> /// <summary>
/// Gets the root of the controls visual tree and the distance from the root. /// Gets the root of the controls visual tree and the distance from the root.
/// </summary> /// </summary>
@ -436,28 +537,23 @@ namespace Perspex
{ {
if (_visualParent != value) if (_visualParent != value)
{ {
var old = _visualParent; var oldArgs = GetAttachmentEventArgs();
var oldRoot = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var newRoot = default(IRenderRoot);
if (value != null)
{
newRoot = value.GetSelfAndVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
}
_visualParent = value; _visualParent = value;
if (oldRoot != null) if (oldArgs != null)
{ {
NotifyDetachedFromVisualTree(oldRoot); NotifyDetachedFromVisualTree(oldArgs);
} }
if (newRoot != null) var newArgs = GetAttachmentEventArgs();
if (newArgs != null)
{ {
NotifyAttachedToVisualTree(newRoot); NotifyAttachedToVisualTree(newArgs);
} }
RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue); RaisePropertyChanged(VisualParentProperty, oldArgs, value, BindingPriority.LocalValue);
} }
} }
@ -491,43 +587,56 @@ namespace Perspex
} }
/// <summary> /// <summary>
/// Calls the <see cref="OnAttachedToVisualTree(IRenderRoot)"/> method for this control /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// and all of its visual descendents. /// for this control and all of its visual descendents.
/// </summary> /// </summary>
/// <param name="root">The root of the visual tree.</param> /// <param name="e">The event args.</param>
private void NotifyAttachedToVisualTree(IRenderRoot root) private void NotifyAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_visualLogger.Verbose("Attached to visual tree"); _visualLogger.Verbose("Attached to visual tree");
_isAttachedToVisualTree = true; _isAttachedToVisualTree = true;
OnAttachedToVisualTree(root);
if (Name != null && e.NameScope != null)
{
e.NameScope.Register(Name, this);
}
OnAttachedToVisualTree(e);
if (_visualChildren != null) if (_visualChildren != null)
{ {
foreach (Visual child in _visualChildren.OfType<Visual>()) foreach (Visual child in _visualChildren.OfType<Visual>())
{ {
child.NotifyAttachedToVisualTree(root); var ce = child.GetAttachmentEventArgs(e);
child.NotifyAttachedToVisualTree(ce);
} }
} }
} }
/// <summary> /// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(IRenderRoot)"/> method for this control /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// and all of its visual descendents. /// for this control and all of its visual descendents.
/// </summary> /// </summary>
/// <param name="root">The root of the visual tree.</param> /// <param name="e">The event args.</param>
private void NotifyDetachedFromVisualTree(IRenderRoot root) private void NotifyDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_visualLogger.Verbose("Detached from visual tree"); _visualLogger.Verbose("Detached from visual tree");
if (Name != null && e.NameScope != null)
{
e.NameScope.Unregister(Name);
}
_isAttachedToVisualTree = false; _isAttachedToVisualTree = false;
OnDetachedFromVisualTree(root); OnDetachedFromVisualTree(e);
if (_visualChildren != null) if (_visualChildren != null)
{ {
foreach (Visual child in _visualChildren.OfType<Visual>()) foreach (Visual child in _visualChildren.OfType<Visual>())
{ {
child.NotifyDetachedFromVisualTree(root); var ce = child.GetAttachmentEventArgs(e);
child.NotifyDetachedFromVisualTree(ce);
} }
} }
} }

36
src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs

@ -0,0 +1,36 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Rendering;
namespace Perspex
{
/// <summary>
/// Holds the event arguments for the <see cref="Visual.AttachedToVisualTree"/> and
/// <see cref="Visual.DetachedFromVisualTree"/> events.
/// </summary>
public class VisualTreeAttachmentEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class.
/// </summary>
/// <param name="root">The root visual.</param>
/// <param name="nameScope">The name scope.</param>
public VisualTreeAttachmentEventArgs(IRenderRoot root, INameScope nameScope)
{
Root = root;
NameScope = nameScope;
}
/// <summary>
/// Gets the root of the visual tree that the visual is being attached to or detached from.
/// </summary>
public IRenderRoot Root { get; }
/// <summary>
/// Gets the element's name scope.
/// </summary>
public INameScope NameScope { get; }
}
}

1
src/Perspex.Styling/Perspex.Styling.csproj

@ -46,7 +46,6 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\Classes.cs" /> <Compile Include="Styling\Classes.cs" />
<Compile Include="Styling\IGlobalStyles.cs" /> <Compile Include="Styling\IGlobalStyles.cs" />
<Compile Include="Styling\INamed.cs" />
<Compile Include="Styling\ISetter.cs" /> <Compile Include="Styling\ISetter.cs" />
<Compile Include="Styling\IStyle.cs" /> <Compile Include="Styling\IStyle.cs" />
<Compile Include="Styling\IStyleable.cs" /> <Compile Include="Styling\IStyleable.cs" />

3
src/Perspex.Themes.Default/ScrollViewer.paml

@ -2,7 +2,8 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<ScrollContentPresenter Content="{TemplateBinding Content}" <ScrollContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Path=Extent, Mode=TwoWay}" Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
Offset="{TemplateBinding Path=Offset, Mode=TwoWay}" Offset="{TemplateBinding Path=Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}" Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"

4
src/Perspex.Themes.Default/TextBox.paml

@ -22,9 +22,9 @@
<TextBlock.IsVisible> <TextBlock.IsVisible>
<MultiBinding Converter="{Static BoolConverters.And}"> <MultiBinding Converter="{Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" <Binding RelativeSource="{RelativeSource TemplatedParent}"
SourcePropertyPath="UseFloatingWatermark"/> Path="UseFloatingWatermark"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" <Binding RelativeSource="{RelativeSource TemplatedParent}"
SourcePropertyPath="Text" Path="Text"
Converter="{Static StringConverters.NotNullOrEmpty}"/> Converter="{Static StringConverters.NotNullOrEmpty}"/>
</MultiBinding> </MultiBinding>
</TextBlock.IsVisible> </TextBlock.IsVisible>

2
tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs

@ -214,7 +214,7 @@ namespace Perspex.Controls.UnitTests.Primitives
}; };
target.ApplyTemplate(); target.ApplyTemplate();
var popup = target.GetTemplateChild<Popup>("popup"); var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
popup.Open(); popup.Open();
var popupRoot = popup.PopupRoot; var popupRoot = popup.PopupRoot;

7
tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using Perspex.Controls.Primitives; using Perspex.Controls.Primitives;
using Perspex.Controls.Templates; using Perspex.Controls.Templates;
using Perspex.Media; using Perspex.Media;
@ -20,7 +21,7 @@ namespace Perspex.Controls.UnitTests.Primitives
}; };
target.ApplyTemplate(); target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track"); var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 50; target.Value = 50;
Assert.Equal(track.Value, 50); Assert.Equal(track.Value, 50);
@ -35,7 +36,7 @@ namespace Perspex.Controls.UnitTests.Primitives
}; };
target.ApplyTemplate(); target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track"); var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
track.Value = 50; track.Value = 50;
Assert.Equal(target.Value, 50); Assert.Equal(target.Value, 50);
@ -51,7 +52,7 @@ namespace Perspex.Controls.UnitTests.Primitives
target.ApplyTemplate(); target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track"); var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 25; target.Value = 25;
track.Value = 50; track.Value = 50;

4
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -403,13 +403,13 @@ namespace Perspex.Controls.UnitTests.Primitives
var itemsBinding = new Binding var itemsBinding = new Binding
{ {
SourcePropertyPath = "Items", Path = "Items",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };
var selectedItemsBinding = new Binding var selectedItemsBinding = new Binding
{ {
SourcePropertyPath = "SelectedItems", Path = "SelectedItems",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };

23
tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -50,6 +50,29 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.Empty(target.GetLogicalChildren()); Assert.Empty(target.GetLogicalChildren());
} }
[Fact]
public void Templated_Child_Should_Be_NameScope()
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
{
Child = new Panel
{
Children = new Controls
{
new TextBlock(),
new Border(),
}
}
}),
};
target.ApplyTemplate();
Assert.NotNull(NameScope.GetNameScope((Visual)target.GetVisualChildren().Single()));
}
[Fact] [Fact]
public void Templated_Children_Should_Have_TemplatedParent_Set() public void Templated_Children_Should_Have_TemplatedParent_Set()
{ {

6
tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs

@ -24,9 +24,7 @@ namespace Perspex.Controls.UnitTests
target.ApplyTemplate(); target.ApplyTemplate();
var presenter = target.GetTemplateChild<ScrollContentPresenter>("contentPresenter"); Assert.IsType<TextBlock>(target.Presenter.Child);
Assert.IsType<TextBlock>(presenter.Child);
} }
[Fact] [Fact]
@ -65,7 +63,7 @@ namespace Perspex.Controls.UnitTests
{ {
new ScrollContentPresenter new ScrollContentPresenter
{ {
Name = "contentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],

32
tests/Perspex.Controls.UnitTests/TabControlTests.cs

@ -97,23 +97,23 @@ namespace Perspex.Controls.UnitTests
public void Removal_Should_Set_Next_Tab() public void Removal_Should_Set_Next_Tab()
{ {
var collection = new ObservableCollection<TabItem>() var collection = new ObservableCollection<TabItem>()
{
new TabItem
{ {
new TabItem Name = "first",
{ Content = "foo",
Name = "first", },
Content = "foo", new TabItem
}, {
new TabItem Name = "second",
{ Content = "bar",
Name = "second", },
Content = "bar", new TabItem
}, {
new TabItem Name = "3rd",
{ Content = "barf",
Name = "3rd", },
Content = "barf", };
},
};
var target = new TabControl var target = new TabControl
{ {

2
tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs

@ -14,7 +14,7 @@ namespace Perspex.Controls.UnitTests
base.AddVisualChild(visual); base.AddVisualChild(visual);
} }
protected override void OnTemplateApplied() protected override void OnTemplateApplied(INameScope nameScope)
{ {
OnTemplateAppliedCalled = true; OnTemplateAppliedCalled = true;
} }

122
tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs

@ -0,0 +1,122 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using Perspex.Controls;
using Xunit;
namespace Perspex.Markup.UnitTests
{
public class ControlLocatorTests
{
[Fact]
public async void Track_By_Name_Should_Find_Control_Added_Earlier()
{
TextBlock target;
TextBlock relativeTo;
var root = new TestRoot
{
Child = new StackPanel
{
Children = new Controls.Controls
{
(target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }),
}
}
};
var locator = ControlLocator.Track(relativeTo, "target");
var result = await locator.Take(1);
Assert.Same(target, result);
Assert.Equal(0, root.NameScopeRegisteredSubscribers);
Assert.Equal(0, root.NameScopeUnregisteredSubscribers);
}
[Fact]
public void Track_By_Name_Should_Find_Control_Added_Later()
{
StackPanel panel;
TextBlock relativeTo;
var root = new TestRoot
{
Child = (panel = new StackPanel
{
Children = new Controls.Controls
{
(relativeTo = new TextBlock
{
Name = "start"
}),
}
})
};
var locator = ControlLocator.Track(relativeTo, "target");
var target = new TextBlock { Name = "target" };
var result = new List<IControl>();
using (locator.Subscribe(x => result.Add(x)))
{
panel.Children.Add(target);
}
Assert.Equal(new[] { null, target }, result);
Assert.Equal(0, root.NameScopeRegisteredSubscribers);
Assert.Equal(0, root.NameScopeUnregisteredSubscribers);
}
[Fact]
public void Track_By_Name_Should_Find_Control_When_Tree_Changed()
{
TextBlock target1;
TextBlock target2;
TextBlock relativeTo;
var root1 = new TestRoot
{
Child = new StackPanel
{
Children = new Controls.Controls
{
(relativeTo = new TextBlock
{
Name = "start"
}),
(target1 = new TextBlock { Name = "target" }),
}
}
};
var root2 = new TestRoot
{
Child = new StackPanel
{
Children = new Controls.Controls
{
(target2 = new TextBlock { Name = "target" }),
}
}
};
var locator = ControlLocator.Track(relativeTo, "target");
var target = new TextBlock { Name = "target" };
var result = new List<IControl>();
using (locator.Subscribe(x => result.Add(x)))
{
((StackPanel)root1.Child).Children.Remove(relativeTo);
((StackPanel)root2.Child).Children.Add(relativeTo);
}
Assert.Equal(new[] { target1, null, target2 }, result);
Assert.Equal(0, root1.NameScopeRegisteredSubscribers);
Assert.Equal(0, root1.NameScopeUnregisteredSubscribers);
}
}
}

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionNodeBuilderTests.cs

@ -6,7 +6,7 @@ using System.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionNodeBuilderTests public class ExpressionNodeBuilderTests
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionNodeBuilderTests_Errors.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionNodeBuilderTests_Errors.cs

@ -4,7 +4,7 @@
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionNodeBuilderTests_Errors public class ExpressionNodeBuilderTests_Errors
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Indexer.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Indexer.cs

@ -8,7 +8,7 @@ using System.Reactive.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_Indexer public class ExpressionObserverTests_Indexer
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Negation.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs

@ -6,7 +6,7 @@ using System.Reactive.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_Negation public class ExpressionObserverTests_Negation
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Observable.cs

@ -8,7 +8,7 @@ using System.Reactive.Subjects;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_Observable public class ExpressionObserverTests_Observable
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_PerspexProperty.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_PerspexProperty.cs

@ -7,7 +7,7 @@ using System.Reactive.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_PerspexProperty public class ExpressionObserverTests_PerspexProperty
{ {

30
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Property.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs

@ -3,12 +3,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using Microsoft.Reactive.Testing;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_Property public class ExpressionObserverTests_Property
{ {
@ -203,6 +204,26 @@ namespace Perspex.Markup.UnitTests.Binding
Assert.Equal(new[] { "foo", "bar" }, result); Assert.Equal(new[] { "foo", "bar" }, result);
} }
[Fact]
public void Should_Track_Property_Value_From_Observable_Root()
{
var scheduler = new TestScheduler();
var source = scheduler.CreateColdObservable(
OnNext(1, new Class1 { Foo = "foo" }),
OnNext(2, new Class1 { Foo = "bar" }));
var target = new ExpressionObserver(source, "Foo");
var result = new List<object>();
using (target.Subscribe(x => result.Add(x)))
{
scheduler.Start();
}
Assert.Equal(new[] { PerspexProperty.UnsetValue, "foo", "bar" }, result);
Assert.Equal(1, source.Subscriptions.Count);
Assert.NotEqual(Subscription.Infinite, source.Subscriptions[0].Unsubscribe);
}
[Fact] [Fact]
public void SetValue_Should_Set_Simple_Property_Value() public void SetValue_Should_Set_Simple_Property_Value()
{ {
@ -333,5 +354,10 @@ namespace Perspex.Markup.UnitTests.Binding
private class WithoutBar : NotifyingBase, INext private class WithoutBar : NotifyingBase, INext
{ {
} }
public Recorded<Notification<object>> OnNext(long time, object value)
{
return new Recorded<Notification<object>>(time, Notification.CreateOnNext<object>(value));
}
} }
} }

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_SetValue.cs

@ -7,7 +7,7 @@ using System.Reactive.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_SetValue public class ExpressionObserverTests_SetValue
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Task.cs

@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionObserverTests_Task public class ExpressionObserverTests_Task
{ {

2
tests/Perspex.Markup.UnitTests/Binding/ExpressionSubjectTests.cs → tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs

@ -5,7 +5,7 @@ using System.Reactive.Linq;
using Perspex.Markup.Data; using Perspex.Markup.Data;
using Xunit; using Xunit;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class ExpressionSubjectTests public class ExpressionSubjectTests
{ {

2
tests/Perspex.Markup.UnitTests/Binding/NotifyingBase.cs → tests/Perspex.Markup.UnitTests/Data/NotifyingBase.cs

@ -4,7 +4,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
namespace Perspex.Markup.UnitTests.Binding namespace Perspex.Markup.UnitTests.Data
{ {
public class NotifyingBase : INotifyPropertyChanged public class NotifyingBase : INotifyPropertyChanged
{ {

57
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@ -34,6 +34,11 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.Reactive.Testing, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Testing.2.2.5\lib\net45\Microsoft.Reactive.Testing.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@ -72,19 +77,21 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" /> <Compile Include="ControlLocatorTests.cs" />
<Compile Include="Binding\ExpressionObserverTests_PerspexProperty.cs" /> <Compile Include="Data\ExpressionNodeBuilderTests.cs" />
<Compile Include="Binding\ExpressionSubjectTests.cs" /> <Compile Include="Data\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Binding\ExpressionObserverTests_Observable.cs" /> <Compile Include="Data\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Binding\ExpressionObserverTests_SetValue.cs" /> <Compile Include="Data\ExpressionObserverTests_Negation.cs" />
<Compile Include="Binding\ExpressionObserverTests_Task.cs" /> <Compile Include="Data\ExpressionObserverTests_Observable.cs" />
<Compile Include="Binding\ExpressionObserverTests_Indexer.cs" /> <Compile Include="Data\ExpressionObserverTests_PerspexProperty.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" /> <Compile Include="Data\ExpressionObserverTests_Property.cs" />
<Compile Include="Binding\ExpressionObserverTests_Property.cs" /> <Compile Include="Data\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Binding\ExpressionNodeBuilderTests.cs" /> <Compile Include="Data\ExpressionObserverTests_Task.cs" />
<Compile Include="Binding\NotifyingBase.cs" /> <Compile Include="Data\ExpressionSubjectTests.cs" />
<Compile Include="Data\NotifyingBase.cs" />
<Compile Include="DefaultValueConverterTests.cs" /> <Compile Include="DefaultValueConverterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="UnitTestSynchronizationContext.cs" /> <Compile Include="UnitTestSynchronizationContext.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -95,10 +102,38 @@
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project> <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name> <Name>Perspex.Markup</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj"> <ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project> <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name> <Name>Perspex.Base</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

61
tests/Perspex.Markup.UnitTests/TestRoot.cs

@ -0,0 +1,61 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Controls;
using Perspex.Platform;
using Perspex.Rendering;
namespace Perspex.Markup.UnitTests
{
public class TestRoot : Decorator, IRenderRoot, INameScope
{
private NameScope _nameScope = new NameScope();
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; ++NameScopeRegisteredSubscribers; }
remove { _nameScope.Registered -= value; --NameScopeRegisteredSubscribers; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; ++NameScopeUnregisteredSubscribers; }
remove { _nameScope.Unregistered -= value; --NameScopeUnregisteredSubscribers; }
}
public int NameScopeRegisteredSubscribers { get; private set; }
public int NameScopeUnregisteredSubscribers { get; private set; }
public IRenderTarget RenderTarget
{
get { throw new NotImplementedException(); }
}
public IRenderQueueManager RenderQueueManager
{
get { throw new NotImplementedException(); }
}
public Point TranslatePointToScreen(Point p)
{
throw new NotImplementedException();
}
public void Register(string name, object element)
{
_nameScope.Register(name, element);
}
public object Find(string name)
{
return _nameScope.Find(name);
}
public void Unregister(string name)
{
_nameScope.Unregister(name);
}
}
}

1
tests/Perspex.Markup.UnitTests/packages.config

@ -5,6 +5,7 @@
<package id="Rx-Linq" version="2.2.5" targetFramework="net46" /> <package id="Rx-Linq" version="2.2.5" targetFramework="net46" />
<package id="Rx-Main" version="2.2.5" targetFramework="net46" /> <package id="Rx-Main" version="2.2.5" targetFramework="net46" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net46" /> <package id="Rx-PlatformServices" version="2.2.5" targetFramework="net46" />
<package id="Rx-Testing" version="2.2.5" targetFramework="net46" />
<package id="xunit" version="2.0.0" targetFramework="net46" /> <package id="xunit" version="2.0.0" targetFramework="net46" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net46" /> <package id="xunit.abstractions" version="2.0.0" targetFramework="net46" />
<package id="xunit.assert" version="2.0.0" targetFramework="net46" /> <package id="xunit.assert" version="2.0.0" targetFramework="net46" />

20
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -20,7 +20,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(); var target = CreateTarget();
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };
@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(); var target = CreateTarget();
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };
@ -58,7 +58,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(dataContext: dataContext); var target = CreateTarget(dataContext: dataContext);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneTime, Mode = BindingMode.OneTime,
}; };
@ -87,7 +87,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object); var target = CreateTarget(text: textObservable.Object);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWayToSource, Mode = BindingMode.OneWayToSource,
}; };
@ -102,7 +102,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null); var target = CreateTarget(null);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -127,7 +127,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Header", Path = "Header",
}; };
binding.Bind(parent.Child, Control.DataContextProperty); binding.Bind(parent.Child, Control.DataContextProperty);
@ -145,7 +145,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null); var target = CreateTarget(null);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
}; };
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -161,7 +161,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
Converter = converter.Object, Converter = converter.Object,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -189,13 +189,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var fooBinding = new Binding var fooBinding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };
var barBinding = new Binding var barBinding = new Binding
{ {
SourcePropertyPath = "Bar", Path = "Bar",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };

83
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs

@ -0,0 +1,83 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
using Perspex.Markup.Xaml.Data;
using Xunit;
namespace Perspex.Markup.Xaml.UnitTests.Data
{
public class BindingTests_ElementName
{
[Fact]
public void Should_Bind_To_Element()
{
TextBlock target;
var root = new TestRoot
{
Child = new StackPanel
{
Children = new Controls.Controls
{
new TextBlock
{
Name = "source",
Text = "foo",
},
(target = new TextBlock
{
Name = "target",
})
}
}
};
var binding = new Binding
{
ElementName = "source",
Path = "Text",
};
binding.Bind(target, TextBlock.TextProperty);
Assert.Equal("foo", target.Text);
}
[Fact]
public void Should_Bind_To_Later_Added_Element()
{
TextBlock target;
StackPanel stackPanel;
var root = new TestRoot
{
Child = stackPanel = new StackPanel
{
Children = new Controls.Controls
{
(target = new TextBlock
{
Name = "target",
}),
}
}
};
var binding = new Binding
{
ElementName = "source",
Path = "Text",
};
binding.Bind(target, TextBlock.TextProperty);
stackPanel.Children.Add(new TextBlock
{
Name = "source",
Text = "foo",
});
Assert.Equal("foo", target.Text);
}
}
}

6
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@ -23,7 +23,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -62,7 +62,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object); var target = CreateTarget(text: textObservable.Object);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWayToSource, Mode = BindingMode.OneWayToSource,
}; };

6
tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@ -25,9 +25,9 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Converter = new ConcatConverter(), Converter = new ConcatConverter(),
Bindings = new[] Bindings = new[]
{ {
new Binding { SourcePropertyPath = "A" }, new Binding { Path = "A" },
new Binding { SourcePropertyPath = "B" }, new Binding { Path = "B" },
new Binding { SourcePropertyPath = "C" }, new Binding { Path = "C" },
} }
}; };

2
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@ -88,6 +88,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="Data\BindingTests_ElementName.cs" />
<Compile Include="Data\MultiBindingTests.cs" /> <Compile Include="Data\MultiBindingTests.cs" />
<Compile Include="Data\BindingTests_TemplatedParent.cs" /> <Compile Include="Data\BindingTests_TemplatedParent.cs" />
<Compile Include="Data\BindingTests.cs" /> <Compile Include="Data\BindingTests.cs" />
@ -104,6 +105,7 @@
<Compile Include="SampleModel\UserRepositoriesViewModel.cs" /> <Compile Include="SampleModel\UserRepositoriesViewModel.cs" />
<Compile Include="SamplePerspexObject.cs" /> <Compile Include="SamplePerspexObject.cs" />
<Compile Include="Templates\DataTemplateTests.cs" /> <Compile Include="Templates\DataTemplateTests.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="TypeProviderMock.cs" /> <Compile Include="TypeProviderMock.cs" />
<Compile Include="ViewModelMock.cs" /> <Compile Include="ViewModelMock.cs" />
</ItemGroup> </ItemGroup>

2
tests/Perspex.Markup.Xaml.UnitTests/SamplePerspexObject.cs

@ -3,7 +3,7 @@
using System; using System;
namespace Perspex.Xaml.Base.UnitTest namespace Perspex.Markup.Xaml.UnitTests
{ {
internal class SamplePerspexObject : PerspexObject internal class SamplePerspexObject : PerspexObject
{ {

61
tests/Perspex.Markup.Xaml.UnitTests/TestRoot.cs

@ -0,0 +1,61 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Controls;
using Perspex.Platform;
using Perspex.Rendering;
namespace Perspex.Markup.Xaml.UnitTests
{
public class TestRoot : Decorator, IRenderRoot, INameScope
{
private readonly NameScope _nameScope = new NameScope();
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; ++NameScopeRegisteredSubscribers; }
remove { _nameScope.Registered -= value; --NameScopeRegisteredSubscribers; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; ++NameScopeUnregisteredSubscribers; }
remove { _nameScope.Unregistered -= value; --NameScopeUnregisteredSubscribers; }
}
public int NameScopeRegisteredSubscribers { get; private set; }
public int NameScopeUnregisteredSubscribers { get; private set; }
public IRenderTarget RenderTarget
{
get { throw new NotImplementedException(); }
}
public IRenderQueueManager RenderQueueManager
{
get { throw new NotImplementedException(); }
}
public Point TranslatePointToScreen(Point p)
{
throw new NotImplementedException();
}
public void Register(string name, object element)
{
_nameScope.Register(name, element);
}
public object Find(string name)
{
return _nameScope.Find(name);
}
public void Unregister(string name)
{
_nameScope.Unregister(name);
}
}
}

2
tests/Perspex.Markup.Xaml.UnitTests/TypeProviderMock.cs

@ -4,7 +4,7 @@
using OmniXaml; using OmniXaml;
using System; using System;
namespace Perspex.Xaml.Base.UnitTest namespace Perspex.Markup.Xaml.UnitTests
{ {
internal class TypeProviderMock : ITypeProvider internal class TypeProviderMock : ITypeProvider
{ {

2
tests/Perspex.Markup.Xaml.UnitTests/ViewModelMock.cs

@ -4,7 +4,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Perspex.Xaml.Base.UnitTest namespace Perspex.Markup.Xaml.UnitTests
{ {
internal class ViewModelMock : INotifyPropertyChanged internal class ViewModelMock : INotifyPropertyChanged
{ {

55
tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs

@ -0,0 +1,55 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Xunit;
namespace Perspex.SceneGraph.UnitTests
{
public class NameScopeTests
{
[Fact]
public void Register_Registers_Element()
{
var target = new NameScope();
var element = new object();
target.Register("foo", element);
Assert.Same(element, target.Find("foo"));
}
[Fact]
public void Unregister_Unregisters_Element()
{
var target = new NameScope();
var element = new object();
target.Register("foo", element);
target.Unregister("foo");
Assert.Null(target.Find("foo"));
}
[Fact]
public void Cannot_Register_New_Element_With_Existing_Name()
{
var target = new NameScope();
target.Register("foo", new object());
Assert.Throws<ArgumentException>(() => target.Register("foo", new object()));
}
[Fact]
public void Can_Register_Same_Element_More_Than_Once()
{
var target = new NameScope();
var element = new object();
target.Register("foo", element);
target.Register("foo", element);
Assert.Same(element, target.Find("foo"));
}
}
}

1
tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj

@ -72,6 +72,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="NameScopeTests.cs" />
<Compile Include="RelativeRectComparer.cs" /> <Compile Include="RelativeRectComparer.cs" />
<Compile Include="RelativeRectTests.cs" /> <Compile Include="RelativeRectTests.cs" />
<Compile Include="ThicknessTests.cs" /> <Compile Include="ThicknessTests.cs" />

31
tests/Perspex.SceneGraph.UnitTests/TestRoot.cs

@ -7,8 +7,22 @@ using Perspex.Rendering;
namespace Perspex.SceneGraph.UnitTests namespace Perspex.SceneGraph.UnitTests
{ {
public class TestRoot : TestVisual, IRenderRoot public class TestRoot : TestVisual, IRenderRoot, INameScope
{ {
private NameScope _nameScope = new NameScope();
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
public IRenderTarget RenderTarget public IRenderTarget RenderTarget
{ {
get { throw new NotImplementedException(); } get { throw new NotImplementedException(); }
@ -23,5 +37,20 @@ namespace Perspex.SceneGraph.UnitTests
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void Register(string name, object element)
{
_nameScope.Register(name, element);
}
public object Find(string name)
{
return _nameScope.Find(name);
}
public void Unregister(string name)
{
_nameScope.Unregister(name);
}
} }
} }

39
tests/Perspex.SceneGraph.UnitTests/TestVisual.cs

@ -20,11 +20,28 @@ namespace Perspex.SceneGraph.UnitTests
public class TestVisual : Visual public class TestVisual : Visual
{ {
public event EventHandler<ParamEventArgs<IRenderRoot>> AttachedToVisualTreeCalled; public new PerspexObject InheritanceParent => base.InheritanceParent;
public event EventHandler<ParamEventArgs<IRenderRoot>> DetachedFromVisualTreeCalled; public IVisual Child
{
get
{
return ((IVisual)this).VisualChildren.FirstOrDefault();
}
public new PerspexObject InheritanceParent => base.InheritanceParent; set
{
if (Child != null)
{
RemoveVisualChild(Child);
}
if (value != null)
{
AddVisualChild(value);
}
}
}
public void AddChild(Visual v) public void AddChild(Visual v)
{ {
@ -45,21 +62,5 @@ namespace Perspex.SceneGraph.UnitTests
{ {
ClearVisualChildren(); ClearVisualChildren();
} }
protected override void OnAttachedToVisualTree(IRenderRoot root)
{
if (AttachedToVisualTreeCalled != null)
{
AttachedToVisualTreeCalled(this, new ParamEventArgs<IRenderRoot>(root));
}
}
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
{
if (DetachedFromVisualTreeCalled != null)
{
DetachedFromVisualTreeCalled(this, new ParamEventArgs<IRenderRoot>(oldRoot));
}
}
} }
} }

87
tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Perspex.Controls;
using Perspex.VisualTree; using Perspex.VisualTree;
using Xunit; using Xunit;
@ -84,5 +85,91 @@ namespace Perspex.SceneGraph.UnitTests
Assert.Equal(new Visual[] { null, null }, result); Assert.Equal(new Visual[] { null, null }, result);
} }
[Fact]
public void Adding_Children_Should_Fire_OnAttachedToVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
child1.AttachedToVisualTree += (s, e) => called1 = true;
child2.AttachedToVisualTree += (s, e) => called2 = true;
root.Child = child1;
Assert.True(called1);
Assert.True(called2);
}
[Fact]
public void Removing_Children_Should_Fire_OnDetachedFromVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
root.Child = child1;
child1.DetachedFromVisualTree += (s, e) => called1 = true;
child2.DetachedFromVisualTree += (s, e) => called2 = true;
root.Child = null;
Assert.True(called1);
Assert.True(called2);
}
[Fact]
public void Controls_Should_Add_Themselves_To_Root_NameScope()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2 };
var root = new TestRoot { Child = child1 };
Assert.Same(child1, root.Find("foo"));
Assert.Same(child2, root.Find("bar"));
}
[Fact]
public void Controls_Should_Add_Themselves_To_NameScopes_In_Attached_Property()
{
var child2 = new TestVisual { Name = "bar", [NameScope.NameScopeProperty] = new NameScope() };
var child1 = new TestVisual { Name = "foo", Child = child2};
var root = new TestRoot { Child = child1 };
Assert.Same(child1, root.Find("foo"));
Assert.Null(root.Find("bar"));
Assert.Same(child2, NameScope.GetNameScope(child2).Find("bar"));
}
[Fact]
public void Controls_Should_Remove_Themselves_From_Root_NameScope()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2 };
var root = new TestRoot { Child = child1 };
root.Child = null;
Assert.Null(root.Find("foo"));
Assert.Null(root.Find("bar"));
}
[Fact]
public void Controls_Should_Remove_Themselves_From_NameScopes_In_Attached_Property()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2,[NameScope.NameScopeProperty] = new NameScope() };
var root = new TestRoot { Child = child1 };
root.Child = null;
Assert.Null(root.Find("foo"));
Assert.Null(root.Find("bar"));
Assert.Null(NameScope.GetNameScope(child1).Find("bar"));
}
} }
} }

Loading…
Cancel
Save