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>
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
</Project>
</Project>

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

@ -150,6 +150,7 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Base.props" />
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\System.Memory.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));
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var rootServiceProviderField = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Fields
.First(x => x.Name == "RootServiceProviderV1"));
var createRootServiceProviderMethod = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
.First(x => x.Name == "CreateRootServiceProviderV2"));
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@ -211,7 +211,7 @@ namespace Avalonia.Build.Tasks
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
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.Brfalse, regularStart));
@ -307,7 +307,7 @@ namespace Avalonia.Build.Tasks
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
else
{
i.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField));
i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
}

2
src/Avalonia.Controls/AutoCompleteBox.cs

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

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

@ -8,7 +8,7 @@ using JetBrains.Annotations;
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)
{
@ -51,25 +51,6 @@ namespace Avalonia.Controls.Embedding
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);
public void Dispose() => PlatformImpl?.Dispose();
}

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

@ -30,25 +30,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
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);
public void Dispose()

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

@ -92,7 +92,6 @@ namespace Avalonia.Controls.Generators
result.DataContext = item;
}
NameScope.SetNameScope((Control)(object)result, new NameScope());
Index.Add(item, result);
return result;
@ -123,11 +122,20 @@ namespace Avalonia.Controls.Generators
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)
{
var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default;
var treeTemplate = template as ITreeDataTemplate ??
new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
var treeTemplate = template as ITreeDataTemplate ?? new WrapperTreeDataTemplate(template);
return treeTemplate;
}
}

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

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

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

@ -257,13 +257,14 @@ namespace Avalonia.Controls.Primitives
{
Logger.Verbose(LogArea.Control, this, "Creating control template");
var child = template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Control)child, nameScope);
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child);
RegisterNames(child, nameScope);
((ISetLogicalParent)child).SetParent(this);
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));
}
@ -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.
/// </summary>
/// <param name="build">The build function.</param>
public FuncControlTemplate(Func<ITemplatedControl, IControl> build)
public FuncControlTemplate(Func<ITemplatedControl, INameScope, IControl> 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.
/// </summary>
/// <param name="build">The build function.</param>
public FuncControlTemplate(Func<T, IControl> build)
: base(x => build((T)x))
public FuncControlTemplate(Func<T, INameScope, IControl> build)
: base((x, s) => build((T)x, s))
{
}
}
}
}

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

