Skip to content

Update AppThemeResourceExtension to not use IProvideParentValues #2639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// <summary>
/// A XAML markup extension that enables using <see cref="AppThemeColor"/> and <see cref="AppThemeObject"/> from XAML.
/// </summary>
[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideParentValues)])]
[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideValueTarget), typeof(IRootObjectProvider)])]
public sealed class AppThemeResourceExtension : IMarkupExtension<BindingBase>
{
/// <summary>
Expand All @@ -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<object> parentObjects, out object? resource, out ResourceDictionary? resourceDictionary)
/// <summary>
/// Attempts to locate a resource by walking up the visual tree from a target object.
/// </summary>
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);
Expand Down
Loading