Browse Source

WIP: really cache xaml types and props and other and other minor things

donandren 8 years ago
parent
commit
9d93b83fa6

+ 18 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticExtension.cs

@@ -1,7 +1,12 @@
 // 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 Portable.Xaml;
+using Portable.Xaml.ComponentModel;
+using Portable.Xaml.Markup;
 using System;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
@@ -21,6 +26,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
+            var tdc = (ITypeDescriptorContext)serviceProvider;
+            var ns = tdc.GetService<IXamlNamespaceResolver>();
+
+            if (!ns.GetNamespacePrefixes().Any())
+            {
+               //TODO: issue of portable.xaml ??? investigate
+                var cvp = tdc.GetService<IRootObjectProvider>();
+                var pvt = tdc.GetService<IProvideValueTarget>(); 
+                //ok we have a problem namespaces shouldn't be empty
+                var nslist = ns.GetNamespacePrefixes() as IList<NamespaceDeclaration>;
+                nslist.Add(new NamespaceDeclaration("https://github.com/avaloniaui", ""));
+                //this is just a hack
+            }
             return base.ProvideValue(serviceProvider);
         }
     }

+ 10 - 0
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AttributeExtensions.cs

@@ -25,5 +25,15 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
             return new pm.AmbientAttribute();
         }
+
+        public static pm.DependsOnAttribute ToPortableXaml(this avm.DependsOnAttribute attrib)
+        {
+            if (attrib == null)
+            {
+                return null;
+            }
+
+            return new pm.DependsOnAttribute(attrib.Name);
+        }
     }
 }

+ 6 - 0
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs

