Browse Source

Merge pull request #2705 from AvaloniaUI/name-scopes

Manually handle name scope registrations
pull/2740/head
Nikita Tsukanov 7 years ago
committed by GitHub
parent
commit
0948300b43
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/Base.props
  2. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  3. 106
      src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs
  4. 10
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  5. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  6. 21
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  7. 19
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  8. 14
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  9. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  10. 30
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  11. 10
      src/Avalonia.Controls/Templates/FuncControlTemplate.cs
  12. 6
      src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs
  13. 8
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  14. 26
      src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
  15. 14
      src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs
  16. 15
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  17. 4
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  18. 15
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
  19. 22
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  20. 4
      src/Avalonia.Controls/Templates/ITemplate`2.cs
  21. 34
      src/Avalonia.Controls/UserControl.cs
  22. 34
      src/Avalonia.Controls/Window.cs
  23. 4
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  24. 73
      src/Avalonia.Styling/Controls/ChildNameScope.cs
  25. 34
      src/Avalonia.Styling/Controls/INameScope.cs
  26. 99
      src/Avalonia.Styling/Controls/NameScope.cs
  27. 41
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  28. 64
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  29. 74
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  30. 21
      src/Avalonia.Styling/StyledElement.cs
  31. 1
      src/Avalonia.Styling/Styling/Setter.cs
  32. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  33. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  34. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  35. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  36. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  37. 5
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  38. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  39. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  40. 22
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  41. 107
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  42. 26
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  43. 36
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  44. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  45. 8
      src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs
  46. 14
      src/Markup/Avalonia.Markup/Data/Binding.cs
  47. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  48. 7
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  49. 13
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  50. 8
      tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs
  51. 4
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  52. 8
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  53. 10
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  54. 10
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  55. 21
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  56. 4
      tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
  57. 31
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  58. 30
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  59. 11
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  60. 13
      tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs
  61. 132
      tests/Avalonia.Controls.UnitTests/NameScopeTests.cs
  62. 29
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  63. 16
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  64. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs
  65. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  66. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  67. 8
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  68. 6
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  69. 8
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  70. 4
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
  71. 8
      tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
  72. 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  73. 4
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs
  74. 10
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  75. 4
      tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs
  76. 112
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  77. 10
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  78. 16
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  79. 4
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  80. 4
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  81. 4
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  82. 35
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  83. 4
      tests/Avalonia.Controls.UnitTests/UserControlTests.cs
  84. 4
      tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs
  85. 4
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  86. 8
      tests/Avalonia.LeakTests/ControlTests.cs
  87. 15
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs
  88. 5
      tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs
  89. 6
      tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs
  90. 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  91. 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  92. 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  93. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  94. 6
      tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs
  95. 6
      tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs
  96. 155
      tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs
  97. 12
      tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
  98. 13
      tests/Avalonia.Styling.UnitTests/SetterTests.cs
  99. 15
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
  100. 69
      tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs

2
build/Base.props

@ -2,4 +2,4 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -150,6 +150,7 @@
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" /> <Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Base.props" />
<Import Project="..\..\..\build\Rx.props" /> <Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\System.Memory.props" /> <Import Project="..\..\..\build\System.Memory.props" />
<Import Project="..\..\..\build\AndroidWorkarounds.props" /> <Import Project="..\..\..\build\AndroidWorkarounds.props" />

106
src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
/// <summary>
/// A task-like operation that is guaranteed to finish continuations synchronously,
/// can be used for parametrized one-shot events
/// </summary>
public struct SynchronousCompletionAsyncResult<T> : INotifyCompletion
{
private readonly SynchronousCompletionAsyncResultSource<T> _source;
private readonly T _result;
private readonly bool _isValid;
internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource<T> source)
{
_source = source;
_result = default;
_isValid = true;
}
public SynchronousCompletionAsyncResult(T result)
{
_result = result;
_source = null;
_isValid = true;
}
static void ThrowNotInitialized() =>
throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized");
public bool IsCompleted
{
get
{
if (!_isValid)
ThrowNotInitialized();
return _source == null || _source.IsCompleted;
}
}
public T GetResult()
{
if (!_isValid)
ThrowNotInitialized();
return _source == null ? _result : _source.Result;
}
public void OnCompleted(Action continuation)
{
if (!_isValid)
ThrowNotInitialized();
if (_source == null)
continuation();
else
_source.OnCompleted(continuation);
}
public SynchronousCompletionAsyncResult<T> GetAwaiter() => this;
}
/// <summary>
/// Source for incomplete SynchronousCompletionAsyncResult
/// </summary>
/// <typeparam name="T"></typeparam>
public class SynchronousCompletionAsyncResultSource<T>
{
private T _result;
internal bool IsCompleted { get; private set; }
public SynchronousCompletionAsyncResult<T> AsyncResult => new SynchronousCompletionAsyncResult<T>(this);
internal T Result => IsCompleted ?
_result :
throw new InvalidOperationException("Asynchronous operation is not yet completed");
private List<Action> _continuations;
internal void OnCompleted(Action continuation)
{
if(_continuations==null)
_continuations = new List<Action>();
_continuations.Add(continuation);
}
public void SetResult(T result)
{
if (IsCompleted)
throw new InvalidOperationException("Asynchronous operation is already completed");
_result = result;
IsCompleted = true;
if(_continuations!=null)
foreach (var c in _continuations)
c();
_continuations = null;
}
public void TrySetResult(T result)
{
if(IsCompleted)
return;
SetResult(result);
}
}
}

10
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -75,9 +75,9 @@ namespace Avalonia.Build.Tasks
.First(c => c.Parameters.Count == 1)); .First(c => c.Parameters.Count == 1));
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var rootServiceProviderField = asm.MainModule.ImportReference( var createRootServiceProviderMethod = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Fields typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
.First(x => x.Name == "RootServiceProviderV1")); .First(x => x.Name == "CreateRootServiceProviderV2"));
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader", var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object); TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@ -211,7 +211,7 @@ namespace Avalonia.Build.Tasks
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
classTypeDefinition.Methods.Add(trampoline); classTypeDefinition.Methods.Add(trampoline);
var regularStart = Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField); var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart));
@ -307,7 +307,7 @@ namespace Avalonia.Build.Tasks
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor)); i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
else else
{ {
i.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField)); i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod)); i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
} }

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -795,7 +795,7 @@ namespace Avalonia.Controls
var template = var template =
new FuncDataTemplate( new FuncDataTemplate(
typeof(object), typeof(object),
o => (o, _) =>
{ {
var control = new ContentControl(); var control = new ContentControl();
control.Bind(ContentControl.ContentProperty, value); control.Bind(ContentControl.ContentProperty, value);

21
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -8,7 +8,7 @@ using JetBrains.Annotations;
namespace Avalonia.Controls.Embedding namespace Avalonia.Controls.Embedding
{ {
public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, INameScope, IDisposable public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, IDisposable
{ {
public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl) public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl)
{ {
@ -51,25 +51,6 @@ namespace Avalonia.Controls.Embedding
return rv; return rv;
} }
private readonly NameScope _nameScope = new NameScope();
public event EventHandler<NameScopeEventArgs> Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
public void Register(string name, object element) => _nameScope.Register(name, element);
public object Find(string name) => _nameScope.Find(name);
public void Unregister(string name) => _nameScope.Unregister(name);
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose() => PlatformImpl?.Dispose(); public void Dispose() => PlatformImpl?.Dispose();
} }

19
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -30,25 +30,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
init.EndInit(); init.EndInit();
} }
} }
private readonly NameScope _nameScope = new NameScope();
public event EventHandler<NameScopeEventArgs> Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
public void Register(string name, object element) => _nameScope.Register(name, element);
public object Find(string name) => _nameScope.Find(name);
public void Unregister(string name) => _nameScope.Unregister(name);
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose() public void Dispose()

14
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -92,7 +92,6 @@ namespace Avalonia.Controls.Generators
result.DataContext = item; result.DataContext = item;
} }
NameScope.SetNameScope((Control)(object)result, new NameScope());
Index.Add(item, result); Index.Add(item, result);
return result; return result;
@ -123,11 +122,20 @@ namespace Avalonia.Controls.Generators
return false; return false;
} }
class WrapperTreeDataTemplate : ITreeDataTemplate
{
private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param);
public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null;
}
private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary) private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary)
{ {
var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default; var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default;
var treeTemplate = template as ITreeDataTemplate ?? var treeTemplate = template as ITreeDataTemplate ?? new WrapperTreeDataTemplate(template);
new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
return treeTemplate; return treeTemplate;
} }
} }

6
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -325,12 +325,6 @@ namespace Avalonia.Controls.Presenters
{ {
_dataTemplate = dataTemplate; _dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content); newChild = _dataTemplate.Build(content);
// Give the new control its own name scope.
if (newChild is Control controlResult)
{
NameScope.SetNameScope(controlResult, new NameScope());
}
} }
} }
else else

30
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -257,13 +257,14 @@ namespace Avalonia.Controls.Primitives
{ {
Logger.Verbose(LogArea.Control, this, "Creating control template"); Logger.Verbose(LogArea.Control, this, "Creating control template");
var child = template.Build(this); var (child, nameScope) = template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Control)child, nameScope);
ApplyTemplatedParent(child); ApplyTemplatedParent(child);
RegisterNames(child, nameScope);
((ISetLogicalParent)child).SetParent(this); ((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child); VisualChildren.Add(child);
// Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null)
nameScope = new NameScope();
OnTemplateApplied(new TemplateAppliedEventArgs(nameScope)); OnTemplateApplied(new TemplateAppliedEventArgs(nameScope));
} }
@ -342,26 +343,5 @@ namespace Avalonia.Controls.Primitives
} }
} }
} }
/// <summary>
/// Registers each control with its name scope.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="nameScope">The name scope.</param>
private void RegisterNames(IControl control, INameScope nameScope)
{
if (control.Name != null)
{
nameScope.Register(control.Name, control);
}
if (control.TemplatedParent == this)
{
foreach (IControl child in control.GetLogicalChildren())
{
RegisterNames(child, nameScope);
}
}
}
} }
} }

10
src/Avalonia.Controls/Templates/FuncControlTemplate.cs

@ -16,9 +16,15 @@ namespace Avalonia.Controls.Templates
/// Initializes a new instance of the <see cref="FuncControlTemplate"/> class. /// Initializes a new instance of the <see cref="FuncControlTemplate"/> class.
/// </summary> /// </summary>
/// <param name="build">The build function.</param> /// <param name="build">The build function.</param>
public FuncControlTemplate(Func<ITemplatedControl, IControl> build) public FuncControlTemplate(Func<ITemplatedControl, INameScope, IControl> build)
: base(build) : base(build)
{ {
} }
public new ControlTemplateResult Build(ITemplatedControl param)
{
var (control, scope) = BuildWithNameScope(param);
return new ControlTemplateResult(control, scope);
}
} }
} }

6
src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs

@ -17,9 +17,9 @@ namespace Avalonia.Controls.Templates
/// Initializes a new instance of the <see cref="FuncControlTemplate{T}"/> class. /// Initializes a new instance of the <see cref="FuncControlTemplate{T}"/> class.
/// </summary> /// </summary>
/// <param name="build">The build function.</param> /// <param name="build">The build function.</param>
public FuncControlTemplate(Func<T, IControl> build) public FuncControlTemplate(Func<T, INameScope, IControl> build)
: base(x => build((T)x)) : base((x, s) => build((T)x, s))
{ {
} }
} }
} }

8
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -17,7 +17,7 @@ namespace Avalonia.Controls.Templates
/// </summary> /// </summary>
public static readonly FuncDataTemplate Default = public static readonly FuncDataTemplate Default =
new FuncDataTemplate<object>( new FuncDataTemplate<object>(
data => (data, s) =>
{ {
if (data != null) if (data != null)
{ {
@ -49,7 +49,7 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param> /// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate( public FuncDataTemplate(
Type type, Type type,
Func<object, IControl> build, Func<object, INameScope, IControl> build,
bool supportsRecycling = false) bool supportsRecycling = false)
: this(o => IsInstance(o, type), build, supportsRecycling) : this(o => IsInstance(o, type), build, supportsRecycling)
{ {
@ -67,7 +67,7 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param> /// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate( public FuncDataTemplate(
Func<object, bool> match, Func<object, bool> match,
Func<object, IControl> build, Func<object, INameScope, IControl> build,
bool supportsRecycling = false) bool supportsRecycling = false)
: base(build) : base(build)
{ {
@ -105,4 +105,4 @@ namespace Avalonia.Controls.Templates
return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
} }
} }
} }

26
src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Templates
/// A function which when passed an object of <typeparamref name="T"/> returns a control. /// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param> /// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param> /// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(Func<T, IControl> build, bool supportsRecycling = false) public FuncDataTemplate(Func<T, INameScope, IControl> build, bool supportsRecycling = false)
: base(typeof(T), CastBuild(build), supportsRecycling) : base(typeof(T), CastBuild(build), supportsRecycling)
{ {
} }
@ -35,12 +35,30 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param> /// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate( public FuncDataTemplate(
Func<T, bool> match, Func<T, bool> match,
Func<T, IControl> build, Func<T, INameScope, IControl> build,
bool supportsRecycling = false) bool supportsRecycling = false)
: base(CastMatch(match), CastBuild(build), supportsRecycling) : base(CastMatch(match), CastBuild(build), supportsRecycling)
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Func<T, bool> match,
Func<T, IControl> build,
bool supportsRecycling = false)
: this(match, (a, _) => build(a), supportsRecycling)
{
}
/// <summary> /// <summary>
/// Casts a strongly typed match function to a weakly typed one. /// Casts a strongly typed match function to a weakly typed one.
/// </summary> /// </summary>
@ -57,9 +75,9 @@ namespace Avalonia.Controls.Templates
/// <typeparam name="TResult">The strong data type.</typeparam> /// <typeparam name="TResult">The strong data type.</typeparam>
/// <param name="f">The strongly typed function.</param> /// <param name="f">The strongly typed function.</param>
/// <returns>The weakly typed function.</returns> /// <returns>The weakly typed function.</returns>
private static Func<object, TResult> CastBuild<TResult>(Func<T, TResult> f) private static Func<object, INameScope, TResult> CastBuild<TResult>(Func<T, INameScope, TResult> f)
{ {
return o => f((T)o); return (o, s) => f((T)o, s);
} }
} }
} }

14
src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Controls.Templates
{
public static class FuncTemplateNameScopeExtensions
{
public static T RegisterInNameScope<T>(this T control, INameScope scope)
where T : StyledElement
{
scope.Register(control.Name, control);
return control;
}
}
}

15
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls.Templates
public class FuncTemplate<TParam, TControl> : ITemplate<TParam, TControl> public class FuncTemplate<TParam, TControl> : ITemplate<TParam, TControl>
where TControl : IControl where TControl : IControl
{ {
private readonly Func<TParam, TControl> _func; private readonly Func<TParam, INameScope, TControl> _func;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FuncTemplate{TControl, TParam}"/> class. /// Initializes a new instance of the <see cref="FuncTemplate{TControl, TParam}"/> class.
/// </summary> /// </summary>
/// <param name="func">The function used to create the control.</param> /// <param name="func">The function used to create the control.</param>
public FuncTemplate(Func<TParam, TControl> func) public FuncTemplate(Func<TParam, INameScope, TControl> func)
{ {
Contract.Requires<ArgumentNullException>(func != null); Contract.Requires<ArgumentNullException>(func != null);
@ -35,7 +35,14 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
public TControl Build(TParam param) public TControl Build(TParam param)
{ {
return _func(param); return BuildWithNameScope(param).control;
}
protected (TControl control, INameScope nameScope) BuildWithNameScope(TParam param)
{
var scope = new NameScope();
var rv = _func(param, scope);
return (rv, scope);
} }
} }
} }

4
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Templates
/// </param> /// </param>
public FuncTreeDataTemplate( public FuncTreeDataTemplate(
Type type, Type type,
Func<object, IControl> build, Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector) Func<object, IEnumerable> itemsSelector)
: this(o => IsInstance(o, type), build, itemsSelector) : this(o => IsInstance(o, type), build, itemsSelector)
{ {
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Templates
/// </param> /// </param>
public FuncTreeDataTemplate( public FuncTreeDataTemplate(
Func<object, bool> match, Func<object, bool> match,
Func<object, IControl> build, Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector) Func<object, IEnumerable> itemsSelector)
: base(match, build) : base(match, build)
{ {

15
src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.Templates
/// items. /// items.
/// </param> /// </param>
public FuncTreeDataTemplate( public FuncTreeDataTemplate(
Func<T, Control> build, Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector) Func<T, IEnumerable> itemsSelector)
: base( : base(
typeof(T), typeof(T),
@ -46,7 +46,7 @@ namespace Avalonia.Controls.Templates
/// </param> /// </param>
public FuncTreeDataTemplate( public FuncTreeDataTemplate(
Func<T, bool> match, Func<T, bool> match,
Func<T, Control> build, Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector) Func<T, IEnumerable> itemsSelector)
: base( : base(
CastMatch(match), CastMatch(match),
@ -65,6 +65,17 @@ namespace Avalonia.Controls.Templates
return o => (o is T) && f((T)o); return o => (o is T) && f((T)o);
} }
/// <summary>
/// Casts a function with a typed parameter to an untyped function.
/// </summary>
/// <typeparam name="TResult">The result.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<object, INameScope, TResult> Cast<TResult>(Func<T, INameScope, TResult> f)
{
return (o, s) => f((T)o, s);
}
/// <summary> /// <summary>
/// Casts a function with a typed parameter to an untyped function. /// Casts a function with a typed parameter to an untyped function.
/// </summary> /// </summary>

22
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -9,7 +9,25 @@ namespace Avalonia.Controls.Templates
/// <summary> /// <summary>
/// Interface representing a template used to build a <see cref="TemplatedControl"/>. /// Interface representing a template used to build a <see cref="TemplatedControl"/>.
/// </summary> /// </summary>
public interface IControlTemplate : ITemplate<ITemplatedControl, IControl> public interface IControlTemplate : ITemplate<ITemplatedControl, ControlTemplateResult>
{ {
} }
}
public class ControlTemplateResult
{
public IControl Control { get; }
public INameScope NameScope { get; }
public ControlTemplateResult(IControl control, INameScope nameScope)
{
Control = control;
NameScope = nameScope;
}
public void Deconstruct(out IControl control, out INameScope scope)
{
control = Control;
scope = NameScope;
}
}
}

4
src/Avalonia.Controls/Templates/ITemplate`2.cs

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Templates
/// </summary> /// </summary>
/// <typeparam name="TParam">The type of the parameter.</typeparam> /// <typeparam name="TParam">The type of the parameter.</typeparam>
/// <typeparam name="TControl">The type of control.</typeparam> /// <typeparam name="TControl">The type of control.</typeparam>
public interface ITemplate<TParam, TControl> where TControl : IControl public interface ITemplate<TParam, TControl>
{ {
/// <summary> /// <summary>
/// Creates the control. /// Creates the control.
@ -19,4 +19,4 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
TControl Build(TParam param); TControl Build(TParam param);
} }
} }

