Browse Source

Merge pull request #3 from AvaloniaUI/master

Merge
Benedikt Schroeder 7 năm trước cách đây
mục cha
commit
0f242aed2a
99 tập tin đã thay đổi với 1471 bổ sung748 xóa
  1. 17 0
      .github/PULL_REQUEST_TEMPLATE.md
  2. 10 2
      samples/BindingTest/MainWindow.xaml
  3. 2 2
      samples/ControlCatalog/DecoratedWindow.xaml
  4. 1 1
      samples/ControlCatalog/MainView.xaml
  5. 1 1
      samples/ControlCatalog/MainWindow.xaml
  6. 1 1
      samples/RenderTest/MainWindow.xaml
  7. 5 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  8. 4 5
      src/Avalonia.Base/AttachedProperty.cs
  9. 19 31
      src/Avalonia.Base/AvaloniaObject.cs
  10. 6 2
      src/Avalonia.Base/AvaloniaProperty.cs
  11. 133 150
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  12. 3 0
      src/Avalonia.Base/DirectProperty.cs
  13. 7 0
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  14. 16 0
      src/Avalonia.Base/Platform/IAssetLoader.cs
  15. 1 1
      src/Avalonia.Controls/ColumnDefinitions.cs
  16. 4 3
      src/Avalonia.Controls/ContextMenu.cs
  17. 6 8
      src/Avalonia.Controls/GridLength.cs
  18. 8 2
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  19. 5 0
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  20. 31 8
      src/Avalonia.Controls/Primitives/Popup.cs
  21. 26 0
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  22. 2 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  23. 1 1
      src/Avalonia.Controls/RowDefinitions.cs
  24. 14 0
      src/Avalonia.Controls/Window.cs
  25. 5 0
      src/Avalonia.Controls/WindowBase.cs
  26. 3 1
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  27. 1 1
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  28. 8 0
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  29. 8 0
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  30. 1 1
      src/Avalonia.Diagnostics/Views/TreePageView.xaml
  31. 6 6
      src/Avalonia.Input/DragDropDevice.cs
  32. 25 2
      src/Avalonia.Input/DragEventArgs.cs
  33. 4 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  34. 18 22
      src/Avalonia.Visuals/CornerRadius.cs
  35. 2 3
      src/Avalonia.Visuals/Matrix.cs
  36. 2 3
      src/Avalonia.Visuals/Point.cs
  37. 2 3
      src/Avalonia.Visuals/Rect.cs
  38. 4 5
      src/Avalonia.Visuals/RelativePoint.cs
  39. 6 7
      src/Avalonia.Visuals/RelativeRect.cs
  40. 2 2
      src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs
  41. 2 3
      src/Avalonia.Visuals/Size.cs
  42. 13 11
      src/Avalonia.Visuals/Thickness.cs
  43. 18 13
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  44. 15 0
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  45. 2 0
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  46. 0 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  47. 232 6
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  48. 0 227
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs
  49. 1 4
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  50. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
  51. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs
  52. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
  53. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs
  54. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  55. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
  56. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs
  57. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs
  58. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs
  59. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs
  60. 0 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  61. 8 6
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
  62. 1 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  63. 21 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  64. 1 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  65. 6 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  66. 2 1
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs
  67. 52 3
      src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
  68. 4 0
      src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
  69. 15 3
      src/OSX/Avalonia.MonoMac/WindowImpl.cs
  70. 21 2
      src/Shared/PlatformSupport/AssetLoader.cs
  71. 0 1
      src/Windows/Avalonia.Win32/DataObject.cs
  72. 49 4
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  73. 2 3
      src/Windows/Avalonia.Win32/OleDataObject.cs
  74. 0 1
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  75. 0 7
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  76. 10 14
      src/Windows/Avalonia.Win32/Win32Platform.cs
  77. 70 14
      src/Windows/Avalonia.Win32/WindowImpl.cs
  78. 4 0
      src/iOS/Avalonia.iOS/EmbeddableImpl.cs
  79. 52 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs
  80. 52 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  81. 4 5
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  82. 2 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs
  83. 34 5
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  84. 33 73
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  85. 9 9
      tests/Avalonia.Controls.UnitTests/GridLengthTests.cs
  86. 9 9
      tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs
  87. 71 0
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  88. 75 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  89. 14 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs
  90. 34 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  91. 66 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  92. 17 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs
  93. 5 0
      tests/Avalonia.UnitTests/MockAssetLoader.cs
  94. 4 4
      tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
  95. 1 1
      tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
  96. 1 1
      tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs
  97. 2 2
      tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs
  98. 3 3
      tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs
  99. 4 4
      tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs

+ 17 - 0
.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,17 @@
+This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
+
+- What does the pull request do?
+- What is the current behavior?
+- What is the updated/expected behavior with this PR?
+- How was the solution implemented (if it's not obvious)?
+
+Checklist:
+
+- [ ] Added unit tests (if possible)?
+- [ ] Added XML documentation to any related classes?
+- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
+
+If the pull request fixes issue(s) list them like this:
+
+Fixes #123
+Fixes #456

+ 10 - 2
samples/BindingTest/MainWindow.xaml

@@ -1,11 +1,15 @@
 <Window xmlns="https://github.com/avaloniaui"
-        xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest"
-        xmlns:local="clr-namespace:BindingTest;assembly=BindingTest">
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:vm="clr-namespace:BindingTest.ViewModels"
+        xmlns:local="clr-namespace:BindingTest">
   <Window.Styles>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="18"/>
     </Style>
   </Window.Styles>
+  <Window.Resources>
+    <vm:TestItem x:Key="SharedItem" StringValue="shared" />
+  </Window.Resources>
   
   <TabControl>
     <TabItem Header="Basic">
@@ -40,6 +44,10 @@
             <TextBlock FontSize="16" Text="Binding Sources"/>
             <TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True" 
                      Text="{Binding #first.Text, Mode=TwoWay}"/>
+            <TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True"
+                     Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
+            <TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True"
+                     Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
           </StackPanel>
           <StackPanel Margin="18" Gap="4" Width="200" HorizontalAlignment="Left">
             <TextBlock FontSize="16" Text="Scheduler"/>

+ 2 - 2
samples/ControlCatalog/DecoratedWindow.xaml

@@ -1,7 +1,7 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
         Title="Avalonia Control Gallery"
-        Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
-        xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog" HasSystemDecorations="False">
+        Icon="resm:ControlCatalog.Assets.test_icon.ico"
+        xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False">
     <Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
         <DockPanel  Grid.Column="1"  Grid.Row="1" >
             <Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

+ 1 - 1
samples/ControlCatalog/MainView.xaml

@@ -1,5 +1,5 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-        xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog"
+        xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <TabControl Classes="sidebar" Name="Sidebar">
     <TabControl.Transition>

+ 1 - 1
samples/ControlCatalog/MainWindow.xaml

@@ -1,6 +1,6 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
         Title="Avalonia Control Gallery"
         Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
-        xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog">
+        xmlns:local="clr-namespace:ControlCatalog">
     <local:MainView/>
 </Window>

+ 1 - 1
samples/RenderTest/MainWindow.xaml

@@ -1,6 +1,6 @@
 <Window xmlns="https://github.com/avaloniaui"
         Title="Avalonia Render Test"
-        xmlns:pages="clr-namespace:RenderTest.Pages;assembly=RenderTest">
+        xmlns:pages="clr-namespace:RenderTest.Pages">
   <DockPanel>
     <Menu DockPanel.Dock="Top">
       <MenuItem Header="Rendering">

+ 5 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _clientSize = value;
             UpdateParams();
         }
-        
+
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+        }
+
         public IScreenImpl Screen { get; }
 
         public Point Position

+ 4 - 5
src/Avalonia.Base/AttachedProperty.cs

@@ -9,7 +9,7 @@ namespace Avalonia
     /// An attached avalonia property.
     /// </summary>
     /// <typeparam name="TValue">The type of the property's value.</typeparam>
-    public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
+    public class AttachedProperty<TValue> : StyledProperty<TValue>
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
@@ -35,11 +35,10 @@ namespace Avalonia
         /// </summary>
         /// <typeparam name="TOwner">The owner type.</typeparam>
         /// <returns>The property.</returns>
-        public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
+        public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
         {
-            var result = new StyledProperty<TValue>(this, typeof(TOwner));
-            AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
-            return result;
+            AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
+            return this;
         }
     }
 }

+ 19 - 31
src/Avalonia.Base/AvaloniaObject.cs

@@ -12,7 +12,6 @@ using Avalonia.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.Threading;
 using Avalonia.Utilities;
-using System.Reactive.Concurrency;
 
 namespace Avalonia
 {
@@ -218,11 +217,6 @@ namespace Avalonia
             }
             else
             {
-                if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
-                {
-                    ThrowNotRegistered(property);
-                }
-
                 return GetValueInternal(property);
             }
         }
@@ -377,11 +371,6 @@ namespace Avalonia
             {
                 PriorityValue v;
 
-                if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
-                {
-                    ThrowNotRegistered(property);
-                }
-
                 if (!_values.TryGetValue(property, out v))
                 {
                     v = CreatePriorityValue(property);
@@ -804,11 +793,6 @@ namespace Avalonia
 
             var originalValue = value;
 
-            if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
-            {
-                ThrowNotRegistered(property);
-            }
-
             if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
             {
                 throw new ArgumentException(string.Format(
@@ -836,18 +820,32 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Given a <see cref="AvaloniaProperty"/> returns a registered avalonia property that is
-        /// equal or throws if not found.
+        /// Given a direct property, returns a registered avalonia property that is equivalent or
+        /// throws if not found.
         /// </summary>
         /// <param name="property">The property.</param>
         /// <returns>The registered property.</returns>
-        public AvaloniaProperty GetRegistered(AvaloniaProperty property)
+        private AvaloniaProperty GetRegistered(AvaloniaProperty property)
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property);
+            var direct = property as IDirectPropertyAccessor;
+
+            if (direct == null)
+            {
+                throw new AvaloniaInternalException(
+                    "AvaloniaObject.GetRegistered should only be called for direct properties");
+            }
+
+            if (property.OwnerType.IsAssignableFrom(GetType()))
+            {
+                return property;
+            }
+
+            var result =  AvaloniaPropertyRegistry.Instance.GetRegistered(this)
+                .FirstOrDefault(x => x == property);
 
             if (result == null)
             {
-                ThrowNotRegistered(property);
+                throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
             }
 
             return result;
@@ -898,15 +896,5 @@ namespace Avalonia
                 value,
                 priority);
         }
-
-        /// <summary>
-        /// Throws an exception indicating that the specified property is not registered on this
-        /// object.
-        /// </summary>
-        /// <param name="p">The property</param>
-        private void ThrowNotRegistered(AvaloniaProperty p)
-        {
-            throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
-        }
     }
 }

+ 6 - 2
src/Avalonia.Base/AvaloniaProperty.cs

@@ -311,7 +311,9 @@ namespace Avalonia
                 defaultBindingMode: defaultBindingMode);
 
             var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits);
-            AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
+            var registry = AvaloniaPropertyRegistry.Instance;
+            registry.Register(typeof(TOwner), result);
+            registry.RegisterAttached(typeof(THost), result);
             return result;
         }
 
@@ -344,7 +346,9 @@ namespace Avalonia
                 defaultBindingMode: defaultBindingMode);
 
             var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits);
-            AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
+            var registry = AvaloniaPropertyRegistry.Instance;
+            registry.Register(ownerType, result);
+            registry.RegisterAttached(typeof(THost), result);
             return result;
         }
 

+ 133 - 150
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -4,7 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 
 namespace Avalonia
@@ -14,23 +13,14 @@ namespace Avalonia
     /// </summary>
     public class AvaloniaPropertyRegistry
     {
-        /// <summary>
-        /// The registered properties by type.
-        /// </summary>
         private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
             new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
-
-        /// <summary>
-        /// The registered properties by type cached values to increase performance.
-        /// </summary>
-        private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registeredCache =
-            new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
-
-        /// <summary>
-        /// The registered attached properties by owner type.
-        /// </summary>
         private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
             new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
+        private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache =
+            new Dictionary<Type, List<AvaloniaProperty>>();
+        private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
+            new Dictionary<Type, List<AvaloniaProperty>>();
 
         /// <summary>
         /// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@@ -39,51 +29,68 @@ namespace Avalonia
             = new AvaloniaPropertyRegistry();
 
         /// <summary>
-        /// Gets all attached <see cref="AvaloniaProperty"/>s registered by an owner.
+        /// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
         /// </summary>
-        /// <param name="ownerType">The owner type.</param>
+        /// <param name="type">The type.</param>
         /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
-        public IEnumerable<AvaloniaProperty> GetAttached(Type ownerType)
+        public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
         {
-            Dictionary<int, AvaloniaProperty> inner;
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            if (_registeredCache.TryGetValue(type, out var result))
+            {
+                return result;
+            }
 
-            // Ensure the type's static ctor has been run.
-            RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
+            var t = type;
+            result = new List<AvaloniaProperty>();
 
-            if (_attached.TryGetValue(ownerType, out inner))
+            while (t != null)
             {
-                return inner.Values;
+                // Ensure the type's static ctor has been run.
+                RuntimeHelpers.RunClassConstructor(t.TypeHandle);
+
+                if (_registered.TryGetValue(t, out var registered))
+                {
+                    result.AddRange(registered.Values);
+                }
+
+                t = t.BaseType;
             }
 
-            return Enumerable.Empty<AvaloniaProperty>();
+            _registeredCache.Add(type, result);
+            return result;
         }
 
         /// <summary>
-        /// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
+        /// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
         /// </summary>
         /// <param name="type">The type.</param>
         /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
-        public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
+        public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type)
         {
             Contract.Requires<ArgumentNullException>(type != null);
 
-            while (type != null)
+            if (_attachedCache.TryGetValue(type, out var result))
             {
-                // Ensure the type's static ctor has been run.
-                RuntimeHelpers.RunClassConstructor(type.TypeHandle);
+                return result;
+            }
 
-                Dictionary<int, AvaloniaProperty> inner;
+            var t = type;
+            result = new List<AvaloniaProperty>();
 
-                if (_registered.TryGetValue(type, out inner))
+            while (t != null)
+            {
+                if (_attached.TryGetValue(t, out var attached))
                 {
-                    foreach (var p in inner)
-                    {
-                        yield return p.Value;
-                    }
+                    result.AddRange(attached.Values);
                 }
 
-                type = type.GetTypeInfo().BaseType;
+                t = t.BaseType;
             }
+
+            _attachedCache.Add(type, result);
+            return result;
         }
 
         /// <summary>
