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 Margin="18" Gap="4" Width="200">
<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 Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
@ -30,6 +30,11 @@
<TextBlock Text="{Binding Path=DoubleValue}"/>
<ProgressBar Maximum="10" Value="{Binding DoubleValue}"/>
</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>
</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.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Glass;
@ -11,21 +9,17 @@ using OmniXaml;
using OmniXaml.Builder;
using OmniXaml.TypeConversion;
using OmniXaml.Typing;
using Perspex.Collections;
using Perspex.Controls;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Input;
using Perspex.Markup.Xaml.Templates;
using Perspex.Markup.Xaml.Converters;
using Perspex.Markup.Xaml.Data;
using Perspex.Markup.Xaml.MarkupExtensions;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.Metadata;
using Perspex.Platform;
using Perspex.Styling;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using System.Collections;
using Perspex.Collections;
namespace Perspex.Markup.Xaml.Context
{
@ -48,9 +42,10 @@ namespace Perspex.Markup.Xaml.Context
var xamlNamespaceRegistry = CreateXamlNamespaceRegistry();
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(
new Metadata<SelectingItemsControl>()
new GenericMetadata<SelectingItemsControl>()
.WithMemberDependency(x => x.SelectedIndex, 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)
{
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider);

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

@ -4,7 +4,6 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using OmniXaml.TypeConversion;
using Perspex.Controls;
using Perspex.Markup.Data;
@ -20,6 +19,11 @@ namespace Perspex.Markup.Xaml.Data
/// </summary>
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>
/// Gets or sets the binding mode.
/// </summary>
@ -38,7 +42,7 @@ namespace Perspex.Markup.Xaml.Data
/// <summary>
/// Gets or sets the binding path.
/// </summary>
public string SourcePropertyPath { get; set; }
public string Path { get; set; }
/// <summary>
/// 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>
public void Bind(IObservablePropertyBag instance, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(instance != null);
Contract.Requires<ArgumentNullException>(property != null);
var subject = CreateSubject(
instance,
instance,
property.PropertyType,
property == Control.DataContextProperty);
@ -72,15 +79,33 @@ namespace Perspex.Markup.Xaml.Data
Type targetType,
bool targetIsDataContext = false)
{
Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
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)
{
observer = CreateTemplatedParentExpressionSubject(target);
observer = CreateTemplatedParentSubject(
target,
pathInfo.Path);
}
else
{
@ -88,8 +113,8 @@ namespace Perspex.Markup.Xaml.Data
}
return new ExpressionSubject(
observer,
targetType,
observer,
targetType,
Converter ?? DefaultValueConverter.Instance);
}
@ -101,6 +126,10 @@ namespace Perspex.Markup.Xaml.Data
/// <param name="subject">The binding subject.</param>
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 ?
property.DefaultBindingMode : Mode;
@ -117,7 +146,7 @@ namespace Perspex.Markup.Xaml.Data
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{
subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
});
});
break;
case BindingMode.OneWayToSource:
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,
string path,
bool targetIsDataContext)
{
Contract.Requires<ArgumentNullException>(target != null);
var dataContextHost = targetIsDataContext ?
target.InheritanceParent as IObservablePropertyBag : target;
@ -136,7 +215,7 @@ namespace Perspex.Markup.Xaml.Data
{
var result = new ExpressionObserver(
() => dataContextHost.GetValue(Control.DataContextProperty),
GetExpression());
path);
dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
result.UpdateRoot());
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(
() => target.GetValue(Control.TemplatedParentProperty),
GetExpression());
path);
if (target.GetValue(Control.TemplatedParentProperty) == null)
{
@ -167,10 +250,38 @@ namespace Perspex.Markup.Xaml.Data
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 == "." ?
string.Empty : SourcePropertyPath;
public string Path { get; set; }
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
{
Converter = Converter,
ElementName = ElementName,
Mode = Mode,
SourcePropertyPath = Path,
Path = Path,
};
}
public IValueConverter Converter { get; set; }
public string ElementName { get; set; }
public BindingMode Mode { 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)
{
return new Data.Binding
return new Binding
{
Converter = Converter,
ElementName = ElementName,
Mode = Mode,
Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
SourcePropertyPath = Path,
Path = Path,
};
}
public IValueConverter Converter { get; set; }
public string ElementName { get; set; }
public BindingMode Mode { 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">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Context\NameScopeWrapper.cs" />
<Compile Include="Converters\PerspexListTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" />
<Compile Include="Data\IXamlBinding.cs" />
@ -185,7 +186,6 @@
<Compile Include="OmniXAML\Source\OmniXaml\Sequence.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TemplateHostingObjectAssembler.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\DoubleTypeConverter.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\ClrNamespace.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\IXamlNamespaceRegistry.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\IXamlTypeRepository.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\Typing\MemberValuePlugin.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\Namespace.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)
{
var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath);
var obs = new ExpressionObserver(item, ItemsSource.Path);
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(),
};
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 ExpressionNode _node;
private readonly ExpressionNode _node;
private ISubject<object> _empty;
/// <summary>
@ -37,21 +40,50 @@ namespace Perspex.Markup.Data
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
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>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </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>
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);
_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))
{
@ -105,9 +137,13 @@ namespace Perspex.Markup.Data
{
return (Leaf as PropertyAccessorNode)?.PropertyType;
}
else if(_rootGetter != null)
{
return _rootGetter()?.GetType();
}
else
{
return _root()?.GetType();
return _root?.GetType();
}
}
finally
@ -134,19 +170,19 @@ namespace Perspex.Markup.Data
}
/// <summary>
/// Causes the root object to be re-read.
/// Causes the root object to be re-read from the root getter.
/// </summary>
public void UpdateRoot()
{
if (_count > 0)
if (_count > 0 && _rootGetter != null)
{
if (_node != null)
{
_node.Target = _root();
_node.Target = _rootGetter();
}
else if (_empty != null)
{
_empty.OnNext(_root());
_empty.OnNext(_rootGetter());
}
}
}
@ -170,7 +206,7 @@ namespace Perspex.Markup.Data
{
if (_empty == null)
{
_empty = new BehaviorSubject<object>(_root());
_empty = new BehaviorSubject<object>(_rootGetter());
}
return _empty.Subscribe(observer);
@ -181,7 +217,18 @@ namespace Perspex.Markup.Data
{
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 (_rootObserverSubscription != null)
{
_rootObserverSubscription.Dispose();
_rootObserverSubscription = null;
}
_node.Target = null;
}
}

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