@@ -23,6 +23,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
         {
             Attribute result = null;
 
+            
             if (attributeType == typeof(pm.XamlDeferLoadAttribute))
             {
                 result = _info.GetCustomAttribute<avm.TemplateContentAttribute>(inherit)
@@ -33,6 +34,11 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                 result = _info.GetCustomAttribute<avm.AmbientAttribute>(inherit)
                                 .ToPortableXaml();
             }
+            else if(attributeType == typeof(pm.DependsOnAttribute))
+            {
+                result = _info.GetCustomAttribute<avm.DependsOnAttribute>(inherit)
+                                .ToPortableXaml();
+            }
 
             if (result == null)
             {

+ 2 - 6
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs

@@ -195,10 +195,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                 {
                     EndInit();
                 }
-                //else
-                //{
-                //    AddTargetIfNeeded(target);
-                //}
             }
 
             private void AddTargetIfNeeded(object target)
@@ -227,7 +223,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                 //TODO: revisit this
                 //apply delayed values and clear
                 //that's the last object let's set all delayed bindings
-                foreach (var dv in Values.Reverse().Where(v => v.Member != null))
+                foreach (var dv in Values.Where(v => v.Member != null))
                 {
                     dv.Member.Invoker.SetValue(dv.Target, dv.Value);
                 }
@@ -235,7 +231,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                 //TODO: check/add some order of end init
                 //currently we are sending end init in the order of
                 //objects creation
-                foreach (var v in Values.Reverse())
+                foreach (var v in Values)
                 {
                     var target = v.Target;
 

+ 36 - 20
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@@ -14,6 +14,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
     {
         public AvaloniaXamlSchemaContext(IRuntimeTypeProvider typeProvider)
         //better not set the references assemblies
+        //TODO: check this on iOS 
         //: base(typeProvider.ReferencedAssemblies)
         {
             _avaloniaTypeProvider = typeProvider;
@@ -160,7 +161,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
         protected override XamlMember GetAttachableProperty(string attachablePropertyName, MethodInfo getter, MethodInfo setter)
         {
-            var key = new Tuple<MemberInfo, MemberInfo>(getter, setter);
+            var key = MemberKey.Create(getter ?? setter, attachablePropertyName, "a");
 
             XamlMember result;
 
@@ -196,7 +197,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
             XamlMember result;
 
-            var key = new Tuple<MemberInfo, MemberInfo>(pi, null);
+            var key = MemberKey.Create(pi, "p");
 
             if (_cachedMembers.TryGetValue(key, out result))
             {
@@ -205,24 +206,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
             var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(objType, name);
 
-            var assignBindingAttr = pi.GetCustomAttribute<AssignBindingAttribute>();
-
             if (avProp != null)
             {
-                result = new AvaloniaPropertyXamlMember(avProp, pi, this)
-                {
-                    AssignBinding = assignBindingAttr != null
-                };
-            }
-
-            if (result == null)
-            {
-                var dependAttr = pi.GetCustomAttribute<am.DependsOnAttribute>();
-
-                if (dependAttr != null)
-                {
-                    result = new DependOnXamlMember(dependAttr.Name, pi, this);
-                }
+                result = new AvaloniaPropertyXamlMember(avProp, pi, this);
             }
 
             if (result == null)
@@ -235,7 +221,37 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
         private Dictionary<Type, XamlType> _cachedTypes = new Dictionary<Type, XamlType>();
 
-        private Dictionary<Tuple<MemberInfo, MemberInfo>, XamlMember> _cachedMembers =
-                        new Dictionary<Tuple<MemberInfo, MemberInfo>, XamlMember>();
+        private Dictionary<MemberKey, XamlMember> _cachedMembers = new Dictionary<MemberKey, XamlMember>();
+
+        private struct MemberKey
+        {
+            public static MemberKey Create(MemberInfo m, string name, string memberType)
+            {
+                return new MemberKey(m.DeclaringType, name, memberType);
+            }
+
+            public static MemberKey Create(MemberInfo m, string memberType)
+            {
+                return Create(m, m.Name, memberType);
+            }
+
+            public MemberKey(Type type, object member, string memberType)
+            {
+                Type = type;
+                Member = member;
+                MemberType = memberType;
+            }
+
+            public Type Type { get; }
+
+            public object Member { get; }
+
+            public string MemberType { get; }
+
+            public override string ToString()
+            {
+                return $"{MemberType}:{Type.Namespace}:{Type.Name}.{Member}";
+            }
+        }
     }
 }

+ 114 - 77
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@@ -3,15 +3,18 @@ using Avalonia.Data;
 using Avalonia.Markup.Xaml.Data;
 using Avalonia.Markup.Xaml.MarkupExtensions;
 using Portable.Xaml;
-using Portable.Xaml.ComponentModel;
 using Portable.Xaml.Schema;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Xml.Serialization;
 
 namespace Avalonia.Markup.Xaml.PortableXaml
 {
+    using Metadata;
+    using PropertyKey = Tuple<Type, string>;
+
     public class AvaloniaXamlType : XamlType
     {
         public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) :
@@ -80,26 +83,131 @@ namespace Avalonia.Markup.Xaml.PortableXaml
         {
         }
 
+        private static readonly List<PropertyKey> _readonlyProps =
+            new List<PropertyKey>()
+            {
+                new PropertyKey(typeof(MultiBinding),nameof(MultiBinding.Bindings)),
+                new PropertyKey(typeof(Panel),nameof(Panel.Children)),
+            };
+
         protected override MethodInfo LookupUnderlyingSetter()
         {
+            var key = new PropertyKey(DeclaringType.UnderlyingType, Name);
+
             //if we have content property a list
             //we have some issues in portable.xaml
             //but if the list is read only, this is solving the problem
             //TODO: investigate is this good enough as solution ???
             //We can add ReadOnyAttribute to cover this
-            if ((Type.IsCollection || Type.IsDictionary) &&
-                 Name == DeclaringType.ContentProperty?.Name)
+            if (_readonlyProps.Contains(key))
             {
                 return null;
             }
 
             return base.LookupUnderlyingSetter();
         }
+
+        protected override XamlMemberInvoker LookupInvoker()
+        {
+            return new PropertyInvoker(this);
+        }
+
+        protected override XamlType LookupType()
+        {
+            var pi = UnderlyingMember as PropertyInfo;
+            if (pi != null)
+            {
+                if (pi.PropertyType == typeof(IEnumerable))
+                {
+                    //let's threat IEnumerable property as list
+                    return DeclaringType.SchemaContext.GetXamlType(typeof(IList));
+                }
+            }
+
+            return base.LookupType();
+        }
+
+        private IList<XamlMember> _dependsOn;
+
+        protected override IList<XamlMember> LookupDependsOn()
+        {
+            if (_dependsOn == null)
+            {
+                var attrib = UnderlyingMember.GetCustomAttribute<DependsOnAttribute>(true);
+
+                if (attrib != null)
+                {
+                    var member = DeclaringType.GetMember(attrib.Name);
+
+                    _dependsOn = new XamlMember[] { member };
+                }
+                else
+                {
+                    _dependsOn = base.LookupDependsOn();
+                }
+            }
+
+            return _dependsOn;
+        }
+
+        private class PropertyInvoker : XamlMemberInvoker
+        {
+            public PropertyInvoker(XamlMember member) : base(member)
+            {
+            }
+
+            public override void SetValue(object instance, object value)
+            {
+                if (Member.DependsOn.Count == 1 &&
+                    value is string)
+                {
+                    value = TransformDependsOnValue(instance, value);
+                }
+
+                if (value is XamlBinding)
+                {
+                    value = (value as XamlBinding).Value;
+                }
+
+                base.SetValue(instance, value);
+            }
+
+            private object TransformDependsOnValue(object instance, object value)
+            {
+                if (value is string &&
+                        (Member.UnderlyingMember as PropertyInfo)
+                                        .PropertyType != typeof(string))
+                {
+                    var dpm = Member.DependsOn[0];
+
+                    object depPropValue = dpm.Invoker.GetValue(instance);
+
+                    Type targetType = (depPropValue as AvaloniaProperty)?.PropertyType ??
+                                                    (depPropValue as Type);
+
+                    if (targetType == null)
+                    {
+                        return value;
+                    }
+
+                    var xamTargetType = Member.DeclaringType.SchemaContext.GetXamlType(targetType);
+                    var ttConv = xamTargetType?.TypeConverter?.ConverterInstance;
+                    if (ttConv != null)
+                    {
+                        value = ttConv.ConvertFromString(value as string);
+                    }
+                }
+
+                return value;
+            }
+        }
     }
 
     public class AvaloniaPropertyXamlMember : PropertyXamlMember
     {
-        public bool AssignBinding { get; set; } = false;
+        private bool? _assignBinding;
+
+        public bool AssignBinding => (bool)(_assignBinding ?? (_assignBinding = UnderlyingMember.GetCustomAttribute<AssignBindingAttribute>() != null));
 
         public AvaloniaProperty Property { get; }
 
@@ -191,11 +299,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                     obj.Bind(property, binding);
             }
 
-            public void SetValue(ITypeDescriptorContext context, object instance, object value)
-            {
-                throw new NotImplementedException();
-            }
-
             private AvaloniaProperty Property => Member.Property;
 
             private new AvaloniaPropertyXamlMember Member =>
@@ -209,7 +312,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
         public AvaloniaAttachedPropertyXamlMember(AvaloniaProperty property,
                                                     string attachablePropertyName,
-                                                    MethodInfo getter, MethodInfo setter, 
+                                                    MethodInfo getter, MethodInfo setter,
                                                     XamlSchemaContext schemaContext)
             : base(property, attachablePropertyName, getter, setter, schemaContext)
         {
@@ -218,75 +321,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
         protected override MethodInfo LookupUnderlyingSetter()
         {
+            return base.LookupUnderlyingSetter();
             //TODO: investigate don't call base stack overflow
             return _setter;
         }
     }
-
-    public class DependOnXamlMember : PropertyXamlMember
-    {
-        private string _dependOn;
-
-        public DependOnXamlMember(string dependOn,
-            PropertyInfo propertyInfo,
-            XamlSchemaContext schemaContext) :
-            base(propertyInfo, schemaContext)
-        {
-            _dependOn = dependOn;
-        }
-
-        private XamlMember _dependOnMember;
-
-        public XamlMember DependOnMember
-        {
-            get
-            {
-                return _dependOnMember ??
-                        (_dependOnMember = DeclaringType.GetMember(_dependOn));
-            }
-        }
-
-        protected override IList<XamlMember> LookupDependsOn()
-        {
-            return new List<XamlMember>() { DeclaringType.GetMember(_dependOn) };
-        }
-
-        protected override XamlMemberInvoker LookupInvoker()
-        {
-            return new DependOnInvoker(this);
-        }
-
-        private class DependOnInvoker : XamlMemberInvoker
-        {
-            public DependOnInvoker(XamlMember member) : base(member)
-            {
-            }
-
-            public override void SetValue(object instance, object value)
-            {
-                if (value is string &&
-                    (Member.UnderlyingMember as PropertyInfo).PropertyType != typeof(string))
-                {
-                    var dpm = (Member as DependOnXamlMember).DependOnMember.UnderlyingMember;
-                    var pi = (dpm as PropertyInfo);
-                    var avp = pi.GetValue(instance) as AvaloniaProperty;
-
-                    Type targetType = avp != null ? avp.PropertyType : pi.PropertyType;
-
-                    var xamTargetType = Member.DeclaringType.SchemaContext.GetXamlType(targetType);
-                    var ttConv = xamTargetType?.TypeConverter?.ConverterInstance;
-                    if (ttConv != null)
-                    {
-                        value = ttConv.ConvertFromString(value as string);
-                    }
-                }
-                if (value is XamlBinding)
-                {
-                    value = (value as XamlBinding).Value;
-                }
-
-                base.SetValue(instance, value);
-            }
-        }
-    }
 }

+ 6 - 1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs

@@ -1,9 +1,12 @@
 using Avalonia.Controls;
 using Avalonia.Data;
 using Avalonia.Styling;
+using Portable.Xaml;
 using Portable.Xaml.ComponentModel;
 using Portable.Xaml.Markup;
 using System;
+using System.Linq;
+
 
 namespace Avalonia.Markup.Xaml.PortableXaml
 {
@@ -45,7 +48,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
             //        .Select(x => x.Instance)
             //        .OfType<IStyle>()
             //        .FirstOrDefault();
-            return anchor ?? context.GetLastOrDefaultAmbientValue<IStyle>();
+            var rs = context.GetService<IRootObjectProvider>().RootObject as IStyle;
+
+            return anchor ?? rs ?? context.GetLastOrDefaultAmbientValue<IStyle>();
         }
 
         private XamlBinding(IBinding binding, object anchor)

+ 72 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@@ -117,6 +117,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             Assert.Equal("Foo", button.Content);
         }
 
+        [Fact]
+        public void Direct_Content_In_ItemsControl_Is_Operational()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'>
+     <ItemsControl Name='items'>
+         <ContentControl>Foo</ContentControl>
+         <ContentControl>Bar</ContentControl>
+      </ItemsControl>
+</Window>";
+
+                var control = AvaloniaXamlLoader.Parse<Window>(xaml);
+
+                var itemsControl = control.FindControl<ItemsControl>("items");
+
+                Assert.NotNull(itemsControl);
+
+                var items = itemsControl.Items.Cast<ContentControl>().ToArray();
+
+                Assert.Equal("Foo", items[0].Content);
+                Assert.Equal("Bar", items[1].Content);
+            }
+        }
+
         [Fact]
         public void Panel_Children_Are_Added()
         {
@@ -471,6 +497,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 
                 var items = new string[] { "Foo", "Bar" };
 
+                //DelayedBinding.ApplyBindings(itemsControl);
+
                 target.DataContext = items;
 
                 Assert.Equal(items, itemsControl.Items);
@@ -626,5 +654,49 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(0, parentSet);
                 Assert.Equal(1, widthChanged);
         }