34
src/Avalonia.Controls/UserControl.cs

@ -9,40 +9,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic. /// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </summary> /// </summary>
public class UserControl : ContentControl, IStyleable, INameScope public class UserControl : ContentControl, IStyleable
{ {
private readonly NameScope _nameScope = new NameScope();
/// <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; }
}
/// <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);
}
} }
} }

34
src/Avalonia.Controls/Window.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A top-level window. /// A top-level window.
/// </summary> /// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{ {
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -157,20 +157,6 @@ namespace Avalonia.Controls
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
} }
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
/// </summary> /// </summary>
@ -494,24 +480,6 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{
_nameScope.Register(name, element);
}
/// <inheritdoc/>
object INameScope.Find(string name)
{
return _nameScope.Find(name);
}
/// <inheritdoc/>
void INameScope.Unregister(string name)
{
_nameScope.Unregister(name);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {

4
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -16,7 +16,7 @@ namespace Avalonia.ReactiveUI
/// </summary> /// </summary>
public class AutoDataTemplateBindingHook : IPropertyBindingHook public class AutoDataTemplateBindingHook : IPropertyBindingHook
{ {
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>(x => private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>((x, _) =>
{ {
var control = new ViewModelViewHost(); var control = new ViewModelViewHost();
var context = control.GetObservable(Control.DataContextProperty); var context = control.GetObservable(Control.DataContextProperty);
@ -52,4 +52,4 @@ namespace Avalonia.ReactiveUI
return true; return true;
} }
} }
} }

73
src/Avalonia.Styling/Controls/ChildNameScope.cs

@ -0,0 +1,73 @@
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class ChildNameScope : INameScope
{
private readonly INameScope _parentScope;
private readonly NameScope _inner = new NameScope();
public ChildNameScope(INameScope parentScope)
{
_parentScope = parentScope;
}
public void Register(string name, object element) => _inner.Register(name, element);
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
return new SynchronousCompletionAsyncResult<object>(found);
// Not found and both current and parent scope are in completed state
if(IsCompleted)
return new SynchronousCompletionAsyncResult<object>(null);
return DoFindAsync(name);
}
public SynchronousCompletionAsyncResult<object> DoFindAsync(string name)
{
var src = new SynchronousCompletionAsyncResultSource<object>();
void ParentSearch()
{
var parentSearch = _parentScope.FindAsync(name);
if (parentSearch.IsCompleted)
src.SetResult(parentSearch.GetResult());
else
parentSearch.OnCompleted(() => src.SetResult(parentSearch.GetResult()));
}
if (!_inner.IsCompleted)
{
// Guaranteed to be incomplete at this point
var innerSearch = _inner.FindAsync(name);
innerSearch.OnCompleted(() =>
{
var value = innerSearch.GetResult();
if (value != null)
src.SetResult(value);
else ParentSearch();
});
}
else
ParentSearch();
return src.AsyncResult;
}
public object Find(string name)
{
var found = _inner.Find(name);
if (found != null)
return found;
if (_inner.IsCompleted)
return _parentScope.Find(name);
return null;
}
public void Complete() => _inner.Complete();
public bool IsCompleted => _inner.IsCompleted && _parentScope.IsCompleted;
}
}

34
src/Avalonia.Styling/Controls/INameScope.cs

@ -2,6 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -10,16 +12,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public interface INameScope 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> /// <summary>
/// Registers an element in the name scope. /// Registers an element in the name scope.
/// </summary> /// </summary>
@ -28,16 +20,30 @@ namespace Avalonia.Controls
void Register(string name, object element); void Register(string name, object element);
/// <summary> /// <summary>
/// Finds a named element in the name scope. /// Finds a named element in the name scope, waits for the scope to be completely populated before returning null
/// Returned task is configured to run any continuations synchronously.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
SynchronousCompletionAsyncResult<object> FindAsync(string name);
/// <summary>
/// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns> /// <returns>The element, or null if the name was not found.</returns>
object Find(string name); object Find(string name);
/// <summary> /// <summary>
/// Unregisters an element with the name scope. /// Marks the name scope as completed, no further registrations will be allowed
/// </summary> /// </summary>
/// <param name="name">The name.</param> void Complete();
void Unregister(string name);
/// <summary>
/// Returns whether further registrations are allowed on the scope
/// </summary>
bool IsCompleted { get; }
} }
} }

99
src/Avalonia.Styling/Controls/NameScope.cs

@ -3,7 +3,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -18,44 +20,14 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<INameScope> NameScopeProperty = public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope"); AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope");
/// <inheritdoc/>
public bool IsCompleted { get; private set; }
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>(); private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
/// <summary> private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches =
/// Raised when an element is registered with the name scope. new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>();
/// </summary>
public event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Finds the containing name scope for a styled element.
/// </summary>
/// <param name="styled">The styled element.</param>
/// <returns>The containing name scope.</returns>
public static INameScope FindNameScope(StyledElement styled)
{
Contract.Requires<ArgumentNullException>(styled != null);
INameScope result;
while (styled != null)
{
result = styled as INameScope ?? GetNameScope(styled);
if (result != null)
{
return result;
}
styled = (styled as ILogical)?.LogicalParent as StyledElement;
}
return null;
}
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element. /// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
/// </summary> /// </summary>
@ -80,13 +52,11 @@ namespace Avalonia.Controls
styled.SetValue(NameScopeProperty, value); styled.SetValue(NameScopeProperty, value);
} }
/// <summary> /// <inheritdoc />
/// 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) public void Register(string name, object element)
{ {
if (IsCompleted)
throw new InvalidOperationException("NameScope is completed, no further registrations are allowed");
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
@ -102,15 +72,29 @@ namespace Avalonia.Controls
else else
{ {
_inner.Add(name, element); _inner.Add(name, element);
Registered?.Invoke(this, new NameScopeEventArgs(name, element)); if (_pendingSearches.TryGetValue(name, out var tcs))
{
_pendingSearches.Remove(name);
tcs.SetResult(element);
}
} }
} }
/// <summary> public SynchronousCompletionAsyncResult<object> FindAsync(string name)
/// Finds a named element in the name scope. {
/// </summary> var found = Find(name);
/// <param name="name">The name.</param> if (found != null)
/// <returns>The element, or null if the name was not found.</returns> return new SynchronousCompletionAsyncResult<object>(found);
if (IsCompleted)
return new SynchronousCompletionAsyncResult<object>((object)null);
if (!_pendingSearches.TryGetValue(name, out var tcs))
// We are intentionally running continuations synchronously here
_pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object>();
return tcs.AsyncResult;
}
/// <inheritdoc />
public object Find(string name) public object Find(string name)
{ {
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
@ -120,21 +104,14 @@ namespace Avalonia.Controls
return result; return result;
} }
/// <summary> public void Complete()
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
public void Unregister(string name)
{ {
Contract.Requires<ArgumentNullException>(name != null); IsCompleted = true;
foreach (var kp in _pendingSearches)
object element; kp.Value.TrySetResult(null);
_pendingSearches.Clear();
if (_inner.TryGetValue(name, out element))
{
_inner.Remove(name);
Unregistered?.Invoke(this, new NameScopeEventArgs(name, element));
}
} }
} }
} }