@ -42,6 +42,7 @@
<Compile Include="Data\ExpressionNodeBuilder.cs" />
<Compile Include="Data\ExpressionParseException.cs" />
<Compile Include="Data\ExpressionSubject.cs" />
<Compile Include="ControlLocator.cs" />
<Compile Include="Data\Plugins\PerspexPropertyAccessorPlugin.cs" />
<Compile Include="Data\Plugins\InpcPropertyAccessorPlugin.cs" />
<Compile Include="Data\Plugins\IPropertyAccessor.cs" />
@ -91,10 +92,30 @@
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</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">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
</ItemGroup>
<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.

12
src/Perspex.Controls/Button.cs

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

6
src/Perspex.Controls/Carousel.cs

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

4
src/Perspex.Controls/ContentControl.cs

@ -104,12 +104,12 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
// We allow ContentControls without ContentPresenters in the template. This can be
// useful for e.g. a simple ToggleButton that displays an image. There's no need to
// have a ContentPresenter in the visual tree for that.
Presenter = 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>
/// The control class extends <see cref="InputElement"/> and adds the following features:
///
/// - A <see cref="Name"/>.
/// - An inherited <see cref="DataContext"/>.
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - A collection of class strings for custom styling.
@ -71,7 +70,6 @@ namespace Perspex.Controls
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
private string _name;
private IPerspexList<ILogical> _logicalChildren;
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>
/// Gets or sets the styles for the control.
/// </summary>
@ -404,9 +372,9 @@ namespace Perspex.Controls
}
/// <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>();

31
src/Perspex.Controls/ControlExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Perspex.LogicalTree;
using Perspex.Styling;
@ -39,17 +40,35 @@ namespace Perspex.Controls
}
/// <summary>
/// Finds the named control in the specified control.
/// Finds the named control in the scope of the specified control.
/// </summary>
/// <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>
/// <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()
.OfType<T>()
.FirstOrDefault(x => x.Name == name);
return control.GetSelfAndLogicalAncestors()
.OfType<Visual>()
.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);
}
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
if (_popup != null)
{
_popup.Opened -= PopupOpened;
}
_popup = this.GetTemplateChild<Popup>("PART_Popup");
_popup = nameScope.Get<Popup>("PART_Popup");
_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>();
}
}

4
src/Perspex.Controls/ItemsControl.cs

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

22
src/Perspex.Controls/Menu.cs

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

6
src/Perspex.Controls/MenuItem.cs

@ -376,11 +376,11 @@ namespace Perspex.Controls
/// <summary>
/// Called when the MenuItem's template has been applied.
/// </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.PopupRootCreated += PopupRootCreated;
_popup.Opened += PopupOpened;

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

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

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

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

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

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

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

