Skip to content

Commit ba6b0bb

Browse files
enabled more converters for nullable
1 parent a1715e4 commit ba6b0bb

18 files changed

+1106
-771
lines changed
Lines changed: 177 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#nullable disable
21
using System;
32
using System.ComponentModel;
43
using System.Diagnostics.CodeAnalysis;
@@ -9,161 +8,212 @@
98
using Microsoft.Extensions.Logging;
109
using 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

Comments
 (0)