Browse Source

Merge branch 'master' into readonly-struct

Steven Kirk 7 years ago
parent
commit
c4e5805da4
100 changed files with 7795 additions and 2201 deletions
  1. 17 0
      .github/PULL_REQUEST_TEMPLATE.md
  2. 10 55
      Avalonia.sln
  3. 1 1
      build/Base.props
  4. 11 0
      build/EmbedXaml.props
  5. 0 1
      build/Rx.props
  6. 13 0
      build/SampleApp.props
  7. 13 0
      build/SharedVersion.props
  8. 5 0
      build/System.Drawing.Common.props
  9. 3 4
      packages.cake
  10. 1 1
      parameters.cake
  11. 34 4
      readme.md
  12. 26 148
      samples/BindingTest/BindingTest.csproj
  13. 13 2
      samples/BindingTest/MainWindow.xaml
  14. 0 36
      samples/BindingTest/Properties/AssemblyInfo.cs
  15. 13 130
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  16. 1 0
      samples/ControlCatalog.Desktop/Program.cs
  17. 0 36
      samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs
  18. 2 0
      samples/ControlCatalog.NetCore/Program.cs
  19. 2 3
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  20. 7 187
      samples/ControlCatalog/ControlCatalog.csproj
  21. 2 2
      samples/ControlCatalog/DecoratedWindow.xaml
  22. 7 3
      samples/ControlCatalog/MainView.xaml
  23. 1 1
      samples/ControlCatalog/MainWindow.xaml
  24. 59 0
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  25. 143 0
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  26. 24 0
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  27. 54 0
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  28. 19 0
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  29. 71 0
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  30. 80 0
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  31. 94 0
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  32. 0 36
      samples/ControlCatalog/Properties/AssemblyInfo.cs
  33. 3 0
      samples/Directory.Build.props
  34. 4 2
      samples/RenderTest/MainWindow.xaml
  35. 0 36
      samples/RenderTest/Properties/AssemblyInfo.cs
  36. 27 178
      samples/RenderTest/RenderTest.csproj
  37. 3 1
      samples/VirtualizationTest/MainWindow.xaml
  38. 0 36
      samples/VirtualizationTest/Properties/AssemblyInfo.cs
  39. 26 144
      samples/VirtualizationTest/VirtualizationTest.csproj
  40. 3 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  41. 5 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  42. 0 28
      src/Avalonia.Animation/Avalonia.Animation.csproj
  43. 0 6
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  44. 4 5
      src/Avalonia.Base/AttachedProperty.cs
  45. 1 28
      src/Avalonia.Base/Avalonia.Base.csproj
  46. 31 32
      src/Avalonia.Base/AvaloniaObject.cs
  47. 6 2
      src/Avalonia.Base/AvaloniaProperty.cs
  48. 133 150
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  49. 2 2
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  50. 21 5
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  51. 127 0
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  52. 3 0
      src/Avalonia.Base/DirectProperty.cs
  53. 7 0
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  54. 16 0
      src/Avalonia.Base/Platform/IAssetLoader.cs
  55. 0 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  56. 2 0
      src/Avalonia.Base/Threading/Dispatcher.cs
  57. 219 0
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  58. 6 3
      src/Avalonia.Controls/Application.cs
  59. 2730 0
      src/Avalonia.Controls/AutoCompleteBox.cs
  60. 1 27
      src/Avalonia.Controls/Avalonia.Controls.csproj
  61. 21 30
      src/Avalonia.Controls/Border.cs
  62. 1 1
      src/Avalonia.Controls/Button.cs
  63. 263 0
      src/Avalonia.Controls/ButtonSpinner.cs
  64. 1 1
      src/Avalonia.Controls/ColumnDefinitions.cs
  65. 4 3
      src/Avalonia.Controls/ContextMenu.cs
  66. 1 0
      src/Avalonia.Controls/DropDown.cs
  67. 148 674
      src/Avalonia.Controls/Grid.cs
  68. 12 7
      src/Avalonia.Controls/GridLength.cs
  69. 5 7
      src/Avalonia.Controls/ItemsControl.cs
  70. 6 0
      src/Avalonia.Controls/LayoutTransformControl.cs
  71. 35 0
      src/Avalonia.Controls/MenuItem.cs
  72. 998 0
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  73. 16 0
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  74. 8 2
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  75. 11 0
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  76. 210 0
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  77. 71 82
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  78. 1 1
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  79. 62 4
      src/Avalonia.Controls/Primitives/Popup.cs
  80. 26 0
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  81. 4 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  82. 3 23
      src/Avalonia.Controls/ProgressBar.cs
  83. 0 1
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  84. 2 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  85. 27 2
      src/Avalonia.Controls/RepeatButton.cs
  86. 1 1
      src/Avalonia.Controls/RowDefinitions.cs
  87. 1 1
      src/Avalonia.Controls/Screens.cs
  88. 174 0
      src/Avalonia.Controls/Spinner.cs
  89. 2 2
      src/Avalonia.Controls/TextBlock.cs
  90. 32 5
      src/Avalonia.Controls/TextBox.cs
  91. 4 0
      src/Avalonia.Controls/UserControl.cs
  92. 279 0
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  93. 700 0
      src/Avalonia.Controls/Utils/GridLayout.cs
  94. 64 0
      src/Avalonia.Controls/Utils/ISelectionAdapter.cs
  95. 342 0
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  96. 7 1
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  97. 95 6
      src/Avalonia.Controls/Window.cs
  98. 24 0
      src/Avalonia.Controls/WindowBase.cs
  99. 23 0
      src/Avalonia.Controls/WindowStartupLocation.cs
  100. 5 3
      src/Avalonia.DesignerSupport/DesignWindowLoader.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 - 55
Avalonia.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.27130.2024
+VisualStudioVersion = 15.0.27130.2027
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Layout", "src\Aval
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{B39A8919-9F95-48FE-AD7B-76E08B509888}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
 EndProject
@@ -76,7 +76,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
 EndProject
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
 EndProject
@@ -114,7 +114,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}"
 EndProject
@@ -122,14 +122,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
 EndProject
-Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.Win32.Shared", "src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.shproj", "{9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.NetStandard", "src\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj", "{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetCoreRuntime", "src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj", "{7863EA94-F0FB-4386-BF8C-E5BFA761560A}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}"
@@ -150,11 +146,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
 		build\Moq.props = build\Moq.props
 		build\NetCore.props = build\NetCore.props
+		build\NetFX.props = build\NetFX.props
 		build\ReactiveUI.props = build\ReactiveUI.props
 		build\Rx.props = build\Rx.props
+		build\SampleApp.props = build\SampleApp.props
 		build\Serilog.props = build\Serilog.props
 		build\SharpDX.props = build\SharpDX.props
-		build\SkiaSharp.Desktop.props = build\SkiaSharp.Desktop.props
 		build\SkiaSharp.props = build\SkiaSharp.props
 		build\Splat.props = build\Splat.props
 		build\Sprache.props = build\Sprache.props
@@ -196,14 +193,11 @@ Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
-		src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{40759a76-d0f2-464e-8000-6ff0f5c4bd7c}*SharedItemsImports = 4
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4
-		src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
-		src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{9defc6b7-845b-4d8f-afc0-d32bf0032b8c}*SharedItemsImports = 13
 		tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
 	EndGlobalSection
@@ -369,6 +363,7 @@ Global
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
+		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.ActiveCfg = Debug|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.Build.0 = Debug|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -378,6 +373,7 @@ Global
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
+		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.Build.0 = Release|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.ActiveCfg = Release|Any CPU
 		{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.Build.0 = Release|Any CPU
 		{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@@ -1994,46 +1990,6 @@ Global
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU
 		{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.Build.0 = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.Build.0 = Debug|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.Build.0 = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.ActiveCfg = Release|Any CPU
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.Build.0 = Release|Any CPU
 		{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
 		{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
 		{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@@ -2623,8 +2579,6 @@ Global
 		{C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
-		{9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
-		{40759A76-D0F2-464E-8000-6FF0F5C4BD7C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
 		{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
 		{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -2637,6 +2591,7 @@ Global
 		{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+		{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
 		{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution

+ 1 - 1
build/Base.props

@@ -2,4 +2,4 @@
   <ItemGroup>
     <PackageReference Include="System.ValueTuple" Version="4.3.1" />
   </ItemGroup>
-</Project>
+</Project>

+ 11 - 0
build/EmbedXaml.props

@@ -0,0 +1,11 @@
+<Project DefaultTargets="Build" 
+  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+     <Compile Update="**\*.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+    <EmbeddedResource Include="**\*.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+</Project>

+ 0 - 1
build/Rx.props

@@ -5,6 +5,5 @@
     <PackageReference Include="System.Reactive.Interfaces" Version="3.1.1" />
     <PackageReference Include="System.Reactive.Linq" Version="3.1.1" />
     <PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" />
-    <PackageReference Condition="$(TargetFramework.StartsWith('net4'))" Include="System.Reactive.Windows.Threading" Version="3.1.1" />
   </ItemGroup>
 </Project>

+ 13 - 0
build/SampleApp.props

@@ -0,0 +1,13 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Condition="'$(TargetFramework)'=='net461'" >
+    <OutputType>WinExe</OutputType>
+  </PropertyGroup>
+
+  <!-- Should be a Condition="'$(TargetFramework)'=='net461'" here but that doesn't work due
+       to https://github.com/dotnet/sdk/issues/1227 -->
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
+  </ItemGroup>
+  <Import Condition="'$(TargetFramework)'=='net461'" Project="SharpDX.props" />
+</Project>

+ 13 - 0
build/SharedVersion.props

@@ -0,0 +1,13 @@
+<Project DefaultTargets="Build" 
+  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Product>Avalonia</Product>
+    <Version>0.6.2</Version>
+    <Copyright>Copyright 2016 &#169; The AvaloniaUI Project</Copyright>
+    <PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
+    <PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
+    <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <NoWarn>CS1591</NoWarn>
+  </PropertyGroup>
+</Project>

+ 5 - 0
build/System.Drawing.Common.props

@@ -0,0 +1,5 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <PackageReference Include="System.Drawing.Common" Version="4.5.0-preview1-25914-04" />
+  </ItemGroup>
+</Project>

+ 3 - 4
packages.cake

@@ -370,14 +370,13 @@ public class Packages
             new NuGetPackSettings()
             {
                 Id = "Avalonia.Win32",
-                Dependencies = new []
+                Dependencies = new DependencyBuilder(this)
                 {
                     new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
-                },
+                }.Deps(new string[]{null}, "System.Drawing.Common"),
                 Files = new []
                 {
-                    new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/Avalonia.Win32.dll", Target = "lib/net45" },
-                    new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
+                    new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
                 },
                 BasePath = context.Directory("./src/Windows"),
                 OutputDirectory = parameters.NugetRoot

+ 1 - 1
parameters.cake

@@ -97,7 +97,7 @@ public class Parameters
             else
             {
                 // Use AssemblyVersion with Build as version
-                Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-alpha";
+                Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
             }
         }
 

+ 34 - 4
readme.md

@@ -2,9 +2,9 @@
 
 # Avalonia
 
-| Gitter Chat | Windows Build Status | Linux/Mac Build Status |
-|---|---|---|
-| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) |
+| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Open Collective |
+|---|---|---|---|
+|  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
 
 ## About
 
@@ -35,7 +35,7 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
 
 ## Documentation
 
-As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/guides/quickstart) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
+As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
 
 There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
 
@@ -48,3 +48,33 @@ See the [build instructions here](http://avaloniaui.net/contributing/build).
 ## Contributing
 
 Please read the [contribution guidelines](http://avaloniaui.net/contributing/contributing) before submitting a pull request.
+
+### Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
+<a href="graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
+
+
+### Backers
+
+Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]
+
+<a href="https://opencollective.com/Avalonia#backers" target="_blank"><img src="https://opencollective.com/Avalonia/backers.svg?width=890"></a>
+
+
+### Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/Avalonia#sponsor)]
+
+<a href="https://opencollective.com/Avalonia/sponsor/0/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/0/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/1/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/1/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/2/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/2/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/3/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/3/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/4/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/4/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/5/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/5/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/6/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/6/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/7/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/7/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/8/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/8/avatar.svg"></a>
+<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
+
+

+ 26 - 148
samples/BindingTest/BindingTest.csproj

@@ -1,155 +1,33 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>BindingTest</RootNamespace>
-    <AssemblyName>BindingTest</AssemblyName>
-    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
-    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.ComponentModel.DataAnnotations" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="App.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <Compile Include="App.xaml.cs">
-      <DependentUpon>App.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="MainWindow.xaml.cs">
-      <DependentUpon>MainWindow.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="TestItemView.xaml.cs">
-      <DependentUpon>TestItemView.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="ViewModels\DataAnnotationsErrorViewModel.cs" />
-    <Compile Include="ViewModels\IndeiErrorViewModel.cs" />
-    <Compile Include="ViewModels\ExceptionErrorViewModel.cs" />
-    <Compile Include="ViewModels\MainWindowViewModel.cs" />
-    <Compile Include="ViewModels\NestedCommandViewModel.cs" />
-    <Compile Include="ViewModels\TestItem.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config" />
-    <EmbeddedResource Include="MainWindow.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="TestItemView.xaml" />
-  </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
-      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
-      <Name>Avalonia.Diagnostics</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
-      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
-      <Name>Avalonia.ReactiveUI</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    
+    <ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+                      
+    <ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\..\build\SampleApp.props" />
+  <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />

+ 13 - 2
samples/BindingTest/MainWindow.xaml

@@ -1,11 +1,18 @@
 <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"
+        Title="AvaloniaUI Bindings Test"
+        Width="800"
+        Height="600">
   <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 +47,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"/>

+ 0 - 36
samples/BindingTest/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("BindingTest")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("BindingTest")]
-[assembly: AssemblyCopyright("Copyright ©  2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("08b3e6b9-1cd5-443c-9f61-6d49d1c5f162")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 13 - 130
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@@ -1,138 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
+
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{2B888490-D14A-4BCA-AB4B-48676FA93C9B}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>ControlCatalog.Desktop</RootNamespace>
-    <AssemblyName>ControlCatalog.Desktop</AssemblyName>
-    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
-    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net461</TargetFramework>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>x86</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
+
   <ItemGroup>
-    <None Include="App.config" />
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
+    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
+
   <ItemGroup>
-    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj">
-      <Project>{bb1f7bb5-6ad4-4776-94d9-c09d0a972658}</Project>
-      <Name>Avalonia.Gtk3</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3E53A01A-B331-47F3-B828-4A5717E77A24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417E941-21BC-467B-A771-0DE389353CE6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{3E908F67-5543-4879-A1DC-08EACE79B3CD}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{811A76CF-1CF6-440F-963B-BBE31BD72A82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj">
-      <Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
-      <Name>ControlCatalog</Name>
-    </ProjectReference>
+    <Folder Include="Properties\" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+
+  <Import Project="..\..\build\SampleApp.props" />
   <Import Project="..\..\build\Serilog.props" />
-  <Import Project="..\..\build\SkiaSharp.props" />
 </Project>

+ 1 - 0
samples/ControlCatalog.Desktop/Program.cs

@@ -10,6 +10,7 @@ namespace ControlCatalog
 {
     internal class Program
     {
+        [STAThread]
         static void Main(string[] args)
         {
             // TODO: Make this work with GTK/Skia/Cairo depending on command-line args

+ 0 - 36
samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("ControlCatalog.Desktop")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ControlCatalog.Desktop")]
-[assembly: AssemblyCopyright("Copyright ©  2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("2b888490-d14a-4bca-ab4b-48676fa93c9b")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 2 - 0
samples/ControlCatalog.NetCore/Program.cs

@@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore
 {
     static class Program
     {
+        
         static void Main(string[] args)
         {
+            Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
             if (args.Contains("--wait-for-attach"))
             {
                 Console.WriteLine("Attach debugger and use 'Set next statement'");

+ 2 - 3
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@@ -170,15 +170,14 @@
       <Name>Avalonia.Themes.Default</Name>
     </ProjectReference>
     <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{D2D3083-71DD-4CC9-8907-39A0D86FB322}</Project>
+      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
       <Name>Avalonia.Skia</Name>
-      <IsAppExtension>false</IsAppExtension>
-      <IsWatchApp>false</IsWatchApp>
     </ProjectReference>
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj">
       <Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
       <Name>ControlCatalog</Name>
     </ProjectReference>
   </ItemGroup>
+  <Import Project="..\..\build\Sprache.props" />
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
 </Project>

+ 7 - 187
samples/ControlCatalog/ControlCatalog.csproj

@@ -1,183 +1,17 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netstandard2.0</TargetFramework>
-    <EnableDefaultCompileItems>False</EnableDefaultCompileItems>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <TargetFramework>netstandard2.0</TargetFramework>    
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <ItemGroup>
-    <None Remove="Pages\ContextMenuPage.xaml" />
-  </ItemGroup>
-  <ItemGroup>
-    <!-- A reference to the entire .NET Framework is automatically included -->
-    <EmbeddedResource Include="App.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="MainView.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="DecoratedWindow.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\DialogsPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\BorderPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ButtonPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\CalendarPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\CanvasPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\CarouselPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\CheckBoxPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ContextMenuPage.xaml" />
-    <EmbeddedResource Include="Pages\DropDownPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\DatePickerPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ExpanderPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ImagePage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\LayoutTransformControlPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\MenuPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ProgressBarPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\RadioButtonPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\SliderPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\TextBoxPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Pages\ToolTipPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
   <ItemGroup>
-    <Compile Include="App.xaml.cs">
-      <DependentUpon>App.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="MainView.xaml.cs">
-      <DependentUpon>MainView.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="DecoratedWindow.xaml.cs">
-      <DependentUpon>DecoratedWindow.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="MainWindow.xaml.cs">
-      <DependentUpon>MainWindow.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\DialogsPage.xaml.cs">
-      <DependentUpon>DialogsPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\BorderPage.xaml.cs">
-      <DependentUpon>BorderPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ButtonPage.xaml.cs">
-      <DependentUpon>ButtonPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\CalendarPage.xaml.cs">
-      <DependentUpon>CalendarPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\CanvasPage.xaml.cs">
-      <DependentUpon>CanvasPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\CarouselPage.xaml.cs">
-      <DependentUpon>CarouselPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ContextMenuPage.xaml.cs">
-      <DependentUpon>ContextMenuPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\CheckBoxPage.xaml.cs">
-      <DependentUpon>CheckBoxPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\DropDownPage.xaml.cs">
-      <DependentUpon>DropDownPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\DatePickerPage.xaml.cs">
-      <DependentUpon>DatePickerPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ExpanderPage.xaml.cs">
-      <DependentUpon>ExpanderPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ImagePage.xaml.cs">
-      <DependentUpon>ImagePage.xaml</DependentUpon>
+    <Compile Update="**\*.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
     </Compile>
-    <Compile Include="Pages\LayoutTransformControlPage.xaml.cs">
-      <DependentUpon>LayoutTransformControlPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\MenuPage.xaml.cs">
-      <DependentUpon>MenuPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ProgressBarPage.xaml.cs">
-      <DependentUpon>ProgressBarPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\RadioButtonPage.xaml.cs">
-      <DependentUpon>RadioButtonPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\SliderPage.xaml.cs">
-      <DependentUpon>SliderPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\TreeViewPage.xaml.cs">
-      <DependentUpon>TreeViewPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\TextBoxPage.xaml.cs">
-      <DependentUpon>TextBoxPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ToolTipPage.xaml.cs">
-      <DependentUpon>ToolTipPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ScreenPage.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Assets\delicate-arch-896885_640.jpg" />
-    <EmbeddedResource Include="Assets\github_icon.png" />
-    <EmbeddedResource Include="Assets\hirsch-899118_640.jpg" />
-    <EmbeddedResource Include="Assets\maple-leaf-888807_640.jpg" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="SideBar.xaml">
+    <EmbeddedResource Include="**\*.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
+    <EmbeddedResource Include="Assets\*" />
   </ItemGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
@@ -194,20 +28,6 @@
     <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
   </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Assets\test_icon.ico" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\TreeViewPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="MainWindow.xaml">
-      <Generator>MSBuild:Compile</Generator>
-      <SubType>
-      </SubType>
-    </EmbeddedResource>
-  </ItemGroup>
+  
   <Import Project="..\..\build\Serilog.props" />
 </Project>

+ 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">

+ 7 - 3
samples/ControlCatalog/MainView.xaml

@@ -1,23 +1,27 @@
 <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>
       <CrossFade Duration="0.25"/>
     </TabControl.Transition>
+    <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
-    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem> 
+    <TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
+    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
     <TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
     <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
     <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
     <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
     <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
+    <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
     <TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
     <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
     <TabItem Header="Image"><pages:ImagePage/></TabItem>
     <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
     <TabItem Header="Menu"><pages:MenuPage/></TabItem>
+	  <TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
     <TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
     <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
     <TabItem Header="Slider"><pages:SliderPage/></TabItem>
@@ -25,4 +29,4 @@
     <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
     <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
   </TabControl>
-</UserControl>
+</UserControl>

+ 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>

+ 59 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -0,0 +1,59 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Classes="h1">AutoCompleteBox</TextBlock>
+    <TextBlock Classes="h2">A control into which the user can input text</TextBlock>
+
+    <StackPanel Orientation="Horizontal"
+              Margin="0,16,0,0"
+              HorizontalAlignment="Center"
+              Gap="8">
+      <StackPanel Orientation="Vertical">
+        <TextBlock Text="MinimumPrefixLength: 1"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPrefixLength="1"/>
+        <TextBlock Text="MinimumPrefixLength: 3"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPrefixLength="3"/>
+        <TextBlock Text="MinimumPopulateDelay: 1 Second"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MinimumPopulateDelay="1"/>
+        <TextBlock Text="MaxDropDownHeight: 60"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         MaxDropDownHeight="60"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         Watermark="Watermark"/>
+        <TextBlock Text="Disabled"/>
+        <AutoCompleteBox Width="200"
+                         IsEnabled="False"/>
+      </StackPanel>
+      
+
+      <StackPanel Orientation="Vertical">
+        
+        <TextBlock Text="ValueMemeberSelector"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         ValueMemberSelector="Capital"/>
+        <TextBlock Text="ValueMemberBinding"/>
+        <AutoCompleteBox Width="200"
+                         Margin="0,0,0,8"
+                         ValueMemberBinding="{Binding Capital}"/>
+        <TextBlock Text="Multi-Binding"/>
+        <AutoCompleteBox Name="MultiBindingBox"
+                         Width="200"
+                         Margin="0,0,0,8"
+                         FilterMode="Contains"/>
+        <TextBlock Text="Async Populate"/>
+        <AutoCompleteBox Name="AsyncBox"
+                         Width="200"
+                         Margin="0,0,0,8"
+                         FilterMode="None"/>
+      </StackPanel>
+    </StackPanel>
+  </StackPanel>
+</UserControl>

+ 143 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -0,0 +1,143 @@
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Markup;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ControlCatalog.Pages
+{
+    public class AutoCompleteBoxPage : UserControl
+    {
+        public class StateData
+        {
+            public string Name { get; private set; }
+            public string Abbreviation { get; private set; }
+            public string Capital { get; private set; }
+
+            public StateData(string name, string abbreviatoin, string capital)
+            {
+                Name = name;
+                Abbreviation = abbreviatoin;
+                Capital = capital;
+            }
+
+            public override string ToString()
+            {
+                return Name;
+            }
+        }
+
+        private StateData[] BuildAllStates()
+        {
+            return new StateData[]
+            {
+                new StateData("Alabama","AL","Montgomery"),
+                new StateData("Alaska","AK","Juneau"),
+                new StateData("Arizona","AZ","Phoenix"),
+                new StateData("Arkansas","AR","Little Rock"),
+                new StateData("California","CA","Sacramento"),
+                new StateData("Colorado","CO","Denver"),
+                new StateData("Connecticut","CT","Hartford"),
+                new StateData("Delaware","DE","Dover"),
+                new StateData("Florida","FL","Tallahassee"),
+                new StateData("Georgia","GA","Atlanta"),
+                new StateData("Hawaii","HI","Honolulu"),
+                new StateData("Idaho","ID","Boise"),
+                new StateData("Illinois","IL","Springfield"),
+                new StateData("Indiana","IN","Indianapolis"),
+                new StateData("Iowa","IA","Des Moines"),
+                new StateData("Kansas","KS","Topeka"),
+                new StateData("Kentucky","KY","Frankfort"),
+                new StateData("Louisiana","LA","Baton Rouge"),
+                new StateData("Maine","ME","Augusta"),
+                new StateData("Maryland","MD","Annapolis"),
+                new StateData("Massachusetts","MA","Boston"),
+                new StateData("Michigan","MI","Lansing"),
+                new StateData("Minnesota","MN","St. Paul"),
+                new StateData("Mississippi","MS","Jackson"),
+                new StateData("Missouri","MO","Jefferson City"),
+                new StateData("Montana","MT","Helena"),
+                new StateData("Nebraska","NE","Lincoln"),
+                new StateData("Nevada","NV","Carson City"),
+                new StateData("New Hampshire","NH","Concord"),
+                new StateData("New Jersey","NJ","Trenton"),
+                new StateData("New Mexico","NM","Santa Fe"),
+                new StateData("New York","NY","Albany"),
+                new StateData("North Carolina","NC","Raleigh"),
+                new StateData("North Dakota","ND","Bismarck"),
+                new StateData("Ohio","OH","Columbus"),
+                new StateData("Oklahoma","OK","Oklahoma City"),
+                new StateData("Oregon","OR","Salem"),
+                new StateData("Pennsylvania","PA","Harrisburg"),
+                new StateData("Rhode Island","RI","Providence"),
+                new StateData("South Carolina","SC","Columbia"),
+                new StateData("South Dakota","SD","Pierre"),
+                new StateData("Tennessee","TN","Nashville"),
+                new StateData("Texas","TX","Austin"),
+                new StateData("Utah","UT","Salt Lake City"),
+                new StateData("Vermont","VT","Montpelier"),
+                new StateData("Virginia","VA","Richmond"),
+                new StateData("Washington","WA","Olympia"),
+                new StateData("West Virginia","WV","Charleston"),
+                new StateData("Wisconsin","WI","Madison"),
+                new StateData("Wyoming","WY","Cheyenne"),
+            };
+        }
+        public StateData[] States { get; private set; }
+        
+        public AutoCompleteBoxPage()
+        {
+            this.InitializeComponent();
+
+            States = BuildAllStates();
+
+            foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
+            {
+                box.Items = States;
+            }
+
+            var converter = new FuncMultiValueConverter<string, string>(parts =>
+            {
+                return String.Format("{0} ({1})", parts.ToArray());
+            });
+            var binding = new MultiBinding { Converter = converter };
+            binding.Bindings.Add(new Binding("Name"));
+            binding.Bindings.Add(new Binding("Abbreviation"));
+
+            var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
+            multibindingBox.ValueMemberBinding = binding;
+
+            var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
+            asyncBox.AsyncPopulator = PopulateAsync;
+        }
+        private IEnumerable<AutoCompleteBox> GetAllAutoCompleteBox()
+        {
+            return
+                this.GetLogicalDescendants()
+                    .OfType<AutoCompleteBox>();
+        }
+
+        private bool StringContains(string str, string query)
+        {
+            return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
+        }
+        private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
+        {
+            await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
+
+            return
+                States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
+                      .ToList();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 24 - 0
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@@ -0,0 +1,24 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Classes="h1">ButtonSpinner</TextBlock>
+    <TextBlock Classes="h2">The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.</TextBlock>
+
+    <StackPanel Orientation="Vertical" Gap="8" Width="200" Margin="0,20,0,0">
+      <CheckBox Name="allowSpinCheck" IsChecked="True">AllowSpin</CheckBox>
+      <CheckBox Name="showSpinCheck" IsChecked="True">ShowButtonSpinner</CheckBox>
+      <ButtonSpinner Spin="OnSpin" Height="30"
+                     AllowSpin="{Binding #allowSpinCheck.IsChecked}"
+                     ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
+        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
+      </ButtonSpinner>
+      <ButtonSpinner Spin="OnSpin" Height="30" ButtonSpinnerLocation="Left"
+                     AllowSpin="{Binding #allowSpinCheck.IsChecked}"
+                     ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
+        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
+      </ButtonSpinner>
+    </StackPanel>
+  </StackPanel>
+
+</UserControl>

+ 54 - 0
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@@ -0,0 +1,54 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class ButtonSpinnerPage : UserControl
+    {
+        public ButtonSpinnerPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        private void OnSpin(object sender, SpinEventArgs e)
+        {
+            var spinner = (ButtonSpinner)sender;
+            var txtBox = (TextBlock)spinner.Content;
+
+            int value = Array.IndexOf(_mountains, txtBox.Text);
+            if (e.Direction == SpinDirection.Increase)
+                value++;
+            else
+                value--;
+
+            if (value < 0)
+                value = _mountains.Length - 1;
+            else if (value >= _mountains.Length)
+                value = 0;
+
+            txtBox.Text = _mountains[value];
+        }
+
+        private readonly string[] _mountains = new[]
+        {
+            "Everest",
+            "K2 (Mount Godwin Austen)",
+            "Kangchenjunga",
+            "Lhotse",
+            "Makalu",
+            "Cho Oyu",
+            "Dhaulagiri",
+            "Manaslu",
+            "Nanga Parbat",
+            "Annapurna"
+        };
+    }
+}

+ 19 - 0
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@@ -0,0 +1,19 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+    <StackPanel Orientation="Vertical" Gap="4">
+        <TextBlock Classes="h1">Drag+Drop</TextBlock>
+        <TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
+
+        <StackPanel Orientation="Horizontal"
+                Margin="0,16,0,0"
+                HorizontalAlignment="Center"
+                Gap="16">
+            <Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
+                <TextBlock Name="DragState">Drag Me</TextBlock>
+            </Border>
+            <Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16" 
+                    DragDrop.AllowDrop="True">
+                <TextBlock Name="DropState">Drop some text or files here</TextBlock>
+            </Border>
+        </StackPanel>
+    </StackPanel>
+</UserControl>

+ 71 - 0
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@@ -0,0 +1,71 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ControlCatalog.Pages
+{
+    public class DragAndDropPage : UserControl
+    {
+        private TextBlock _DropState;
+        private TextBlock _DragState;
+        private Border _DragMe;
+        private int DragCount = 0;
+
+        public DragAndDropPage()
+        {
+            this.InitializeComponent();
+
+            _DragMe.PointerPressed += DoDrag;
+
+            AddHandler(DragDrop.DropEvent, Drop);
+            AddHandler(DragDrop.DragOverEvent, DragOver);
+        }
+
+        private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
+        {
+            DataObject dragData = new DataObject();
+            dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
+
+            var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
+            switch(result)
+            {
+                case DragDropEffects.Copy:
+                    _DragState.Text = "The text was copied"; break;
+                case DragDropEffects.Link:
+                    _DragState.Text = "The text was linked"; break;
+                case DragDropEffects.None:
+                    _DragState.Text = "The drag operation was canceled"; break;
+            }
+        }
+
+        private void DragOver(object sender, DragEventArgs e)
+        {
+            // Only allow Copy or Link as Drop Operations.
+            e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
+
+            // Only allow if the dragged data contains text or filenames.
+            if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
+                e.DragEffects = DragDropEffects.None; 
+        }
+
+        private void Drop(object sender, DragEventArgs e)
+        {
+            if (e.Data.Contains(DataFormats.Text))
+                _DropState.Text = e.Data.GetText();
+            else if (e.Data.Contains(DataFormats.FileNames))
+                _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+
+            _DropState = this.Find<TextBlock>("DropState");
+            _DragState = this.Find<TextBlock>("DragState");
+            _DragMe = this.Find<Border>("DragMe");
+        }
+    }
+}

+ 80 - 0
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -0,0 +1,80 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
+    <TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
+
+    <TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock>
+    <Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto">
+      <Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="35,35,35,35,35">
+        <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock>
+        <CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #upDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/>
+
+        <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">IsReadOnly:</TextBlock>
+        <CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #upDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
+
+        <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock>
+        <CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" IsEnabled="{Binding #upDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
+
+        <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock>
+        <CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
+
+      </Grid>
+      <Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="35,35,35,35,35">
+        <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
+        <DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
+                  VerticalAlignment="Center" Margin="2">
+          <DropDown.ItemTemplate>
+            <DataTemplate>
+              <StackPanel Orientation="Horizontal" Gap="2">
+                <TextBlock Text="{Binding Name}"/>
+                <TextBlock Text="-"/>
+                <TextBlock Text="{Binding Value}"/>
+              </StackPanel>
+            </DataTemplate>
+          </DropDown.ItemTemplate>
+        </DropDown>
+
+        <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
+        <DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
+                  VerticalAlignment="Center" Margin="2"/>
+
+        <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
+        <DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
+                  VerticalAlignment="Center" Margin="2"/>
+
+        <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>
+        <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.Watermark}" VerticalAlignment="Center" Margin="2" />
+
+        <TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock>
+        <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" />
+      </Grid>
+      <Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="35,35,35,35,35" ColumnDefinitions="Auto, 120">
+        <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
+        <NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
+                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+
+        <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
+        <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
+                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+
+        <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
+        <NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
+                       Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+
+        <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
+        <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
+                       Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+
+      </Grid>
+    </Grid>
+
+    <StackPanel Margin="2,10,2,2" Orientation="Horizontal" Gap="10">
+      <TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock>
+      <NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
+                     CultureInfo="en-US" VerticalAlignment="Center" Height="25" Width="100"
+                     Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
+    </StackPanel>
+
+  </StackPanel>
+</UserControl>

+ 94 - 0
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Markup.Xaml;
+using ReactiveUI;
+
+namespace ControlCatalog.Pages
+{
+    public class NumericUpDownPage : UserControl
+    {
+        public NumericUpDownPage()
+        {
+            this.InitializeComponent();
+            var viewModel = new NumbersPageViewModel();
+            DataContext = viewModel;
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+    }
+
+    public class NumbersPageViewModel : ReactiveObject
+    {
+        private IList<FormatObject> _formats;
+        private FormatObject _selectedFormat;
+        private IList<Location> _spinnerLocations;
+
+        public NumbersPageViewModel()
+        {
+            SelectedFormat = Formats.FirstOrDefault();
+        }
+
+        public IList<FormatObject> Formats
+        {
+            get
+            {
+                return _formats ?? (_formats = new List<FormatObject>()
+                {
+                    new FormatObject() {Name = "Currency", Value = "C2"},
+                    new FormatObject() {Name = "Fixed point", Value = "F2"},
+                    new FormatObject() {Name = "General", Value = "G"},
+                    new FormatObject() {Name = "Number", Value = "N"},
+                    new FormatObject() {Name = "Percent", Value = "P"},
+                    new FormatObject() {Name = "Degrees", Value = "{0:N2} °"},
+                });
+            }
+        }
+
+        public IList<Location> SpinnerLocations
+        {
+            get
+            {
+                if (_spinnerLocations == null)
+                {
+                    _spinnerLocations = new List<Location>();
+                    foreach (Location value in Enum.GetValues(typeof(Location)))
+                    {
+                        _spinnerLocations.Add(value);
+                    }
+                }
+                return _spinnerLocations ;
+            }
+        }
+
+        public IList<CultureInfo> Cultures { get; } = new List<CultureInfo>()
+        {
+            new CultureInfo("en-US"),
+            new CultureInfo("en-GB"),
+            new CultureInfo("fr-FR"),
+            new CultureInfo("ar-DZ"),
+            new CultureInfo("zh-CN"),
+            new CultureInfo("cs-CZ")
+        };
+
+        public FormatObject SelectedFormat
+        {
+            get { return _selectedFormat; }
+            set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }
+        }
+    }
+
+    public class FormatObject
+    {
+        public string Value { get; set; }
+        public string Name { get; set; }
+    }
+}

+ 0 - 36
samples/ControlCatalog/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("ControlCatalog")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ControlCatalog")]
-[assembly: AssemblyCopyright("Copyright ©  2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 3 - 0
samples/Directory.Build.props

@@ -0,0 +1,3 @@
+<Project>
+  <Import Project="..\build\SharedVersion.props" />
+</Project>

+ 4 - 2
samples/RenderTest/MainWindow.xaml

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

+ 0 - 36
samples/RenderTest/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("RenderTest")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("RenderTest")]
-[assembly: AssemblyCopyright("Copyright ©  2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("f1fdc5b0-4654-416f-ae69-e3e9bbd87801")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 27 - 178
samples/RenderTest/RenderTest.csproj

@@ -1,184 +1,33 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>RenderTest</RootNamespace>
-    <AssemblyName>RenderTest</AssemblyName>
-    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
-    <Reference Include="WindowsBase" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="App.xaml.cs">
-      <DependentUpon>App.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\DrawingPage.xaml.cs">
-      <DependentUpon>DrawingPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ClippingPage.xaml.cs">
-      <DependentUpon>ClippingPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\AnimationsPage.xaml.cs">
-      <DependentUpon>AnimationsPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="MainWindow.xaml.cs">
-      <DependentUpon>MainWindow.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="ViewModels\MainWindowViewModel.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="App.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
-      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
-      <Name>Avalonia.Diagnostics</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
-      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
-      <Name>Avalonia.ReactiveUI</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
-      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
-      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="MainWindow.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="SideBar.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\AnimationsPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\ClippingPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\DrawingPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    
+    <ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+                      
+    <ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
+  </ItemGroup>
+  <Import Project="..\..\build\SampleApp.props" />
+  <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />

+ 3 - 1
samples/VirtualizationTest/MainWindow.xaml

@@ -1,5 +1,7 @@
 <Window xmlns="https://github.com/avaloniaui"
-        Title="Avalonia Virtualization Test">
+        Title="AvaloniaUI Virtualization Test"
+        Width="800"
+        Height="600">
     <DockPanel LastChildFill="True" Margin="16">
         <StackPanel DockPanel.Dock="Right" 
                     Margin="16 0 0 0" 

+ 0 - 36
samples/VirtualizationTest/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following 
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("VirtualizationTest")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("VirtualizationTest")]
-[assembly: AssemblyCopyright("Copyright ©  2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible 
-// to COM components.  If you need to access a type in this assembly from 
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("fbcaf3d0-2808-4934-8e96-3f607594517b")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version 
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers 
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 26 - 144
samples/VirtualizationTest/VirtualizationTest.csproj

@@ -1,151 +1,33 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{FBCAF3D0-2808-4934-8E96-3F607594517B}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>VirtualizationTest</RootNamespace>
-    <AssemblyName>VirtualizationTest</AssemblyName>
-    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="App.xaml.cs">
-      <DependentUpon>App.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="MainWindow.xaml.cs">
-      <DependentUpon>MainWindow.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="ViewModels\ItemViewModel.cs" />
-    <Compile Include="ViewModels\MainWindowViewModel.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
-      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
-      <Name>Avalonia.Diagnostics</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
-      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
-      <Name>Avalonia.ReactiveUI</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
-      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="App.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="MainWindow.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    
+    <ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+                      
+    <ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
+
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\..\build\SampleApp.props" />
+  <Import Project="..\..\build\EmbedXaml.props" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />

+ 3 - 1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@@ -17,7 +17,9 @@
       <None Remove="MiniCube.fx" />
     </ItemGroup>
     <ItemGroup>
-      <EmbeddedResource Include="MiniCube.fx" />
+      <EmbeddedResource Include="MiniCube.fx">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </EmbeddedResource>
     </ItemGroup>
     <ItemGroup>
         <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />

+ 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

+ 0 - 28
src/Avalonia.Animation/Avalonia.Animation.csproj

@@ -1,35 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Animation.xml</DocumentationFile>
-    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Animation.xml</DocumentationFile>
-    <NoWarn>CS1591</NoWarn>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-  </PropertyGroup>
-  <ItemGroup>
-    <Compile Include="..\Shared\SharedAssemblyInfo.cs">
-      <Link>Properties\SharedAssemblyInfo.cs</Link>
-    </Compile>
-  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
   </ItemGroup>

+ 0 - 6
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@@ -1,6 +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 System.Reflection;
-
-[assembly: AssemblyTitle("Avalonia.Animation")]

+ 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;
         }
     }
 }

+ 1 - 28
src/Avalonia.Base/Avalonia.Base.csproj

@@ -1,36 +1,9 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
-    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <AssemblyName>Avalonia.Base</AssemblyName>
     <RootNamespace>Avalonia</RootNamespace>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Base.xml</DocumentationFile>
-    <NoWarn>CS1591</NoWarn>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Base.xml</DocumentationFile>
-    <NoWarn>CS1591</NoWarn>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-  </PropertyGroup>
-  <ItemGroup>
-    <Compile Include="..\Shared\SharedAssemblyInfo.cs">
-      <Link>Properties\SharedAssemblyInfo.cs</Link>
-    </Compile>
-  </ItemGroup>
   <Import Project="..\..\build\Base.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />

+ 31 - 32
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
 {
@@ -72,7 +71,8 @@ namespace Avalonia
         public AvaloniaObject()
         {
             VerifyAccess();
-            foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
+
+            void Notify(AvaloniaProperty property)
             {
                 object value = property.IsDirect ?
                     ((IDirectPropertyAccessor)property).GetValue(this) :
@@ -87,6 +87,16 @@ namespace Avalonia
 
                 property.NotifyInitialized(e);
             }
+
+            foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
+            {
+                Notify(property);
+            }
+
+            foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()))
+            {
+                Notify(property);
+            }
         }
 
         /// <summary>
@@ -218,11 +228,6 @@ namespace Avalonia
             }
             else
             {
-                if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
-                {
-                    ThrowNotRegistered(property);
-                }
-
                 return GetValueInternal(property);
             }
         }
@@ -377,11 +382,6 @@ namespace Avalonia
             {
                 PriorityValue v;
 
-                if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
-                {
-                    ThrowNotRegistered(property);
-                }
-
                 if (!_values.TryGetValue(property, out v))
                 {
                     v = CreatePriorityValue(property);
@@ -804,11 +804,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 +831,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 +907,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();
         }
     }
 }

+ 2 - 2
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@@ -117,7 +117,7 @@ namespace Avalonia.Collections
             _inner = new Dictionary<TKey, TValue>();
 
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
             
 
             if (CollectionChanged != null)
@@ -222,4 +222,4 @@ namespace Avalonia.Collections
             }
         }
     }
-}
+}

+ 21 - 5
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -34,14 +34,18 @@ namespace Avalonia.Collections
         /// <param name="reset">
         /// An action called when the collection is reset.
         /// </param>
+        /// <param name="weakSubscription">
+        /// Indicates if a weak subscription should be used to track changes to the collection.
+        /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
             this IAvaloniaReadOnlyList<T> collection,
             Action<T> added,
             Action<T> removed,
-            Action reset)
+            Action reset,
+            bool weakSubscription = false)
         {
-            return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
+            return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
         }
 
         /// <summary>
@@ -63,12 +67,16 @@ namespace Avalonia.Collections
         /// An action called when the collection is reset. This will be followed by calls to 
         /// <paramref name="added"/> for each item present in the collection after the reset.
         /// </param>
+        /// <param name="weakSubscription">
+        /// Indicates if a weak subscription should be used to track changes to the collection.
+        /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
             this IAvaloniaReadOnlyList<T> collection,
             Action<int, T> added,
             Action<int, T> removed,
-            Action reset)
+            Action reset,
+            bool weakSubscription = false)
         {
             void Add(int index, IList items)
             {
@@ -118,9 +126,17 @@ namespace Avalonia.Collections
             };
 
             Add(0, (IList)collection);
-            collection.CollectionChanged += handler;
 
-            return Disposable.Create(() => collection.CollectionChanged -= handler);
+            if (weakSubscription)
+            {
+                return collection.WeakSubscribe(handler);
+            }
+            else
+            {
+                collection.CollectionChanged += handler;
+
+                return Disposable.Create(() => collection.CollectionChanged -= handler);
+            }
         }
 
         public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(

+ 127 - 0
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@@ -0,0 +1,127 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Utilities;
+
+namespace Avalonia.Collections
+{
+    public static class NotifyCollectionChangedExtensions
+    {
+        /// <summary>
+        /// Gets a weak observable for the CollectionChanged event.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <returns>An observable.</returns>
+        public static IObservable<NotifyCollectionChangedEventArgs> GetWeakCollectionChangedObservable(
+            this INotifyCollectionChanged collection)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+
+            return new WeakCollectionChangedObservable(new WeakReference<INotifyCollectionChanged>(collection));
+        }
+
+        /// <summary>
+        /// Subcribes to the CollectionChanged event using a weak subscription.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <param name="handler">
+        /// An action called when the collection event is raised.
+        /// </param>
+        /// <returns>A disposable used to terminate the subscription.</returns>
+        public static IDisposable WeakSubscribe(
+            this INotifyCollectionChanged collection, 
+            NotifyCollectionChangedEventHandler handler)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            return
+                collection.GetWeakCollectionChangedObservable()
+                          .Subscribe(e => handler.Invoke(collection, e));
+        }
+
+        /// <summary>
+        /// Subcribes to the CollectionChanged event using a weak subscription.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <param name="handler">
+        /// An action called when the collection event is raised.
+        /// </param>
+        /// <returns>A disposable used to terminate the subscription.</returns>
+        public static IDisposable WeakSubscribe(
+            this INotifyCollectionChanged collection,
+            Action<NotifyCollectionChangedEventArgs> handler)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            return
+                collection.GetWeakCollectionChangedObservable()
+                          .Subscribe(handler);
+        }
+
+        private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
+            IWeakSubscriber<NotifyCollectionChangedEventArgs>
+        {
+            private WeakReference<INotifyCollectionChanged> _sourceReference;
+            private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
+
+            private int _count;
+
+            public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
+            {
+                _sourceReference = source;
+            }
+
+            public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
+            {
+                _changed.OnNext(e);
+            }
+
+            protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
+            {
+                if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+                {
+                    if (_count++ == 0)
+                    {
+                        WeakSubscriptionManager.Subscribe(
+                            instance,
+                            nameof(instance.CollectionChanged),
+                            this);
+                    }
+
+                    return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
+                        .Subscribe(observer);
+                }
+                else
+                {
+                    _changed.OnCompleted();
+                    observer.OnCompleted();
+                    return Disposable.Empty;
+                }
+            }
+
+            private void DecrementCount()
+            {
+                if (--_count == 0)
+                {
+                    if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+                    {
+                        WeakSubscriptionManager.Unsubscribe(
+                            instance,
+                            nameof(instance.CollectionChanged),
+                            this);
+                    }
+                }
+            }
+        }
+    }
+}

+ 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);
     }
 }

+ 0 - 1
src/Avalonia.Base/Properties/AssemblyInfo.cs

@@ -4,7 +4,6 @@
 using System.Reflection;
 using System.Runtime.CompilerServices;
 
-[assembly: AssemblyTitle("Avalonia.Base")]
 [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
 [assembly: InternalsVisibleTo("Avalonia.UnitTests")]
 [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 

+ 2 - 0
src/Avalonia.Base/Threading/Dispatcher.cs

@@ -81,12 +81,14 @@ namespace Avalonia.Threading
         /// <inheritdoc/>
         public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
+            Contract.Requires<ArgumentNullException>(action != null);
             return _jobRunner?.InvokeAsync(action, priority);
         }
 
         /// <inheritdoc/>
         public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
+            Contract.Requires<ArgumentNullException>(action != null);
             _jobRunner?.Post(action, priority);
         }
 

+ 219 - 0
src/Avalonia.Base/Utilities/StringTokenizer.cs

@@ -0,0 +1,219 @@
+using System;
+using System.Globalization;
+using static System.Char;
+
+namespace Avalonia.Utilities
+{
+    public struct StringTokenizer : IDisposable
+    {
+        private const char DefaultSeparatorChar = ',';
+
+        private readonly string _s;
+        private readonly int _length;
+        private readonly char _separator;
+        private readonly string _exceptionMessage;
+        private readonly IFormatProvider _formatProvider;
+        private int _index;
+        private int _tokenIndex;
+        private int _tokenLength;
+
+        public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null)
+            : this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
+        {
+            _formatProvider = formatProvider;
+        }
+
+        public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null)
+        {
+            _s = s ?? throw new ArgumentNullException(nameof(s));
+            _length = s?.Length ?? 0;
+            _separator = separator;
+            _exceptionMessage = exceptionMessage;
+            _formatProvider = CultureInfo.InvariantCulture;
+            _index = 0;
+            _tokenIndex = -1;
+            _tokenLength = 0;
+
+            while (_index < _length && IsWhiteSpace(_s, _index))
+            {
+                _index++;
+            }
+        }
+
+        public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
+
+        public void Dispose()
+        {
+            if (_index != _length)
+            {
+                throw GetFormatException();
+            }
+        }
+
+        public bool TryReadInt32(out Int32 result, char? separator = null)
+        {
+            if (TryReadString(out var stringResult, separator) &&
+                int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result))
+            {
+                return true;
+            }
+            else
+            {
+                result = default(Int32);
+                return false;
+            }
+        }
+
+        public int ReadInt32(char? separator = null)
+        {
+            if (!TryReadInt32(out var result, separator))
+            {
+                throw GetFormatException();
+            }
+
+            return result;
+        }
+
+        public bool TryReadDouble(out double result, char? separator = null)
+        {
+            if (TryReadString(out var stringResult, separator) &&
+                double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result))
+            {
+                return true;
+            }
+            else
+            {
+                result = default(double);
+                return false;
+            }
+        }
+
+        public double ReadDouble(char? separator = null)
+        {
+            if (!TryReadDouble(out var result, separator))
+            {
+                throw GetFormatException();
+            }
+
+            return result;
+        }
+
+        public bool TryReadString(out string result, char? separator = null)
+        {
+            var success = TryReadToken(separator ?? _separator);
+            result = CurrentToken;
+            return success;
+        }
+
+        public string ReadString(char? separator = null)
+        {
+            if (!TryReadString(out var result, separator))
+            {
+                throw GetFormatException();
+            }
+
+            return result;
+        }
+
+        private bool TryReadToken(char separator)
+        {
+            _tokenIndex = -1;
+
+            if (_index >= _length)
+            {
+                return false;
+            }
+
+            var c = _s[_index];
+
+            var index = _index;
+            var length = 0;
+
+            while (_index < _length)
+            {
+                c = _s[_index];
+
+                if (IsWhiteSpace(c) || c == separator)
+                {
+                    break;
+                }
+
+                _index++;
+                length++;
+            }
+
+            SkipToNextToken(separator);
+
+            _tokenIndex = index;
+            _tokenLength = length;
+
+            if (_tokenLength < 1)
+            {
+                throw GetFormatException();
+            }
+
+            return true;
+        }
+
+        private void SkipToNextToken(char separator)
+        {
+            if (_index < _length)
+            {
+                var c = _s[_index];
+
+                if (c != separator && !IsWhiteSpace(c))
+                {
+                    throw GetFormatException();
+                }
+
+                var length = 0;
+
+                while (_index < _length)
+                {
+                    c = _s[_index];
+
+                    if (c == separator)
+                    {
+                        length++;
+                        _index++;
+
+                        if (length > 1)
+                        {
+                            throw GetFormatException();
+                        }
+                    }
+                    else
+                    {
+                        if (!IsWhiteSpace(c))
+                        {
+                            break;
+                        }
+
+                        _index++;
+                    }
+                }
+
+                if (length > 0 && _index >= _length)
+                {
+                    throw GetFormatException();
+                }
+            }
+        }
+
+        private FormatException GetFormatException() =>
+            _exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
+
+        private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
+        {
+            var c = DefaultSeparatorChar;
+
+            var formatInfo = NumberFormatInfo.GetInstance(provider);
+            if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
+            {
+                c = ';';
+            }
+
+            return c;
+        }
+    }
+}

+ 6 - 3
src/Avalonia.Controls/Application.cs

@@ -2,16 +2,17 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Reactive.Concurrency;
 using System.Threading;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
+using Avalonia.Input.Raw;
 using Avalonia.Layout;
-using Avalonia.Rendering;
+using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Threading;
-using System.Reactive.Concurrency;
 
 namespace Avalonia
 {
@@ -234,7 +235,9 @@ namespace Avalonia
                 .Bind<IStyler>().ToConstant(_styler)
                 .Bind<ILayoutManager>().ToSingleton<LayoutManager>()
                 .Bind<IApplicationLifecycle>().ToConstant(this)
-                .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
+                .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
+                .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
+                .Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
         }
     }
 }

+ 2730 - 0
src/Avalonia.Controls/AutoCompleteBox.cs

@@ -0,0 +1,2730 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Collections;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Provides data for the
+    /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
+    /// event.
+    /// </summary>
+    public class PopulatedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the list of possible matches added to the drop-down portion of
+        /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
+        /// control.
+        /// </summary>
+        /// <value>The list of possible matches added to the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
+        public IEnumerable Data { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />.
+        /// </summary>
+        /// <param name="data">The list of possible matches added to the
+        /// drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
+        public PopulatedEventArgs(IEnumerable data)
+        {
+            Data = data;
+        }
+    }
+
+    /// <summary>
+    /// Provides data for the
+    /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
+    /// event.
+    /// </summary>
+    /// <QualityBand>Stable</QualityBand>
+    public class PopulatingEventArgs : CancelEventArgs
+    {
+        /// <summary>
+        /// Gets the text that is used to determine which items to display in
+        /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
+        /// control.
+        /// </summary>
+        /// <value>The text that is used to determine which items to display in
+        /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />.</value>
+        public string Parameter { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" />.
+        /// </summary>
+        /// <param name="parameter">The value of the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
+        /// property, which is used to filter items for the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</param>
+        public PopulatingEventArgs(string parameter)
+        {
+            Parameter = parameter;
+        }
+    }
+
+    /// <summary>
+    /// Represents the filter used by the
+    /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control to
+    /// determine whether an item is a possible match for the specified text.
+    /// </summary>
+    /// <returns>true to indicate <paramref name="item" /> is a possible match
+    /// for <paramref name="search" />; otherwise false.</returns>
+    /// <param name="search">The string used as the basis for filtering.</param>
+    /// <param name="item">The item that is compared with the
+    /// <paramref name="search" /> parameter.</param>
+    /// <typeparam name="T">The type used for filtering the
+    /// <see cref="T:Avalonia.Controls.AutoCompleteBox" />. This type can
+    /// be either a string or an object.</typeparam>
+    /// <QualityBand>Stable</QualityBand>
+    public delegate bool AutoCompleteFilterPredicate<T>(string search, T item);
+
+    /// <summary>
+    /// Specifies how text in the text box portion of the
+    /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control is used
+    /// to filter items specified by the
+    /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+    /// property for display in the drop-down.
+    /// </summary>
+    /// <QualityBand>Stable</QualityBand>
+    public enum AutoCompleteFilterMode
+    {
+        /// <summary>
+        /// Specifies that no filter is used. All items are returned.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-insensitive filter where the
+        /// returned items start with the specified text. The filter uses the
+        /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
+        /// the string comparison criteria.
+        /// </summary>
+        StartsWith = 1,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-sensitive filter where the
+        /// returned items start with the specified text. The filter uses the
+        /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
+        /// comparison criteria.
+        /// </summary>
+        StartsWithCaseSensitive = 2,
+
+        /// <summary>
+        /// Specifies an ordinal, case-insensitive filter where the returned
+        /// items start with the specified text. The filter uses the
+        /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
+        /// string comparison criteria.
+        /// </summary>
+        StartsWithOrdinal = 3,
+
+        /// <summary>
+        /// Specifies an ordinal, case-sensitive filter where the returned items
+        /// start with the specified text. The filter uses the
+        /// <see cref="M:System.String.StartsWith(System.String,System.StringComparison)" />
+        /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
+        /// the string comparison criteria.
+        /// </summary>
+        StartsWithOrdinalCaseSensitive = 4,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-insensitive filter where the
+        /// returned items contain the specified text.
+        /// </summary>
+        Contains = 5,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-sensitive filter where the
+        /// returned items contain the specified text.
+        /// </summary>
+        ContainsCaseSensitive = 6,
+
+        /// <summary>
+        /// Specifies an ordinal, case-insensitive filter where the returned
+        /// items contain the specified text.
+        /// </summary>
+        ContainsOrdinal = 7,
+
+        /// <summary>
+        /// Specifies an ordinal, case-sensitive filter where the returned items
+        /// contain the specified text.
+        /// </summary>
+        ContainsOrdinalCaseSensitive = 8,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-insensitive filter where the
+        /// returned items equal the specified text. The filter uses the
+        /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.CurrentCultureIgnoreCase" /> as
+        /// the search comparison criteria.
+        /// </summary>
+        Equals = 9,
+
+        /// <summary>
+        /// Specifies a culture-sensitive, case-sensitive filter where the
+        /// returned items equal the specified text. The filter uses the
+        /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.CurrentCulture" /> as the string
+        /// comparison criteria.
+        /// </summary>
+        EqualsCaseSensitive = 10,
+
+        /// <summary>
+        /// Specifies an ordinal, case-insensitive filter where the returned
+        /// items equal the specified text. The filter uses the
+        /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
+        /// method, specifying
+        /// <see cref="P:System.StringComparer.OrdinalIgnoreCase" /> as the
+        /// string comparison criteria.
+        /// </summary>
+        EqualsOrdinal = 11,
+
+        /// <summary>
+        /// Specifies an ordinal, case-sensitive filter where the returned items
+        /// equal the specified text. The filter uses the
+        /// <see cref="M:System.String.Equals(System.String,System.StringComparison)" />
+        /// method, specifying <see cref="P:System.StringComparer.Ordinal" /> as
+        /// the string comparison criteria.
+        /// </summary>
+        EqualsOrdinalCaseSensitive = 12,
+
+        /// <summary>
+        /// Specifies that a custom filter is used. This mode is used when the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
+        /// or
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
+        /// properties are set.
+        /// </summary>
+        Custom = 13,
+    }
+
+    /// <summary>
+    /// Represents a control that provides a text box for user input and a
+    /// drop-down that contains possible matches based on the input in the text
+    /// box.
+    /// </summary>
+    public class AutoCompleteBox : TemplatedControl
+    {
+        /// <summary>
+        /// Specifies the name of the selection adapter TemplatePart.
+        /// </summary>
+        private const string ElementSelectionAdapter = "PART_SelectionAdapter";
+
+        /// <summary>
+        /// Specifies the name of the Selector TemplatePart.
+        /// </summary>
+        private const string ElementSelector = "PART_SelectingItemsControl";
+
+        /// <summary>
+        /// Specifies the name of the Popup TemplatePart.
+        /// </summary>
+        private const string ElementPopup = "PART_Popup";
+
+        /// <summary>
+        /// The name for the text box part.
+        /// </summary>
+        private const string ElementTextBox = "PART_TextBox";
+
+        private IEnumerable _itemsEnumerable;
+
+        /// <summary>
+        /// Gets or sets a local cached copy of the items data.
+        /// </summary>
+        private List<object> _items;
+
+        /// <summary>
+        /// Gets or sets the observable collection that contains references to
+        /// all of the items in the generated view of data that is provided to
+        /// the selection-style control adapter.
+        /// </summary>
+        private AvaloniaList<object> _view;
+
+        /// <summary>
+        /// Gets or sets a value to ignore a number of pending change handlers.
+        /// The value is decremented after each use. This is used to reset the
+        /// value of properties without performing any of the actions in their
+        /// change handlers.
+        /// </summary>
+        /// <remarks>The int is important as a value because the TextBox
+        /// TextChanged event does not immediately fire, and this will allow for
+        /// nested property changes to be ignored.</remarks>
+        private int _ignoreTextPropertyChange;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether to ignore calling a pending
+        /// change handlers.
+        /// </summary>
+        private bool _ignorePropertyChange;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether to ignore the selection
+        /// changed event.
+        /// </summary>
+        private bool _ignoreTextSelectionChange;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether to skip the text update
+        /// processing when the selected item is updated.
+        /// </summary>
+        private bool _skipSelectedItemTextUpdate;
+
+        /// <summary>
+        /// Gets or sets the last observed text box selection start location.
+        /// </summary>
+        private int _textSelectionStart;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the user initiated the
+        /// current populate call.
+        /// </summary>
+        private bool _userCalledPopulate;
+
+        /// <summary>
+        /// A value indicating whether the popup has been opened at least once.
+        /// </summary>
+        private bool _popupHasOpened;
+
+        /// <summary>
+        /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay
+        /// condition for auto completion.
+        /// </summary>
+        private DispatcherTimer _delayTimer;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether a read-only dependency
+        /// property change handler should allow the value to be set.  This is
+        /// used to ensure that read-only properties cannot be changed via
+        /// SetValue, etc.
+        /// </summary>
+        private bool _allowWrite;
+
+        /// <summary>
+        /// The TextBox template part.
+        /// </summary>
+        private TextBox _textBox;
+        private IDisposable _textBoxSubscriptions;
+
+        /// <summary>
+        /// The SelectionAdapter.
+        /// </summary>
+        private ISelectionAdapter _adapter;
+
+        /// <summary>
+        /// A control that can provide updated string values from a binding.
+        /// </summary>
+        private BindingEvaluator<string> _valueBindingEvaluator;
+
+        /// <summary>
+        /// A weak subscription for the collection changed event.
+        /// </summary>
+        private IDisposable _collectionChangeSubscription;
+
+        private IMemberSelector _valueMemberSelector;
+        private Func<string, CancellationToken, Task<IEnumerable<object>>> _asyncPopulator;
+        private CancellationTokenSource _populationCancellationTokenSource;
+
+        private bool _itemTemplateIsFromValueMemeberBinding = true;
+        private bool _settingItemTemplateFromValueMemeberBinding;
+
+        private object _selectedItem;
+        private bool _isDropDownOpen;
+        private bool _isFocused = false;
+
+        private string _text = string.Empty;
+        private string _searchText = string.Empty;
+
+        private AutoCompleteFilterPredicate<object> _itemFilter;
+        private AutoCompleteFilterPredicate<string> _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
+
+        public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
+            RoutedEvent.Register<SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
+
+        public static readonly StyledProperty<string> WatermarkProperty =
+            TextBox.WatermarkProperty.AddOwner<AutoCompleteBox>();
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPrefixLength" />
+        /// dependency property.</value>
+        public static readonly StyledProperty<int> MinimumPrefixLengthProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, int>(
+                nameof(MinimumPrefixLength), 1,
+                validate: ValidateMinimumPrefixLength);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MinimumPopulateDelay" />
+        /// dependency property.</value>
+        public static readonly StyledProperty<TimeSpan> MinimumPopulateDelayProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, TimeSpan>(
+                nameof(MinimumPopulateDelay),
+                TimeSpan.Zero,
+                validate: ValidateMinimumPopulateDelay);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.MaxDropDownHeight" />
+        /// dependency property.</value>
+        public static readonly StyledProperty<double> MaxDropDownHeightProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, double>(
+                nameof(MaxDropDownHeight),
+                double.PositiveInfinity,
+                validate: ValidateMaxDropDownHeight);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsTextCompletionEnabled" />
+        /// dependency property.</value>
+        public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemTemplate" />
+        /// dependency property.</value>
+        public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
+                nameof(IsDropDownOpen),
+                o => o.IsDropDownOpen,
+                (o, v) => o.IsDropDownOpen = v);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SelectedItem" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, object> SelectedItemProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, object>(
+                nameof(SelectedItem),
+                o => o.SelectedItem,
+                (o, v) => o.SelectedItem = v);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, string> TextProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
+                nameof(Text),
+                o => o.Text,
+                (o, v) => o.Text = v);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.SearchText" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, string> SearchTextProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, string>(
+                nameof(SearchText),
+                o => o.SearchText,
+                unsetValue: string.Empty);
+
+        /// <summary>
+        /// Gets the identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.FilterMode" />
+        /// dependency property.
+        /// </summary>
+        public static readonly StyledProperty<AutoCompleteFilterMode> FilterModeProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterMode>(
+                nameof(FilterMode),
+                defaultValue: AutoCompleteFilterMode.StartsWith,
+                validate: ValidateFilterMode);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemFilter" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object>> ItemFilterProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object>>(
+                nameof(ItemFilter),
+                o => o.ItemFilter,
+                (o, v) => o.ItemFilter = v);
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.TextFilter" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string>> TextFilterProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string>>(
+                nameof(TextFilter),
+                o => o.TextFilter,
+                (o, v) => o.TextFilter = v,
+                unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
+
+        /// <summary>
+        /// Identifies the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// dependency property.
+        /// </summary>
+        /// <value>The identifier for the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// dependency property.</value>
+        public static readonly DirectProperty<AutoCompleteBox, IEnumerable> ItemsProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable>(
+                nameof(Items),
+                o => o.Items,
+                (o, v) => o.Items = v);
+
+        public static readonly DirectProperty<AutoCompleteBox, IMemberSelector> ValueMemberSelectorProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, IMemberSelector>(
+                nameof(ValueMemberSelector),
+                o => o.ValueMemberSelector,
+                (o, v) => o.ValueMemberSelector = v);
+
+        public static readonly DirectProperty<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>> AsyncPopulatorProperty =
+            AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string, CancellationToken, Task<IEnumerable<object>>>>(
+                nameof(AsyncPopulator),
+                o => o.AsyncPopulator,
+                (o, v) => o.AsyncPopulator = v);
+
+        private static int ValidateMinimumPrefixLength(AutoCompleteBox control, int value)
+        {
+            Contract.Requires<ArgumentOutOfRangeException>(value >= -1);
+
+            return value;
+        }
+
+        private static TimeSpan ValidateMinimumPopulateDelay(AutoCompleteBox control, TimeSpan value)
+        {
+            Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds >= 0.0);
+
+            return value;
+        }
+
+        private static double ValidateMaxDropDownHeight(AutoCompleteBox control, double value)
+        {
+            Contract.Requires<ArgumentOutOfRangeException>(value >= 0.0);
+
+            return value;
+        }
+
+        private static bool IsValidFilterMode(AutoCompleteFilterMode mode)
+        {
+            switch (mode)
+            {
+                case AutoCompleteFilterMode.None:
+                case AutoCompleteFilterMode.StartsWith:
+                case AutoCompleteFilterMode.StartsWithCaseSensitive:
+                case AutoCompleteFilterMode.StartsWithOrdinal:
+                case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
+                case AutoCompleteFilterMode.Contains:
+                case AutoCompleteFilterMode.ContainsCaseSensitive:
+                case AutoCompleteFilterMode.ContainsOrdinal:
+                case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
+                case AutoCompleteFilterMode.Equals:
+                case AutoCompleteFilterMode.EqualsCaseSensitive:
+                case AutoCompleteFilterMode.EqualsOrdinal:
+                case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
+                case AutoCompleteFilterMode.Custom:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+        private static AutoCompleteFilterMode ValidateFilterMode(AutoCompleteBox control, AutoCompleteFilterMode value)
+        {
+            Contract.Requires<ArgumentException>(IsValidFilterMode(value));
+
+            return value;
+        }
+
+        /// <summary>
+        /// Handle the change of the IsEnabled property.
+        /// </summary>
+        /// <param name="e">The event data.</param>
+        private void OnControlIsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            bool isEnabled = (bool)e.NewValue;
+            if (!isEnabled)
+            {
+                IsDropDownOpen = false;
+            }
+        }
+
+        /// <summary>
+        /// MinimumPopulateDelayProperty property changed handler. Any current
+        /// dispatcher timer will be stopped. The timer will not be restarted
+        /// until the next TextUpdate call by the user.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnMinimumPopulateDelayChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var newValue = (TimeSpan)e.NewValue;
+
+            // Stop any existing timer
+            if (_delayTimer != null)
+            {
+                _delayTimer.Stop();
+
+                if (newValue == TimeSpan.Zero)
+                {
+                    _delayTimer = null;
+                }
+            }
+
+            if (newValue > TimeSpan.Zero)
+            {
+                // Create or clear a dispatcher timer instance
+                if (_delayTimer == null)
+                {
+                    _delayTimer = new DispatcherTimer();
+                    _delayTimer.Tick += PopulateDropDown;
+                }
+
+                // Set the new tick interval
+                _delayTimer.Interval = newValue;
+            }
+        }
+
+        /// <summary>
+        /// IsDropDownOpenProperty property changed handler.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            // Ignore the change if requested
+            if (_ignorePropertyChange)
+            {
+                _ignorePropertyChange = false;
+                return;
+            }
+
+            bool oldValue = (bool)e.OldValue;
+            bool newValue = (bool)e.NewValue;
+
+            if (newValue)
+            {
+                TextUpdated(Text, true);
+            }
+            else
+            {
+                ClosingDropDown(oldValue);
+            }
+
+            UpdatePseudoClasses();
+        }
+
+        private void OnSelectedItemPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_ignorePropertyChange)
+            {
+                _ignorePropertyChange = false;
+                return;
+            }
+
+            // Update the text display
+            if (_skipSelectedItemTextUpdate)
+            {
+                _skipSelectedItemTextUpdate = false;
+            }
+            else
+            {
+                OnSelectedItemChanged(e.NewValue);
+            }
+
+            // Fire the SelectionChanged event
+            List<object> removed = new List<object>();
+            if (e.OldValue != null)
+            {
+                removed.Add(e.OldValue);
+            }
+
+            List<object> added = new List<object>();
+            if (e.NewValue != null)
+            {
+                added.Add(e.NewValue);
+            }
+
+            OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
+        }
+
+        /// <summary>
+        /// TextProperty property changed handler.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            TextUpdated((string)e.NewValue, false);
+        }
+
+        private void OnSearchTextPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_ignorePropertyChange)
+            {
+                _ignorePropertyChange = false;
+                return;
+            }
+
+            // Ensure the property is only written when expected
+            if (!_allowWrite)
+            {
+                // Reset the old value before it was incorrectly written
+                _ignorePropertyChange = true;
+                SetValue(e.Property, e.OldValue);
+
+                throw new InvalidOperationException("Cannot set read-only property SearchText.");
+            }
+        }
+
+        /// <summary>
+        /// FilterModeProperty property changed handler.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnFilterModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue;
+
+            // Sets the filter predicate for the new value
+            TextFilter = AutoCompleteSearch.GetFilter(mode);
+        }
+
+        /// <summary>
+        /// ItemFilterProperty property changed handler.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnItemFilterPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            AutoCompleteFilterPredicate<object> value = e.NewValue as AutoCompleteFilterPredicate<object>;
+
+            // If null, revert to the "None" predicate
+            if (value == null)
+            {
+                FilterMode = AutoCompleteFilterMode.None;
+            }
+            else
+            {
+                FilterMode = AutoCompleteFilterMode.Custom;
+                TextFilter = null;
+            }
+        }
+
+        /// <summary>
+        /// ItemsSourceProperty property changed handler.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            OnItemsChanged((IEnumerable)e.NewValue);
+        }
+
+        private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (!_settingItemTemplateFromValueMemeberBinding)
+                _itemTemplateIsFromValueMemeberBinding = false;
+        }
+        private void OnValueMemberBindingChanged(IBinding value)
+        {
+            if(_itemTemplateIsFromValueMemeberBinding)
+            {
+                var template =
+                    new FuncDataTemplate(
+                        typeof(object),
+                        o =>
+                        {
+                            var control = new ContentControl();
+                            control.Bind(ContentControl.ContentProperty, value);
+                            return control;
+                        });
+
+                _settingItemTemplateFromValueMemeberBinding = true;
+                ItemTemplate = template;
+                _settingItemTemplateFromValueMemeberBinding = false;
+            }
+        }
+
+        static AutoCompleteBox()
+        {
+            FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
+
+            MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged);
+            IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged);
+            SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged);
+            TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged);
+            SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged);
+            FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged);
+            ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged);
+            ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged);
+            IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> class.
+        /// </summary>
+        public AutoCompleteBox()
+        {
+            ClearView();
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum number of characters required to be entered
+        /// in the text box before the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> displays
+        /// possible matches.
+        /// matches.
+        /// </summary>
+        /// <value>
+        /// The minimum number of characters to be entered in the text box
+        /// before the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
+        /// displays possible matches. The default is 1.
+        /// </value>
+        /// <remarks>
+        /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
+        /// not provide possible matches. There is no maximum value, but
+        /// setting MinimumPrefixLength to value that is too large will
+        /// prevent the AutoCompleteBox from providing possible matches as well.
+        /// </remarks>
+        public int MinimumPrefixLength
+        {
+            get { return GetValue(MinimumPrefixLengthProperty); }
+            set { SetValue(MinimumPrefixLengthProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the first possible match
+        /// found during the filtering process will be displayed automatically
+        /// in the text box.
+        /// </summary>
+        /// <value>
+        /// True if the first possible match found will be displayed
+        /// automatically in the text box; otherwise, false. The default is
+        /// false.
+        /// </value>
+        public bool IsTextCompletionEnabled
+        {
+            get { return GetValue(IsTextCompletionEnabledProperty); }
+            set { SetValue(IsTextCompletionEnabledProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="T:Avalonia.DataTemplate" /> used
+        /// to display each item in the drop-down portion of the control.
+        /// </summary>
+        /// <value>The <see cref="T:Avalonia.DataTemplate" /> used to
+        /// display each item in the drop-down. The default is null.</value>
+        /// <remarks>
+        /// You use the ItemTemplate property to specify the visualization
+        /// of the data objects in the drop-down portion of the AutoCompleteBox
+        /// control. If your AutoCompleteBox is bound to a collection and you
+        /// do not provide specific display instructions by using a
+        /// DataTemplate, the resulting UI of each item is a string
+        /// representation of each object in the underlying collection.
+        /// </remarks>
+        public IDataTemplate ItemTemplate
+        {
+            get { return GetValue(ItemTemplateProperty); }
+            set { SetValue(ItemTemplateProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum delay, after text is typed
+        /// in the text box before the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
+        /// populates the list of possible matches in the drop-down.
+        /// </summary>
+        /// <value>The minimum delay, after text is typed in
+        /// the text box, but before the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> populates
+        /// the list of possible matches in the drop-down. The default is 0.</value>
+        public TimeSpan MinimumPopulateDelay
+        {
+            get { return GetValue(MinimumPopulateDelayProperty); }
+            set { SetValue(MinimumPopulateDelayProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum height of the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+        /// </summary>
+        /// <value>The maximum height of the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+        /// The default is <see cref="F:System.Double.PositiveInfinity" />.</value>
+        /// <exception cref="T:System.ArgumentException">The specified value is less than 0.</exception>
+        public double MaxDropDownHeight
+        {
+            get { return GetValue(MaxDropDownHeightProperty); }
+            set { SetValue(MaxDropDownHeightProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the drop-down portion of
+        /// the control is open.
+        /// </summary>
+        /// <value>
+        /// True if the drop-down is open; otherwise, false. The default is
+        /// false.
+        /// </value>
+        public bool IsDropDownOpen
+        {
+            get { return  _isDropDownOpen; }
+            set { SetAndRaise(IsDropDownOpenProperty, ref  _isDropDownOpen, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the  <see cref="T:Avalonia.Data.Binding" /> that
+        /// is used to get the values for display in the text portion of
+        /// the <see cref="T:Avalonia.Controls.AutoCompleteBox" />
+        /// control.
+        /// </summary>
+        /// <value>The <see cref="T:Avalonia.Data.IBinding" /> object used
+        /// when binding to a collection property.</value>
+        [AssignBinding]
+        public IBinding ValueMemberBinding
+        {
+            get { return _valueBindingEvaluator?.ValueBinding; }
+            set
+            {
+                if (ValueMemberBinding != value)
+                {
+                    _valueBindingEvaluator = new BindingEvaluator<string>(value);
+                    OnValueMemberBindingChanged(value);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the MemberSelector that is used to get values for
+        /// display in the text portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+        /// </summary>
+        /// <value>The MemberSelector that is used to get values for display in
+        /// the text portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
+        public IMemberSelector ValueMemberSelector
+        {
+            get { return _valueMemberSelector; }
+            set { SetAndRaise(ValueMemberSelectorProperty, ref _valueMemberSelector, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the selected item in the drop-down.
+        /// </summary>
+        /// <value>The selected item in the drop-down.</value>
+        /// <remarks>
+        /// If the IsTextCompletionEnabled property is true and text typed by
+        /// the user matches an item in the ItemsSource collection, which is
+        /// then displayed in the text box, the SelectedItem property will be
+        /// a null reference.
+        /// </remarks>
+        public object SelectedItem
+        {
+            get { return _selectedItem; }
+            set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the text in the text box portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+        /// </summary>
+        /// <value>The text in the text box portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
+        public string Text
+        {
+            get { return _text; }
+            set { SetAndRaise(TextProperty, ref _text, value); }
+        }
+
+        /// <summary>
+        /// Gets the text that is used to filter items in the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// item collection.
+        /// </summary>
+        /// <value>The text that is used to filter items in the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// item collection.</value>
+        /// <remarks>
+        /// The SearchText value is typically the same as the
+        /// Text property, but is set after the TextChanged event occurs
+        /// and before the Populating event.
+        /// </remarks>
+        public string SearchText
+        {
+            get { return _searchText; }
+            private set
+            {
+                try
+                {
+                    _allowWrite = true;
+                    SetAndRaise(SearchTextProperty, ref _searchText, value);
+                }
+                finally
+                {
+                    _allowWrite = false;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets how the text in the text box is used to filter items
+        /// specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// property for display in the drop-down.
+        /// </summary>
+        /// <value>One of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />
+        /// values The default is
+        /// <see cref="F:Avalonia.Controls.AutoCompleteFilterMode.StartsWith" />.</value>
+        /// <exception cref="T:System.ArgumentException">The specified value is
+        /// not a valid
+        /// <see cref="T:Avalonia.Controls.AutoCompleteFilterMode" />.</exception>
+        /// <remarks>
+        /// Use the FilterMode property to specify how possible matches are
+        /// filtered. For example, possible matches can be filtered in a
+        /// predefined or custom way. The search mode is automatically set to
+        /// Custom if you set the ItemFilter property.
+        /// </remarks>
+        public AutoCompleteFilterMode FilterMode
+        {
+            get { return GetValue(FilterModeProperty); }
+            set { SetValue(FilterModeProperty, value); }
+        }
+
+        public string Watermark
+        {
+            get { return GetValue(WatermarkProperty); }
+            set { SetValue(WatermarkProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the custom method that uses user-entered text to filter
+        /// the items specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// property for display in the drop-down.
+        /// </summary>
+        /// <value>The custom method that uses the user-entered text to filter
+        /// the items specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// property. The default is null.</value>
+        /// <remarks>
+        /// The filter mode is automatically set to Custom if you set the
+        /// ItemFilter property.
+        /// </remarks>
+        public AutoCompleteFilterPredicate<object> ItemFilter
+        {
+            get { return _itemFilter; }
+            set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the custom method that uses the user-entered text to
+        /// filter items specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// property in a text-based way for display in the drop-down.
+        /// </summary>
+        /// <value>The custom method that uses the user-entered text to filter
+        /// items specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ItemsSource" />
+        /// property in a text-based way for display in the drop-down.</value>
+        /// <remarks>
+        /// The search mode is automatically set to Custom if you set the
+        /// TextFilter property.
+        /// </remarks>
+        public AutoCompleteFilterPredicate<string> TextFilter
+        {
+            get { return _textFilter; }
+            set { SetAndRaise(TextFilterProperty, ref _textFilter, value); }
+        }
+
+        public Func<string, CancellationToken, Task<IEnumerable<object>>> AsyncPopulator
+        {
+            get { return _asyncPopulator; }
+            set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a collection that is used to generate the items for the
+        /// drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+        /// </summary>
+        /// <value>The collection that is used to generate the items of the
+        /// drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.</value>
+        public IEnumerable Items
+        {
+            get { return _itemsEnumerable; }
+            set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the drop down popup control.
+        /// </summary>
+        private Popup DropDownPopup { get; set; }
+
+        /// <summary>
+        /// Gets or sets the Text template part.
+        /// </summary>
+        private TextBox TextBox
+        {
+            get { return _textBox; }
+            set
+            {
+                _textBoxSubscriptions?.Dispose();
+                _textBox = value;
+
+                // Attach handlers
+                if (_textBox != null)
+                {
+                    _textBoxSubscriptions =
+                        _textBox.GetObservable(TextBox.TextProperty)
+                                .Subscribe(_ => OnTextBoxTextChanged());
+
+                    if (Text != null)
+                    {
+                        UpdateTextValue(Text);
+                    }
+                }
+            }
+        }
+
+        private int TextBoxSelectionStart
+        {
+            get
+            {
+                if (TextBox != null)
+                {
+                    return Math.Min(TextBox.SelectionStart, TextBox.SelectionEnd);
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+        private int TextBoxSelectionLength
+        {
+            get
+            {
+                if (TextBox != null)
+                {
+                    return Math.Abs(TextBox.SelectionEnd - TextBox.SelectionStart);
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the selection adapter used to populate the drop-down
+        /// with a list of selectable items.
+        /// </summary>
+        /// <value>The selection adapter used to populate the drop-down with a
+        /// list of selectable items.</value>
+        /// <remarks>
+        /// You can use this property when you create an automation peer to
+        /// use with AutoCompleteBox or deriving from AutoCompleteBox to
+        /// create a custom control.
+        /// </remarks>
+        protected ISelectionAdapter SelectionAdapter
+        {
+            get { return _adapter; }
+            set
+            {
+                if (_adapter != null)
+                {
+                    _adapter.SelectionChanged -= OnAdapterSelectionChanged;
+                    _adapter.Commit -= OnAdapterSelectionComplete;
+                    _adapter.Cancel -= OnAdapterSelectionCanceled;
+                    _adapter.Cancel -= OnAdapterSelectionComplete;
+                    _adapter.Items = null;
+                }
+
+                _adapter = value;
+
+                if (_adapter != null)
+                {
+                    _adapter.SelectionChanged += OnAdapterSelectionChanged;
+                    _adapter.Commit += OnAdapterSelectionComplete;
+                    _adapter.Cancel += OnAdapterSelectionCanceled;
+                    _adapter.Cancel += OnAdapterSelectionComplete;
+                    _adapter.Items = _view;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns the
+        /// <see cref="T:Avalonia.Controls.ISelectionAdapter" /> part, if
+        /// possible.
+        /// </summary>
+        /// <returns>
+        /// A <see cref="T:Avalonia.Controls.ISelectionAdapter" /> object,
+        /// if possible. Otherwise, null.
+        /// </returns>
+        protected virtual ISelectionAdapter GetSelectionAdapterPart(INameScope nameScope)
+        {
+            ISelectionAdapter adapter = null;
+            SelectingItemsControl selector = nameScope.Find<SelectingItemsControl>(ElementSelector);
+            if (selector != null)
+            {
+                // Check if it is already an IItemsSelector
+                adapter = selector as ISelectionAdapter;
+                if (adapter == null)
+                {
+                    // Built in support for wrapping a Selector control
+                    adapter = new SelectingItemsControlSelectionAdapter(selector);
+                }
+            }
+            if (adapter == null)
+            {
+                adapter = nameScope.Find<ISelectionAdapter>(ElementSelectionAdapter);
+            }
+            return adapter;
+        }
+
+        /// <summary>
+        /// Builds the visual tree for the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
+        /// when a new template is applied.
+        /// </summary>
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+
+            if (DropDownPopup != null)
+            {
+                DropDownPopup.Closed -= DropDownPopup_Closed;
+                DropDownPopup = null;
+            }
+
+            // Set the template parts. Individual part setters remove and add
+            // any event handlers.
+            Popup popup = e.NameScope.Find<Popup>(ElementPopup);
+            if (popup != null)
+            {
+                DropDownPopup = popup;
+                DropDownPopup.Closed += DropDownPopup_Closed;
+            }
+
+            SelectionAdapter = GetSelectionAdapterPart(e.NameScope);
+            TextBox = e.NameScope.Find<TextBox>(ElementTextBox);
+
+            // If the drop down property indicates that the popup is open,
+            // flip its value to invoke the changed handler.
+            if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen)
+            {
+                OpeningDropDown(false);
+            }
+
+            base.OnTemplateApplied(e);
+        }
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.InputElement.KeyDown" /> event.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
+        /// that contains the event data.</param>
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            Contract.Requires<ArgumentNullException>(e != null);
+
+            base.OnKeyDown(e);
+
+            if (e.Handled || !IsEnabled)
+            {
+                return;
+            }
+
+            // The drop down is open, pass along the key event arguments to the
+            // selection adapter. If it isn't handled by the adapter's logic,
+            // then we handle some simple navigation scenarios for controlling
+            // the drop down.
+            if (IsDropDownOpen)
+            {
+                if (SelectionAdapter != null)
+                {
+                    SelectionAdapter.HandleKeyDown(e);
+                    if (e.Handled)
+                    {
+                        return;
+                    }
+                }
+
+                if (e.Key == Key.Escape)
+                {
+                    OnAdapterSelectionCanceled(this, new RoutedEventArgs());
+                    e.Handled = true;
+                }
+            }
+            else
+            {
+                // The drop down is not open, the Down key will toggle it open.
+                if (e.Key == Key.Down)
+                {
+                    IsDropDownOpen = true;
+                    e.Handled = true;
+                }
+            }
+
+            // Standard drop down navigation
+            switch (e.Key)
+            {
+                case Key.F4:
+                    IsDropDownOpen = !IsDropDownOpen;
+                    e.Handled = true;
+                    break;
+
+                case Key.Enter:
+                    OnAdapterSelectionComplete(this, new RoutedEventArgs());
+                    e.Handled = true;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.UIElement.GotFocus" /> event.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
+        /// that contains the event data.</param>
+        protected override void OnGotFocus(GotFocusEventArgs e)
+        {
+            base.OnGotFocus(e);
+            FocusChanged(HasFocus());
+        }
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.UIElement.LostFocus" /> event.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
+        /// that contains the event data.</param>
+        protected override void OnLostFocus(RoutedEventArgs e)
+        {
+            base.OnLostFocus(e);
+            FocusChanged(HasFocus());
+        }
+
+        /// <summary>
+        /// Determines whether the text box or drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control has
+        /// focus.
+        /// </summary>
+        /// <returns>true to indicate the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus;
+        /// otherwise, false.</returns>
+        protected bool HasFocus()
+        {
+            IVisual focused = FocusManager.Instance.Current;
+
+            while (focused != null)
+            {
+                if (object.ReferenceEquals(focused, this))
+                {
+                    return true;
+                }
+
+                // This helps deal with popups that may not be in the same
+                // visual tree
+                IVisual parent = focused.GetVisualParent();
+                if (parent == null)
+                {
+                    // Try the logical parent.
+                    IControl element = focused as IControl;
+                    if (element != null)
+                    {
+                        parent = element.Parent;
+                    }
+                }
+                focused = parent;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Handles the FocusChanged event.
+        /// </summary>
+        /// <param name="hasFocus">A value indicating whether the control
+        /// currently has the focus.</param>
+        private void FocusChanged(bool hasFocus)
+        {
+            // The OnGotFocus & OnLostFocus are asynchronously and cannot
+            // reliably tell you that have the focus.  All they do is let you
+            // know that the focus changed sometime in the past.  To determine
+            // if you currently have the focus you need to do consult the
+            // FocusManager (see HasFocus()).
+
+            bool wasFocused = _isFocused;
+            _isFocused = hasFocus;
+
+            if (hasFocus)
+            {
+
+                if (!wasFocused && TextBox != null && TextBoxSelectionLength <= 0)
+                {
+                    TextBox.Focus();
+                    TextBox.SelectionStart = 0;
+                    TextBox.SelectionEnd = TextBox.Text?.Length ?? 0;
+                }
+            }
+            else
+            {
+                IsDropDownOpen = false;
+                _userCalledPopulate = false;
+                ClearTextBoxSelection();
+            }
+
+            _isFocused = hasFocus;
+        }
+
+        /// <summary>
+        /// Occurs when the text in the text box portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> changes.
+        /// </summary>
+        public event EventHandler TextChanged;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> is
+        /// populating the drop-down with possible matches based on the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
+        /// property.
+        /// </summary>
+        /// <remarks>
+        /// If the event is canceled, by setting the PopulatingEventArgs.Cancel
+        /// property to true, the AutoCompleteBox will not automatically
+        /// populate the selection adapter contained in the drop-down.
+        /// In this case, if you want possible matches to appear, you must
+        /// provide the logic for populating the selection adapter.
+        /// </remarks>
+        public event EventHandler<PopulatingEventArgs> Populating;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
+        /// populated the drop-down with possible matches based on the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Text" />
+        /// property.
+        /// </summary>
+        public event EventHandler<PopulatedEventArgs> Populated;
+
+        /// <summary>
+        /// Occurs when the value of the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// property is changing from false to true.
+        /// </summary>
+        public event EventHandler<CancelEventArgs> DropDownOpening;
+
+        /// <summary>
+        /// Occurs when the value of the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// property has changed from false to true and the drop-down is open.
+        /// </summary>
+        public event EventHandler DropDownOpened;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// property is changing from true to false.
+        /// </summary>
+        public event EventHandler<CancelEventArgs> DropDownClosing;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.IsDropDownOpen" />
+        /// property was changed from true to false and the drop-down is open.
+        /// </summary>
+        public event EventHandler DropDownClosed;
+
+        /// <summary>
+        /// Occurs when the selected item in the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has
+        /// changed.
+        /// </summary>
+        public event EventHandler<SelectionChangedEventArgs> SelectionChanged
+        {
+            add { AddHandler(SelectionChangedEvent, value); }
+            remove { RemoveHandler(SelectionChangedEvent, value); }
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populating" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:Avalonia.Controls.PopulatingEventArgs" /> that
+        /// contains the event data.</param>
+        protected virtual void OnPopulating(PopulatingEventArgs e)
+        {
+            Populating?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.Populated" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:Avalonia.Controls.PopulatedEventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnPopulated(PopulatedEventArgs e)
+        {
+            Populated?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.SelectionChanged" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:Avalonia.Controls.SelectionChangedEventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
+        {
+            RaiseEvent(e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpening" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnDropDownOpening(CancelEventArgs e)
+        {
+            DropDownOpening?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownOpened" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:System.EventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnDropDownOpened(EventArgs e)
+        {
+            DropDownOpened?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosing" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:Avalonia.Controls.CancelEventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnDropDownClosing(CancelEventArgs e)
+        {
+            DropDownClosing?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.DropDownClosed" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A
+        /// <see cref="T:System.EventArgs" />
+        /// which contains the event data.</param>
+        protected virtual void OnDropDownClosed(EventArgs e)
+        {
+            DropDownClosed?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.AutoCompleteBox.TextChanged" />
+        /// event.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.RoutedEventArgs" />
+        /// that contains the event data.</param>
+        protected virtual void OnTextChanged(RoutedEventArgs e)
+        {
+            TextChanged?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Begin closing the drop-down.
+        /// </summary>
+        /// <param name="oldValue">The original value.</param>
+        private void ClosingDropDown(bool oldValue)
+        {
+            var args = new CancelEventArgs();
+            OnDropDownClosing(args);
+
+            if (args.Cancel)
+            {
+                _ignorePropertyChange = true;
+                SetValue(IsDropDownOpenProperty, oldValue);
+            }
+            else
+            {
+                CloseDropDown();
+            }
+
+            UpdatePseudoClasses();
+        }
+
+        /// <summary>
+        /// Begin opening the drop down by firing cancelable events, opening the
+        /// drop-down or reverting, depending on the event argument values.
+        /// </summary>
+        /// <param name="oldValue">The original value, if needed for a revert.</param>
+        private void OpeningDropDown(bool oldValue)
+        {
+            var args = new CancelEventArgs();
+
+            // Opening
+            OnDropDownOpening(args);
+
+            if (args.Cancel)
+            {
+                _ignorePropertyChange = true;
+                SetValue(IsDropDownOpenProperty, oldValue);
+            }
+            else
+            {
+                OpenDropDown();
+            }
+
+            UpdatePseudoClasses();
+        }
+
+        /// <summary>
+        /// Connects to the DropDownPopup Closed event.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void DropDownPopup_Closed(object sender, EventArgs e)
+        {
+            // Force the drop down dependency property to be false.
+            if (IsDropDownOpen)
+            {
+                IsDropDownOpen = false;
+            }
+
+            // Fire the DropDownClosed event
+            if (_popupHasOpened)
+            {
+                OnDropDownClosed(EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Handles the timer tick when using a populate delay.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event arguments.</param>
+        private void PopulateDropDown(object sender, EventArgs e)
+        {
+            if (_delayTimer != null)
+            {
+                _delayTimer.Stop();
+            }
+
+            // Update the prefix/search text.
+            SearchText = Text;
+
+            if(TryPopulateAsync(SearchText))
+            {
+                return;
+            }
+
+            // The Populated event enables advanced, custom filtering. The
+            // client needs to directly update the ItemsSource collection or
+            // call the Populate method on the control to continue the
+            // display process if Cancel is set to true.
+            PopulatingEventArgs populating = new PopulatingEventArgs(SearchText);
+            OnPopulating(populating);
+            if (!populating.Cancel)
+            {
+                PopulateComplete();
+            }
+        }
+        private bool TryPopulateAsync(string searchText)
+        {
+            _populationCancellationTokenSource?.Cancel(false);
+            _populationCancellationTokenSource?.Dispose();
+            _populationCancellationTokenSource = null;
+
+            if(_asyncPopulator == null)
+            {
+                return false;
+            }
+
+            _populationCancellationTokenSource = new CancellationTokenSource();
+            var task = PopulateAsync(searchText, _populationCancellationTokenSource.Token);
+            if (task.Status == TaskStatus.Created)
+                task.Start();
+
+            return true;
+        }
+        private async Task PopulateAsync(string searchText, CancellationToken cancellationToken)
+        {
+
+            try
+            {
+                IEnumerable<object> result = await _asyncPopulator.Invoke(searchText, cancellationToken);
+                var resultList = result.ToList();
+
+                if (cancellationToken.IsCancellationRequested)
+                {
+                    return;
+                }
+
+                await Dispatcher.UIThread.InvokeAsync(() =>
+                {
+                    if (!cancellationToken.IsCancellationRequested)
+                    {
+                        Items = resultList;
+                        PopulateComplete();
+                    }
+                });
+            }
+            catch (TaskCanceledException)
+            { }
+            finally
+            {
+                _populationCancellationTokenSource?.Dispose();
+                _populationCancellationTokenSource = null;
+            }
+
+        }
+
+        /// <summary>
+        /// Private method that directly opens the popup, checks the expander
+        /// button, and then fires the Opened event.
+        /// </summary>
+        private void OpenDropDown()
+        {
+            if (DropDownPopup != null)
+            {
+                DropDownPopup.IsOpen = true;
+            }
+            _popupHasOpened = true;
+            OnDropDownOpened(EventArgs.Empty);
+        }
+
+        /// <summary>
+        /// Private method that directly closes the popup, flips the Checked
+        /// value, and then fires the Closed event.
+        /// </summary>
+        private void CloseDropDown()
+        {
+            if (_popupHasOpened)
+            {
+                if (SelectionAdapter != null)
+                {
+                    SelectionAdapter.SelectedItem = null;
+                }
+                if (DropDownPopup != null)
+                {
+                    DropDownPopup.IsOpen = false;
+                }
+                OnDropDownClosed(EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Formats an Item for text comparisons based on Converter
+        /// and ConverterCulture properties.
+        /// </summary>
+        /// <param name="value">The object to format.</param>
+        /// <param name="clearDataContext">A value indicating whether to clear
+        /// the data context after the lookup is performed.</param>
+        /// <returns>Formatted Value.</returns>
+        private string FormatValue(object value, bool clearDataContext)
+        {
+            string result = FormatValue(value);
+            if(clearDataContext && _valueBindingEvaluator != null)
+            {
+                _valueBindingEvaluator.ClearDataContext();
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Converts the specified object to a string by using the
+        /// <see cref="P:Avalonia.Data.Binding.Converter" /> and
+        /// <see cref="P:Avalonia.Data.Binding.ConverterCulture" /> values
+        /// of the binding object specified by the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.ValueMemberBinding" />
+        /// property.
+        /// </summary>
+        /// <param name="value">The object to format as a string.</param>
+        /// <returns>The string representation of the specified object.</returns>
+        /// <remarks>
+        /// Override this method to provide a custom string conversion.
+        /// </remarks>
+        protected virtual string FormatValue(object value)
+        {
+            if (_valueBindingEvaluator != null)
+            {
+                return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty;
+            }
+
+            if (_valueMemberSelector != null)
+            {
+                value = _valueMemberSelector.Select(value);
+            }
+
+            return value == null ? String.Empty : value.ToString();
+        }
+
+        /// <summary>
+        /// Handle the TextChanged event that is directly attached to the
+        /// TextBox part. This ensures that only user initiated actions will
+        /// result in an AutoCompleteBox suggestion and operation.
+        /// </summary>
+        private void OnTextBoxTextChanged()
+        {
+            //Uses Dispatcher.Post to allow the TextBox selection to update before processing
+            Dispatcher.UIThread.Post(() =>
+            {
+                // Call the central updated text method as a user-initiated action
+                TextUpdated(_textBox.Text, true);
+            });
+        }
+
+        /// <summary>
+        /// Updates both the text box value and underlying text dependency
+        /// property value if and when they change. Automatically fires the
+        /// text changed events when there is a change.
+        /// </summary>
+        /// <param name="value">The new string value.</param>
+        private void UpdateTextValue(string value)
+        {
+            UpdateTextValue(value, null);
+        }
+
+        /// <summary>
+        /// Updates both the text box value and underlying text dependency
+        /// property value if and when they change. Automatically fires the
+        /// text changed events when there is a change.
+        /// </summary>
+        /// <param name="value">The new string value.</param>
+        /// <param name="userInitiated">A nullable bool value indicating whether
+        /// the action was user initiated. In a user initiated mode, the
+        /// underlying text dependency property is updated. In a non-user
+        /// interaction, the text box value is updated. When user initiated is
+        /// null, all values are updated.</param>
+        private void UpdateTextValue(string value, bool? userInitiated)
+        {
+            bool callTextChanged = false;
+            // Update the Text dependency property
+            if ((userInitiated == null || userInitiated == true) && Text != value)
+            {
+                _ignoreTextPropertyChange++;
+                Text = value;
+                callTextChanged = true;
+            }
+
+            // Update the TextBox's Text dependency property
+            if ((userInitiated == null || userInitiated == false) && TextBox != null && TextBox.Text != value)
+            {
+                _ignoreTextPropertyChange++;
+                TextBox.Text = value ?? string.Empty;
+
+                // Text dependency property value was set, fire event
+                if (!callTextChanged && (Text == value || Text == null))
+                {
+                    callTextChanged = true;
+                }
+            }
+
+            if (callTextChanged)
+            {
+                OnTextChanged(new RoutedEventArgs());
+            }
+        }
+
+        /// <summary>
+        /// Handle the update of the text for the control from any source,
+        /// including the TextBox part and the Text dependency property.
+        /// </summary>
+        /// <param name="newText">The new text.</param>
+        /// <param name="userInitiated">A value indicating whether the update
+        /// is a user-initiated action. This should be a True value when the
+        /// TextUpdated method is called from a TextBox event handler.</param>
+        private void TextUpdated(string newText, bool userInitiated)
+        {
+            // Only process this event if it is coming from someone outside
+            // setting the Text dependency property directly.
+            if (_ignoreTextPropertyChange > 0)
+            {
+                _ignoreTextPropertyChange--;
+                return;
+            }
+
+            if (newText == null)
+            {
+                newText = string.Empty;
+            }
+
+            // The TextBox.TextChanged event was not firing immediately and
+            // was causing an immediate update, even with wrapping. If there is
+            // a selection currently, no update should happen.
+            if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
+            {
+                return;
+            }
+
+            // Evaluate the conditions needed for completion.
+            // 1. Minimum prefix length
+            // 2. If a delay timer is in use, use it
+            bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
+            if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
+            {
+                populateReady = false;
+            }
+            _userCalledPopulate = populateReady ? userInitiated : false;
+
+            // Update the interface and values only as necessary
+            UpdateTextValue(newText, userInitiated);
+
+            if (populateReady)
+            {
+                _ignoreTextSelectionChange = true;
+
+                if (_delayTimer != null)
+                {
+                    _delayTimer.Start();
+                }
+                else
+                {
+                    PopulateDropDown(this, EventArgs.Empty);
+                }
+            }
+            else
+            {
+                SearchText = string.Empty;
+                if (SelectedItem != null)
+                {
+                    _skipSelectedItemTextUpdate = true;
+                }
+                SelectedItem = null;
+                if (IsDropDownOpen)
+                {
+                    IsDropDownOpen = false;
+                }
+            }
+        }
+
+        /// <summary>
+        /// A simple helper method to clear the view and ensure that a view
+        /// object is always present and not null.
+        /// </summary>
+        private void ClearView()
+        {
+            if (_view == null)
+            {
+                _view = new AvaloniaList<object>();
+            }
+            else
+            {
+                _view.Clear();
+            }
+        }
+
+        /// <summary>
+        /// Walks through the items enumeration. Performance is not going to be
+        /// perfect with the current implementation.
+        /// </summary>
+        private void RefreshView()
+        {
+            if (_items == null)
+            {
+                ClearView();
+                return;
+            }
+
+            // Cache the current text value
+            string text = Text ?? string.Empty;
+
+            // Determine if any filtering mode is on
+            bool stringFiltering = TextFilter != null;
+            bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null;
+
+            int view_index = 0;
+            int view_count = _view.Count;
+            List<object> items = _items;
+            foreach (object item in items)
+            {
+                bool inResults = !(stringFiltering || objectFiltering);
+                if (!inResults)
+                {
+                    inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item);
+                }
+
+                if (view_count > view_index && inResults && _view[view_index] == item)
+                {
+                    // Item is still in the view
+                    view_index++;
+                }
+                else if (inResults)
+                {
+                    // Insert the item
+                    if (view_count > view_index && _view[view_index] != item)
+                    {
+                        // Replace item
+                        // Unfortunately replacing via index throws a fatal
+                        // exception: View[view_index] = item;
+                        // Cost: O(n) vs O(1)
+                        _view.RemoveAt(view_index);
+                        _view.Insert(view_index, item);
+                        view_index++;
+                    }
+                    else
+                    {
+                        // Add the item
+                        if (view_index == view_count)
+                        {
+                            // Constant time is preferred (Add).
+                            _view.Add(item);
+                        }
+                        else
+                        {
+                            _view.Insert(view_index, item);
+                        }
+                        view_index++;
+                        view_count++;
+                    }
+                }
+                else if (view_count > view_index && _view[view_index] == item)
+                {
+                    // Remove the item
+                    _view.RemoveAt(view_index);
+                    view_count--;
+                }
+            }
+
+            // Clear the evaluator to discard a reference to the last item
+            if (_valueBindingEvaluator != null)
+            {
+                _valueBindingEvaluator.ClearDataContext();
+            }
+        }
+
+        /// <summary>
+        /// Handle any change to the ItemsSource dependency property, update
+        /// the underlying ObservableCollection view, and set the selection
+        /// adapter's ItemsSource to the view if appropriate.
+        /// </summary>
+        /// <param name="newValue">The new enumerable reference.</param>
+        private void OnItemsChanged(IEnumerable newValue)
+        {
+            // Remove handler for oldValue.CollectionChanged (if present)
+            _collectionChangeSubscription?.Dispose();
+            _collectionChangeSubscription = null;
+
+            // Add handler for newValue.CollectionChanged (if possible)
+            if (newValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
+            {
+                _collectionChangeSubscription = newValueINotifyCollectionChanged.WeakSubscribe(ItemsCollectionChanged);
+            }
+
+            // Store a local cached copy of the data
+            _items = newValue == null ? null : new List<object>(newValue.Cast<object>().ToList());
+
+            // Clear and set the view on the selection adapter
+            ClearView();
+            if (SelectionAdapter != null && SelectionAdapter.Items != _view)
+            {
+                SelectionAdapter.Items = _view;
+            }
+            if (IsDropDownOpen)
+            {
+                RefreshView();
+            }
+        }
+
+        /// <summary>
+        /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property.
+        /// </summary>
+        /// <param name="sender">The object that raised the event.</param>
+        /// <param name="e">The event data.</param>
+        private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            // Update the cache
+            if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
+            {
+                for (int index = 0; index < e.OldItems.Count; index++)
+                {
+                    _items.RemoveAt(e.OldStartingIndex);
+                }
+            }
+            if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex)
+            {
+                for (int index = 0; index < e.NewItems.Count; index++)
+                {
+                    _items.Insert(e.NewStartingIndex + index, e.NewItems[index]);
+                }
+            }
+            if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null)
+            {
+                for (int index = 0; index < e.NewItems.Count; index++)
+                {
+                    _items[e.NewStartingIndex] = e.NewItems[index];
+                }
+            }
+
+            // Update the view
+            if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
+            {
+                for (int index = 0; index < e.OldItems.Count; index++)
+                {
+                    _view.Remove(e.OldItems[index]);
+                }
+            }
+
+            if (e.Action == NotifyCollectionChangedAction.Reset)
+            {
+                // Significant changes to the underlying data.
+                ClearView();
+                if (Items != null)
+                {
+                    _items = new List<object>(Items.Cast<object>().ToList());
+                }
+            }
+
+            // Refresh the observable collection used in the selection adapter.
+            RefreshView();
+        }
+
+        /// <summary>
+        /// Notifies the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> that the
+        /// <see cref="P:Avalonia.Controls.AutoCompleteBox.Items" />
+        /// property has been set and the data can be filtered to provide
+        /// possible matches in the drop-down.
+        /// </summary>
+        /// <remarks>
+        /// Call this method when you are providing custom population of
+        /// the drop-down portion of the AutoCompleteBox, to signal the control
+        /// that you are done with the population process.
+        /// Typically, you use PopulateComplete when the population process
+        /// is a long-running process and you want to cancel built-in filtering
+        ///  of the ItemsSource items. In this case, you can handle the
+        /// Populated event and set PopulatingEventArgs.Cancel to true.
+        /// When the long-running process has completed you call
+        /// PopulateComplete to indicate the drop-down is populated.
+        /// </remarks>
+        public void PopulateComplete()
+        {
+            // Apply the search filter
+            RefreshView();
+
+            // Fire the Populated event containing the read-only view data.
+            PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection<object>(_view));
+            OnPopulated(populated);
+
+            if (SelectionAdapter != null && SelectionAdapter.Items != _view)
+            {
+                SelectionAdapter.Items = _view;
+            }
+
+            bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0);
+            if (isDropDownOpen != IsDropDownOpen)
+            {
+                _ignorePropertyChange = true;
+                IsDropDownOpen = isDropDownOpen;
+            }
+            if (IsDropDownOpen)
+            {
+                OpeningDropDown(false);
+            }
+            else
+            {
+                ClosingDropDown(true);
+            }
+
+            UpdateTextCompletion(_userCalledPopulate);
+        }
+
+        /// <summary>
+        /// Performs text completion, if enabled, and a lookup on the underlying
+        /// item values for an exact match. Will update the SelectedItem value.
+        /// </summary>
+        /// <param name="userInitiated">A value indicating whether the operation
+        /// was user initiated. Text completion will not be performed when not
+        /// directly initiated by the user.</param>
+        private void UpdateTextCompletion(bool userInitiated)
+        {
+            // By default this method will clear the selected value
+            object newSelectedItem = null;
+            string text = Text;
+
+            // Text search is StartsWith explicit and only when enabled, in
+            // line with WPF's ComboBox lookup. When in use it will associate
+            // a Value with the Text if it is found in ItemsSource. This is
+            // only valid when there is data and the user initiated the action.
+            if (_view.Count > 0)
+            {
+                if (IsTextCompletionEnabled && TextBox != null && userInitiated)
+                {
+                    int currentLength = TextBox.Text.Length;
+                    int selectionStart = TextBoxSelectionStart;
+                    if (selectionStart == text.Length && selectionStart > _textSelectionStart)
+                    {
+                        // When the FilterMode dependency property is set to
+                        // either StartsWith or StartsWithCaseSensitive, the
+                        // first item in the view is used. This will improve
+                        // performance on the lookup. It assumes that the
+                        // FilterMode the user has selected is an acceptable
+                        // case sensitive matching function for their scenario.
+                        object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive
+                            ? _view[0]
+                            : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
+
+                        // If the search was successful, update SelectedItem
+                        if (top != null)
+                        {
+                            newSelectedItem = top;
+                            string topString = FormatValue(top, true);
+
+                            // Only replace partially when the two words being the same
+                            int minLength = Math.Min(topString.Length, Text.Length);
+                            if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength)))
+                            {
+                                // Update the text
+                                UpdateTextValue(topString);
+
+                                // Select the text past the user's caret
+                                TextBox.SelectionStart = currentLength;
+                                TextBox.SelectionEnd = topString.Length;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    // Perform an exact string lookup for the text. This is a
+                    // design change from the original Toolkit release when the
+                    // IsTextCompletionEnabled property behaved just like the
+                    // WPF ComboBox's IsTextSearchEnabled property.
+                    //
+                    // This change provides the behavior that most people expect
+                    // to find: a lookup for the value is always performed.
+                    newSelectedItem = TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive));
+                }
+            }
+
+            // Update the selected item property
+
+            if (SelectedItem != newSelectedItem)
+            {
+                _skipSelectedItemTextUpdate = true;
+            }
+            SelectedItem = newSelectedItem;
+
+            // Restore updates for TextSelection
+            if (_ignoreTextSelectionChange)
+            {
+                _ignoreTextSelectionChange = false;
+                if (TextBox != null)
+                {
+                    _textSelectionStart = TextBoxSelectionStart;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Attempts to look through the view and locate the specific exact
+        /// text match.
+        /// </summary>
+        /// <param name="searchText">The search text.</param>
+        /// <param name="view">The view reference.</param>
+        /// <param name="predicate">The predicate to use for the partial or
+        /// exact match.</param>
+        /// <returns>Returns the object or null.</returns>
+        private object TryGetMatch(string searchText, AvaloniaList<object> view, AutoCompleteFilterPredicate<string> predicate)
+        {
+            if (view != null && view.Count > 0)
+            {
+                foreach (object o in view)
+                {
+                    if (predicate(searchText, FormatValue(o)))
+                    {
+                        return o;
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        private void UpdatePseudoClasses()
+        {
+            PseudoClasses.Set(":dropdownopen", IsDropDownOpen);
+        }
+
+        private void ClearTextBoxSelection()
+        {
+            if (TextBox != null)
+            {
+                int length = TextBox.Text?.Length ?? 0;
+                TextBox.SelectionStart = length;
+                TextBox.SelectionEnd = length;
+            }
+        }
+
+        /// <summary>
+        /// Called when the selected item is changed, updates the text value
+        /// that is displayed in the text box part.
+        /// </summary>
+        /// <param name="newItem">The new item.</param>
+        private void OnSelectedItemChanged(object newItem)
+        {
+            string text;
+
+            if (newItem == null)
+            {
+                text = SearchText;
+            }
+            else
+            {
+                text = FormatValue(newItem, true);
+            }
+
+            // Update the Text property and the TextBox values
+            UpdateTextValue(text);
+
+            // Move the caret to the end of the text box
+            ClearTextBoxSelection();
+        }
+
+        /// <summary>
+        /// Handles the SelectionChanged event of the selection adapter.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The selection changed event data.</param>
+        private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            SelectedItem = _adapter.SelectedItem;
+        }
+
+        //TODO Check UpdateTextCompletion
+        /// <summary>
+        /// Handles the Commit event on the selection adapter.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e)
+        {
+            IsDropDownOpen = false;
+
+            // Completion will update the selected value
+            //UpdateTextCompletion(false);
+
+            // Text should not be selected
+            ClearTextBoxSelection();
+
+            TextBox.Focus();
+        }
+
+        /// <summary>
+        /// Handles the Cancel event on the selection adapter.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e)
+        {
+            UpdateTextValue(SearchText);
+
+            // Completion will update the selected value
+            UpdateTextCompletion(false);
+        }
+
+        /// <summary>
+        /// A predefined set of filter functions for the known, built-in
+        /// AutoCompleteFilterMode enumeration values.
+        /// </summary>
+        private static class AutoCompleteSearch
+        {
+            /// <summary>
+            /// Index function that retrieves the filter for the provided
+            /// AutoCompleteFilterMode.
+            /// </summary>
+            /// <param name="FilterMode">The built-in search mode.</param>
+            /// <returns>Returns the string-based comparison function.</returns>
+            public static AutoCompleteFilterPredicate<string> GetFilter(AutoCompleteFilterMode FilterMode)
+            {
+                switch (FilterMode)
+                {
+                    case AutoCompleteFilterMode.Contains:
+                        return Contains;
+
+                    case AutoCompleteFilterMode.ContainsCaseSensitive:
+                        return ContainsCaseSensitive;
+
+                    case AutoCompleteFilterMode.ContainsOrdinal:
+                        return ContainsOrdinal;
+
+                    case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive:
+                        return ContainsOrdinalCaseSensitive;
+
+                    case AutoCompleteFilterMode.Equals:
+                        return Equals;
+
+                    case AutoCompleteFilterMode.EqualsCaseSensitive:
+                        return EqualsCaseSensitive;
+
+                    case AutoCompleteFilterMode.EqualsOrdinal:
+                        return EqualsOrdinal;
+
+                    case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive:
+                        return EqualsOrdinalCaseSensitive;
+
+                    case AutoCompleteFilterMode.StartsWith:
+                        return StartsWith;
+
+                    case AutoCompleteFilterMode.StartsWithCaseSensitive:
+                        return StartsWithCaseSensitive;
+
+                    case AutoCompleteFilterMode.StartsWithOrdinal:
+                        return StartsWithOrdinal;
+
+                    case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive:
+                        return StartsWithOrdinalCaseSensitive;
+
+                    case AutoCompleteFilterMode.None:
+                    case AutoCompleteFilterMode.Custom:
+                    default:
+                        return null;
+                }
+            }
+
+            /// <summary>
+            /// An implementation of the Contains member of string that takes in a
+            /// string comparison. The traditional .NET string Contains member uses
+            /// StringComparison.Ordinal.
+            /// </summary>
+            /// <param name="s">The string.</param>
+            /// <param name="value">The string value to search for.</param>
+            /// <param name="comparison">The string comparison type.</param>
+            /// <returns>Returns true when the substring is found.</returns>
+            private static bool Contains(string s, string value, StringComparison comparison)
+            {
+                return s.IndexOf(value, comparison) >= 0;
+            }
+
+            /// <summary>
+            /// Check if the string value begins with the text.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool StartsWith(string text, string value)
+            {
+                return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the string value begins with the text.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool StartsWithCaseSensitive(string text, string value)
+            {
+                return value.StartsWith(text, StringComparison.CurrentCulture);
+            }
+
+            /// <summary>
+            /// Check if the string value begins with the text.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool StartsWithOrdinal(string text, string value)
+            {
+                return value.StartsWith(text, StringComparison.OrdinalIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the string value begins with the text.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool StartsWithOrdinalCaseSensitive(string text, string value)
+            {
+                return value.StartsWith(text, StringComparison.Ordinal);
+            }
+
+            /// <summary>
+            /// Check if the prefix is contained in the string value. The current
+            /// culture's case insensitive string comparison operator is used.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool Contains(string text, string value)
+            {
+                return Contains(value, text, StringComparison.CurrentCultureIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the prefix is contained in the string value.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool ContainsCaseSensitive(string text, string value)
+            {
+                return Contains(value, text, StringComparison.CurrentCulture);
+            }
+
+            /// <summary>
+            /// Check if the prefix is contained in the string value.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool ContainsOrdinal(string text, string value)
+            {
+                return Contains(value, text, StringComparison.OrdinalIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the prefix is contained in the string value.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool ContainsOrdinalCaseSensitive(string text, string value)
+            {
+                return Contains(value, text, StringComparison.Ordinal);
+            }
+
+            /// <summary>
+            /// Check if the string values are equal.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool Equals(string text, string value)
+            {
+                return value.Equals(text, StringComparison.CurrentCultureIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the string values are equal.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool EqualsCaseSensitive(string text, string value)
+            {
+                return value.Equals(text, StringComparison.CurrentCulture);
+            }
+
+            /// <summary>
+            /// Check if the string values are equal.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool EqualsOrdinal(string text, string value)
+            {
+                return value.Equals(text, StringComparison.OrdinalIgnoreCase);
+            }
+
+            /// <summary>
+            /// Check if the string values are equal.
+            /// </summary>
+            /// <param name="text">The AutoCompleteBox prefix text.</param>
+            /// <param name="value">The item's string value.</param>
+            /// <returns>Returns true if the condition is met.</returns>
+            public static bool EqualsOrdinalCaseSensitive(string text, string value)
+            {
+                return value.Equals(text, StringComparison.Ordinal);
+            }
+        }
+
+        /// <summary>
+        /// A framework element that permits a binding to be evaluated in a new data
+        /// context leaf node.
+        /// </summary>
+        /// <typeparam name="T">The type of dynamic binding to return.</typeparam>
+        public class BindingEvaluator<T> : Control
+        {
+            /// <summary>
+            /// Gets or sets the string value binding used by the control.
+            /// </summary>
+            private IBinding _binding;
+
+            #region public T Value
+
+            /// <summary>
+            /// Identifies the Value dependency property.
+            /// </summary>
+            public static readonly StyledProperty<T> ValueProperty =
+                AvaloniaProperty.Register<BindingEvaluator<T>, T>(nameof(Value));
+
+            /// <summary>
+            /// Gets or sets the data item value.
+            /// </summary>
+            public T Value
+            {
+                get { return GetValue(ValueProperty); }
+                set { SetValue(ValueProperty, value); }
+            }
+
+            #endregion public string Value
+
+            /// <summary>
+            /// Gets or sets the value binding.
+            /// </summary>
+            public IBinding ValueBinding
+            {
+                get { return _binding; }
+                set
+                {
+                    _binding = value;
+                    AvaloniaObjectExtensions.Bind(this, ValueProperty, value);
+                }
+            }
+
+            /// <summary>
+            /// Initializes a new instance of the BindingEvaluator class.
+            /// </summary>
+            public BindingEvaluator()
+            { }
+
+            /// <summary>
+            /// Initializes a new instance of the BindingEvaluator class,
+            /// setting the initial binding to the provided parameter.
+            /// </summary>
+            /// <param name="binding">The initial string value binding.</param>
+            public BindingEvaluator(IBinding binding)
+                : this()
+            {
+                ValueBinding = binding;
+            }
+
+            /// <summary>
+            /// Clears the data context so that the control does not keep a
+            /// reference to the last-looked up item.
+            /// </summary>
+            public void ClearDataContext()
+            {
+                DataContext = null;
+            }
+
+            /// <summary>
+            /// Updates the data context of the framework element and returns the
+            /// updated binding value.
+            /// </summary>
+            /// <param name="o">The object to use as the data context.</param>
+            /// <param name="clearDataContext">If set to true, this parameter will
+            /// clear the data context immediately after retrieving the value.</param>
+            /// <returns>Returns the evaluated T value of the bound dependency
+            /// property.</returns>
+            public T GetDynamicValue(object o, bool clearDataContext)
+            {
+                DataContext = o;
+                T value = Value;
+                if (clearDataContext)
+                {
+                    DataContext = null;
+                }
+                return value;
+            }
+
+            /// <summary>
+            /// Updates the data context of the framework element and returns the
+            /// updated binding value.
+            /// </summary>
+            /// <param name="o">The object to use as the data context.</param>
+            /// <returns>Returns the evaluated T value of the bound dependency
+            /// property.</returns>
+            public T GetDynamicValue(object o)
+            {
+                DataContext = o;
+                return Value;
+            }
+        }
+    }
+}

+ 1 - 27
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -1,35 +1,9 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
+    <LangVersion>latest</LangVersion>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Debug\Avalonia.Controls.xml</DocumentationFile>
-    <NoWarn>CS1591;CS0067</NoWarn>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <DocumentationFile>bin\Release\Avalonia.Controls.xml</DocumentationFile>
-    <NoWarn>CS1591</NoWarn>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-  </PropertyGroup>
-  <ItemGroup>
-    <Compile Include="..\Shared\SharedAssemblyInfo.cs">
-      <Link>Properties\SharedAssemblyInfo.cs</Link>
-    </Compile>
-  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />

+ 21 - 30
src/Avalonia.Controls/Border.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 Avalonia;
+using Avalonia.Controls.Utils;
 using Avalonia.Media;
 
 namespace Avalonia.Controls
@@ -8,7 +10,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// A control which decorates a child with a border and background.
     /// </summary>
-    public class Border : Decorator
+    public partial class Border : Decorator
     {
         /// <summary>
         /// Defines the <see cref="Background"/> property.
@@ -25,21 +27,24 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
         /// </summary>
-        public static readonly StyledProperty<double> BorderThicknessProperty =
-            AvaloniaProperty.Register<Border, double>(nameof(BorderThickness));
+        public static readonly StyledProperty<Thickness> BorderThicknessProperty =
+            AvaloniaProperty.Register<Border, Thickness>(nameof(BorderThickness));
 
         /// <summary>
         /// Defines the <see cref="CornerRadius"/> property.
         /// </summary>
-        public static readonly StyledProperty<float> CornerRadiusProperty =
-            AvaloniaProperty.Register<Border, float>(nameof(CornerRadius));
+        public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
+            AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
+
+        private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
 
         /// <summary>
         /// Initializes static members of the <see cref="Border"/> class.
         /// </summary>
         static Border()
         {
-            AffectsRender(BackgroundProperty, BorderBrushProperty);
+            AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
+            AffectsMeasure(BorderThicknessProperty);
         }
 
         /// <summary>
@@ -63,7 +68,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the thickness of the border.
         /// </summary>
-        public double BorderThickness
+        public Thickness BorderThickness
         {
             get { return GetValue(BorderThicknessProperty); }
             set { SetValue(BorderThicknessProperty, value); }
@@ -72,7 +77,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the radius of the border rounded corners.
         /// </summary>
-        public float CornerRadius
+        public CornerRadius CornerRadius
         {
             get { return GetValue(CornerRadiusProperty); }
             set { SetValue(CornerRadiusProperty, value); }
@@ -84,21 +89,7 @@ namespace Avalonia.Controls
         /// <param name="context">The drawing context.</param>
         public override void Render(DrawingContext context)
         {
-            var background = Background;
-            var borderBrush = BorderBrush;
-            var borderThickness = BorderThickness;
-            var cornerRadius = CornerRadius;
-            var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
-
-            if (background != null)
-            {
-                context.FillRectangle(background, rect, cornerRadius);
-            }
-
-            if (borderBrush != null && borderThickness > 0)
-            {
-                context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
-            }
+            _borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
         }
 
         /// <summary>
@@ -120,10 +111,12 @@ namespace Avalonia.Controls
         {
             if (Child != null)
             {
-                var padding = Padding + new Thickness(BorderThickness);
+                var padding = Padding + BorderThickness;
                 Child.Arrange(new Rect(finalSize).Deflate(padding));
             }
 
+            _borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
+
             return finalSize;
         }
 
@@ -131,19 +124,17 @@ namespace Avalonia.Controls
             Size availableSize,
             IControl child,
             Thickness padding,
-            double borderThickness)
+            Thickness borderThickness)
         {
-            padding += new Thickness(borderThickness);
+            padding += borderThickness;
 
             if (child != null)
             {
                 child.Measure(availableSize.Deflate(padding));
                 return child.DesiredSize.Inflate(padding);
             }
-            else
-            {
-                return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
-            }
+
+            return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
         }
     }
 }

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

@@ -245,7 +245,7 @@ namespace Avalonia.Controls
         {
             base.OnPointerReleased(e);
 
-            if (e.MouseButton == MouseButton.Left)
+            if (IsPressed && e.MouseButton == MouseButton.Left)
             {
                 e.Device.Capture(null);
                 IsPressed = false;

+ 263 - 0
src/Avalonia.Controls/ButtonSpinner.cs

@@ -0,0 +1,263 @@
+using System;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+    public enum Location
+    {
+        Left,
+        Right
+    }
+
+    /// <summary>
+    /// Represents a spinner control that includes two Buttons.
+    /// </summary>
+    public class ButtonSpinner : Spinner
+    {
+        /// <summary>
+        /// Defines the <see cref="AllowSpin"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> AllowSpinProperty =
+            AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(AllowSpin), true);
+
+        /// <summary>
+        /// Defines the <see cref="ShowButtonSpinner"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> ShowButtonSpinnerProperty =
+            AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(ShowButtonSpinner), true);
+
+        /// <summary>
+        /// Defines the <see cref="ButtonSpinnerLocation"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
+            AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
+
+        private Button _decreaseButton;
+        /// <summary>
+        /// Gets or sets the DecreaseButton template part.
+        /// </summary>
+        private Button DecreaseButton
+        {
+            get { return _decreaseButton; }
+            set
+            {
+                if (_decreaseButton != null)
+                {
+                    _decreaseButton.Click  -= OnButtonClick;
+                }
+                _decreaseButton = value;
+                if (_decreaseButton != null)
+                {
+                    _decreaseButton.Click += OnButtonClick;
+                }
+            }
+        }
+
+        private Button _increaseButton;
+        /// <summary>
+        /// Gets or sets the IncreaseButton template part.
+        /// </summary>
+        private Button IncreaseButton
+        {
+            get
+            {
+                return _increaseButton;
+            }
+            set
+            {
+                if (_increaseButton != null)
+                {
+                    _increaseButton.Click -= OnButtonClick;
+                }
+                _increaseButton = value;
+                if (_increaseButton != null)
+                {
+                    _increaseButton.Click += OnButtonClick;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Initializes static members of the <see cref="ButtonSpinner"/> class.
+        /// </summary>
+        static ButtonSpinner()
+        {
+            AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
+            PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
+            PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the <see cref="ButtonSpinner"/> should allow to spin.
+        /// </summary>
+        public bool AllowSpin
+        {
+            get { return GetValue(AllowSpinProperty); }
+            set { SetValue(AllowSpinProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the spin buttons should be shown.
+        /// </summary>
+        public bool ShowButtonSpinner
+        {
+            get { return GetValue(ShowButtonSpinnerProperty); }
+            set { SetValue(ShowButtonSpinnerProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets current location of the <see cref="ButtonSpinner"/>.
+        /// </summary>
+        public Location ButtonSpinnerLocation
+        {
+            get { return GetValue(ButtonSpinnerLocationProperty); }
+            set { SetValue(ButtonSpinnerLocationProperty, value); }
+        }
+
+        /// <inheritdoc />
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
+            DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
+            SetButtonUsage();
+        }
+
+        /// <inheritdoc />
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            base.OnPointerReleased(e);
+            Point mousePosition;
+            if (IncreaseButton != null && IncreaseButton.IsEnabled == false)
+            {
+                mousePosition = e.GetPosition(IncreaseButton);
+                if (mousePosition.X > 0 && mousePosition.X < IncreaseButton.Width &&
+                    mousePosition.Y > 0 && mousePosition.Y < IncreaseButton.Height)
+                {
+                    e.Handled = true;
+                }
+            }
+
+            if (DecreaseButton != null && DecreaseButton.IsEnabled == false)
+            {
+                mousePosition = e.GetPosition(DecreaseButton);
+                if (mousePosition.X > 0 && mousePosition.X < DecreaseButton.Width &&
+                    mousePosition.Y > 0 && mousePosition.Y < DecreaseButton.Height)
+                {
+                    e.Handled = true;
+                }
+            }
+        }
+
+        /// <inheritdoc />
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Up:
+                {
+                    if (AllowSpin)
+                    {
+                        OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Increase));
+                        e.Handled = true;
+                    }
+                    break;
+                }
+                case Key.Down:
+                {
+                    if (AllowSpin)
+                    {
+                        OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Decrease));
+                        e.Handled = true;
+                    }
+                    break;
+                }
+                case Key.Enter:
+                {
+                    //Do not Spin on enter Key when spinners have focus
+                    if (((IncreaseButton != null) && (IncreaseButton.IsFocused))
+                        || ((DecreaseButton != null) && DecreaseButton.IsFocused))
+                    {
+                        e.Handled = true;
+                    }
+                    break;
+                }
+            }
+        }
+
+        /// <inheritdoc />
+        protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
+        {
+            base.OnPointerWheelChanged(e);
+            if (!e.Handled && AllowSpin)
+            {
+                if (e.Delta.Y != 0)
+                {
+                    var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true);
+                    OnSpin(spinnerEventArgs);
+                    e.Handled = spinnerEventArgs.Handled;
+                }
+            }
+        }
+
+        protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
+        {
+            SetButtonUsage();
+        }
+
+        /// <summary>
+        /// Called when the <see cref="AllowSpin"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnAllowSpinChanged(bool oldValue, bool newValue)
+        {
+            SetButtonUsage();
+        }
+
+        /// <summary>
+        /// Called when the <see cref="AllowSpin"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void AllowSpinChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is ButtonSpinner spinner)
+            {
+                var oldValue = (bool)e.OldValue;
+                var newValue = (bool)e.NewValue;
+                spinner.OnAllowSpinChanged(oldValue, newValue);
+            }
+        }
+        
+        /// <summary>
+        /// Disables or enables the buttons based on the valid spin direction.
+        /// </summary>
+        private void SetButtonUsage()
+        {
+            if (IncreaseButton != null)
+            {
+                IncreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase);
+            }
+
+            if (DecreaseButton != null)
+            {
+                DecreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease);
+            }
+        }
+
+        /// <summary>
+        /// Called when user clicks one of the spin buttons.
+        /// </summary>
+        /// <param name="sender">The event sender.</param>
+        /// <param name="e">The event args.</param>
+        private void OnButtonClick(object sender, RoutedEventArgs e)
+        {
+            if (AllowSpin)
+            {
+                var direction = sender == IncreaseButton ? SpinDirection.Increase : SpinDirection.Decrease;
+                OnSpin(new SpinEventArgs(SpinEvent, direction));
+            }
+        }
+    }
+}

+ 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;

+ 1 - 0
src/Avalonia.Controls/DropDown.cs

@@ -164,6 +164,7 @@ namespace Avalonia.Controls
                 else
                 {
                     IsDropDownOpen = !IsDropDownOpen;
+                    e.Handled = true;
                 }
             }
             base.OnPointerPressed(e);

+ 148 - 674
src/Avalonia.Controls/Grid.cs

@@ -4,7 +4,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using Avalonia.Collections;
+using Avalonia.Controls.Utils;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls
 {
@@ -45,10 +48,6 @@ namespace Avalonia.Controls
 
         private RowDefinitions _rowDefinitions;
 
-        private Segment[,] _rowMatrix;
-
-        private Segment[,] _colMatrix;
-
         /// <summary>
         /// Gets or sets the columns definitions for the grid.
         /// </summary>
@@ -183,6 +182,18 @@ namespace Avalonia.Controls
             element.SetValue(RowSpanProperty, value);
         }
 
+        /// <summary>
+        /// Gets the result of the last column measurement.
+        /// Use this result to reduce the arrange calculation.
+        /// </summary>
+        private GridLayout.MeasureResult _columnMeasureCache;
+
+        /// <summary>
+        /// Gets the result of the last row measurement.
+        /// Use this result to reduce the arrange calculation.
+        /// </summary>
+        private GridLayout.MeasureResult _rowMeasureCache;
+
         /// <summary>
         /// Measures the grid.
         /// </summary>
@@ -190,293 +201,74 @@ namespace Avalonia.Controls
         /// <returns>The desired size of the control.</returns>
         protected override Size MeasureOverride(Size constraint)
         {
-            Size totalSize = constraint;
-            int colCount = ColumnDefinitions.Count;
-            int rowCount = RowDefinitions.Count;
-            double totalStarsX = 0;
-            double totalStarsY = 0;
-            bool emptyRows = rowCount == 0;
-            bool emptyCols = colCount == 0;
-            bool hasChildren = Children.Count > 0;
-
-            if (emptyRows)
-            {
-                rowCount = 1;
-            }
-
-            if (emptyCols)
-            {
-                colCount = 1;
-            }
-
-            CreateMatrices(rowCount, colCount);
+            // Situation 1/2:
+            // If the grid doesn't have any column/row definitions, it behaves like a normal panel.
+            // GridLayout supports this situation but we handle this separately for performance.
 
-            if (emptyRows)
-            {
-                _rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
-                _rowMatrix[0, 0].Stars = 1.0;
-                totalStarsY += 1.0;
-            }
-            else
+            if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
             {
-                for (int i = 0; i < rowCount; i++)
+                var maxWidth = 0.0;
+                var maxHeight = 0.0;
+                foreach (var child in Children.OfType<Control>())
                 {
-                    RowDefinition rowdef = RowDefinitions[i];
-                    GridLength height = rowdef.Height;
-
-                    rowdef.ActualHeight = double.PositiveInfinity;
-                    _rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType);
-
-                    if (height.GridUnitType == GridUnitType.Pixel)
-                    {
-                        _rowMatrix[i, i].OfferedSize = Clamp(height.Value, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max);
-                        _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize;
-                        rowdef.ActualHeight = _rowMatrix[i, i].OfferedSize;
-                    }
-                    else if (height.GridUnitType == GridUnitType.Star)
-                    {
-                        _rowMatrix[i, i].Stars = height.Value;
-                        totalStarsY += height.Value;
-                    }
-                    else if (height.GridUnitType == GridUnitType.Auto)
-                    {
-                        _rowMatrix[i, i].OfferedSize = Clamp(0, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max);
-                        _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize;
-                    }
+                    child.Measure(constraint);
+                    maxWidth = Math.Max(maxWidth, child.DesiredSize.Width);
+                    maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
                 }
-            }
 
-            if (emptyCols)
-            {
-                _colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
-                _colMatrix[0, 0].Stars = 1.0;
-                totalStarsX += 1.0;
-            }
-            else
-            {
-                for (int i = 0; i < colCount; i++)
-                {
-                    ColumnDefinition coldef = ColumnDefinitions[i];
-                    GridLength width = coldef.Width;
-
-                    coldef.ActualWidth = double.PositiveInfinity;
-                    _colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType);
-
-                    if (width.GridUnitType == GridUnitType.Pixel)
-                    {
-                        _colMatrix[i, i].OfferedSize = Clamp(width.Value, _colMatrix[i, i].Min, _colMatrix[i, i].Max);
-                        _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize;
-                        coldef.ActualWidth = _colMatrix[i, i].OfferedSize;
-                    }
-                    else if (width.GridUnitType == GridUnitType.Star)
-                    {
-                        _colMatrix[i, i].Stars = width.Value;
-                        totalStarsX += width.Value;
-                    }
-                    else if (width.GridUnitType == GridUnitType.Auto)
-                    {
-                        _colMatrix[i, i].OfferedSize = Clamp(0, _colMatrix[i, i].Min, _colMatrix[i, i].Max);
-                        _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize;
-                    }
-                }
+                maxWidth = Math.Min(maxWidth, constraint.Width);
+                maxHeight = Math.Min(maxHeight, constraint.Height);
+                return new Size(maxWidth, maxHeight);
             }
 
-            List<GridNode> sizes = new List<GridNode>();
-            GridNode node;
-            GridNode separator = new GridNode(null, 0, 0, 0);
-            int separatorIndex;
+            // Situation 2/2:
+            // If the grid defines some columns or rows.
+            // Debug Tip:
+            //     - GridLayout doesn't hold any state, so you can drag the debugger execution
+            //       arrow back to any statements and re-run them without any side-effect.
 
-            sizes.Add(separator);
+            var measureCache = new Dictionary<Control, Size>();
+            var (safeColumns, safeRows) = GetSafeColumnRows();
+            var columnLayout = new GridLayout(ColumnDefinitions);
+            var rowLayout = new GridLayout(RowDefinitions);
+            // Note: If a child stays in a * or Auto column/row, use constraint to measure it.
+            columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width);
+            rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height);
 
-            // Pre-process the grid children so that we know what types of elements we have so
-            // we can apply our special measuring rules.
-            GridWalker gridWalker = new GridWalker(this, _rowMatrix, _colMatrix);
+            // Calculate measurement.
+            var columnResult = columnLayout.Measure(constraint.Width);
+            var rowResult = rowLayout.Measure(constraint.Height);
 
-            for (int i = 0; i < 6; i++)
+            // Use the results of the measurement to measure the rest of the children.
+            foreach (var child in Children.OfType<Control>())
             {
-                // These bools tell us which grid element type we should be measuring. i.e.
-                // 'star/auto' means we should measure elements with a star row and auto col
-                bool autoAuto = i == 0;
-                bool starAuto = i == 1;
-                bool autoStar = i == 2;
-                bool starAutoAgain = i == 3;
-                bool nonStar = i == 4;
-                bool remainingStar = i == 5;
-
-                if (hasChildren)
-                {
-                    ExpandStarCols(totalSize);
-                    ExpandStarRows(totalSize);
-                }
+                var (column, columnSpan) = safeColumns[child];
+                var (row, rowSpan) = safeRows[child];
+                var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum();
+                var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum();
 
-                foreach (Control child in Children)
-                {
-                    int col, row;
-                    int colspan, rowspan;
-                    double childSizeX = 0;
-                    double childSizeY = 0;
-                    bool starCol = false;
-                    bool starRow = false;
-                    bool autoCol = false;
-                    bool autoRow = false;
-
-                    col = Math.Min(GetColumn(child), colCount - 1);
-                    row = Math.Min(GetRow(child), rowCount - 1);
-                    colspan = Math.Min(GetColumnSpan(child), colCount - col);
-                    rowspan = Math.Min(GetRowSpan(child), rowCount - row);
-
-                    for (int r = row; r < row + rowspan; r++)
-                    {
-                        starRow |= _rowMatrix[r, r].Type == GridUnitType.Star;
-                        autoRow |= _rowMatrix[r, r].Type == GridUnitType.Auto;
-                    }
-
-                    for (int c = col; c < col + colspan; c++)
-                    {
-                        starCol |= _colMatrix[c, c].Type == GridUnitType.Star;
-                        autoCol |= _colMatrix[c, c].Type == GridUnitType.Auto;
-                    }
-
-                    // This series of if statements checks whether or not we should measure
-                    // the current element and also if we need to override the sizes
-                    // passed to the Measure call.
-
-                    // If the element has Auto rows and Auto columns and does not span Star
-                    // rows/cols it should only be measured in the auto_auto phase.
-                    // There are similar rules governing auto/star and star/auto elements.
-                    // NOTE: star/auto elements are measured twice. The first time with
-                    // an override for height, the second time without it.
-                    if (autoRow && autoCol && !starRow && !starCol)
-                    {
-                        if (!autoAuto)
-                        {
-                            continue;
-                        }
-
-                        childSizeX = double.PositiveInfinity;
-                        childSizeY = double.PositiveInfinity;
-                    }
-                    else if (starRow && autoCol && !starCol)
-                    {
-                        if (!(starAuto || starAutoAgain))
-                        {
-                            continue;
-                        }
-
-                        if (starAuto && gridWalker.HasAutoStar)
-                        {
-                            childSizeY = double.PositiveInfinity;
-                        }
-
-                        childSizeX = double.PositiveInfinity;
-                    }
-                    else if (autoRow && starCol && !starRow)
-                    {
-                        if (!autoStar)
-                        {
-                            continue;
-                        }
-
-                        childSizeY = double.PositiveInfinity;
-                    }
-                    else if ((autoRow || autoCol) && !(starRow || starCol))
-                    {
-                        if (!nonStar)
-                        {
-                            continue;
-                        }
-
-                        if (autoRow)
-                        {
-                            childSizeY = double.PositiveInfinity;
-                        }
-
-                        if (autoCol)
-                        {
-                            childSizeX = double.PositiveInfinity;
-                        }
-                    }
-                    else if (!(starRow || starCol))
-                    {
-                        if (!nonStar)
-                        {
-                            continue;
-                        }
-                    }
-                    else
-                    {
-                        if (!remainingStar)
-                        {
-                            continue;
-                        }
-                    }
-
-                    for (int r = row; r < row + rowspan; r++)
-                    {
-                        childSizeY += _rowMatrix[r, r].OfferedSize;
-                    }
-
-                    for (int c = col; c < col + colspan; c++)
-                    {
-                        childSizeX += _colMatrix[c, c].OfferedSize;
-                    }
-
-                    child.Measure(new Size(childSizeX, childSizeY));
-                    Size desired = child.DesiredSize;
-
-                    // Elements distribute their height based on two rules:
-                    // 1) Elements with rowspan/colspan == 1 distribute their height first
-                    // 2) Everything else distributes in a LIFO manner.
-                    // As such, add all UIElements with rowspan/colspan == 1 after the separator in
-                    // the list and everything else before it. Then to process, just keep popping
-                    // elements off the end of the list.
-                    if (!starAuto)
-                    {
-                        node = new GridNode(_rowMatrix, row + rowspan - 1, row, desired.Height);
-                        separatorIndex = sizes.IndexOf(separator);
-                        sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
-                    }
-
-                    node = new GridNode(_colMatrix, col + colspan - 1, col, desired.Width);
-
-                    separatorIndex = sizes.IndexOf(separator);
-                    sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
-                }
+                MeasureOnce(child, new Size(width, height));
+            }
 
-                sizes.Remove(separator);
+            // Cache the measure result and return the desired size.
+            _columnMeasureCache = columnResult;
+            _rowMeasureCache = rowResult;
+            return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
 
-                while (sizes.Count > 0)
+            // Measure each child only once.
+            // If a child has been measured, it will just return the desired size.
+            Size MeasureOnce(Control child, Size size)
+            {
+                if (measureCache.TryGetValue(child, out var desiredSize))
                 {
-                    node = sizes.Last();
-                    node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size);
-                    AllocateDesiredSize(rowCount, colCount);
-                    sizes.Remove(node);
+                    return desiredSize;
                 }
 
-                sizes.Add(separator);
-            }
-
-            // Once we have measured and distributed all sizes, we have to store
-            // the results. Every time we want to expand the rows/cols, this will
-            // be used as the baseline.
-            SaveMeasureResults();
-
-            sizes.Remove(separator);
-
-            double gridSizeX = 0;
-            double gridSizeY = 0;
-
-            for (int c = 0; c < colCount; c++)
-            {
-                gridSizeX += _colMatrix[c, c].DesiredSize;
+                child.Measure(size);
+                desiredSize = child.DesiredSize;
+                measureCache[child] = desiredSize;
+                return desiredSize;
             }
-
-            for (int r = 0; r < rowCount; r++)
-            {
-                gridSizeY += _rowMatrix[r, r].DesiredSize;
-            }
-
-            return new Size(gridSizeX, gridSizeY);
         }
 
         /// <summary>
@@ -486,456 +278,138 @@ namespace Avalonia.Controls
         /// <returns>The space taken.</returns>
         protected override Size ArrangeOverride(Size finalSize)
         {
-            int colCount = ColumnDefinitions.Count;
-            int rowCount = RowDefinitions.Count;
-            int colMatrixDim = _colMatrix.GetLength(0);
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-
-            RestoreMeasureResults();
+            // Situation 1/2:
+            // If the grid doesn't have any column/row definitions, it behaves like a normal panel.
+            // GridLayout supports this situation but we handle this separately for performance.
 
-            double totalConsumedX = 0;
-            double totalConsumedY = 0;
-
-            for (int c = 0; c < colMatrixDim; c++)
+            if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
             {
-                _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize;
-                totalConsumedX += _colMatrix[c, c].OfferedSize;
-            }
-
-            for (int r = 0; r < rowMatrixDim; r++)
-            {
-                _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize;
-                totalConsumedY += _rowMatrix[r, r].OfferedSize;
-            }
-
-            if (totalConsumedX != finalSize.Width)
-            {
-                ExpandStarCols(finalSize);
-            }
-
-            if (totalConsumedY != finalSize.Height)
-            {
-                ExpandStarRows(finalSize);
-            }
-
-            for (int c = 0; c < colCount; c++)
-            {
-                ColumnDefinitions[c].ActualWidth = _colMatrix[c, c].OfferedSize;
-            }
-
-            for (int r = 0; r < rowCount; r++)
-            {
-                RowDefinitions[r].ActualHeight = _rowMatrix[r, r].OfferedSize;
-            }
-
-            foreach (Control child in Children)
-            {
-                int col = Math.Min(GetColumn(child), colMatrixDim - 1);
-                int row = Math.Min(GetRow(child), rowMatrixDim - 1);
-                int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col);
-                int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row);
-
-                double childFinalX = 0;
-                double childFinalY = 0;
-                double childFinalW = 0;
-                double childFinalH = 0;
-
-                for (int c = 0; c < col; c++)
-                {
-                    childFinalX += _colMatrix[c, c].OfferedSize;
-                }
-
-                for (int c = col; c < col + colspan; c++)
+                foreach (var child in Children.OfType<Control>())
                 {
-                    childFinalW += _colMatrix[c, c].OfferedSize;
+                    child.Arrange(new Rect(finalSize));
                 }
 
-                for (int r = 0; r < row; r++)
-                {
-                    childFinalY += _rowMatrix[r, r].OfferedSize;
-                }
-
-                for (int r = row; r < row + rowspan; r++)
-                {
-                    childFinalH += _rowMatrix[r, r].OfferedSize;
-                }
-
-                child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH));
+                return finalSize;
             }
 
-            return finalSize;
-        }
-
-        private static double Clamp(double val, double min, double max)
-        {
-            if (val < min)
-            {
-                return min;
-            }
-            else if (val > max)
-            {
-                return max;
-            }
-            else
-            {
-                return val;
-            }
-        }
+            // Situation 2/2:
+            // If the grid defines some columns or rows.
+            // Debug Tip:
+            //     - GridLayout doesn't hold any state, so you can drag the debugger execution
+            //       arrow back to any statements and re-run them without any side-effect.
 
-        private static int ValidateColumn(AvaloniaObject o, int value)
-        {
-            if (value < 0)
-            {
-                throw new ArgumentException("Invalid Grid.Column value.");
-            }
+            var (safeColumns, safeRows) = GetSafeColumnRows();
+            var columnLayout = new GridLayout(ColumnDefinitions);
+            var rowLayout = new GridLayout(RowDefinitions);
 
-            return value;
-        }
+            // Calculate for arrange result.
+            var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
+            var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
 
-        private static int ValidateRow(AvaloniaObject o, int value)
-        {
-            if (value < 0)
+            // Arrange the children.
+            foreach (var child in Children.OfType<Control>())
             {
-                throw new ArgumentException("Invalid Grid.Row value.");
-            }
+                var (column, columnSpan) = safeColumns[child];
+                var (row, rowSpan) = safeRows[child];
+                var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]);
+                var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]);
+                var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]);
+                var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]);
 
-            return value;
-        }
-
-        private void CreateMatrices(int rowCount, int colCount)
-        {
-            if (_rowMatrix == null || _colMatrix == null ||
-                _rowMatrix.GetLength(0) != rowCount ||
-                _colMatrix.GetLength(0) != colCount)
-            {
-                _rowMatrix = new Segment[rowCount, rowCount];
-                _colMatrix = new Segment[colCount, colCount];
-            }
-            else
-            {
-                Array.Clear(_rowMatrix, 0, _rowMatrix.Length);
-                Array.Clear(_colMatrix, 0, _colMatrix.Length);
+                child.Arrange(new Rect(x, y, width, height));
             }
-        }
-
-        private void ExpandStarCols(Size availableSize)
-        {
-            int matrixCount = _colMatrix.GetLength(0);
-            int columnsCount = ColumnDefinitions.Count;
-            double width = availableSize.Width;
 
-            for (int i = 0; i < matrixCount; i++)
+            // Assign the actual width.
+            for (var i = 0; i < ColumnDefinitions.Count; i++)
             {
-                if (_colMatrix[i, i].Type == GridUnitType.Star)
-                {
-                    _colMatrix[i, i].OfferedSize = 0;
-                }
-                else
-                {
-                    width = Math.Max(width - _colMatrix[i, i].OfferedSize, 0);
-                }
+                ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i];
             }
 
-            AssignSize(_colMatrix, 0, matrixCount - 1, ref width, GridUnitType.Star, false);
-            width = Math.Max(0, width);
-
-            if (columnsCount > 0)
+            // Assign the actual height.
+            for (var i = 0; i < RowDefinitions.Count; i++)
             {
-                for (int i = 0; i < matrixCount; i++)
-                {
-                    if (_colMatrix[i, i].Type == GridUnitType.Star)
-                    {
-                        ColumnDefinitions[i].ActualWidth = _colMatrix[i, i].OfferedSize;
-                    }
-                }
-            }
-        }
-
-        private void ExpandStarRows(Size availableSize)
-        {
-            int matrixCount = _rowMatrix.GetLength(0);
-            int rowCount = RowDefinitions.Count;
-            double height = availableSize.Height;
-
-            // When expanding star rows, we need to zero out their height before
-            // calling AssignSize. AssignSize takes care of distributing the
-            // available size when there are Mins and Maxs applied.
-            for (int i = 0; i < matrixCount; i++)
-            {
-                if (_rowMatrix[i, i].Type == GridUnitType.Star)
-                {
-                    _rowMatrix[i, i].OfferedSize = 0.0;
-                }
-                else
-                {
-                    height = Math.Max(height - _rowMatrix[i, i].OfferedSize, 0);
-                }
+                RowDefinitions[i].ActualHeight = rowResult.LengthList[i];
             }
 
-            AssignSize(_rowMatrix, 0, matrixCount - 1, ref height, GridUnitType.Star, false);
-
-            if (rowCount > 0)
-            {
-                for (int i = 0; i < matrixCount; i++)
-                {
-                    if (_rowMatrix[i, i].Type == GridUnitType.Star)
-                    {
-                        RowDefinitions[i].ActualHeight = _rowMatrix[i, i].OfferedSize;
-                    }
-                }
-            }
+            // Return the render size.
+            return finalSize;
         }
 
-        private void AssignSize(
-            Segment[,] matrix,
-            int start,
-            int end,
-            ref double size,
-            GridUnitType type,
-            bool desiredSize)
+        /// <summary>
+        /// Get the safe column/columnspan and safe row/rowspan.
+        /// This method ensures that none of the children has a column/row outside the bounds of the definitions.
+        /// </summary>
+        [Pure]
+        private (Dictionary<Control, (int index, int span)> safeColumns,
+            Dictionary<Control, (int index, int span)> safeRows) GetSafeColumnRows()
         {
-            double count = 0;
-            bool assigned;
-
-            // Count how many segments are of the correct type. If we're measuring Star rows/cols
-            // we need to count the number of stars instead.
-            for (int i = start; i <= end; i++)
-            {
-                double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
-                if (segmentSize < matrix[i, i].Max)
-                {
-                    count += type == GridUnitType.Star ? matrix[i, i].Stars : 1;
-                }
-            }
-
-            do
-            {
-                double contribution = size / count;
-
-                assigned = false;
-
-                for (int i = start; i <= end; i++)
-                {
-                    double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
-
-                    if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max))
-                    {
-                        continue;
-                    }
-
-                    double newsize = segmentSize;
-                    newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1);
-                    newsize = Math.Min(newsize, matrix[i, i].Max);
-                    assigned |= newsize > segmentSize;
-                    size -= newsize - segmentSize;
-
-                    if (desiredSize)
-                    {
-                        matrix[i, i].DesiredSize = newsize;
-                    }
-                    else
-                    {
-                        matrix[i, i].OfferedSize = newsize;
-                    }
-                }
-            }
-            while (assigned);
+            var columnCount = ColumnDefinitions.Count;
+            var rowCount = RowDefinitions.Count;
+            columnCount = columnCount == 0 ? 1 : columnCount;
+            rowCount = rowCount == 0 ? 1 : rowCount;
+            var safeColumns = Children.OfType<Control>().ToDictionary(child => child,
+                child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
+            var safeRows = Children.OfType<Control>().ToDictionary(child => child,
+                child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child)));
+            return (safeColumns, safeRows);
         }
 
-        private void AllocateDesiredSize(int rowCount, int colCount)
+        /// <summary>
+        /// Gets the safe row/column and rowspan/columnspan for a specified range.
+        /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside.
+        /// </summary>
+        /// <param name="length">The row or column count.</param>
+        /// <param name="userIndex">The row or column that the user assigned.</param>
+        /// <param name="userSpan">The rowspan or columnspan that the user assigned.</param>
+        /// <returns>The safe row/column and rowspan/columnspan.</returns>
+        [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan)
         {
-            // First allocate the heights of the RowDefinitions, then allocate
-            // the widths of the ColumnDefinitions.
-            for (int i = 0; i < 2; i++)
-            {
-                Segment[,] matrix = i == 0 ? _rowMatrix : _colMatrix;
-                int count = i == 0 ? rowCount : colCount;
-
-                for (int row = count - 1; row >= 0; row--)
-                {
-                    for (int col = row; col >= 0; col--)
-                    {
-                        bool spansStar = false;
-                        for (int j = row; j >= col; j--)
-                        {
-                            spansStar |= matrix[j, j].Type == GridUnitType.Star;
-                        }
-
-                        // This is the amount of pixels which must be available between the grid rows
-                        // at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
-                        // be at least 'matrix [row][col].size' pixels of height allocated between
-                        // all the rows in the range col -> row.
-                        double current = matrix[row, col].DesiredSize;
-
-                        // Count how many pixels have already been allocated between the grid rows
-                        // in the range col -> row. The amount of pixels allocated to each grid row/column
-                        // is found on the diagonal of the matrix.
-                        double totalAllocated = 0;
-
-                        for (int k = row; k >= col; k--)
-                        {
-                            totalAllocated += matrix[k, k].DesiredSize;
-                        }
-
-                        // If the size requirement has not been met, allocate the additional required
-                        // size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
-                        // height has been assigned.
-                        if (totalAllocated < current)
-                        {
-                            double additional = current - totalAllocated;
-
-                            if (spansStar)
-                            {
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true);
-                            }
-                            else
-                            {
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true);
-                                AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true);
-                            }
-                        }
-                    }
-                }
-            }
-
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
+            var index = userIndex;
+            var span = userSpan;
 
-            for (int r = 0; r < rowMatrixDim; r++)
+            if (index < 0)
             {
-                _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize;
+                span = index + span;
+                index = 0;
             }
 
-            for (int c = 0; c < colMatrixDim; c++)
+            if (span <= 0)
             {
-                _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize;
+                span = 1;
             }
-        }
 
-        private void SaveMeasureResults()
-        {
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
-
-            for (int i = 0; i < rowMatrixDim; i++)
+            if (userIndex >= length)
             {
-                for (int j = 0; j < rowMatrixDim; j++)
-                {
-                    _rowMatrix[i, j].OriginalSize = _rowMatrix[i, j].OfferedSize;
-                }
+                index = length - 1;
+                span = 1;
             }
-
-            for (int i = 0; i < colMatrixDim; i++)
+            else if (userIndex + userSpan > length)
             {
-                for (int j = 0; j < colMatrixDim; j++)
-                {
-                    _colMatrix[i, j].OriginalSize = _colMatrix[i, j].OfferedSize;
-                }
-            }
-        }
-
-        private void RestoreMeasureResults()
-        {
-            int rowMatrixDim = _rowMatrix.GetLength(0);
-            int colMatrixDim = _colMatrix.GetLength(0);
-
-            for (int i = 0; i < rowMatrixDim; i++)
-            {
-                for (int j = 0; j < rowMatrixDim; j++)
-                {
-                    _rowMatrix[i, j].OfferedSize = _rowMatrix[i, j].OriginalSize;
-                }
+                span = length - userIndex;
             }
 
-            for (int i = 0; i < colMatrixDim; i++)
-            {
-                for (int j = 0; j < colMatrixDim; j++)
-                {
-                    _colMatrix[i, j].OfferedSize = _colMatrix[i, j].OriginalSize;
-                }
-            }
+            return (index, span);
         }
 
-        private struct Segment
+        private static int ValidateColumn(AvaloniaObject o, int value)
         {
-            public double OriginalSize;
-            public double Max;
-            public double Min;
-            public double DesiredSize;
-            public double OfferedSize;
-            public double Stars;
-            public GridUnitType Type;
-
-            public Segment(double offeredSize, double min, double max, GridUnitType type)
+            if (value < 0)
             {
-                OriginalSize = 0;
-                Min = min;
-                Max = max;
-                DesiredSize = 0;
-                OfferedSize = offeredSize;
-                Stars = 0;
-                Type = type;
+                throw new ArgumentException("Invalid Grid.Column value.");
             }
-        }
 
-        private struct GridNode
-        {
-            public readonly int Row;
-            public readonly int Column;
-            public readonly double Size;
-            public readonly Segment[,] Matrix;
-
-            public GridNode(Segment[,] matrix, int row, int col, double size)
-            {
-                Matrix = matrix;
-                Row = row;
-                Column = col;
-                Size = size;
-            }
+            return value;
         }
 
-        private class GridWalker
+        private static int ValidateRow(AvaloniaObject o, int value)
         {
-            public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix)
+            if (value < 0)
             {
-                int rowMatrixDim = rowMatrix.GetLength(0);
-                int colMatrixDim = colMatrix.GetLength(0);
-
-                foreach (Control child in grid.Children)
-                {
-                    bool starCol = false;
-                    bool starRow = false;
-                    bool autoCol = false;
-                    bool autoRow = false;
-
-                    int col = Math.Min(GetColumn(child), colMatrixDim - 1);
-                    int row = Math.Min(GetRow(child), rowMatrixDim - 1);
-                    int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - 1);
-                    int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - 1);
-
-                    for (int r = row; r < row + rowspan; r++)
-                    {
-                        starRow |= rowMatrix[r, r].Type == GridUnitType.Star;
-                        autoRow |= rowMatrix[r, r].Type == GridUnitType.Auto;
-                    }
-
-                    for (int c = col; c < col + colspan; c++)
-                    {
-                        starCol |= colMatrix[c, c].Type == GridUnitType.Star;
-                        autoCol |= colMatrix[c, c].Type == GridUnitType.Auto;
-                    }
-
-                    HasAutoAuto |= autoRow && autoCol && !starRow && !starCol;
-                    HasStarAuto |= starRow && autoCol;
-                    HasAutoStar |= autoRow && starCol;
-                }
+                throw new ArgumentException("Invalid Grid.Row value.");
             }
 
-            public bool HasAutoAuto { get; }
-
-            public bool HasStarAuto { get; }
-
-            public bool HasAutoStar { get; }
+            return value;
         }
     }
-}
+}

+ 12 - 7
src/Avalonia.Controls/GridLength.cs

@@ -1,6 +1,7 @@
 // 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.Utilities;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -179,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();
 
@@ -192,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);
             }
         }
@@ -206,11 +206,16 @@ 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)
         {
-            return s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Parse(x, culture));
+            using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture))
+            {
+                while (tokenizer.TryReadString(out var item))
+                {
+                    yield return Parse(item);
+                }
+            }
         }
     }
 }

+ 5 - 7
src/Avalonia.Controls/ItemsControl.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
@@ -54,6 +55,7 @@ namespace Avalonia.Controls
 
         private IEnumerable _items = new AvaloniaList<object>();
         private IItemContainerGenerator _itemContainerGenerator;
+        private IDisposable _itemsCollectionChangedSubscription;
 
         /// <summary>
         /// Initializes static members of the <see cref="ItemsControl"/> class.
@@ -326,12 +328,8 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var incc = e.OldValue as INotifyCollectionChanged;
-
-            if (incc != null)
-            {
-                incc.CollectionChanged -= ItemsCollectionChanged;
-            }
+            _itemsCollectionChangedSubscription?.Dispose();
+            _itemsCollectionChangedSubscription = null;
 
             var oldValue = e.OldValue as IEnumerable;
             var newValue = e.NewValue as IEnumerable;
@@ -428,7 +426,7 @@ namespace Avalonia.Controls
 
             if (incc != null)
             {
-                incc.CollectionChanged += ItemsCollectionChanged;
+                _itemsCollectionChangedSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
             }
         }
 

+ 6 - 0
src/Avalonia.Controls/LayoutTransformControl.cs

@@ -15,6 +15,9 @@ using System.Reactive.Linq;
 
 namespace Avalonia.Controls
 {
+    /// <summary>
+    /// Control that implements support for transformations as if applied by LayoutTransform.
+    /// </summary>
     public class LayoutTransformControl : ContentControl
     {
         public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
@@ -26,6 +29,9 @@ namespace Avalonia.Controls
                 .AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
         }
 
+        /// <summary>
+        /// Gets or sets a graphics transformation that should apply to this element when layout is performed.
+        /// </summary>
         public Transform LayoutTransform
         {
             get { return GetValue(LayoutTransformProperty); }

+ 35 - 0
src/Avalonia.Controls/MenuItem.cs

@@ -93,6 +93,7 @@ namespace Avalonia.Controls
         static MenuItem()
         {
             SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
+            CommandProperty.Changed.Subscribe(CommandChanged);
             FocusableProperty.OverrideDefaultValue<MenuItem>(true);
             IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
             ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@@ -424,6 +425,40 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Called when the <see cref="Command"/> property changes.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is MenuItem menuItem)
+            {
+                if (e.OldValue is ICommand oldCommand)
+                {
+                    oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
+                }
+
+                if (e.NewValue is ICommand newCommand)
+                {
+                    newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
+                }
+
+                menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
+        /// </summary>
+        /// <param name="sender">The event sender.</param>
+        /// <param name="e">The event args.</param>
+        private void CanExecuteChanged(object sender, EventArgs e)
+        {
+            // HACK: Just set the IsEnabled property for the moment. This needs to be changed to
+            // use IsEnabledCore etc. but it will do for now.
+            IsEnabled = Command == null || Command.CanExecute(CommandParameter);
+        }
+
         /// <summary>
         /// Called when the <see cref="Icon"/> property changes.
         /// </summary>

+ 998 - 0
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -0,0 +1,998 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
+    /// </summary>
+    public class NumericUpDown : TemplatedControl
+    {
+        /// <summary>
+        /// Defines the <see cref="AllowSpin"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> AllowSpinProperty =
+            ButtonSpinner.AllowSpinProperty.AddOwner<NumericUpDown>();
+
+        /// <summary>
+        /// Defines the <see cref="ButtonSpinnerLocation"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
+            ButtonSpinner.ButtonSpinnerLocationProperty.AddOwner<NumericUpDown>();
+
+        /// <summary>
+        /// Defines the <see cref="ShowButtonSpinner"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> ShowButtonSpinnerProperty =
+            ButtonSpinner.ShowButtonSpinnerProperty.AddOwner<NumericUpDown>();
+
+        /// <summary>
+        /// Defines the <see cref="ClipValueToMinMax"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, bool> ClipValueToMinMaxProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
+                updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
+
+        /// <summary>
+        /// Defines the <see cref="CultureInfo"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo,
+                (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
+
+        /// <summary>
+        /// Defines the <see cref="FormatString"/> property.
+        /// </summary>
+        public static readonly StyledProperty<string> FormatStringProperty =
+            AvaloniaProperty.Register<NumericUpDown, string>(nameof(FormatString), string.Empty);
+
+        /// <summary>
+        /// Defines the <see cref="Increment"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> IncrementProperty =
+            AvaloniaProperty.Register<NumericUpDown, double>(nameof(Increment), 1.0d, validate: OnCoerceIncrement);
+
+        /// <summary>
+        /// Defines the <see cref="IsReadOnly"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> IsReadOnlyProperty =
+            AvaloniaProperty.Register<NumericUpDown, bool>(nameof(IsReadOnly));
+
+        /// <summary>
+        /// Defines the <see cref="Maximum"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> MaximumProperty =
+            AvaloniaProperty.Register<NumericUpDown, double>(nameof(Maximum), double.MaxValue, validate: OnCoerceMaximum);
+
+        /// <summary>
+        /// Defines the <see cref="Minimum"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> MinimumProperty =
+            AvaloniaProperty.Register<NumericUpDown, double>(nameof(Minimum), double.MinValue, validate: OnCoerceMinimum);
+
+        /// <summary>
+        /// Defines the <see cref="ParsingNumberStyle"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, NumberStyles> ParsingNumberStyleProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle),
+                updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
+
+        /// <summary>
+        /// Defines the <see cref="Text"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, string> TextProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, string>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
+                defaultBindingMode: BindingMode.TwoWay);
+
+        /// <summary>
+        /// Defines the <see cref="Value"/> property.
+        /// </summary>
+        public static readonly DirectProperty<NumericUpDown, double> ValueProperty =
+            AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value,
+                (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay);
+
+        /// <summary>
+        /// Defines the <see cref="Watermark"/> property.
+        /// </summary>
+        public static readonly StyledProperty<string> WatermarkProperty =
+            AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
+
+        private IDisposable _textBoxTextChangedSubscription;
+
+        private double _value;
+        private string _text;
+        private bool _internalValueSet;
+        private bool _clipValueToMinMax;
+        private bool _isSyncingTextAndValueProperties;
+        private bool _isTextChangedFromUI;
+        private CultureInfo _cultureInfo;
+        private NumberStyles _parsingNumberStyle = NumberStyles.Any;
+        
+        /// <summary>
+        /// Gets the Spinner template part.
+        /// </summary>
+        private Spinner Spinner { get; set; }
+
+        /// <summary>
+        /// Gets the TextBox template part.
+        /// </summary>
+        private TextBox TextBox { get; set; }
+
+        /// <summary>
+        /// Gets or sets the ability to perform increment/decrement operations via the keyboard, button spinners, or mouse wheel.
+        /// </summary>
+        public bool AllowSpin
+        {
+            get { return GetValue(AllowSpinProperty); }
+            set { SetValue(AllowSpinProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets current location of the <see cref="ButtonSpinner"/>.
+        /// </summary>
+        public Location ButtonSpinnerLocation
+        {
+            get { return GetValue(ButtonSpinnerLocationProperty); }
+            set { SetValue(ButtonSpinnerLocationProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the spin buttons should be shown.
+        /// </summary>
+        public bool ShowButtonSpinner
+        {
+            get { return GetValue(ShowButtonSpinnerProperty); }
+            set { SetValue(ShowButtonSpinnerProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets if the value should be clipped when minimum/maximum is reached.
+        /// </summary>
+        public bool ClipValueToMinMax
+        {
+            get { return _clipValueToMinMax; }
+            set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the current CultureInfo.
+        /// </summary>
+        public CultureInfo CultureInfo
+        {
+            get { return _cultureInfo; }
+            set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the display format of the <see cref="Value"/>.
+        /// </summary>
+        public string FormatString
+        {
+            get { return GetValue(FormatStringProperty); }
+            set { SetValue(FormatStringProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the amount in which to increment the <see cref="Value"/>.
+        /// </summary>
+        public double Increment
+        {
+            get { return GetValue(IncrementProperty); }
+            set { SetValue(IncrementProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets if the control is read only.
+        /// </summary>
+        public bool IsReadOnly
+        {
+            get { return GetValue(IsReadOnlyProperty); }
+            set { SetValue(IsReadOnlyProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum allowed value.
+        /// </summary>
+        public double Maximum
+        {
+            get { return GetValue(MaximumProperty); }
+            set { SetValue(MaximumProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum allowed value.
+        /// </summary>
+        public double Minimum
+        {
+            get { return GetValue(MinimumProperty); }
+            set { SetValue(MinimumProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
+        /// </summary>
+        public NumberStyles ParsingNumberStyle
+        {
+            get { return _parsingNumberStyle; }
+            set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the formatted string representation of the value.
+        /// </summary>
+        public string Text
+        {
+            get { return _text; }
+            set { SetAndRaise(TextProperty, ref _text, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the value.
+        /// </summary>
+        public double Value
+        {
+            get { return _value; }
+            set
+            {
+                value = OnCoerceValue(value);
+                SetAndRaise(ValueProperty, ref _value, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the object to use as a watermark if the <see cref="Value"/> is null.
+        /// </summary>
+        public string Watermark
+        {
+            get { return GetValue(WatermarkProperty); }
+            set { SetValue(WatermarkProperty, value); }
+        }
+
+        /// <summary>
+        /// Initializes new instance of <see cref="NumericUpDown"/> class.
+        /// </summary>
+        public NumericUpDown()
+        {
+            Initialized += (sender, e) =>
+            {
+                if (!_internalValueSet && IsInitialized)
+                {
+                    SyncTextAndValueProperties(false, null, true);
+                }
+
+                SetValidSpinDirection();
+            };
+        }
+
+        /// <summary>
+        /// Initializes static members of the <see cref="NumericUpDown"/> class.
+        /// </summary>
+        static NumericUpDown()
+        {
+            CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
+            FormatStringProperty.Changed.Subscribe(FormatStringChanged);
+            IncrementProperty.Changed.Subscribe(IncrementChanged);
+            IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
+            MaximumProperty.Changed.Subscribe(OnMaximumChanged);
+            MinimumProperty.Changed.Subscribe(OnMinimumChanged);
+            TextProperty.Changed.Subscribe(OnTextChanged);
+            ValueProperty.Changed.Subscribe(OnValueChanged);
+        }
+
+        /// <inheritdoc />
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            if (TextBox != null)
+            {
+                TextBox.PointerPressed -= TextBoxOnPointerPressed;
+                _textBoxTextChangedSubscription?.Dispose();
+            }
+            TextBox = e.NameScope.Find<TextBox>("PART_TextBox");
+            if (TextBox != null)
+            {
+                TextBox.Text = Text;
+                TextBox.PointerPressed += TextBoxOnPointerPressed;
+                _textBoxTextChangedSubscription = TextBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBoxOnTextChanged());
+            }
+
+            if (Spinner != null)
+            {
+                Spinner.Spin -= OnSpinnerSpin;
+            }
+
+            Spinner = e.NameScope.Find<Spinner>("PART_Spinner");
+
+            if (Spinner != null)
+            {
+                Spinner.Spin += OnSpinnerSpin;
+            }
+
+            SetValidSpinDirection();
+        }
+
+        /// <inheritdoc />
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Enter:
+                    var commitSuccess = CommitInput();
+                    e.Handled = !commitSuccess;
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="CultureInfo"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnCultureInfoChanged(CultureInfo oldValue, CultureInfo newValue)
+        {
+            if (IsInitialized)
+            {
+                SyncTextAndValueProperties(false, null);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="FormatString"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnFormatStringChanged(string oldValue, string newValue)
+        {
+            if (IsInitialized)
+            {
+                SyncTextAndValueProperties(false, null);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Increment"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnIncrementChanged(double oldValue, double newValue)
+        {
+            if (IsInitialized)
+            {
+                SetValidSpinDirection();
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="IsReadOnly"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue)
+        {
+            SetValidSpinDirection();
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Maximum"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnMaximumChanged(double oldValue, double newValue)
+        {
+            if (IsInitialized)
+            {
+                SetValidSpinDirection();
+            }
+            if (ClipValueToMinMax)
+            {
+                Value = MathUtilities.Clamp(Value, Minimum, Maximum);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Minimum"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnMinimumChanged(double oldValue, double newValue)
+        {
+            if (IsInitialized)
+            {
+                SetValidSpinDirection();
+            }
+            if (ClipValueToMinMax)
+            {
+                Value = MathUtilities.Clamp(Value, Minimum, Maximum);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Text"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnTextChanged(string oldValue, string newValue)
+        {
+            if (IsInitialized)
+            {
+                SyncTextAndValueProperties(true, Text);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Value"/> property value changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnValueChanged(double oldValue, double newValue)
+        {
+            if (!_internalValueSet && IsInitialized)
+            {
+                SyncTextAndValueProperties(false, null, true);
+            }
+
+            SetValidSpinDirection();
+
+            RaiseValueChangedEvent(oldValue, newValue);
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Increment"/> property has to be coerced.
+        /// </summary>
+        /// <param name="baseValue">The value.</param>
+        protected virtual double OnCoerceIncrement(double baseValue)
+        {
+            return baseValue;
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Maximum"/> property has to be coerced.
+        /// </summary>
+        /// <param name="baseValue">The value.</param>
+        protected virtual double OnCoerceMaximum(double baseValue)
+        {
+            return Math.Max(baseValue, Minimum);
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Minimum"/> property has to be coerced.
+        /// </summary>
+        /// <param name="baseValue">The value.</param>
+        protected virtual double OnCoerceMinimum(double baseValue)
+        {
+            return Math.Min(baseValue, Maximum);
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Value"/> property has to be coerced.
+        /// </summary>
+        /// <param name="baseValue">The value.</param>
+        protected virtual double OnCoerceValue(double baseValue)
+        {
+            return baseValue;
+        }
+
+        /// <summary>
+        /// Raises the OnSpin event when spinning is initiated by the end-user.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected virtual void OnSpin(SpinEventArgs e)
+        {
+            if (e == null)
+            {
+                throw new ArgumentNullException("e");
+            }
+
+            var handler = Spinned;
+            handler?.Invoke(this, e);
+
+            if (e.Direction == SpinDirection.Increase)
+            {
+                DoIncrement();
+            }
+            else
+            {
+                DoDecrement();
+            }
+        }
+
+        /// <summary>
+        /// Raises the <see cref="ValueChanged"/> event.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void RaiseValueChangedEvent(double oldValue, double newValue)
+        {
+            var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
+            RaiseEvent(e);
+        }
+
+        /// <summary>
+        /// Converts the formatted text to a value.
+        /// </summary>
+        private double ConvertTextToValue(string text)
+        {
+            double result = 0;
+
+            if (string.IsNullOrEmpty(text))
+            {
+                return result;
+            }
+
+            // Since the conversion from Value to text using a FormartString may not be parsable,
+            // we verify that the already existing text is not the exact same value.
+            var currentValueText = ConvertValueToText();
+            if (Equals(currentValueText, text))
+            {
+                return Value;
+            }
+
+            result = ConvertTextToValueCore(currentValueText, text);
+
+            if (ClipValueToMinMax)
+            {
+                return MathUtilities.Clamp(result, Minimum, Maximum);
+            }
+
+            ValidateMinMax(result);
+
+            return result;
+        }
+
+        /// <summary>
+        /// Converts the value to formatted text.
+        /// </summary>
+        /// <returns></returns>
+        private string ConvertValueToText()
+        {
+            //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
+            if (FormatString.Contains("{0"))
+            {
+                return string.Format(CultureInfo, FormatString, Value);
+            }
+
+            return Value.ToString(FormatString, CultureInfo);
+        }
+
+        /// <summary>
+        /// Called by OnSpin when the spin direction is SpinDirection.Increase.
+        /// </summary>
+        private void OnIncrement()
+        {
+            var result = Value + Increment;
+            Value = MathUtilities.Clamp(result, Minimum, Maximum);
+        }
+
+        /// <summary>
+        /// Called by OnSpin when the spin direction is SpinDirection.Descrease.
+        /// </summary>
+        private void OnDecrement()
+        {
+            var result = Value - Increment;
+            Value = MathUtilities.Clamp(result, Minimum, Maximum);
+        }
+
+        /// <summary>
+        /// Sets the valid spin directions.
+        /// </summary>
+        private void SetValidSpinDirection()
+        {
+            var validDirections = ValidSpinDirections.None;
+
+            // Zero increment always prevents spin.
+            if (Increment != 0 && !IsReadOnly)
+            {
+                if (Value < Maximum)
+                {
+                    validDirections = validDirections | ValidSpinDirections.Increase;
+                }
+
+                if (Value > Minimum)
+                {
+                    validDirections = validDirections | ValidSpinDirections.Decrease;
+                }
+            }
+
+            if (Spinner != null)
+            {
+                Spinner.ValidSpinDirection = validDirections;
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="CultureInfo"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (CultureInfo)e.OldValue;
+                var newValue = (CultureInfo)e.NewValue;
+                upDown.OnCultureInfoChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Increment"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void IncrementChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (double)e.OldValue;
+                var newValue = (double)e.NewValue;
+                upDown.OnIncrementChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="FormatString"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void FormatStringChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (string)e.OldValue;
+                var newValue = (string)e.NewValue;
+                upDown.OnFormatStringChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="IsReadOnly"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (bool)e.OldValue;
+                var newValue = (bool)e.NewValue;
+                upDown.OnIsReadOnlyChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Maximum"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnMaximumChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (double)e.OldValue;
+                var newValue = (double)e.NewValue;
+                upDown.OnMaximumChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Minimum"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (double)e.OldValue;
+                var newValue = (double)e.NewValue;
+                upDown.OnMinimumChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Text"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (string)e.OldValue;
+                var newValue = (string)e.NewValue;
+                upDown.OnTextChanged(oldValue, newValue);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Value"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is NumericUpDown upDown)
+            {
+                var oldValue = (double)e.OldValue;
+                var newValue = (double)e.NewValue;
+                upDown.OnValueChanged(oldValue, newValue);
+            }
+        }
+
+        private void SetValueInternal(double value)
+        {
+            _internalValueSet = true;
+            try
+            {
+                Value = value;
+            }
+            finally
+            {
+                _internalValueSet = false;
+            }
+        }
+
+        private static double OnCoerceMaximum(NumericUpDown upDown, double value)
+        {
+            return upDown.OnCoerceMaximum(value);
+        }
+
+        private static double OnCoerceMinimum(NumericUpDown upDown, double value)
+        {
+            return upDown.OnCoerceMinimum(value);
+        }
+
+        private static double OnCoerceIncrement(NumericUpDown upDown, double value)
+        {
+            return upDown.OnCoerceIncrement(value);
+        }
+
+        private void TextBoxOnTextChanged()
+        {
+            try
+            {
+                _isTextChangedFromUI = true;
+                if (TextBox != null)
+                {
+                    Text = TextBox.Text;
+                }
+            }
+            finally
+            {
+                _isTextChangedFromUI = false;
+            }
+        }
+
+        private void OnSpinnerSpin(object sender, SpinEventArgs e)
+        {
+            if (AllowSpin && !IsReadOnly)
+            {
+                var spin = !e.UsingMouseWheel;
+                spin |= ((TextBox != null) && TextBox.IsFocused);
+
+                if (spin)
+                {
+                    e.Handled = true;
+                    OnSpin(e);
+                }
+            }
+        }
+
+        private void DoDecrement()
+        {
+            if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease)
+            {
+                OnDecrement();
+            }
+        }
+
+        private void DoIncrement()
+        {
+            if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase)
+            {
+                OnIncrement();
+            }
+        }
+
+        public event EventHandler<SpinEventArgs> Spinned;
+
+        private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e)
+        {
+            if (e.Device.Captured != Spinner)
+            {
+                Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input);
+            }
+        }
+
+        /// <summary>
+        /// Defines the <see cref="ValueChanged"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<NumericUpDownValueChangedEventArgs> ValueChangedEvent =
+            RoutedEvent.Register<NumericUpDown, NumericUpDownValueChangedEventArgs>(nameof(ValueChanged), RoutingStrategies.Bubble);
+
+        /// <summary>
+        /// Raised when the <see cref="Value"/> changes.
+        /// </summary>
+        public event EventHandler<SpinEventArgs> ValueChanged
+        {
+            add { AddHandler(ValueChangedEvent, value); }
+            remove { RemoveHandler(ValueChangedEvent, value); }
+        }
+
+        private bool CommitInput()
+        {
+            return SyncTextAndValueProperties(true, Text);
+        }
+
+        /// <summary>
+        /// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
+        /// </summary>
+        /// <param name="updateValueFromText">If value should be updated from text.</param>
+        /// <param name="text">The text.</param>
+        private bool SyncTextAndValueProperties(bool updateValueFromText, string text)
+        {
+            return SyncTextAndValueProperties(updateValueFromText, text, false);
+        }
+
+        /// <summary>
+        /// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
+        /// </summary>
+        /// <param name="updateValueFromText">If value should be updated from text.</param>
+        /// <param name="text">The text.</param>
+        /// <param name="forceTextUpdate">Force text update.</param>
+        private bool SyncTextAndValueProperties(bool updateValueFromText, string text, bool forceTextUpdate)
+        {
+            if (_isSyncingTextAndValueProperties)
+                return true;
+
+            _isSyncingTextAndValueProperties = true;
+            var parsedTextIsValid = true;
+            try
+            {
+                if (updateValueFromText)
+                {
+                    if (!string.IsNullOrEmpty(text))
+                    {
+                        try
+                        {
+                            var newValue = ConvertTextToValue(text);
+                            if (!Equals(newValue, Value))
+                            {
+                                SetValueInternal(newValue);
+                            }
+                        }
+                        catch
+                        {
+                            parsedTextIsValid = false;
+                        }
+                    }
+                }
+
+                // Do not touch the ongoing text input from user.
+                if (!_isTextChangedFromUI)
+                {
+                    var keepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text);
+                    if (!keepEmpty)
+                    {
+                        var newText = ConvertValueToText();
+                        if (!Equals(Text, newText))
+                        {
+                            Text = newText;
+                        }
+                    }
+
+                    // Sync Text and textBox
+                    if (TextBox != null)
+                    {
+                        TextBox.Text = Text;
+                    }
+                }
+
+                if (_isTextChangedFromUI && !parsedTextIsValid)
+                {
+                    // Text input was made from the user and the text
+                    // repesents an invalid value. Disable the spinner in this case.
+                    if (Spinner != null)
+                    {
+                        Spinner.ValidSpinDirection = ValidSpinDirections.None;
+                    }
+                }
+                else
+                {
+                    SetValidSpinDirection();
+                }
+            }
+            finally
+            {
+                _isSyncingTextAndValueProperties = false;
+            }
+            return parsedTextIsValid;
+        }
+
+        private double ConvertTextToValueCore(string currentValueText, string text)
+        {
+            double result;
+
+            if (IsPercent(FormatString))
+            {
+                result = decimal.ToDouble(ParsePercent(text, CultureInfo));
+            }
+            else
+            {
+                // Problem while converting new text
+                if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue))
+                {
+                    var shouldThrow = true;
+
+                    // Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
+                    if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _))
+                    {
+                        // extract non-digit characters
+                        var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
+                        var textSpecialCharacters = text.Where(c => !char.IsDigit(c));
+                        // same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
+                        if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0)
+                        {
+                            foreach (var character in textSpecialCharacters)
+                            {
+                                text = text.Replace(character.ToString(), string.Empty);
+                            }
+                            // if without the special characters, parsing is good, do not throw
+                            if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue))
+                            {
+                                shouldThrow = false;
+                            }
+                        }
+                    }
+
+                    if (shouldThrow)
+                    {
+                        throw new InvalidDataException("Input string was not in a correct format.");
+                    }
+                }
+                result = outputValue;
+            }
+            return result;
+        }
+
+        private void ValidateMinMax(double value)
+        {
+            if (value < Minimum)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum));
+            }
+            else if (value > Maximum)
+            {
+                throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum));
+            }
+        }
+
+        /// <summary>
+        /// Parse percent format text
+        /// </summary>
+        /// <param name="text">Text to parse.</param>
+        /// <param name="cultureInfo">The culture info.</param>
+        private static decimal ParsePercent(string text, IFormatProvider cultureInfo)
+        {
+            var info = NumberFormatInfo.GetInstance(cultureInfo);
+            text = text.Replace(info.PercentSymbol, null);
+            var result = decimal.Parse(text, NumberStyles.Any, info);
+            result = result / 100;
+            return result;
+        }
+
+
+        private bool IsPercent(string stringToTest)
+        {
+            var PIndex = stringToTest.IndexOf("P", StringComparison.Ordinal);
+            if (PIndex >= 0)
+            {
+                //stringToTest contains a "P" between 2 "'", it's considered as text, not percent
+                var isText = stringToTest.Substring(0, PIndex).Contains("'")
+                             && stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'");
+
+                return !isText;
+            }
+            return false;
+        }
+    }
+}

+ 16 - 0
src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

@@ -0,0 +1,16 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+    public class NumericUpDownValueChangedEventArgs : RoutedEventArgs
+    {
+        public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue,  double newValue) : base(routedEvent)
+        {
+            OldValue = oldValue;
+            NewValue = newValue;
+        }
+
+        public double OldValue { get; }
+        public double NewValue { get; }
+    }
+}

+ 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>

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

@@ -44,5 +44,16 @@ namespace Avalonia.Platform
         /// Enables or disables the taskbar icon
         /// </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.
+        /// </summary>
+        Func<bool> Closing { get; set; }
     }
 }

+ 210 - 0
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@@ -0,0 +1,210 @@
+using System;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Input.Raw;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Platform
+{
+    class InProcessDragSource : IPlatformDragSource
+    {
+        private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
+        private readonly IDragDropDevice _dragDrop;
+        private readonly IInputManager _inputManager;
+        private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
+
+        private DragDropEffects _allowedEffects;
+        private IDataObject _draggedData;
+        private IInputElement _lastRoot;
+        private Point _lastPosition;
+        private StandardCursorType _lastCursorType;
+        private object _originalCursor;
+        private InputModifiers? _initialInputModifiers;
+
+        public InProcessDragSource()
+        {
+            _inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
+            _dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
+        }
+
+        public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
+        {
+            Dispatcher.UIThread.VerifyAccess();
+            if (_draggedData == null)
+            {
+                _draggedData = data;
+                _lastRoot = null;
+                _lastPosition = default(Point);
+                _allowedEffects = allowedEffects;
+
+                using (_inputManager.PreProcess.OfType<RawMouseEventArgs>().Subscribe(ProcessMouseEvents))
+                {
+                    using (_inputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(ProcessKeyEvents))
+                    {
+                        var effect = await _result.FirstAsync();
+                        return effect;
+                    }
+                }
+            }
+            return DragDropEffects.None;
+        }
+
+
+        private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
+        {
+            _lastPosition = pt;
+
+            RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
+            var tl = root.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault();
+            tl.PlatformImpl.Input(rawEvent);
+
+            var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
+            UpdateCursor(root, effect);
+            return effect;
+        }
+
+        private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers)
+        {
+            if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
+                return effect; // No need to check for the modifiers.
+            if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt))
+                return DragDropEffects.Link;
+            if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control))
+                return DragDropEffects.Copy;
+            return DragDropEffects.Move;
+        }
+
+        private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
+        {
+            if (effects.HasFlag(DragDropEffects.Copy))
+                return StandardCursorType.DragCopy;
+            if (effects.HasFlag(DragDropEffects.Move))
+                return StandardCursorType.DragMove;
+            if (effects.HasFlag(DragDropEffects.Link))
+                return StandardCursorType.DragLink;
+            return StandardCursorType.No;
+        }
+        
+        private void UpdateCursor(IInputElement root, DragDropEffects effect)
+        {
+            if (_lastRoot != root)
+            {
+                if (_lastRoot is InputElement ieLast)
+                {
+                    if (_originalCursor == AvaloniaProperty.UnsetValue)
+                        ieLast.ClearValue(InputElement.CursorProperty);
+                    else
+                        ieLast.Cursor = _originalCursor as Cursor;
+                }
+
+                if (root is InputElement ieNew)
+                {
+                    if (!ieNew.IsSet(InputElement.CursorProperty))
+                        _originalCursor = AvaloniaProperty.UnsetValue;
+                    else
+                        _originalCursor = root.Cursor;
+                }
+                else
+                    _originalCursor = null;
+
+                _lastCursorType = StandardCursorType.Arrow;
+                _lastRoot = root;
+            }
+
+            if (root is InputElement ie)
+            {
+                var ct = GetCursorForDropEffect(effect);
+                if (ct != _lastCursorType)
+                {
+                    _lastCursorType = ct;
+                    ie.Cursor = new Cursor(ct);
+                }
+            }  
+        }
+
+        private void CancelDragging()
+        {
+            if (_lastRoot != null)
+                RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
+            UpdateCursor(null, DragDropEffects.None);
+            _result.OnNext(DragDropEffects.None);
+        }
+
+        private void ProcessKeyEvents(RawKeyEventArgs e)
+        {
+            if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
+            {
+                if (_lastRoot != null)
+                    RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
+                UpdateCursor(null, DragDropEffects.None);
+                _result.OnNext(DragDropEffects.None);
+                e.Handled = true;
+            }
+            else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
+                RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
+        }
+
+        private void ProcessMouseEvents(RawMouseEventArgs e)
+        {
+            if (!_initialInputModifiers.HasValue)
+                _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
+
+            
+            void CheckDraggingAccepted(InputModifiers changedMouseButton)
+            {
+                if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
+                {
+                    var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
+                    UpdateCursor(null, DragDropEffects.None);
+                    _result.OnNext(result);
+                }
+                else
+                    CancelDragging();
+                e.Handled = true;
+            }
+            
+            switch (e.Type)
+            {
+                case RawMouseEventType.LeftButtonDown:
+                case RawMouseEventType.RightButtonDown:
+                case RawMouseEventType.MiddleButtonDown:
+                case RawMouseEventType.NonClientLeftButtonDown:
+                    CancelDragging();
+                    e.Handled = true;
+                    return;
+                case RawMouseEventType.LeaveWindow:
+                    RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position,  e.InputModifiers); break;
+                case RawMouseEventType.LeftButtonUp:
+                    CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
+                case RawMouseEventType.MiddleButtonUp:
+                    CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
+                case RawMouseEventType.RightButtonUp:
+                    CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
+                case RawMouseEventType.Move:
+                    var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
+                    if (_initialInputModifiers.Value != mods)
+                    {
+                        CancelDragging();
+                        e.Handled = true;
+                        return;
+                    }
+
+                    if (e.Root != _lastRoot)
+                    {
+                        if (_lastRoot != null)
+                            RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers);
+                        RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
+                    }
+                    else
+                        RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers);
+                    break;
+            }
+        }
+    }
+}

+ 71 - 82
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -4,6 +4,7 @@
 using System;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
@@ -31,7 +32,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
         /// </summary>
-        public static readonly StyledProperty<double> BorderThicknessProperty =
+        public static readonly StyledProperty<Thickness> BorderThicknessProperty =
             Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
@@ -57,7 +58,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="CornerRadius"/> property.
         /// </summary>
-        public static readonly StyledProperty<float> CornerRadiusProperty =
+        public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
             Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
@@ -76,17 +77,20 @@ namespace Avalonia.Controls.Presenters
         /// Defines the <see cref="Padding"/> property.
         /// </summary>
         public static readonly StyledProperty<Thickness> PaddingProperty =
-            Border.PaddingProperty.AddOwner<ContentPresenter>();
+            Decorator.PaddingProperty.AddOwner<ContentPresenter>();
 
         private IControl _child;
         private bool _createdChild;
         private IDataTemplate _dataTemplate;
+        private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
 
         /// <summary>
         /// Initializes static members of the <see cref="ContentPresenter"/> class.
         /// </summary>
         static ContentPresenter()
         {
+            AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
+            AffectsMeasure(BorderThicknessProperty);
             ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
             TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
@@ -120,7 +124,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Gets or sets the thickness of the border.
         /// </summary>
-        public double BorderThickness
+        public Thickness BorderThickness
         {
             get { return GetValue(BorderThicknessProperty); }
             set { SetValue(BorderThicknessProperty, value); }
@@ -157,7 +161,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Gets or sets the radius of the border rounded corners.
         /// </summary>
-        public float CornerRadius
+        public CornerRadius CornerRadius
         {
             get { return GetValue(CornerRadiusProperty); }
             set { SetValue(CornerRadiusProperty, value); }
@@ -221,7 +225,7 @@ namespace Avalonia.Controls.Presenters
         {
             var content = Content;
             var oldChild = Child;
-            var newChild = CreateChild();            
+            var newChild = CreateChild();
 
             // Remove the old child if we're not recycling it.
             if (oldChild != null && newChild != oldChild)
@@ -277,21 +281,7 @@ namespace Avalonia.Controls.Presenters
         /// <inheritdoc/>
         public override void Render(DrawingContext context)
         {
-            var background = Background;
-            var borderBrush = BorderBrush;
-            var borderThickness = BorderThickness;
-            var cornerRadius = CornerRadius;
-            var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
-
-            if (background != null)
-            {
-                context.FillRectangle(background, rect, cornerRadius);
-            }
-
-            if (borderBrush != null && borderThickness > 0)
-            {
-                context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
-            }
+            _borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
         }
 
         /// <summary>
@@ -344,7 +334,11 @@ namespace Avalonia.Controls.Presenters
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
         {
-            return ArrangeOverrideImpl(finalSize, new Vector());
+            finalSize = ArrangeOverrideImpl(finalSize, new Vector());
+
+            _borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
+
+            return finalSize;
         }
 
         /// <summary>
@@ -372,74 +366,69 @@ namespace Avalonia.Controls.Presenters
 
         internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
         {
-            if (Child != null)
-            {
-                var padding = Padding;
-                var borderThickness = BorderThickness;
-                var horizontalContentAlignment = HorizontalContentAlignment;
-                var verticalContentAlignment = VerticalContentAlignment;
-                var useLayoutRounding = UseLayoutRounding;
-                var availableSizeMinusMargins = new Size(
-                    Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
-                    Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
-                var size = availableSizeMinusMargins;
-                var scale = GetLayoutScale();
-                var originX = offset.X + padding.Left + borderThickness;
-                var originY = offset.Y + padding.Top + borderThickness;
-
-                if (horizontalContentAlignment != HorizontalAlignment.Stretch)
-                {
-                    size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
-                }
+            if (Child == null) return finalSize;
 
-                if (verticalContentAlignment != VerticalAlignment.Stretch)
-                {
-                    size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
-                }
-
-                size = LayoutHelper.ApplyLayoutConstraints(Child, size);
+            var padding = Padding;
+            var borderThickness = BorderThickness;
+            var horizontalContentAlignment = HorizontalContentAlignment;
+            var verticalContentAlignment = VerticalContentAlignment;
+            var useLayoutRounding = UseLayoutRounding;
+            var availableSizeMinusMargins = new Size(
+                Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness.Left - borderThickness.Right),
+                Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness.Top - borderThickness.Bottom));
+            var size = availableSizeMinusMargins;
+            var scale = GetLayoutScale();
+            var originX = offset.X + padding.Left + borderThickness.Left;
+            var originY = offset.Y + padding.Top + borderThickness.Top;
+
+            if (horizontalContentAlignment != HorizontalAlignment.Stretch)
+            {
+                size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
+            }
 
-                if (useLayoutRounding)
-                {
-                    size = new Size(
-                        Math.Ceiling(size.Width * scale) / scale,
-                        Math.Ceiling(size.Height * scale) / scale);
-                    availableSizeMinusMargins = new Size(
-                        Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
-                        Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
-                }
+            if (verticalContentAlignment != VerticalAlignment.Stretch)
+            {
+                size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
+            }
 
-                switch (horizontalContentAlignment)
-                {
-                    case HorizontalAlignment.Center:
-                    case HorizontalAlignment.Stretch:
-                        originX += (availableSizeMinusMargins.Width - size.Width) / 2;
-                        break;
-                    case HorizontalAlignment.Right:
-                        originX += availableSizeMinusMargins.Width - size.Width;
-                        break;
-                }
+            if (useLayoutRounding)
+            {
+                size = new Size(
+                    Math.Ceiling(size.Width * scale) / scale,
+                    Math.Ceiling(size.Height * scale) / scale);
+                availableSizeMinusMargins = new Size(
+                    Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
+                    Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
+            }
 
-                switch (verticalContentAlignment)
-                {
-                    case VerticalAlignment.Center:
-                    case VerticalAlignment.Stretch:
-                        originY += (availableSizeMinusMargins.Height - size.Height) / 2;
-                        break;
-                    case VerticalAlignment.Bottom:
-                        originY += availableSizeMinusMargins.Height - size.Height;
-                        break;
-                }
+            switch (horizontalContentAlignment)
+            {
+                case HorizontalAlignment.Center:
+                    originX += (availableSizeMinusMargins.Width - size.Width) / 2;
+                    break;
+                case HorizontalAlignment.Right:
+                    originX += availableSizeMinusMargins.Width - size.Width;
+                    break;
+            }
 
-                if (useLayoutRounding)
-                {
-                    originX = Math.Floor(originX * scale) / scale;
-                    originY = Math.Floor(originY * scale) / scale;
-                }
+            switch (verticalContentAlignment)
+            {
+                case VerticalAlignment.Center:
+                    originY += (availableSizeMinusMargins.Height - size.Height) / 2;
+                    break;
+                case VerticalAlignment.Bottom:
+                    originY += availableSizeMinusMargins.Height - size.Height;
+                    break;
+            }
 
-                Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
+            if (useLayoutRounding)
+            {
+                originX = Math.Floor(originX * scale) / scale;
+                originY = Math.Floor(originY * scale) / scale;
             }
 
+            Child.Arrange(new Rect(originX, originY, Math.Max(0, size.Width), Math.Max(0, size.Height)));
+
             return finalSize;
         }
 

+ 1 - 1
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -224,7 +224,7 @@ namespace Avalonia.Controls.Presenters
                 CanVerticallyScroll ? Math.Max(Child.DesiredSize.Height, finalSize.Height) : finalSize.Height);
             ArrangeOverrideImpl(size, -Offset);
             Viewport = finalSize;
-            Extent = Child.Bounds.Size;
+            Extent = Child.Bounds.Size.Inflate(Child.Margin);
             return finalSize;
         }
 

+ 62 - 4
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>
@@ -215,7 +231,17 @@ namespace Avalonia.Controls.Primitives
             {
                 var window = _topLevel as Window;
                 if (window != null)
+                {
                     window.Deactivated += WindowDeactivated;
+                }
+                else
+                {
+                    var parentPopuproot = _topLevel as PopupRoot;
+                    if (parentPopuproot != null && parentPopuproot.Parent != null)
+                    {
+                        ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
+                    }
+                }
                 _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
                 _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
             }
@@ -224,6 +250,11 @@ namespace Avalonia.Controls.Primitives
 
             _popupRoot.Show();
 
+            if (ObeyScreenEdges)
+            {
+                _popupRoot.SnapInsideScreenEdges();
+            }
+
             _ignoreIsOpenChanged = true;
             IsOpen = true;
             _ignoreIsOpenChanged = false;
@@ -244,6 +275,14 @@ namespace Avalonia.Controls.Primitives
                     var window = _topLevel as Window;
                     if (window != null)
                         window.Deactivated -= WindowDeactivated;
+                    else
+                    {
+                        var parentPopuproot = _topLevel as PopupRoot;
+                        if (parentPopuproot != null && parentPopuproot.Parent != null)
+                        {
+                            ((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
+                        }
+                    }
                     _nonClientListener?.Dispose();
                     _nonClientListener = null;
                 }
@@ -328,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)
@@ -381,9 +422,7 @@ namespace Avalonia.Controls.Primitives
         {
             if (!StaysOpen)
             {
-                var root = ((IVisual)e.Source).GetVisualRoot();
-
-                if (root != this.PopupRoot)
+                if (!IsChildOrThis((IVisual)e.Source))
                 {
                     Close();
                     e.Handled = true;
@@ -391,6 +430,17 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        private bool IsChildOrThis(IVisual child)
+        {
+            IVisual root = child.GetVisualRoot();
+            while (root is PopupRoot)
+            {
+                if (root == PopupRoot) return true;
+                root = ((PopupRoot)root).Parent.GetVisualRoot();
+            }
+            return false;
+        }
+
         private void WindowDeactivated(object sender, EventArgs e)
         {
             if (!StaysOpen)
@@ -398,5 +448,13 @@ namespace Avalonia.Controls.Primitives
                 Close();
             }
         }
+
+        private void ParentClosed(object sender, EventArgs e)
+        {
+            if (!StaysOpen)
+            {
+                Close();
+            }
+        }
     }
 }

+ 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)
         {

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

@@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
         /// </summary>
-        public static readonly StyledProperty<double> BorderThicknessProperty =
+        public static readonly StyledProperty<Thickness> BorderThicknessProperty =
             Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
 
         /// <summary>
@@ -132,7 +132,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets or sets the thickness of the control's border.
         /// </summary>
-        public double BorderThickness
+        public Thickness BorderThickness
         {
             get { return GetValue(BorderThicknessProperty); }
             set { SetValue(BorderThicknessProperty, value); }
@@ -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);
         }

+ 3 - 23
src/Avalonia.Controls/ProgressBar.cs

@@ -26,12 +26,13 @@ namespace Avalonia.Controls
 
         static ProgressBar()
         {
+            PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
+            PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
+
             ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
 
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
                 (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
-            OrientationProperty.Changed.AddClassHandler<ProgressBar>(
-                (p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
         }
 
         public bool IsIndeterminate
@@ -59,7 +60,6 @@ namespace Avalonia.Controls
             _indicator = e.NameScope.Get<Border>("PART_Indicator");
 
             UpdateIndicator(Bounds.Size);
-            UpdateOrientation(Orientation);
             UpdateIsIndeterminate(IsIndeterminate);
         }
 
@@ -86,26 +86,6 @@ namespace Avalonia.Controls
             }
         }
 
-        private void UpdateOrientation(Orientation orientation)
-        {
-            if (orientation == Orientation.Horizontal)
-            {
-                MinHeight = 14;
-                MinWidth = 200;
-
-                _indicator.HorizontalAlignment = HorizontalAlignment.Left;
-                _indicator.VerticalAlignment = VerticalAlignment.Stretch;
-            }
-            else
-            {
-                MinHeight = 200;
-                MinWidth = 14;
-
-                _indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
-                _indicator.VerticalAlignment = VerticalAlignment.Bottom;
-            }
-        }
-
         private void UpdateIsIndeterminate(bool isIndeterminate)
         {
             if (isIndeterminate)

+ 0 - 1
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@@ -5,7 +5,6 @@ using System.Reflection;
 using System.Runtime.CompilerServices;
 using Avalonia.Metadata;
 
-[assembly: AssemblyTitle("Avalonia.Controls")]
 [assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
 [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
 

+ 2 - 2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Controls.Remote
     {
         private readonly IAvaloniaRemoteTransportConnection _connection;
         private FrameMessage _lastFrame;
-        private WritableBitmap _bitmap;
+        private WriteableBitmap _bitmap;
         public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
         {
             _connection = connection;
@@ -62,7 +62,7 @@ namespace Avalonia.Controls.Remote
                 var fmt = (PixelFormat) _lastFrame.Format;
                 if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width ||
                     _bitmap.PixelHeight != _lastFrame.Height)
-                    _bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt);
+                    _bitmap = new WriteableBitmap(_lastFrame.Width, _lastFrame.Height, fmt);
                 using (var l = _bitmap.Lock())
                 {
                     var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width;

+ 27 - 2
src/Avalonia.Controls/RepeatButton.cs

@@ -6,17 +6,32 @@ namespace Avalonia.Controls
 {
     public class RepeatButton : Button
     {
+        /// <summary>
+        /// Defines the <see cref="Interval"/> property.
+        /// </summary>
+        public static readonly StyledProperty<int> IntervalProperty =
+            AvaloniaProperty.Register<Button, int>(nameof(Interval), 100);
+
         /// <summary>
         /// Defines the <see cref="Delay"/> property.
         /// </summary>
         public static readonly StyledProperty<int> DelayProperty =
-            AvaloniaProperty.Register<Button, int>(nameof(Delay), 100);
+            AvaloniaProperty.Register<Button, int>(nameof(Delay), 300);
 
         private DispatcherTimer _repeatTimer;
 
         /// <summary>
         /// Gets or sets the amount of time, in milliseconds, of repeating clicks.
         /// </summary>
+        public int Interval
+        {
+            get { return GetValue(IntervalProperty); }
+            set { SetValue(IntervalProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the amount of time, in milliseconds, to wait before repeating begins.
+        /// </summary>
         public int Delay
         {
             get { return GetValue(DelayProperty); }
@@ -28,7 +43,7 @@ namespace Avalonia.Controls
             if (_repeatTimer == null)
             {
                 _repeatTimer = new DispatcherTimer();
-                _repeatTimer.Tick += (o, e) => OnClick();
+                _repeatTimer.Tick += RepeatTimerOnTick;
             }
 
             if (_repeatTimer.IsEnabled) return;
@@ -37,6 +52,16 @@ namespace Avalonia.Controls
             _repeatTimer.Start();
         }
 
+        private void RepeatTimerOnTick(object sender, EventArgs e)
+        {
+            var interval = TimeSpan.FromMilliseconds(Interval);
+            if (_repeatTimer.Interval != interval)
+            {
+                _repeatTimer.Interval = interval;
+            }
+            OnClick();
+        }
+
         private void StopTimer()
         {
             _repeatTimer?.Stop();

+ 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)));
         }
     }
 }

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

@@ -39,7 +39,7 @@ namespace Avalonia.Controls
             return currMaxScreen;
         }
         
-        public Screen SceenFromPoint(Point point)
+        public Screen ScreenFromPoint(Point point)
         {
             return All.FirstOrDefault(x=>x.Bounds.Contains(point));        
         }

+ 174 - 0
src/Avalonia.Controls/Spinner.cs

@@ -0,0 +1,174 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Represents spin directions that are valid.
+    /// </summary>
+    [Flags]
+    public enum ValidSpinDirections
+    {
+        /// <summary>
+        /// Can not increase nor decrease.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// Can increase.
+        /// </summary>
+        Increase = 1,
+
+        /// <summary>
+        /// Can decrease.
+        /// </summary>
+        Decrease = 2
+    }
+
+    /// <summary>
+    /// Represents spin directions that could be initiated by the end-user.
+    /// </summary>
+    public enum SpinDirection
+    {
+        /// <summary>
+        /// Represents a spin initiated by the end-user in order to Increase a value.
+        /// </summary>
+        Increase = 0,
+
+        /// <summary>
+        /// Represents a spin initiated by the end-user in order to Decrease a value.
+        /// </summary>
+        Decrease = 1
+    }
+
+    /// <summary>
+    /// Provides data for the Spinner.Spin event.
+    /// </summary>
+    public class SpinEventArgs : RoutedEventArgs
+    {
+        /// <summary>
+        /// Gets the SpinDirection for the spin that has been initiated by the end-user.
+        /// </summary>
+        public SpinDirection Direction { get; }
+
+        /// <summary>
+        /// Get or set whheter the spin event originated from a mouse wheel event.
+        /// </summary>
+        public bool UsingMouseWheel{ get; }
+
+        /// <summary>
+        /// Initializes a new instance of the SpinEventArgs class.
+        /// </summary>
+        /// <param name="direction">Spin direction.</param>
+        public SpinEventArgs(SpinDirection direction)
+        {
+            Direction = direction;
+        }
+
+        public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction)
+            : base(routedEvent)
+        {
+            Direction = direction;
+        }
+
+        public SpinEventArgs(SpinDirection direction, bool usingMouseWheel)
+        {
+            Direction = direction;
+            UsingMouseWheel = usingMouseWheel;
+        }
+
+        public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction, bool usingMouseWheel)
+            : base(routedEvent)
+        {
+            Direction = direction;
+            UsingMouseWheel = usingMouseWheel;
+        }
+    }
+
+    /// <summary>
+    /// Base class for controls that represents controls that can spin.
+    /// </summary>
+    public abstract class Spinner : ContentControl
+    {
+        /// <summary>
+        /// Defines the <see cref="ValidSpinDirection"/> property.
+        /// </summary>
+        public static readonly StyledProperty<ValidSpinDirections> ValidSpinDirectionProperty =
+            AvaloniaProperty.Register<Spinner, ValidSpinDirections>(nameof(ValidSpinDirection),
+                ValidSpinDirections.Increase | ValidSpinDirections.Decrease);
+
+        /// <summary>
+        /// Defines the <see cref="Spin"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<SpinEventArgs> SpinEvent =
+            RoutedEvent.Register<Spinner, SpinEventArgs>(nameof(Spin), RoutingStrategies.Bubble);
+
+        /// <summary>
+        /// Initializes static members of the <see cref="Spinner"/> class.
+        /// </summary>
+        static Spinner()
+        {
+            ValidSpinDirectionProperty.Changed.Subscribe(OnValidSpinDirectionPropertyChanged);
+        }
+
+        /// <summary>
+        /// Occurs when spinning is initiated by the end-user.
+        /// </summary>
+        public event EventHandler<SpinEventArgs> Spin
+        {
+            add { AddHandler(SpinEvent, value); }
+            remove { RemoveHandler(SpinEvent, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets <see cref="ValidSpinDirections"/> allowed for this control.
+        /// </summary>
+        public ValidSpinDirections ValidSpinDirection
+        {
+            get { return GetValue(ValidSpinDirectionProperty); }
+            set { SetValue(ValidSpinDirectionProperty, value); }
+        }
+
+        /// <summary>
+        /// Called when valid spin direction changed.
+        /// </summary>
+        /// <param name="oldValue">The old value.</param>
+        /// <param name="newValue">The new value.</param>
+        protected virtual void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
+        {
+        }
+
+        /// <summary>
+        /// Raises the OnSpin event when spinning is initiated by the end-user.
+        /// </summary>
+        /// <param name="e">Spin event args.</param>
+        protected virtual void OnSpin(SpinEventArgs e)
+        {
+            var valid = e.Direction == SpinDirection.Increase
+                ? ValidSpinDirections.Increase
+                : ValidSpinDirections.Decrease;
+
+            //Only raise the event if spin is allowed.
+            if ((ValidSpinDirection & valid) == valid)
+            {
+                RaiseEvent(e);
+            }
+        }
+
+        /// <summary>
+        /// Called when the <see cref="ValidSpinDirection"/> property value changed.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private static void OnValidSpinDirectionPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.Sender is Spinner spinner)
+            {
+                var oldValue = (ValidSpinDirections)e.OldValue;
+                var newValue = (ValidSpinDirections)e.NewValue;
+                spinner.OnValidSpinDirectionChanged(oldValue, newValue);
+            }
+        }
+    }
+}

+ 2 - 2
src/Avalonia.Controls/TextBlock.cs

@@ -120,6 +120,7 @@ namespace Avalonia.Controls
                 .Subscribe(_ =>
                 {
                     InvalidateFormattedText();
+                    InvalidateMeasure();
                 });
         }
 
@@ -370,8 +371,6 @@ namespace Avalonia.Controls
                 _constraint = _formattedText.Constraint;
                 _formattedText = null;
             }
-
-            InvalidateMeasure();
         }
 
         /// <summary>
@@ -402,6 +401,7 @@ namespace Avalonia.Controls
         {
             base.OnAttachedToLogicalTree(e);
             InvalidateFormattedText();
+            InvalidateMeasure();
         }
     }
 }

+ 32 - 5
src/Avalonia.Controls/TextBox.cs

@@ -85,6 +85,7 @@ namespace Avalonia.Controls
         private int _selectionEnd;
         private TextPresenter _presenter;
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
+        private bool _isUndoingRedoing;
         private bool _ignoreTextChanges;
         private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
 
@@ -198,7 +199,11 @@ namespace Avalonia.Controls
                 if (!_ignoreTextChanges)
                 {
                     CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
-                    SetAndRaise(TextProperty, ref _text, value);
+
+                    if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
+                    {
+                        _undoRedoHelper.Clear();
+                    }
                 }
             }
         }
@@ -256,6 +261,8 @@ namespace Avalonia.Controls
             {
                 _presenter?.ShowCaret();
             }
+
+            e.Handled = true;
         }
 
         protected override void OnLostFocus(RoutedEventArgs e)
@@ -268,7 +275,11 @@ namespace Avalonia.Controls
 
         protected override void OnTextInput(TextInputEventArgs e)
         {
-            HandleTextInput(e.Text);
+            if (!e.Handled)
+            {
+                HandleTextInput(e.Text);
+                e.Handled = true;
+            }
         }
 
         private void HandleTextInput(string input)
@@ -364,14 +375,30 @@ namespace Avalonia.Controls
                 case Key.Z:
                     if (modifiers == InputModifiers.Control)
                     {
-                        _undoRedoHelper.Undo();
+                        try
+                        {
+                            _isUndoingRedoing = true;
+                            _undoRedoHelper.Undo();
+                        }
+                        finally
+                        {
+                            _isUndoingRedoing = false;
+                        }
                         handled = true;
                     }
                     break;
                 case Key.Y:
                     if (modifiers == InputModifiers.Control)
                     {
-                        _undoRedoHelper.Redo();
+                        try
+                        {
+                            _isUndoingRedoing = true;
+                            _undoRedoHelper.Redo();
+                        }
+                        finally
+                        {
+                            _isUndoingRedoing = false;
+                        }
                         handled = true;
                     }
                     break;
@@ -788,7 +815,7 @@ namespace Avalonia.Controls
             int pos = 0;
             int i;
 
-            for (i = 0; i < lines.Count; ++i)
+            for (i = 0; i < lines.Count - 1; ++i)
             {
                 var line = lines[i];
                 pos += line.Length;

+ 4 - 0
src/Avalonia.Controls/UserControl.cs

@@ -6,6 +6,9 @@ using Avalonia.Styling;
 
 namespace Avalonia.Controls
 {
+    /// <summary>
+    /// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
+    /// </summary>
     public class UserControl : ContentControl, IStyleable, INameScope
     {
         private readonly NameScope _nameScope = new NameScope();
@@ -24,6 +27,7 @@ namespace Avalonia.Controls
             remove { _nameScope.Unregistered -= value; }
         }
 
+        /// <inheritdoc/>
         Type IStyleable.StyleKey => typeof(ContentControl);
 
         /// <inheritdoc/>

+ 279 - 0
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@@ -0,0 +1,279 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.Media;
+
+namespace Avalonia.Controls.Utils
+{
+    internal class BorderRenderHelper
+    {
+        private bool _useComplexRendering;
+        private StreamGeometry _backgroundGeometryCache;
+        private StreamGeometry _borderGeometryCache;
+
+        public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
+        {
+            if (borderThickness.IsUniform && cornerRadius.IsUniform)
+            {
+                _backgroundGeometryCache = null;
+                _borderGeometryCache = null;
+                _useComplexRendering = false;
+            }
+            else
+            {
+                _useComplexRendering = true;
+
+                var boundRect = new Rect(finalSize);
+                var innerRect = boundRect.Deflate(borderThickness);
+                var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
+
+                StreamGeometry backgroundGeometry = null;
+
+                if (innerRect.Width != 0 && innerRect.Height != 0)
+                {
+                    backgroundGeometry = new StreamGeometry();
+
+                    using (var ctx = backgroundGeometry.Open())
+                    {
+                        CreateGeometry(ctx, innerRect, innerCoordinates);
+                    }
+
+                    _backgroundGeometryCache = backgroundGeometry;
+                }
+                else
+                {
+                    _backgroundGeometryCache = null;
+                }
+
+                if (boundRect.Width != 0 && innerRect.Height != 0)
+                {
+                    var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
+                    var borderGeometry = new StreamGeometry();
+
+                    using (var ctx = borderGeometry.Open())
+                    {
+                        CreateGeometry(ctx, boundRect, outerCoordinates);
+
+                        if (backgroundGeometry != null)
+                        {
+                            CreateGeometry(ctx, innerRect, innerCoordinates);
+                        }
+                    }
+
+                    _borderGeometryCache = borderGeometry;
+                }
+                else
+                {
+                    _borderGeometryCache = null;
+                }
+            }
+        }
+
+        public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
+        {
+            if (_useComplexRendering)
+            {
+                var backgroundGeometry = _backgroundGeometryCache;
+                if (backgroundGeometry != null)
+                {
+                    context.DrawGeometry(background, null, backgroundGeometry);
+                }
+
+                var borderGeometry = _borderGeometryCache;
+                if (borderGeometry != null)
+                {
+                    context.DrawGeometry(borderBrush, null, borderGeometry);
+                }
+            }
+            else
+            {
+                var borderThickness = borders.Left;
+                var cornerRadius = (float)radii.TopLeft;
+                var rect = new Rect(size);
+
+                if (background != null)
+                {
+                    context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
+                }
+
+                if (borderBrush != null && borderThickness > 0)
+                {
+                    context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
+                }
+            }
+        }
+
+        private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderCoordinates borderCoordinates)
+        {
+            var topLeft = new Point(borderCoordinates.LeftTop, 0);
+            var topRight = new Point(boundRect.Width - borderCoordinates.RightTop, 0);
+            var rightTop = new Point(boundRect.Width, borderCoordinates.TopRight);
+            var rightBottom = new Point(boundRect.Width, boundRect.Height - borderCoordinates.BottomRight);
+            var bottomRight = new Point(boundRect.Width - borderCoordinates.RightBottom, boundRect.Height);
+            var bottomLeft = new Point(borderCoordinates.LeftBottom, boundRect.Height);
+            var leftBottom = new Point(0, boundRect.Height - borderCoordinates.BottomLeft);
+            var leftTop = new Point(0, borderCoordinates.TopLeft);
+
+
+            if (topLeft.X > topRight.X)
+            {
+                var scaledX = borderCoordinates.LeftTop / (borderCoordinates.LeftTop + borderCoordinates.RightTop) * boundRect.Width;
+                topLeft = new Point(scaledX, topLeft.Y);
+                topRight = new Point(scaledX, topRight.Y);
+            }
+
+            if (rightTop.Y > rightBottom.Y)
+            {
+                var scaledY = borderCoordinates.TopRight / (borderCoordinates.TopRight + borderCoordinates.BottomRight) * boundRect.Height;
+                rightTop = new Point(rightTop.X, scaledY);
+                rightBottom = new Point(rightBottom.X, scaledY);
+            }
+
+            if (bottomRight.X < bottomLeft.X)
+            {
+                var scaledX = borderCoordinates.LeftBottom / (borderCoordinates.LeftBottom + borderCoordinates.RightBottom) * boundRect.Width;
+                bottomRight = new Point(scaledX, bottomRight.Y);
+                bottomLeft = new Point(scaledX, bottomLeft.Y);
+            }
+
+            if (leftBottom.Y < leftTop.Y)
+            {
+                var scaledY = borderCoordinates.TopLeft / (borderCoordinates.TopLeft + borderCoordinates.BottomLeft) * boundRect.Height;
+                leftBottom = new Point(leftBottom.X, scaledY);
+                leftTop = new Point(leftTop.X, scaledY);
+            }
+
+            var offset = new Vector(boundRect.TopLeft.X, boundRect.TopLeft.Y);
+            topLeft += offset;
+            topRight += offset;
+            rightTop += offset;
+            rightBottom += offset;
+            bottomRight += offset;
+            bottomLeft += offset;
+            leftBottom += offset;
+            leftTop += offset;
+
+            context.BeginFigure(topLeft, true);
+
+            //Top
+            context.LineTo(topRight);
+
+            //TopRight corner
+            var radiusX = boundRect.TopRight.X - topRight.X;
+            var radiusY = rightTop.Y - boundRect.TopRight.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(rightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            //Right
+            context.LineTo(rightBottom);
+
+            //BottomRight corner
+            radiusX = boundRect.BottomRight.X - bottomRight.X;
+            radiusY = boundRect.BottomRight.Y - rightBottom.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            //Bottom
+            context.LineTo(bottomLeft);
+
+            //BottomLeft corner
+            radiusX = bottomLeft.X - boundRect.BottomLeft.X;
+            radiusY = boundRect.BottomLeft.Y - leftBottom.Y;
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            //Left
+            context.LineTo(leftTop);
+
+            //TopLeft corner
+            radiusX = topLeft.X - boundRect.TopLeft.X;
+            radiusY = leftTop.Y - boundRect.TopLeft.Y;
+
+            if (radiusX != 0 || radiusY != 0)
+            {
+                context.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
+            }
+
+            context.EndFigure(true);
+        }
+
+        private struct BorderCoordinates
+        {
+            internal BorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness, bool isOuter)
+            {
+                var left = 0.5 * borderThickness.Left;
+                var top = 0.5 * borderThickness.Top;
+                var right = 0.5 * borderThickness.Right;
+                var bottom = 0.5 * borderThickness.Bottom;
+
+                if (isOuter)
+                {
+                    if (cornerRadius.TopLeft == 0)
+                    {
+                        LeftTop = TopLeft = 0.0;
+                    }
+                    else
+                    {
+                        LeftTop = cornerRadius.TopLeft + left;
+                        TopLeft = cornerRadius.TopLeft + top;
+                    }
+                    if (cornerRadius.TopRight == 0)
+                    {
+                        TopRight = RightTop = 0;
+                    }
+                    else
+                    {
+                        TopRight = cornerRadius.TopRight + top;
+                        RightTop = cornerRadius.TopRight + right;
+                    }
+                    if (cornerRadius.BottomRight == 0)
+                    {
+                        RightBottom = BottomRight = 0;
+                    }
+                    else
+                    {
+                        RightBottom = cornerRadius.BottomRight + right;
+                        BottomRight = cornerRadius.BottomRight + bottom;
+                    }
+                    if (cornerRadius.BottomLeft == 0)
+                    {
+                        BottomLeft = LeftBottom = 0;
+                    }
+                    else
+                    {
+                        BottomLeft = cornerRadius.BottomLeft + bottom;
+                        LeftBottom = cornerRadius.BottomLeft + left;
+                    }
+                }
+                else
+                {
+                    LeftTop = Math.Max(0, cornerRadius.TopLeft - left);
+                    TopLeft = Math.Max(0, cornerRadius.TopLeft - top);
+                    TopRight = Math.Max(0, cornerRadius.TopRight - top);
+                    RightTop = Math.Max(0, cornerRadius.TopRight - right);
+                    RightBottom = Math.Max(0, cornerRadius.BottomRight - right);
+                    BottomRight = Math.Max(0, cornerRadius.BottomRight - bottom);
+                    BottomLeft = Math.Max(0, cornerRadius.BottomLeft - bottom);
+                    LeftBottom = Math.Max(0, cornerRadius.BottomLeft - left);
+                }
+            }
+
+            internal readonly double LeftTop;
+            internal readonly double TopLeft;
+            internal readonly double TopRight;
+            internal readonly double RightTop;
+            internal readonly double RightBottom;
+            internal readonly double BottomRight;
+            internal readonly double BottomLeft;
+            internal readonly double LeftBottom;
+        }
+
+    }
+}

+ 700 - 0
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -0,0 +1,700 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Avalonia.Layout;
+using JetBrains.Annotations;
+
+namespace Avalonia.Controls.Utils
+{
+    /// <summary>
+    /// Contains algorithms that can help to measure and arrange a Grid.
+    /// </summary>
+    internal class GridLayout
+    {
+        /// <summary>
+        /// Initialize a new <see cref="GridLayout"/> instance from the column definitions.
+        /// The instance doesn't care about whether the definitions are rows or columns.
+        /// It will not calculate the column or row differently.
+        /// </summary>
+        internal GridLayout([NotNull] ColumnDefinitions columns)
+        {
+            if (columns == null) throw new ArgumentNullException(nameof(columns));
+            _conventions = columns.Count == 0
+                ? new List<LengthConvention> { new LengthConvention() }
+                : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
+        }
+
+        /// <summary>
+        /// Initialize a new <see cref="GridLayout"/> instance from the row definitions.
+        /// The instance doesn't care about whether the definitions are rows or columns.
+        /// It will not calculate the column or row differently.
+        /// </summary>
+        internal GridLayout([NotNull] RowDefinitions rows)
+        {
+            if (rows == null) throw new ArgumentNullException(nameof(rows));
+            _conventions = rows.Count == 0
+                ? new List<LengthConvention> { new LengthConvention() }
+                : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
+        }
+
+        /// <summary>
+        /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same.
+        /// </summary>
+        private const double LayoutTolerance = 1.0 / 256.0;
+
+        /// <summary>
+        /// Gets all the length conventions that come from column/row definitions.
+        /// These conventions provide cell limitations, such as the expected pixel length, the min/max pixel length and the * count.
+        /// </summary>
+        [NotNull]
+        private readonly List<LengthConvention> _conventions;
+
+        /// <summary>
+        /// Gets all the length conventions that come from the grid children.
+        /// </summary>
+        [NotNull]
+        private readonly List<AdditionalLengthConvention> _additionalConventions =
+            new List<AdditionalLengthConvention>();
+
+        /// <summary>
+        /// Appending these elements into the convention list helps lay them out according to their desired sizes.
+        /// <para/>
+        /// Some elements are not only in a single grid cell, they have one or more column/row spans,
+        /// and these elements may affect the grid layout especially the measuring procedure.<para/>
+        /// Append these elements into the convention list can help to layout them correctly through
+        /// their desired size. Only a small subset of children need to be measured before layout starts
+        /// and they will be called via the<paramref name="getDesiredLength"/> callback.
+        /// </summary>
+        /// <typeparam name="T">The grid children type.</typeparam>
+        /// <param name="source">
+        /// Contains the safe column/row index and its span.
+        /// Notice that we will not verify whether the range is in the column/row count,
+        /// so you should get the safe column/row info first.
+        /// </param>
+        /// <param name="getDesiredLength">
+        /// This callback will be called if the <see cref="GridLayout"/> thinks that a child should be
+        /// measured first. Usually, these are the children that have the * or Auto length.
+        /// </param>
+        internal void AppendMeasureConventions<T>([NotNull] IDictionary<T, (int index, int span)> source,
+            [NotNull] Func<T, double> getDesiredLength)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength));
+
+            // M1/7. Find all the Auto and * length columns/rows. (M1/7 means the 1st procedure of measurement.)
+            // Only these columns/rows' layout can be affected by the child desired size.
+            // 
+            // Find all columns/rows that have Auto or * length. We'll measure the children in advance.
+            // Only these kind of columns/rows will affect the Grid layout.
+            // Please note:
+            // - If the column / row has Auto length, the Grid.DesiredSize and the column width
+            //   will be affected by the child's desired size.
+            // - If the column / row has* length, the Grid.DesiredSize will be affected by the
+            //   child's desired size but the column width not.
+
+            //               +-----------------------------------------------------------+
+            //               |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            //               +-----------------------------------------------------------+
+            // _conventions: | min | max |     |           | min |     |  min max  | max |
+            // _additionalC:                   |<-   desired   ->|     |< desired >|
+            // _additionalC:       |< desired >|           |<-        desired          ->|
+
+            // 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。
+            // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。
+            // 请注意:
+            // - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度和 Grid 本身的 DesiredSize;
+            // - 而对于 * 长度,只有 Grid.DesiredSize 会受到子元素布局影响,而行列长度不会受影响。
+
+            // Find all the Auto and * length columns/rows.
+            var found = new Dictionary<T, (int index, int span)>();
+            for (var i = 0; i < _conventions.Count; i++)
+            {
+                var index = i;
+                var convention = _conventions[index];
+                if (convention.Length.IsAuto || convention.Length.IsStar)
+                {
+                    foreach (var pair in source.Where(x =>
+                        x.Value.index <= index && index < x.Value.index + x.Value.span))
+                    {
+                        found[pair.Key] = pair.Value;
+                    }
+                }
+            }
+
+            // Append these layout into the additional convention list.
+            foreach (var pair in found)
+            {
+                var t = pair.Key;
+                var (index, span) = pair.Value;
+                var desiredLength = getDesiredLength(t);
+                if (Math.Abs(desiredLength) > LayoutTolerance)
+                {
+                    _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength));
+                }
+            }
+        }
+
+        /// <summary>
+        /// Run measure procedure according to the <paramref name="containerLength"/> and gets the <see cref="MeasureResult"/>.
+        /// </summary>
+        /// <param name="containerLength">
+        /// The container length. Usually, it is the constraint of the <see cref="Layoutable.MeasureOverride"/> method.
+        /// </param>
+        /// <returns>
+        /// The measured result that containing the desired size and all the column/row lengths.
+        /// </returns>
+        [NotNull, Pure]
+        internal MeasureResult Measure(double containerLength)
+        {
+            // Prepare all the variables that this method needs to use.
+            var conventions = _conventions.Select(x => x.Clone()).ToList();
+            var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
+            var aggregatedLength = 0.0;
+            double starUnitLength;
+
+            // M2/7. Aggregate all the pixel lengths. Then we can get the remaining length by `containerLength - aggregatedLength`.
+            // We mark the aggregated length as "fix" because we can completely determine their values. Same as below.
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            //                   |#fix#|           |#fix#|
+            //
+            // 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。
+            // 我们会将所有能够确定下长度的行列标记为 fix。下同。
+            // 请注意:
+            // - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。
+
+            aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
+
+            // M3/7. Fix all the * lengths that have reached the minimum.
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // | min | max |     |           | min |     |  min max  | max |
+            //                   | fix |     |#fix#| fix |
+
+            var shouldTestStarMin = true;
+            while (shouldTestStarMin)
+            {
+                // Calculate the unit * length to estimate the length of each column/row that has * length.
+                // Under this estimated length, check if there is a minimum value that has a length less than its constraint.
+                // If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value.
+                //
+                // 计算单位 * 的长度,以便预估出每一个 * 行列的长度。
+                // 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。
+                // 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。
+                var @fixed = false;
+                starUnitLength = (containerLength - aggregatedLength) / starCount;
+                foreach (var convention in conventions.Where(x => x.Length.IsStar))
+                {
+                    var (star, min) = (convention.Length.Value, convention.MinLength);
+                    var starLength = star * starUnitLength;
+                    if (starLength < min)
+                    {
+                        convention.Fix(min);
+                        starLength = min;
+                        aggregatedLength += starLength;
+                        starCount -= star;
+                        @fixed = true;
+                        break;
+                    }
+                }
+
+                shouldTestStarMin = @fixed;
+            }
+
+            // M4/7. Determine the absolute pixel size of all columns/rows that have an Auto length.
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // | min | max |     |           | min |     |  min max  | max |
+            //       |#fix#|     | fix |#fix#| fix | fix |
+
+            var shouldTestAuto = true;
+            while (shouldTestAuto)
+            {
+                var @fixed = false;
+                starUnitLength = (containerLength - aggregatedLength) / starCount;
+                for (var i = 0; i < conventions.Count; i++)
+                {
+                    var convention = conventions[i];
+                    if (!convention.Length.IsAuto)
+                    {
+                        continue;
+                    }
+
+                    var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength);
+                    convention.Fix(more);
+                    aggregatedLength += more;
+                    @fixed = true;
+                    break;
+                }
+
+                shouldTestAuto = @fixed;
+            }
+
+            // M5/7. Expand the stars according to the additional conventions (usually the child desired length).
+            // We can't fix this kind of length, so we just mark them as desired (des).
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // | min | max |     |           | min |     |  min max  | max |
+            // |#des#| fix |#des#| fix | fix | fix | fix |   #des#   |#des#|
+
+            var desiredStarMin = AggregateAdditionalConventionsForStars(conventions);
+            aggregatedLength += desiredStarMin;
+
+            // M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength.
+            // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // | min | max |     |           | min |     |  min max  | max |
+            // |#des#| fix |#des#| fix | fix | fix | fix |   #des#   |#des#|
+            // Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure.
+            // 
+            // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des)
+            // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des
+
+            var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength;
+            var greedyDesiredLength = aggregatedLength;
+
+            // M7/7. Expand all the rest stars. These stars have no conventions or only have
+            // max value they can be expanded from zero to constraint.
+            //
+            // +-----------------------------------------------------------+
+            // |  *  |  A  |  *  |  P  |  A  |  *  |  P  |     *     |  *  |
+            // +-----------------------------------------------------------+
+            // | min | max |     |           | min |     |  min max  | max |
+            // |#fix#| fix |#fix#| fix | fix | fix | fix |   #fix#   |#fix#|
+            // Note: This table will be stored as the final result into the MeasureResult.
+
+            var dynamicConvention = ExpandStars(conventions, containerLength);
+            Clip(dynamicConvention, containerLength);
+
+            // Returns the measuring result.
+            return new MeasureResult(containerLength, desiredLength, greedyDesiredLength,
+                conventions, dynamicConvention);
+        }
+
+        /// <summary>
+        /// Run arrange procedure according to the <paramref name="measure"/> and gets the <see cref="ArrangeResult"/>.
+        /// </summary>
+        /// <param name="finalLength">
+        /// The container length. Usually, it is the finalSize of the <see cref="Layoutable.ArrangeOverride"/> method.
+        /// </param>
+        /// <param name="measure">
+        /// The result that the measuring procedure returns. If it is null, a new measure procedure will run.
+        /// </param>
+        /// <returns>
+        /// The measured result that containing the desired size and all the column/row length.
+        /// </returns>
+        [NotNull, Pure]
+        public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure)
+        {
+            measure = measure ?? Measure(finalLength);
+
+            // If the arrange final length does not equal to the measure length, we should measure again.
+            if (finalLength - measure.ContainerLength > LayoutTolerance)
+            {
+                // If the final length is larger, we will rerun the whole measure.
+                measure = Measure(finalLength);
+            }
+            else if (finalLength - measure.ContainerLength < -LayoutTolerance)
+            {
+                // If the final length is smaller, we measure the M6/6 procedure only.
+                var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength);
+                measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,
+                    measure.LeanLengthList, dynamicConvention);
+            }
+
+            return new ArrangeResult(measure.LengthList);
+        }
+
+        /// <summary>
+        /// Use the <see cref="_additionalConventions"/> to calculate the fixed length of the Auto column/row.
+        /// </summary>
+        /// <param name="conventions">The convention list that all the * with minimum length are fixed.</param>
+        /// <param name="index">The column/row index that should be fixed.</param>
+        /// <param name="starUnitLength">The unit * length for the current rest length.</param>
+        /// <returns>The final length of the Auto length column/row.</returns>
+        [Pure]
+        private double ApplyAdditionalConventionsForAuto(IReadOnlyList<LengthConvention> conventions,
+            int index, double starUnitLength)
+        {
+            // 1. Calculate all the * length with starUnitLength.
+            // 2. Exclude all the fixed length and all the * length.
+            // 3. Compare the rest of the desired length and the convention.
+            // +-----------------+
+            // |  *  |  A  |  *  |
+            // +-----------------+
+            // | exl |     | exl |
+            // |< desired >|
+            //       |< desired >|
+
+            var more = 0.0;
+            foreach (var additional in _additionalConventions)
+            {
+                // If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length.
+                if (index == additional.Index + additional.Span - 1)
+                {
+                    var min = Enumerable.Range(additional.Index, additional.Span)
+                        .Select(x =>
+                        {
+                            var c = conventions[x];
+                            if (c.Length.IsAbsolute) return c.Length.Value;
+                            if (c.Length.IsStar) return c.Length.Value * starUnitLength;
+                            return 0.0;
+                        }).Sum();
+                    more = Math.Max(additional.Min - min, more);
+                }
+            }
+
+            return Math.Min(conventions[index].MaxLength, more);
+        }
+
+        /// <summary>
+        /// Calculate the total desired length of all the * length.
+        /// Bug Warning:
+        /// - The behavior of this method is undefined! Different UI Frameworks have different behaviors.
+        /// - We ignore all the span columns/rows and just take single cells into consideration.
+        /// </summary>
+        /// <param name="conventions">All the conventions that have almost been fixed except the rest *.</param>
+        /// <returns>The total desired length of all the * length.</returns>
+        [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private double AggregateAdditionalConventionsForStars(
+            IReadOnlyList<LengthConvention> conventions)
+        {
+            // 1. Determine all one-span column's desired widths or row's desired heights.
+            // 2. Order the multi-span conventions by its last index
+            //    (Notice that the sorted data is much smaller than the source.)
+            // 3. Determine each multi-span last index by calculating the maximun desired size.
+
+            // Before we determine the behavior of this method, we just aggregate the one-span * columns.
+
+            var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
+
+            // Prepare a lengthList variable indicating the fixed length of each column/row.
+            var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList();
+            foreach (var group in _additionalConventions
+                .Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar)
+                .ToLookup(x => x.Index))
+            {
+                lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min));
+            }
+
+            // Now the lengthList is fixed by every one-span columns/rows.
+            // Then we should determine the multi-span column's/row's length.
+            foreach (var group in _additionalConventions
+                .Where(x => x.Span > 1)
+                .ToLookup(x => x.Index + x.Span - 1)
+                // Order the multi-span columns/rows by last index.
+                .OrderBy(x => x.Key))
+            {
+                var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r]));
+                lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0);
+            }
+
+            return lengthList.Sum() - fixedLength;
+        }
+
+        /// <summary>
+        /// This method implements the last procedure (M7/7) of measure.
+        /// It expands all the * length to the fixed length according to the <paramref name="constraint"/>.
+        /// </summary>
+        /// <param name="conventions">All the conventions that have almost been fixed except the remaining *.</param>
+        /// <param name="constraint">The container length.</param>
+        /// <returns>The final pixel length list.</returns>
+        [Pure]
+        private static List<double> ExpandStars(IEnumerable<LengthConvention> conventions, double constraint)
+        {
+            // Initial.
+            var dynamicConvention = conventions.Select(x => x.Clone()).ToList();
+            constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
+            var starUnitLength = 0.0;
+
+            // M6/6.
+            if (constraint >= 0)
+            {
+                var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
+
+                var shouldTestStarMax = true;
+                while (shouldTestStarMax)
+                {
+                    var @fixed = false;
+                    starUnitLength = constraint / starCount;
+                    foreach (var convention in dynamicConvention.Where(x =>
+                        x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength)))
+                    {
+                        var (star, max) = (convention.Length.Value, convention.MaxLength);
+                        var starLength = star * starUnitLength;
+                        if (starLength > max)
+                        {
+                            convention.Fix(max);
+                            starLength = max;
+                            constraint -= starLength;
+                            starCount -= star;
+                            @fixed = true;
+                            break;
+                        }
+                    }
+
+                    shouldTestStarMax = @fixed;
+                }
+            }
+
+            Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto));
+
+            var starUnit = starUnitLength;
+            var result = dynamicConvention.Select(x =>
+            {
+                if (x.Length.IsStar)
+                {
+                    return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value;
+                }
+
+                return x.Length.Value;
+            }).ToList();
+
+            return result;
+        }
+
+        /// <summary>
+        /// If the container length is not infinity. It may be not enough to contain all the columns/rows.
+        /// We should clip the columns/rows that have been out of the container bounds.
+        /// Note: This method may change the items value of <paramref name="lengthList"/>.
+        /// </summary>
+        /// <param name="lengthList">A list of all the column widths and row heights with a fixed pixel length</param>
+        /// <param name="constraint">the container length. It can be positive infinity.</param>
+        private static void Clip([NotNull] IList<double> lengthList, double constraint)
+        {
+            if (double.IsInfinity(constraint))
+            {
+                return;
+            }
+
+            var measureLength = 0.0;
+            for (var i = 0; i < lengthList.Count; i++)
+            {
+                var length = lengthList[i];
+                if (constraint - measureLength > length)
+                {
+                    measureLength += length;
+                }
+                else
+                {
+                    lengthList[i] = constraint - measureLength;
+                    measureLength = constraint;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Contains the convention of each column/row.
+        /// This is mostly the same as <see cref="RowDefinition"/> or <see cref="ColumnDefinition"/>.
+        /// We use this because we can treat the column and the row the same.
+        /// </summary>
+        [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
+        internal class LengthConvention : ICloneable
+        {
+            /// <summary>
+            /// Initialize a new instance of <see cref="LengthConvention"/>.
+            /// </summary>
+            public LengthConvention()
+            {
+                Length = new GridLength(1.0, GridUnitType.Star);
+                MinLength = 0.0;
+                MaxLength = double.PositiveInfinity;
+            }
+
+            /// <summary>
+            /// Initialize a new instance of <see cref="LengthConvention"/>.
+            /// </summary>
+            public LengthConvention(GridLength length, double minLength, double maxLength)
+            {
+                Length = length;
+                MinLength = minLength;
+                MaxLength = maxLength;
+                if (length.IsAbsolute)
+                {
+                    _isFixed = true;
+                }
+            }
+
+            /// <summary>
+            /// Gets the <see cref="GridLength"/> of a column or a row.
+            /// </summary>
+            internal GridLength Length { get; private set; }
+
+            /// <summary>
+            /// Gets the minimum convention for a column or a row.
+            /// </summary>
+            internal double MinLength { get; }
+
+            /// <summary>
+            /// Gets the maximum convention for a column or a row.
+            /// </summary>
+            internal double MaxLength { get; }
+
+            /// <summary>
+            /// Fix the <see cref="LengthConvention"/>.
+            /// If all columns/rows are fixed, we can get the size of all columns/rows in pixels.
+            /// </summary>
+            /// <param name="pixel">
+            /// The pixel length that should be used to fix the convention.
+            /// </param>
+            /// <exception cref="InvalidOperationException">
+            /// If the convention is pixel length, this exception will throw.
+            /// </exception>
+            public void Fix(double pixel)
+            {
+                if (_isFixed)
+                {
+                    throw new InvalidOperationException("Cannot fix the length convention if it is fixed.");
+                }
+
+                Length = new GridLength(pixel);
+                _isFixed = true;
+            }
+
+            /// <summary>
+            /// Gets a value that indicates whether this convention is fixed.
+            /// </summary>
+            private bool _isFixed;
+
+            /// <summary>
+            /// Helps the debugger to display the intermediate column/row calculation result.
+            /// </summary>
+            private string DebuggerDisplay =>
+                $"{(_isFixed ? Length.Value.ToString(CultureInfo.InvariantCulture) : (Length.GridUnitType == GridUnitType.Auto ? "Auto" : $"{Length.Value}*"))}, ∈[{MinLength}, {MaxLength}]";
+
+            /// <inheritdoc />
+            object ICloneable.Clone() => Clone();
+
+            /// <summary>
+            /// Get a deep copy of this convention list.
+            /// We need this because we want to store some intermediate states.
+            /// </summary>
+            internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength);
+        }
+
+        /// <summary>
+        /// Contains the convention that comes from the grid children.
+        /// Some children span multiple columns or rows, so even a simple column/row can have multiple conventions.
+        /// </summary>
+        [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
+        internal struct AdditionalLengthConvention
+        {
+            /// <summary>
+            /// Initialize a new instance of <see cref="AdditionalLengthConvention"/>.
+            /// </summary>
+            public AdditionalLengthConvention(int index, int span, double min)
+            {
+                Index = index;
+                Span = span;
+                Min = min;
+            }
+
+            /// <summary>
+            /// Gets the start index of this additional convention.
+            /// </summary>
+            public int Index { get; }
+
+            /// <summary>
+            /// Gets the span of this additional convention.
+            /// </summary>
+            public int Span { get; }
+
+            /// <summary>
+            /// Gets the minimum length of this additional convention.
+            /// This value is usually provided by the child's desired length.
+            /// </summary>
+            public double Min { get; }
+
+            /// <summary>
+            /// Helps the debugger to display the intermediate column/row calculation result.
+            /// </summary>
+            private string DebuggerDisplay =>
+                $"{{{string.Join(",", Enumerable.Range(Index, Span))}}}, ∈[{Min},∞)";
+        }
+
+        /// <summary>
+        /// Stores the result of the measuring procedure.
+        /// This result can be used to measure children and assign the desired size.
+        /// Passing this result to <see cref="Arrange"/> can reduce calculation.
+        /// </summary>
+        [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
+        internal class MeasureResult
+        {
+            /// <summary>
+            /// Initialize a new instance of <see cref="MeasureResult"/>.
+            /// </summary>
+            internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength,
+                IReadOnlyList<LengthConvention> leanConventions, IReadOnlyList<double> expandedConventions)
+            {
+                ContainerLength = containerLength;
+                DesiredLength = desiredLength;
+                GreedyDesiredLength = greedyDesiredLength;
+                LeanLengthList = leanConventions;
+                LengthList = expandedConventions;
+            }
+
+            /// <summary>
+            /// Gets the container length for this result.
+            /// This property will be used by <see cref="Arrange"/> to determine whether to measure again or not.
+            /// </summary>
+            public double ContainerLength { get; }
+
+            /// <summary>
+            /// Gets the desired length of this result.
+            /// Just return this value as the desired size in <see cref="Layoutable.MeasureOverride"/>.
+            /// </summary>
+            public double DesiredLength { get; }
+
+            /// <summary>
+            /// Gets the desired length if the container has infinite length.
+            /// </summary>
+            public double GreedyDesiredLength { get; }
+
+            /// <summary>
+            /// Contains the column/row calculation intermediate result.
+            /// This value is used by <see cref="Arrange"/> for reducing repeat calculation.
+            /// </summary>
+            public IReadOnlyList<LengthConvention> LeanLengthList { get; }
+
+            /// <summary>
+            /// Gets the length list for each column/row.
+            /// </summary>
+            public IReadOnlyList<double> LengthList { get; }
+        }
+
+        /// <summary>
+        /// Stores the result of the measuring procedure.
+        /// This result can be used to arrange children and assign the render size.
+        /// </summary>
+        [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
+        internal class ArrangeResult
+        {
+            /// <summary>
+            /// Initialize a new instance of <see cref="ArrangeResult"/>.
+            /// </summary>
+            internal ArrangeResult(IReadOnlyList<double> lengthList)
+            {
+                LengthList = lengthList;
+            }
+
+            /// <summary>
+            /// Gets the length list for each column/row.
+            /// </summary>
+            public IReadOnlyList<double> LengthList { get; }
+        }
+    }
+}

+ 64 - 0
src/Avalonia.Controls/Utils/ISelectionAdapter.cs

@@ -0,0 +1,64 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Collections;
+using Avalonia.Interactivity;
+using Avalonia.Input;
+
+namespace Avalonia.Controls.Utils
+{
+    /// <summary>
+    /// Defines an item collection, selection members, and key handling for the
+    /// selection adapter contained in the drop-down portion of an
+    /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+    /// </summary>
+    public interface ISelectionAdapter
+    {
+        /// <summary>
+        /// Gets or sets the selected item.
+        /// </summary>
+        /// <value>The currently selected item.</value>
+        object SelectedItem { get; set; }
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.Utils.ISelectionAdapter.SelectedItem" />
+        /// property value changes.
+        /// </summary>
+        event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+        
+        /// <summary>
+        /// Gets or sets a collection that is used to generate content for the
+        /// selection adapter.
+        /// </summary>
+        /// <value>The collection that is used to generate content for the
+        /// selection adapter.</value>
+        IEnumerable Items { get; set; }
+
+        /// <summary>
+        /// Occurs when a selected item is not cancelled and is committed as the
+        /// selected item.
+        /// </summary>
+        event EventHandler<RoutedEventArgs> Commit;
+
+        /// <summary>
+        /// Occurs when a selection has been canceled.
+        /// </summary>
+        event EventHandler<RoutedEventArgs> Cancel;
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
+        /// when a key is pressed while the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
+        /// that contains data about the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
+        void HandleKeyDown(KeyEventArgs e);
+    }
+
+}

+ 342 - 0
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@@ -0,0 +1,342 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using System.Collections;
+using System.Diagnostics;
+
+namespace Avalonia.Controls.Utils
+{
+    /// <summary>
+    /// Represents the selection adapter contained in the drop-down portion of
+    /// an <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
+    /// </summary>
+    public class SelectingItemsControlSelectionAdapter : ISelectionAdapter
+    {
+        /// <summary>
+        /// The SelectingItemsControl instance.
+        /// </summary>
+        private SelectingItemsControl _selector;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the selection change event 
+        /// should not be fired.
+        /// </summary>
+        private bool IgnoringSelectionChanged { get; set; }
+
+        /// <summary>
+        /// Gets or sets the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        /// <value>The underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.</value>
+        public SelectingItemsControl SelectorControl
+        {
+            get { return _selector; }
+
+            set
+            {
+                if (_selector != null)
+                {
+                    _selector.SelectionChanged -= OnSelectionChanged;
+                    _selector.PointerReleased -= OnSelectorPointerReleased;
+                }
+
+                _selector = value;
+
+                if (_selector != null)
+                {
+                    _selector.SelectionChanged += OnSelectionChanged;
+                    _selector.PointerReleased += OnSelectorPointerReleased;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.SelectedItem" />
+        /// property value changes.
+        /// </summary>
+        public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+
+        /// <summary>
+        /// Occurs when an item is selected and is committed to the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Commit;
+
+        /// <summary>
+        /// Occurs when a selection is canceled before it is committed.
+        /// </summary>
+        public event EventHandler<RoutedEventArgs> Cancel;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />
+        /// class.
+        /// </summary>
+        public SelectingItemsControlSelectionAdapter()
+        {
+
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapterr" />
+        /// class with the specified
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        /// <param name="selector">The
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" /> control
+        /// to wrap as a
+        /// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />.</param>
+        public SelectingItemsControlSelectionAdapter(SelectingItemsControl selector)
+        {
+            SelectorControl = selector;
+        }
+
+        /// <summary>
+        /// Gets or sets the selected item of the selection adapter.
+        /// </summary>
+        /// <value>The selected item of the underlying selection adapter.</value>
+        public object SelectedItem
+        {
+            get
+            {
+                return SelectorControl?.SelectedItem;
+            }
+
+            set
+            {
+                IgnoringSelectionChanged = true;
+                if (SelectorControl != null)
+                {
+                    SelectorControl.SelectedItem = value;
+                }
+
+                // Attempt to reset the scroll viewer's position
+                if (value == null)
+                {
+                    ResetScrollViewer();
+                }
+
+                IgnoringSelectionChanged = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a collection that is used to generate the content of
+        /// the selection adapter.
+        /// </summary>
+        /// <value>The collection used to generate content for the selection
+        /// adapter.</value>
+        public IEnumerable Items
+        {
+            get
+            {
+                return SelectorControl?.Items;
+            }
+            set
+            {
+                if (SelectorControl != null)
+                {
+                    SelectorControl.Items = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// If the control contains a ScrollViewer, this will reset the viewer 
+        /// to be scrolled to the top.
+        /// </summary>
+        private void ResetScrollViewer()
+        {
+            if (SelectorControl != null)
+            {
+                ScrollViewer sv = SelectorControl.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
+                if (sv != null)
+                {
+                    sv.Offset = new Vector(0, 0);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handles the mouse left button up event on the selector control.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnSelectorPointerReleased(object sender, PointerReleasedEventArgs e)
+        {
+            if (e.MouseButton == MouseButton.Left)
+            {
+                OnCommit();
+            }
+        }
+
+        /// <summary>
+        /// Handles the SelectionChanged event on the SelectingItemsControl control.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The selection changed event data.</param>
+        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (IgnoringSelectionChanged)
+            {
+                return;
+            }
+
+            SelectionChanged?.Invoke(sender, e);
+        }
+
+        /// <summary>
+        /// Increments the
+        /// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
+        /// property of the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        protected void SelectedIndexIncrement()
+        {
+            if (SelectorControl != null)
+            {
+                SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.ItemCount ? -1 : SelectorControl.SelectedIndex + 1;
+            }
+        }
+
+        /// <summary>
+        /// Decrements the
+        /// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
+        /// property of the underlying
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
+        /// control.
+        /// </summary>
+        protected void SelectedIndexDecrement()
+        {
+            if (SelectorControl != null)
+            {
+                int index = SelectorControl.SelectedIndex;
+                if (index >= 0)
+                {
+                    SelectorControl.SelectedIndex--;
+                }
+                else if (index == -1)
+                {
+                    SelectorControl.SelectedIndex = SelectorControl.ItemCount - 1;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Provides handling for the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
+        /// when a key is pressed while the drop-down portion of the
+        /// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
+        /// </summary>
+        /// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
+        /// that contains data about the
+        /// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
+        public void HandleKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Enter:
+                    OnCommit();
+                    e.Handled = true;
+                    break;
+
+                case Key.Up:
+                    SelectedIndexDecrement();
+                    e.Handled = true;
+                    break;
+
+                case Key.Down:
+                    if ((e.Modifiers & InputModifiers.Alt) == InputModifiers.None)
+                    {
+                        SelectedIndexIncrement();
+                        e.Handled = true;
+                    }
+                    break;
+
+                case Key.Escape:
+                    OnCancel();
+                    e.Handled = true;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Commit" />
+        /// event.
+        /// </summary>
+        protected virtual void OnCommit()
+        {
+            OnCommit(this, new RoutedEventArgs());
+        }
+
+        /// <summary>
+        /// Fires the Commit event.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnCommit(object sender, RoutedEventArgs e)
+        {
+            Commit?.Invoke(sender, e);
+
+            AfterAdapterAction();
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Cancel" />
+        /// event.
+        /// </summary>
+        protected virtual void OnCancel()
+        {
+            OnCancel(this, new RoutedEventArgs());
+        }
+
+        /// <summary>
+        /// Fires the Cancel event.
+        /// </summary>
+        /// <param name="sender">The source object.</param>
+        /// <param name="e">The event data.</param>
+        private void OnCancel(object sender, RoutedEventArgs e)
+        {
+            Cancel?.Invoke(sender, e);
+
+            AfterAdapterAction();
+        }
+
+        /// <summary>
+        /// Change the selection after the actions are complete.
+        /// </summary>
+        private void AfterAdapterAction()
+        {
+            IgnoringSelectionChanged = true;
+            if (SelectorControl != null)
+            {
+                SelectorControl.SelectedItem = null;
+                SelectorControl.SelectedIndex = -1;
+            }
+            IgnoringSelectionChanged = false;
+        }
+    }
+}

+ 7 - 1
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@@ -59,7 +59,7 @@ namespace Avalonia.Controls.Utils
 
         public void UpdateLastState()
         {
-            _states.Last.Value = _host.UndoRedoState;
+            UpdateLastState(_host.UndoRedoState);
         }
 
         public void DiscardRedo()
@@ -91,6 +91,12 @@ namespace Avalonia.Controls.Utils
             }
         }
 
+        public void Clear()
+        {
+            _states.Clear();
+            _currentNode = null;
+        }
+
         bool WeakTimer.IWeakTimerSubscriber.Tick()
         {
             Snapshot();

+ 95 - 6
src/Avalonia.Controls/Window.cs

@@ -13,6 +13,7 @@ using Avalonia.Styling;
 using System.Collections.Generic;
 using System.Linq;
 using JetBrains.Annotations;
+using System.ComponentModel;
 
 namespace Avalonia.Controls
 {
@@ -85,9 +86,22 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<WindowIcon> IconProperty =
             AvaloniaProperty.Register<Window, WindowIcon>(nameof(Icon));
 
+        /// <summary>
+        /// Defines the <see cref="WindowStartupLocation"/> proeprty.
+        /// </summary>
+        public static readonly DirectProperty<Window, WindowStartupLocation> WindowStartupLocationProperty =
+            AvaloniaProperty.RegisterDirect<Window, WindowStartupLocation>(
+                nameof(WindowStartupLocation),
+                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;
+        private WindowStartupLocation _windowStartupLoction;
 
         /// <summary>
         /// Initializes static members of the <see cref="Window"/> class.
@@ -102,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>
@@ -119,6 +135,7 @@ namespace Avalonia.Controls
         public Window(IWindowImpl impl)
             : base(impl)
         {
+            impl.Closing = HandleClosing;
             _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
             Screens = new Screens(PlatformImpl?.Screen);
         }
@@ -196,6 +213,17 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Enables or disables resizing of the window.
+        /// Note that if <see cref="HasSystemDecorations"/> is set to False then this property
+        /// has no effect and should be treated as a recommendation for the user setting HasSystemDecorations.
+        /// </summary>
+        public bool CanResize
+        {
+            get { return GetValue(CanResizeProperty); }
+            set { SetValue(CanResizeProperty, value); }
+        }
+
         /// <summary>
         /// Gets or sets the icon of the window.
         /// </summary>
@@ -205,26 +233,38 @@ namespace Avalonia.Controls
             set { SetValue(IconProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the startup location of the window.
+        /// </summary>
+        public WindowStartupLocation WindowStartupLocation
+        {
+            get { return _windowStartupLoction; }
+            set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); }
+        }
+
         /// <inheritdoc/>
         Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
 
         /// <inheritdoc/>
         Type IStyleable.StyleKey => typeof(Window);
 
+        /// <summary>
+        /// Fired before a window is closed.
+        /// </summary>
+        public event EventHandler<CancelEventArgs> Closing;
+
         /// <summary>
         /// Closes the window.
         /// </summary>
         public void Close()
         {
-            s_windows.Remove(this);
-            PlatformImpl?.Dispose();
-            IsVisible = false;
+            Close(false);
         }
 
         protected override void HandleApplicationExiting()
         {
             base.HandleApplicationExiting();
-            Close();
+            Close(true);
         }
 
         /// <summary>
@@ -239,7 +279,35 @@ namespace Avalonia.Controls
         public void Close(object dialogResult)
         {
             _dialogResult = dialogResult;
-            Close();
+            Close(false);
+        }
+
+        internal void Close(bool ignoreCancel)
+        {
+            var cancelClosing = false;
+            try
+            {
+                cancelClosing = HandleClosing();
+            }
+            finally
+            {
+                if (ignoreCancel || !cancelClosing)
+                {
+                    s_windows.Remove(this);
+                    PlatformImpl?.Dispose();
+                    IsVisible = false;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
+        /// </summary>
+        protected virtual bool HandleClosing()
+        {
+            var args = new CancelEventArgs();
+            Closing?.Invoke(this, args);
+            return args.Cancel;
         }
 
         /// <summary>
@@ -274,6 +342,7 @@ namespace Avalonia.Controls
             s_windows.Add(this);
 
             EnsureInitialized();
+            SetWindowStartupLocation();
             IsVisible = true;
             LayoutManager.Instance.ExecuteInitialLayoutPass(this);
 
@@ -314,6 +383,7 @@ namespace Avalonia.Controls
             s_windows.Add(this);
 
             EnsureInitialized();
+            SetWindowStartupLocation();
             IsVisible = true;
             LayoutManager.Instance.ExecuteInitialLayoutPass(this);
 
@@ -337,7 +407,7 @@ namespace Avalonia.Controls
                         modal?.Dispose();
                         SetIsEnabled(affectedWindows, true);
                         activated?.Activate();
-                        result.SetResult((TResult)_dialogResult);
+                        result.SetResult((TResult)(_dialogResult ?? default(TResult)));
                     });
 
                 return result.Task;
@@ -352,6 +422,25 @@ namespace Avalonia.Controls
             }
         }
 
+        void SetWindowStartupLocation()
+        {
+            if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
+            {
+                var screen = Screens.ScreenFromPoint(Bounds.Position);
+
+                if (screen != null)
+                    Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
+            }
+            else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
+            {
+                if (Owner != null)
+                {
+                    var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
+                    Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
+                }
+            }
+        }
+
         /// <inheritdoc/>
         void INameScope.Register(string name, object element)
         {

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

@@ -29,14 +29,29 @@ namespace Avalonia.Controls
         public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
             AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
 
+        /// <summary>
+        /// Defines the <see cref="Owner"/> property.
+        /// </summary>
+        public static readonly DirectProperty<WindowBase, WindowBase> OwnerProperty =
+            AvaloniaProperty.RegisterDirect<WindowBase, WindowBase>(
+                nameof(Owner),
+                o => o.Owner,
+                (o, v) => o.Owner = v);
+
         private bool _hasExecutedInitialLayoutPass;
         private bool _isActive;
         private bool _ignoreVisibilityChange;
+        private WindowBase _owner;
 
         static WindowBase()
         {
             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)
@@ -100,6 +115,15 @@ namespace Avalonia.Controls
             private set;
         }
 
+        /// <summary>
+        /// Gets or sets the owner of the window.
+        /// </summary>
+        public WindowBase Owner
+        {
+            get { return _owner; }
+            set { SetAndRaise(OwnerProperty, ref _owner, value); }
+        }
+
         /// <summary>
         /// Activates the window.
         /// </summary>

+ 23 - 0
src/Avalonia.Controls/WindowStartupLocation.cs

@@ -0,0 +1,23 @@
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Determines the startup location of the window.
+    /// </summary>
+    public enum WindowStartupLocation
+    {
+        /// <summary>
+        /// The startup location is defined by the Position property.
+        /// </summary>
+        Manual,
+
+        /// <summary>
+        /// The startup location is the center of the screen.
+        /// </summary>
+        CenterScreen,
+
+        /// <summary>
+        /// The startup location is the center of the owner window. If the owner window is not specified, the startup location will be <see cref="Manual"/>.
+        /// </summary>
+        CenterOwner
+    }
+}

+ 5 - 3
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)
                 {
@@ -54,7 +56,7 @@ namespace Avalonia.DesignerSupport
                             }
                         };
                 }
-                if (loaded is Application)
+                else if (loaded is Application)
                     control = new TextBlock {Text = "Application can't be previewed in design view"};
                 else
                     control = (Control) loaded;
@@ -73,4 +75,4 @@ namespace Avalonia.DesignerSupport
             return window;
         }
     }
-}
+}

Some files were not shown because too many files changed in this diff