From 0b0667f5eee9dff00321b4cee1a948335535f840 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 18:33:46 +0200 Subject: [PATCH] Delay load static resources if necessary. --- .../Data/DelayedBinding.cs | 85 +++++++++++++++++-- .../StaticResourceExtension.cs | 18 ++++ .../Xaml/StaticResourceTests.cs | 27 ++++++ 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs index 083cb24b11..7b2a9181ae 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Logging; namespace Avalonia.Markup.Xaml.Data { @@ -50,7 +52,36 @@ namespace Avalonia.Markup.Xaml.Data target.Initialized += ApplyBindings; } - bindings.Add(new Entry(binding, property)); + bindings.Add(new BindingEntry(property, binding)); + } + } + + /// + /// Adds a delayed binding to a control. + /// + /// The control. + /// The property on the control to bind to. + /// The binding. + public static void Add(IControl target, PropertyInfo property, Func value) + { + if (target.IsInitialized) + { + property.SetValue(target, value(target)); + } + else + { + List bindings; + + if (!_entries.TryGetValue(target, out bindings)) + { + bindings = new List(); + _entries.Add(target, bindings); + + // TODO: Make this a weak event listener. + target.Initialized += ApplyBindings; + } + + bindings.Add(new ClrPropertyValueEntry(property, value)); } } @@ -60,13 +91,13 @@ namespace Avalonia.Markup.Xaml.Data /// The control. public static void ApplyBindings(IControl control) { - List bindings; + List entries; - if (_entries.TryGetValue(control, out bindings)) + if (_entries.TryGetValue(control, out entries)) { - foreach (var binding in bindings) + foreach (var entry in entries) { - control.Bind(binding.Property, binding.Binding); + entry.Apply(control); } _entries.Remove(control); @@ -80,9 +111,14 @@ namespace Avalonia.Markup.Xaml.Data target.Initialized -= ApplyBindings; } - private class Entry + private abstract class Entry + { + public abstract void Apply(IControl control); + } + + private class BindingEntry : Entry { - public Entry(IBinding binding, AvaloniaProperty property) + public BindingEntry(AvaloniaProperty property, IBinding binding) { Binding = binding; Property = property; @@ -90,6 +126,41 @@ namespace Avalonia.Markup.Xaml.Data public IBinding Binding { get; } public AvaloniaProperty Property { get; } + + public override void Apply(IControl control) + { + control.Bind(Property, Binding); + } + } + + private class ClrPropertyValueEntry : Entry + { + public ClrPropertyValueEntry(PropertyInfo property, Func value) + { + Property = property; + Value = value; + } + + public PropertyInfo Property { get; } + public Func Value { get; } + + public override void Apply(IControl control) + { + try + { + Property.SetValue(control, Value(control)); + } + catch (Exception e) + { + Logger.Error( + LogArea.Property, + control, + "Error setting {Property} on {Target}: {Exception}", + Property.Name, + control, + e); + } + } } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 44c4af5338..3508128691 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -3,7 +3,9 @@ using System; using System.ComponentModel; +using System.Reflection; using Avalonia.Controls; +using Avalonia.Markup.Xaml.Data; using Avalonia.Styling; using Portable.Xaml; using Portable.Xaml.ComponentModel; @@ -32,6 +34,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceProvider)); var resourceProviders = ambientProvider.GetAllAmbientValues(resourceProviderType); + // Look up the ambient context for IResourceProviders which might be able to give us + // the resource. foreach (IResourceProvider resourceProvider in resourceProviders) { if (resourceProvider is IControl control && control.StylingParent != null) @@ -47,7 +51,21 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions } } + // The resource still hasn't been found, so add a delayed one-time binding. + var provideTarget = context.GetService(); + + if (provideTarget.TargetObject is IControl target && + provideTarget.TargetProperty is PropertyInfo property) + { + DelayedBinding.Add(target, property, GetValue); + } + return AvaloniaProperty.UnsetValue; } + + private object GetValue(IControl control) + { + return control.FindResource(ResourceKey); + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StaticResourceTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StaticResourceTests.cs index 10532ab6c0..b5c185d96e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StaticResourceTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StaticResourceTests.cs @@ -81,6 +81,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } } + [Fact] + public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070)); + + var xaml = @" + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + // We don't actually know where the global styles are until we attach the control + // to a window, as Window has StylingParent set to Application. + var window = new Window { Content = userControl }; + window.Show(); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + } + [Fact] public void StaticResource_Can_Be_Assigned_To_Setter() {