diff --git a/src/CommunityToolkit.Maui/Extensions/AppThemeResourceExtension.shared.cs b/src/CommunityToolkit.Maui/Extensions/AppThemeResourceExtension.shared.cs index f96cbe622..0180e2112 100644 --- a/src/CommunityToolkit.Maui/Extensions/AppThemeResourceExtension.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/AppThemeResourceExtension.shared.cs @@ -3,7 +3,7 @@ /// /// A XAML markup extension that enables using and from XAML. /// -[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideParentValues)])] +[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideValueTarget), typeof(IRootObjectProvider)])] public sealed class AppThemeResourceExtension : IMarkupExtension { /// @@ -21,68 +21,106 @@ public BindingBase ProvideValue(IServiceProvider serviceProvider) if (Key is null) { - throw new XamlParseException($"{nameof(AppThemeResourceExtension)}.{nameof(Key)} Cannot be null. You must set a {nameof(Key)} that specifies the AppTheme resource to use", serviceProvider); + throw new XamlParseException($"{nameof(AppThemeResourceExtension)}.{nameof(Key)} cannot be null.", serviceProvider); } - if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideParentValues valueProvider) + var valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; + var targetObject = valueTarget?.TargetObject; + if (targetObject is null) { - throw new ArgumentException(null, nameof(serviceProvider)); + var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo; + throw new XamlParseException($"Cannot determine target for {nameof(AppThemeResourceExtension)}.", info); } - if (!TryGetResource(Key, valueProvider.ParentObjects, out var resource, out var resourceDictionary) - && !TryGetApplicationLevelResource(Key, out resource, out resourceDictionary)) + if (TryFindResourceInVisualElement(targetObject, Key, out var resource)) { - var xmlLineInfo = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider ? xmlLineInfoProvider.XmlLineInfo : null; - throw new XamlParseException($"Resource not found for key {Key}", xmlLineInfo); + if (resource is AppThemeColor color) + { + return color.GetBinding(); + } + if (resource is AppThemeObject theme) + { + return theme.GetBinding(); + } + + var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo; + throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info); } - switch (resource) + // Fallback to root object ResourceDictionary (e.g. page-level resources) + var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider; + var root = rootProvider?.RootObject; + if (root is IResourcesProvider rootResources && rootResources.IsResourcesCreated + && rootResources.Resources.TryGetValue(Key, out resource)) { - case AppThemeColor color: + if (resource is AppThemeColor rootColor) + { + return rootColor.GetBinding(); + } + if (resource is AppThemeObject rootTheme) + { + return rootTheme.GetBinding(); + } + var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo; + throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info); + } + + if (Application.Current?.Resources.TryGetValueAndSource(Key, out resource, out _) == true) + { + if (resource is AppThemeColor color) + { return color.GetBinding(); - case AppThemeObject themeResource: - return themeResource.GetBinding(); - default: - { - var xmlLineInfo = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider ? xmlLineInfoProvider.XmlLineInfo : null; - throw new XamlParseException($"Resource found for key {Key} is not of type {nameof(AppThemeColor)} or {nameof(AppThemeObject)}", xmlLineInfo); - } + } + if (resource is AppThemeObject theme) + { + return theme.GetBinding(); + } + var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo; + throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info); } + var xmlInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo; + throw new XamlParseException($"Resource not found for key {Key}.", xmlInfo); } - static bool TryGetResource(string key, IEnumerable parentObjects, out object? resource, out ResourceDictionary? resourceDictionary) + /// + /// Attempts to locate a resource by walking up the visual tree from a target object. + /// + static bool TryFindResourceInVisualElement(object element, string key, out object resource) { - resource = null; - resourceDictionary = null; + resource = default!; - foreach (var parentObject in parentObjects) + // If the element has a Resources property via IResourcesProvider + if (element is IResourcesProvider provider && provider.IsResourcesCreated) { - var resDict = parentObject is IResourcesProvider { IsResourcesCreated: true } resourcesProvider - ? resourcesProvider.Resources - : parentObject as ResourceDictionary; - if (resDict is null) + if (provider.Resources.TryGetValue(key, out resource)) { - continue; + return true; } + } - if (resDict.TryGetValueAndSource(key, out resource, out resourceDictionary)) + // Walk up the element tree + if (element is Element elementObj) + { + var parent = elementObj.Parent; + while (parent is not null) { - return true; + if (parent is IResourcesProvider parentProvider && parentProvider.IsResourcesCreated + && parentProvider.Resources.TryGetValue(key, out resource)) + { + return true; + } + parent = parent.Parent; } } - return false; - } - - static bool TryGetApplicationLevelResource(string key, out object? resource, out ResourceDictionary? resourceDictionary) - { - resource = null; - resourceDictionary = null; + // If it's a ResourceDictionary, check it directly + if (element is ResourceDictionary dict && dict.TryGetValue(key, out resource)) + { + return true; + } - return Application.Current is not null - && ((IResourcesProvider)Application.Current).IsResourcesCreated - && Application.Current.Resources.TryGetValueAndSource(key, out resource, out resourceDictionary); + return false; } object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);