@@ -99,142 +106,92 @@ namespace Avalonia
         }
 
         /// <summary>
-        /// Finds a <see cref="AvaloniaProperty"/> registered on a type.
+        /// Finds a registered non-attached property on a type by name.
         /// </summary>
         /// <param name="type">The type.</param>
-        /// <param name="property">The property.</param>
-        /// <returns>The registered property or null if not found.</returns>
-        /// <remarks>
-        /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a 
-        /// different object but is equal according to <see cref="object.Equals(object)"/>.
-        /// </remarks>
-        public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property)
+        /// <param name="name">The property name.</param>
+        /// <returns>
+        /// The registered property or null if no matching property found.
+        /// </returns>
+        /// <exception cref="InvalidOperationException">
+        /// The property name contains a '.'.
+        /// </exception>
+        public AvaloniaProperty FindRegistered(Type type, string name)
         {
-            Type currentType = type;
-            Dictionary<int, AvaloniaProperty> cache;
-            AvaloniaProperty result;
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(name != null);
 
-            if (_registeredCache.TryGetValue(type, out cache))
+            if (name.Contains('.'))
             {
-                if (cache.TryGetValue(property.Id, out result))
-                {
-                    return result;
-                }
+                throw new InvalidOperationException("Attached properties not supported.");
             }
 
-            while (currentType != null)
-            {
-                Dictionary<int, AvaloniaProperty> inner;
-
-                if (_registered.TryGetValue(currentType, out inner))
-                {
-                    if (inner.TryGetValue(property.Id, out result))
-                    {
-                        if (cache == null)
-                        {
-                            _registeredCache[type] = cache = new Dictionary<int, AvaloniaProperty>();
-                        }
-
-                        cache[property.Id] = result;
-
-                        return result;
-                    }
-                }
-
-                currentType = currentType.GetTypeInfo().BaseType;
-            }
-
-            return null;
+            return GetRegistered(type).FirstOrDefault(x => x.Name == name);
         }
 
         /// <summary>
-        /// Finds <see cref="AvaloniaProperty"/> registered on an object.
+        /// Finds a registered non-attached property on a type by name.
         /// </summary>
         /// <param name="o">The object.</param>
-        /// <param name="property">The property.</param>
-        /// <returns>The registered property or null if not found.</returns>
-        /// <remarks>
-        /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
-        /// different object but is equal according to <see cref="object.Equals(object)"/>.
-        /// </remarks>
-        public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property)
+        /// <param name="name">The property name.</param>
+        /// <returns>
+        /// The registered property or null if no matching property found.
+        /// </returns>
+        /// <exception cref="InvalidOperationException">
+        /// The property name contains a '.'.
+        /// </exception>
+        public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
         {
-            return FindRegistered(o.GetType(), property);
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(name != null);
+
+            return FindRegistered(o.GetType(), name);
         }
 
         /// <summary>
-        /// Finds a registered property on a type by name.
+        /// Finds a registered attached property on a type by name.
         /// </summary>
         /// <param name="type">The type.</param>
-        /// <param name="name">
-        /// The property name. If an attached property it should be in the form
-        /// "OwnerType.PropertyName".
-        /// </param>
+        /// <param name="ownerType">The owner type.</param>
+        /// <param name="name">The property name.</param>
         /// <returns>
         /// The registered property or null if no matching property found.
         /// </returns>
-        public AvaloniaProperty FindRegistered(Type type, string name)
+        /// <exception cref="InvalidOperationException">
+        /// The property name contains a '.'.
+        /// </exception>
+        public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
         {
             Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(ownerType != null);
             Contract.Requires<ArgumentNullException>(name != null);
 
-            var parts = name.Split('.');
-            var types = GetImplementedTypes(type).ToList();
-
-            if (parts.Length < 1 || parts.Length > 2)
+            if (name.Contains('.'))
             {
-                throw new ArgumentException("Invalid property name.");
+                throw new InvalidOperationException("Attached properties not supported.");
             }
 
-            string propertyName;
-            var results = GetRegistered(type);
-
-            if (parts.Length == 1)
-            {
-                propertyName = parts[0];
-                results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name));
-            }
-            else
-            {
-                if (!types.Contains(parts[0]))
-                {
-                    results = results.Where(x => x.OwnerType.Name == parts[0]);
-                }
-
-                propertyName = parts[1];
-            }
-
-            return results.FirstOrDefault(x => x.Name == propertyName);
+            return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name);
         }
 
         /// <summary>
-        /// Finds a registered property on an object by name.
+        /// Finds a registered non-attached property on a type by name.
         /// </summary>
         /// <param name="o">The object.</param>
-        /// <param name="name">
-        /// The property name. If an attached property it should be in the form
-        /// "OwnerType.PropertyName".
-        /// </param>
+        /// <param name="ownerType">The owner type.</param>
+        /// <param name="name">The property name.</param>
         /// <returns>
         /// The registered property or null if no matching property found.
         /// </returns>
-        public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
+        /// <exception cref="InvalidOperationException">
+        /// The property name contains a '.'.
+        /// </exception>
+        public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
         {
-            return FindRegistered(o.GetType(), name);
-        }
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(name != null);
 
-        /// <summary>
-        /// Returns a type and all its base types.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns>The type and all its base types.</returns>
-        private IEnumerable<string> GetImplementedTypes(Type type)
-        {
-            while (type != null)
-            {
-                yield return type.Name;
-                type = type.GetTypeInfo().BaseType;
-            }
+            return FindRegisteredAttached(o.GetType(), ownerType, name);
         }
 
         /// <summary>
@@ -245,7 +202,11 @@ namespace Avalonia
         /// <returns>True if the property is registered, otherwise false.</returns>
         public bool IsRegistered(Type type, AvaloniaProperty property)
         {
-            return FindRegistered(type, property) != null;
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            return Instance.GetRegistered(type).Any(x => x == property) ||
+                Instance.GetRegisteredAttached(type).Any(x => x == property);
         }
 
         /// <summary>
@@ -256,6 +217,9 @@ namespace Avalonia
         /// <returns>True if the property is registered, otherwise false.</returns>
         public bool IsRegistered(object o, AvaloniaProperty property)
         {
+            Contract.Requires<ArgumentNullException>(o != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
             return IsRegistered(o.GetType(), property);
         }
 
@@ -274,34 +238,53 @@ namespace Avalonia
             Contract.Requires<ArgumentNullException>(type != null);
             Contract.Requires<ArgumentNullException>(property != null);
 
-            Dictionary<int, AvaloniaProperty> inner;
-
-            if (!_registered.TryGetValue(type, out inner))
+            if (!_registered.TryGetValue(type, out var inner))
             {
                 inner = new Dictionary<int, AvaloniaProperty>();
+                inner.Add(property.Id, property);
                 _registered.Add(type, inner);
             }
-
-            if (!inner.ContainsKey(property.Id))
+            else if (!inner.ContainsKey(property.Id))
             {
                 inner.Add(property.Id, property);
             }
+ 
+            _registeredCache.Clear();
+        }
 
-            if (property.IsAttached)
+        /// <summary>
+        /// Registers an attached <see cref="AvaloniaProperty"/> on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="property">The property.</param>
+        /// <remarks>
+        /// You won't usually want to call this method directly, instead use the
+        /// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
+        /// method.
+        /// </remarks>
+        public void RegisterAttached(Type type, AvaloniaProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            if (!property.IsAttached)
             {
-                if (!_attached.TryGetValue(property.OwnerType, out inner))
-                {
-                    inner = new Dictionary<int, AvaloniaProperty>();
-                    _attached.Add(property.OwnerType, inner);
-                }
+                throw new InvalidOperationException(
+                    "Cannot register a non-attached property as attached.");
+            }
 
-                if (!inner.ContainsKey(property.Id))
-                {
-                    inner.Add(property.Id, property);
-                }
+            if (!_attached.TryGetValue(type, out var inner))
+            {
+                inner = new Dictionary<int, AvaloniaProperty>();
+                inner.Add(property.Id, property);
+                _attached.Add(type, inner);
+            }
+            else
+            {
+                inner.Add(property.Id, property);
             }
 
-            _registeredCache.Clear();
+            _attachedCache.Clear();
         }
     }
 }

+ 3 - 0
src/Avalonia.Base/DirectProperty.cs

@@ -75,6 +75,9 @@ namespace Avalonia
         /// </summary>
         public Action<TOwner, TValue> Setter { get; }
 
+        /// <inheritdoc/>
+        Type IDirectPropertyAccessor.Owner => typeof(TOwner);
+
         /// <summary>
         /// Registers the direct property on another type.
         /// </summary>

+ 7 - 0
src/Avalonia.Base/IDirectPropertyAccessor.cs

@@ -1,6 +1,8 @@
 // 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;
+
 namespace Avalonia
 {
     /// <summary>
@@ -14,6 +16,11 @@ namespace Avalonia
         /// </summary>
         bool IsReadOnly { get; }
 
+        /// <summary>
+        /// Gets the class that registered the property.
+        /// </summary>
+        Type Owner { get; }
+
         /// <summary>
         /// Gets the value of the property on the instance.
         /// </summary>

+ 16 - 0
src/Avalonia.Base/Platform/IAssetLoader.cs

@@ -43,5 +43,21 @@ namespace Avalonia.Platform
         /// The resource was not found.
         /// </exception>
         Stream Open(Uri uri, Uri baseUri = null);
+
+        /// <summary>
+        /// Opens the resource with the requested URI and returns the resource string and the
+        /// assembly containing the resource.
+        /// </summary>
+        /// <param name="uri">The URI.</param>
+        /// <param name="baseUri">
+        /// A base URI to use if <paramref name="uri"/> is relative.
+        /// </param>
+        /// <returns>
+        /// The stream containing the resource contents together with the assembly.
+        /// </returns>
+        /// <exception cref="FileNotFoundException">
+        /// The resource was not found.
+        /// </exception>
+        Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null);
     }
 }

+ 1 - 1
src/Avalonia.Controls/ColumnDefinitions.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Controls
         public ColumnDefinitions(string s)
             : this()
         {
-            AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x)));
+            AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
         }
     }
 }

+ 4 - 3
src/Avalonia.Controls/ContextMenu.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         {
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
 
-            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);            
+            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
         }
 
         /// <summary>
