From fe5c1cec767b36980b5a09f61b34e26198e6e3d9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 9 Jul 2019 19:08:48 +0300 Subject: [PATCH] Use INameScope from IServiceProvider --- .../XamlCompilerTaskExecutor.cs | 10 +-- .../Primitives/TemplatedControl.cs | 4 +- .../Templates/FuncControlTemplate.cs | 6 ++ .../Templates/FuncTemplate`2.cs | 10 ++- .../Templates/IControlTemplate.cs | 22 +++++- .../Templates/ITemplate`2.cs | 4 +- src/Avalonia.Styling/Controls/NameScope.cs | 15 ---- .../Controls/NameScopeLocator.cs | 79 +++++++++++++++++++ .../LogicalTree/ControlLocator.cs | 74 +---------------- .../MarkupExtensions/BindingExtension.cs | 3 +- .../Templates/ControlTemplate.cs | 4 +- .../Templates/DataTemplate.cs | 4 +- .../Templates/ItemsPanelTemplate.cs | 4 +- .../Templates/Template.cs | 4 +- .../Templates/TemplateContent.cs | 5 +- .../Templates/TreeDataTemplate.cs | 4 +- .../XamlIl/AvaloniaXamlIlRuntimeCompiler.cs | 6 +- .../AvaloniaXamlIlLanguage.cs | 21 +++++ .../Transformers/AddNameScopeRegistration.cs | 59 ++++++++++++-- .../AvaloniaXamlIlWellKnownTypes.cs | 18 +++-- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 33 +++++++- .../Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- src/Markup/Avalonia.Markup/Data/Binding.cs | 14 +++- .../Parsers/ExpressionObserverBuilder.cs | 6 +- .../Markup/Parsers/ExpressionParser.cs | 7 +- .../Markup/Parsers/Nodes/ElementNameNode.cs | 13 ++- .../ItemsControlTests.cs | 2 +- .../ContentPresenterTests_InTemplate.cs | 2 +- .../Primitives/TemplatedControlTests.cs | 2 +- .../Data/BindingTests_ElementName.cs | 5 ++ .../Xaml/BasicTests.cs | 6 +- .../ControlLocatorTests.cs | 18 ++--- tests/Avalonia.UnitTests/TestRoot.cs | 2 +- 33 files changed, 305 insertions(+), 163 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/NameScopeLocator.cs diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index c54d8e19a1..0dd52c9b48 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/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)); } diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 2927a3c7b3..47c3240374 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/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) diff --git a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs index 8a588b4089..19458cc6b8 100644 --- a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs +++ b/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); + } } } diff --git a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs index a779f796e7..9c2e77c325 100644 --- a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs @@ -34,13 +34,15 @@ namespace Avalonia.Controls.Templates /// The created control. /// 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); } } } diff --git a/src/Avalonia.Controls/Templates/IControlTemplate.cs b/src/Avalonia.Controls/Templates/IControlTemplate.cs index 408329c450..a3ef0fa954 100644 --- a/src/Avalonia.Controls/Templates/IControlTemplate.cs +++ b/src/Avalonia.Controls/Templates/IControlTemplate.cs @@ -9,7 +9,25 @@ namespace Avalonia.Controls.Templates /// /// Interface representing a template used to build a . /// - public interface IControlTemplate : ITemplate + public interface IControlTemplate : ITemplate { } -} \ No newline at end of file + + 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; + } + } +} diff --git a/src/Avalonia.Controls/Templates/ITemplate`2.cs b/src/Avalonia.Controls/Templates/ITemplate`2.cs index f61292c64b..a0c0e88eca 100644 --- a/src/Avalonia.Controls/Templates/ITemplate`2.cs +++ b/src/Avalonia.Controls/Templates/ITemplate`2.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls.Templates /// /// The type of the parameter. /// The type of control. - public interface ITemplate where TControl : IControl + public interface ITemplate { /// /// Creates the control. @@ -19,4 +19,4 @@ namespace Avalonia.Controls.Templates /// TControl Build(TParam param); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index aeafa0bb0c..afafa03772 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/src/Avalonia.Styling/Controls/NameScope.cs @@ -105,21 +105,6 @@ namespace Avalonia.Controls Registered?.Invoke(this, new NameScopeEventArgs(name, element)); } } - - /// - /// Registers an element in the name scope associated with the scopeElement. - /// Creates the scope if one isn't associated with the element yet - /// - /// - /// - /// - 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); - } /// /// Finds a named element in the name scope. diff --git a/src/Avalonia.Styling/Controls/NameScopeLocator.cs b/src/Avalonia.Styling/Controls/NameScopeLocator.cs new file mode 100644 index 0000000000..024fdb70a5 --- /dev/null +++ b/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 + { + /// + /// Tracks a named control relative to another control. + /// + /// + /// The control relative from which the other control should be found. + /// + /// The name of the control to find. + public static IObservable Track(INameScope scope, string name) + { + return new ScopeTracker(scope, name); + } + + private class ScopeTracker : LightweightObservableBase + { + 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(_name); + } + + protected override void Deinitialize() + { + if (_nameScope != null) + { + _nameScope.Registered -= Registered; + _nameScope.Unregistered -= Unregistered; + } + + _value = null; + } + + protected override void Subscribed(IObserver 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); + } + } + + } + } +} diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 7186143bf9..491f38c153 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -15,18 +15,6 @@ namespace Avalonia.LogicalTree /// public static class ControlLocator { - /// - /// Tracks a named control relative to another control. - /// - /// - /// The control relative from which the other control should be found. - /// - /// The name of the control to find. - public static IObservable Track(ILogical relativeTo, string name) - { - return new ControlTracker(relativeTo, name); - } - public static IObservable 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 { 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(_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); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 726f4221f8..a466714136 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/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(serviceProvider.GetService()) }; } diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs index e28828625f..f46ee8787b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs +++ b/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); } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs index ec5de8a059..c81a3718a7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs +++ b/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; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs index 6e4d2a3980..0dd4e57a7f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs +++ b/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(); } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs index b8fec4e8d0..5f36cfe0ac 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs +++ b/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(); } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index ead373d380..6a30c3861a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/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 direct) { - return (IControl)direct(null); + return (ControlTemplateResult)direct(null); } throw new ArgumentException(nameof(templateContent)); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs index bd2b9d2efd..0e2a131afd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs +++ b/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; } } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs index 167b75603f..b91d679fba 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs @@ -156,7 +156,7 @@ namespace Avalonia.Markup.Xaml.XamlIl { overrideField.SetValue(null, new Action( - 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>( 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; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs index b4d090e278..ea9eaee73a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/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; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs index 05548808af..d19770b8bd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs +++ b/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); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 81bbc80851..7ddd815e47 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/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); + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index c6d2ecee72..0b0e1c5c5e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/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().Parents .OfType().ToList(); var rootObject = provider.GetService().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 _parentResourceNodes; + private readonly INameScope _nameScope; public DeferredParentServiceProvider(IServiceProvider parentProvider, List 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; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index fc52295ea6..c2ec091f79 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit fc52295ea6c45f709bf63975026a4a18ce230be9 +Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022 diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 0b85e3224d..dbe5800e55 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/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 NameScope { get; set; } /// /// 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(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; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index 8cb14277ff..f957bcab1e 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/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 typeResolver = null) + internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func 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) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index a1350a8393..ed3e65feda 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs +++ b/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 _typeResolver; + private readonly INameScope _nameScope; - public ExpressionParser(bool enableValidation, Func typeResolver) + public ExpressionParser(bool enableValidation, Func 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; } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index f09efca7d0..981e93c534 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/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 _nameScope; private readonly string _name; private IDisposable _subscription; - public ElementNameNode(string name) + public ElementNameNode(INameScope nameScope, string name) { + _nameScope = new WeakReference(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() diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 6680766ffa..d5245869b3 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/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[] diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index b8b329099a..020de2880e 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/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(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index db0f2b6fb3..cd5cee1329 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/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 diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs index e2a5824102..0019b06911 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs +++ b/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(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(NameScope.GetNameScope(root)) }; target.Bind(ContentControl.ContentProperty, binding); @@ -108,6 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data { ElementName = "source", Path = "Text", + NameScope = new WeakReference(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(NameScope.GetNameScope(root)) }; target.Bind(ContentControl.ContentProperty, binding); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 720ff3b0de..d74eed992e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -296,7 +296,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; var template = AvaloniaXamlLoader.Parse(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(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); } diff --git a/tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs b/tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs index 8440652d0a..8806cad28b 100644 --- a/tests/Avalonia.Styling.UnitTests/ControlLocatorTests.cs +++ b/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(); - 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(); - 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(); - 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; } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 5ad67969c4..969d7bc821 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/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))