41
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@ -37,6 +37,25 @@ namespace Avalonia.Controls
return (T)result; return (T)result;
} }
/// <summary>
/// Finds a named element in an <see cref="INameScope"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="anchor">The control to take the name scope from.</param>
/// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns>
public static T Find<T>(this ILogical anchor, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(anchor != null);
Contract.Requires<ArgumentNullException>(name != null);
var styledAnchor = anchor as StyledElement;
if (styledAnchor == null)
return null;
var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope(styledAnchor);
return nameScope?.Find<T>(name);
}
/// <summary> /// <summary>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the /// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found. /// requested name was found.
@ -67,6 +86,28 @@ namespace Avalonia.Controls
return (T)result; 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="anchor">The control to take the name scope from.</param>
/// <param name="name">The name.</param>
/// <returns>The named element.</returns>
public static T Get<T>(this ILogical anchor, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(anchor != null);
Contract.Requires<ArgumentNullException>(name != null);
var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor);
if (nameScope == null)
throw new InvalidOperationException(
"The control doesn't have an associated name scope, probably no registrations has been done yet");
return nameScope.Get<T>(name);
}
public static INameScope FindNameScope(this ILogical control) public static INameScope FindNameScope(this ILogical control)
{ {
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);

64
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Threading.Tasks;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class NameScopeLocator
{
/// <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<object> Track(INameScope scope, string name)
{
return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name));
}
// This class is implemented in such weird way because for some reason
// our binding system doesn't expect OnCompleted to be ever called and
// seems to treat it as binding cancellation or something
private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T>
{
private T _value;
private SynchronousCompletionAsyncResult<T>? _asyncResult;
public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task)
{
if (task.IsCompleted)
_value = task.GetResult();
else
_asyncResult = task;
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (_asyncResult?.IsCompleted == true)
{
_value = _asyncResult.Value.GetResult();
_asyncResult = null;
}
if (_asyncResult != null)
_asyncResult.Value.OnCompleted(() =>
{
observer.OnNext(_asyncResult.Value.GetResult());
});
else
observer.OnNext(_value);
return Disposable.Empty;
}
}
}
}

74
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@ -15,18 +15,6 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
public static class ControlLocator 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<ILogical> Track(ILogical relativeTo, string name)
{
return new ControlTracker(relativeTo, name);
}
public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null) public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null)
{ {
return new ControlTracker(relativeTo, ancestorLevel, ancestorType); return new ControlTracker(relativeTo, ancestorLevel, ancestorType);
@ -35,18 +23,10 @@ namespace Avalonia.LogicalTree
private class ControlTracker : LightweightObservableBase<ILogical> private class ControlTracker : LightweightObservableBase<ILogical>
{ {
private readonly ILogical _relativeTo; private readonly ILogical _relativeTo;
private readonly string _name;
private readonly int _ancestorLevel; private readonly int _ancestorLevel;
private readonly Type _ancestorType; private readonly Type _ancestorType;
INameScope _nameScope;
ILogical _value; ILogical _value;
public ControlTracker(ILogical relativeTo, string name)
{
_relativeTo = relativeTo;
_name = name;
}
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType) public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
{ {
_relativeTo = relativeTo; _relativeTo = relativeTo;
@ -66,12 +46,6 @@ namespace Avalonia.LogicalTree
_relativeTo.AttachedToLogicalTree -= Attached; _relativeTo.AttachedToLogicalTree -= Attached;
_relativeTo.DetachedFromLogicalTree -= Detached; _relativeTo.DetachedFromLogicalTree -= Detached;
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null; _value = null;
} }
@ -88,57 +62,15 @@ namespace Avalonia.LogicalTree
private void Detached(object sender, LogicalTreeAttachmentEventArgs e) private void Detached(object sender, LogicalTreeAttachmentEventArgs e)
{ {
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null; _value = null;
PublishNext(null); PublishNext(null);
} }
private void Registered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name && e.Element is ILogical logical)
{
_value = logical;
PublishNext(logical);
}
}
private void Unregistered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name)
{
_value = null;
PublishNext(null);
}
}
private void Update() private void Update()
{ {
if (_name != null) _value = _relativeTo.GetLogicalAncestors()
{ .Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
_nameScope = _relativeTo.FindNameScope(); .ElementAtOrDefault(_ancestorLevel);
if (_nameScope != null)
{
_nameScope.Registered += Registered;
_nameScope.Unregistered += Unregistered;
_value = _nameScope.Find<ILogical>(_name);
}
else
{
_value = null;
}
}
else
{
_value = _relativeTo.GetLogicalAncestors()
.Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(_ancestorLevel);
}
} }
} }
} }

21
src/Avalonia.Styling/StyledElement.cs

@ -61,7 +61,6 @@ namespace Avalonia
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
private bool _isAttachedToLogicalTree; private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren; private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources; private IResourceDictionary _resources;
private Styles _styles; private Styles _styles;
private bool _styled; private bool _styled;
@ -82,7 +81,6 @@ namespace Avalonia
/// </summary> /// </summary>
public StyledElement() public StyledElement()
{ {
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot; _isAttachedToLogicalTree = this is IStyleRoot;
} }
@ -381,7 +379,6 @@ namespace Avalonia
{ {
if (_initCount == 0 && (!_styled || force)) if (_initCount == 0 && (!_styled || force))
{ {
RegisterWithNameScope();
ApplyStyling(); ApplyStyling();
_styled = true; _styled = true;
} }
@ -675,19 +672,6 @@ namespace Avalonia
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this); AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
} }
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
}
}
private static void ValidateLogicalChild(ILogical c) private static void ValidateLogicalChild(ILogical c)
{ {
if (c == null) if (c == null)
@ -724,11 +708,6 @@ namespace Avalonia
{ {
if (_isAttachedToLogicalTree) if (_isAttachedToLogicalTree)
{ {
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false; _isAttachedToLogicalTree = false;
_styleDetach.OnNext(this); _styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);

1
src/Avalonia.Styling/Styling/Setter.cs

@ -99,7 +99,6 @@ namespace Avalonia.Styling
if (template != null && !isPropertyOfTypeITemplate) if (template != null && !isPropertyOfTypeITemplate)
{ {
var materialized = template.Build(); var materialized = template.Build();
NameScope.SetNameScope((StyledElement)materialized, new NameScope());
value = materialized; value = materialized;
} }

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -40,7 +40,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source, Source = Source,
StringFormat = StringFormat, StringFormat = StringFormat,
RelativeSource = RelativeSource, RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)) DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
}; };
} }

4
src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs

@ -17,6 +17,6 @@ namespace Avalonia.Markup.Xaml.Templates
public Type TargetType { get; set; } public Type TargetType { get; set; }
public IControl Build(ITemplatedControl control) => TemplateContent.Load(Content); public ControlTemplateResult Build(ITemplatedControl control) => TemplateContent.Load(Content);
} }
} }

4
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -32,6 +32,6 @@ namespace Avalonia.Markup.Xaml.Templates
} }
} }
public IControl Build(object data) => TemplateContent.Load(Content); public IControl Build(object data) => TemplateContent.Load(Content).Control;
} }
} }

4
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@ -14,8 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates
public object Content { get; set; } public object Content { get; set; }
public IPanel Build() public IPanel Build()
=> (IPanel)TemplateContent.Load(Content); => (IPanel)TemplateContent.Load(Content).Control;
object ITemplate.Build() => Build(); object ITemplate.Build() => Build();
} }
} }

4
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@ -13,8 +13,8 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent] [TemplateContent]
public object Content { get; set; } public object Content { get; set; }
public IControl Build() => TemplateContent.Load(Content); public IControl Build() => TemplateContent.Load(Content).Control;
object ITemplate.Build() => Build(); object ITemplate.Build() => Build();
} }
} }

5
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -4,17 +4,18 @@
using System; using System;
using Avalonia.Controls; using Avalonia.Controls;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
public static class TemplateContent public static class TemplateContent
{ {
public static IControl Load(object templateContent) public static ControlTemplateResult Load(object templateContent)
{ {
if (templateContent is Func<IServiceProvider, object> direct) if (templateContent is Func<IServiceProvider, object> direct)
{ {
return (IControl)direct(null); return (ControlTemplateResult)direct(null);
} }
throw new ArgumentException(nameof(templateContent)); throw new ArgumentException(nameof(templateContent));
} }

4
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -51,9 +51,9 @@ namespace Avalonia.Markup.Xaml.Templates
public IControl Build(object data) public IControl Build(object data)
{ {
var visualTreeForItem = TemplateContent.Load(Content); var visualTreeForItem = TemplateContent.Load(Content).Control;
visualTreeForItem.DataContext = data; visualTreeForItem.DataContext = data;
return visualTreeForItem; return visualTreeForItem;
} }
} }
} }

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -156,7 +156,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
{ {
overrideField.SetValue(null, overrideField.SetValue(null,
new Action<object>( new Action<object>(
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); })); target => { populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), target); }));
try try
{ {
return Activator.CreateInstance(targetType); return Activator.CreateInstance(targetType);
@ -170,11 +170,11 @@ namespace Avalonia.Markup.Xaml.XamlIl
var createCb = Expression.Lambda<Func<IServiceProvider, object>>( var createCb = Expression.Lambda<Func<IServiceProvider, object>>(
Expression.Convert(Expression.Call( Expression.Convert(Expression.Call(
created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile(); created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile();
return createCb(XamlIlRuntimeHelpers.RootServiceProviderV1); return createCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2());
} }
else else
{ {
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance); populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), rootInstance);
return rootInstance; return rootInstance;
} }
} }