@ -17,7 +17,7 @@ namespace Avalonia.Controls.Templates
/// </summary>
public static readonly FuncDataTemplate Default =
new FuncDataTemplate<object>(
data =>
(data, s) =>
{
if (data != null)
{
@ -49,7 +49,7 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
bool supportsRecycling = false)
: 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>
public FuncDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
bool supportsRecycling = false)
: base(build)
{
@ -105,4 +105,4 @@ namespace Avalonia.Controls.Templates
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.
/// </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)
{
}
@ -35,12 +35,30 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Func<T, bool> match,
Func<T, IControl> build,
Func<T, INameScope, IControl> build,
bool supportsRecycling = false)
: 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>
/// Casts a strongly typed match function to a weakly typed one.
/// </summary>
@ -57,9 +75,9 @@ namespace Avalonia.Controls.Templates
/// <typeparam name="TResult">The strong data type.</typeparam>
/// <param name="f">The strongly typed function.</param>
/// <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>
where TControl : IControl
{
private readonly Func<TParam, TControl> _func;
private readonly Func<TParam, INameScope, TControl> _func;
/// <summary>
/// Initializes a new instance of the <see cref="FuncTemplate{TControl, TParam}"/> class.
/// </summary>
/// <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);
@ -35,7 +35,14 @@ namespace Avalonia.Controls.Templates
/// </returns>
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>
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector)
: this(o => IsInstance(o, type), build, itemsSelector)
{
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Templates
/// </param>
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector)
: base(match, build)
{

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

@ -23,7 +23,7 @@ namespace Avalonia.Controls.Templates
/// items.
/// </param>
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(
typeof(T),
@ -46,7 +46,7 @@ namespace Avalonia.Controls.Templates
/// </param>
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(
CastMatch(match),
@ -65,6 +65,17 @@ namespace Avalonia.Controls.Templates
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>
/// Casts a function with a typed parameter to an untyped function.
/// </summary>

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

@ -9,7 +9,25 @@ namespace Avalonia.Controls.Templates
/// <summary>
/// Interface representing a template used to build a <see cref="TemplatedControl"/>.
/// </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>
/// <typeparam name="TParam">The type of the parameter.</typeparam>
/// <typeparam name="TControl">The type of control.</typeparam>
public interface ITemplate<TParam, TControl> where TControl : IControl
public interface ITemplate<TParam, TControl>
{
/// <summary>
/// Creates the control.
@ -19,4 +19,4 @@ namespace Avalonia.Controls.Templates
/// </returns>
TControl Build(TParam param);
}
}
}

34
src/Avalonia.Controls/UserControl.cs

@ -9,40 +9,8 @@ namespace Avalonia.Controls
/// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </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>
/// A top-level window.
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -157,20 +157,6 @@ namespace Avalonia.Controls
_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>
/// Gets the platform-specific window implementation.
/// </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/>
protected override Size MeasureOverride(Size availableSize)
{

4
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -16,7 +16,7 @@ namespace Avalonia.ReactiveUI
/// </summary>
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 context = control.GetObservable(Control.DataContextProperty);
@ -52,4 +52,4 @@ namespace Avalonia.ReactiveUI
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.
using System;
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -10,16 +12,6 @@ namespace Avalonia.Controls
/// </summary>
public interface INameScope
{
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Registers an element in the name scope.
/// </summary>
@ -28,16 +20,30 @@ namespace Avalonia.Controls
void Register(string name, object element);
/// <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>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
object Find(string name);
/// <summary>
/// Unregisters an element with the name scope.
/// Marks the name scope as completed, no further registrations will be allowed
/// </summary>
/// <param name="name">The name.</param>
void Unregister(string name);
void Complete();
/// <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.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -18,44 +20,14 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope");
/// <inheritdoc/>
public bool IsCompleted { get; private set; }
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// 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;
}
private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches =
new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>();
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
/// </summary>
@ -80,13 +52,11 @@ namespace Avalonia.Controls
styled.SetValue(NameScopeProperty, value);
}
/// <summary>
/// Registers an element with the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
/// <inheritdoc />
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>(element != null);
@ -102,15 +72,29 @@ namespace Avalonia.Controls
else
{
_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>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
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)
{
Contract.Requires<ArgumentNullException>(name != null);
@ -120,21 +104,14 @@ namespace Avalonia.Controls
return result;
}
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
public void Unregister(string name)
public void Complete()
{
Contract.Requires<ArgumentNullException>(name != null);
object element;
if (_inner.TryGetValue(name, out element))
{
_inner.Remove(name);
Unregistered?.Invoke(this, new NameScopeEventArgs(name, element));
}
IsCompleted = true;
foreach (var kp in _pendingSearches)
kp.Value.TrySetResult(null);
_pendingSearches.Clear();
}
}
}

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

@ -37,6 +37,25 @@ namespace Avalonia.Controls
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>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found.
@ -67,6 +86,28 @@ namespace Avalonia.Controls
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)
{
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>
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)
{
return new ControlTracker(relativeTo, ancestorLevel, ancestorType);
@ -35,18 +23,10 @@ namespace Avalonia.LogicalTree
private class ControlTracker : LightweightObservableBase<ILogical>
{
private readonly ILogical _relativeTo;
private readonly string _name;
private readonly int _ancestorLevel;
private readonly Type _ancestorType;
INameScope _nameScope;
ILogical _value;
public ControlTracker(ILogical relativeTo, string name)
{
_relativeTo = relativeTo;
_name = name;
}
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
{
_relativeTo = relativeTo;
@ -66,12 +46,6 @@ namespace Avalonia.LogicalTree
_relativeTo.AttachedToLogicalTree -= Attached;
_relativeTo.DetachedFromLogicalTree -= Detached;
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null;
}
@ -88,57 +62,15 @@ namespace Avalonia.LogicalTree
private void Detached(object sender, LogicalTreeAttachmentEventArgs e)
{
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = 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()
{
if (_name != null)
{
_nameScope = _relativeTo.FindNameScope();
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);
}
_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 bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
@ -82,7 +81,6 @@ namespace Avalonia
/// </summary>
public StyledElement()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
@ -381,7 +379,6 @@ namespace Avalonia
{
if (_initCount == 0 && (!_styled || force))
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
@ -675,19 +672,6 @@ namespace Avalonia
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)
{
if (c == null)
@ -724,11 +708,6 @@ namespace Avalonia
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);

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

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

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

@ -40,7 +40,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source,
StringFormat = StringFormat,
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 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 IPanel Build()
=> (IPanel)TemplateContent.Load(Content);
=> (IPanel)TemplateContent.Load(Content).Control;
object ITemplate.Build() => Build();
}
}
}

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

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

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