@@ -75,13 +75,14 @@ namespace Avalonia.Controls
         {
             if (control != null)
             {
-                if(_popup == null)
+                if (_popup == null)
                 {
                     _popup = new Popup()
                     {
                         PlacementMode = PlacementMode.Pointer,
                         PlacementTarget = control,
-                        StaysOpen = false                                         
+                        StaysOpen = false,
+                        ObeyScreenEdges = true
                     };
 
                     _popup.Closed += PopupClosed;

+ 6 - 8
src/Avalonia.Controls/GridLength.cs

@@ -180,9 +180,8 @@ namespace Avalonia.Controls
         /// Parses a string to return a <see cref="GridLength"/>.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="GridLength"/>.</returns>
-        public static GridLength Parse(string s, CultureInfo culture)
+        public static GridLength Parse(string s)
         {
             s = s.ToUpperInvariant();
 
@@ -193,12 +192,12 @@ namespace Avalonia.Controls
             else if (s.EndsWith("*"))
             {
                 var valueString = s.Substring(0, s.Length - 1).Trim();
-                var value = valueString.Length > 0 ? double.Parse(valueString, culture) : 1;
+                var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1;
                 return new GridLength(value, GridUnitType.Star);
             }
             else
             {
-                var value = double.Parse(s, culture);
+                var value = double.Parse(s, CultureInfo.InvariantCulture);
                 return new GridLength(value, GridUnitType.Pixel);
             }
         }
@@ -207,15 +206,14 @@ namespace Avalonia.Controls
         /// Parses a string to return a collection of <see cref="GridLength"/>s.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="GridLength"/>.</returns>
-        public static IEnumerable<GridLength> ParseLengths(string s, CultureInfo culture)
+        public static IEnumerable<GridLength> ParseLengths(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture))
             {
                 while (tokenizer.TryReadString(out var item))
                 {
-                    yield return Parse(item, culture);
+                    yield return Parse(item);
                 }
             }
         }

+ 8 - 2
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -55,7 +55,7 @@ namespace Avalonia.Platform
         /// Gets the platform window handle.
         /// </summary>
         IPlatformHandle Handle { get; }
-        
+       
         /// <summary>
         /// Gets the maximum size of a window on the system.
         /// </summary>
@@ -65,7 +65,13 @@ namespace Avalonia.Platform
         /// Sets the client size of the toplevel.
         /// </summary>
         void Resize(Size clientSize);
-        
+
+        /// <summary>
+        /// Minimum width of the window.
+        /// </summary>
+        /// 
+        void SetMinMaxSize(Size minSize, Size maxSize);
+
         /// <summary>
         /// Gets platform specific display information
         /// </summary>

+ 5 - 0
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -45,6 +45,11 @@ namespace Avalonia.Platform
         /// </summary>
         void ShowTaskbarIcon(bool value);
 
+        /// <summary>
+        /// Enables or disables resizing of the window
+        /// </summary>
+        void CanResize(bool value);
+
         /// <summary>
         /// Gets or sets a method called before the underlying implementation is destroyed.
         /// Return true to prevent the underlying implementation from closing.

+ 31 - 8
src/Avalonia.Controls/Primitives/Popup.cs

@@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
             AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
 
+        /// <summary>
+        /// Defines the <see cref="ObeyScreenEdges"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
+            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
+
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// </summary>
@@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementModeProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
+        /// when its opened at a position where it would otherwise overlap the screen edge.
+        /// </summary>
+        public bool ObeyScreenEdges
+        {
+            get => GetValue(ObeyScreenEdgesProperty);
+            set => SetValue(ObeyScreenEdgesProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
         /// </summary>
@@ -216,12 +232,12 @@ namespace Avalonia.Controls.Primitives
                 var window = _topLevel as Window;
                 if (window != null)
                 {
-                    window.Deactivated += WindowDeactivated;                  
+                    window.Deactivated += WindowDeactivated;
                 }
                 else
                 {
                     var parentPopuproot = _topLevel as PopupRoot;
-                    if(parentPopuproot != null && parentPopuproot.Parent!=null)
+                    if (parentPopuproot != null && parentPopuproot.Parent != null)
                     {
                         ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
                     }
@@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives
 
             _popupRoot.Show();
 
+            if (ObeyScreenEdges)
+            {
+                _popupRoot.SnapInsideScreenEdges();
+            }
+
             _ignoreIsOpenChanged = true;
             IsOpen = true;
             _ignoreIsOpenChanged = false;
 
             Opened?.Invoke(this, EventArgs.Empty);
         }
-        
+
         /// <summary>
         /// Closes the popup.
         /// </summary>
@@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives
         /// <returns>The popup's position in screen coordinates.</returns>
         protected virtual Point GetPosition()
         {
-            return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot, 
+            var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
                 HorizontalOffset, VerticalOffset);
+
+            return result;
         }
 
         internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
@@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives
         {
             if (!StaysOpen)
             {
-                if(!IsChildOrThis((IVisual)e.Source))
-                {                     
+                if (!IsChildOrThis((IVisual)e.Source))
+                {
                     Close();
                     e.Handled = true;
                 }
@@ -412,12 +435,12 @@ namespace Avalonia.Controls.Primitives
             IVisual root = child.GetVisualRoot();
             while (root is PopupRoot)
             {
-                if (root == PopupRoot) return true;              
+                if (root == PopupRoot) return true;
                 root = ((PopupRoot)root).Parent.GetVisualRoot();
             }
             return false;
         }
-        
+
         private void WindowDeactivated(object sender, EventArgs e)
         {
             if (!StaysOpen)

+ 26 - 0
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -2,10 +2,12 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Linq;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Presenters;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
+using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Styling;
@@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
 
+        /// <summary>
+        /// Moves the Popups position so that it doesnt overlap screen edges.
+        /// This method can be called immediately after Show has been called.
+        /// </summary>
+        public void SnapInsideScreenEdges()
+        {
+            var window = this.GetSelfAndLogicalAncestors().OfType<Window>().First();
+            
+            var screen = window.Screens.ScreenFromPoint(Position);
+
+            var screenX = Position.X + Bounds.Width - screen.Bounds.X;
+            var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;
+
+            if (screenX > screen.Bounds.Width)
+            {
+                Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
+            }
+
+            if (screenY > screen.Bounds.Height)
+            {
+                Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {

+ 2 - 2
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives
         /// <param name="control">The control.</param>
         /// <returns>The property value.</returns>
         /// <see cref="SetIsTemplateFocusTarget(Control, bool)"/>
-        public bool GetIsTemplateFocusTarget(Control control)
+        public static bool GetIsTemplateFocusTarget(Control control)
         {
             return control.GetValue(IsTemplateFocusTargetProperty);
         }
@@ -223,7 +223,7 @@ namespace Avalonia.Controls.Primitives
         /// attached property is set to true on an element in the control template, then the focus
         /// adorner will be shown around that control instead.
         /// </remarks>
-        public void SetIsTemplateFocusTarget(Control control, bool value)
+        public static void SetIsTemplateFocusTarget(Control control, bool value)
         {
             control.SetValue(IsTemplateFocusTargetProperty, value);
         }

+ 1 - 1
src/Avalonia.Controls/RowDefinitions.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Controls
         public RowDefinitions(string s)
             : this()
         {
-            AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x)));
+            AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x)));
         }
     }
 }

+ 14 - 0
src/Avalonia.Controls/Window.cs

@@ -95,6 +95,9 @@ namespace Avalonia.Controls
                 o => o.WindowStartupLocation,
                 (o, v) => o.WindowStartupLocation = v);
 
+        public static readonly StyledProperty<bool> CanResizeProperty =
+            AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
+
         private readonly NameScope _nameScope = new NameScope();
         private object _dialogResult;
         private readonly Size _maxPlatformClientSize;
@@ -113,6 +116,8 @@ namespace Avalonia.Controls
             ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
 
             IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
+
+            CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue));
         }
 
         /// <summary>
@@ -208,6 +213,15 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Enables or disables resizing of the window
+        /// </summary>
+        public bool CanResize
+        {
+            get { return GetValue(CanResizeProperty); }
+            set { SetValue(CanResizeProperty, value); }
+        }
+
         /// <summary>
         /// Gets or sets the icon of the window.
         /// </summary>

+ 5 - 0
src/Avalonia.Controls/WindowBase.cs

@@ -47,6 +47,11 @@ namespace Avalonia.Controls
         {
             IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
             IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
+
+            MinWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
+            MinHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
+            MaxWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
+            MaxHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
         }
 
         public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)

+ 3 - 1
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform;
@@ -30,7 +31,8 @@ namespace Avalonia.DesignerSupport
                         new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath));
                 }
 
-                var loaded = loader.Load(stream, null, baseUri);
+                var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
+                var loaded = loader.Load(stream, localAsm, null, baseUri);
                 var styles = loaded as Styles;
                 if (styles != null)
                 {

+ 1 - 1
src/Avalonia.DesignerSupport/DesignerAssist.cs

@@ -24,7 +24,7 @@ namespace Avalonia.DesignerSupport
 
                 var loader = new AvaloniaXamlLoader();
                 var baseLight = (IStyle)loader.Load(
-                    new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
+                    new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"), null);
                 Styles.Add(baseLight);
             }
         }

+ 8 - 0
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -67,6 +67,10 @@ namespace Avalonia.DesignerSupport.Remote
             RenderIfNeeded();
         }
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+        }
+
         public IScreenImpl Screen { get; } = new ScreenStub();
 
         public void Activate()
@@ -93,5 +97,9 @@ namespace Avalonia.DesignerSupport.Remote
         public void ShowTaskbarIcon(bool value)
         {
         }
+
+        public void CanResize(bool value)
+        {
+        }
     }
 }

+ 8 - 0
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -78,6 +78,10 @@ namespace Avalonia.DesignerSupport.Remote
 
         public IScreenImpl Screen { get; } = new ScreenStub();
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+        }
+
         public void SetTitle(string title)
         {
         }
@@ -95,6 +99,10 @@ namespace Avalonia.DesignerSupport.Remote
         public void ShowTaskbarIcon(bool value)
         {
         }
+
+        public void CanResize(bool value)
+        {
+        }
     }
 
     class ClipboardStub : IClipboard

+ 1 - 1
src/Avalonia.Diagnostics/Views/TreePageView.xaml

@@ -1,5 +1,5 @@
 <UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels;assembly=Avalonia.Diagnostics">
+             xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
   <Grid ColumnDefinitions="*,4,3*">    
     <TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
       <TreeView.DataTemplates>

+ 6 - 6
src/Avalonia.Input/DragDropDevice.cs

@@ -19,11 +19,11 @@ namespace Avalonia.Input
             return null;
         }
         
-        private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
+        private DragDropEffects RaiseDragEvent(Interactive target, IInputElement inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
         {
             if (target == null)
                 return DragDropEffects.None;
-            var args = new DragEventArgs(routedEvent, data)
+            var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target))
             {
                 RoutedEvent = routedEvent,
                 DragEffects = operation
@@ -35,7 +35,7 @@ namespace Avalonia.Input
         private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
         {
             _lastTarget = GetTarget(inputRoot, point);
-            return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data);
+            return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data);
         }
 
         private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
@@ -43,13 +43,13 @@ namespace Avalonia.Input
             var target = GetTarget(inputRoot, point);
 
             if (target == _lastTarget)
-                return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data);
+                return RaiseDragEvent(target, inputRoot, point, DragDrop.DragOverEvent, effects, data);
             
             try
             {
                 if (_lastTarget != null)
                     _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
-                return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data);
+                return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data);
             }
             finally
             {
@@ -75,7 +75,7 @@ namespace Avalonia.Input
         {
             try
             {
-                return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data);
+                return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DropEvent, effects, data);
             }
             finally 
             {

+ 25 - 2
src/Avalonia.Input/DragEventArgs.cs

@@ -1,17 +1,40 @@
-using Avalonia.Interactivity;
+using System;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Input
 {
     public class DragEventArgs : RoutedEventArgs
     {
+        private Interactive _target;
+        private Point _targetLocation;
+
         public DragDropEffects DragEffects { get; set; }
 
         public IDataObject Data { get; private set; }
 
-        public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data)
+        public Point GetPosition(IVisual relativeTo)
+        {
+            var point = new Point(0, 0);
+
+            if (relativeTo == null)
+            {
+                throw new ArgumentNullException(nameof(relativeTo));
+            }
+
+            if (_target != null)
+            {
+                point = _target.TranslatePoint(_targetLocation, relativeTo);
+            }
+            return point;
+        }
+
+        public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation)
             : base(routedEvent)
         {
             this.Data = data;
+            this._target = target;
+            this._targetLocation = targetLocation;
         }
 
     }

+ 4 - 2
src/Avalonia.Themes.Default/MenuItem.xaml

@@ -45,7 +45,8 @@
             <Popup Name="PART_Popup"
                    PlacementMode="Right"
                    StaysOpen="True"
-                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
+                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
+                   ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="1">
@@ -92,7 +93,8 @@
             </ContentPresenter>
             <Popup Name="PART_Popup"
                    IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
-                   StaysOpen="True">
+                   StaysOpen="True" 
+                   ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="1">

+ 18 - 22
src/Avalonia.Visuals/CornerRadius.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Globalization;
 using System.Linq;
+using Avalonia.Utilities;
 
 namespace Avalonia
 {
@@ -53,31 +54,26 @@ namespace Avalonia
             return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}";
         }
 
-        public static CornerRadius Parse(string s, CultureInfo culture)
+        public static CornerRadius Parse(string s)
         {
-            var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
-                .Select(x => x.Trim())
-                .ToList();
-
-            switch (parts.Count)
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
             {
-                case 1:
-                    var uniform = double.Parse(parts[0], culture);
-                    return new CornerRadius(uniform);
-                case 2:
-                    var top = double.Parse(parts[0], culture);
-                    var bottom = double.Parse(parts[1], culture);
-                    return new CornerRadius(top, bottom);
-                case 4:
-                    var topLeft = double.Parse(parts[0], culture);
-                    var topRight = double.Parse(parts[1], culture);
-                    var bottomRight = double.Parse(parts[2], culture);
-                    var bottomLeft = double.Parse(parts[3], culture);
-                    return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft);
-                default:
+                if (tokenizer.TryReadDouble(out var a))
+                {
+                    if (tokenizer.TryReadDouble(out var b))
                     {
-                        throw new FormatException("Invalid CornerRadius.");
+                        if (tokenizer.TryReadDouble(out var c))
+                        {
+                            return new CornerRadius(a, b, c, tokenizer.ReadDouble());
+                        }
+
+                        return new CornerRadius(a, b);
                     }
+
+                    return new CornerRadius(a);
+                }
+
+                throw new FormatException("Invalid CornerRadius.");
             }
         }
 
@@ -85,7 +81,7 @@ namespace Avalonia
         {
             return cr1.TopLeft.Equals(cr2.TopLeft)
                    && cr1.TopRight.Equals(cr2.TopRight)
-                   && cr1.BottomRight.Equals(cr2.BottomRight) 
+                   && cr1.BottomRight.Equals(cr2.BottomRight)
                    && cr1.BottomLeft.Equals(cr2.BottomLeft);
         }
 

+ 2 - 3
src/Avalonia.Visuals/Matrix.cs

@@ -314,11 +314,10 @@ namespace Avalonia
         /// Parses a <see cref="Matrix"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="Matrix"/>.</returns>
