diff --git a/build/Base.props b/build/Base.props index e565ab1664..a60373ebb3 100644 --- a/build/Base.props +++ b/build/Base.props @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index c2c0cd4301..9c72321472 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -150,6 +150,7 @@ + diff --git a/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs new file mode 100644 index 0000000000..f321b2b2f1 --- /dev/null +++ b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities +{ + /// + /// A task-like operation that is guaranteed to finish continuations synchronously, + /// can be used for parametrized one-shot events + /// + public struct SynchronousCompletionAsyncResult : INotifyCompletion + { + private readonly SynchronousCompletionAsyncResultSource _source; + private readonly T _result; + private readonly bool _isValid; + internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource 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 GetAwaiter() => this; + } + + /// + /// Source for incomplete SynchronousCompletionAsyncResult + /// + /// + public class SynchronousCompletionAsyncResultSource + { + private T _result; + internal bool IsCompleted { get; private set; } + public SynchronousCompletionAsyncResult AsyncResult => new SynchronousCompletionAsyncResult(this); + + internal T Result => IsCompleted ? + _result : + throw new InvalidOperationException("Asynchronous operation is not yet completed"); + + private List _continuations; + + internal void OnCompleted(Action continuation) + { + if(_continuations==null) + _continuations = new List(); + _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); + } + } +} 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/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index b87e10d284..473c4fe21b 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/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); diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 83d1c4aae3..2d48a7d33b 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/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 Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - public event EventHandler 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(); } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs index c4f83ffd54..d326ab5734 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs +++ b/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 Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - public event EventHandler 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() diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 304c86dbf7..43d1108fb9 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/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; } } diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 49f268c128..c2690d503d 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/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 diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 32e220b789..47c3240374 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/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 } } } - - /// - /// Registers each control with its name scope. - /// - /// The control. - /// The name scope. - 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); - } - } - } } } diff --git a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs index 4c3c92309e..19458cc6b8 100644 --- a/src/Avalonia.Controls/Templates/FuncControlTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncControlTemplate.cs @@ -16,9 +16,15 @@ namespace Avalonia.Controls.Templates /// Initializes a new instance of the class. /// /// The build function. - public FuncControlTemplate(Func build) + public FuncControlTemplate(Func build) : base(build) { } + + public new ControlTemplateResult Build(ITemplatedControl param) + { + var (control, scope) = BuildWithNameScope(param); + return new ControlTemplateResult(control, scope); + } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs index 8e49b51cb8..eec7a6030f 100644 --- a/src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs @@ -17,9 +17,9 @@ namespace Avalonia.Controls.Templates /// Initializes a new instance of the class. /// /// The build function. - public FuncControlTemplate(Func build) - : base(x => build((T)x)) + public FuncControlTemplate(Func build) + : base((x, s) => build((T)x, s)) { } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index 1d90fcd01e..204540e431 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.Templates /// public static readonly FuncDataTemplate Default = new FuncDataTemplate( - data => + (data, s) => { if (data != null) { @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Templates /// Whether the control can be recycled. public FuncDataTemplate( Type type, - Func build, + Func build, bool supportsRecycling = false) : this(o => IsInstance(o, type), build, supportsRecycling) { @@ -67,7 +67,7 @@ namespace Avalonia.Controls.Templates /// Whether the control can be recycled. public FuncDataTemplate( Func match, - Func build, + Func build, bool supportsRecycling = false) : base(build) { @@ -105,4 +105,4 @@ namespace Avalonia.Controls.Templates return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs index 9339aa6924..68b737928d 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Templates /// A function which when passed an object of returns a control. /// /// Whether the control can be recycled. - public FuncDataTemplate(Func build, bool supportsRecycling = false) + public FuncDataTemplate(Func build, bool supportsRecycling = false) : base(typeof(T), CastBuild(build), supportsRecycling) { } @@ -35,12 +35,30 @@ namespace Avalonia.Controls.Templates /// Whether the control can be recycled. public FuncDataTemplate( Func match, - Func build, + Func build, bool supportsRecycling = false) : base(CastMatch(match), CastBuild(build), supportsRecycling) { } + /// + /// Initializes a new instance of the class. + /// + /// + /// A function which determines whether the data template matches the specified data. + /// + /// + /// A function which when passed an object of returns a control. + /// + /// Whether the control can be recycled. + public FuncDataTemplate( + Func match, + Func build, + bool supportsRecycling = false) + : this(match, (a, _) => build(a), supportsRecycling) + { + } + /// /// Casts a strongly typed match function to a weakly typed one. /// @@ -57,9 +75,9 @@ namespace Avalonia.Controls.Templates /// The strong data type. /// The strongly typed function. /// The weakly typed function. - private static Func CastBuild(Func f) + private static Func CastBuild(Func f) { - return o => f((T)o); + return (o, s) => f((T)o, s); } } } diff --git a/src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs b/src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs new file mode 100644 index 0000000000..76bc298e30 --- /dev/null +++ b/src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs @@ -0,0 +1,14 @@ +using System; + +namespace Avalonia.Controls.Templates +{ + public static class FuncTemplateNameScopeExtensions + { + public static T RegisterInNameScope(this T control, INameScope scope) + where T : StyledElement + { + scope.Register(control.Name, control); + return control; + } + } +} diff --git a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs index b1ce63fdf1..e0608d0471 100644 --- a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs @@ -13,13 +13,13 @@ namespace Avalonia.Controls.Templates public class FuncTemplate : ITemplate where TControl : IControl { - private readonly Func _func; + private readonly Func _func; /// /// Initializes a new instance of the class. /// /// The function used to create the control. - public FuncTemplate(Func func) + public FuncTemplate(Func func) { Contract.Requires(func != null); @@ -35,7 +35,14 @@ namespace Avalonia.Controls.Templates /// 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); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs index e7c9cf8608..998ef0e9f2 100644 --- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Templates /// public FuncTreeDataTemplate( Type type, - Func build, + Func build, Func itemsSelector) : this(o => IsInstance(o, type), build, itemsSelector) { @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Templates /// public FuncTreeDataTemplate( Func match, - Func build, + Func build, Func itemsSelector) : base(match, build) { diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs index 4ca96f60bd..41f870ab42 100644 --- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls.Templates /// items. /// public FuncTreeDataTemplate( - Func build, + Func build, Func itemsSelector) : base( typeof(T), @@ -46,7 +46,7 @@ namespace Avalonia.Controls.Templates /// public FuncTreeDataTemplate( Func match, - Func build, + Func build, Func itemsSelector) : base( CastMatch(match), @@ -65,6 +65,17 @@ namespace Avalonia.Controls.Templates return o => (o is T) && f((T)o); } + /// + /// Casts a function with a typed parameter to an untyped function. + /// + /// The result. + /// The typed function. + /// The untyped function. + private static Func Cast(Func f) + { + return (o, s) => f((T)o, s); + } + /// /// Casts a function with a typed parameter to an untyped function. /// 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.Controls/UserControl.cs b/src/Avalonia.Controls/UserControl.cs index e42ca5e0e6..4f15839821 100644 --- a/src/Avalonia.Controls/UserControl.cs +++ b/src/Avalonia.Controls/UserControl.cs @@ -9,40 +9,8 @@ namespace Avalonia.Controls /// /// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic. /// - public class UserControl : ContentControl, IStyleable, INameScope + public class UserControl : ContentControl, IStyleable { - private readonly NameScope _nameScope = new NameScope(); - /// - event EventHandler INameScope.Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - /// - event EventHandler INameScope.Unregistered - { - add { _nameScope.Unregistered += value; } - remove { _nameScope.Unregistered -= value; } - } - - /// - void INameScope.Register(string name, object element) - { - _nameScope.Register(name, element); - } - - /// - object INameScope.Find(string name) - { - return _nameScope.Find(name); - } - - /// - void INameScope.Unregister(string name) - { - _nameScope.Unregister(name); - } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 5c117f508b..d2793fe0dd 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls /// /// A top-level window. /// - public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope + public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot { /// /// Defines the property. @@ -157,20 +157,6 @@ namespace Avalonia.Controls _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); } - /// - event EventHandler INameScope.Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - /// - event EventHandler INameScope.Unregistered - { - add { _nameScope.Unregistered += value; } - remove { _nameScope.Unregistered -= value; } - } - /// /// Gets the platform-specific window implementation. /// @@ -494,24 +480,6 @@ namespace Avalonia.Controls } } - /// - void INameScope.Register(string name, object element) - { - _nameScope.Register(name, element); - } - - /// - object INameScope.Find(string name) - { - return _nameScope.Find(name); - } - - /// - void INameScope.Unregister(string name) - { - _nameScope.Unregister(name); - } - /// protected override Size MeasureOverride(Size availableSize) { diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs index 3f41f54363..4881c77034 100644 --- a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -16,7 +16,7 @@ namespace Avalonia.ReactiveUI /// public class AutoDataTemplateBindingHook : IPropertyBindingHook { - private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate(x => + private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate((x, _) => { var control = new ViewModelViewHost(); var context = control.GetObservable(Control.DataContextProperty); @@ -52,4 +52,4 @@ namespace Avalonia.ReactiveUI return true; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Styling/Controls/ChildNameScope.cs b/src/Avalonia.Styling/Controls/ChildNameScope.cs new file mode 100644 index 0000000000..e6707e71db --- /dev/null +++ b/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 FindAsync(string name) + { + var found = Find(name); + if (found != null) + return new SynchronousCompletionAsyncResult(found); + // Not found and both current and parent scope are in completed state + if(IsCompleted) + return new SynchronousCompletionAsyncResult(null); + return DoFindAsync(name); + } + + public SynchronousCompletionAsyncResult DoFindAsync(string name) + { + var src = new SynchronousCompletionAsyncResultSource(); + + 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; + } +} diff --git a/src/Avalonia.Styling/Controls/INameScope.cs b/src/Avalonia.Styling/Controls/INameScope.cs index e4ff90c573..b4f4c8e57b 100644 --- a/src/Avalonia.Styling/Controls/INameScope.cs +++ b/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 /// public interface INameScope { - /// - /// Raised when an element is registered with the name scope. - /// - event EventHandler Registered; - - /// - /// Raised when an element is unregistered with the name scope. - /// - event EventHandler Unregistered; - /// /// Registers an element in the name scope. /// @@ -28,16 +20,30 @@ namespace Avalonia.Controls void Register(string name, object element); /// - /// 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. + /// + /// The name. + /// The element, or null if the name was not found. + SynchronousCompletionAsyncResult FindAsync(string name); + + /// + /// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack /// /// The name. /// The element, or null if the name was not found. object Find(string name); /// - /// Unregisters an element with the name scope. + /// Marks the name scope as completed, no further registrations will be allowed /// - /// The name. - void Unregister(string name); + void Complete(); + + /// + /// Returns whether further registrations are allowed on the scope + /// + bool IsCompleted { get; } + + } } diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index e3a29af541..ec01a53cfd 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/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 NameScopeProperty = AvaloniaProperty.RegisterAttached("NameScope"); + /// + public bool IsCompleted { get; private set; } + private readonly Dictionary _inner = new Dictionary(); - /// - /// Raised when an element is registered with the name scope. - /// - public event EventHandler Registered; - - /// - /// Raised when an element is unregistered with the name scope. - /// - public event EventHandler Unregistered; - - /// - /// Finds the containing name scope for a styled element. - /// - /// The styled element. - /// The containing name scope. - public static INameScope FindNameScope(StyledElement styled) - { - Contract.Requires(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> _pendingSearches = + new Dictionary>(); + /// /// Gets the value of the attached on a styled element. /// @@ -80,13 +52,11 @@ namespace Avalonia.Controls styled.SetValue(NameScopeProperty, value); } - /// - /// Registers an element with the name scope. - /// - /// The element name. - /// The element. + /// public void Register(string name, object element) { + if (IsCompleted) + throw new InvalidOperationException("NameScope is completed, no further registrations are allowed"); Contract.Requires(name != null); Contract.Requires(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); + } } } - /// - /// Finds a named element in the name scope. - /// - /// The name. - /// The element, or null if the name was not found. + public SynchronousCompletionAsyncResult FindAsync(string name) + { + var found = Find(name); + if (found != null) + return new SynchronousCompletionAsyncResult(found); + if (IsCompleted) + return new SynchronousCompletionAsyncResult((object)null); + if (!_pendingSearches.TryGetValue(name, out var tcs)) + // We are intentionally running continuations synchronously here + _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource(); + + return tcs.AsyncResult; + } + + /// public object Find(string name) { Contract.Requires(name != null); @@ -120,21 +104,14 @@ namespace Avalonia.Controls return result; } - /// - /// Unregisters an element with the name scope. - /// - /// The name. - public void Unregister(string name) + public void Complete() { - Contract.Requires(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(); } + + } } diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs index 991a97a614..5921acd3b6 100644 --- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs @@ -37,6 +37,25 @@ namespace Avalonia.Controls return (T)result; } + /// + /// Finds a named element in an . + /// + /// The element type. + /// The control to take the name scope from. + /// The name. + /// The named element or null if not found. + public static T Find(this ILogical anchor, string name) + where T : class + { + Contract.Requires(anchor != null); + Contract.Requires(name != null); + var styledAnchor = anchor as StyledElement; + if (styledAnchor == null) + return null; + var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope(styledAnchor); + return nameScope?.Find(name); + } + /// /// Gets a named element from an or throws if no element of the /// requested name was found. @@ -67,6 +86,28 @@ namespace Avalonia.Controls return (T)result; } + /// + /// Gets a named element from an or throws if no element of the + /// requested name was found. + /// + /// The element type. + /// The control to take the name scope from. + /// The name. + /// The named element. + public static T Get(this ILogical anchor, string name) + where T : class + { + Contract.Requires(anchor != null); + Contract.Requires(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(name); + } + public static INameScope FindNameScope(this ILogical control) { Contract.Requires(control != null); diff --git a/src/Avalonia.Styling/Controls/NameScopeLocator.cs b/src/Avalonia.Styling/Controls/NameScopeLocator.cs new file mode 100644 index 0000000000..719cf9344b --- /dev/null +++ b/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 + { + /// + /// 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 NeverEndingSynchronousCompletionAsyncResultObservable(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 : IObservable + { + private T _value; + private SynchronousCompletionAsyncResult? _asyncResult; + + public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult task) + { + if (task.IsCompleted) + _value = task.GetResult(); + else + _asyncResult = task; + } + + public IDisposable Subscribe(IObserver 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; + } + } + } +} 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/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 146a4c75e7..b4aa89d5bf 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -61,7 +61,6 @@ namespace Avalonia private readonly Classes _classes = new Classes(); private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; - private INameScope _nameScope; private IResourceDictionary _resources; private Styles _styles; private bool _styled; @@ -82,7 +81,6 @@ namespace Avalonia /// 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()?.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); diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 3702259f35..9312d38c51 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/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; } 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 c25e1186d0..ea9eaee73a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/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; 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 33056fa3e8..805b733feb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs +++ b/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().Any()) + && mg.Children.OfType().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); + } + } } 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 c054e57380..1efae902c6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/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); + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index 2d8ea643ac..747b21c679 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,14 @@ 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 parentScope = sp.GetService(); + 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 _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 +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; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 894b2c0282..c2ec091f79 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 894b2c02827fd5eb16a338de5d5b6c9fbc60fef5 +Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022 diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs index 06cc85101a..5ef3dc9753 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs @@ -10,7 +10,15 @@ namespace Avalonia.Markup.Xaml public interface IRootObjectProvider { + /// + /// The root object of the xaml file + /// object RootObject { get; } + /// + /// The "current" root object, contains either the root of the xaml file + /// or the root object of the control/data template + /// + object IntermediateRootObject { get; } } public interface IUriContext 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/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index b10929cbdc..015a122677 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -1012,23 +1012,23 @@ namespace Avalonia.Controls.UnitTests } private IControlTemplate CreateTemplate() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((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); diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs index 515e18434d..b16ac6bb8e 100644 --- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs +++ b/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); } } } diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 70ec6c1408..599e214b31 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate GetTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((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) } }; }); diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index c17893604c..93355a22f2 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -126,7 +126,7 @@ namespace Avalonia.Controls.UnitTests var target = new ContentControl { Template = GetTemplate(), - ContentTemplate = new FuncDataTemplate(_ => new Canvas()), + ContentTemplate = new FuncDataTemplate((_, __) => new Canvas()), }; target.Content = "Foo"; @@ -302,8 +302,8 @@ namespace Avalonia.Controls.UnitTests var target = new ContentControl { - Template = new FuncControlTemplate(_ => presenter), - ContentTemplate = new FuncDataTemplate(x => new Canvas()), + Template = new FuncControlTemplate((_, __) => presenter), + ContentTemplate = new FuncDataTemplate((_, __) => new Canvas()), Content = "foo", }; @@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate GetTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((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) }; }); } diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index 936d700ad0..7de80f72cc 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs @@ -93,28 +93,28 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTemplate() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((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); diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index a1ba608ec6..a790d2fca1 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/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("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("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("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("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)); } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs index 66789ef874..fcf7bf3c8c 100644 --- a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs +++ b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs @@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate GetTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((parent, scope) => { return new Border { @@ -71,7 +71,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_HeaderPresenter", [~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty], - } + }.RegisterInNameScope(scope) }; }); } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 3cf886ade4..2599d24354 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests var target = new ItemsControl { Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), }; target.Items = new[] { "Foo" }; @@ -411,7 +411,7 @@ namespace Avalonia.Controls.UnitTests DataContext = "Base", DataTemplates = { - new FuncDataTemplate(x => new Button { Content = x }) + new FuncDataTemplate((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(parent => + return new FuncControlTemplate((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) }; }); } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 238e214a5d..9a459328aa 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests { Template = ListBoxTemplate(), Items = new[] { "Foo" }, - ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), }; Prepare(target); @@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests DataContext = "Base", DataTemplates = { - new FuncDataTemplate(x => new Button { Content = x }) + new FuncDataTemplate((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(x => new TextBlock { Height = 10 }), + ItemTemplate = new FuncDataTemplate((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(x => new TextBlock { Width = 20, Height = 10 }), + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), SelectedIndex = 0, }; @@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests { Template = ListBoxTemplate(), Items = items, - ItemTemplate = new FuncDataTemplate(x => new TextBlock { Width = 20, Height = 10 }), + ItemTemplate = new FuncDataTemplate((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(x => new TextBlock { Height = 10 }) + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }) }; Prepare(target); @@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests { Template = ListBoxTemplate(), Items = items, - ItemTemplate = new FuncDataTemplate(x => new TextBlock { Height = 11 }) + ItemTemplate = new FuncDataTemplate((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(c => + target.ItemTemplate = new FuncDataTemplate((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(parent => + return new FuncControlTemplate((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(parent => + return new FuncControlTemplate((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(parent => + return new FuncControlTemplate((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) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index de34558ad1..2a61ff1566 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/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) diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs index f06553411c..638443e17f 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs +++ b/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) } }) }; diff --git a/tests/Avalonia.Controls.UnitTests/NameScopeTests.cs b/tests/Avalonia.Controls.UnitTests/NameScopeTests.cs index 802f7717e8..0fdcd3fd34 100644 --- a/tests/Avalonia.Controls.UnitTests/NameScopeTests.cs +++ b/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(() => 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(() => target.Register("foo", new object())); + target.Register("foo", element); + target.Complete(); + Assert.Throws(() => 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")); } + } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index 708e934214..7d05547799 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/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(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(_ => new Canvas()); + target.ContentTemplate = new FuncDataTemplate((_, __) => new Canvas()); target.Content = "Foo"; Assert.IsType(target.Child); @@ -184,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = "Foo"; Assert.IsType(target.Child); - target.ContentTemplate = new FuncDataTemplate(_ => new Canvas()); + target.ContentTemplate = new FuncDataTemplate((_, __) => new Canvas()); Assert.IsType(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(_ => new Border(), true)); + target.DataTemplates.Add(new FuncDataTemplate((_, __) => 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(_ => new Border(), false)); + target.DataTemplates.Add(new FuncDataTemplate((_, __) => new Border(), false)); target.Content = "foo"; @@ -256,7 +245,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var (target, _) = CreateTarget(); target.DataTemplates.Add(new FuncDataTemplate(x => x == "bar", _ => new Canvas(), true)); - target.DataTemplates.Add(new FuncDataTemplate(_ => new Border(), true)); + target.DataTemplates.Add(new FuncDataTemplate((_, __) => new Border(), true)); target.Content = "foo"; @@ -278,8 +267,8 @@ namespace Avalonia.Controls.UnitTests.Presenters }; var (target, host) = CreateTarget(); - host.DataTemplates.Add(new FuncDataTemplate(x => textBlock)); - host.DataTemplates.Add(new FuncDataTemplate(x => new Canvas())); + host.DataTemplates.Add(new FuncDataTemplate((_, __) => textBlock)); + host.DataTemplates.Add(new FuncDataTemplate((_, __) => 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(x => + Template = new FuncControlTemplate((_, s) => new ContentPresenter { Name = "PART_ContentPresenter", - }), + }.RegisterInNameScope(s)), }; var root = new TestRoot { Child = templatedParent }; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 2facee16b7..ab75a87110 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = new ContentPresenter { ContentTemplate = - new FuncDataTemplate(t => new ContentControl() { Content = t }, false) + new FuncDataTemplate((t, _) => new ContentControl() { Content = t }, false) }; var parentMock = new Mock(); @@ -93,14 +93,14 @@ namespace Avalonia.Controls.UnitTests.Presenters { var contentControl = new ContentControl { - Template = new FuncControlTemplate(c => new ContentPresenter() + Template = new FuncControlTemplate((c, scope) => new ContentPresenter() { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty], [~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty] - }), + }.RegisterInNameScope(scope)), ContentTemplate = - new FuncDataTemplate(t => new ContentControl() { Content = t }, false) + new FuncDataTemplate((t, _) => new ContentControl() { Content = t }, false) }; var parentMock = new Mock(); @@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = new ContentPresenter { ContentTemplate = - new FuncDataTemplate(t => new ContentControl() { Content = t }, false) + new FuncDataTemplate((t, _) => new ContentControl() { Content = t }, false) }; var parentMock = new Mock(); @@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = new ContentPresenter { ContentTemplate = - new FuncDataTemplate(t => new ContentControl() { Content = t }, false) + new FuncDataTemplate((t, _) => new ContentControl() { Content = t }, false) }; target.Content = "foo"; @@ -235,8 +235,8 @@ namespace Avalonia.Controls.UnitTests.Presenters { DataTemplates = { - new FuncDataTemplate(x => textBlock), - new FuncDataTemplate(x => new Canvas()), + new FuncDataTemplate((x, _) => textBlock), + new FuncDataTemplate((x, _) => new Canvas()), }, }; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs index 5268c9ac0d..09970926fa 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs @@ -90,7 +90,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { DataTemplates = { - new FuncDataTemplate(x => new Decorator()), + new FuncDataTemplate((x, _) => new Decorator()), }, }; @@ -99,4 +99,4 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.IsType(target.Child); } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index 3d13e4c32f..7ca11d9bed 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -220,7 +220,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { VirtualizationMode = ItemVirtualizationMode.None, Items = items, - ItemTemplate = new FuncDataTemplate(x => new TextBlock { Height = 10 }), + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }), }; target.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 3ea32ed719..269da884ba 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -309,7 +309,7 @@ namespace Avalonia.Controls.UnitTests.Presenters private static IDataTemplate ItemTemplate() { - return new FuncDataTemplate(x => new Canvas + return new FuncDataTemplate((x, _) => new Canvas { Width = 10, Height = 10, diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 97d57e9eb6..7a6cf0fba7 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/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(x => new TextBlock { Height = 10 }), + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }), }; target.ApplyTemplate(); @@ -1046,7 +1046,7 @@ namespace Avalonia.Controls.UnitTests.Presenters private static IDataTemplate StringDataTemplate() { - return new FuncDataTemplate(x => new Canvas + return new FuncDataTemplate((x, _) => new Canvas { Width = 10, Height = 10, @@ -1095,12 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters { public TestContainer() { - Template = new FuncControlTemplate(parent => new ContentPresenter + Template = new FuncControlTemplate((parent, scope) => new ContentPresenter { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], - }); + }.RegisterInNameScope(scope)); } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs index d4b5d01a6b..059146f17d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs @@ -134,12 +134,12 @@ namespace Avalonia.Controls.UnitTests.Primitives { var result = new PopupRoot { - Template = new FuncControlTemplate(parent => + Template = new FuncControlTemplate((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(parent => + Template = new FuncControlTemplate((parent, _) => new Popup { [!Popup.ChildProperty] = parent[!TemplatedControlWithPopup.PopupContentProperty], diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index bd11cf2e3c..e266150901 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/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 diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index d913e3e54f..2eeff4cdf9 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -111,7 +111,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var target = new TestRange() { - Template = new FuncControlTemplate(c => + Template = new FuncControlTemplate((c, scope) => { track = new Track() { @@ -122,7 +122,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Name = "PART_Track", Thumb = new Thumb() - }; + }.RegisterInNameScope(scope); if (useXamlBinding) { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs index 672b5c608b..2372987c4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs +++ b/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(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 }; } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 75cababf54..bc002174ec 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -896,13 +896,13 @@ namespace Avalonia.Controls.UnitTests.Primitives private FuncControlTemplate Template() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((control, scope) => new ItemsPresenter { Name = "itemsPresenter", [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], - }); + }.RegisterInNameScope(scope)); } private class Item : Control, ISelectable diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs index 88cb726469..8618387150 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs @@ -84,13 +84,13 @@ namespace Avalonia.Controls.UnitTests.Primitives private FuncControlTemplate Template() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((control, scope) => new ItemsPresenter { Name = "itemsPresenter", [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], - }); + }.RegisterInNameScope(scope)); } private class TestSelector : SelectingItemsControl diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index a33d97779e..9d0cc368e0 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/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(x => new TextBlock { Width = 20, Height = 10 }), + ItemTemplate = new FuncDataTemplate((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(x => new TextBlock { Width = 20, Height = 10 }), + ItemTemplate = new FuncDataTemplate((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(x => new TextBlock { Width = 20, Height = 10 }), + ItemTemplate = new FuncDataTemplate((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(control => + return new FuncControlTemplate((control, scope) => new ItemsPresenter { Name = "PART_ItemsPresenter", [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty], [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], - }); + }.RegisterInNameScope(scope)); } private class TestSelector : SelectingItemsControl diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs index 55b3d6f756..a7b90afa70 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TabStripTests.cs +++ b/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); } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 166586ace1..d36d0b609b 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/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(ScrollingContentControlTemplate), - Content = "foo" - }; - - var root = new TestRoot { Child = target }; - target.ApplyTemplate(); - - var border = target.GetVisualChildren().FirstOrDefault(); - Assert.IsType(border); - - var scrollViewer = border.GetVisualChildren().FirstOrDefault(); - Assert.IsType(scrollViewer); - ((ScrollViewer)scrollViewer).ApplyTemplate(); - - var scrollContentPresenter = scrollViewer.GetVisualChildren().FirstOrDefault(); - Assert.IsType(scrollContentPresenter); - ((ContentPresenter)scrollContentPresenter).UpdateChild(); - - var contentPresenter = scrollContentPresenter.GetVisualChildren().FirstOrDefault(); - Assert.IsType(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; } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index d1385176c5..75a2f4178b 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/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), }, }; } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index e00084eba4..ee8d9cc62e 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -129,7 +129,7 @@ namespace Avalonia.Controls.UnitTests }, }; - var template = new FuncControlTemplate(x => new Decorator()); + var template = new FuncControlTemplate((x, __) => new Decorator()); using (UnitTestApplication.Start(TestServices.RealStyler)) { @@ -176,7 +176,7 @@ namespace Avalonia.Controls.UnitTests DataContext = "Base", DataTemplates = { - new FuncDataTemplate(x => new Button { Content = x }) + new FuncDataTemplate((x, __) => new Button { Content = x }) }, Items = items, }; @@ -273,7 +273,7 @@ namespace Avalonia.Controls.UnitTests TabControl target = new TabControl { Template = TabControlTemplate(), - ContentTemplate = new FuncDataTemplate(x => + ContentTemplate = new FuncDataTemplate((x, _) => new TextBlock { Tag = "bar", Text = x }), Items = new[] { "Foo" }, }; @@ -289,7 +289,7 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate TabControlTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((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(parent => + return new FuncControlTemplate((parent, scope) => new ContentPresenter { Name = "PART_ContentPresenter", [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty], [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty] - }); + }.RegisterInNameScope(scope)); } private void ApplyTemplate(TabControl target) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 932aada64e..f40571bc39 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -456,7 +456,7 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTemplate() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((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) diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 4d79dd557e..4aaf0ab5b7 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs @@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTemplate() { - return new FuncControlTemplate(control => + return new FuncControlTemplate((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 diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index f112289460..0ee772425b 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -226,12 +226,12 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate CreateTemplate() { - return new FuncControlTemplate(x => + return new FuncControlTemplate((x, scope) => new ContentPresenter { Name = "PART_ContentPresenter", [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], - }); + }.RegisterInNameScope(scope)); } private class TestTopLevel : TopLevel diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index b66d6ed11c..2868928455 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTreeViewTemplate(), Items = CreateTestTreeData(), ItemTemplate = new FuncTreeDataTemplate( - _ => new Canvas(), + (_, __) => new Canvas(), x => x.Children), } }; @@ -475,7 +475,7 @@ namespace Avalonia.Controls.UnitTests DataContext = "Base", DataTemplates = { - new FuncDataTemplate(x => new Button { Content = x }) + new FuncDataTemplate((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(parent => new ItemsPresenter + return new FuncControlTemplate((parent, scope) => new ItemsPresenter { Name = "PART_ItemsPresenter", [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], - }); + }.RegisterInNameScope(scope)); } private IControlTemplate CreateTreeViewItemTemplate() { - return new FuncControlTemplate(parent => new Panel + return new FuncControlTemplate((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) } }); } diff --git a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs index 6da771217c..9d3e568582 100644 --- a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs @@ -40,7 +40,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate GetTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((parent, scope) => { return new Border { @@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], - } + }.RegisterInNameScope(scope) }; }); } diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 641979638a..df522397ee 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -59,13 +59,13 @@ namespace Avalonia.Controls.UnitTests.Utils private FuncControlTemplate CreateWindowTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate((parent, scope) => { return new ContentPresenter { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], - }; + }.RegisterInNameScope(scope); }); } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 12d29f2e5b..3ee6a50e69 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -253,12 +253,12 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate CreateTemplate() { - return new FuncControlTemplate(x => + return new FuncControlTemplate((x, scope) => new ContentPresenter { Name = "PART_ContentPresenter", [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], - }); + }.RegisterInNameScope(scope)); } private class TestWindowBase : WindowBase diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index cd25b79f1f..a841174d2d 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -67,13 +67,15 @@ namespace Avalonia.LeakTests { Func 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( - x => new TextBlock { Text = x.Name }, + (x, _) => new TextBlock { Text = x.Name }, x => x.Children) }, Items = nodes diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_ElementName.cs index 61df0bffdf..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; @@ -34,10 +35,13 @@ namespace Avalonia.Markup.UnitTests.Data } }; + root.RegisterChildrenNames(); + var binding = new Binding { ElementName = "source", Path = "Text", + NameScope = new WeakReference(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(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(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(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); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs index d3e3ce5507..ee95463f1f 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs +++ b/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] diff --git a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs index ef3864abd7..51cb75a867 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.UnitTests.Data { var source = new Button { - Template = new FuncControlTemplate