@ -4,17 +4,18 @@
using System;
using Avalonia.Controls;
using System.Collections.Generic;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates
{
public static class TemplateContent
{
public static IControl Load(object templateContent)
public static ControlTemplateResult Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
return (IControl)direct(null);
return (ControlTemplateResult)direct(null);
}
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)
{
var visualTreeForItem = TemplateContent.Load(Content);
var visualTreeForItem = TemplateContent.Load(Content).Control;
visualTreeForItem.DataContext = data;
return visualTreeForItem;
}
}
}
}

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

@ -156,7 +156,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
overrideField.SetValue(null,
new Action<object>(
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); }));
target => { populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), target); }));
try
{
return Activator.CreateInstance(targetType);
@ -170,11 +170,11 @@ namespace Avalonia.Markup.Xaml.XamlIl
var createCb = Expression.Lambda<Func<IServiceProvider, object>>(
Expression.Convert(Expression.Call(
created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile();
return createCb(XamlIlRuntimeHelpers.RootServiceProviderV1);
return createCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2());
}
else
{
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance);
populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), 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"),
RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"),
RootObjectProviderIntermediateRootPropertyName = "IntermediateRootObject",
UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"),
ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
@ -53,9 +54,30 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
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
{
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")
{
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
&& mg.Children.OfType<ScopeRegistrationNode>().Any())
&& mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
return node;
IXamlIlAstValueNode value = null;
@ -41,54 +41,99 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Children =
{
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;
}
class ScopeRegistrationNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class HandleRootObjectScopeNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
public IXamlIlAstValueNode Value { get; set; }
public ScopeRegistrationNode(IXamlIlAstValueNode value) : base(value)
private readonly AvaloniaXamlIlWellKnownTypes _types;
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)
{
var exts = context.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScopeExtensions");
var findNameScope = exts.FindMethod(m => m.Name == "FindNameScope");
var registerMethod = findNameScope.ReturnType.FindMethod(m => m.Name == "Register");
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
using (var nameScopeLoc = context.GetLocal(findNameScope.ReturnType))
var next = codeGen.DefineLabel();
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
{
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
.Ldloc(targetLoc.Local)
.EmitCall(registerMethod)
.MarkLabel(exit);
.Isinst(_types.StyledElement)
.Dup()
.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);
}
}
}
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 IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public XamlIlLanguageTypeMappings XamlIlMappings { get; }
public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { 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)
{
XamlIlTypes = ctx.Configuration.WellKnownTypes;
XamlIlMappings = ctx.Configuration.TypeMappings;
AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
@ -37,8 +45,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AvaloniaProperty,
IBinding, ctx.Configuration.WellKnownTypes.Object);
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,
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
}
}

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

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -17,7 +19,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
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 :
@ -27,12 +36,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes;
private readonly INameScope _nameScope;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
object rootObject)
object rootObject, INameScope nameScope)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
_nameScope = nameScope;
RootObject = rootObject;
}
@ -48,6 +59,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public object GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
if (serviceType == typeof(IRootObjectProvider))
@ -56,6 +69,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
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
{
private readonly INameScope _nameScope;
public RootServiceProvider(INameScope nameScope)
{
_nameScope = nameScope;
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
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
{
/// <summary>
/// The root object of the xaml file
/// </summary>
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

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

@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.LogicalTree;
@ -90,6 +91,8 @@ namespace Avalonia.Data
public string StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
@ -110,7 +113,9 @@ namespace Avalonia.Data
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)
{
@ -254,9 +259,12 @@ namespace Avalonia.Data
ExpressionNode node)
{
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(
ControlLocator.Track(target, elementName),
NameScopeLocator.Track(scope, elementName),
node,
null);
return result;

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

@ -1,5 +1,6 @@
using System;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@ -7,7 +8,8 @@ namespace Avalonia.Markup.Parsers
{
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))
{
@ -15,7 +17,7 @@ namespace Avalonia.Markup.Parsers
}
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);
if (!reader.End)

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

@ -7,6 +7,7 @@ using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
namespace Avalonia.Markup.Parsers
{
@ -20,10 +21,12 @@ namespace Avalonia.Markup.Parsers
{
private readonly bool _enableValidation;
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;
_nameScope = nameScope;
_enableValidation = enableValidation;
}
@ -213,7 +216,7 @@ namespace Avalonia.Markup.Parsers
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;
}

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

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

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

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

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

@ -302,7 +302,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("FooBar", target.SelectedItem);
}
private Control CreateTemplate(Carousel control)
private Control CreateTemplate(Carousel control, INameScope scope)
{
return new CarouselPresenter
{
@ -312,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
[~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty],
[~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty],
[~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()
{
return new FuncControlTemplate<ComboBox>(parent =>
return new FuncControlTemplate<ComboBox>((parent, scope) =>
{
return new Panel
{
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
new ToggleButton
{
Name = "toggle",
},
}.RegisterInNameScope(scope),
new Popup
{
Name = "PART_Popup",
@ -102,8 +102,8 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ItemsPresenter",
[!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
{
Template = GetTemplate(),
ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Content = "Foo";
@ -302,8 +302,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(_ => presenter),
ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()),
Template = new FuncControlTemplate<ContentControl>((_, __) => presenter),
ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
Content = "foo",
};
@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ContentControl>(parent =>
return new FuncControlTemplate<ContentControl>((parent, scope) =>
{
return new Border
{
@ -343,7 +343,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~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()
{
return new FuncControlTemplate<DatePicker>(control =>
return new FuncControlTemplate<DatePicker>((control, scope) =>
{
var textBox =
new TextBox
{
Name = "PART_TextBox"
};
}.RegisterInNameScope(scope);
var button =
new Button
{
Name = "PART_Button"
};
}.RegisterInNameScope(scope);
var calendar =
new Calendar
{
Name = "PART_Calendar"
};
}.RegisterInNameScope(scope);
var popup =
new Popup
{
Name = "PART_Popup"
};
}.RegisterInNameScope(scope);
var panel = new Panel();
panel.Children.Add(textBox);

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

@ -20,6 +20,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Detects_Horizontal_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
RowDefinitions = new RowDefinitions("*,Auto,*"),
@ -27,7 +28,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.RowProperty] = 0 },
new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.RowProperty] = 1 }),
new Border { [Grid.RowProperty] = 2 }
}
};
@ -35,12 +36,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(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]
public void Detects_Vertical_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
@ -48,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
@ -56,12 +58,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(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]
public void Detects_With_Both_Auto()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
@ -69,7 +72,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
@ -77,7 +80,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(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]
@ -129,13 +132,14 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void In_First_Position_Doesnt_Throw_Exception()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 0} ),
new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 },
}
@ -144,7 +148,6 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
var splitter = grid.FindControl<GridSplitter>("splitter");
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
@ -199,4 +202,4 @@ namespace Avalonia.Controls.UnitTests
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()
{
return new FuncControlTemplate<HeaderedItemsControl>(parent =>
return new FuncControlTemplate<HeaderedItemsControl>((parent, scope) =>
{
return new Border
{
@ -71,7 +71,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_HeaderPresenter",
[~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
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Items = new[] { "Foo" };
@ -411,7 +411,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
Items = items,
};
@ -472,29 +472,6 @@ namespace Avalonia.Controls.UnitTests
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]
public void Focuses_Next_Item_On_Key_Down()
{
@ -578,7 +555,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>(parent =>
return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{
return new Border
{
@ -588,7 +565,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector,
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}
}.RegisterInNameScope(scope)
};
});
}

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

@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = new[] { "Foo" },
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
Prepare(target);
@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
},
Items = items,
};
@ -138,7 +138,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
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,
};
@ -162,7 +162,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
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,
};
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
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,
};
@ -205,7 +205,7 @@ namespace Avalonia.Controls.UnitTests
Template = ListBoxTemplate(),
Items = items,
SelectionMode = SelectionMode.Toggle,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 })
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
};
Prepare(target);
@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 11 })
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 11 })
};
Prepare(target);
@ -287,7 +287,7 @@ namespace Avalonia.Controls.UnitTests
target.DataContext = items;
target.VirtualizationMode = virtualizationMode;
target.ItemTemplate = new FuncDataTemplate<object>(c =>
target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
{
var tb = new TextBlock() { Height = 10, Width = 30 };
tb.Bind(TextBlock.TextProperty, new Data.Binding());
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate<ListBox>(parent =>
return new FuncControlTemplate<ListBox>((parent, scope) =>
new ScrollViewer
{
Name = "PART_ScrollViewer",
@ -345,24 +345,24 @@ namespace Avalonia.Controls.UnitTests
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
[~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
[~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(),
}
});
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope));
}
private FuncControlTemplate ListBoxItemTemplate()
{
return new FuncControlTemplate<ListBoxItem>(parent =>
return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
});
}.RegisterInNameScope(scope));
}
private FuncControlTemplate ScrollViewerTemplate()
{
return new FuncControlTemplate<ScrollViewer>(parent =>
return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
@ -370,7 +370,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
});
}.RegisterInNameScope(scope));
}
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
{
@ -254,17 +254,18 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
}
}.RegisterInNameScope(scope)
};
}
private Control CreateScrollViewerTemplate(ITemplatedControl parent)
private Control CreateScrollViewerTemplate(ITemplatedControl parent, INameScope scope)
{
return new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
};
[~ContentPresenter.ContentProperty] =
parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
}.RegisterInNameScope(scope);
}
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()
{
Template = new FuncControlTemplate(_ => new Panel
Template = new FuncControlTemplate((_, scope) => new Panel
{
Children =
{
new ContentPresenter { Name = "Content_1_Presenter" },
new ContentPresenter { Name = "Content_2_Presenter" }
new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope),
new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope)
}
})
};
var ex = Record.Exception(() => target.ApplyTemplate());
@ -43,12 +44,12 @@ namespace Avalonia.Controls.UnitTests.Mixins
var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
var target = new TestControl
{
Template = new FuncControlTemplate(_ => new Panel
Template = new FuncControlTemplate((_, scope) => new Panel
{
Children =
{
p1,
p2
p1.RegisterInNameScope(scope),
p2.RegisterInNameScope(scope)
}
})
};

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

@ -20,36 +20,146 @@ namespace Avalonia.Controls.UnitTests
}
[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 element = new object();
target.Register("foo", element);
target.Unregister("foo");
target.Register("foo", element);
Assert.Null(target.Find("foo"));
Assert.Same(element, target.Find("foo"));
}
[Fact]
public void Cannot_Register_New_Element_With_Existing_Name()
public void Cannot_Register_New_Element_For_Completed_Scope()
{
var target = new NameScope();
var element = new object();
target.Register("foo", new object());
Assert.Throws<ArgumentException>(() => target.Register("foo", new object()));
target.Register("foo", element);
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]
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();
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);
target.Register("foo", element);
[Fact]
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));
}
[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]
public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
{
@ -170,7 +159,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var (target, _) = CreateTarget();
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
target.Content = "Foo";
Assert.IsType<Canvas>(target.Child);
@ -184,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
Assert.IsType<Canvas>(target.Child);
target.ContentTemplate = null;
@ -209,7 +198,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Recycles_DataTemplate()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), true));
target.Content = "foo";
@ -239,7 +228,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Detects_DataTemplate_Doesnt_Support_Recycling()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), false));
target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), false));
target.Content = "foo";
@ -256,7 +245,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var (target, _) = CreateTarget();
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";
@ -278,8 +267,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
};
var (target, host) = CreateTarget();
host.DataTemplates.Add(new FuncDataTemplate<string>(x => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>(x => new Canvas()));
host.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>((_, __) => new Canvas()));
target.Content = "foo";
Assert.Same(textBlock, target.Child);
@ -296,11 +285,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var templatedParent = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(x =>
Template = new FuncControlTemplate<ContentControl>((_, s) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
}),
}.RegisterInNameScope(s)),
};
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
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -93,14 +93,14 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var contentControl = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(c => new ContentPresenter()
Template = new FuncControlTemplate<ContentControl>((c, scope) => new ContentPresenter()
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty]
}),
}.RegisterInNameScope(scope)),
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
target.Content = "foo";
@ -235,8 +235,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
DataTemplates =
{
new FuncDataTemplate<string>(x => textBlock),
new FuncDataTemplate<int>(x => new Canvas()),
new FuncDataTemplate<string>((x, _) => textBlock),
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 =
{
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);
}
}
}
}

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

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

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