-        public static Matrix Parse(string s, CultureInfo culture)
+        public static Matrix Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix"))
             {
                 return new Matrix(
                     tokenizer.ReadDouble(),

+ 2 - 3
src/Avalonia.Visuals/Point.cs

@@ -170,11 +170,10 @@ namespace Avalonia
         /// Parses a <see cref="Point"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="Thickness"/>.</returns>
-        public static Point Parse(string s, CultureInfo culture)
+        public static Point Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point"))
             {
                 return new Point(
                     tokenizer.ReadDouble(),

+ 2 - 3
src/Avalonia.Visuals/Rect.cs

@@ -487,11 +487,10 @@ namespace Avalonia
         /// Parses a <see cref="Rect"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The parsed <see cref="Rect"/>.</returns>
-        public static Rect Parse(string s, CultureInfo culture)
+        public static Rect Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect"))
             {
                 return new Rect(
                     tokenizer.ReadDouble(),

+ 4 - 5
src/Avalonia.Visuals/RelativePoint.cs

@@ -154,11 +154,10 @@ namespace Avalonia
         /// Parses a <see cref="RelativePoint"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The parsed <see cref="RelativePoint"/>.</returns>
-        public static RelativePoint Parse(string s, CultureInfo culture)
+        public static RelativePoint Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint"))
             {
                 var x = tokenizer.ReadString();
                 var y = tokenizer.ReadString();
@@ -180,8 +179,8 @@ namespace Avalonia
                 }
 
                 return new RelativePoint(
-                    double.Parse(x, culture) * scale,
-                    double.Parse(y, culture) * scale,
+                    double.Parse(x, CultureInfo.InvariantCulture) * scale,
+                    double.Parse(y, CultureInfo.InvariantCulture) * scale,
                     unit);
             }
         }

+ 6 - 7
src/Avalonia.Visuals/RelativeRect.cs

@@ -167,11 +167,10 @@ namespace Avalonia
         /// Parses a <see cref="RelativeRect"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The parsed <see cref="RelativeRect"/>.</returns>
-        public static RelativeRect Parse(string s, CultureInfo culture)
+        public static RelativeRect Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect"))
+            using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect"))
             {
                 var x = tokenizer.ReadString();
                 var y = tokenizer.ReadString();
@@ -202,10 +201,10 @@ namespace Avalonia
                 }
 
                 return new RelativeRect(
-                    double.Parse(x, culture) * scale,
-                    double.Parse(y, culture) * scale,
-                    double.Parse(width, culture) * scale,
-                    double.Parse(height, culture) * scale,
+                    double.Parse(x, CultureInfo.InvariantCulture) * scale,
+                    double.Parse(y, CultureInfo.InvariantCulture) * scale,
+                    double.Parse(width, CultureInfo.InvariantCulture) * scale,
+                    double.Parse(height, CultureInfo.InvariantCulture) * scale,
                     unit);
             }
         }

+ 2 - 2
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@@ -41,12 +41,12 @@ namespace Avalonia.Rendering
         {
             add
             {
+                _tick += value;
+
                 if (_subscriberCount++ == 0)
                 {
                     Start();
                 }
-
-                _tick += value;
             }
 
             remove

+ 2 - 3
src/Avalonia.Visuals/Size.cs

@@ -150,11 +150,10 @@ namespace Avalonia
         /// Parses a <see cref="Size"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="Size"/>.</returns>
-        public static Size Parse(string s, CultureInfo culture)
+        public static Size Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
             {
                 return new Size(
                     tokenizer.ReadDouble(),

+ 13 - 11
src/Avalonia.Visuals/Thickness.cs

@@ -165,25 +165,27 @@ namespace Avalonia
         /// Parses a <see cref="Thickness"/> string.
         /// </summary>
         /// <param name="s">The string.</param>
-        /// <param name="culture">The current culture.</param>
         /// <returns>The <see cref="Thickness"/>.</returns>
-        public static Thickness Parse(string s, CultureInfo culture)
+        public static Thickness Parse(string s)
         {
-            using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness"))
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
             {
-                var a = tokenizer.ReadDouble();
-
-                if (tokenizer.TryReadDouble(out var b))
+                if(tokenizer.TryReadDouble(out var a))
                 {
-                    if (tokenizer.TryReadDouble(out var c))
+                    if (tokenizer.TryReadDouble(out var b))
                     {
-                        return new Thickness(a, b, c, tokenizer.ReadDouble());
+                        if (tokenizer.TryReadDouble(out var c))
+                        {
+                            return new Thickness(a, b, c, tokenizer.ReadDouble());
+                        }
+
+                        return new Thickness(a, b);
                     }
 
-                    return new Thickness(a, b);
+                    return new Thickness(a);
                 }
-                
-                return new Thickness(a);
+
+                throw new FormatException("Invalid Thickness.");
             }
         }
 

+ 18 - 13
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -115,6 +115,8 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_set_title(GtkWindow gtkWindow, Utf8Buffer title);
 
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_set_resizable(GtkWindow gtkWindow, bool resizable);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated);
@@ -263,7 +265,7 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_close(GtkWindow window);
 
-            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
@@ -395,6 +397,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry;
         public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea;
         public static D.gtk_window_set_decorated GtkWindowSetDecorated;
+        public static D.gtk_window_set_resizable GtkWindowSetResizable;
         public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint;
         public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint;
         public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint;
@@ -421,6 +424,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect;
         public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest;
         public static D.gtk_window_set_default_size GtkWindowSetDefaultSize;
+        public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints;
         public static D.gtk_window_get_position GtkWindowGetPosition;
         public static D.gtk_window_move GtkWindowMove;
         public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew;
@@ -502,6 +506,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.cairo_set_font_size CairoSetFontSize;
         public static D.cairo_move_to CairoMoveTo;
         public static D.cairo_destroy CairoDestroy;
+
         public const int G_TYPE_OBJECT = 80;
     }
 
@@ -739,19 +744,19 @@ namespace Avalonia.Gtk3.Interop
     }
 
     [StructLayout(LayoutKind.Sequential)]
-    struct GdkGeometry
+    public struct GdkGeometry
     {
-        gint min_width;
-        gint min_height;
-        gint max_width;
-        gint max_height;
-        gint base_width;
-        gint base_height;
-        gint width_inc;
-        gint height_inc;
-        gdouble min_aspect;
-        gdouble max_aspect;
-        gint win_gravity;
+        public gint min_width;
+        public gint min_height;
+        public gint max_width;
+        public gint max_height;
+        public gint base_width;
+        public gint base_height;
+        public gint width_inc;
+        public gint height_inc;
+        public gdouble min_aspect;
+        public gdouble max_aspect;
+        public gint win_gravity;
     }
 
     enum GdkWindowHints

+ 15 - 0
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -341,6 +341,20 @@ namespace Avalonia.Gtk3
             }
         }
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+            if (GtkWidget.IsClosed)
+                return;
+
+            GdkGeometry geometry = new GdkGeometry();
+            geometry.min_width = minSize.Width > 0 ? (int)minSize.Width : -1;
+            geometry.min_height = minSize.Height > 0 ? (int)minSize.Height : -1;
+            geometry.max_width = !Double.IsInfinity(maxSize.Width) && maxSize.Width > 0 ? (int)maxSize.Width : 999999;
+            geometry.max_height = !Double.IsInfinity(maxSize.Height) && maxSize.Height > 0 ? (int)maxSize.Height : 999999;
+
+            Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE);
+        } 
+
         public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
 
         public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
@@ -431,6 +445,7 @@ namespace Avalonia.Gtk3
         {
             if (GtkWidget.IsClosed)
                 return;
+         
             Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height);
             if (OverrideRedirect)
             {

+ 2 - 0
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@@ -61,6 +61,8 @@ namespace Avalonia.Gtk3
         }
 
         public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value);
+
+        public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value);
         
 
         class EmptyDisposable : IDisposable

+ 0 - 1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -29,7 +29,6 @@
         <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
             <Link>Properties\SharedAssemblyInfo.cs</Link>
         </Compile>
-        <Compile Include="AvaloniaXamlLoaderPortableXaml.cs" />
         <Compile Include="AvaloniaXamlLoader.cs" />
         <Compile Include="Converters\CornerRadiusTypeConverter.cs" />
         <Compile Include="Converters\MatrixTypeConverter.cs" />