+
+
+        [Fact]
+        public void BeginInit_Matches_EndInit()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+             xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+             xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <local:InitializationOrderTracker />
+</Window>";
+
+                var window = AvaloniaXamlLoader.Parse<Window>(xaml);
+                var tracker = (InitializationOrderTracker)window.Content;
+
+                Assert.Equal(0, tracker.InitState);
+            }
+        }
+
+
+        [Fact]
+        public void DeferedXamlLoader_Should_Preserve_NamespacesContext()
+        {
+            var xaml =
+@"<ContentControl xmlns='https://github.com/avaloniaui'
+            xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <ContentControl.ContentTemplate>
+        <DataTemplate>
+            <TextBlock  Tag='{Static local:NonControl.StringProperty}'/>
+        </DataTemplate>
+    </ContentControl.ContentTemplate>
+</ContentControl>";
+
+            var contentControl = AvaloniaXamlLoader.Parse<ContentControl>(xaml);
+            var template = contentControl.ContentTemplate;
+
+            Assert.NotNull(template);
+
+            var txt = (TextBlock)template.Build(null);
+
+            Assert.Equal((object)NonControl.StringProperty, txt.Tag);
+        }
     }
 }

+ 18 - 3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs

@@ -1,16 +1,19 @@
 // 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.Collections.Generic;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
+using System.Collections.Generic;
 
 namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 {
-    public class InitializationOrderTracker : Control
+    public class InitializationOrderTracker : Control,
+        ISupportInitialize
     {
         public IList<string> Order { get; } = new List<string>();
 
+        public int InitState { get; private set; }
+
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             Order.Add("AttachedToLogicalTree");
@@ -22,5 +25,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             Order.Add($"Property {e.Property.Name} Changed");
             base.OnPropertyChanged(e);
         }
+
+        void ISupportInitialize.BeginInit()
+        {
+            ++InitState;
+            base.BeginInit();
+        }
+
+        void ISupportInitialize.EndInit()
+        {
+            --InitState;
+            base.EndInit();
+        }
     }
-}
+}