1- #nullable disable
21using System ;
32using System . ComponentModel ;
43using System . Diagnostics . CodeAnalysis ;
98using Microsoft . Extensions . Logging ;
109using Microsoft . Maui . Controls . Xaml ;
1110
12- namespace Microsoft . Maui . Controls
11+ namespace Microsoft . Maui . Controls ;
12+
13+ /// <include file="../../docs/Microsoft.Maui.Controls/BindablePropertyConverter.xml" path="Type[@FullName='Microsoft.Maui.Controls.BindablePropertyConverter']/Docs/*" />
14+ [ Xaml . ProvideCompiled ( "Microsoft.Maui.Controls.XamlC.BindablePropertyConverter" ) ]
15+ public sealed class BindablePropertyConverter : TypeConverter , IExtendedTypeConverter
1316{
14- /// <include file="../../docs/Microsoft.Maui.Controls/BindablePropertyConverter.xml" path="Type[@FullName='Microsoft.Maui.Controls.BindablePropertyConverter']/Docs/*" />
15- [ Xaml . ProvideCompiled ( "Microsoft.Maui.Controls.XamlC.BindablePropertyConverter" ) ]
16- public sealed class BindablePropertyConverter : TypeConverter , IExtendedTypeConverter
17+ public override bool CanConvertFrom ( ITypeDescriptorContext ? context , Type sourceType )
18+ => sourceType == typeof ( string ) ;
19+
20+ public override bool CanConvertTo ( ITypeDescriptorContext ? context , Type ? destinationType )
21+ => true ;
22+
23+ public override object ? ConvertFrom ( ITypeDescriptorContext ? context , CultureInfo ? culture , object value )
1724 {
18- public override bool CanConvertFrom ( ITypeDescriptorContext context , Type sourceType )
19- => sourceType == typeof ( string ) ;
20-
21- public override bool CanConvertTo ( ITypeDescriptorContext context , Type destinationType )
22- => true ;
23-
24- object IExtendedTypeConverter . ConvertFromInvariantString ( string value , IServiceProvider serviceProvider )
25- {
26- if ( string . IsNullOrWhiteSpace ( value ) )
27- return null ;
28- if ( serviceProvider == null )
29- return null ;
30- if ( ! ( serviceProvider . GetService ( typeof ( IXamlTypeResolver ) ) is IXamlTypeResolver typeResolver ) )
31- return null ;
32- IXmlLineInfo lineinfo = null ;
33- if ( serviceProvider . GetService ( typeof ( IXmlLineInfoProvider ) ) is IXmlLineInfoProvider xmlLineInfoProvider )
34- lineinfo = xmlLineInfoProvider . XmlLineInfo ;
35- string [ ] parts = value . Split ( '.' ) ;
36- Type type = null ;
37- if ( parts . Length == 1 )
38- {
39- if ( ! ( serviceProvider . GetService ( typeof ( IProvideValueTarget ) ) is IProvideParentValues parentValuesProvider ) )
40- {
41- string msg = string . Format ( "Can't resolve {0}" , parts [ 0 ] ) ;
42- throw new XamlParseException ( msg , lineinfo ) ;
43- }
44- object parent = parentValuesProvider . ParentObjects . Skip ( 1 ) . FirstOrDefault ( ) ;
45- if ( parentValuesProvider . TargetObject is Setter )
46- {
47- if ( parent is Style style )
48- type = style . TargetType ;
49- else if ( parent is TriggerBase triggerBase )
50- type = triggerBase . TargetType ;
51- else if ( parent is VisualState visualState )
52- type = FindTypeForVisualState ( parentValuesProvider , lineinfo ) ;
53- }
54- else if ( parentValuesProvider . TargetObject is Trigger )
55- type = ( parentValuesProvider . TargetObject as Trigger ) . TargetType ;
56- else if ( parentValuesProvider . TargetObject is PropertyCondition && parent is TriggerBase )
57- type = ( parent as TriggerBase ) . TargetType ;
25+ var strValue = value ? . ToString ( ) ?? string . Empty ;
5826
59- if ( type == null )
60- throw new XamlParseException ( $ "Can't resolve { parts [ 0 ] } ", lineinfo ) ;
27+ if ( string . IsNullOrWhiteSpace ( strValue ) || string . IsNullOrEmpty ( strValue ) )
28+ {
29+ return null ;
30+ }
6131
62- return ConvertFrom ( type , parts [ 0 ] , lineinfo ) ;
63- }
64- if ( parts . Length == 2 )
65- {
66- if ( ! typeResolver . TryResolve ( parts [ 0 ] , out type ) )
67- {
68- string msg = string . Format ( "Can't resolve {0}" , parts [ 0 ] ) ;
69- throw new XamlParseException ( msg , lineinfo ) ;
70- }
71- return ConvertFrom ( type , parts [ 1 ] , lineinfo ) ;
72- }
73- throw new XamlParseException ( $ "Can't resolve { value } . Syntax is [[prefix:]Type.]PropertyName.", lineinfo ) ;
32+ if ( strValue . IndexOf ( ":" , StringComparison . Ordinal ) != - 1 )
33+ {
34+ Application . Current ? . FindMauiContext ( ) ? . CreateLogger < BindablePropertyConverter > ( ) ? . LogWarning ( "Can't resolve properties with xml namespace prefix." ) ;
35+ return null ;
36+ }
37+ string [ ] parts = strValue . Split ( '.' ) ;
38+ if ( parts . Length != 2 )
39+ {
40+ Application . Current ? . FindMauiContext ( ) ? . CreateLogger < BindablePropertyConverter > ( ) ? . LogWarning ( $ "Can't resolve { value } . Accepted syntax is Type.PropertyName.") ;
41+ return null ;
7442 }
43+ Type type = GetControlType ( parts [ 0 ] ) ;
44+ return ConvertFrom ( type , parts [ 1 ] , null ) ;
45+ }
7546
76- public override object ConvertFrom ( ITypeDescriptorContext context , CultureInfo culture , object value )
47+ public override object ? ConvertTo ( ITypeDescriptorContext ? context , CultureInfo ? culture , object ? value , Type destinationType )
48+ {
49+ if ( value is not BindableProperty bp )
7750 {
78- var strValue = value ? . ToString ( ) ;
51+ throw new NotSupportedException ( ) ;
52+ }
7953
80- if ( string . IsNullOrWhiteSpace ( strValue ) )
81- return null ;
82- if ( strValue . IndexOf ( ":" , StringComparison . Ordinal ) != - 1 )
83- {
84- Application . Current ? . FindMauiContext ( ) ? . CreateLogger < BindablePropertyConverter > ( ) ? . LogWarning ( "Can't resolve properties with xml namespace prefix." ) ;
85- return null ;
86- }
87- string [ ] parts = strValue . Split ( '.' ) ;
88- if ( parts . Length != 2 )
89- {
90- Application . Current ? . FindMauiContext ( ) ? . CreateLogger < BindablePropertyConverter > ( ) ? . LogWarning ( $ "Can't resolve { value } . Accepted syntax is Type.PropertyName.") ;
91- return null ;
92- }
93- Type type = GetControlType ( parts [ 0 ] ) ;
94- return ConvertFrom ( type , parts [ 1 ] , null ) ;
54+ return $ "{ bp . DeclaringType . Name } .{ bp . PropertyName } ";
55+ }
56+
57+ #nullable disable
58+ BindableProperty ConvertFrom ( Type type , string propertyName , IXmlLineInfo lineinfo )
59+ {
60+ var name = propertyName + "Property" ;
61+ FieldInfo bpinfo = GetPropertyField ( type , name ) ;
62+ if ( bpinfo == null || bpinfo . FieldType != typeof ( BindableProperty ) )
63+ {
64+ throw new XamlParseException ( $ "Can't resolve { name } on { type . Name } ", lineinfo ) ;
9565 }
9666
97- BindableProperty ConvertFrom ( Type type , string propertyName , IXmlLineInfo lineinfo )
67+ var bp = bpinfo . GetValue ( null ) as BindableProperty ;
68+ var isObsolete = GetObsoleteAttribute ( bpinfo ) != null ;
69+ if ( bp . PropertyName != propertyName && ! isObsolete )
9870 {
99- var name = propertyName + "Property" ;
100- FieldInfo bpinfo = GetPropertyField ( type , name ) ;
101- if ( bpinfo == null || bpinfo . FieldType != typeof ( BindableProperty ) )
102- throw new XamlParseException ( $ "Can't resolve { name } on { type . Name } ", lineinfo ) ;
103- var bp = bpinfo . GetValue ( null ) as BindableProperty ;
104- var isObsolete = GetObsoleteAttribute ( bpinfo ) != null ;
105- if ( bp . PropertyName != propertyName && ! isObsolete )
106- throw new XamlParseException ( $ "The PropertyName of { type . Name } .{ name } is not { propertyName } ", lineinfo ) ;
107- return bp ;
71+ throw new XamlParseException ( $ "The PropertyName of { type . Name } .{ name } is not { propertyName } ", lineinfo ) ;
10872 }
10973
110- [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2045:AttributeRemoval" ,
111- Justification = "ObsoleteAttribute instances are removed by the trimmer in production builds." ) ]
112- static ObsoleteAttribute GetObsoleteAttribute ( FieldInfo fieldInfo )
113- => fieldInfo . GetCustomAttribute < ObsoleteAttribute > ( ) ;
74+ return bp ;
75+ }
11476
115- [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2057:TypeGetType" ,
116- Justification = "The converter is only used when parsing XAML at runtime. The developer will receive a warning " +
117- "saying that parsing XAML at runtime may not work as expected when trimming." ) ]
118- static Type GetControlType ( string typeName )
119- => Type . GetType ( "Microsoft.Maui.Controls." + typeName ) ;
77+ [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2045:AttributeRemoval" ,
78+ Justification = "ObsoleteAttribute instances are removed by the trimmer in production builds." ) ]
79+ static ObsoleteAttribute GetObsoleteAttribute ( FieldInfo fieldInfo )
80+ => fieldInfo . GetCustomAttribute < ObsoleteAttribute > ( ) ;
12081
121- [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2070:UnrecognizedReflectionPattern " ,
122- Justification = "The converter is only used when parsing XAML at runtime. The developer will receive a warning " +
123- "saying that parsing XAML at runtime may not work as expected when trimming." ) ]
124- static FieldInfo GetPropertyField ( Type type , string fieldName )
125- => type . GetField ( fieldName , BindingFlags . Public | BindingFlags . Static | BindingFlags . FlattenHierarchy ) ;
82+ [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2057:TypeGetType " ,
83+ Justification = "The converter is only used when parsing XAML at runtime. The developer will receive a warning " +
84+ "saying that parsing XAML at runtime may not work as expected when trimming." ) ]
85+ static Type GetControlType ( string typeName )
86+ => Type . GetType ( "Microsoft.Maui.Controls." + typeName ) ;
12687
127- Type FindTypeForVisualState ( IProvideParentValues parentValueProvider , IXmlLineInfo lineInfo )
88+ [ UnconditionalSuppressMessage ( "TrimAnalysis" , "IL2070:UnrecognizedReflectionPattern" ,
89+ Justification = "The converter is only used when parsing XAML at runtime. The developer will receive a warning " +
90+ "saying that parsing XAML at runtime may not work as expected when trimming." ) ]
91+ static FieldInfo GetPropertyField ( Type type , string fieldName )
92+ => type . GetField ( fieldName , BindingFlags . Public | BindingFlags . Static | BindingFlags . FlattenHierarchy ) ;
93+
94+ Type FindTypeForVisualState ( IProvideParentValues parentValueProvider , IXmlLineInfo lineInfo )
95+ {
96+ var parents = parentValueProvider . ParentObjects . ToList ( ) ;
97+
98+ // Skip 0; we would not be making this check if TargetObject were not a Setter
99+ // Skip 1; we would not be making this check if the immediate parent were not a VisualState
100+
101+ // VisualStates must be in a VisualStateGroup
102+ if ( parents [ 2 ] is not VisualStateGroup )
128103 {
129- var parents = parentValueProvider . ParentObjects . ToList ( ) ;
104+ throw new XamlParseException ( $ "Expected { nameof ( VisualStateGroup ) } but found { parents [ 2 ] } .", lineInfo ) ;
105+ }
130106
131- // Skip 0; we would not be making this check if TargetObject were not a Setter
132- // Skip 1; we would not be making this check if the immediate parent were not a VisualState
107+ // Are these Visual States directly on a VisualElement?
108+ if ( parents [ 3 ] is VisualElement vsTarget )
109+ {
110+ return vsTarget . GetType ( ) ;
111+ }
133112
134- // VisualStates must be in a VisualStateGroup
135- if ( parents [ 2 ] is not VisualStateGroup )
136- throw new XamlParseException ( $ "Expected { nameof ( VisualStateGroup ) } but found { parents [ 2 ] } .", lineInfo ) ;
113+ if ( parents [ 3 ] is not VisualStateGroupList )
114+ {
115+ throw new XamlParseException ( $ "Expected { nameof ( VisualStateGroupList ) } but found { parents [ 3 ] } .", lineInfo ) ;
116+ }
137117
138- // Are these Visual States directly on a VisualElement?
139- if ( parents [ 3 ] is VisualElement vsTarget )
140- return vsTarget . GetType ( ) ;
118+ if ( parents [ 4 ] is VisualElement veTarget )
119+ {
120+ return veTarget . GetType ( ) ;
121+ }
141122
142- if ( parents [ 3 ] is not VisualStateGroupList )
143- throw new XamlParseException ( $ "Expected { nameof ( VisualStateGroupList ) } but found { parents [ 3 ] } .", lineInfo ) ;
123+ if ( parents [ 4 ] is not Setter )
124+ {
125+ throw new XamlParseException ( $ "Expected { nameof ( Setter ) } but found { parents [ 4 ] } .", lineInfo ) ;
126+ }
144127
145- if ( parents [ 4 ] is VisualElement veTarget )
146- return veTarget . GetType ( ) ;
128+ if ( parents [ 5 ] is TriggerBase trigger )
129+ {
130+ return trigger . TargetType ;
131+ }
147132
148- if ( parents [ 4 ] is not Setter )
149- throw new XamlParseException ( $ "Expected { nameof ( Setter ) } but found { parents [ 4 ] } .", lineInfo ) ;
133+ // These must be part of a Style; verify that
134+ if ( parents [ 5 ] is Style style )
135+ {
136+ return style . TargetType ;
137+ }
150138
151- if ( parents [ 5 ] is TriggerBase trigger )
152- return trigger . TargetType ;
139+ throw new XamlParseException ( $ "Unable to find a TragetType for the Bindable Property. Try prefixing it with the TargetType.", lineInfo ) ;
153140
154- // These must be part of a Style; verify that
155- if ( parents [ 5 ] is Style style )
156- return style . TargetType ;
141+ }
157142
158- throw new XamlParseException ( $ "Unable to find a TragetType for the Bindable Property. Try prefixing it with the TargetType.", lineInfo ) ;
143+ object IExtendedTypeConverter . ConvertFromInvariantString ( string value , IServiceProvider serviceProvider )
144+ {
145+ if ( string . IsNullOrWhiteSpace ( value ) )
146+ {
147+ return null ;
148+ }
159149
150+ if ( serviceProvider == null )
151+ {
152+ return null ;
160153 }
161154
162- public override object ConvertTo ( ITypeDescriptorContext context , CultureInfo culture , object value , Type destinationType )
155+ if ( ! ( serviceProvider . GetService ( typeof ( IXamlTypeResolver ) ) is IXamlTypeResolver typeResolver ) )
163156 {
164- if ( value is not BindableProperty bp )
165- throw new NotSupportedException ( ) ;
166- return $ "{ bp . DeclaringType . Name } .{ bp . PropertyName } ";
157+ return null ;
167158 }
159+
160+ IXmlLineInfo lineinfo = null ;
161+ if ( serviceProvider . GetService ( typeof ( IXmlLineInfoProvider ) ) is IXmlLineInfoProvider xmlLineInfoProvider )
162+ {
163+ lineinfo = xmlLineInfoProvider . XmlLineInfo ;
164+ }
165+
166+ string [ ] parts = value . Split ( '.' ) ;
167+ Type type = null ;
168+ if ( parts . Length == 1 )
169+ {
170+ if ( ! ( serviceProvider . GetService ( typeof ( IProvideValueTarget ) ) is IProvideParentValues parentValuesProvider ) )
171+ {
172+ string msg = string . Format ( "Can't resolve {0}" , parts [ 0 ] ) ;
173+ throw new XamlParseException ( msg , lineinfo ) ;
174+ }
175+ object parent = parentValuesProvider . ParentObjects . Skip ( 1 ) . FirstOrDefault ( ) ;
176+ if ( parentValuesProvider . TargetObject is Setter )
177+ {
178+ if ( parent is Style style )
179+ {
180+ type = style . TargetType ;
181+ }
182+ else if ( parent is TriggerBase triggerBase )
183+ {
184+ type = triggerBase . TargetType ;
185+ }
186+ else if ( parent is VisualState visualState )
187+ {
188+ type = FindTypeForVisualState ( parentValuesProvider , lineinfo ) ;
189+ }
190+ }
191+ else if ( parentValuesProvider . TargetObject is Trigger )
192+ {
193+ type = ( parentValuesProvider . TargetObject as Trigger ) . TargetType ;
194+ }
195+ else if ( parentValuesProvider . TargetObject is PropertyCondition && parent is TriggerBase )
196+ {
197+ type = ( parent as TriggerBase ) . TargetType ;
198+ }
199+
200+ if ( type == null )
201+ {
202+ throw new XamlParseException ( $ "Can't resolve { parts [ 0 ] } ", lineinfo ) ;
203+ }
204+
205+ return ConvertFrom ( type , parts [ 0 ] , lineinfo ) ;
206+ }
207+ if ( parts . Length == 2 )
208+ {
209+ if ( ! typeResolver . TryResolve ( parts [ 0 ] , out type ) )
210+ {
211+ string msg = string . Format ( "Can't resolve {0}" , parts [ 0 ] ) ;
212+ throw new XamlParseException ( msg , lineinfo ) ;
213+ }
214+ return ConvertFrom ( type , parts [ 1 ] , lineinfo ) ;
215+ }
216+ throw new XamlParseException ( $ "Can't resolve { value } . Syntax is [[prefix:]Type.]PropertyName.", lineinfo ) ;
168217 }
169- }
218+
219+ }
0 commit comments