@ -195,19 +195,21 @@ namespace Perspex.Controls.Primitives
_templateLog.Verbose("Creating control template");
var child = Template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Visual)child, nameScope);
// We need to call SetTemplatedParentAndApplyChildTemplates twice - once
// before the controls are added to the visual tree so that the logical
// tree can be set up before styling is applied.
((ISetLogicalParent)child).SetParent(this);
SetTemplatedParentAndApplyChildTemplates(child);
SetupTemplateControls(child, nameScope);
// And again after the controls are added to the visual tree, and have their
// styling and thus Template property set.
AddVisualChild((Visual)child);
SetTemplatedParentAndApplyChildTemplates(child);
SetupTemplateControls(child, nameScope);
OnTemplateApplied();
OnTemplateApplied(nameScope);
}
_templateApplied = true;
@ -217,20 +219,30 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Called when the control's template is applied.
/// </summary>
protected virtual void OnTemplateApplied()
/// <param name="nameScope">The template name scope.</param>
protected virtual void OnTemplateApplied(INameScope nameScope)
{
}
/// <summary>
/// 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>
/// <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)
{
control.SetValue(TemplatedParentProperty, this);
if (control.Name != null)
{
nameScope.Register(control.Name, control);
}
}
control.ApplyTemplate();
@ -239,7 +251,7 @@ namespace Perspex.Controls.Primitives
{
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/>
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);
}

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

@ -41,27 +41,6 @@ namespace Perspex.Controls.Templates
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)
{
var visual = control as IVisual;

4
src/Perspex.Controls/TextBox.cs

@ -136,9 +136,9 @@ namespace Perspex.Controls
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);
}

4
src/Perspex.Controls/TopLevel.cs

@ -287,9 +287,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
throw new InvalidOperationException(
$"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/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
_treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
}

36
src/Perspex.Controls/Window.cs