+ 232 - 6
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -1,11 +1,237 @@
-namespace Avalonia.Markup.Xaml
+// 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.Xaml.Data;
+using Avalonia.Markup.Xaml.PortableXaml;
+using Avalonia.Platform;
+using Portable.Xaml;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+
+namespace Avalonia.Markup.Xaml
 {
-    public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml
+    /// <summary>
+    /// Loads XAML for a avalonia application.
+    /// </summary>
+    public class AvaloniaXamlLoader
     {
-        public static object Parse(string xaml)
-                => new AvaloniaXamlLoader().Load(xaml);
+        private readonly AvaloniaXamlSchemaContext _context = GetContext();
+
+        private static AvaloniaXamlSchemaContext GetContext()
+        {
+            var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
+
+            if (result == null)
+            {
+                result = AvaloniaXamlSchemaContext.Create();
+
+                AvaloniaLocator.CurrentMutable
+                    .Bind<AvaloniaXamlSchemaContext>()
+                    .ToConstant(result);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
+        /// </summary>
+        public AvaloniaXamlLoader()
+        {
+        }
+
+        /// <summary>
+        /// Loads the XAML into a Avalonia component.
+        /// </summary>
+        /// <param name="obj">The object to load the XAML into.</param>
+        public static void Load(object obj)
+        {
+            Contract.Requires<ArgumentNullException>(obj != null);
+
+            var loader = new AvaloniaXamlLoader();
+            loader.Load(obj.GetType(), obj);
+        }
+
+        /// <summary>
+        /// Loads the XAML for a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="rootInstance">
+        /// The optional instance into which the XAML should be loaded.
+        /// </param>
+        /// <returns>The loaded object.</returns>
+        public object Load(Type type, object rootInstance = null)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            // HACK: Currently Visual Studio is forcing us to change the extension of xaml files
+            // in certain situations, so we try to load .xaml and if that's not found we try .xaml.
+            // Ideally we'd be able to use .xaml everywhere
+            var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
+
+            if (assetLocator == null)
+            {
+                throw new InvalidOperationException(
+                    "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
+            }
+
+            foreach (var uri in GetUrisFor(type))
+            {
+                if (assetLocator.Exists(uri))
+                {
+                    using (var stream = assetLocator.Open(uri))
+                    {
+                        var initialize = rootInstance as ISupportInitialize;
+                        initialize?.BeginInit();
+                        try
+                        {
+                            return Load(stream, type.Assembly, rootInstance, uri);
+                        }
+                        finally
+                        {
+                            initialize?.EndInit();
+                        }
+                    }
+                }
+            }
+
+            throw new FileNotFoundException("Unable to find view for " + type.FullName);
+        }
+
+        /// <summary>
+        /// Loads XAML from a URI.
+        /// </summary>
+        /// <param name="uri">The URI of the XAML file.</param>
+        /// <param name="baseUri">
+        /// A base URI to use if <paramref name="uri"/> is relative.
+        /// </param>
+        /// <param name="rootInstance">
+        /// The optional instance into which the XAML should be loaded.
+        /// </param>
+        /// <returns>The loaded object.</returns>
+        public object Load(Uri uri, Uri baseUri = null, object rootInstance = null)
+        {
+            Contract.Requires<ArgumentNullException>(uri != null);
+
+            var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
+
+            if (assetLocator == null)
+            {
+                throw new InvalidOperationException(
+                    "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
+            }
+
+            var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
+            using (var stream = asset.Item1)
+            {
+                try
+                {
+                    return Load(stream, asset.Item2, rootInstance, uri);
+                }
+                catch (Exception e)
+                {
+                    var uriString = uri.ToString();
+                    if (!uri.IsAbsoluteUri)
+                    {
+                        uriString = new Uri(baseUri, uri).AbsoluteUri;
+                    }
+                    throw new XamlLoadException("Error loading xaml at " + uriString, e);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Loads XAML from a string.
+        /// </summary>
+        /// <param name="xaml">The string containing the XAML.</param>
+        /// <param name="localAssembly">Default assembly for clr-namespace:</param>
+        /// <param name="rootInstance">
+        /// The optional instance into which the XAML should be loaded.
+        /// </param>
+        /// <returns>The loaded object.</returns>
+        public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null)
+        {
+            Contract.Requires<ArgumentNullException>(xaml != null);
+
+            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+            {
+                return Load(stream, localAssembly, rootInstance);
+            }
+        }
+
+        /// <summary>
+        /// Loads XAML from a stream.
+        /// </summary>
+        /// <param name="stream">The stream containing the XAML.</param>
+        /// <param name="localAssembly">Default assembly for clr-namespace</param>
+        /// <param name="rootInstance">
+        /// The optional instance into which the XAML should be loaded.
+        /// </param>
+        /// <param name="uri">The URI of the XAML</param>
+        /// <returns>The loaded object.</returns>
+        public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
+        {
+            var readerSettings = new XamlXmlReaderSettings()
+            {
+                BaseUri = uri,
+                LocalAssembly = localAssembly
+            };
+
+            var reader = new XamlXmlReader(stream, _context, readerSettings);
+
+            object result = LoadFromReader(
+                reader,
+                AvaloniaXamlContext.For(readerSettings, rootInstance));
+
+            var topLevel = result as TopLevel;
+
+            if (topLevel != null)
+            {
+                DelayedBinding.ApplyBindings(topLevel);
+            }
+
+            return result;
+        }
+
+        internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null)
+        {
+            var writer = AvaloniaXamlObjectWriter.Create(
+                                    reader.SchemaContext,
+                                    context,
+                                    parentAmbientProvider);
+
+            XamlServices.Transform(reader, writer);
+            writer.ApplyAllDelayedProperties();
+            return writer.Result;
+        }
+
+        internal static object LoadFromReader(XamlReader reader)
+        {
+            //return XamlServices.Load(reader);
+            return LoadFromReader(reader, null);
+        }
+
+        /// <summary>
+        /// Gets the URI for a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>The URI.</returns>
+        private static IEnumerable<Uri> GetUrisFor(Type type)
+        {
+            var asm = type.GetTypeInfo().Assembly.GetName().Name;
+            var typeName = type.FullName;
+            yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
+            yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
+        }
+        
+        public static object Parse(string xaml, Assembly localAssembly = null)
+            => new AvaloniaXamlLoader().Load(xaml, localAssembly);
 
-        public static T Parse<T>(string xaml)
-                     => (T)Parse(xaml);
+        public static T Parse<T>(string xaml, Assembly localAssembly = null)
+            => (T)Parse(xaml, localAssembly);
     }
 }

+ 0 - 227
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs

@@ -1,227 +0,0 @@
-// 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.Xaml.Data;
-using Avalonia.Markup.Xaml.PortableXaml;
-using Avalonia.Platform;
-using Portable.Xaml;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Text;
-
-namespace Avalonia.Markup.Xaml
-{
-    /// <summary>
-    /// Loads XAML for a avalonia application.
-    /// </summary>
-    public class AvaloniaXamlLoaderPortableXaml
-    {
-        private readonly AvaloniaXamlSchemaContext _context = GetContext();
-
-        private static AvaloniaXamlSchemaContext GetContext()
-        {
-            var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
-
-            if (result == null)
-            {
-                result = AvaloniaXamlSchemaContext.Create();
-
-                AvaloniaLocator.CurrentMutable
-                    .Bind<AvaloniaXamlSchemaContext>()
-                    .ToConstant(result);
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
-        /// </summary>
-        public AvaloniaXamlLoaderPortableXaml()
-        {
-        }
-
-        /// <summary>
-        /// Loads the XAML into a Avalonia component.
-        /// </summary>
-        /// <param name="obj">The object to load the XAML into.</param>
-        public static void Load(object obj)
-        {
-            Contract.Requires<ArgumentNullException>(obj != null);
-
-            var loader = new AvaloniaXamlLoader();
-            loader.Load(obj.GetType(), obj);
-        }
-
-        /// <summary>
-        /// Loads the XAML for a type.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <returns>The loaded object.</returns>
-        public object Load(Type type, object rootInstance = null)
-        {
-            Contract.Requires<ArgumentNullException>(type != null);
-
-            // HACK: Currently Visual Studio is forcing us to change the extension of xaml files
-            // in certain situations, so we try to load .xaml and if that's not found we try .xaml.
-            // Ideally we'd be able to use .xaml everywhere
-            var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
-
-            if (assetLocator == null)
-            {
-                throw new InvalidOperationException(
-                    "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
-            }
-
-            foreach (var uri in GetUrisFor(type))
-            {
-                if (assetLocator.Exists(uri))
-                {
-                    using (var stream = assetLocator.Open(uri))
-                    {
-                        var initialize = rootInstance as ISupportInitialize;
-                        initialize?.BeginInit();
-                        try
-                        {
-                            return Load(stream, rootInstance, uri);
-                        }
-                        finally
-                        {
-                            initialize?.EndInit();
-                        }
-                    }
-                }
-            }
-
-            throw new FileNotFoundException("Unable to find view for " + type.FullName);
-        }
-
-        /// <summary>
-        /// Loads XAML from a URI.
-        /// </summary>
-        /// <param name="uri">The URI of the XAML file.</param>
-        /// <param name="baseUri">
-        /// A base URI to use if <paramref name="uri"/> is relative.
-        /// </param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <returns>The loaded object.</returns>
-        public object Load(Uri uri, Uri baseUri = null, object rootInstance = null)
-        {
-            Contract.Requires<ArgumentNullException>(uri != null);
-
-            var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
-
-            if (assetLocator == null)
-            {
-                throw new InvalidOperationException(
-                    "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
-            }
-
-            using (var stream = assetLocator.Open(uri, baseUri))
-            {
-                try
-                {
-                    return Load(stream, rootInstance, uri);
-                }
-                catch (Exception e)
-                {
-                    var uriString = uri.ToString();
-                    if (!uri.IsAbsoluteUri)
-                    {
-                        uriString = new Uri(baseUri, uri).AbsoluteUri;
-                    }
-                    throw new XamlLoadException("Error loading xaml at " + uriString, e);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Loads XAML from a string.
-        /// </summary>
-        /// <param name="xaml">The string containing the XAML.</param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <returns>The loaded object.</returns>
-        public object Load(string xaml, object rootInstance = null)
-        {
-            Contract.Requires<ArgumentNullException>(xaml != null);
-
-            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
-            {
-                return Load(stream, rootInstance);
-            }
-        }
-
-        /// <summary>
-        /// Loads XAML from a stream.
-        /// </summary>
-        /// <param name="stream">The stream containing the XAML.</param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <param name="uri">The URI of the XAML</param>
-        /// <returns>The loaded object.</returns>
-        public object Load(Stream stream, object rootInstance = null, Uri uri = null)
-        {
-            var readerSettings = new XamlXmlReaderSettings()
-            {
-                BaseUri = uri,
-                LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly
-            };
-
-            var reader = new XamlXmlReader(stream, _context, readerSettings);
-
-            object result = LoadFromReader(
-                reader,
-                AvaloniaXamlContext.For(readerSettings, rootInstance));
-
-            var topLevel = result as TopLevel;
-
-            if (topLevel != null)
-            {
-                DelayedBinding.ApplyBindings(topLevel);
-            }
-
-            return result;
-        }
-
-        internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null)
-        {
-            var writer = AvaloniaXamlObjectWriter.Create(
-                                    reader.SchemaContext,
-                                    context);
-
-            XamlServices.Transform(reader, writer);
-            writer.ApplyAllDelayedProperties();
-            return writer.Result;
-        }
-
-        internal static object LoadFromReader(XamlReader reader)
-        {
-            //return XamlServices.Load(reader);
-            return LoadFromReader(reader, null);
-        }
-
-        /// <summary>
-        /// Gets the URI for a type.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns>The URI.</returns>
-        private static IEnumerable<Uri> GetUrisFor(Type type)
-        {
-            var asm = type.GetTypeInfo().Assembly.GetName().Name;
-            var typeName = type.FullName;
-            yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
-            yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
-        }
-    }
-}

+ 1 - 4
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@@ -53,10 +53,7 @@ namespace Avalonia.Markup.Xaml.Converters
                 }
             }
 
-            // First look for non-attached property on the type and then look for an attached property.
-            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, s) ??
-                           AvaloniaPropertyRegistry.Instance.GetAttached(type)
-                           .FirstOrDefault(x => x.Name == propertyName);
+            AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName);
 
             if (property == null)
             {

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return CornerRadius.Parse((string)value, culture);
+            return CornerRadius.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return GridLength.Parse((string)value, culture);
+            return GridLength.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return Matrix.Parse((string)value, culture);
+            return Matrix.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return Point.Parse((string)value, culture);
+            return Point.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters
             var result = new List<Point>(pointStrs.Length);
             foreach (var pointStr in pointStrs)
             {
-                result.Add(Point.Parse(pointStr, culture));
+                result.Add(Point.Parse(pointStr));
             }
 
             return result;

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return Rect.Parse((string)value, culture);
+            return Rect.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return RelativePoint.Parse((string)value, culture);
+            return RelativePoint.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return RelativeRect.Parse((string)value, culture);
+            return RelativeRect.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return Size.Parse((string)value, culture);
+            return Size.Parse((string)value);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
 
         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
         {
-            return Thickness.Parse((string)value, culture);
+            return Thickness.Parse((string)value);
         }
     }
 }

+ 0 - 7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@@ -36,13 +36,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
             // Look upwards though the ambient context for IResourceProviders which might be able
             // to give us the resource.
-            //
-            // TODO: If we're in a template then only the ambient values since the root of the
-            // template wil be included here. We need some way to get hold of the parent ambient
-            // context and search that. See the test:
-            //
-            //   StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File
-            //
             foreach (var ambientValue in ambientValues)
             {
                 // We override XamlType.CanAssignTo in BindingXamlType so the results we get back

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

@@ -11,7 +11,8 @@ namespace Avalonia.Markup.Xaml.PortableXaml
     {
         public static AvaloniaXamlObjectWriter Create(
             XamlSchemaContext schemaContext,
-            AvaloniaXamlContext context)
+            AvaloniaXamlContext context,
+            IAmbientProvider parentAmbientProvider = null)
         {
             var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance };
 
@@ -23,8 +24,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
             };
 
             return new AvaloniaXamlObjectWriter(schemaContext,
-                                                writerSettings.WithContext(context),
-                                                nameScope);
+                writerSettings.WithContext(context),
+                nameScope,
+                parentAmbientProvider);
         }
 
         private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper();
@@ -34,9 +36,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
         private AvaloniaXamlObjectWriter(
             XamlSchemaContext schemaContext,
             XamlObjectWriterSettings settings,
-            AvaloniaNameScope nameScope
-            )
-            : base(schemaContext, settings)
+            AvaloniaNameScope nameScope,
+            IAmbientProvider parentAmbientProvider)
+            : base(schemaContext, settings, parentAmbientProvider)
         {
             _nameScope = nameScope;
         }

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@@ -200,8 +200,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
             var type = (getter ?? setter).DeclaringType;
 
-            var prop = AvaloniaPropertyRegistry.Instance.GetAttached(type)
-                    .FirstOrDefault(v => v.Name == attachablePropertyName);
+            var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName);
 
             if (prop != null)
             {

+ 21 - 1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@@ -19,16 +19,36 @@ namespace Avalonia.Markup.Xaml.PortableXaml
 
     public class AvaloniaXamlType : XamlType
     {
+        static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter();
+
         public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) :
             base(underlyingType, schemaContext)
         {
         }
 
+        protected override XamlMember LookupAttachableMember(string name)
+        {
+            var m =  base.LookupAttachableMember(name);
+
+            if (m == null)
+            {
+                // Might be an AddOwnered attached property.
+                var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name);
+
+                if (avProp?.IsAttached == true)
+                {
+                    return new AvaloniaPropertyXamlMember(avProp, this);
+                }
+            }
+
+            return m;
+        }
+
         protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
         {
             var m = base.LookupMember(name, skipReadOnlyCheck);
 
-            if (m == null)
+            if (m == null && !name.Contains("."))
             {
                 //so far Portable.xaml haven't found the member/property
                 //but what if we have AvaloniaProperty

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@@ -1 +1 @@
-Subproject commit c0664014455392ac221a765e66f9837704339b6f
+Subproject commit cdf46d7892def8a6ba29f12a9339147377f7cf5c

+ 6 - 2
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@@ -10,8 +10,10 @@ namespace Avalonia.Markup.Xaml.Templates
 
     public class TemplateContent
     {
-        public TemplateContent(IEnumerable<NamespaceDeclaration> namespaces, XamlReader reader)
+        public TemplateContent(IEnumerable<NamespaceDeclaration> namespaces, XamlReader reader,
+            IAmbientProvider ambientProvider)
         {
+            ParentAmbientProvider = ambientProvider;
             List = new XamlNodeList(reader.SchemaContext);
 
             //we need to rpeserve all namespace and prefixes to writer
@@ -26,9 +28,11 @@ namespace Avalonia.Markup.Xaml.Templates
 
         public XamlNodeList List { get; }
 
+        private IAmbientProvider ParentAmbientProvider { get; }
+
         public IControl Load()
         {
-            return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader());
+            return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader(), parentAmbientProvider: ParentAmbientProvider);
         }
 
         public static IControl Load(object templateContent)

+ 2 - 1
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs

@@ -14,7 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates
         {
             var tdc = (ITypeDescriptorContext)serviceProvider;
             var ns = tdc.GetService<IXamlNamespaceResolver>();
-            return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader);
+            var ambientProvider = tdc.GetService<IAmbientProvider>();
+            return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider);
         }
 
         public override XamlReader Save(object value, IServiceProvider serviceProvider)

+ 52 - 3
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Linq;
 using System.Reactive.Linq;
 using Avalonia.Data;
 
@@ -15,9 +16,9 @@ namespace Avalonia.Markup.Data.Plugins
         /// <inheritdoc/>
         public bool Match(object obj, string propertyName)
         {
-            if (obj is AvaloniaObject a)
+            if (obj is AvaloniaObject o)
             {
-                return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null;
+                return LookupProperty(o, propertyName) != null;
             }
 
             return false;
@@ -39,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins
 
             var instance = reference.Target;
             var o = (AvaloniaObject)instance;
-            var p = AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName);
+            var p = LookupProperty(o, propertyName);
 
             if (p != null)
             {
@@ -57,6 +58,54 @@ namespace Avalonia.Markup.Data.Plugins
             }
         }
 
