From 6bd0d2818c5e0c759d3a2729566ad2c0fced2689 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 13:40:10 +0200 Subject: [PATCH 1/6] Add deferred support to ResourceDictionary. --- .../Controls/ResourceDictionary.cs | 40 +++++++++++++++---- .../Controls/Templates/ITemplateResult.cs | 8 ++++ .../Controls}/Templates/TemplateResult.cs | 3 +- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Base/Controls/Templates/ITemplateResult.cs rename src/{Avalonia.Controls => Avalonia.Base/Controls}/Templates/TemplateResult.cs (80%) diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index 77863e5101..e3a8bbbcbc 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Avalonia.Collections; +using Avalonia.Controls.Templates; namespace Avalonia.Controls { @@ -29,7 +30,11 @@ namespace Avalonia.Controls public object? this[object key] { - get => _inner?[key]; + get + { + TryGetValue(key, out var value); + return value; + } set { Inner[key] = value; @@ -119,6 +124,12 @@ namespace Avalonia.Controls Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); } + public void AddDeferred(object key, Func factory) + { + Inner.Add(key, new DeferredItem(factory)); + Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); + } + public void Clear() { if (_inner?.Count > 0) @@ -143,10 +154,8 @@ namespace Avalonia.Controls public bool TryGetResource(object key, out object? value) { - if (_inner is not null && _inner.TryGetValue(key, out value)) - { + if (TryGetValue(key, out value)) return true; - } if (_mergedDictionaries != null) { @@ -165,13 +174,24 @@ namespace Avalonia.Controls public bool TryGetValue(object key, out object? value) { - if (_inner is not null) - return _inner.TryGetValue(key, out value); + if (_inner is not null && _inner.TryGetValue(key, out value)) + { + if (value is DeferredItem deffered) + { + _inner[key] = value = deffered.Factory(null) switch + { + ITemplateResult t => t.Result, + object v => v, + _ => null, + }; + } + return true; + } + value = null; return false; } - void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); @@ -258,5 +278,11 @@ namespace Avalonia.Controls } } } + + private class DeferredItem + { + public DeferredItem(Func factory) => Factory = factory; + public Func Factory { get; } + } } } diff --git a/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs new file mode 100644 index 0000000000..6bd4d735a7 --- /dev/null +++ b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Controls.Templates +{ + public interface ITemplateResult + { + public object? Result { get; } + public INameScope NameScope { get; } + } +} diff --git a/src/Avalonia.Controls/Templates/TemplateResult.cs b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs similarity index 80% rename from src/Avalonia.Controls/Templates/TemplateResult.cs rename to src/Avalonia.Base/Controls/Templates/TemplateResult.cs index 770aecc329..0e38c6c0ce 100644 --- a/src/Avalonia.Controls/Templates/TemplateResult.cs +++ b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs @@ -1,9 +1,10 @@ namespace Avalonia.Controls.Templates { - public class TemplateResult + public class TemplateResult : ITemplateResult { public T Result { get; } public INameScope NameScope { get; } + object? ITemplateResult.Result => Result; public TemplateResult(T result, INameScope nameScope) { From 5c6eeed4fae39ec31824c9dedb94183aee143e5d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 14:01:41 +0200 Subject: [PATCH 2/6] Initial support for deferred resources in XAML compiler. --- .../Controls/ResourceDictionary.cs | 18 +++- .../AvaloniaXamlIlCompiler.cs | 4 + ...aloniaXamlIlDeferredResourceTransformer.cs | 80 +++++++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 9 ++ .../Xaml/ResourceDictionaryTests.cs | 98 +++++++++++++++++++ 5 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs index e3a8bbbcbc..d6197c50c6 100644 --- a/src/Avalonia.Base/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs @@ -192,6 +192,11 @@ namespace Avalonia.Controls return false; } + public IEnumerator> GetEnumerator() + { + return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); + } + void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); @@ -218,12 +223,17 @@ namespace Avalonia.Controls return false; } - public IEnumerator> GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal bool ContainsDeferredKey(object key) { - return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator(); - } + if (_inner is not null && _inner.TryGetValue(key, out var result)) + { + return result is DeferredItem; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return false; + } void IResourceProvider.AddOwner(IResourceHost owner) { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 04a61e5f10..fac053df14 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -59,6 +59,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertAfter( new XDataTypeTransformer()); + InsertBefore( + new AvaloniaXamlIlDeferredResourceTransformer() + ); + // After everything else InsertBefore( new AddNameScopeRegistration(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs new file mode 100644 index 0000000000..099878df08 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) + return node; + + var types = context.GetAvaloniaTypes(); + + if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd), + }; + } + else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary)) + { + pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration); + pa.PossibleSetters = new List + { + new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd), + }; + } + + return node; + } + + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + { + private readonly IXamlMethod _getter; + private readonly IXamlMethod _adder; + + public AdderSetter(IXamlMethod getter, IXamlMethod adder) + { + _getter = getter; + _adder = adder; + TargetType = getter.DeclaringType; + Parameters = adder.ParametersWithThis().Skip(1).ToList(); + } + + public IXamlType TargetType { get; } + + public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters + { + AllowMultiple = true + }; + + public IReadOnlyList Parameters { get; } + public void Emit(IXamlILEmitter emitter) + { + var locals = new Stack(); + // Save all "setter" parameters + for (var c = Parameters.Count - 1; c >= 0; c--) + { + var loc = emitter.LocalsPool.GetLocal(Parameters[c]); + locals.Push(loc); + emitter.Stloc(loc.Local); + } + + emitter.EmitCall(_getter); + while (locals.Count > 0) + using (var loc = locals.Pop()) + emitter.Ldloc(loc.Local); + emitter.EmitCall(_adder, true); + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 28787d9b84..330d836f49 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -96,6 +96,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType TextDecorations { get; } public IXamlType TextTrimming { get; } public IXamlType ISetter { get; } + public IXamlType IResourceDictionary { get; } + public IXamlType ResourceDictionary { get; } + public IXamlMethod ResourceDictionaryDeferredAdd { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -210,6 +213,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); + IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); + ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); + ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, + cfg.TypeSystem.GetType("System.Func`2").MakeGenericType( + cfg.TypeSystem.GetType("System.IServiceProvider"), + XamlIlTypes.Object)); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 578fa888a3..939c5319e4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -66,6 +66,104 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Item_Is_Added_To_ResourceDictionary_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + Red +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Window_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + Red + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)window.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + Red + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)window.Resources.MergedDictionaries[0]; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Style_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" +"; + var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)style.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + + [Fact] + public void Item_Is_Added_To_Styles_Resources_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + Red + +"; + var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml); + var resources = (ResourceDictionary)style.Resources; + + Assert.True(resources.ContainsDeferredKey("Red")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From ccdc11d0accad930a11996546d4cef9af41d191b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 30 Jun 2022 15:34:48 +0200 Subject: [PATCH 3/6] Fix missing subscribe if owner is already set. --- src/Avalonia.Base/Controls/ResourceNodeExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 1758c45650..6121646107 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -132,6 +132,11 @@ namespace Avalonia.Controls { _target.OwnerChanged += OwnerChanged; _owner = _target.Owner; + + if (_owner is object) + { + _owner.ResourcesChanged += ResourcesChanged; + } } protected override void Deinitialize() From 705e9065a3b677359a9db1fadd7665dec96bb050 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 02:28:05 -0400 Subject: [PATCH 4/6] Add more tests for lazy resources --- .../Xaml/ResourceDictionaryTests.cs | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 939c5319e4..74b6a1e15d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -74,13 +74,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @" - Red + "; var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); Assert.True(resources.ContainsDeferredKey("Red")); } } + + [Fact] + public void Item_Added_To_ResourceDictionary_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.True(resources.ContainsDeferredKey("Red")); + + Assert.IsType(resources["Red"]); + + Assert.False(resources.ContainsDeferredKey("Red")); + } + } [Fact] public void Item_Is_Added_To_Window_Resources_As_Deferred() @@ -91,7 +111,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -113,7 +133,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + @@ -135,7 +155,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -154,7 +174,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - Red + "; var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -164,6 +184,60 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void Item_Can_Be_StaticReferenced_As_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.True(windowResources.ContainsDeferredKey("Red")); + Assert.True(buttonResources.ContainsDeferredKey("Red2")); + } + } + + [Fact] + public void Item_StaticReferenced_Is_UnDeferred_On_Read() + { + using (StyledWindow()) + { + var xaml = @" + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var windowResources = (ResourceDictionary)window.Resources; + var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources; + + Assert.IsType(buttonResources["Red2"]); + + Assert.False(windowResources.ContainsDeferredKey("Red")); + Assert.False(buttonResources.ContainsDeferredKey("Red2")); + } + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 6520722e5488350433e525232877e139ad57cf53 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:20:37 -0400 Subject: [PATCH 5/6] Attempt to not deferr value types --- ...valoniaXamlIlDeferredResourceTransformer.cs | 10 ++++++++++ .../Xaml/ResourceDictionaryTests.cs | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 099878df08..662263e513 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -15,6 +15,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2) return node; + if (!ShouldBeDeferred(pa.Values[1])) + return node; + var types = context.GetAvaloniaTypes(); if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content") @@ -37,6 +40,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + private static bool ShouldBeDeferred(IXamlAstValueNode node) + { + // XAML compiler is currently strict about value types, allowing them to be created only through converters. + // At the moment it should be safe to not defer structs. + return !node.Type.GetClrType().IsValueType; + } + class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _getter; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 74b6a1e15d..19fb88b391 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -237,6 +238,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.False(buttonResources.ContainsDeferredKey("Red2")); } } + + [Fact] + public void Value_Type_With_Parse_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + Red +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Red")); + Assert.IsType(resources["Red"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) { From 41d31d8857dec976aa293d8070ea69296f84c2d1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 3 Aug 2022 03:53:01 -0400 Subject: [PATCH 6/6] Add thickness test as well --- .../Xaml/ResourceDictionaryTests.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 19fb88b391..5066341bbb 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -240,7 +240,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Value_Type_With_Parse_Should_Not_Be_Deferred() + public void Value_Type_With_Parse_Converter_Should_Not_Be_Deferred() { using (StyledWindow()) { @@ -255,6 +255,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.IsType(resources["Red"]); } } + + [Fact] + public void Value_Type_With_Ctor_Converter_Should_Not_Be_Deferred() + { + using (StyledWindow()) + { + var xaml = @" + + 1 1 1 1 +"; + var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.False(resources.ContainsDeferredKey("Margin")); + Assert.IsType(resources["Margin"]); + } + } private IDisposable StyledWindow(params (string, string)[] assets) {