22
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -35,6 +35,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}, },
ProvideValueTarget = typeSystem.GetType("Avalonia.Markup.Xaml.IProvideValueTarget"), ProvideValueTarget = typeSystem.GetType("Avalonia.Markup.Xaml.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"), RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"),
RootObjectProviderIntermediateRootPropertyName = "IntermediateRootObject",
UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"), UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"),
ParentStackProvider = ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"), typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
@ -53,9 +54,30 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit, ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
}; };
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv); rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
return rv; return rv;
} }
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
IXamlIlTypeSystem typeSystem,
IXamlIlTypeBuilder typebuilder, IXamlIlEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
var field = typebuilder.DefineField(nameScopeType,
ContextNameScopeFieldName, true, false);
constructor
.Ldarg_0()
.Ldarg(1)
.Ldtype(nameScopeType)
.EmitCall(mappings.ServiceProvider.GetMethod(new FindMethodMethodSignature("GetService",
typeSystem.FindType("System.Object"), typeSystem.FindType("System.Type"))))
.Stfld(field);
}
class AttributeResolver : IXamlIlCustomAttributeResolver class AttributeResolver : IXamlIlCustomAttributeResolver
{ {
private readonly IXamlIlType _typeConverterAttribute; private readonly IXamlIlType _typeConverterAttribute;

107
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement") && pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
{ {
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
&& mg.Children.OfType<ScopeRegistrationNode>().Any()) && mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
return node; return node;
IXamlIlAstValueNode value = null; IXamlIlAstValueNode value = null;
@ -41,54 +41,99 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Children = Children =
{ {
pa, pa,
new ScopeRegistrationNode(value) new AvaloniaNameScopeRegistrationXamlIlNode(value, context.GetAvaloniaTypes())
} }
}; };
} }
if (!context.ParentNodes().Any()
&& node is XamlIlValueWithManipulationNode mnode)
{
mnode.Manipulation = new XamlIlManipulationGroupNode(mnode,
new[]
{
mnode.Manipulation,
new HandleRootObjectScopeNode(mnode, context.GetAvaloniaTypes())
});
}
return node; return node;
} }
class ScopeRegistrationNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode class HandleRootObjectScopeNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{ {
public IXamlIlAstValueNode Value { get; set; } private readonly AvaloniaXamlIlWellKnownTypes _types;
public ScopeRegistrationNode(IXamlIlAstValueNode value) : base(value)
public HandleRootObjectScopeNode(IXamlIlLineInfo lineInfo,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{ {
Value = value; _types = types;
} }
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Value = (IXamlIlAstValueNode)Value.Visit(visitor);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{ {
var exts = context.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScopeExtensions"); var next = codeGen.DefineLabel();
var findNameScope = exts.FindMethod(m => m.Name == "FindNameScope"); var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
var registerMethod = findNameScope.ReturnType.FindMethod(m => m.Name == "Register"); f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object)) using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
using (var nameScopeLoc = context.GetLocal(findNameScope.ReturnType))
{ {
var exit = codeGen.DefineLabel();
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// var scope = target.FindNameScope()
.Ldloc(targetLoc.Local)
.Castclass(findNameScope.Parameters[0])
.EmitCall(findNameScope)
.Stloc(nameScopeLoc.Local)
// if({scope} != null) goto call;
.Ldloc(nameScopeLoc.Local)
.Brfalse(exit)
.Ldloc(nameScopeLoc.Local);
context.Emit(Value, codeGen, Value.Type.GetClrType());
codeGen codeGen
.Ldloc(targetLoc.Local) .Isinst(_types.StyledElement)
.EmitCall(registerMethod) .Dup()
.MarkLabel(exit); .Stloc(local.Local)
.Brfalse(next)
.Ldloc(local.Local)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.NameScopeSetNameScope, true)
.MarkLabel(next)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.INameScopeComplete, true);
} }
return XamlIlNodeEmitResult.Void(1); return XamlIlNodeEmitResult.Void(1);
} }
} }
} }
class AvaloniaNameScopeRegistrationXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public IXamlIlAstValueNode Name { get; set; }
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types) : base(name)
{
_types = types;
Name = name;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Name = (IXamlIlAstValueNode)Name.Visit(visitor);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
{
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(Name, codeGen, Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(_types.INameScopeRegister, true);
}
return XamlIlNodeEmitResult.Void(1);
}
}
} }

26
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -16,13 +16,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; } public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlIlType IDisposable { get; } public IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; } public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public XamlIlLanguageTypeMappings XamlIlMappings { get; }
public IXamlIlType Transitions { get; } public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { get; } public IXamlIlType AssignBindingAttribute { get; }
public IXamlIlType UnsetValueType { get; } public IXamlIlType UnsetValueType { get; }
public IXamlIlType StyledElement { get; }
public IXamlIlType NameScope { get; }
public IXamlIlMethod NameScopeSetNameScope { get; }
public IXamlIlType INameScope { get; }
public IXamlIlMethod INameScopeRegister { get; }
public IXamlIlMethod INameScopeComplete { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx) public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx)
{ {
XamlIlTypes = ctx.Configuration.WellKnownTypes; XamlIlTypes = ctx.Configuration.WellKnownTypes;
XamlIlMappings = ctx.Configuration.TypeMappings;
AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject"); AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject"); IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
@ -37,8 +45,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AvaloniaProperty, AvaloniaProperty,
IBinding, ctx.Configuration.WellKnownTypes.Object); IBinding, ctx.Configuration.WellKnownTypes.Object);
UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType"); UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = ctx.Configuration.TypeSystem.GetType("Avalonia.StyledElement");
INameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.INameScope");
INameScopeRegister = INameScope.GetMethod(
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
XamlIlTypes.String, XamlIlTypes.Object)
{
IsStatic = false, DeclaringOnly = true, IsExactMatch = true
});
INameScopeComplete = INameScope.GetMethod(
new FindMethodMethodSignature("Complete", XamlIlTypes.Void)
{
IsStatic = false, DeclaringOnly = true, IsExactMatch = true
});
NameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScope");
NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
XamlIlTypes.Void, StyledElement, INameScope) {IsStatic = true});
AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void, AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority); false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
} }
} }

36
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global // ReSharper disable UnusedParameter.Global
@ -17,7 +19,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList(); .OfType<IResourceNode>().ToList();
var rootObject = provider.GetService<IRootObjectProvider>().RootObject; var rootObject = provider.GetService<IRootObjectProvider>().RootObject;
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject)); return sp =>
{
var parentScope = sp.GetService<INameScope>();
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
return new ControlTemplateResult((IControl)obj, scope);
};
} }
class DeferredParentServiceProvider : class DeferredParentServiceProvider :
@ -27,12 +36,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{ {
private readonly IServiceProvider _parentProvider; private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes; private readonly List<IResourceNode> _parentResourceNodes;
private readonly INameScope _nameScope;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes, public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
object rootObject) object rootObject, INameScope nameScope)
{ {
_parentProvider = parentProvider; _parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes; _parentResourceNodes = parentResourceNodes;
_nameScope = nameScope;
RootObject = rootObject; RootObject = rootObject;
} }
@ -48,6 +59,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public object GetService(Type serviceType) public object GetService(Type serviceType)
{ {
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this; return this;
if (serviceType == typeof(IRootObjectProvider)) if (serviceType == typeof(IRootObjectProvider))
@ -56,6 +69,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
} }
public object RootObject { get; } public object RootObject { get; }
public object IntermediateRootObject => RootObject;
} }
@ -132,12 +146,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
} }
} }
public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider(); [Obsolete("Don't use", true)]
public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider(null);
[DebuggerStepThrough]
public static IServiceProvider CreateRootServiceProviderV2()
{
return new RootServiceProvider(new NameScope());
}
class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider
{ {
private readonly INameScope _nameScope;
public RootServiceProvider(INameScope nameScope)
{
_nameScope = nameScope;
}
public object GetService(Type serviceType) public object GetService(Type serviceType)
{ {
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this; return this;
return null; return null;

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 894b2c02827fd5eb16a338de5d5b6c9fbc60fef5 Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022

8
src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs

@ -10,7 +10,15 @@ namespace Avalonia.Markup.Xaml
public interface IRootObjectProvider public interface IRootObjectProvider
{ {
/// <summary>
/// The root object of the xaml file
/// </summary>
object RootObject { get; } object RootObject { get; }
/// <summary>
/// The "current" root object, contains either the root of the xaml file
/// or the root object of the control/data template
/// </summary>
object IntermediateRootObject { get; }
} }
public interface IUriContext public interface IUriContext

14
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -90,6 +91,8 @@ namespace Avalonia.Data
public string StringFormat { get; set; } public string StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; } public WeakReference DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
/// <summary> /// <summary>
/// Gets or sets a function used to resolve types from names in the binding path. /// Gets or sets a function used to resolve types from names in the binding path.
@ -110,7 +113,9 @@ namespace Avalonia.Data
ExpressionObserver observer; ExpressionObserver observer;
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver); INameScope nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
if (ElementName != null) if (ElementName != null)
{ {
@ -254,9 +259,12 @@ namespace Avalonia.Data
ExpressionNode node) ExpressionNode node)
{ {
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
NameScope.TryGetTarget(out var scope);
if (scope == null)
throw new InvalidOperationException("Name scope is null or was already collected");
var result = new ExpressionObserver( var result = new ExpressionObserver(
ControlLocator.Track(target, elementName), NameScopeLocator.Track(scope, elementName),
node, node,
null); null);
return result; return result;

6
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Reactive; using System.Reactive;
using Avalonia.Controls;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -7,7 +8,8 @@ namespace Avalonia.Markup.Parsers
{ {
public static class ExpressionObserverBuilder public static class ExpressionObserverBuilder
{ {
internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null) internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null,
INameScope nameScope = null)
{ {
if (string.IsNullOrWhiteSpace(expression)) if (string.IsNullOrWhiteSpace(expression))
{ {
@ -15,7 +17,7 @@ namespace Avalonia.Markup.Parsers
} }
var reader = new CharacterReader(expression.AsSpan()); var reader = new CharacterReader(expression.AsSpan());
var parser = new ExpressionParser(enableValidation, typeResolver); var parser = new ExpressionParser(enableValidation, typeResolver, nameScope);
var node = parser.Parse(ref reader); var node = parser.Parse(ref reader);
if (!reader.End) if (!reader.End)

7
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -7,6 +7,7 @@ using Avalonia.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls;
namespace Avalonia.Markup.Parsers namespace Avalonia.Markup.Parsers
{ {
@ -20,10 +21,12 @@ namespace Avalonia.Markup.Parsers
{ {
private readonly bool _enableValidation; private readonly bool _enableValidation;
private readonly Func<string, string, Type> _typeResolver; private readonly Func<string, string, Type> _typeResolver;
private readonly INameScope _nameScope;
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver) public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver, INameScope nameScope)
{ {
_typeResolver = typeResolver; _typeResolver = typeResolver;
_nameScope = nameScope;
_enableValidation = enableValidation; _enableValidation = enableValidation;
} }
@ -213,7 +216,7 @@ namespace Avalonia.Markup.Parsers
throw new ExpressionParseException(r.Position, "Element name expected after '#'."); throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
} }
nodes.Add(new ElementNameNode(name.ToString())); nodes.Add(new ElementNameNode(_nameScope, name.ToString()));
return State.AfterMember; return State.AfterMember;
} }

13
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Controls;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -6,11 +7,13 @@ namespace Avalonia.Markup.Parsers.Nodes
{ {
internal class ElementNameNode : ExpressionNode internal class ElementNameNode : ExpressionNode
{ {
private readonly WeakReference<INameScope> _nameScope;
private readonly string _name; private readonly string _name;
private IDisposable _subscription; private IDisposable _subscription;
public ElementNameNode(string name) public ElementNameNode(INameScope nameScope, string name)
{ {
_nameScope = new WeakReference<INameScope>(nameScope);
_name = name; _name = name;
} }
@ -18,14 +21,10 @@ namespace Avalonia.Markup.Parsers.Nodes
protected override void StartListeningCore(WeakReference reference) protected override void StartListeningCore(WeakReference reference)
{ {
if (reference.Target is ILogical logical) if (_nameScope.TryGetTarget(out var scope))
{ _subscription = NameScopeLocator.Track(scope, _name).Subscribe(ValueChanged);
_subscription = ControlLocator.Track(logical, _name).Subscribe(ValueChanged);
}
else else
{
_subscription = null; _subscription = null;
}
} }
protected override void StopListeningCore() protected override void StopListeningCore()

8
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

@ -1012,23 +1012,23 @@ namespace Avalonia.Controls.UnitTests
} }
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<AutoCompleteBox>(control => return new FuncControlTemplate<AutoCompleteBox>((control, scope) =>
{ {
var textBox = var textBox =
new TextBox new TextBox
{ {
Name = "PART_TextBox" Name = "PART_TextBox"
}; }.RegisterInNameScope(scope);
var listbox = var listbox =
new ListBox new ListBox
{ {
Name = "PART_SelectingItemsControl" Name = "PART_SelectingItemsControl"
}; }.RegisterInNameScope(scope);
var popup = var popup =
new Popup new Popup
{ {
Name = "PART_Popup" Name = "PART_Popup"
}; }.RegisterInNameScope(scope);
var panel = new Panel(); var panel = new Panel();
panel.Children.Add(textBox); panel.Children.Add(textBox);

4
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -302,7 +302,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("FooBar", target.SelectedItem); Assert.Equal("FooBar", target.SelectedItem);
} }
private Control CreateTemplate(Carousel control) private Control CreateTemplate(Carousel control, INameScope scope)
{ {
return new CarouselPresenter return new CarouselPresenter
{ {
@ -312,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
[~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty], [~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty],
[~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty], [~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty],
[~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty], [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
}; }.RegisterInNameScope(scope);
} }
} }
} }

8
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ComboBox>(parent => return new FuncControlTemplate<ComboBox>((parent, scope) =>
{ {
return new Panel return new Panel
{ {
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
new ToggleButton new ToggleButton
{ {
Name = "toggle", Name = "toggle",
}, }.RegisterInNameScope(scope),
new Popup new Popup
{ {
Name = "PART_Popup", Name = "PART_Popup",
@ -102,8 +102,8 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[!ItemsPresenter.ItemsProperty] = parent[!ComboBox.ItemsProperty], [!ItemsPresenter.ItemsProperty] = parent[!ComboBox.ItemsProperty],
} }.RegisterInNameScope(scope)
} }.RegisterInNameScope(scope)
} }
}; };
}); });

10
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@ -126,7 +126,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ContentControl var target = new ContentControl
{ {
Template = GetTemplate(), Template = GetTemplate(),
ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()), ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
}; };
target.Content = "Foo"; target.Content = "Foo";
@ -302,8 +302,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ContentControl var target = new ContentControl
{ {
Template = new FuncControlTemplate<ContentControl>(_ => presenter), Template = new FuncControlTemplate<ContentControl>((_, __) => presenter),
ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()), ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
Content = "foo", Content = "foo",
}; };
@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ContentControl>(parent => return new FuncControlTemplate<ContentControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -343,7 +343,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }

10
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -93,28 +93,28 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<DatePicker>(control => return new FuncControlTemplate<DatePicker>((control, scope) =>
{ {
var textBox = var textBox =
new TextBox new TextBox
{ {
Name = "PART_TextBox" Name = "PART_TextBox"
}; }.RegisterInNameScope(scope);
var button = var button =
new Button new Button
{ {
Name = "PART_Button" Name = "PART_Button"
}; }.RegisterInNameScope(scope);
var calendar = var calendar =
new Calendar new Calendar
{ {
Name = "PART_Calendar" Name = "PART_Calendar"
}; }.RegisterInNameScope(scope);
var popup = var popup =
new Popup new Popup
{ {
Name = "PART_Popup" Name = "PART_Popup"
}; }.RegisterInNameScope(scope);
var panel = new Panel(); var panel = new Panel();
panel.Children.Add(textBox); panel.Children.Add(textBox);

21
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -20,6 +20,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Detects_Horizontal_Orientation() public void Detects_Horizontal_Orientation()
{ {
GridSplitter splitter;
var grid = new Grid() var grid = new Grid()
{ {
RowDefinitions = new RowDefinitions("*,Auto,*"), RowDefinitions = new RowDefinitions("*,Auto,*"),
@ -27,7 +28,7 @@ namespace Avalonia.Controls.UnitTests
Children = Children =
{ {
new Border { [Grid.RowProperty] = 0 }, new Border { [Grid.RowProperty] = 0 },
new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" }, (splitter = new GridSplitter { [Grid.RowProperty] = 1 }),
new Border { [Grid.RowProperty] = 2 } new Border { [Grid.RowProperty] = 2 }
} }
}; };
@ -35,12 +36,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300)); root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300)); root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":horizontal".Equals); Assert.Contains(splitter.Classes, ":horizontal".Equals);
} }
[Fact] [Fact]
public void Detects_Vertical_Orientation() public void Detects_Vertical_Orientation()
{ {
GridSplitter splitter;
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"), ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
@ -48,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Children = Children =
{ {
new Border { [Grid.ColumnProperty] = 0 }, new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, (splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
}; };
@ -56,12 +58,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300)); root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300)); root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":vertical".Equals); Assert.Contains(splitter.Classes, ":vertical".Equals);
} }
[Fact] [Fact]
public void Detects_With_Both_Auto() public void Detects_With_Both_Auto()
{ {
GridSplitter splitter;
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"), ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
@ -69,7 +72,7 @@ namespace Avalonia.Controls.UnitTests
Children = Children =
{ {
new Border { [Grid.ColumnProperty] = 0 }, new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, (splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
}; };
@ -77,7 +80,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300)); root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300)); root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":vertical".Equals); Assert.Contains(splitter.Classes, ":vertical".Equals);
} }
[Fact] [Fact]
@ -129,13 +132,14 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void In_First_Position_Doesnt_Throw_Exception() public void In_First_Position_Doesnt_Throw_Exception()
{ {
GridSplitter splitter;
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"), ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"), RowDefinitions = new RowDefinitions("*,*"),
Children = Children =
{ {
new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" }, (splitter = new GridSplitter { [Grid.ColumnProperty] = 0} ),
new Border { [Grid.ColumnProperty] = 1 }, new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
@ -144,7 +148,6 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300)); root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300)); root.Arrange(new Rect(0, 0, 100, 300));
var splitter = grid.FindControl<GridSplitter>("splitter");
splitter.RaiseEvent(new VectorEventArgs splitter.RaiseEvent(new VectorEventArgs
{ {
RoutedEvent = Thumb.DragDeltaEvent, RoutedEvent = Thumb.DragDeltaEvent,
@ -199,4 +202,4 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star)); Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star));
} }
} }
} }