+        private static AvaloniaProperty LookupProperty(AvaloniaObject o, string propertyName)
+        {
+            if (!propertyName.Contains("."))
+            {
+                return AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName);
+            }
+            else
+            {
+                var split = propertyName.Split('.');
+
+                if (split.Length == 2)
+                {
+                    // HACK: We need a way to resolve types here using something like IXamlTypeResolver.
+                    // We don't currently have that so we have to make our best guess.
+                    var type = split[0];
+                    var name = split[1];
+                    var registry = AvaloniaPropertyRegistry.Instance;
+                    var registered = registry.GetRegisteredAttached(o.GetType())
+                        .Concat(registry.GetRegistered(o.GetType()));
+
+                    foreach (var p in registered)
+                    {
+                        if (p.Name == name && IsOfType(p.OwnerType, type))
+                        {
+                            return p;
+                        }
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        private static bool IsOfType(Type type, string typeName)
+        {
+            while (type != null)
+            {
+                if (type.Name == typeName)
+                {
+                    return true;
+                }
+
+                type = type.BaseType;
+            }
+
+            return false;
+        }
+
         private class Accessor : PropertyAccessorBase
         {
             private readonly WeakReference<AvaloniaObject> _reference;

+ 4 - 0
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@@ -161,6 +161,10 @@ namespace Avalonia.MonoMac
             Position = pos;
         }
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+        }
+
         public IScreenImpl Screen
         {
             get;

+ 15 - 3
src/OSX/Avalonia.MonoMac/WindowImpl.cs

@@ -9,6 +9,7 @@ namespace Avalonia.MonoMac
     class WindowImpl : WindowBaseImpl, IWindowImpl
     {
         public bool IsDecorated = true;
+        public bool IsResizable = true;
         public CGRect? UndecoratedLastUnmaximizedFrame;
 
         public WindowImpl()
@@ -76,10 +77,15 @@ namespace Avalonia.MonoMac
 
         protected override NSWindowStyle GetStyle()
         {
+            var windowStyle = NSWindowStyle.Borderless;
+
             if (IsDecorated)
-                return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable |
-                       NSWindowStyle.Titled;
-            return NSWindowStyle.Borderless;
+                windowStyle |= NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled;
+
+            if (IsResizable)
+                windowStyle |= NSWindowStyle.Resizable;
+
+            return windowStyle;
         }
 
         public void SetSystemDecorations(bool enabled)
@@ -88,6 +94,12 @@ namespace Avalonia.MonoMac
             UpdateStyle();
         }
 
+        public void CanResize(bool value)
+        {
+            IsResizable = value;
+            UpdateStyle();
+        }
+
         public void SetTitle(string title) => Window.Title = title;
 
         class ModalDisposable : IDisposable

+ 21 - 2
src/Shared/PlatformSupport/AssetLoader.cs

@@ -67,7 +67,23 @@ namespace Avalonia.Shared.PlatformSupport
         /// <exception cref="FileNotFoundException">
         /// The resource was not found.
         /// </exception>
-        public Stream Open(Uri uri, Uri baseUri = null)
+        public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
+        
+        /// <summary>
+        /// Opens the resource with the requested URI and returns the resource string and the
+        /// assembly containing the resource.
+        /// </summary>
+        /// <param name="uri">The URI.</param>
+        /// <param name="baseUri">
+        /// A base URI to use if <paramref name="uri"/> is relative.
+        /// </param>
+        /// <returns>
+        /// The stream containing the resource contents together with the assembly.
+        /// </returns>
+        /// <exception cref="FileNotFoundException">
+        /// The resource was not found.
+        /// </exception>
+        public Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null)
         {
             var asset = GetAsset(uri, baseUri);
 
@@ -76,7 +92,7 @@ namespace Avalonia.Shared.PlatformSupport
                 throw new FileNotFoundException($"The resource {uri} could not be found.");
             }
 
-            return asset.GetStream();
+            return Tuple.Create(asset.GetStream(), asset.Assembly);
         }
 
         private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
@@ -162,6 +178,7 @@ namespace Avalonia.Shared.PlatformSupport
         private interface IAssetDescriptor
         {
             Stream GetStream();
+            Assembly Assembly { get; }
         }
 
         private class AssemblyResourceDescriptor : IAssetDescriptor
@@ -179,6 +196,8 @@ namespace Avalonia.Shared.PlatformSupport
             {
                 return _asm.GetManifestResourceStream(_name);
             }
+
+            public Assembly Assembly => _asm;
         }
 
         private class AssemblyDescriptor

+ 0 - 1
src/Windows/Avalonia.Win32/DataObject.cs

@@ -7,7 +7,6 @@ using System.Text;
 using Avalonia.Input;
 using Avalonia.Win32.Interop;
 using IDataObject = Avalonia.Input.IDataObject;
-using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
 using System.IO;
 using System.Runtime.Serialization.Formatters.Binary;
 

+ 49 - 4
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -558,7 +558,18 @@ namespace Avalonia.Win32.Interop
         {
             DIB_RGB_COLORS = 0,     /* color table in RGBs */
             DIB_PAL_COLORS          /* color table in palette indices */
-        };
+        }
+
+        public enum WindowLongParam
+        {
+            GWL_WNDPROC = -4,
+            GWL_HINSTANCE = -6,
+            GWL_HWNDPARENT = -8,
+            GWL_ID = -12,
+            GWL_STYLE = -16,
+            GWL_EXSTYLE = -20,
+            GWL_USERDATA = -21
+        }
 
         [StructLayout(LayoutKind.Sequential)]
         public struct RGBQUAD
@@ -615,6 +626,16 @@ namespace Avalonia.Win32.Interop
             public uint[] cols;
         }
 
+        [StructLayout(LayoutKind.Sequential)]
+        public struct MINMAXINFO
+        {
+            public POINT ptReserved;
+            public POINT ptMaxSize;
+            public POINT ptMaxPosition;
+            public POINT ptMinTrackSize;
+            public POINT ptMaxTrackSize;
+        }
+
         public const int SizeOf_BITMAPINFOHEADER = 40;
 
         [DllImport("user32.dll")]
@@ -849,6 +870,9 @@ namespace Avalonia.Win32.Interop
             return SetClassLong64(hWnd, nIndex, dwNewLong);
         }
 
+        [DllImport("user32.dll", EntryPoint = "SetCursor")]
+        internal static extern IntPtr SetCursor(IntPtr hCursor);
+
         [DllImport("ole32.dll", PreserveSig = true)]
         internal static extern int CoCreateInstance(ref Guid clsid,
             IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter);
@@ -974,7 +998,7 @@ namespace Avalonia.Win32.Interop
         public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
 
         [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
-        public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
+        public static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
 
 
 
@@ -1366,13 +1390,13 @@ namespace Avalonia.Win32.Interop
     internal interface IDropTarget
     {
         [PreserveSig]
-        UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
+        UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
         [PreserveSig]
         UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
         [PreserveSig]
         UnmanagedMethods.HRESULT DragLeave();
         [PreserveSig]
-        UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
+        UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
     }
 
     [ComImport]
@@ -1387,6 +1411,27 @@ namespace Avalonia.Win32.Interop
     }
 
 
+    [ComImport]
+    [Guid("0000010E-0000-0000-C000-000000000046")]
+    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    public interface IOleDataObject
+    {
+        void GetData([In] ref FORMATETC format, out STGMEDIUM medium);
+        void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium);
+        [PreserveSig]
+        int QueryGetData([In] ref FORMATETC format);
+        [PreserveSig]
+        int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut);
+        void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release);
+        IEnumFORMATETC EnumFormatEtc(DATADIR direction);
+        [PreserveSig]
+        int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection);
+        void DUnadvise(int connection);
+        [PreserveSig]
+        int EnumDAdvise(out IEnumSTATDATA enumAdvise);
+    }
+
+
     [StructLayoutAttribute(LayoutKind.Sequential)]
     internal struct _DROPFILES
     {

+ 2 - 3
src/Windows/Avalonia.Win32/OleDataObject.cs

@@ -8,15 +8,14 @@ using System.Runtime.Serialization.Formatters.Binary;
 using System.Text;
 using Avalonia.Input;
 using Avalonia.Win32.Interop;
-using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
 
 namespace Avalonia.Win32
 {
     class OleDataObject : Avalonia.Input.IDataObject
     {
-        private IDataObject _wrapped;
+        private IOleDataObject _wrapped;
 
-        public OleDataObject(IDataObject wrapped)
+        public OleDataObject(IOleDataObject wrapped)
         {
             _wrapped = wrapped;
         }

+ 0 - 1
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -3,7 +3,6 @@ using Avalonia.Input.Raw;
 using Avalonia.Platform;
 using Avalonia.Win32.Interop;
 using IDataObject = Avalonia.Input.IDataObject;
-using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
 
 namespace Avalonia.Win32
 {

+ 0 - 7
src/Windows/Avalonia.Win32/ScreenImpl.cs

@@ -2,16 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Linq;
-using Avalonia.Controls;
 using Avalonia.Platform;
-using Avalonia.Utilities;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
 
-#if NETSTANDARD
-using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
-#endif
-
 namespace Avalonia.Win32
 {
     public class ScreenImpl : IScreenImpl

+ 10 - 14
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -1,27 +1,23 @@
 // 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.Input.Platform;
 using System;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
+using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reactive.Disposables;
 using System.Runtime.InteropServices;
 using System.Threading;
+using Avalonia.Controls;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
+using Avalonia.Input.Platform;
 using Avalonia.Platform;
-using Avalonia.Win32.Input;
-using Avalonia.Win32.Interop;
-using Avalonia.Controls;
 using Avalonia.Rendering;
 using Avalonia.Threading;
-using System.IO;
-#if NETSTANDARD
-using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
-#else
-using System.ComponentModel;
-#endif
+using Avalonia.Win32.Input;
+using Avalonia.Win32.Interop;
 
 namespace Avalonia
 {
@@ -86,11 +82,11 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
 
-            if (OleContext.Current != null)
-                AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
-
             UseDeferredRendering = deferredRendering;
             _uiThread = UnmanagedMethods.GetCurrentThreadId();
+
+            if (OleContext.Current != null)
+                AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
         }
 
         public bool HasMessages()

+ 70 - 14
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -31,10 +31,14 @@ namespace Avalonia.Win32
         private IInputRoot _owner;
         private bool _trackingMouse;
         private bool _decorated = true;
+        private bool _resizable = true;
         private double _scaling = 1;
         private WindowState _showWindowState;
         private FramebufferManager _framebuffer;
         private OleDropTarget _dropTarget;
+        private Size _minSize;
+        private Size _maxSize;
+
 #if USE_MANAGED_DRAG
         private readonly ManagedWindowResizeDragHelper _managedDrag;
 #endif
@@ -77,8 +81,8 @@ namespace Avalonia.Win32
         {
             get
             {
-                var style = UnmanagedMethods.GetWindowLong(_hwnd, -16);
-                var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20);
+                var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
+                var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
                 var padding = new UnmanagedMethods.RECT();
 
                 if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle))
@@ -102,6 +106,12 @@ namespace Avalonia.Win32
             }
         }
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+            _minSize = minSize;
+            _maxSize = maxSize;
+        }
+
         public IScreenImpl Screen
         {
             get;
@@ -235,13 +245,19 @@ namespace Avalonia.Win32
                 return;
             }
 
-            var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16);
+            var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
 
-            style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
+            var systemDecorationStyles = UnmanagedMethods.WindowStyles.WS_OVERLAPPED
+                | UnmanagedMethods.WindowStyles.WS_CAPTION
+                | UnmanagedMethods.WindowStyles.WS_SYSMENU
+                | UnmanagedMethods.WindowStyles.WS_MINIMIZEBOX
+                | UnmanagedMethods.WindowStyles.WS_MAXIMIZEBOX;
+
+            style |= systemDecorationStyles;
 
             if (!value)
             {
-                style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
+                style ^= systemDecorationStyles;
             }
 
             UnmanagedMethods.RECT windowRect;
@@ -251,7 +267,7 @@ namespace Avalonia.Win32
             Rect newRect;
             var oldThickness = BorderThickness;
 
-            UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style);
+            UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style);
 
             if (value)
             {
@@ -383,8 +399,11 @@ namespace Avalonia.Win32
 
         public void SetCursor(IPlatformHandle cursor)
         {
-            UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR,
-                cursor?.Handle ?? DefaultCursor);
+            var hCursor = cursor?.Handle ?? DefaultCursor;
+            UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor);
+
+            if (_owner.IsPointerOver)
+                UnmanagedMethods.SetCursor(hCursor);
         }
 
         protected virtual IntPtr CreateWindowOverride(ushort atom)
@@ -611,7 +630,26 @@ namespace Avalonia.Win32
                 case UnmanagedMethods.WindowsMessage.WM_MOVE:
                     PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
                     return IntPtr.Zero;
-                    
+
+                case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO:
+
+                    MINMAXINFO mmi = Marshal.PtrToStructure<UnmanagedMethods.MINMAXINFO>(lParam);
+
+                    if  (_minSize.Width > 0)
+                        mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
+
+                    if (_minSize.Height > 0)
+                        mmi.ptMinTrackSize.Y = (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
+
+                    if (!Double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
+                        mmi.ptMaxTrackSize.X = (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
+
+                    if (!Double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
+                        mmi.ptMaxTrackSize.Y = (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
+
+                    Marshal.StructureToPtr(mmi, lParam, true);
+                    return IntPtr.Zero;
+
                 case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE:
                     (Screen as ScreenImpl)?.InvalidateScreensCache();
                     return IntPtr.Zero;
@@ -798,11 +836,11 @@ namespace Avalonia.Win32
 
         public void ShowTaskbarIcon(bool value)
         {
-            var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20);
-            
-            style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);   
+            var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
 
-            style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;   
+            style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);
+
+            style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;
             if (value)
                 style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW;
             else
@@ -813,9 +851,27 @@ namespace Avalonia.Win32
             {
                 //Toggle to make the styles stick
                 UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide);
-                UnmanagedMethods.SetWindowLong(_hwnd, -20, (uint)style);
+                UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE, (uint)style);
                 UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd);
             }
         }
+
+        public void CanResize(bool value)
+        {
+            if (value == _resizable)
+            {
+                return;
+            }
+
+            var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
+            
+            if (value)
+                style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME;
+            else
+                style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME);
+            
+            UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style);
+            _resizable = value;
+        }
     }
 }

+ 4 - 0
src/iOS/Avalonia.iOS/EmbeddableImpl.cs

@@ -14,6 +14,10 @@ namespace Avalonia.iOS
             
         }
 