@ -309,7 +309,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static IDataTemplate ItemTemplate()
{
return new FuncDataTemplate<string>(x => new Canvas
return new FuncDataTemplate<string>((x, _) => new Canvas
{
Width = 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,
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
};
target.ApplyTemplate();
@ -1046,7 +1046,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
private static IDataTemplate StringDataTemplate()
{
return new FuncDataTemplate<string>(x => new Canvas
return new FuncDataTemplate<string>((x, _) => new Canvas
{
Width = 10,
Height = 10,
@ -1095,12 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
public TestContainer()
{
Template = new FuncControlTemplate<TestContainer>(parent => new ContentPresenter
Template = new FuncControlTemplate<TestContainer>((parent, scope) => new ContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~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
{
Template = new FuncControlTemplate<PopupRoot>(parent =>
Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty],
}),
}.RegisterInNameScope(scope)),
};
result.ApplyTemplate();
@ -154,7 +154,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
public TemplatedControlWithPopup()
{
Template = new FuncControlTemplate<TemplatedControlWithPopup>(parent =>
Template = new FuncControlTemplate<TemplatedControlWithPopup>((parent, _) =>
new Popup
{
[!Popup.ChildProperty] = parent[!TemplatedControlWithPopup.PopupContentProperty],

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

@ -315,16 +315,16 @@ namespace Avalonia.Controls.UnitTests.Primitives
return result;
}
private static IControl PopupRootTemplate(PopupRoot control)
private static IControl PopupRootTemplate(PopupRoot control, INameScope scope)
{
return new ContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
};
}.RegisterInNameScope(scope);
}
private static IControl PopupContentControlTemplate(PopupContentControl control)
private static IControl PopupContentControlTemplate(PopupContentControl control, INameScope scope)
{
return new Popup
{
@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
}
};
}.RegisterInNameScope(scope);
}
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()
{
Template = new FuncControlTemplate<RangeBase>(c =>
Template = new FuncControlTemplate<RangeBase>((c, scope) =>
{
track = new Track()
{
@ -122,7 +122,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Name = "PART_Track",
Thumb = new Thumb()
};
}.RegisterInNameScope(scope);
if (useXamlBinding)
{

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

@ -169,7 +169,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.False(target.IsVisible);
}
private static Control Template(ScrollBar control)
private static Control Template(ScrollBar control, INameScope scope)
{
return new Border
{
@ -185,11 +185,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Template = new FuncControlTemplate<Thumb>(ThumbTemplate),
},
},
}.RegisterInNameScope(scope),
};
}
private static Control ThumbTemplate(Thumb control)
private static Control ThumbTemplate(Thumb control, INameScope scope)
{
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()
{
return new FuncControlTemplate<SelectingItemsControl>(control =>
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter
{
Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
});
}.RegisterInNameScope(scope));
}
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()
{
return new FuncControlTemplate<SelectingItemsControl>(control =>
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter
{
Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
});
}.RegisterInNameScope(scope));
}
private class TestSelector : SelectingItemsControl

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

@ -964,7 +964,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Template = Template(),
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,
};
@ -988,7 +988,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Template = Template(),
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,
};
@ -1010,7 +1010,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Template = Template(),
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,
};
@ -1035,13 +1035,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
private FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>(control =>
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
});
}.RegisterInNameScope(scope));
}
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);
}
private Control CreateTabStripTemplate(TabStrip parent)
private Control CreateTabStripTemplate(TabStrip parent, INameScope scope)
{
return new ItemsPresenter
{
Name = "itemsPresenter",
[!ItemsPresenter.ItemsProperty] = parent[!ItemsControl.ItemsProperty],
[!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;
var template = new FuncControlTemplate(_ =>
var template = new FuncControlTemplate((_, __) =>
{
executed = true;
return new Control();
@ -42,7 +42,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
bool executed = false;
var template = new FuncControlTemplate(_ =>
var template = new FuncControlTemplate((_, __) =>
{
executed = true;
return new Control();
@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
Template = new FuncControlTemplate((_, __) => new Decorator
{
Child = new Panel
{
@ -92,35 +92,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Empty(target.GetLogicalChildren());
}
[Fact]
public void Templated_Child_Should_Be_NameScope()
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
{
Child = new Panel
{
Children =
{
new TextBlock(),
new Border(),
}
}
}),
};
target.ApplyTemplate();
Assert.NotNull(NameScope.GetNameScope((Control)target.GetVisualChildren().Single()));
}
[Fact]
public void Templated_Children_Should_Have_TemplatedParent_Set()
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
Template = new FuncControlTemplate((_, __) => new Decorator
{
Child = new Panel
{
@ -149,7 +126,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator())
Template = new FuncControlTemplate((_, __) => new Decorator())
};
target.ApplyTemplate();
@ -165,14 +142,14 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator())
Template = new FuncControlTemplate((_, __) => new Decorator())
};
target.ApplyTemplate();
var child = (Decorator)target.GetVisualChildren().Single();
target.Template = new FuncControlTemplate(_ => new Canvas());
target.Template = new FuncControlTemplate((_, __) => new Canvas());
target.ApplyTemplate();
Assert.Null(child.Parent);
@ -183,7 +160,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new ScrollViewer())
Template = new FuncControlTemplate((_, __) => new ScrollViewer())
};
target.ApplyTemplate();
@ -203,7 +180,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ =>
Template = new FuncControlTemplate((_, __) =>
{
return new StackPanel
{
@ -232,11 +209,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ =>
Template = new FuncControlTemplate((_, __) =>
{
return new ContentControl
{
Template = new FuncControlTemplate(parent =>
Template = new FuncControlTemplate((parent, ___) =>
{
return new Border
{
@ -271,47 +248,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
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]
public void ApplyTemplate_Should_Raise_TemplateApplied()
{
var target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator())
Template = new FuncControlTemplate((_, __) => new Decorator())
};
var raised = false;
@ -334,7 +276,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
var target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
Template = new FuncControlTemplate((_, __) => new Decorator
{
Child = new Border(),
})
@ -348,7 +290,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(target, decorator.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
// somewhere with the same template, so they could still be of use.
@ -370,7 +312,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
Template = new FuncControlTemplate((_, __) => new Decorator
{
Child = templateChild,
})
@ -392,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
Template = new FuncControlTemplate((_, __) => new Decorator
{
Child = templateChild,
})
@ -425,7 +367,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
new Setter(
TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator
new FuncControlTemplate((_, __) => new Decorator
{
Child = new Border(),
}))
@ -461,7 +403,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
new Setter(
TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator
new FuncControlTemplate((_, __) => new Decorator
{
Child = new Border(),
}))
@ -500,7 +442,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
new Setter(
TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator
new FuncControlTemplate((_, __) => new Decorator
{
Child = new Border(),
}))
@ -520,7 +462,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
new Setter(
TemplatedControl.TemplateProperty,
new FuncControlTemplate(_ => new Decorator
new FuncControlTemplate((_, __) => new Decorator
{
Child = new Border(),
}))
@ -555,7 +497,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
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
{
@ -585,20 +527,20 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Name = "PART_ContentPresenter",
[!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
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
};
}.RegisterInNameScope(scope);
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);
}
private Control CreateTemplate(ScrollViewer control)
private Control CreateTemplate(ScrollViewer control, INameScope scope)
{
return new Grid
{
@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
},
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "horizontalScrollBar",
@ -98,7 +98,7 @@ namespace Avalonia.Controls.UnitTests
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1,
},
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "verticalScrollBar",
@ -108,9 +108,9 @@ namespace Avalonia.Controls.UnitTests
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[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))
{
@ -176,7 +176,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
Items = items,
};
@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests
TabControl target = new TabControl
{
Template = TabControlTemplate(),
ContentTemplate = new FuncDataTemplate<string>(x =>
ContentTemplate = new FuncDataTemplate<string>((x, _) =>
new TextBlock { Tag = "bar", Text = x }),
Items = new[] { "Foo" },
};
@ -289,7 +289,7 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate TabControlTemplate()
{
return new FuncControlTemplate<TabControl>(parent =>
return new FuncControlTemplate<TabControl>((parent, scope) =>
new StackPanel
{
Children =
@ -299,26 +299,26 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ItemsPresenter",
[!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
},
}.RegisterInNameScope(scope),
new ContentPresenter
{
Name = "PART_SelectedContentHost",
[!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
}
}.RegisterInNameScope(scope)
}
});
}
private IControlTemplate TabItemTemplate()
{
return new FuncControlTemplate<TabItem>(parent =>
return new FuncControlTemplate<TabItem>((parent, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
});
}.RegisterInNameScope(scope));
}
private void ApplyTemplate(TabControl target)

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

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

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

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

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

@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemTemplate = new FuncTreeDataTemplate<Node>(
_ => new Canvas(),
(_, __) => new Canvas(),
x => x.Children),
}
};
@ -475,7 +475,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Node>(x => new Button { Content = x })
new FuncDataTemplate<Node>((x, _) => new Button { Content = x })
},
Items = items,
};
@ -513,27 +513,6 @@ namespace Avalonia.Controls.UnitTests
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]
public void Should_React_To_Children_Changing()
{
@ -799,16 +778,16 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTreeViewTemplate()
{
return new FuncControlTemplate<TreeView>(parent => new ItemsPresenter
return new FuncControlTemplate<TreeView>((parent, scope) => new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
});
}.RegisterInNameScope(scope));
}
private IControlTemplate CreateTreeViewItemTemplate()
{
return new FuncControlTemplate<TreeViewItem>(parent => new Panel
return new FuncControlTemplate<TreeViewItem>((parent, scope) => new Panel
{
Children =
{
@ -816,12 +795,12 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_HeaderPresenter",
[~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
},
}.RegisterInNameScope(scope),
new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~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()
{
return new FuncControlTemplate<UserControl>(parent =>
return new FuncControlTemplate<UserControl>((parent, scope) =>
{
return new Border
{
@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ContentPresenter",
[~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()
{
return new FuncControlTemplate<Window>(parent =>
return new FuncControlTemplate<Window>((parent, scope) =>
{
return new ContentPresenter
{
Name = "PART_ContentPresenter",
[~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()
{
return new FuncControlTemplate<TestWindowBase>(x =>
return new FuncControlTemplate<TestWindowBase>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
});
}.RegisterInNameScope(scope));
}
private class TestWindowBase : WindowBase

8
tests/Avalonia.LeakTests/ControlTests.cs

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

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

@ -1,6 +1,7 @@
// 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 Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Data;
@ -34,10 +35,13 @@ namespace Avalonia.Markup.UnitTests.Data
}
};
root.RegisterChildrenNames();
var binding = new Binding
{
ElementName = "source",
Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(TextBox.TextProperty, binding);
@ -69,10 +73,12 @@ namespace Avalonia.Markup.UnitTests.Data
}
}
};
root.RegisterChildrenNames();
var binding = new Binding
{
ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(ContentControl.ContentProperty, binding);
@ -99,11 +105,13 @@ namespace Avalonia.Markup.UnitTests.Data
}
}
};
root.RegisterChildrenNames();
var binding = new Binding
{
ElementName = "source",
Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(TextBox.TextProperty, binding);
@ -113,7 +121,7 @@ namespace Avalonia.Markup.UnitTests.Data
Name = "source",
Text = "foo",
});
root.RegisterChildrenNames();
Assert.Equal("foo", target.Text);
}
@ -136,10 +144,12 @@ namespace Avalonia.Markup.UnitTests.Data
}
}
};
root.RegisterChildrenNames();
var binding = new Binding
{
ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(ContentControl.ContentProperty, binding);
@ -151,6 +161,7 @@ namespace Avalonia.Markup.UnitTests.Data
};
stackPanel.Children.Add(source);
root.RegisterChildrenNames();
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(),
};
var format = "{0:0.0} + {1:00}";
var target = new MultiBinding
{
StringFormat = "{0:0.0} + {1:00}",
StringFormat = format,
Bindings =
{
new Binding(nameof(Class1.Foo)),
@ -35,7 +36,7 @@ namespace Avalonia.Markup.UnitTests.Data
textBlock.Bind(TextBlock.TextProperty, target);
Assert.Equal("1.0 + 02", textBlock.Text);
Assert.Equal(string.Format(format, 1, 2), textBlock.Text);
}
[Fact]

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

@ -18,7 +18,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
var source = new Button
{
Template = new FuncControlTemplate<Button>(parent =>
Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter
{
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
@ -41,7 +41,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
var source = new Button
{
Template = new FuncControlTemplate<Button>(parent =>
Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter
{
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
@ -67,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
var source = new Button
{
Template = new FuncControlTemplate<Button>(parent =>
Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter
{
[~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(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!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(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!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(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!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 parent = (ContentControl)template.Build(new ContentControl());
var parent = (ContentControl)template.Build(new ContentControl()).Control;
Assert.Equal("parent", parent.Name);
@ -320,7 +320,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.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);
@ -681,7 +681,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var control = new ContentControl();
var result = (ContentPresenter)template.Build(control);
var result = (ContentPresenter)template.Build(control).Control;
Assert.NotNull(result);
}

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

@ -98,7 +98,7 @@ namespace Avalonia.ReactiveUI.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>(parent =>
return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{
return new Border
{
@ -108,9 +108,9 @@ namespace Avalonia.ReactiveUI.UnitTests
Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector,
[~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()
{
return new FuncControlTemplate<ContentControl>(parent =>
return new FuncControlTemplate<ContentControl>((parent, scope) =>
{
return new Border
{
@ -55,9 +55,9 @@ namespace Avalonia.ReactiveUI.UnitTests
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~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]
public void Named_Template_Child_Of_Control_With_Two_Classes()
{
var template = new FuncControlTemplate(parent =>
var template = new FuncControlTemplate((parent, scope) =>
{
return new Border
{
Name = "border",
};
}.RegisterInNameScope(scope);
});
var control = new Button
@ -55,12 +55,12 @@ namespace Avalonia.Styling.UnitTests
[Fact]
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
{
Name = "border",
};
}.RegisterInNameScope(scope);
});
var control = new Button
@ -88,12 +88,12 @@ namespace Avalonia.Styling.UnitTests
[Fact]
public void Named_Class_Template_Child_Of_Control()
{
var template = new FuncControlTemplate(parent =>
var template = new FuncControlTemplate((parent, scope) =>
{
return new Border
{
Name = "border",
};
}.RegisterInNameScope(scope);
});
var control = new Button

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

@ -51,19 +51,6 @@ namespace Avalonia.Styling.UnitTests
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]
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]
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