| File: Visuals\VisualTypeConverter.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#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) { #if NET8_0 #pragma warning disable IL2026 // FeatureGuardAttribute is not supported on .NET 8 #endif ScanAllAssemblies(mappings); #if NET8_0 #pragma warning restore IL2026 // FeatureGuardAttribute is not supported on .NET 8 #endif } 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) }); } }