From c8363ddeb715efafb41e6f8eebf31b8d61bd0433 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Jan 2019 11:05:19 +0100 Subject: [PATCH 01/57] Supply more detailed exception details. So we can display better errors in a designer. --- .../Remote/RemoteDesignerEntryPoint.cs | 12 +++++++++++- src/Avalonia.Remote.Protocol/DesignMessages.cs | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 09196e4fb7..67a93f3c9c 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -8,6 +8,7 @@ using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Viewport; using Avalonia.Threading; +using Portable.Xaml; namespace Avalonia.DesignerSupport.Remote { @@ -204,9 +205,18 @@ namespace Avalonia.DesignerSupport.Remote } catch (Exception e) { + var xamlException = e as XamlException; + s_transport.Send(new UpdateXamlResultMessage { - Error = e.ToString() + Error = e.ToString(), + Exception = new ExceptionDetails + { + ExceptionType = e.GetType().FullName, + Message = e.Message.ToString(), + LineNumber = xamlException?.LineNumber, + LinePosition = xamlException?.LinePosition, + } }); } } diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs index f70bcef6b3..5ff16c574d 100644 --- a/src/Avalonia.Remote.Protocol/DesignMessages.cs +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -15,6 +15,7 @@ namespace Avalonia.Remote.Protocol.Designer { public string Error { get; set; } public string Handle { get; set; } + public ExceptionDetails Exception { get; set; } } [AvaloniaRemoteMessageGuid("854887CF-2694-4EB6-B499-7461B6FB96C7")] @@ -23,4 +24,11 @@ namespace Avalonia.Remote.Protocol.Designer public string SessionId { get; set; } } + public class ExceptionDetails + { + public string ExceptionType { get; set; } + public string Message { get; set; } + public int? LineNumber { get; set; } + public int? LinePosition { get; set; } + } } From 8caa0d2924aa91b4cab6b1e0673d02e4c8c863f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Jan 2019 11:06:10 +0100 Subject: [PATCH 02/57] Strong name sign Avalonia.Remote.Protocol. It needs to be signed to be used in VS. --- .../Avalonia.Remote.Protocol.csproj | 2 ++ src/Avalonia.Remote.Protocol/Key.snk | Bin 0 -> 596 bytes 2 files changed, 2 insertions(+) create mode 100644 src/Avalonia.Remote.Protocol/Key.snk diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 871c9cd995..6684772bfe 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -2,6 +2,8 @@ netstandard2.0 AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + true + Key.snk diff --git a/src/Avalonia.Remote.Protocol/Key.snk b/src/Avalonia.Remote.Protocol/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..d6cbc36f52ccda288283effe3294eac82a5c7ef7 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097%7Y|vta8^3KR$Smx;K{fFS_SHi*Psw3 zJkx=j;*YrY^86pNz5vg5O~4U_MzBsH{`RYL9%T;+mhYMfmF$a-hma@g6C=)}fn}{@ zI6+_-6SrY={k8i>Zro$$X_F^INltv6z|DqkHoVCrb@d@)Ynl ziDFw_@9Kk?>cNGZ zkCmu~lKW60E{OV*o5~ku8U4_BVgA~yhp_&l8GDAo5|W{$&Z?`@AZ@_1&~jq@i>hZp zfr$zNo<&wCV%9Y16^ult`xUXBU{dwnHju#G_+DC2%24bQ6(nW*Ru#AksAm} zVrGJ%!vumsmQv4#B#c13373GWA)F8F&Jcl|4?#CU|8T13H{?NW1^lSD_)<`0%4AFb zT{Zy{*o=br4QtNUt*gZX42(6>YX3sMl5R$?_iRW)5tpm!JF-;s%CmTExp4_Y))=1A z5!R5C0%+Tm4TlE_P);~#r&{t`3qauN`OdjpTZIT$iHJqcKU?P+59wR{R01ve4D1B~ zFg7f2wt{GtEMYmt)ZZ)c>-o!;2RLaY_uLW_ zs%cQ6S|W66#6C;m>k<*}q>-#musb}vAeE695xEBO7zyW1c9{!H`NE?Kb$f6CtyJ66 iSF5$`(ApW(ItQ!B9xp{O{xF;%!Gd-<-CHQkxaEbh4H%~Y literal 0 HcmV?d00001 From 718c01f206c43214238f9cdf96979a733dc78f6c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Jan 2019 11:05:19 +0100 Subject: [PATCH 03/57] Supply more detailed exception details. So we can display better errors in a designer. --- .../Remote/RemoteDesignerEntryPoint.cs | 12 +++++++++++- src/Avalonia.Remote.Protocol/DesignMessages.cs | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 09196e4fb7..67a93f3c9c 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -8,6 +8,7 @@ using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Viewport; using Avalonia.Threading; +using Portable.Xaml; namespace Avalonia.DesignerSupport.Remote { @@ -204,9 +205,18 @@ namespace Avalonia.DesignerSupport.Remote } catch (Exception e) { + var xamlException = e as XamlException; + s_transport.Send(new UpdateXamlResultMessage { - Error = e.ToString() + Error = e.ToString(), + Exception = new ExceptionDetails + { + ExceptionType = e.GetType().FullName, + Message = e.Message.ToString(), + LineNumber = xamlException?.LineNumber, + LinePosition = xamlException?.LinePosition, + } }); } } diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs index f70bcef6b3..5ff16c574d 100644 --- a/src/Avalonia.Remote.Protocol/DesignMessages.cs +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -15,6 +15,7 @@ namespace Avalonia.Remote.Protocol.Designer { public string Error { get; set; } public string Handle { get; set; } + public ExceptionDetails Exception { get; set; } } [AvaloniaRemoteMessageGuid("854887CF-2694-4EB6-B499-7461B6FB96C7")] @@ -23,4 +24,11 @@ namespace Avalonia.Remote.Protocol.Designer public string SessionId { get; set; } } + public class ExceptionDetails + { + public string ExceptionType { get; set; } + public string Message { get; set; } + public int? LineNumber { get; set; } + public int? LinePosition { get; set; } + } } From f32832878e9729a50dbaba071440722aa93a411c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 31 Jan 2019 11:06:10 +0100 Subject: [PATCH 04/57] Strong name sign Avalonia.Remote.Protocol. It needs to be signed to be used in VS. --- .../Avalonia.Remote.Protocol.csproj | 2 ++ src/Avalonia.Remote.Protocol/Key.snk | Bin 0 -> 596 bytes 2 files changed, 2 insertions(+) create mode 100644 src/Avalonia.Remote.Protocol/Key.snk diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 871c9cd995..6684772bfe 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -2,6 +2,8 @@ netstandard2.0 AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) + true + Key.snk diff --git a/src/Avalonia.Remote.Protocol/Key.snk b/src/Avalonia.Remote.Protocol/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..d6cbc36f52ccda288283effe3294eac82a5c7ef7 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097%7Y|vta8^3KR$Smx;K{fFS_SHi*Psw3 zJkx=j;*YrY^86pNz5vg5O~4U_MzBsH{`RYL9%T;+mhYMfmF$a-hma@g6C=)}fn}{@ zI6+_-6SrY={k8i>Zro$$X_F^INltv6z|DqkHoVCrb@d@)Ynl ziDFw_@9Kk?>cNGZ zkCmu~lKW60E{OV*o5~ku8U4_BVgA~yhp_&l8GDAo5|W{$&Z?`@AZ@_1&~jq@i>hZp zfr$zNo<&wCV%9Y16^ult`xUXBU{dwnHju#G_+DC2%24bQ6(nW*Ru#AksAm} zVrGJ%!vumsmQv4#B#c13373GWA)F8F&Jcl|4?#CU|8T13H{?NW1^lSD_)<`0%4AFb zT{Zy{*o=br4QtNUt*gZX42(6>YX3sMl5R$?_iRW)5tpm!JF-;s%CmTExp4_Y))=1A z5!R5C0%+Tm4TlE_P);~#r&{t`3qauN`OdjpTZIT$iHJqcKU?P+59wR{R01ve4D1B~ zFg7f2wt{GtEMYmt)ZZ)c>-o!;2RLaY_uLW_ zs%cQ6S|W66#6C;m>k<*}q>-#musb}vAeE695xEBO7zyW1c9{!H`NE?Kb$f6CtyJ66 iSF5$`(ApW(ItQ!B9xp{O{xF;%!Gd-<-CHQkxaEbh4H%~Y literal 0 HcmV?d00001 From ba316b89333cf0ebffcdd940f0facd3756510990 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 1 Feb 2019 14:51:44 +0100 Subject: [PATCH 05/57] Updated Portable.Xaml. --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index ab55261737..b9f886b93a 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975 +Subproject commit b9f886b93ab28dd69722e72ef8cb6c33889b3749 From 521c9a38414ca4a9e80c9619f23bf1da7460f083 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 1 Feb 2019 18:17:02 +0100 Subject: [PATCH 06/57] Updated Portable.Xaml. To get better line/column info for errors. --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index b9f886b93a..452ced4782 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit b9f886b93ab28dd69722e72ef8cb6c33889b3749 +Subproject commit 452ced47823d963c1e2d0ad809d0327c9e9b5247 From 03e84dea7bf86d5133a9ed79891c24899e0d771a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 13 Feb 2019 13:11:53 +0100 Subject: [PATCH 07/57] 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 +} From 9ed23bbf89158792812965b9f39afeab71314ef9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 13 Feb 2019 16:33:27 +0100 Subject: [PATCH 08/57] Handle null modifiers collection. --- .../Remote/Server/RemoteServerTopLevelImpl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index e1767fca36..028a78aea4 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -61,6 +61,11 @@ namespace Avalonia.Controls.Remote.Server { var result = InputModifiers.None; + if (modifiers == null) + { + return result; + } + foreach(var modifier in modifiers) { switch (modifier) From 2b34a2789213796454d62a7706dc55f3ddb37a27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 13 Feb 2019 19:36:33 +0100 Subject: [PATCH 09/57] Return a frame even for an empty control. If the user has a control with no content then we've still got a valid control so return a frame with size 0. --- .../Remote/Server/RemoteServerTopLevelImpl.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 028a78aea4..3fa0b108ec 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -270,11 +270,15 @@ namespace Avalonia.Controls.Remote.Server var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; var data = new byte[width * height * bpp]; var handle = GCHandle.Alloc(data, GCHandleType.Pinned); + try { - _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt, - null); - Paint?.Invoke(new Rect(0, 0, width, height)); + if (width > 0 && height > 0) + { + _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt, + null); + Paint?.Invoke(new Rect(0, 0, width, height)); + } } finally { @@ -306,8 +310,7 @@ namespace Avalonia.Controls.Remote.Server return; } - if (ClientSize.Width < 1 || ClientSize.Height < 1) - return; + var format = ProtocolPixelFormat.Rgba8888; foreach(var fmt in _supportedFormats) if (fmt <= ProtocolPixelFormat.MaxValue) From 81510372b4a8d445e083b83ac988b8fb772acfbf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 13 Feb 2019 21:01:26 +0100 Subject: [PATCH 10/57] Don't invalidate a disposed TopLevelImpl. --- .../Embedding/Offscreen/OffscreenTopLevelImpl.cs | 4 +++- .../Remote/Server/RemoteServerTopLevelImpl.cs | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index d328e1ee88..9c53dc0c10 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -11,11 +11,13 @@ namespace Avalonia.Controls.Embedding.Offscreen { private double _scaling = 1; private Size _clientSize; + public IInputRoot InputRoot { get; private set; } + public bool IsDisposed { get; private set; } public virtual void Dispose() { - //No-op + IsDisposed = true; } public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 3fa0b108ec..6293cbfbfd 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -331,8 +331,11 @@ namespace Avalonia.Controls.Remote.Server public override void Invalidate(Rect rect) { - _invalidated = true; - Dispatcher.UIThread.Post(RenderIfNeeded); + if (!IsDisposed) + { + _invalidated = true; + Dispatcher.UIThread.Post(RenderIfNeeded); + } } public override IMouseDevice MouseDevice { get; } = new MouseDevice(); From e3e42e8ae08fb91c8f7a28a3ea858eff709639ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 Feb 2019 22:24:45 +0100 Subject: [PATCH 11/57] Fix bad XAML in DesignWindowLoader message. --- src/Avalonia.DesignerSupport/DesignWindowLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 2df171295f..68feb2edb5 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -56,8 +56,8 @@ namespace Avalonia.DesignerSupport { new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"}, new TextBlock {Text = ""}, - new TextBlock {Text = " "}, - new TextBlock {Text = ""}, + new TextBlock {Text = " "}, + new TextBlock {Text = ""}, new TextBlock {Text = "before setters in your first Style"} } }; From 8d9c23446a8d4e7cde8416c913104d41e282508a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 14 Feb 2019 23:35:36 +0100 Subject: [PATCH 12/57] Make Styles inherit from AvaloniaObject. This way we can add attached properties (such as `Design.PreviewWith`) to `Styles`. --- src/Avalonia.Styling/Styling/Styles.cs | 79 +++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 51499b737a..288cf35d08 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls; @@ -12,16 +14,17 @@ namespace Avalonia.Styling /// /// A style that consists of a number of child styles. /// - public class Styles : AvaloniaList, IStyle, ISetStyleParent + public class Styles : AvaloniaObject, IAvaloniaList, IStyle, ISetStyleParent { private IResourceNode _parent; private IResourceDictionary _resources; + private AvaloniaList _styles = new AvaloniaList(); private Dictionary> _cache; public Styles() { - ResetBehavior = ResetBehavior.Remove; - this.ForEachItem( + _styles.ResetBehavior = ResetBehavior.Remove; + _styles.ForEachItem( x => { if (x.ResourceParent == null && x is ISetStyleParent setParent) @@ -57,9 +60,18 @@ namespace Avalonia.Styling () => { }); } + public event NotifyCollectionChangedEventHandler CollectionChanged + { + add => _styles.CollectionChanged += value; + remove => _styles.CollectionChanged -= value; + } + /// public event EventHandler ResourcesChanged; + /// + public int Count => _styles.Count; + /// public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources); @@ -94,6 +106,19 @@ namespace Avalonia.Styling /// IResourceNode IResourceNode.ResourceParent => _parent; + /// + bool ICollection.IsReadOnly => false; + + /// + IStyle IReadOnlyList.this[int index] => _styles[index]; + + /// + public IStyle this[int index] + { + get => _styles[index]; + set => _styles[index] = value; + } + /// /// Attaches the style to a control if the style's selector matches. /// @@ -172,6 +197,54 @@ namespace Avalonia.Styling return false; } + /// + public void AddRange(IEnumerable items) => _styles.AddRange(items); + + /// + public void InsertRange(int index, IEnumerable items) => _styles.InsertRange(index, items); + + /// + public void Move(int oldIndex, int newIndex) => _styles.Move(oldIndex, newIndex); + + /// + public void MoveRange(int oldIndex, int count, int newIndex) => _styles.MoveRange(oldIndex, count, newIndex); + + /// + public void RemoveAll(IEnumerable items) => _styles.RemoveAll(items); + + /// + public void RemoveRange(int index, int count) => _styles.RemoveRange(index, count); + + /// + public int IndexOf(IStyle item) => _styles.IndexOf(item); + + /// + public void Insert(int index, IStyle item) => _styles.Insert(index, item); + + /// + public void RemoveAt(int index) => _styles.RemoveAt(index); + + /// + public void Add(IStyle item) => _styles.Add(item); + + /// + public void Clear() => _styles.Clear(); + + /// + public bool Contains(IStyle item) => _styles.Contains(item); + + /// + public void CopyTo(IStyle[] array, int arrayIndex) => _styles.CopyTo(array, arrayIndex); + + /// + public bool Remove(IStyle item) => _styles.Remove(item); + + /// + public IEnumerator GetEnumerator() => _styles.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator(); + /// void ISetStyleParent.SetParent(IResourceNode parent) { From c438f9301e8d8a53269d485cdb05f551457afef7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 15 Feb 2019 00:13:57 +0100 Subject: [PATCH 13/57] Make DesignWindowLoader expect PreviewWith on Styles. --- samples/ControlCatalog/SideBar.xaml | 8 ++++++++ src/Avalonia.Controls/Design.cs | 6 +++--- src/Avalonia.DesignerSupport/DesignWindowLoader.cs | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 625b344b8c..fea55bcb07 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,6 +1,14 @@ + + + + + + + + - + \ No newline at end of file From 64a0ae0921ec0625081b8a976f8df22a0e589c6a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 25 Feb 2019 14:04:07 +0300 Subject: [PATCH 20/57] [X11] Fixed window size hints handling --- src/Avalonia.X11/X11Window.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index cbff4e38cf..3af2d5f3fe 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -557,8 +557,14 @@ namespace Avalonia.X11 private bool _systemDecorations = true; private bool _canResize = true; - private (Size minSize, Size maxSize) _scaledMinMaxSize; - private (PixelSize minSize, PixelSize maxSize) _minMaxSize; + private const int MaxWindowDimension = 100000; + + private (Size minSize, Size maxSize) _scaledMinMaxSize = + (new Size(1, 1), new Size(double.PositiveInfinity, double.PositiveInfinity)); + + private (PixelSize minSize, PixelSize maxSize) _minMaxSize = (new PixelSize(1, 1), + new PixelSize(MaxWindowDimension, MaxWindowDimension)); + private double _scaling = 1; void ScheduleInput(RawInputEventArgs args, ref XEvent xev) @@ -874,10 +880,10 @@ namespace Avalonia.X11 (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling), (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling)); - const int maxDim = 100000; + const int maxDim = MaxWindowDimension; var max = new PixelSize( - (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, minSize.Width * Scaling)), - (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, minSize.Height * Scaling))); + (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)), + (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling))); _minMaxSize = (min, max); UpdateSizeHints(null); From 4af9b22c59723845e3ef8760fde6c7522aba796f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 26 Feb 2019 16:29:25 +0300 Subject: [PATCH 21/57] Introduced WeakEventHandlerManager --- .../Utilities/WeakEventHandlerManager.cs | 219 ++++++++++++++++++ .../WeakEventHandlerManagerTests.cs | 71 ++++++ 2 files changed, 290 insertions(+) create mode 100644 src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs create mode 100644 tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs new file mode 100644 index 0000000000..a724878317 --- /dev/null +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -0,0 +1,219 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities +{ + /// + /// Manages subscriptions to events using weak listeners. + /// + public static class WeakEventHandlerManager + { + /// + /// Subscribes to an event on an object using a weak subscription. + /// + /// The type of the target. + /// The type of the event arguments. + /// The event source. + /// The name of the event. + /// The subscriber. + public static void Subscribe(TTarget target, string eventName, EventHandler subscriber) + where TEventArgs : EventArgs where TSubscriber : class + { + var dic = SubscriptionTypeStorage.Subscribers.GetOrCreateValue(target); + Subscription sub; + + if (!dic.TryGetValue(eventName, out sub)) + { + dic[eventName] = sub = new Subscription(dic, typeof(TTarget), target, eventName); + } + + sub.Add(subscriber); + } + + /// + /// Unsubscribes from an event. + /// + /// The type of the event arguments. + /// The event source. + /// The name of the event. + /// The subscriber. + public static void Unsubscribe(object target, string eventName, EventHandler subscriber) + where TEventArgs : EventArgs where TSubscriber : class + { + SubscriptionDic dic; + + if (SubscriptionTypeStorage.Subscribers.TryGetValue(target, out dic)) + { + Subscription sub; + + if (dic.TryGetValue(eventName, out sub)) + { + sub.Remove(subscriber); + } + } + } + + private static class SubscriptionTypeStorage + where TArgs : EventArgs where TSubscriber : class + { + public static readonly ConditionalWeakTable> Subscribers + = new ConditionalWeakTable>(); + } + + private class SubscriptionDic : Dictionary> + where T : EventArgs where TSubscriber : class + { + } + + private static readonly Dictionary> Accessors + = new Dictionary>(); + + private class Subscription where T : EventArgs where TSubscriber : class + { + private readonly EventInfo _info; + private readonly SubscriptionDic _sdic; + private readonly object _target; + private readonly string _eventName; + private readonly Delegate _delegate; + + private Descriptor[] _data = new Descriptor[2]; + private int _count = 0; + + delegate void CallerDelegate(TSubscriber s, object sender, T args); + + struct Descriptor + { + public WeakReference Subscriber; + public CallerDelegate Caller; + } + + private static Dictionary s_Callers = + new Dictionary(); + + public Subscription(SubscriptionDic sdic, Type targetType, object target, string eventName) + { + _sdic = sdic; + _target = target; + _eventName = eventName; + Dictionary evDic; + if (!Accessors.TryGetValue(targetType, out evDic)) + Accessors[targetType] = evDic = new Dictionary(); + + if (!evDic.TryGetValue(eventName, out _info)) + { + var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName); + + if (ev == null) + { + throw new ArgumentException( + $"The event {eventName} was not found on {target.GetType()}."); + } + + evDic[eventName] = _info = ev; + } + + var del = new Action(OnEvent); + _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType, del.Target); + _info.AddMethod.Invoke(target, new[] { _delegate }); + + } + + void Destroy() + { + _info.RemoveMethod.Invoke(_target, new[] { _delegate }); + _sdic.Remove(_eventName); + } + + public void Add(EventHandler s) + { + if (_count == _data.Length) + { + //Extend capacity + var ndata = new Descriptor[_data.Length*2]; + Array.Copy(_data, ndata, _data.Length); + _data = ndata; + } + + var subscriber = (TSubscriber)s.Target; + if (!s_Callers.TryGetValue(s.Method, out var caller)) + s_Callers[s.Method] = caller = + (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, s.Method); + _data[_count] = new Descriptor + { + Caller = caller, + Subscriber = new WeakReference(subscriber) + }; + _count++; + } + + public void Remove(EventHandler s) + { + var removed = false; + + for (int c = 0; c < _count; ++c) + { + var reference = _data[c].Subscriber; + TSubscriber instance; + + if (reference != null && reference.TryGetTarget(out instance) && instance == s) + { + _data[c] = default; + removed = true; + } + } + + if (removed) + { + Compact(); + } + } + + void Compact() + { + int empty = -1; + for (int c = 0; c < _count; c++) + { + var r = _data[c]; + //Mark current index as first empty + if (r.Subscriber == null && empty == -1) + empty = c; + //If current element isn't null and we have an empty one + if (r.Subscriber != null && empty != -1) + { + _data[c] = default; + _data[empty] = r; + empty++; + } + } + if (empty != -1) + _count = empty; + if (_count == 0) + Destroy(); + } + + void OnEvent(object sender, T eventArgs) + { + var needCompact = false; + for(var c=0; c<_count; c++) + { + var r = _data[c].Subscriber; + TSubscriber sub; + if (r.TryGetTarget(out sub)) + { + _data[c].Caller(sub, sender, eventArgs); + } + else + needCompact = true; + } + if (needCompact) + Compact(); + } + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs b/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs new file mode 100644 index 0000000000..9ed6590821 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/WeakEventHandlerManagerTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class WeakEventHandlerManagerTests + { + class EventSource + { + public event EventHandler Event; + + public void Fire() + { + Event?.Invoke(this, new EventArgs()); + } + } + + class Subscriber + { + private readonly Action _onEvent; + + public Subscriber(Action onEvent) + { + _onEvent = onEvent; + } + + public void OnEvent(object sender, EventArgs ev) + { + _onEvent?.Invoke(); + } + } + + [Fact] + public void EventShoudBePassedToSubscriber() + { + bool handled = false; + var subscriber = new Subscriber(() => handled = true); + var source = new EventSource(); + WeakEventHandlerManager.Subscribe(source, "Event", + subscriber.OnEvent); + source.Fire(); + Assert.True(handled); + } + + + [Fact] + public void EventHandlerShouldNotBeKeptAlive() + { + bool handled = false; + var source = new EventSource(); + AddCollectableSubscriber(source, "Event", () => handled = true); + for (int c = 0; c < 10; c++) + { + GC.Collect(); + GC.Collect(3, GCCollectionMode.Forced, true); + } + source.Fire(); + Assert.False(handled); + } + + private void AddCollectableSubscriber(EventSource source, string name, Action func) + { + WeakEventHandlerManager.Subscribe(source, name, new Subscriber(func).OnEvent); + } + } +} From 556c1373e119846d56baab5fbf580e9eba937a91 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 26 Feb 2019 14:28:11 +0000 Subject: [PATCH 22/57] fix affectsrender memory leak with WeakEventHandlerManager --- src/Avalonia.Visuals/Visual.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index bf282db72f..d9b21237ae 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -11,6 +11,7 @@ using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Rendering; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia @@ -343,16 +344,17 @@ namespace Avalonia { void Invalidate(AvaloniaPropertyChangedEventArgs e) { + if (e.Sender is T sender) { if (e.OldValue is IAffectsRender oldValue) { - oldValue.Invalidated -= sender.AffectsRenderInvalidated; + WeakEventHandlerManager.Unsubscribe(oldValue, nameof(oldValue.Invalidated), sender.AffectsRenderInvalidated); } if (e.NewValue is IAffectsRender newValue) { - newValue.Invalidated += sender.AffectsRenderInvalidated; + WeakEventHandlerManager.Subscribe(newValue, nameof(newValue.Invalidated), sender.AffectsRenderInvalidated); } sender.InvalidateVisual(); From 2d7d9bc90006fa477eea7bc77461903dd778cccf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 26 Feb 2019 14:29:44 +0000 Subject: [PATCH 23/57] whitespace. --- src/Avalonia.Visuals/Visual.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index d9b21237ae..d2b4c97a05 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -344,7 +344,6 @@ namespace Avalonia { void Invalidate(AvaloniaPropertyChangedEventArgs e) { - if (e.Sender is T sender) { if (e.OldValue is IAffectsRender oldValue) From f85995ed7e6e83fe1fcaef2ef3daf2dcc4287aa8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 26 Feb 2019 15:43:10 +0100 Subject: [PATCH 24/57] Move design-time handling of events... ...to `Avalonia.Markup.Xaml`. Also added tests: tests required a new instance of `AvaloniaXamlSchemaContext` for each `AvaloniaXamlLoader` as otherwise one test can affect other tests. --- .../DesignWindowLoader.cs | 5 -- .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaTypeConverters.cs | 4 +- .../AvaloniaXamlLoader.cs | 31 ++------- .../Converters/AvaloniaEventConverter.cs} | 30 ++++++--- .../PortableXaml/portable.xaml.github | 2 +- .../Xaml/EventTests.cs | 66 +++++++++++++++++++ 7 files changed, 99 insertions(+), 40 deletions(-) rename src/{Avalonia.DesignerSupport/DesignerEventConverter.cs => Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs} (74%) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 5d49ddf6a1..a7d4b96974 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -12,11 +12,6 @@ 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/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 38d207a31d..3c1ef84c54 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index c30822aacb..be92b6a8d7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Templates; namespace Avalonia.Markup.Xaml { + using System.Reflection; using Avalonia.Media; /// @@ -41,7 +42,8 @@ namespace Avalonia.Markup.Xaml { typeof(WindowIcon), typeof(IconTypeConverter) }, { typeof(CultureInfo), typeof(CultureInfoConverter) }, { typeof(Uri), typeof(AvaloniaUriTypeConverter) }, - { typeof(FontFamily), typeof(FontFamilyTypeConverter) } + { typeof(FontFamily), typeof(FontFamilyTypeConverter) }, + { typeof(EventInfo), typeof(AvaloniaEventConverter) }, }; internal static Type GetBuiltinTypeConverter(Type type) diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index b99864b050..800afb3976 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,21 +1,20 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Controls; -using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.PortableXaml; -using Avalonia.Platform; -using Portable.Xaml; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; using System.Text; using System.Xml.Linq; -using System.Linq; +using Avalonia.Controls; +using Avalonia.Markup.Data; +using Avalonia.Markup.Xaml.PortableXaml; +using Avalonia.Platform; +using Portable.Xaml; namespace Avalonia.Markup.Xaml { @@ -24,7 +23,7 @@ namespace Avalonia.Markup.Xaml /// public class AvaloniaXamlLoader { - private readonly AvaloniaXamlSchemaContext _context = GetContext(); + private readonly AvaloniaXamlSchemaContext _context = AvaloniaXamlSchemaContext.Create(); public bool IsDesignMode { @@ -32,22 +31,6 @@ namespace Avalonia.Markup.Xaml set => _context.IsDesignMode = value; } - private static AvaloniaXamlSchemaContext GetContext() - { - var result = AvaloniaLocator.Current.GetService(); - - if (result == null) - { - result = AvaloniaXamlSchemaContext.Create(); - - AvaloniaLocator.CurrentMutable - .Bind() - .ToConstant(result); - } - - return result; - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Avalonia.DesignerSupport/DesignerEventConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs similarity index 74% rename from src/Avalonia.DesignerSupport/DesignerEventConverter.cs rename to src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs index b420448698..665e71bfea 100644 --- a/src/Avalonia.DesignerSupport/DesignerEventConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs @@ -4,11 +4,13 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.PortableXaml; using Portable.Xaml; -namespace Avalonia.DesignerSupport +namespace Avalonia.Markup.Xaml.Converters { - internal class DesignerEventConverter : TypeConverter + internal class AvaloniaEventConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { @@ -72,13 +74,23 @@ namespace Avalonia.DesignerSupport } } - // 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(); + var contextProvider = (IXamlSchemaContextProvider)context.GetService(typeof(IXamlSchemaContextProvider)); + var avaloniaContext = (AvaloniaXamlSchemaContext)contextProvider.SchemaContext; + + if (avaloniaContext.IsDesignMode) + { + // 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(); + } + else + { + throw new XamlObjectWriterException($"Referenced value method {text} in type {target.GetType()} indicated by event {eventType.FullName} was not found"); + } } } return base.ConvertFrom(context, culture, value); diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index 5d2edfcc1b..ab55261737 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit 5d2edfcc1b2988f80303b1b2f3dd2b7c3de53db7 +Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs new file mode 100644 index 0000000000..6ea3015138 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Controls; +using Avalonia.Input; +using Portable.Xaml; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class EventTests + { + [Fact] + public void Event_Is_Attached() + { + var xaml = @"