From 03e84dea7bf86d5133a9ed79891c24899e0d771a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 13 Feb 2019 13:11:53 +0100 Subject: [PATCH] Ignore missing events in the designer. Do this by registering a special type converter for EventInfo, and add in the plumbing for Portable.Xaml to use that type converter instead of its own (internal) `EventConverter`. --- .../DesignWindowLoader.cs | 5 ++ .../DesignerEventConverter.cs | 87 +++++++++++++++++++ .../AvaloniaMemberAttributeProvider.cs | 14 ++- .../PortableXaml/AvaloniaXamlType.cs | 2 +- 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.DesignerSupport/DesignerEventConverter.cs diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 8fee31408f..2df171295f 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -12,6 +12,11 @@ namespace Avalonia.DesignerSupport { public class DesignWindowLoader { + static DesignWindowLoader() + { + AvaloniaTypeConverters.Register(typeof(EventInfo), typeof(DesignerEventConverter)); + } + public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath) { Window window; diff --git a/src/Avalonia.DesignerSupport/DesignerEventConverter.cs b/src/Avalonia.DesignerSupport/DesignerEventConverter.cs new file mode 100644 index 0000000000..b420448698 --- /dev/null +++ b/src/Avalonia.DesignerSupport/DesignerEventConverter.cs @@ -0,0 +1,87 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Portable.Xaml; + +namespace Avalonia.DesignerSupport +{ + internal class DesignerEventConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + var text = value as string; + if (text != null) + { + var rootObjectProvider = context.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider; + var destinationTypeProvider = context.GetService(typeof(IDestinationTypeProvider)) as IDestinationTypeProvider; + if (rootObjectProvider != null && destinationTypeProvider != null) + { + var target = rootObjectProvider.RootObject; + var eventType = destinationTypeProvider.GetDestinationType(); + var eventParameters = eventType.GetRuntimeMethods().First(r => r.Name == "Invoke").GetParameters(); + // go in reverse to match System.Xaml behaviour + var methods = target.GetType().GetRuntimeMethods().Reverse(); + + // find based on exact match parameter types first + foreach (var method in methods) + { + if (method.Name != text) + continue; + var parameters = method.GetParameters(); + if (eventParameters.Length != parameters.Length) + continue; + if (parameters.Length == 0) + return method.CreateDelegate(eventType, target); + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + var eventParam = eventParameters[i]; + if (param.ParameterType != eventParam.ParameterType) + break; + if (i == parameters.Length - 1) + return method.CreateDelegate(eventType, target); + } + } + + // EnhancedXaml: Find method with compatible base class parameters + foreach (var method in methods) + { + if (method.Name != text) + continue; + var parameters = method.GetParameters(); + if (parameters.Length == 0 || eventParameters.Length != parameters.Length) + continue; + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + var eventParam = eventParameters[i]; + if (!param.ParameterType.GetTypeInfo().IsAssignableFrom(eventParam.ParameterType.GetTypeInfo())) + break; + if (i == parameters.Length - 1) + return method.CreateDelegate(eventType, target); + } + } + + // We want to ignore missing events in the designer, so if event handler + // wasn't found create an empty delegate. + var lambdaExpression = Expression.Lambda( + eventType, + Expression.Empty(), + eventParameters.Select(x => Expression.Parameter(x.ParameterType))); + return lambdaExpression.Compile(); + } + } + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs index e9f6ba6945..529cbab938 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs @@ -49,6 +49,18 @@ namespace Avalonia.Markup.Xaml.PortableXaml //Portable.Xaml is not searching for Type Converter result = new TypeConverterAttribute(typeof(SetterValueTypeConverter)); } + else if (attributeType == typeof(TypeConverterAttribute) && _info is EventInfo) + { + // If a type converter for `EventInfo` is registered, then use that to convert + // event handler values. This is used by the designer to override the lookup + // for event handlers with a null handler. + var eventConverter = AvaloniaTypeConverters.GetTypeConverter(typeof(EventInfo)); + + if (eventConverter != null) + { + result = new TypeConverterAttribute(eventConverter); + } + } if (result == null) { @@ -68,4 +80,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml private readonly MemberInfo _info; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 2194223cb7..10cf716912 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -385,4 +385,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml { } } -} \ No newline at end of file +}