+        public void SetMinMaxSize(Size minSize, Size maxSize)
+        {
+        }
+
         public IDisposable ShowDialog()
         {
             return Disposable.Empty;

+ 52 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs

@@ -0,0 +1,52 @@
+// 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 Xunit;
+
+namespace Avalonia.Base.UnitTests
+{
+    public class AvaloniaObjectTests_AddOwner
+    {
+        [Fact]
+        public void AddOwnered_Property_Retains_Default_Value()
+        {
+            var target = new Class2();
+
+            Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
+        }
+
+        [Fact]
+        public void AddOwnered_Property_Does_Not_Retain_Validation()
+        {
+            var target = new Class2();
+
+            target.SetValue(Class2.FooProperty, "throw");
+        }
+
+        private class Class1 : AvaloniaObject
+        {
+            public static readonly StyledProperty<string> FooProperty =
+                AvaloniaProperty.Register<Class1, string>(
+                    "Foo",
+                    "foodefault",
+                    validate: ValidateFoo);
+
+            private static string ValidateFoo(AvaloniaObject arg1, string arg2)
+            {
+                if (arg2 == "throw")
+                {
+                    throw new IndexOutOfRangeException();
+                }
+
+                return arg2;
+            }
+        }
+
+        private class Class2 : AvaloniaObject
+        {
+            public static readonly StyledProperty<string> FooProperty =
+                Class1.FooProperty.AddOwner<Class2>();
+        }
+    }
+}

+ 52 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs

@@ -0,0 +1,52 @@
+// 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 Xunit;
+
+namespace Avalonia.Base.UnitTests
+{
+    public class AvaloniaObjectTests_Attached
+    {
+        [Fact]
+        public void AddOwnered_Property_Retains_Default_Value()
+        {
+            var target = new Class2();
+
+            Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
+        }
+
+        [Fact]
+        public void AddOwnered_Property_Retains_Validation()
+        {
+            var target = new Class2();
+
+            Assert.Throws<IndexOutOfRangeException>(() => target.SetValue(Class2.FooProperty, "throw"));
+        }
+
+        private class Class1 : AvaloniaObject
+        {
+            public static readonly AttachedProperty<string> FooProperty =
+                AvaloniaProperty.RegisterAttached<Class1, Class2, string>(
+                    "Foo",
+                    "foodefault",
+                    validate: ValidateFoo);
+
+            private static string ValidateFoo(AvaloniaObject arg1, string arg2)
+            {
+                if (arg2 == "throw")
+                {
+                    throw new IndexOutOfRangeException();
+                }
+
+                return arg2;
+            }
+        }
+
+        private class Class2 : AvaloniaObject
+        {
+            public static readonly AttachedProperty<string> FooProperty =
+                Class1.FooProperty.AddOwner<Class2>();
+        }
+    }
+}

+ 4 - 5
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -92,14 +92,13 @@ namespace Avalonia.Base.UnitTests
         }
 
         [Fact]
-        public void Bind_Throws_Exception_For_Unregistered_Property()
+        public void Bind_Does_Not_Throw_Exception_For_Unregistered_Property()
         {
             Class1 target = new Class1();
 
-            Assert.Throws<ArgumentException>(() =>
-            {
-                target.Bind(Class2.BarProperty, Observable.Return("foo"));
-            });
+            target.Bind(Class2.BarProperty, Observable.Never<string>().StartWith("foo"));
+
+            Assert.Equal("foo", target.GetValue(Class2.BarProperty));
         }
 
         [Fact]

+ 2 - 2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs

@@ -46,11 +46,11 @@ namespace Avalonia.Base.UnitTests
         }
 
         [Fact]
-        public void GetValue_Throws_Exception_For_Unregistered_Property()
+        public void GetValue_Doesnt_Throw_Exception_For_Unregistered_Property()
         {
             var target = new Class3();
 
-            Assert.Throws<ArgumentException>(() => target.GetValue(Class1.FooProperty));
+            Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
         }
 
         private class Class1 : AvaloniaObject

+ 34 - 5
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@@ -30,6 +30,16 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
         }
 
+        [Fact]
+        public void SetValue_Sets_Attached_Value()
+        {
+            Class2 target = new Class2();
+
+            target.SetValue(AttachedOwner.AttachedProperty, "newvalue");
+
+            Assert.Equal("newvalue", target.GetValue(AttachedOwner.AttachedProperty));
+        }
+
         [Fact]
         public void SetValue_Raises_PropertyChanged()
         {
@@ -84,14 +94,27 @@ namespace Avalonia.Base.UnitTests
         }
 
         [Fact]
-        public void SetValue_Throws_Exception_For_Unregistered_Property()
+        public void SetValue_Allows_Setting_Unregistered_Property()
         {
             Class1 target = new Class1();
 
-            Assert.Throws<ArgumentException>(() =>
-            {
-                target.SetValue(Class2.BarProperty, "invalid");
-            });
+            Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, Class2.BarProperty));
+
+            target.SetValue(Class2.BarProperty, "bar");
+
+            Assert.Equal("bar", target.GetValue(Class2.BarProperty));
+        }
+
+        [Fact]
+        public void SetValue_Allows_Setting_Unregistered_Attached_Property()
+        {
+            Class1 target = new Class1();
+
+            Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, AttachedOwner.AttachedProperty));
+
+            target.SetValue(AttachedOwner.AttachedProperty, "bar");
+
+            Assert.Equal("bar", target.GetValue(AttachedOwner.AttachedProperty));
         }
 
         [Fact]
@@ -189,6 +212,12 @@ namespace Avalonia.Base.UnitTests
             }
         }
 
+        private class AttachedOwner
+        {
+            public static readonly AttachedProperty<string> AttachedProperty =
+                AvaloniaProperty.RegisterAttached<AttachedOwner, Class2, string>("Attached");
+        }
+
         private class ImplictDouble
         {
             public ImplictDouble(double value)

+ 33 - 73
tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs

@@ -4,12 +4,13 @@
 using System.Linq;
 using System.Reactive.Linq;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Avalonia.Base.UnitTests
 {
     public class AvaloniaPropertyRegistryTests
     {
-        public AvaloniaPropertyRegistryTests()
+        public AvaloniaPropertyRegistryTests(ITestOutputHelper s)
         {
             // Ensure properties are registered.
             AvaloniaProperty p;
@@ -25,7 +26,7 @@ namespace Avalonia.Base.UnitTests
                 .Select(x => x.Name)
                 .ToArray();
 
-            Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names);
+            Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names);
         }
 
         [Fact]
@@ -35,61 +36,41 @@ namespace Avalonia.Base.UnitTests
                 .Select(x => x.Name)
                 .ToArray();
 
-            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names);
+            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names);
         }
 
         [Fact]
-        public void GetAttached_Returns_Registered_Properties_For_Base_Types()
+        public void GetRegisteredAttached_Returns_Registered_Properties()
         {
-            string[] names = AvaloniaPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray();
+            string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class1))
+                .Select(x => x.Name)
+                .ToArray();
 
             Assert.Equal(new[] { "Attached" }, names);
         }
 
         [Fact]
-        public void FindRegistered_Finds_Untyped_Property()
+        public void GetRegisteredAttached_Returns_Registered_Properties_For_Base_Types()
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo");
+            string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class2))
+                .Select(x => x.Name)
+                .ToArray();
 
-            Assert.Equal(Class1.FooProperty, result);
+            Assert.Equal(new[] { "Attached" }, names);
         }
 
         [Fact]
-        public void FindRegistered_Finds_Typed_Property()
+        public void FindRegistered_Finds_Property()
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Class1.Foo");
+            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo");
 
             Assert.Equal(Class1.FooProperty, result);
         }
 
         [Fact]
-        public void FindRegistered_Finds_Typed_Inherited_Property()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class1.Foo");
-
-            Assert.Equal(Class2.FooProperty, result);
-        }
-
-        [Fact]
-        public void FindRegistered_Finds_Inherited_Property_With_Derived_Type_Name()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class2.Foo");
-
-            Assert.Equal(Class2.FooProperty, result);
-        }
-
-        [Fact]
-        public void FindRegistered_Finds_Attached_Property()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "AttachedOwner.Attached");
-
-            Assert.Equal(AttachedOwner.AttachedProperty, result);
-        }
-
-        [Fact]
-        public void FindRegistered_Doesnt_Finds_Unqualified_Attached_Property()
+        public void FindRegistered_Doesnt_Find_Nonregistered_Property()
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached");
+            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar");
 
             Assert.Null(result);
         }
@@ -99,55 +80,34 @@ namespace Avalonia.Base.UnitTests
         {
             var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(AttachedOwner), "Attached");
 
-            Assert.True(AttachedOwner.AttachedProperty == result);
+            Assert.Same(AttachedOwner.AttachedProperty, result);
         }
 
         [Fact]
-        public void FindRegistered_Finds_AddOwnered_Untyped_Attached_Property()
+        public void FindRegistered_Finds_AddOwnered_Attached_Property()
         {
             var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Attached");
 
-            Assert.True(AttachedOwner.AttachedProperty == result);
+            Assert.Same(AttachedOwner.AttachedProperty, result);
         }
 
         [Fact]
-        public void FindRegistered_Finds_AddOwnered_Typed_Attached_Property()
+        public void FindRegistered_Doesnt_Find_Non_AddOwnered_Attached_Property()
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class3.Attached");
-
-            Assert.True(AttachedOwner.AttachedProperty == result);
-        }
-
-        [Fact]
-        public void FindRegistered_Finds_AddOwnered_AttachedTyped_Attached_Property()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "AttachedOwner.Attached");
-
-            Assert.True(AttachedOwner.AttachedProperty == result);
-        }
-
-        [Fact]
-        public void FindRegistered_Finds_AddOwnered_BaseTyped_Attached_Property()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class1.Attached");
-
-            Assert.True(AttachedOwner.AttachedProperty == result);
-        }
-
-        [Fact]
-        public void FindRegistered_Doesnt_Find_Nonregistered_Property()
-        {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar");
+            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached");
 
             Assert.Null(result);
         }
 
         [Fact]
-        public void FindRegistered_Doesnt_Find_Nonregistered_Attached_Property()
+        public void FindRegisteredAttached_Finds_Property()
         {
-            var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class4), "AttachedOwner.Attached");
+            var result = AvaloniaPropertyRegistry.Instance.FindRegisteredAttached(
+                typeof(Class1),
+                typeof(AttachedOwner),
+                "Attached");
 
-            Assert.Null(result);
+            Assert.Equal(AttachedOwner.AttachedProperty, result);
         }
 
         private class Class1 : AvaloniaObject
@@ -176,18 +136,18 @@ namespace Avalonia.Base.UnitTests
 
         private class Class3 : Class1
         {
-            public static readonly StyledProperty<string> AttachedProperty =
+            public static readonly AttachedProperty<string> AttachedProperty =
                 AttachedOwner.AttachedProperty.AddOwner<Class3>();
         }
 
-        public class Class4 : AvaloniaObject
-        {
-        }
-
         private class AttachedOwner : Class1
         {
             public static readonly AttachedProperty<string> AttachedProperty =
                 AvaloniaProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
         }
+
+        private class AttachedOwner2 : AttachedOwner
+        {
+        }
     }
 }

+ 9 - 9
tests/Avalonia.Controls.UnitTests/GridLengthTests.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Parse_Auto()
         {
-            var result = GridLength.Parse("Auto", CultureInfo.InvariantCulture);
+            var result = GridLength.Parse("Auto");
 
             Assert.Equal(GridLength.Auto, result);
         }
@@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Parse_Auto_Lowercase()
         {
-            var result = GridLength.Parse("auto", CultureInfo.InvariantCulture);
+            var result = GridLength.Parse("auto");
 
             Assert.Equal(GridLength.Auto, result);
         }
@@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Parse_Star()
         {
-            var result = GridLength.Parse("*", CultureInfo.InvariantCulture);
+            var result = GridLength.Parse("*");
 
             Assert.Equal(new GridLength(1, GridUnitType.Star), result);
         }
@@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Parse_Star_Value()
         {
-            var result = GridLength.Parse("2*", CultureInfo.InvariantCulture);
+            var result = GridLength.Parse("2*");
 
             Assert.Equal(new GridLength(2, GridUnitType.Star), result);
         }
@@ -45,7 +45,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Parse_Pixel_Value()
         {
-            var result = GridLength.Parse("2", CultureInfo.InvariantCulture);
+            var result = GridLength.Parse("2");
 
             Assert.Equal(new GridLength(2, GridUnitType.Pixel), result);
         }