4
tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<HeaderedItemsControl>(parent => return new FuncControlTemplate<HeaderedItemsControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -71,7 +71,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_HeaderPresenter", Name = "PART_HeaderPresenter",
[~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty], [~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }

31
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl var target = new ItemsControl
{ {
Template = GetTemplate(), Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()), ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
}; };
target.Items = new[] { "Foo" }; target.Items = new[] { "Foo" };
@ -411,7 +411,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
}, },
Items = items, Items = items,
}; };
@ -472,29 +472,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((TextBlock)item)); Assert.Null(NameScope.GetNameScope((TextBlock)item));
} }
[Fact]
public void DataTemplate_Created_Content_Should_Be_NameScope()
{
var items = new object[]
{
"foo",
};
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var container = (ContentPresenter)target.Presenter.Panel.LogicalChildren[0];
container.UpdateChild();
Assert.NotNull(NameScope.GetNameScope((TextBlock)container.Child));
}
[Fact] [Fact]
public void Focuses_Next_Item_On_Key_Down() public void Focuses_Next_Item_On_Key_Down()
{ {
@ -578,7 +555,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ItemsControl>(parent => return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -588,7 +565,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector, MemberSelector = parent.MemberSelector,
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }

30
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = new[] { "Foo" }, Items = new[] { "Foo" },
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()), ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
}; };
Prepare(target); Prepare(target);
@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
}, },
Items = items, Items = items,
}; };
@ -138,7 +138,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(), Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
SelectedIndex = 0, SelectedIndex = 0,
}; };
@ -162,7 +162,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(), Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0, SelectedIndex = 0,
}; };
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = items, Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0, SelectedIndex = 0,
}; };
@ -205,7 +205,7 @@ namespace Avalonia.Controls.UnitTests
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = items, Items = items,
SelectionMode = SelectionMode.Toggle, SelectionMode = SelectionMode.Toggle,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }) ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
}; };
Prepare(target); Prepare(target);
@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
Items = items, Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 11 }) ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 11 })
}; };
Prepare(target); Prepare(target);
@ -287,7 +287,7 @@ namespace Avalonia.Controls.UnitTests
target.DataContext = items; target.DataContext = items;
target.VirtualizationMode = virtualizationMode; target.VirtualizationMode = virtualizationMode;
target.ItemTemplate = new FuncDataTemplate<object>(c => target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
{ {
var tb = new TextBlock() { Height = 10, Width = 30 }; var tb = new TextBlock() { Height = 10, Width = 30 };
tb.Bind(TextBlock.TextProperty, new Data.Binding()); tb.Bind(TextBlock.TextProperty, new Data.Binding());
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate ListBoxTemplate() private FuncControlTemplate ListBoxTemplate()
{ {
return new FuncControlTemplate<ListBox>(parent => return new FuncControlTemplate<ListBox>((parent, scope) =>
new ScrollViewer new ScrollViewer
{ {
Name = "PART_ScrollViewer", Name = "PART_ScrollViewer",
@ -345,24 +345,24 @@ namespace Avalonia.Controls.UnitTests
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(), [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
[~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(), [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
[~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(), [~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(),
} }.RegisterInNameScope(scope)
}); }.RegisterInNameScope(scope));
} }
private FuncControlTemplate ListBoxItemTemplate() private FuncControlTemplate ListBoxItemTemplate()
{ {
return new FuncControlTemplate<ListBoxItem>(parent => return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty], [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty], [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
}); }.RegisterInNameScope(scope));
} }
private FuncControlTemplate ScrollViewerTemplate() private FuncControlTemplate ScrollViewerTemplate()
{ {
return new FuncControlTemplate<ScrollViewer>(parent => return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
new ScrollContentPresenter new ScrollContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
@ -370,7 +370,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
}); }.RegisterInNameScope(scope));
} }
private void Prepare(ListBox target) private void Prepare(ListBox target)

11
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests
} }
} }
private Control CreateListBoxTemplate(ITemplatedControl parent) private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)
{ {
return new ScrollViewer return new ScrollViewer
{ {
@ -254,17 +254,18 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(), [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
} }.RegisterInNameScope(scope)
}; };
} }
private Control CreateScrollViewerTemplate(ITemplatedControl parent) private Control CreateScrollViewerTemplate(ITemplatedControl parent, INameScope scope)
{ {
return new ScrollContentPresenter return new ScrollContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).ToBinding(), [~ContentPresenter.ContentProperty] =
}; parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
}.RegisterInNameScope(scope);
} }
private void ApplyTemplate(ListBox target) private void ApplyTemplate(ListBox target)

13
tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs

@ -21,15 +21,16 @@ namespace Avalonia.Controls.UnitTests.Mixins
{ {
var target = new TestControl() var target = new TestControl()
{ {
Template = new FuncControlTemplate(_ => new Panel Template = new FuncControlTemplate((_, scope) => new Panel
{ {
Children = Children =
{ {
new ContentPresenter { Name = "Content_1_Presenter" }, new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope),
new ContentPresenter { Name = "Content_2_Presenter" } new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope)
} }
}) })
}; };
var ex = Record.Exception(() => target.ApplyTemplate()); var ex = Record.Exception(() => target.ApplyTemplate());
@ -43,12 +44,12 @@ namespace Avalonia.Controls.UnitTests.Mixins
var p2 = new ContentPresenter { Name = "Content_2_Presenter" }; var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
var target = new TestControl var target = new TestControl
{ {
Template = new FuncControlTemplate(_ => new Panel Template = new FuncControlTemplate((_, scope) => new Panel
{ {
Children = Children =
{ {
p1, p1.RegisterInNameScope(scope),
p2 p2.RegisterInNameScope(scope)
} }
}) })
}; };

132
tests/Avalonia.Controls.UnitTests/NameScopeTests.cs

@ -20,36 +20,146 @@ namespace Avalonia.Controls.UnitTests
} }
[Fact] [Fact]
public void Unregister_Unregisters_Element() 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 target = new NameScope();
var element = new object(); var element = new object();
target.Register("foo", element); target.Register("foo", element);
target.Unregister("foo"); target.Register("foo", element);
Assert.Null(target.Find("foo")); Assert.Same(element, target.Find("foo"));
} }
[Fact] [Fact]
public void Cannot_Register_New_Element_With_Existing_Name() public void Cannot_Register_New_Element_For_Completed_Scope()
{ {
var target = new NameScope(); var target = new NameScope();
var element = new object();
target.Register("foo", new object()); target.Register("foo", element);
Assert.Throws<ArgumentException>(() => target.Register("foo", new object())); target.Complete();
Assert.Throws<InvalidOperationException>(() => target.Register("bar", element));
} }
/*
`async void` here is intentional since we expect the continuation to be
executed *synchronously* and behave more like an event handler to make sure that
that the object graph is completely ready to use after it's built
rather than have pending continuations queued by SynchronizationContext.
*/
object _found = null;
async void FindAsync(INameScope scope, string name)
{
_found = await scope.FindAsync(name);
}
[Fact] [Fact]
public void Can_Register_Same_Element_More_Than_Once() public void FindAsync_Should_Find_Controls_Added_Earlier()
{ {
var target = new NameScope(); var scope = new NameScope();
var element = new object();
scope.Register("foo", element);
FindAsync(scope, "foo");
Assert.Same(_found, element);
}
[Fact]
public void FindAsync_Should_Find_Controls_Added_Later()
{
var scope = new NameScope();
var element = new object(); var element = new object();
FindAsync(scope, "foo");
Assert.Null(_found);
scope.Register("foo", element);
Assert.Same(_found, element);
}
[Fact]
public void FindAsync_Should_Return_Null_After_Scope_Completion()
{
var scope = new NameScope();
var element = new object();
bool finished = false;
async void Find(string name)
{
Assert.Null(await scope.FindAsync(name));
finished = true;
}
Find("foo");
Assert.False(finished);
scope.Register("bar", element);
Assert.False(finished);
scope.Complete();
Assert.True(finished);
}
target.Register("foo", element); [Fact]
target.Register("foo", element); public void Child_Scope_Should_Not_Find_Control_In_Parent_Scope_Unless_Completed()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
scope.Register("foo", element);
Assert.Null(childScope.Find("foo"));
childScope.Complete();
Assert.Same(element, childScope.Find("foo"));
}
[Fact]
public void Child_Scope_Should_Prefer_Own_Elements()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
var childElement = new object();
scope.Register("foo", element);
childScope.Register("foo", childElement);
childScope.Complete();
Assert.Same(childElement, childScope.Find("foo"));
}
Assert.Same(element, target.Find("foo")); [Fact]
public void Child_Scope_FindAsync_Should_Find_Elements_In_Parent_Scope_When_Child_Is_Completed()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
scope.Register("foo", element);
FindAsync(childScope, "foo");
Assert.Null(_found);
childScope.Complete();
Assert.Same(element, _found);
}
[Fact]
public void Child_Scope_FindAsync_Should_Prefer_Own_Elements()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
var childElement = new object();
FindAsync(childScope, "foo");
scope.Register("foo", element);
Assert.Null(_found);
childScope.Register("foo", childElement);
Assert.Same(childElement, childScope.Find("foo"));
childScope.Complete();
FindAsync(childScope, "foo");
Assert.Same(childElement, childScope.Find("foo"));
} }
} }
} }

