-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
VisualTypeConverter.cs
198 lines (165 loc) · 6.66 KB
/
VisualTypeConverter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Xaml;
namespace Microsoft.Maui.Controls
{
/// <include file="../../../docs/Microsoft.Maui.Controls/VisualTypeConverter.xml" path="Type[@FullName='Microsoft.Maui.Controls.VisualTypeConverter']/Docs/*" />
public class VisualTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
=> destinationType == typeof(string);
static Dictionary<string, IVisual> _visualTypeMappings;
void InitMappings()
{
var mappings = new Dictionary<string, IVisual>(StringComparer.OrdinalIgnoreCase);
if (RuntimeFeature.IsIVisualAssemblyScanningEnabled)
{
ScanAllAssemblies(mappings);
}
else
{
Register(typeof(VisualMarker.MaterialVisual), mappings);
Register(typeof(VisualMarker.DefaultVisual), mappings);
Register(typeof(VisualMarker.MatchParentVisual), mappings);
}
_visualTypeMappings = mappings;
}
[RequiresUnreferencedCode("The IVisual types might be removed by trimming and automatic registration via assembly scanning may not work as expected.")]
void ScanAllAssemblies(Dictionary<string, IVisual> mappings)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
// Check for IVisual Types
foreach (var assembly in assemblies)
RegisterAllIVisualTypesInAssembly(assembly, mappings);
if (Internals.Registrar.ExtraAssemblies != null)
foreach (var assembly in Internals.Registrar.ExtraAssemblies)
RegisterAllIVisualTypesInAssembly(assembly, mappings);
// Check for visual assembly attributes after scanning for IVisual Types
// this will let users replace the default visual names if they want to
foreach (var assembly in assemblies)
RegisterFromAttributes(assembly, mappings);
if (Internals.Registrar.ExtraAssemblies != null)
foreach (var assembly in Internals.Registrar.ExtraAssemblies)
RegisterFromAttributes(assembly, mappings);
static void RegisterAllIVisualTypesInAssembly(Assembly assembly, Dictionary<string, IVisual> mappings)
{
if (assembly.IsDynamic)
return;
try
{
foreach (var type in assembly.GetExportedTypes())
if (typeof(IVisual).IsAssignableFrom(type) && type != typeof(IVisual))
Register(type, mappings);
}
catch (NotSupportedException)
{
Application.Current?.FindMauiContext()?.CreateLogger<IVisual>()?.LogWarning("Cannot scan assembly {assembly} for Visual types.", assembly.FullName);
}
catch (FileNotFoundException)
{
Application.Current?.FindMauiContext()?.CreateLogger<IVisual>()?.LogWarning("Unable to load a dependent assembly for {assembly}. It cannot be scanned for Visual types.", assembly.FullName);
}
catch (ReflectionTypeLoadException)
{
Application.Current?.FindMauiContext()?.CreateLogger<IVisual>()?.LogWarning("Unable to load a dependent assembly for {assembly}. Types cannot be loaded.", assembly.FullName);
}
}
static void RegisterFromAttributes(Assembly assembly, Dictionary<string, IVisual> mappings)
{
object[] attributes = assembly.GetCustomAttributesSafe(typeof(VisualAttribute));
if (attributes != null)
{
foreach (VisualAttribute attribute in attributes)
{
var visual = CreateVisual(attribute.Visual);
if (visual != null)
mappings[attribute.Key] = visual;
}
}
}
}
static void Register(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type visual,
Dictionary<string, IVisual> mappings)
{
IVisual registeredVisual = CreateVisual(visual);
if (registeredVisual == null)
return;
string name = visual.Name;
string fullName = visual.FullName;
if (name.EndsWith("Visual", StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(0, name.Length - 6);
fullName = fullName.Substring(0, fullName.Length - 6);
}
mappings[name] = registeredVisual;
mappings[fullName] = registeredVisual;
mappings[$"{name}Visual"] = registeredVisual;
mappings[$"{fullName}Visual"] = registeredVisual;
}
static IVisual CreateVisual(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type visualType)
{
try
{
return (IVisual)Activator.CreateInstance(visualType);
}
catch
{
Application.Current?.FindMauiContext()?.CreateLogger<IVisual>()?.LogWarning("Unable to register {visualType} please add a public default constructor", visualType.ToString());
}
return null;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var strValue = value?.ToString();
if (_visualTypeMappings == null)
InitMappings();
if (strValue != null)
{
if (_visualTypeMappings.TryGetValue(strValue, out IVisual returnValue))
return returnValue;
if (!RuntimeFeature.IsIVisualAssemblyScanningEnabled)
{
Application.Current?.FindMauiContext()?.CreateLogger<IVisual>()?.LogWarning(
"Unable to find visual {key}. Automatic discovery of IVisual types is disabled. You can enabled it by setting the $(MauiEnableIVisualAssemblyScanning)=true MSBuild property. " +
"Note: automatic registration of IVisual types through assembly scanning is not trimming-compatible and it can lead to slower app startup.", strValue);
}
return VisualMarker.Default;
}
throw new XamlParseException($"Cannot convert \"{strValue}\" into {typeof(IVisual)}");
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is not IVisual visual)
throw new NotSupportedException();
if (_visualTypeMappings == null)
InitMappings();
if (visual == VisualMarker.Default)
return "default";
if (_visualTypeMappings.ContainsValue(visual))
return _visualTypeMappings.Keys.Skip(_visualTypeMappings.Values.IndexOf(visual)).First();
throw new NotSupportedException();
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
=> false;
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
=> true;
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
=> new(new[] {
nameof(VisualMarker.Default),
// nameof(VisualMarker.Material)
});
}
}