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 ) ] ) ]
79public 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