29
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -136,17 +136,6 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(NameScope.GetNameScope((Control)target.Child)); Assert.Null(NameScope.GetNameScope((Control)target.Child));
} }
[Fact]
public void DataTemplate_Created_Control_Should_Be_NameScope()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
Assert.NotNull(NameScope.GetNameScope((Control)target.Child));
}
[Fact] [Fact]
public void Assigning_Control_To_Content_Should_Not_Set_DataContext() public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
{ {
@ -170,7 +159,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
var (target, _) = CreateTarget(); var (target, _) = CreateTarget();
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()); target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
target.Content = "Foo"; target.Content = "Foo";
Assert.IsType<Canvas>(target.Child); Assert.IsType<Canvas>(target.Child);
@ -184,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = "Foo"; target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child); Assert.IsType<TextBlock>(target.Child);
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()); target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
Assert.IsType<Canvas>(target.Child); Assert.IsType<Canvas>(target.Child);
target.ContentTemplate = null; target.ContentTemplate = null;
@ -209,7 +198,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Recycles_DataTemplate() public void Recycles_DataTemplate()
{ {
var (target, _) = CreateTarget(); var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true)); target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), true));
target.Content = "foo"; target.Content = "foo";
@ -239,7 +228,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Detects_DataTemplate_Doesnt_Support_Recycling() public void Detects_DataTemplate_Doesnt_Support_Recycling()
{ {
var (target, _) = CreateTarget(); var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), false)); target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), false));
target.Content = "foo"; target.Content = "foo";
@ -256,7 +245,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var (target, _) = CreateTarget(); var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "bar", _ => new Canvas(), true)); target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "bar", _ => new Canvas(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true)); target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), true));
target.Content = "foo"; target.Content = "foo";
@ -278,8 +267,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
}; };
var (target, host) = CreateTarget(); var (target, host) = CreateTarget();
host.DataTemplates.Add(new FuncDataTemplate<string>(x => textBlock)); host.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>(x => new Canvas())); host.DataTemplates.Add(new FuncDataTemplate<int>((_, __) => new Canvas()));
target.Content = "foo"; target.Content = "foo";
Assert.Same(textBlock, target.Child); Assert.Same(textBlock, target.Child);
@ -296,11 +285,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
var templatedParent = new ContentControl var templatedParent = new ContentControl
{ {
Template = new FuncControlTemplate<ContentControl>(x => Template = new FuncControlTemplate<ContentControl>((_, s) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
}), }.RegisterInNameScope(s)),
}; };
var root = new TestRoot { Child = templatedParent }; var root = new TestRoot { Child = templatedParent };

16
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter var target = new ContentPresenter
{ {
ContentTemplate = ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false) new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
}; };
var parentMock = new Mock<Control>(); var parentMock = new Mock<Control>();
@ -93,14 +93,14 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
var contentControl = new ContentControl var contentControl = new ContentControl
{ {
Template = new FuncControlTemplate<ContentControl>(c => new ContentPresenter() Template = new FuncControlTemplate<ContentControl>((c, scope) => new ContentPresenter()
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty] [~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty]
}), }.RegisterInNameScope(scope)),
ContentTemplate = ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false) new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
}; };
var parentMock = new Mock<Control>(); var parentMock = new Mock<Control>();
@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter var target = new ContentPresenter
{ {
ContentTemplate = ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false) new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
}; };
var parentMock = new Mock<Control>(); var parentMock = new Mock<Control>();
@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter var target = new ContentPresenter
{ {
ContentTemplate = ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false) new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
}; };
target.Content = "foo"; target.Content = "foo";
@ -235,8 +235,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<string>(x => textBlock), new FuncDataTemplate<string>((x, _) => textBlock),
new FuncDataTemplate<int>(x => new Canvas()), new FuncDataTemplate<int>((x, _) => new Canvas()),
}, },
}; };

4
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs

@ -90,7 +90,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<string>(x => new Decorator()), new FuncDataTemplate<string>((x, _) => new Decorator()),
}, },
}; };
@ -99,4 +99,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.IsType<Decorator>(target.Child); Assert.IsType<Decorator>(target.Child);
} }
} }
} }

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -220,7 +220,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
VirtualizationMode = ItemVirtualizationMode.None, VirtualizationMode = ItemVirtualizationMode.None,
Items = items, Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
}; };
target.ApplyTemplate(); target.ApplyTemplate();

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -309,7 +309,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static IDataTemplate ItemTemplate() private static IDataTemplate ItemTemplate()
{ {
return new FuncDataTemplate<string>(x => new Canvas return new FuncDataTemplate<string>((x, _) => new Canvas
{ {
Width = 10, Width = 10,
Height = 10, Height = 10,

8
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -470,7 +470,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
VirtualizationMode = ItemVirtualizationMode.None, VirtualizationMode = ItemVirtualizationMode.None,
Items = items, Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
}; };
target.ApplyTemplate(); target.ApplyTemplate();
@ -1046,7 +1046,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static IDataTemplate StringDataTemplate() private static IDataTemplate StringDataTemplate()
{ {
return new FuncDataTemplate<string>(x => new Canvas return new FuncDataTemplate<string>((x, _) => new Canvas
{ {
Width = 10, Width = 10,
Height = 10, Height = 10,
@ -1095,12 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
{ {
public TestContainer() public TestContainer()
{ {
Template = new FuncControlTemplate<TestContainer>(parent => new ContentPresenter Template = new FuncControlTemplate<TestContainer>((parent, scope) => new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
}); }.RegisterInNameScope(scope));
} }
} }
} }

6
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -134,12 +134,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var result = new PopupRoot var result = new PopupRoot
{ {
Template = new FuncControlTemplate<PopupRoot>(parent => Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty], [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
}), }.RegisterInNameScope(scope)),
}; };
result.ApplyTemplate(); result.ApplyTemplate();
@ -154,7 +154,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
public TemplatedControlWithPopup() public TemplatedControlWithPopup()
{ {
Template = new FuncControlTemplate<TemplatedControlWithPopup>(parent => Template = new FuncControlTemplate<TemplatedControlWithPopup>((parent, _) =>
new Popup new Popup
{ {
[!Popup.ChildProperty] = parent[!TemplatedControlWithPopup.PopupContentProperty], [!Popup.ChildProperty] = parent[!TemplatedControlWithPopup.PopupContentProperty],

8
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -315,16 +315,16 @@ namespace Avalonia.Controls.UnitTests.Primitives
return result; return result;
} }
private static IControl PopupRootTemplate(PopupRoot control) private static IControl PopupRootTemplate(PopupRoot control, INameScope scope)
{ {
return new ContentPresenter return new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
}; }.RegisterInNameScope(scope);
} }
private static IControl PopupContentControlTemplate(PopupContentControl control) private static IControl PopupContentControlTemplate(PopupContentControl control, INameScope scope)
{ {
return new Popup return new Popup
{ {
@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
} }
}; }.RegisterInNameScope(scope);
} }
private class PopupContentControl : ContentControl private class PopupContentControl : ContentControl

4
tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestRange() var target = new TestRange()
{ {
Template = new FuncControlTemplate<RangeBase>(c => Template = new FuncControlTemplate<RangeBase>((c, scope) =>
{ {
track = new Track() track = new Track()
{ {
@ -122,7 +122,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Name = "PART_Track", Name = "PART_Track",
Thumb = new Thumb() Thumb = new Thumb()
}; }.RegisterInNameScope(scope);
if (useXamlBinding) if (useXamlBinding)
{ {

8
tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs

@ -169,7 +169,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.False(target.IsVisible); Assert.False(target.IsVisible);
} }
private static Control Template(ScrollBar control) private static Control Template(ScrollBar control, INameScope scope)
{ {
return new Border return new Border
{ {
@ -185,11 +185,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Template = new FuncControlTemplate<Thumb>(ThumbTemplate), Template = new FuncControlTemplate<Thumb>(ThumbTemplate),
}, },
}, }.RegisterInNameScope(scope),
}; };
} }
private static Control ThumbTemplate(Thumb control) private static Control ThumbTemplate(Thumb control, INameScope scope)
{ {
return new Border return new Border
{ {
@ -197,4 +197,4 @@ namespace Avalonia.Controls.UnitTests.Primitives
}; };
} }
} }
} }

4
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -896,13 +896,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
private FuncControlTemplate Template() private FuncControlTemplate Template()
{ {
return new FuncControlTemplate<SelectingItemsControl>(control => return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter new ItemsPresenter
{ {
Name = "itemsPresenter", Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}); }.RegisterInNameScope(scope));
} }
private class Item : Control, ISelectable private class Item : Control, ISelectable

4
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs

@ -84,13 +84,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
private FuncControlTemplate Template() private FuncControlTemplate Template()
{ {
return new FuncControlTemplate<SelectingItemsControl>(control => return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter new ItemsPresenter
{ {
Name = "itemsPresenter", Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}); }.RegisterInNameScope(scope));
} }
private class TestSelector : SelectingItemsControl private class TestSelector : SelectingItemsControl

10
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -964,7 +964,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Template = Template(), Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" }, Items = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple, SelectionMode = SelectionMode.Multiple,
}; };
@ -988,7 +988,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Template = Template(), Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" }, Items = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple, SelectionMode = SelectionMode.Multiple,
}; };
@ -1010,7 +1010,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Template = Template(), Template = Template(),
Items = new[] { "Foo", "Bar", "Baz" }, Items = new[] { "Foo", "Bar", "Baz" },
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }), ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple, SelectionMode = SelectionMode.Multiple,
}; };
@ -1035,13 +1035,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
private FuncControlTemplate Template() private FuncControlTemplate Template()
{ {
return new FuncControlTemplate<SelectingItemsControl>(control => return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter new ItemsPresenter
{ {
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}); }.RegisterInNameScope(scope));
} }
private class TestSelector : SelectingItemsControl private class TestSelector : SelectingItemsControl

4
tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs

@ -159,14 +159,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Same("3rd", ((TabItem)target.SelectedItem).Name); Assert.Same("3rd", ((TabItem)target.SelectedItem).Name);
} }
private Control CreateTabStripTemplate(TabStrip parent) private Control CreateTabStripTemplate(TabStrip parent, INameScope scope)
{ {
return new ItemsPresenter return new ItemsPresenter
{ {
Name = "itemsPresenter", Name = "itemsPresenter",
[!ItemsPresenter.ItemsProperty] = parent[!ItemsControl.ItemsProperty], [!ItemsPresenter.ItemsProperty] = parent[!ItemsControl.ItemsProperty],
[!ItemsPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], [!ItemsPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty],
}; }.RegisterInNameScope(scope);
} }
} }
} }

