Skip to content

Commit ee08ac2

Browse files
Update AppThemeResourceExtension to not use IProvideParentValues (#2639)
* Update AppThemeResourceExtension to not use IProvideParentValues * Refactor, Remove Null Forgiving Operator * Fix Typo --------- Co-authored-by: Brandon Minnick <[email protected]>
1 parent f26b899 commit ee08ac2

File tree

2 files changed

+82
-42
lines changed

2 files changed

+82
-42
lines changed

src/CommunityToolkit.Maui/Essentials/AppTheme/AppThemeObject.shared.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public abstract class AppThemeObject<T>
2929
public T? Default { get; set; }
3030

3131
/// <summary>
32-
/// Gets a bindable object which holds the diffent values for each operating system theme.
32+
/// Gets a bindable object which holds the different values for each operating system theme.
3333
/// </summary>
3434
/// <returns>A <see cref="AppThemeBinding"/> instance with the respective theme values.</returns>
3535
public virtual BindingBase GetBinding()
Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
namespace CommunityToolkit.Maui.Extensions;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace CommunityToolkit.Maui.Extensions;
24

35
/// <summary>
46
/// A XAML markup extension that enables using <see cref="AppThemeColor"/> and <see cref="AppThemeObject"/> from XAML.
57
/// </summary>
6-
[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideParentValues)])]
8+
[ContentProperty(nameof(Key)), RequireService([typeof(IServiceProvider), typeof(IProvideValueTarget), typeof(IRootObjectProvider)])]
79
public sealed class AppThemeResourceExtension : IMarkupExtension<BindingBase>
810
{
911
/// <summary>
@@ -21,69 +23,107 @@ public BindingBase ProvideValue(IServiceProvider serviceProvider)
2123

2224
if (Key is null)
2325
{
24-
throw new XamlParseException($"{nameof(AppThemeResourceExtension)}.{nameof(Key)} Cannot be null. You must set a {nameof(Key)} that specifies the AppTheme resource to use", serviceProvider);
26+
throw new XamlParseException($"{nameof(AppThemeResourceExtension)}.{nameof(Key)} cannot be null.", serviceProvider);
2527
}
2628

27-
if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideParentValues valueProvider)
29+
var valueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
30+
var targetObject = valueTarget?.TargetObject;
31+
if (targetObject is null)
2832
{
29-
throw new ArgumentException(null, nameof(serviceProvider));
33+
var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo;
34+
throw new XamlParseException($"Cannot determine target for {nameof(AppThemeResourceExtension)}.", info);
3035
}
3136

32-
if (!TryGetResource(Key, valueProvider.ParentObjects, out var resource, out var resourceDictionary)
33-
&& !TryGetApplicationLevelResource(Key, out resource, out resourceDictionary))
37+
if (TryFindResourceInVisualElement(targetObject, Key, out var resource))
3438
{
35-
var xmlLineInfo = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider ? xmlLineInfoProvider.XmlLineInfo : null;
36-
throw new XamlParseException($"Resource not found for key {Key}", xmlLineInfo);
39+
switch (resource)
40+
{
41+
case AppThemeColor color:
42+
return color.GetBinding();
43+
case AppThemeObject theme:
44+
return theme.GetBinding();
45+
default:
46+
var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo;
47+
throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info);
48+
}
3749
}
3850

39-
switch (resource)
51+
// Fallback to root object ResourceDictionary (e.g. page-level resources)
52+
var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
53+
var root = rootProvider?.RootObject;
54+
if (root is IResourcesProvider { IsResourcesCreated: true } rootResources
55+
&& rootResources.Resources.TryGetValue(Key, out resource))
4056
{
41-
case AppThemeColor color:
42-
return color.GetBinding();
43-
case AppThemeObject themeResource:
44-
return themeResource.GetBinding();
45-
default:
46-
{
47-
var xmlLineInfo = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider ? xmlLineInfoProvider.XmlLineInfo : null;
48-
throw new XamlParseException($"Resource found for key {Key} is not of type {nameof(AppThemeColor)} or {nameof(AppThemeObject)}", xmlLineInfo);
49-
}
57+
switch (resource)
58+
{
59+
case AppThemeColor rootColor:
60+
return rootColor.GetBinding();
61+
case AppThemeObject rootTheme:
62+
return rootTheme.GetBinding();
63+
default:
64+
var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo;
65+
throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info);
66+
}
5067
}
5168

69+
if (Application.Current?.Resources.TryGetValueAndSource(Key, out resource, out _) is true)
70+
{
71+
switch (resource)
72+
{
73+
case AppThemeColor color:
74+
return color.GetBinding();
75+
case AppThemeObject theme:
76+
return theme.GetBinding();
77+
default:
78+
var info = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo;
79+
throw new XamlParseException($"Resource found for key {Key} is not a valid AppTheme resource.", info);
80+
}
81+
}
82+
83+
var xmlInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo;
84+
throw new XamlParseException($"Resource not found for key {Key}.", xmlInfo);
5285
}
5386

54-
static bool TryGetResource(string key, IEnumerable<object> parentObjects, out object? resource, out ResourceDictionary? resourceDictionary)
87+
/// <summary>
88+
/// Attempts to locate a resource by walking up the visual tree from a target object.
89+
/// </summary>
90+
static bool TryFindResourceInVisualElement(object element, string key, [NotNullWhen(true)] out object? resource)
5591
{
5692
resource = null;
57-
resourceDictionary = null;
5893

59-
foreach (var parentObject in parentObjects)
94+
// If the element has a Resources property via IResourcesProvider
95+
if (element is IResourcesProvider { IsResourcesCreated: true } provider
96+
&& provider.Resources.TryGetValue(key, out resource))
6097
{
61-
var resDict = parentObject is IResourcesProvider { IsResourcesCreated: true } resourcesProvider
62-
? resourcesProvider.Resources
63-
: parentObject as ResourceDictionary;
64-
if (resDict is null)
65-
{
66-
continue;
67-
}
98+
return true;
99+
}
68100

69-
if (resDict.TryGetValueAndSource(key, out resource, out resourceDictionary))
101+
switch (element)
102+
{
103+
// Walk up the element tree to try to find the resource
104+
case Element elementObj:
70105
{
71-
return true;
106+
var parent = elementObj.Parent;
107+
while (parent is not null)
108+
{
109+
if (parent is IResourcesProvider { IsResourcesCreated: true } parentProvider
110+
&& parentProvider.Resources.TryGetValue(key, out resource))
111+
{
112+
return true;
113+
}
114+
115+
parent = parent.Parent;
116+
}
117+
118+
break;
72119
}
120+
// If it's a ResourceDictionary, check it directly
121+
case ResourceDictionary dict when dict.TryGetValue(key, out resource):
122+
return true;
73123
}
74124

75125
return false;
76126
}
77127

78-
static bool TryGetApplicationLevelResource(string key, out object? resource, out ResourceDictionary? resourceDictionary)
79-
{
80-
resource = null;
81-
resourceDictionary = null;
82-
83-
return Application.Current is not null
84-
&& ((IResourcesProvider)Application.Current).IsResourcesCreated
85-
&& Application.Current.Resources.TryGetValueAndSource(key, out resource, out resourceDictionary);
86-
}
87-
88128
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
89129
}

0 commit comments

Comments
 (0)