Browse Source

Use INameScope from IServiceProvider

pull/2705/head
Nikita Tsukanov 7 years ago
parent
commit
fe5c1cec76
  1. 10
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  2. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  3. 6
      src/Avalonia.Controls/Templates/FuncControlTemplate.cs
  4. 10
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  5. 22
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  6. 4
      src/Avalonia.Controls/Templates/ITemplate`2.cs
  7. 15
      src/Avalonia.Styling/Controls/NameScope.cs
  8. 79
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  9. 74
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  10. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  11. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  12. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  13. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  14. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  15. 5
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  16. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  17. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  18. 21
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  19. 59
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  20. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  21. 33
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  22. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  23. 14
      src/Markup/Avalonia.Markup/Data/Binding.cs
  24. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  25. 7
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  26. 13
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  27. 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  28. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  29. 2
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  30. 5
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs
  31. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  32. 18
      tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs
  33. 2
      tests/Avalonia.UnitTests/TestRoot.cs

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

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

@ -257,12 +257,10 @@ namespace Avalonia.Controls.Primitives
{
Logger.Verbose(LogArea.Control, this, "Creating control template");
var child = template.Build(this);
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
var nameScope = (child is StyledElement styledChild) ? NameScope.GetNameScope(styledChild) : null;
// Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null)

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

@ -20,5 +20,11 @@ namespace Avalonia.Controls.Templates
: base(build)
{
}
public new ControlTemplateResult Build(ITemplatedControl param)
{
var (control, scope) = BuildWithNameScope(param);
return new ControlTemplateResult(control, scope);
}
}
}

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

@ -34,13 +34,15 @@ namespace Avalonia.Controls.Templates
/// The created control.
/// </returns>
public TControl Build(TParam param)
{
return BuildWithNameScope(param).Item1;
}
protected (TControl, INameScope) BuildWithNameScope(TParam param)
{
var scope = new NameScope();
var rv = _func(param, scope);
// TODO: May be return the name scope alongside with the control instead?
if (rv is StyledElement sl)
NameScope.SetNameScope(sl, scope);
return rv;
return (rv, scope);
}
}
}

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

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

@ -105,21 +105,6 @@ namespace Avalonia.Controls
Registered?.Invoke(this, new NameScopeEventArgs(name, element));
}
}
/// <summary>
/// Registers an element in the name scope associated with the scopeElement.
/// Creates the scope if one isn't associated with the element yet
/// </summary>
/// <param name="scopeElement"></param>
/// <param name="name"></param>
/// <param name="element"></param>
public static void Register(StyledElement scopeElement, string name, object element)
{
var scope = scopeElement as INameScope ?? GetNameScope(scopeElement);
if(scope == null)
SetNameScope(scopeElement, scope = new NameScope());
scope.Register(name, element);
}
/// <summary>
/// Finds a named element in the name scope.

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

@ -0,0 +1,79 @@
using System;
using System.Linq;
using System.Reflection;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
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 ScopeTracker(scope, name);
}
private class ScopeTracker : LightweightObservableBase<object>
{
private readonly string _name;
INameScope _nameScope;
object _value;
public ScopeTracker(INameScope nameScope, string name)
{
_nameScope = nameScope;
_name = name;
}
protected override void Initialize()
{
_nameScope.Registered += Registered;
_nameScope.Unregistered += Unregistered;
_value = _nameScope.Find<ILogical>(_name);
}
protected override void Deinitialize()
{
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null;
}
protected override void Subscribed(IObserver<object> observer, bool first)
{
observer.OnNext(_value);
}
private void Registered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name)
{
_value = e.Element;
PublishNext(_value);
}
}
private void Unregistered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name)
{
_value = null;
PublishNext(null);
}
}
}
}
}

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

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

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

@ -54,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;

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

@ -46,10 +46,52 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
};
}
if (!context.ParentNodes().Any()
&& node is XamlIlValueWithManipulationNode mnode)
{
mnode.Manipulation = new XamlIlManipulationGroupNode(mnode,
new[]
{
mnode.Manipulation,
new AddNameScopeToRootObjectXamlIlNode(mnode, context.GetAvaloniaTypes())
});
}
return node;
}
class AddNameScopeToRootObjectXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public AddNameScopeToRootObjectXamlIlNode(IXamlIlLineInfo lineInfo,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
_types = types;
}
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var next = codeGen.DefineLabel();
using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
{
codeGen
.Isinst(_types.StyledElement)
.Dup()
.Stloc(local.Local)
.Brfalse(next)
.Ldloc(local.Local)
.Ldloc(context.ContextLocal)
.Ldfld(context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName))
.EmitCall(_types.NameScopeSetNameScope)
.MarkLabel(next);
}
return XamlIlNodeEmitResult.Void(1);
}
}
}
class AvaloniaNameScopeRegistrationXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
@ -68,19 +110,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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}
// var target = {pop}
.Stloc(targetLoc.Local)
// NameScope.Register(context.IntermediateRoot, Name, target)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(context.RuntimeContext.IntermediateRootObjectField)
.Castclass(_types.StyledElement);
.Ldfld(scopeField);
context.Emit(Name, codeGen, Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(_types.NameScopeStaticRegister, true);
.EmitCall(_types.INameScopeRegister, true);
}
return XamlIlNodeEmitResult.Void(1);

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

@ -16,16 +16,20 @@ 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 NameScopeStaticRegister { get; }
public IXamlIlMethod NameScopeSetNameScope { get; }
public IXamlIlType INameScope { get; }
public IXamlIlMethod INameScopeRegister { 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");
@ -41,16 +45,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IBinding, ctx.Configuration.WellKnownTypes.Object);
UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = ctx.Configuration.TypeSystem.GetType("Avalonia.StyledElement");
NameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScope");
NameScopeStaticRegister = NameScope.FindMethod(
INameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.INameScope");
INameScopeRegister = INameScope.GetMethod(
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
StyledElement, XamlIlTypes.String, XamlIlTypes.Object)
XamlIlTypes.String, XamlIlTypes.Object)
{
IsStatic = true, DeclaringOnly = true, IsExactMatch = true
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);
}
}

33
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,12 @@ 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 scope = new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
return new ControlTemplateResult((IControl)obj, scope);
};
}
class DeferredParentServiceProvider :
@ -27,12 +34,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 +57,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))
@ -133,12 +144,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 fc52295ea6c45f709bf63975026a4a18ce230be9
Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022

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()

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

@ -472,7 +472,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((TextBlock)item));
}
[Fact]
[Fact(Skip="We are using name scopes from IServiceProvider now")]
public void DataTemplate_Created_Content_Should_Be_NameScope()
{
var items = new object[]

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

@ -136,7 +136,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(NameScope.GetNameScope((Control)target.Child));
}
[Fact]
[Fact(Skip = "We are using name scopes from IServiceProvider now")]
public void DataTemplate_Created_Control_Should_Be_NameScope()
{
var (target, _) = CreateTarget();

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

@ -92,7 +92,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Empty(target.GetLogicalChildren());
}
[Fact]
[Fact(Skip = "We are using name scopes from IServiceProvider now")]
public void Templated_Child_Should_Be_NameScope()
{
var target = new TemplatedControl

5
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;
@ -40,6 +41,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
ElementName = "source",
Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(TextBox.TextProperty, binding);
@ -76,6 +78,7 @@ namespace Avalonia.Markup.UnitTests.Data
var binding = new Binding
{
ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(ContentControl.ContentProperty, binding);
@ -108,6 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
ElementName = "source",
Path = "Text",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(TextBox.TextProperty, binding);
@ -145,6 +149,7 @@ namespace Avalonia.Markup.UnitTests.Data
var binding = new Binding
{
ElementName = "source",
NameScope = new WeakReference<INameScope>(NameScope.GetNameScope(root))
};
target.Bind(ContentControl.ContentProperty, binding);

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

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

@ -34,7 +34,7 @@ namespace Avalonia.Styling.UnitTests
var scope = Register(root, relativeTo);
Register(root, target);
var locator = ControlLocator.Track(relativeTo, "target");
var locator = NameScopeLocator.Track(scope, "target");
var result = await locator.Take(1);
Assert.Same(target, result);
@ -65,11 +65,11 @@ namespace Avalonia.Styling.UnitTests
};
var scope = Register(root, relativeTo);
var locator = ControlLocator.Track(relativeTo, "target");
var locator = NameScopeLocator.Track(scope, "target");
var target = new TextBlock { Name = "target" };
var result = new List<ILogical>();
using (locator.Subscribe(x => result.Add(x)))
using (locator.Subscribe(x => result.Add((ILogical)x)))
{
panel.Children.Add(target);
Register(root, target);
@ -101,9 +101,9 @@ namespace Avalonia.Styling.UnitTests
var scope = Register(root, target);
Register(root, relativeTo);
var locator = ControlLocator.Track(relativeTo, "target");
var locator = NameScopeLocator.Track(scope, "target");
var result = new List<ILogical>();
locator.Subscribe(x => result.Add(x));
locator.Subscribe(x => result.Add((IControl)x));
var other = new TextBlock { Name = "target" };
panel.Children.Remove(target);
@ -114,7 +114,7 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new[] { target, null, other }, result);
}
[Fact]
[Fact(Skip = "I'm going to remove that logic anyway")]
public void Track_By_Name_Should_Find_Control_When_Tree_Changed()
{
TextBlock target1;
@ -151,10 +151,10 @@ namespace Avalonia.Styling.UnitTests
};
var scope2 = Register(root2, target2);
var locator = ControlLocator.Track(relativeTo, "target");
var locator = NameScopeLocator.Track(scope1, "target");
var result = new List<ILogical>();
using (locator.Subscribe(x => result.Add(x)))
using (locator.Subscribe(x => result.Add((ILogical)x)))
{
((StackPanel)root1.Child).Children.Remove(relativeTo);
scope1.Unregister(relativeTo.Name);
@ -174,7 +174,7 @@ namespace Avalonia.Styling.UnitTests
var scope = (TrackingNameScope)NameScope.GetNameScope(anchor);
if (scope == null)
NameScope.SetNameScope(anchor, scope = new TrackingNameScope());
NameScope.Register(anchor, element.Name, element);
scope.Register(element.Name, element);
return scope;
}

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -88,7 +88,7 @@ namespace Avalonia.UnitTests
if (element.Name != null)
{
if (scope.Find(element.Name) != element)
NameScope.Register(this, element.Name, element);
scope.Register(element.Name, element);
}
if(element is IVisual visual && (force || NameScope.GetNameScope(element) == null))

Loading…
Cancel
Save