112
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
bool executed = false; bool executed = false;
var template = new FuncControlTemplate(_ => var template = new FuncControlTemplate((_, __) =>
{ {
executed = true; executed = true;
return new Control(); return new Control();
@ -42,7 +42,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
bool executed = false; bool executed = false;
var template = new FuncControlTemplate(_ => var template = new FuncControlTemplate((_, __) =>
{ {
executed = true; executed = true;
return new Control(); return new Control();
@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TemplatedControl var target = new TemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator Template = new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Panel Child = new Panel
{ {
@ -92,35 +92,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Empty(target.GetLogicalChildren()); Assert.Empty(target.GetLogicalChildren());
} }
[Fact]
public void Templated_Child_Should_Be_NameScope()
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
{
Child = new Panel
{
Children =
{
new TextBlock(),
new Border(),
}
}
}),
};
target.ApplyTemplate();
Assert.NotNull(NameScope.GetNameScope((Control)target.GetVisualChildren().Single()));
}
[Fact] [Fact]
public void Templated_Children_Should_Have_TemplatedParent_Set() public void Templated_Children_Should_Have_TemplatedParent_Set()
{ {
var target = new TemplatedControl var target = new TemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator Template = new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Panel Child = new Panel
{ {
@ -149,7 +126,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TemplatedControl var target = new TemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator()) Template = new FuncControlTemplate((_, __) => new Decorator())
}; };
target.ApplyTemplate(); target.ApplyTemplate();
@ -165,14 +142,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TemplatedControl var target = new TemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator()) Template = new FuncControlTemplate((_, __) => new Decorator())
}; };
target.ApplyTemplate(); target.ApplyTemplate();
var child = (Decorator)target.GetVisualChildren().Single(); var child = (Decorator)target.GetVisualChildren().Single();
target.Template = new FuncControlTemplate(_ => new Canvas()); target.Template = new FuncControlTemplate((_, __) => new Canvas());
target.ApplyTemplate(); target.ApplyTemplate();
Assert.Null(child.Parent); Assert.Null(child.Parent);
@ -183,7 +160,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TemplatedControl var target = new TemplatedControl
{ {
Template = new FuncControlTemplate(_ => new ScrollViewer()) Template = new FuncControlTemplate((_, __) => new ScrollViewer())
}; };
target.ApplyTemplate(); target.ApplyTemplate();
@ -203,7 +180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = target = new TestTemplatedControl Child = target = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => Template = new FuncControlTemplate((_, __) =>
{ {
return new StackPanel return new StackPanel
{ {
@ -232,11 +209,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TestTemplatedControl var target = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => Template = new FuncControlTemplate((_, __) =>
{ {
return new ContentControl return new ContentControl
{ {
Template = new FuncControlTemplate(parent => Template = new FuncControlTemplate((parent, ___) =>
{ {
return new Border return new Border
{ {
@ -271,47 +248,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(target, textBlock.TemplatedParent); Assert.Equal(target, textBlock.TemplatedParent);
} }
[Fact]
public void Nested_TemplatedControls_Should_Register_With_Correct_NameScope()
{
var target = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(ScrollingContentControlTemplate),
Content = "foo"
};
var root = new TestRoot { Child = target };
target.ApplyTemplate();
var border = target.GetVisualChildren().FirstOrDefault();
Assert.IsType<Border>(border);
var scrollViewer = border.GetVisualChildren().FirstOrDefault();
Assert.IsType<ScrollViewer>(scrollViewer);
((ScrollViewer)scrollViewer).ApplyTemplate();
var scrollContentPresenter = scrollViewer.GetVisualChildren().FirstOrDefault();
Assert.IsType<ScrollContentPresenter>(scrollContentPresenter);
((ContentPresenter)scrollContentPresenter).UpdateChild();
var contentPresenter = scrollContentPresenter.GetVisualChildren().FirstOrDefault();
Assert.IsType<ContentPresenter>(contentPresenter);
var borderNs = NameScope.GetNameScope((Control)border);
var scrollContentPresenterNs = NameScope.GetNameScope((Control)scrollContentPresenter);
Assert.NotNull(borderNs);
Assert.Same(scrollViewer, borderNs.Find("ScrollViewer"));
Assert.Same(contentPresenter, borderNs.Find("PART_ContentPresenter"));
Assert.Same(scrollContentPresenter, scrollContentPresenterNs.Find("PART_ContentPresenter"));
}
[Fact] [Fact]
public void ApplyTemplate_Should_Raise_TemplateApplied() public void ApplyTemplate_Should_Raise_TemplateApplied()
{ {
var target = new TestTemplatedControl var target = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator()) Template = new FuncControlTemplate((_, __) => new Decorator())
}; };
var raised = false; var raised = false;
@ -334,7 +276,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
var target = new TestTemplatedControl var target = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator Template = new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Border(), Child = new Border(),
}) })
@ -348,7 +290,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(target, decorator.TemplatedParent); Assert.Equal(target, decorator.TemplatedParent);
Assert.Equal(target, border.TemplatedParent); Assert.Equal(target, border.TemplatedParent);
target.Template = new FuncControlTemplate(_ => new Canvas()); target.Template = new FuncControlTemplate((_, __) => new Canvas());
// Templated children should not be removed here: the control may be re-added // Templated children should not be removed here: the control may be re-added
// somewhere with the same template, so they could still be of use. // somewhere with the same template, so they could still be of use.
@ -370,7 +312,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = new TestTemplatedControl Child = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator Template = new FuncControlTemplate((_, __) => new Decorator
{ {
Child = templateChild, Child = templateChild,
}) })
@ -392,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = new TestTemplatedControl Child = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator Template = new FuncControlTemplate((_, __) => new Decorator
{ {
Child = templateChild, Child = templateChild,
}) })
@ -425,7 +367,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
new Setter( new Setter(
TemplatedControl.TemplateProperty, TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Border(), Child = new Border(),
})) }))
@ -461,7 +403,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
new Setter( new Setter(
TemplatedControl.TemplateProperty, TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Border(), Child = new Border(),
})) }))
@ -500,7 +442,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
new Setter( new Setter(
TemplatedControl.TemplateProperty, TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Border(), Child = new Border(),
})) }))
@ -520,7 +462,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
new Setter( new Setter(
TemplatedControl.TemplateProperty, TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator new FuncControlTemplate((_, __) => new Decorator
{ {
Child = new Border(), Child = new Border(),
})) }))
@ -555,7 +497,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = target = new TestTemplatedControl Child = target = new TestTemplatedControl
{ {
Template = new FuncControlTemplate(_ => new Decorator()), Template = new FuncControlTemplate((_, __) => new Decorator()),
} }
}; };
@ -573,7 +515,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
} }
private static IControl ScrollingContentControlTemplate(ContentControl control) private static IControl ScrollingContentControlTemplate(ContentControl control, INameScope scope)
{ {
return new Border return new Border
{ {
@ -585,20 +527,20 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = control[!ContentControl.ContentProperty], [!ContentPresenter.ContentProperty] = control[!ContentControl.ContentProperty],
} }.RegisterInNameScope(scope)
} }.RegisterInNameScope(scope)
}; };
} }
private static Control ScrollViewerTemplate(ScrollViewer control) private static Control ScrollViewerTemplate(ScrollViewer control, INameScope scope)
{ {
var result = new ScrollContentPresenter var result = new ScrollContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
}; }.RegisterInNameScope(scope);
return result; return result;
} }
} }
} }

10
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(10, 10), target.Offset); Assert.Equal(new Vector(10, 10), target.Offset);
} }
private Control CreateTemplate(ScrollViewer control) private Control CreateTemplate(ScrollViewer control, INameScope scope)
{ {
return new Grid return new Grid
{ {
@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty], [~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty], [~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
}, }.RegisterInNameScope(scope),
new ScrollBar new ScrollBar
{ {
Name = "horizontalScrollBar", Name = "horizontalScrollBar",
@ -98,7 +98,7 @@ namespace Avalonia.Controls.UnitTests
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty], [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty], [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1, [Grid.RowProperty] = 1,
}, }.RegisterInNameScope(scope),
new ScrollBar new ScrollBar
{ {
Name = "verticalScrollBar", Name = "verticalScrollBar",
@ -108,9 +108,9 @@ namespace Avalonia.Controls.UnitTests
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty], [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty], [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[Grid.ColumnProperty] = 1, [Grid.ColumnProperty] = 1,
}, }.RegisterInNameScope(scope),
}, },
}; };
} }
} }
} }

16
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -129,7 +129,7 @@ namespace Avalonia.Controls.UnitTests
}, },
}; };
var template = new FuncControlTemplate<TabItem>(x => new Decorator()); var template = new FuncControlTemplate<TabItem>((x, __) => new Decorator());
using (UnitTestApplication.Start(TestServices.RealStyler)) using (UnitTestApplication.Start(TestServices.RealStyler))
{ {
@ -176,7 +176,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
}, },
Items = items, Items = items,
}; };
@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests
TabControl target = new TabControl TabControl target = new TabControl
{ {
Template = TabControlTemplate(), Template = TabControlTemplate(),
ContentTemplate = new FuncDataTemplate<string>(x => ContentTemplate = new FuncDataTemplate<string>((x, _) =>
new TextBlock { Tag = "bar", Text = x }), new TextBlock { Tag = "bar", Text = x }),
Items = new[] { "Foo" }, Items = new[] { "Foo" },
}; };
@ -289,7 +289,7 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate TabControlTemplate() private IControlTemplate TabControlTemplate()
{ {
return new FuncControlTemplate<TabControl>(parent => return new FuncControlTemplate<TabControl>((parent, scope) =>
new StackPanel new StackPanel
{ {
Children = Children =
@ -299,26 +299,26 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
}, }.RegisterInNameScope(scope),
new ContentPresenter new ContentPresenter
{ {
Name = "PART_SelectedContentHost", Name = "PART_SelectedContentHost",
[!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
} }.RegisterInNameScope(scope)
} }
}); });
} }
private IControlTemplate TabItemTemplate() private IControlTemplate TabItemTemplate()
{ {
return new FuncControlTemplate<TabItem>(parent => return new FuncControlTemplate<TabItem>((parent, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty], [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty] [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
}); }.RegisterInNameScope(scope));
} }
private void ApplyTemplate(TabControl target) private void ApplyTemplate(TabControl target)

4
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -456,7 +456,7 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<TextBox>(control => return new FuncControlTemplate<TextBox>((control, scope) =>
new TextPresenter new TextPresenter
{ {
Name = "PART_TextPresenter", Name = "PART_TextPresenter",
@ -467,7 +467,7 @@ namespace Avalonia.Controls.UnitTests
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
}, },
}); }.RegisterInNameScope(scope));
} }
private void RaiseKeyEvent(TextBox textBox, Key key, InputModifiers inputModifiers) private void RaiseKeyEvent(TextBox textBox, Key key, InputModifiers inputModifiers)

4
tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate() private IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<TextBox>(control => return new FuncControlTemplate<TextBox>((control, scope) =>
new TextPresenter new TextPresenter
{ {
Name = "PART_TextPresenter", Name = "PART_TextPresenter",
@ -104,7 +104,7 @@ namespace Avalonia.Controls.UnitTests
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
}, },
}); }.RegisterInNameScope(scope));
} }
private class ExceptionTest private class ExceptionTest

4
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -226,12 +226,12 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate<TestTopLevel> CreateTemplate() private FuncControlTemplate<TestTopLevel> CreateTemplate()
{ {
return new FuncControlTemplate<TestTopLevel>(x => return new FuncControlTemplate<TestTopLevel>((x, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}); }.RegisterInNameScope(scope));
} }
private class TestTopLevel : TopLevel private class TestTopLevel : TopLevel

35
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(), Items = CreateTestTreeData(),
ItemTemplate = new FuncTreeDataTemplate<Node>( ItemTemplate = new FuncTreeDataTemplate<Node>(
_ => new Canvas(), (_, __) => new Canvas(),
x => x.Children), x => x.Children),
} }
}; };
@ -475,7 +475,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
new FuncDataTemplate<Node>(x => new Button { Content = x }) new FuncDataTemplate<Node>((x, _) => new Button { Content = x })
}, },
Items = items, Items = items,
}; };
@ -513,27 +513,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((TreeViewItem)item)); Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
} }
[Fact]
public void DataTemplate_Created_Item_Should_Be_NameScope()
{
var items = new object[]
{
"foo",
};
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var item = target.Presenter.Panel.LogicalChildren[0];
Assert.NotNull(NameScope.GetNameScope((TreeViewItem)item));
}
[Fact] [Fact]
public void Should_React_To_Children_Changing() public void Should_React_To_Children_Changing()
{ {
@ -799,16 +778,16 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTreeViewTemplate() private IControlTemplate CreateTreeViewTemplate()
{ {
return new FuncControlTemplate<TreeView>(parent => new ItemsPresenter return new FuncControlTemplate<TreeView>((parent, scope) => new ItemsPresenter
{ {
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}); }.RegisterInNameScope(scope));
} }
private IControlTemplate CreateTreeViewItemTemplate() private IControlTemplate CreateTreeViewItemTemplate()
{ {
return new FuncControlTemplate<TreeViewItem>(parent => new Panel return new FuncControlTemplate<TreeViewItem>((parent, scope) => new Panel
{ {
Children = Children =
{ {
@ -816,12 +795,12 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_HeaderPresenter", Name = "PART_HeaderPresenter",
[~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty], [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
}, }.RegisterInNameScope(scope),
new ItemsPresenter new ItemsPresenter
{ {
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
} }.RegisterInNameScope(scope)
} }
}); });
} }

4
tests/Avalonia.Controls.UnitTests/UserControlTests.cs

@ -40,7 +40,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<UserControl>(parent => return new FuncControlTemplate<UserControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }

4
tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs

@ -59,13 +59,13 @@ namespace Avalonia.Controls.UnitTests.Utils
private FuncControlTemplate CreateWindowTemplate() private FuncControlTemplate CreateWindowTemplate()
{ {
return new FuncControlTemplate<Window>(parent => return new FuncControlTemplate<Window>((parent, scope) =>
{ {
return new ContentPresenter return new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
}; }.RegisterInNameScope(scope);
}); });
} }
} }