@@ -53,13 +53,13 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Parse_Should_Throw_FormatException_For_Invalid_String()
         {
-            Assert.Throws<FormatException>(() => GridLength.Parse("2x", CultureInfo.InvariantCulture));
+            Assert.Throws<FormatException>(() => GridLength.Parse("2x"));
         }
 
         [Fact]
         public void ParseLengths_Accepts_Comma_Separators()
         {
-            var result = GridLength.ParseLengths("*,Auto,2*,4", CultureInfo.InvariantCulture).ToList();
+            var result = GridLength.ParseLengths("*,Auto,2*,4").ToList();
 
             Assert.Equal(
                 new[]
@@ -75,7 +75,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void ParseLengths_Accepts_Space_Separators()
         {
-            var result = GridLength.ParseLengths("* Auto 2* 4", CultureInfo.InvariantCulture).ToList();
+            var result = GridLength.ParseLengths("* Auto 2* 4").ToList();
 
             Assert.Equal(
                 new[]
@@ -91,7 +91,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void ParseLengths_Accepts_Comma_Separators_With_Spaces()
         {
-            var result = GridLength.ParseLengths("*, Auto, 2* ,4", CultureInfo.InvariantCulture).ToList();
+            var result = GridLength.ParseLengths("*, Auto, 2* ,4").ToList();
 
             Assert.Equal(
                 new[]

+ 9 - 9
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data
         [Fact]
         public async Task Should_Convert_Get_String_To_Double()
         {
-            var data = new Class1 { StringValue = "5.6" };
+            var data = new Class1 { StringValue = $"{5.6}" };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
             var result = await target.Take(1);
 
@@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data
         [Fact]
         public void Should_Convert_Set_String_To_Double()
         {
-            var data = new Class1 { StringValue = (5.6).ToString() };
+            var data = new Class1 { StringValue = $"{5.6}" };
             var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
 
             target.OnNext(6.7);
 
-            Assert.Equal((6.7).ToString(), data.StringValue);
+            Assert.Equal($"{6.7}", data.StringValue);
 
             GC.KeepAlive(data);
         }
@@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data
             var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
             var result = await target.Take(1);
 
-            Assert.Equal((5.6).ToString(), result);
+            Assert.Equal($"{5.6}", result);
 
             GC.KeepAlive(data);
         }
@@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data
             var data = new Class1 { DoubleValue = 5.6 };
             var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
 
-            target.OnNext("6.7");
+            target.OnNext($"{6.7}");
 
             Assert.Equal(6.7, data.DoubleValue);
 
@@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data
 
             target.Subscribe(x => result.Add(x));
             target.OnNext(1.2);
-            target.OnNext("3.4");
+            target.OnNext($"{3.4}");
             target.OnNext("bar");
 
             Assert.Equal(
                 new[]
                 {
-                    new BindingNotification("5.6"),
-                    new BindingNotification("1.2"),
-                    new BindingNotification("3.4"),
+                    new BindingNotification($"{5.6}"),
+                    new BindingNotification($"{1.2}"),
+                    new BindingNotification($"{3.4}"),
                     new BindingNotification(
                         new InvalidCastException("'bar' is not a valid number."),
                         BindingErrorType.Error)

+ 71 - 0
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
+{
+    public class BindingExtensionTests
+    {
+
+        [Fact]
+        public void BindingExtension_Binds_To_Source()
+        {
+            using (StyledWindow())
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+             xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.Resources>
+        <x:String x:Key='text'>foobar</x:String>
+    </Window.Resources>
+
+    <TextBlock Name='textBlock' Text='{Binding Source={StaticResource text}}'/>
+</Window>";
+
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                window.Show();
+
+                Assert.Equal("foobar", textBlock.Text);
+            }
+        }
+
+        private IDisposable StyledWindow(params (string, string)[] assets)
+        {
+            var services = TestServices.StyledWindow.With(
+                assetLoader: new MockAssetLoader(assets),
+                theme: () => new Styles
+                {
+                    WindowStyle(),
+                });
+
+            return UnitTestApplication.Start(services);
+        }
+
+        private Style WindowStyle()
+        {
+            return new Style(x => x.OfType<Window>())
+            {
+                Setters =
+                {
+                    new Setter(
+                        Window.TemplateProperty,
+                        new FuncControlTemplate<Window>(x =>
+                            new ContentPresenter
+                            {
+                                Name = "PART_ContentPresenter",
+                                [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
+                            }))
+                }
+            };
+        }
+    }
+}

+ 75 - 4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@@ -323,7 +323,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             Assert.Equal(0xff506070, brush.Color.ToUint32());
         }
 
-        [Fact(Skip = "Not yet supported by Portable.Xaml")]
+        [Fact]
         public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
         {
             var styleXaml = @"
@@ -361,9 +361,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
 
                 var border = (Border)button.GetVisualChildren().Single();
                 var brush = (SolidColorBrush)border.Background;
-
-                // To make this work we somehow need to be able to get hold of the parent ambient
-                // context from Portable.Xaml. See TODO in StaticResourceExtension.
+                
                 Assert.Equal(0xff506070, brush.Color.ToUint32());
             }
         }
@@ -417,6 +415,79 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             }
         }
 
+        [Fact]
+        public void StaticResource_Can_Be_Assigned_To_Binding_Converter_In_DataTemplate()
+        {
+            using (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.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Window.Resources>
+        <local:TestValueConverter x:Key='converter' Append='bar'/>
+        <DataTemplate x:Key='PurpleData'>
+          <TextBlock Name='textBlock' Text='{Binding Converter={StaticResource converter}}'/>
+        </DataTemplate>
+    </Window.Resources>
+
+    <ContentPresenter Name='presenter' Content='foo' ContentTemplate='{StaticResource PurpleData}'/>
+</Window>";
+
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+
+                window.DataContext = "foo";
+                var presenter = window.FindControl<ContentPresenter>("presenter");
+
+                window.Show();
+
+                var textBlock = (TextBlock)presenter.GetVisualChildren().Single();
+
+                Assert.NotNull(textBlock);
+                Assert.Equal("foobar", textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate()
+        {
+            // this tests if IAmbientProviders in DataTemplate contexts are in correct order
+            // if they wouldn't be, Purple brush would be bound to
+            using (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.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Window.Resources>
+        <local:TestValueConverter x:Key='converter' Append='-bar'/>
+        <SolidColorBrush x:Key='brush' Color='Purple'/>
+        <DataTemplate x:Key='WhiteData'>
+          <Border>
+            <Border.Resources>
+              <SolidColorBrush x:Key='brush' Color='White'/>
+            </Border.Resources>
+            <TextBlock Name='textBlock' Text='{Binding Color, Source={StaticResource brush}, Converter={StaticResource converter}}' Foreground='{StaticResource brush}' />
+          </Border>
+        </DataTemplate>
+    </Window.Resources>
+
+    <ContentPresenter Content='foo' ContentTemplate='{StaticResource WhiteData}'/>
+</Window>";
+
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+
+                window.Show();
+
+                var textBlock = window.GetVisualDescendants().OfType<TextBlock>().Single();
+
+                Assert.NotNull(textBlock);
+                Assert.Equal("White-bar", textBlock.Text);
+            }
+        }
+
         [Fact]
         public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed()
         {

+ 14 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs

@@ -0,0 +1,14 @@
+using System;
+using Avalonia.Controls;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml
+{
+    public class AttachedPropertyOwner
+    {
+        public static readonly AttachedProperty<double> DoubleProperty =
+            AvaloniaProperty.RegisterAttached<AttachedPropertyOwner, Control, double>("Double");
+
+        public static double GetDouble(Control control) => control.GetValue(DoubleProperty);
+        public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value);
+    }
+}

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

@@ -11,6 +11,7 @@ using Avalonia.Media;
 using Avalonia.Media.Immutable;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
+using Portable.Xaml;
 using System.Collections;
 using System.ComponentModel;
 using System.Linq;
@@ -80,6 +81,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             Assert.Equal(21.0, TextBlock.GetFontSize(target));
         }
 
+        [Fact]
+        public void Attached_Property_Is_Set_On_Control_Outside_Avalonia_Namspace()
+        {
+            // Test for issue #1548
+            var xaml =
+@"<UserControl xmlns='https://github.com/avaloniaui'
+    xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+  <local:TestControl Grid.Column='2' />
+</UserControl>";
+
+            var target = AvaloniaXamlLoader.Parse<UserControl>(xaml);
+
+            Assert.Equal(2, Grid.GetColumn((TestControl)target.Content));
+        }
+
         [Fact]
         public void Attached_Property_With_Namespace_Is_Set()
         {
@@ -125,6 +141,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             Assert.Equal("Foo", ToolTip.GetTip(target));
         }
 
+        [Fact]
+        public void NonExistent_Property_Throws()
+        {
+            var xaml =
+        @"<ContentControl xmlns='https://github.com/avaloniaui' DoesntExist='foo'/>";
+
+            Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
+        }
+
+        [Fact]
+        public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
+        {
+            var xaml =
+        @"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";
+
+            Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
+        }
+
         [Fact]
         public void ContentControl_ContentTemplate_Is_Functional()
         {

+ 66 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@@ -215,5 +215,71 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal("bar", textBlock.Text);
             }
         }
+
+        [Fact]
+        public void Binding_To_Namespaced_Attached_Property_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
+            {
+                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'>
+    <TextBlock local:AttachedPropertyOwner.Double='{Binding}'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = (TextBlock)window.Content;
+
+                window.DataContext = 5.6;
+                window.ApplyTemplate();
+
+                Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
+            }
+        }
+
+        [Fact]
+        public void Binding_To_AddOwnered_Attached_Property_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
+            {
+                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:TestControl Double='{Binding}'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var testControl = (TestControl)window.Content;
+
+                window.DataContext = 5.6;
+                window.ApplyTemplate();
+
+                Assert.Equal(5.6, testControl.Double);
+            }
+        }
+
+        [Fact]
+        public void Binding_To_Attached_Property_Using_AddOwnered_Type_Works()
+        {
+            using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
+            {
+                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'>
+    <TextBlock local:TestControl.Double='{Binding}'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = (TextBlock)window.Content;
+
+                window.DataContext = 5.6;
+                window.ApplyTemplate();
+
+                Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
+            }
+        }
     }
 }

+ 17 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs

@@ -0,0 +1,17 @@
+using System;
+using Avalonia.Controls;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Xaml
+{
+    public class TestControl : Control
+    {
+        public static readonly StyledProperty<double> DoubleProperty =
+            AttachedPropertyOwner.DoubleProperty.AddOwner<TestControl>();
+
+        public double Double
+        {
+            get => GetValue(DoubleProperty);
+            set => SetValue(DoubleProperty, value);
+        }
+    }
+}

+ 5 - 0
tests/Avalonia.UnitTests/MockAssetLoader.cs

@@ -27,6 +27,11 @@ namespace Avalonia.UnitTests
         {
             return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri]));
         }
+        
+        public Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null)
+        {
+            return Tuple.Create(Open(uri, baseUri), (Assembly)null);
+        }
 
         public void SetDefaultAssembly(Assembly asm)
         {

+ 4 - 4
tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_Single_Uniform_Radius()
         {
-            var result = CornerRadius.Parse("3.4", CultureInfo.InvariantCulture);
+            var result = CornerRadius.Parse("3.4");
 
             Assert.Equal(new CornerRadius(3.4), result);
         }
@@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_Top_Bottom()
         {
-            var result = CornerRadius.Parse("1.1,2.2", CultureInfo.InvariantCulture);
+            var result = CornerRadius.Parse("1.1,2.2");
 
             Assert.Equal(new CornerRadius(1.1, 2.2), result);
         }
@@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft()
         {
-            var result = CornerRadius.Parse("1.1,2.2,3.3,4.4", CultureInfo.InvariantCulture);
+            var result = CornerRadius.Parse("1.1,2.2,3.3,4.4");
 
             Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
         }
@@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Accepts_Spaces()
         {
-            var result = CornerRadius.Parse("1.1 2.2 3.3 4.4", CultureInfo.InvariantCulture);
+            var result = CornerRadius.Parse("1.1 2.2 3.3 4.4");
 
             Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
         }

+ 1 - 1
tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media
         [Fact]
         public void Parse_Parses()
         {
-            var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture);
+            var matrix = Matrix.Parse("1,2,3,-4,5 6");
             var expected = new Matrix(1, 2, 3, -4, 5, 6);
             Assert.Equal(expected, matrix);
         }

+ 1 - 1
tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media
         [Fact]
         public void Parse_Parses()
         {
-            var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture);
+            var rect = Rect.Parse("1,2 3,-4");
             var expected = new Rect(1, 2, 3, -4);
             Assert.Equal(expected, rect);
         }

+ 2 - 2
tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Should_Accept_Absolute_Value()
         {
-            var result = RelativePoint.Parse("4,5", CultureInfo.InvariantCulture);
+            var result = RelativePoint.Parse("4,5");
 
             Assert.Equal(new RelativePoint(4, 5, RelativeUnit.Absolute), result);
         }
@@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Should_Accept_Relative_Value()
         {
-            var result = RelativePoint.Parse("25%, 50%", CultureInfo.InvariantCulture);
+            var result = RelativePoint.Parse("25%, 50%");
 
             Assert.Equal(new RelativePoint(0.25, 0.5, RelativeUnit.Relative), result);
         }

+ 3 - 3
tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Should_Accept_Absolute_Value()
         {
-            var result = RelativeRect.Parse("4,5,50,60", CultureInfo.InvariantCulture);
+            var result = RelativeRect.Parse("4,5,50,60");
 
             Assert.Equal(new RelativeRect(4, 5, 50, 60, RelativeUnit.Absolute), result, Compare);
         }
@@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Should_Accept_Relative_Value()
         {
-            var result = RelativeRect.Parse("10%, 20%, 40%, 70%", CultureInfo.InvariantCulture);
+            var result = RelativeRect.Parse("10%, 20%, 40%, 70%");
 
             Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare);
         }
@@ -31,7 +31,7 @@ namespace Avalonia.Visuals.UnitTests
         public void Parse_Should_Throw_Mixed_Values()
         {
             Assert.Throws<FormatException>(() =>
-                RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture));
+                RelativeRect.Parse("10%, 20%, 40, 70%"));
         }
     }
 }

+ 4 - 4
tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_Single_Uniform_Size()
         {
-            var result = Thickness.Parse("1.2", CultureInfo.InvariantCulture);
+            var result = Thickness.Parse("1.2");
 
             Assert.Equal(new Thickness(1.2), result);
         }
@@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_Horizontal_Vertical()
         {
-            var result = Thickness.Parse("1.2,3.4", CultureInfo.InvariantCulture);
+            var result = Thickness.Parse("1.2,3.4");
 
             Assert.Equal(new Thickness(1.2, 3.4), result);
         }
@@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Parses_Left_Top_Right_Bottom()
         {
-            var result = Thickness.Parse("1.2, 3.4, 5, 6", CultureInfo.InvariantCulture);
+            var result = Thickness.Parse("1.2, 3.4, 5, 6");
 
             Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result);
         }
@@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests
         [Fact]
         public void Parse_Accepts_Spaces()
         {
-            var result = Thickness.Parse("1.2 3.4 5 6", CultureInfo.InvariantCulture);
+            var result = Thickness.Parse("1.2 3.4 5 6");
 
             Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result);
         }