@ -41,7 +41,7 @@ namespace Perspex.Controls
/// <summary>
/// A top-level window.
/// </summary>
public class Window : TopLevel, IStyleable, IFocusScope
public class Window : TopLevel, IStyleable, IFocusScope, INameScope
{
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -55,8 +55,8 @@ namespace Perspex.Controls
public static readonly PerspexProperty<string> TitleProperty =
PerspexProperty.Register<Window, string>(nameof(Title), "Window");
private NameScope _nameScope = new NameScope();
private object _dialogResult;
private Size _maxPlatformClientSize;
/// <summary>
@ -77,6 +77,20 @@ namespace Perspex.Controls
_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>
/// Gets the platform-specific window implementation.
/// </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/>
protected override Size MeasureOverride(Size availableSize)
{

8
src/Perspex.Input/InputElement.cs

@ -348,9 +348,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(oldRoot);
base.OnDetachedFromVisualTree(e);
if (IsFocused)
{
@ -359,9 +359,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
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.
// 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
{
/// <summary>
/// Gets the element name.
/// </summary>
string Name { get; }
}
}

11
src/Perspex.SceneGraph/IVisual.cs

@ -1,6 +1,7 @@
// 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.Collections;
using Perspex.Media;
using Perspex.VisualTree;
@ -20,6 +21,16 @@ namespace Perspex
/// </remarks>
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>
/// Gets the bounds of the scene graph node relative to its parent.
/// </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\CrossFade.cs" />
<Compile Include="Animation\IPageTransition.cs" />
<Compile Include="INamed.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="Matrix.cs" />
<Compile Include="Media\AlignmentY.cs" />
<Compile Include="Media\AlignmentX.cs" />
@ -101,6 +103,9 @@
<Compile Include="Media\TileBrush.cs" />
<Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\VisualBrush.cs" />
<Compile Include="NameScopeEventArgs.cs" />
<Compile Include="NameScopeExtensions.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" />
<Compile Include="Platform\IBitmapImpl.cs" />
@ -122,6 +127,7 @@
<Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" />
<Compile Include="Visual.cs" />
<Compile Include="VisualTreeAttachmentEventArgs.cs" />
<Compile Include="VisualTree\IHostedVisualTreeRoot.cs" />
<Compile Include="VisualTree\IVisualTreeHost.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
/// in <see cref="VisualExtensions"/>.
/// </remarks>
public class Visual : Animatable, IVisual
public class Visual : Animatable, IVisual, INamed
{
/// <summary>
/// Defines the <see cref="Bounds"/> property.
@ -76,6 +76,11 @@ namespace Perspex
public static readonly PerspexProperty<int> ZIndexProperty =
PerspexProperty.Register<Visual, int>(nameof(ZIndex));
/// <summary>
/// The name of the visual, if any.
/// </summary>
private string _name;
/// <summary>
/// Holds the children of the visual.
/// </summary>
@ -128,6 +133,16 @@ namespace Perspex
_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>
/// Gets the bounds of the scene graph node relative to its parent.
/// </summary>
@ -163,6 +178,36 @@ namespace Perspex
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>
/// Gets the opacity of the scene graph node.
/// </summary>
@ -340,17 +385,19 @@ namespace Perspex
/// <summary>
/// Called when the control is added to a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected virtual void OnAttachedToVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
AttachedToVisualTree?.Invoke(this, e);
}
/// <summary>
/// Called when the control is removed from a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected virtual void OnDetachedFromVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DetachedFromVisualTree?.Invoke(this, e);
}
/// <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>
/// Gets the root of the controls visual tree and the distance from the root.
/// </summary>
@ -436,28 +537,23 @@ namespace Perspex
{
if (_visualParent != value)
{
var old = _visualParent;
var oldRoot = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var newRoot = default(IRenderRoot);
if (value != null)
{
newRoot = value.GetSelfAndVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
}
var oldArgs = GetAttachmentEventArgs();
_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>
/// Calls the <see cref="OnAttachedToVisualTree(IRenderRoot)"/> method for this control
/// and all of its visual descendents.
/// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
private void NotifyAttachedToVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
private void NotifyAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_visualLogger.Verbose("Attached to visual tree");
_isAttachedToVisualTree = true;
OnAttachedToVisualTree(root);
if (Name != null && e.NameScope != null)
{
e.NameScope.Register(Name, this);
}
OnAttachedToVisualTree(e);
if (_visualChildren != null)
{
foreach (Visual child in _visualChildren.OfType<Visual>())
{
child.NotifyAttachedToVisualTree(root);
var ce = child.GetAttachmentEventArgs(e);
child.NotifyAttachedToVisualTree(ce);
}
}
}
/// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(IRenderRoot)"/> method for this control
/// and all of its visual descendents.
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
private void NotifyDetachedFromVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
private void NotifyDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_visualLogger.Verbose("Detached from visual tree");
if (Name != null && e.NameScope != null)
{
e.NameScope.Unregister(Name);
}
_isAttachedToVisualTree = false;
OnDetachedFromVisualTree(root);
OnDetachedFromVisualTree(e);
if (_visualChildren != null)
{
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="Styling\Classes.cs" />
<Compile Include="Styling\IGlobalStyles.cs" />
<Compile Include="Styling\INamed.cs" />
<Compile Include="Styling\ISetter.cs" />
<Compile Include="Styling\IStyle.cs" />
<Compile Include="Styling\IStyleable.cs" />

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

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

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

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

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

@ -214,7 +214,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var popup = target.GetTemplateChild<Popup>("popup");
var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
popup.Open();
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.
using System;
using System.Linq;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Media;
@ -20,7 +21,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 50;
Assert.Equal(track.Value, 50);
@ -35,7 +36,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
track.Value = 50;
Assert.Equal(target.Value, 50);
@ -51,7 +52,7 @@ namespace Perspex.Controls.UnitTests.Primitives
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 25;
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
{
SourcePropertyPath = "Items",
Path = "Items",
Mode = BindingMode.OneWay,
};
var selectedItemsBinding = new Binding
{
SourcePropertyPath = "SelectedItems",
Path = "SelectedItems",
Mode = BindingMode.OneWay,
};

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

@ -50,6 +50,29 @@ namespace Perspex.Controls.UnitTests.Primitives
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]
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();
var presenter = target.GetTemplateChild<ScrollContentPresenter>("contentPresenter");
Assert.IsType<TextBlock>(presenter.Child);
Assert.IsType<TextBlock>(target.Presenter.Child);
}
[Fact]
@ -65,7 +63,7 @@ namespace Perspex.Controls.UnitTests
{
new ScrollContentPresenter
{
Name = "contentPresenter",
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~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()
{
var collection = new ObservableCollection<TabItem>()
{
new TabItem
{
new TabItem
{
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
};
Name = "first",
Content = "foo",
},
new TabItem
{
Name = "second",
Content = "bar",
},
new TabItem
{
Name = "3rd",
Content = "barf",
},
};
var target = new TabControl
{

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

@ -14,7 +14,7 @@ namespace Perspex.Controls.UnitTests
base.AddVisualChild(visual);
}
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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.Collections.Generic;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Reactive.Testing;
using Perspex.Markup.Data;
using Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
public class ExpressionObserverTests_Property
{
@ -203,6 +204,26 @@ namespace Perspex.Markup.UnitTests.Binding
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]
public void SetValue_Should_Set_Simple_Property_Value()
{
@ -333,5 +354,10 @@ namespace Perspex.Markup.UnitTests.Binding
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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 Xunit;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
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.Linq;
namespace Perspex.Markup.UnitTests.Binding
namespace Perspex.Markup.UnitTests.Data
{
public class NotifyingBase : INotifyPropertyChanged
{

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

@ -34,6 +34,11 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<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.Core" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@ -72,19 +77,21 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Binding\ExpressionObserverTests_PerspexProperty.cs" />
<Compile Include="Binding\ExpressionSubjectTests.cs" />
<Compile Include="Binding\ExpressionObserverTests_Observable.cs" />
<Compile Include="Binding\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Binding\ExpressionObserverTests_Task.cs" />
<Compile Include="Binding\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Binding\ExpressionObserverTests_Negation.cs" />
<Compile Include="Binding\ExpressionObserverTests_Property.cs" />
<Compile Include="Binding\ExpressionNodeBuilderTests.cs" />
<Compile Include="Binding\NotifyingBase.cs" />
<Compile Include="ControlLocatorTests.cs" />
<Compile Include="Data\ExpressionNodeBuilderTests.cs" />
<Compile Include="Data\ExpressionNodeBuilderTests_Errors.cs" />
<Compile Include="Data\ExpressionObserverTests_Indexer.cs" />
<Compile Include="Data\ExpressionObserverTests_Negation.cs" />
<Compile Include="Data\ExpressionObserverTests_Observable.cs" />
<Compile Include="Data\ExpressionObserverTests_PerspexProperty.cs" />
<Compile Include="Data\ExpressionObserverTests_Property.cs" />
<Compile Include="Data\ExpressionObserverTests_SetValue.cs" />
<Compile Include="Data\ExpressionObserverTests_Task.cs" />
<Compile Include="Data\ExpressionSubjectTests.cs" />
<Compile Include="Data\NotifyingBase.cs" />
<Compile Include="DefaultValueConverterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="UnitTestSynchronizationContext.cs" />
</ItemGroup>
<ItemGroup>
@ -95,10 +102,38 @@
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name>
</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">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</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>
<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-Main" 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.abstractions" 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 binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneWay,
};
@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget();
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.TwoWay,
};
@ -58,7 +58,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(dataContext: dataContext);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneTime,
};
@ -87,7 +87,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
@ -102,7 +102,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -127,7 +127,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding
{
SourcePropertyPath = "Header",
Path = "Header",
};
binding.Bind(parent.Child, Control.DataContextProperty);
@ -145,7 +145,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -161,7 +161,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding
{
Converter = converter.Object,
SourcePropertyPath = "Foo",
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -189,13 +189,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var fooBinding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.TwoWay,
};
var barBinding = new Binding
{
SourcePropertyPath = "Bar",
Path = "Bar",
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,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.TwoWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -62,7 +62,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
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(),
Bindings = new[]
{
new Binding { SourcePropertyPath = "A" },
new Binding { SourcePropertyPath = "B" },
new Binding { SourcePropertyPath = "C" },
new Binding { Path = "A" },
new Binding { Path = "B" },
new Binding { Path = "C" },
}
};

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

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

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

@ -3,7 +3,7 @@
using System;
namespace Perspex.Xaml.Base.UnitTest
namespace Perspex.Markup.Xaml.UnitTests
{
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 System;
namespace Perspex.Xaml.Base.UnitTest
namespace Perspex.Markup.Xaml.UnitTests
{
internal class TypeProviderMock : ITypeProvider
{

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

@ -4,7 +4,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Perspex.Xaml.Base.UnitTest
namespace Perspex.Markup.Xaml.UnitTests
{
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 />
</Choose>
<ItemGroup>
<Compile Include="NameScopeTests.cs" />
<Compile Include="RelativeRectComparer.cs" />
<Compile Include="RelativeRectTests.cs" />
<Compile Include="ThicknessTests.cs" />

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

@ -7,8 +7,22 @@ using Perspex.Rendering;
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
{
get { throw new NotImplementedException(); }
@ -23,5 +37,20 @@ namespace Perspex.SceneGraph.UnitTests
{
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 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)
{
@ -45,21 +62,5 @@ namespace Perspex.SceneGraph.UnitTests
{
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.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.VisualTree;
using Xunit;
@ -84,5 +85,91 @@ namespace Perspex.SceneGraph.UnitTests
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