4
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -253,12 +253,12 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate<TestWindowBase> CreateTemplate() private FuncControlTemplate<TestWindowBase> CreateTemplate()
{ {
return new FuncControlTemplate<TestWindowBase>(x => return new FuncControlTemplate<TestWindowBase>((x, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}); }.RegisterInNameScope(scope));
} }
private class TestWindowBase : WindowBase private class TestWindowBase : WindowBase

8
tests/Avalonia.LeakTests/ControlTests.cs

@ -67,13 +67,15 @@ namespace Avalonia.LeakTests
{ {
Func<Window> run = () => Func<Window> run = () =>
{ {
var scope = new NameScope();
var window = new Window var window = new Window
{ {
Content = new Canvas Content = new Canvas
{ {
Name = "foo" Name = "foo"
} }.RegisterInNameScope(scope)
}; };
NameScope.SetNameScope(window, scope);
window.Show(); window.Show();
@ -84,6 +86,8 @@ namespace Avalonia.LeakTests
// Clear the content and ensure the Canvas is removed. // Clear the content and ensure the Canvas is removed.
window.Content = null; window.Content = null;
NameScope.SetNameScope(window, null);
window.LayoutManager.ExecuteLayoutPass(); window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child); Assert.Null(window.Presenter.Child);
@ -279,7 +283,7 @@ namespace Avalonia.LeakTests
DataTemplates = DataTemplates =
{ {
new FuncTreeDataTemplate<Node>( new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Name }, (x, _) => new TextBlock { Text = x.Name },
x => x.Children) x => x.Children)
}, },
Items = nodes Items = nodes

15
tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
@ -34,10 +35,13 @@ namespace Avalonia.Markup.UnitTests.Data
} }
}; };
root.RegisterChildrenNames();
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
Path = "Text", Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
}; };
target.Bind(TextBox.TextProperty, binding); target.Bind(TextBox.TextProperty, binding);
@ -69,10 +73,12 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
}; };
root.RegisterChildrenNames();
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
}; };
target.Bind(ContentControl.ContentProperty, binding); target.Bind(ContentControl.ContentProperty, binding);
@ -99,11 +105,13 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
}; };
root.RegisterChildrenNames();
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
Path = "Text", Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
}; };
target.Bind(TextBox.TextProperty, binding); target.Bind(TextBox.TextProperty, binding);
@ -113,7 +121,7 @@ namespace Avalonia.Markup.UnitTests.Data
Name = "source", Name = "source",
Text = "foo", Text = "foo",
}); });
root.RegisterChildrenNames();
Assert.Equal("foo", target.Text); Assert.Equal("foo", target.Text);
} }
@ -136,10 +144,12 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
}; };
root.RegisterChildrenNames();
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
}; };
target.Bind(ContentControl.ContentProperty, binding); target.Bind(ContentControl.ContentProperty, binding);
@ -151,6 +161,7 @@ namespace Avalonia.Markup.UnitTests.Data
}; };
stackPanel.Children.Add(source); stackPanel.Children.Add(source);
root.RegisterChildrenNames();
Assert.Same(source, target.Content); Assert.Same(source, target.Content);
} }

5
tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs

@ -23,9 +23,10 @@ namespace Avalonia.Markup.UnitTests.Data
DataContext = new Class1(), DataContext = new Class1(),
}; };
var format = "{0:0.0} + {1:00}";
var target = new MultiBinding var target = new MultiBinding
{ {
StringFormat = "{0:0.0} + {1:00}", StringFormat = format,
Bindings = Bindings =
{ {
new Binding(nameof(Class1.Foo)), new Binding(nameof(Class1.Foo)),
@ -35,7 +36,7 @@ namespace Avalonia.Markup.UnitTests.Data
textBlock.Bind(TextBlock.TextProperty, target); textBlock.Bind(TextBlock.TextProperty, target);
Assert.Equal("1.0 + 02", textBlock.Text); Assert.Equal(string.Format(format, 1, 2), textBlock.Text);
} }
[Fact] [Fact]

6
tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var source = new Button var source = new Button
{ {
Template = new FuncControlTemplate<Button>(parent => Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter new ContentPresenter
{ {
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
@ -41,7 +41,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var source = new Button var source = new Button
{ {
Template = new FuncControlTemplate<Button>(parent => Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter new ContentPresenter
{ {
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
@ -67,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Data
{ {
var source = new Button var source = new Button
{ {
Template = new FuncControlTemplate<Button>(parent => Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter new ContentPresenter
{ {
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)

4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@ -58,12 +58,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{ {
new Setter( new Setter(
Window.TemplateProperty, Window.TemplateProperty,
new FuncControlTemplate<Window>(x => new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
})) }.RegisterInNameScope(scope)))
} }
}; };
} }

4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -647,12 +647,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{ {
new Setter( new Setter(
Window.TemplateProperty, Window.TemplateProperty,
new FuncControlTemplate<Window>(x => new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
})) }.RegisterInNameScope(scope)))
} }
}; };
} }

4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -534,12 +534,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{ {
new Setter( new Setter(
Window.TemplateProperty, Window.TemplateProperty,
new FuncControlTemplate<Window>(x => new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
})) }.RegisterInNameScope(scope)))
} }
}; };
} }

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -296,7 +296,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
"; ";
var template = AvaloniaXamlLoader.Parse<ControlTemplate>(xaml); var template = AvaloniaXamlLoader.Parse<ControlTemplate>(xaml);
var parent = (ContentControl)template.Build(new ContentControl()); var parent = (ContentControl)template.Build(new ContentControl()).Control;
Assert.Equal("parent", parent.Name); Assert.Equal("parent", parent.Name);
@ -320,7 +320,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
"; ";
var template = AvaloniaXamlLoader.Parse<ControlTemplate>(xaml); var template = AvaloniaXamlLoader.Parse<ControlTemplate>(xaml);
var panel = (Panel)template.Build(new ContentControl()); var panel = (Panel)template.Build(new ContentControl()).Control;
Assert.Equal(2, panel.Children.Count); Assert.Equal(2, panel.Children.Count);
@ -681,7 +681,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var control = new ContentControl(); var control = new ContentControl();
var result = (ContentPresenter)template.Build(control); var result = (ContentPresenter)template.Build(control).Control;
Assert.NotNull(result); Assert.NotNull(result);
} }

6
tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

@ -98,7 +98,7 @@ namespace Avalonia.ReactiveUI.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ItemsControl>(parent => return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -108,9 +108,9 @@ namespace Avalonia.ReactiveUI.UnitTests
Name = "PART_ItemsPresenter", Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector, MemberSelector = parent.MemberSelector,
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }
} }
} }

6
tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs

@ -45,7 +45,7 @@ namespace Avalonia.ReactiveUI.UnitTests
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ContentControl>(parent => return new FuncControlTemplate<ContentControl>((parent, scope) =>
{ {
return new Border return new Border
{ {
@ -55,9 +55,9 @@ namespace Avalonia.ReactiveUI.UnitTests
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
} }.RegisterInNameScope(scope)
}; };
}); });
} }
} }
} }

155
tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs

@ -1,155 +0,0 @@
// Copyright (c) The Avalonia 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 System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Styling.UnitTests
{
public class ControlLocatorTests
{
[Fact]
public async Task Track_By_Name_Should_Find_Control_Added_Earlier()
{
TextBlock target;
TextBlock relativeTo;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
(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 =
{
(relativeTo = new TextBlock
{
Name = "start"
}),
}
})
};
var locator = ControlLocator.Track(relativeTo, "target");
var target = new TextBlock { Name = "target" };
var result = new List<ILogical>();
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_Track_Removal_And_Readd()
{
StackPanel panel;
TextBlock target;
TextBlock relativeTo;
var root = new TestRoot
{
Child = panel = new StackPanel
{
Children =
{
(target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }),
}
}
};
var locator = ControlLocator.Track(relativeTo, "target");
var result = new List<ILogical>();
locator.Subscribe(x => result.Add(x));
var other = new TextBlock { Name = "target" };
panel.Children.Remove(target);
panel.Children.Add(other);
Assert.Equal(new[] { target, null, other }, result);
}
[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 =
{
(relativeTo = new TextBlock
{
Name = "start"
}),
(target1 = new TextBlock { Name = "target" }),
}
}
};
var root2 = new TestRoot
{
Child = new StackPanel
{
Children =
{
(target2 = new TextBlock { Name = "target" }),
}
}
};
var locator = ControlLocator.Track(relativeTo, "target");
var target = new TextBlock { Name = "target" };
var result = new List<ILogical>();
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);
}
}
}

12
tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs

@ -16,12 +16,12 @@ namespace Avalonia.Styling.UnitTests
[Fact] [Fact]
public void Named_Template_Child_Of_Control_With_Two_Classes() public void Named_Template_Child_Of_Control_With_Two_Classes()
{ {
var template = new FuncControlTemplate(parent => var template = new FuncControlTemplate((parent, scope) =>
{ {
return new Border return new Border
{ {
Name = "border", Name = "border",
}; }.RegisterInNameScope(scope);
}); });
var control = new Button var control = new Button
@ -55,12 +55,12 @@ namespace Avalonia.Styling.UnitTests
[Fact] [Fact]
public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type() public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type()
{ {
var template = new FuncControlTemplate(parent => var template = new FuncControlTemplate((parent, scope) =>
{ {
return new Border return new Border
{ {
Name = "border", Name = "border",
}; }.RegisterInNameScope(scope);
}); });
var control = new Button var control = new Button
@ -88,12 +88,12 @@ namespace Avalonia.Styling.UnitTests
[Fact] [Fact]
public void Named_Class_Template_Child_Of_Control() public void Named_Class_Template_Child_Of_Control()
{ {
var template = new FuncControlTemplate(parent => var template = new FuncControlTemplate((parent, scope) =>
{ {
return new Border return new Border
{ {
Name = "border", Name = "border",
}; }.RegisterInNameScope(scope);
}); });
var control = new Button var control = new Button

13
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -51,19 +51,6 @@ namespace Avalonia.Styling.UnitTests
Assert.IsType<Canvas>(control.Child); Assert.IsType<Canvas>(control.Child);
} }
[Fact]
public void Materializes_Template_Should_Be_NameScope()
{
var control = new Decorator();
var template = new FuncTemplate<Canvas>(() => new Canvas());
var style = Mock.Of<IStyle>();
var setter = new Setter(Decorator.ChildProperty, template);
setter.Apply(style, control, null);
Assert.NotNull(NameScope.GetNameScope((Control)control.Child));
}
[Fact] [Fact]
public void Does_Not_Call_Converter_ConvertBack_On_OneWay_Binding() public void Does_Not_Call_Converter_ConvertBack_On_OneWay_Binding()
{ {

15
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -236,21 +236,6 @@ namespace Avalonia.Styling.UnitTests
} }
} }
[Fact]
public void Adding_To_Logical_Tree_Should_Register_With_NameScope()
{
using (AvaloniaLocator.EnterScope())
{
var root = new TestRoot();
var child = new Border();
child.Name = "foo";
root.Child = child;
Assert.Same(root.FindControl<Border>("foo"), child);
}
}
[Fact] [Fact]
public void Name_Cannot_Be_Set_After_Added_To_Logical_Tree() public void Name_Cannot_Be_Set_After_Added_To_Logical_Tree()
{ {

69
tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs

@ -1,69 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class StyledElementTests_NameScope
{
[Fact]
public void Controls_Should_Register_With_NameScope()
{
var root = new TestRoot
{
Child = new Border
{
Name = "foo",
Child = new Border
{
Name = "bar",
}
}
};
root.ApplyTemplate();
Assert.Same(root.FindControl<Border>("foo"), root.Child);
Assert.Same(root.FindControl<Border>("bar"), ((Border)root.Child).Child);
}
[Fact]
public void Control_Should_Unregister_With_NameScope()
{
var root = new TestRoot
{
Child = new Border
{
Name = "foo",
Child = new Border
{
Name = "bar",
}
}
};
root.Child = null;
Assert.Null(root.FindControl<Border>("foo"));
Assert.Null(root.FindControl<Border>("bar"));
}
[Fact]
public void Control_Should_Not_Register_With_Template_NameScope()
{
var root = new TestTemplatedRoot
{
Content = new Border
{
Name = "foo",
}
};
root.ApplyTemplate();
Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo"));
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save