浏览代码

Merge branch 'master' into refactor/controltemplate-binding-priority

Steven Kirk 3 年之前
父节点
当前提交
2679dab332
共有 100 个文件被更改,包括 1430 次插入1456 次删除
  1. 4 5
      Avalonia.sln
  2. 1 0
      Directory.Build.props
  3. 6 3
      azure-pipelines.yml
  4. 0 16
      build/AndroidWorkarounds.props
  5. 1 0
      build/CoreLibraries.props
  6. 1 1
      build/SharedVersion.props
  7. 2 1
      build/System.Drawing.Common.props
  8. 7 4
      dirs.proj
  9. 1 0
      global.json
  10. 3 2
      nukebuild/Build.cs
  11. 9 14
      readme.md
  12. 0 19
      samples/ControlCatalog.Android/Assets/AboutAssets.txt
  13. 32 151
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  14. 2 3
      samples/ControlCatalog.Android/MainActivity.cs
  15. 3 4
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  16. 0 30
      samples/ControlCatalog.Android/Properties/AssemblyInfo.cs
  17. 0 101
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  18. 1 1
      samples/ControlCatalog.Android/Resources/values/styles.xml
  19. 1 1
      samples/ControlCatalog.Android/SplashActivity.cs
  20. 14 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  21. 7 0
      samples/ControlCatalog.NetCore/rd.xml
  22. 1 0
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  23. 5 2
      samples/ControlCatalog/MainView.xaml
  24. 0 45
      samples/ControlCatalog/Pages/ButtonPage.xaml
  25. 224 0
      samples/ControlCatalog/Pages/ButtonsPage.xaml
  26. 2 2
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  27. 23 0
      samples/ControlCatalog/Pages/ClipboardPage.xaml
  28. 77 0
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  29. 89 71
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  30. 2 0
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  31. 7 1
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  32. 1 3
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  33. 1 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  34. 3 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  35. 23 14
      samples/ControlCatalog/Pages/ScreenPage.cs
  36. 1 1
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  37. 21 0
      samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
  38. 7 0
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  39. 3 0
      samples/RenderDemo/MainWindow.xaml
  40. 8 5
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  41. 7 0
      samples/RenderDemo/Pages/FormattedTextPage.axaml
  42. 60 0
      samples/RenderDemo/Pages/FormattedTextPage.axaml.cs
  43. 5 2
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  44. 3 1
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  45. 4 5
      src/Android/Avalonia.Android/AndroidPlatform.cs
  46. 0 14
      src/Android/Avalonia.Android/AppBuilder.cs
  47. 10 7
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  48. 28 8
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  49. 11 0
      src/Android/Avalonia.Android/AvaloniaViewModel.cs
  50. 0 50
      src/Android/Avalonia.Android/Resources/AboutResources.txt
  51. 0 6
      src/Android/Avalonia.Android/Resources/Values/Strings.xml
  52. 2 2
      src/Android/Avalonia.Android/SoftKeyboardListener.cs
  53. 0 19
      src/Android/Avalonia.AndroidTestApplication/Assets/AboutAssets.txt
  54. 28 144
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  55. 2 0
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  56. 2 4
      src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
  57. 0 30
      src/Android/Avalonia.AndroidTestApplication/Properties/AssemblyInfo.cs
  58. 0 79
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  59. 0 11
      src/Android/Avalonia.AndroidTestApplication/app.config
  60. 9 24
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  61. 1 1
      src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs
  62. 6 1
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  63. 5 1
      src/Avalonia.Controls/ApiCompatBaseline.txt
  64. 20 15
      src/Avalonia.Controls/AppBuilderBase.cs
  65. 2 3
      src/Avalonia.Controls/Application.cs
  66. 13 13
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  67. 3 3
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  68. 1 1
      src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs
  69. 154 140
      src/Avalonia.Controls/AutoCompleteBox.cs
  70. 1 0
      src/Avalonia.Controls/Avalonia.Controls.csproj
  71. 6 6
      src/Avalonia.Controls/Border.cs
  72. 17 17
      src/Avalonia.Controls/Button.cs
  73. 7 7
      src/Avalonia.Controls/ButtonSpinner.cs
  74. 45 31
      src/Avalonia.Controls/Calendar/Calendar.cs
  75. 3 3
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  76. 45 45
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  77. 1 1
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  78. 3 3
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  79. 46 54
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  80. 2 0
      src/Avalonia.Controls/Calendar/DateTimeHelper.cs
  81. 2 2
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  82. 1 1
      src/Avalonia.Controls/Canvas.cs
  83. 0 2
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  84. 1 3
      src/Avalonia.Controls/Chrome/TitleBar.cs
  85. 42 23
      src/Avalonia.Controls/ComboBox.cs
  86. 7 7
      src/Avalonia.Controls/ContentControl.cs
  87. 6 8
      src/Avalonia.Controls/ContextMenu.cs
  88. 0 2
      src/Avalonia.Controls/ContextRequestedEventArgs.cs
  89. 19 4
      src/Avalonia.Controls/Control.cs
  90. 29 6
      src/Avalonia.Controls/ControlExtensions.cs
  91. 1 2
      src/Avalonia.Controls/Converters/CornerRadiusFilterConverter.cs
  92. 2 2
      src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
  93. 1 1
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
  94. 2 2
      src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs
  95. 15 14
      src/Avalonia.Controls/DataValidationErrors.cs
  96. 42 32
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  97. 55 49
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  98. 16 12
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  99. 3 3
      src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs
  100. 41 28
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs

+ 4 - 5
Avalonia.sln

@@ -77,7 +77,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{7CF9
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Android", "src\Android\Avalonia.Android\Avalonia.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.AndroidTestApplication", "src\Android\Avalonia.AndroidTestApplication\Avalonia.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.AndroidTestApplication", "src\Android\Avalonia.AndroidTestApplication\Avalonia.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}"
 EndProject
@@ -95,7 +95,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog", "samples\C
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Desktop", "samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj", "{2B888490-D14A-4BCA-AB4B-48676FA93C9B}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{52F55355-D120-42AC-8116-8410A7D602FA}"
 EndProject
@@ -107,7 +107,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderDemo", "samples\RenderDemo\RenderDemo.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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}"
 EndProject
@@ -115,7 +115,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
 	ProjectSection(SolutionItems) = preProject
-		build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
 		build\ApiDiff.props = build\ApiDiff.props
 		build\Base.props = build\Base.props
 		build\Binding.props = build\Binding.props
@@ -1106,7 +1105,7 @@ Global
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.AppStore|iPhone.Build.0 = AppStore|iPhone
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
-		{57E0455D-D565-44BB-B069-EE1AA20F8337}.Debug|Any CPU.ActiveCfg = Debug|iPhone
+		{57E0455D-D565-44BB-B069-EE1AA20F8337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.Debug|iPhone.ActiveCfg = Debug|iPhone
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.Debug|iPhone.Build.0 = Debug|iPhone
 		{57E0455D-D565-44BB-B069-EE1AA20F8337}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator

+ 1 - 0
Directory.Build.props

@@ -4,5 +4,6 @@
       <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
       <!-- https://github.com/dotnet/msbuild/issues/2661 -->
       <AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
+      <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
   </PropertyGroup>
 </Project>

+ 6 - 3
azure-pipelines.yml

@@ -1,6 +1,3 @@
-variables:
-    MSBuildEnableWorkloadResolver: 'false'
-
 jobs:
 
 - job: GetPRNumber
@@ -146,6 +143,12 @@ jobs:
     inputs:
       version: 6.0.100
 
+  - task: CmdLine@2
+    displayName: 'Install Workloads'
+    inputs:
+      script: |
+       dotnet workload install android
+
   - task: CmdLine@2
     displayName: 'Install Nuke'
     inputs:

+ 0 - 16
build/AndroidWorkarounds.props

@@ -1,16 +0,0 @@
-<Project>
-  <ItemGroup Condition="'$(AndroidApplication)' == 'true'">
-    <!-- WORKAROUND: The packages below are transitively referenced by System.Memory,
-      but Xamarin.Android applications need the newest versions directly referenced for the linker. -->
-    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
-    <PackageReference Include="System.Buffers" Version="4.5.0" />
-  </ItemGroup>
-  <Target Name="_RemoveNonExistingResgenFile" BeforeTargets="CoreCompile" Condition="'$(_SdkSetAndroidResgenFile)' == 'true' And '$(AndroidResgenFile)' != '' And !Exists('$(AndroidResgenFile)')">
-    <ItemGroup>
-      <Compile Remove="$(AndroidResgenFile)"/>
-    </ItemGroup>
-  </Target>
-  <PropertyGroup>
-    <DesignTimeBuild>false</DesignTimeBuild>
-  </PropertyGroup>
-</Project>

+ 1 - 0
build/CoreLibraries.props

@@ -17,5 +17,6 @@
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.MicroCom/Avalonia.MicroCom.csproj" />
       <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
+      <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj" />
   </ItemGroup>
 </Project>

+ 1 - 1
build/SharedVersion.props

@@ -11,7 +11,7 @@
     <LangVersion>latest</LangVersion>
     <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <PackageIcon>Icon.png</PackageIcon>
-    <PackageDescription>Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.</PackageDescription>
+    <PackageDescription>Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, MacOS and with experimental support for Android, iOS and WebAssembly.</PackageDescription>
     <PackageTags>avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin</PackageTags>
     <PackageReleaseNotes>https://github.com/AvaloniaUI/Avalonia/releases</PackageReleaseNotes>
     <RepositoryType>git</RepositoryType>

+ 2 - 1
build/System.Drawing.Common.props

@@ -1,5 +1,6 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="System.Drawing.Common" Version="4.5.0" />
+    <PackageReference Condition="'$(TargetFramework)'!='netstandard2.0'" Include="System.Drawing.Common" Version="6.0.0" />
+    <PackageReference Condition="'$(TargetFramework)'=='netstandard2.0'" Include="System.Drawing.Common" Version="4.5.0" />
   </ItemGroup>
 </Project>

+ 7 - 4
dirs.proj

@@ -1,5 +1,7 @@
 <Project Sdk="Microsoft.Build.Traversal">
   <ItemGroup>
+    <!-- Build Avalonia.Build.Tasks first because everything depends on it -->
+    <ProjectReference Include="src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" />
     <ProjectReference Include="src/**/*.*proj" />
     <ProjectReference Include="samples/**/*.*proj" />
     <ProjectReference Include="tests/**/*.*proj" />
@@ -8,11 +10,8 @@
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
     <ProjectReference Remove="tests/Avalonia.ReactiveUI.Events.UnitTests/Avalonia.ReactiveUI.Events.UnitTests.csproj" />
-  </ItemGroup>
-  <!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
-  <ItemGroup>
-    <ProjectReference Remove="src/Android/**/*.*proj" />
     <ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
+    <ProjectReference Remove="src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj" />
   </ItemGroup>
   <ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS')">
     <ProjectReference Remove="src/iOS/**/*.*proj" />
@@ -23,6 +22,10 @@
     <ProjectReference Remove="samples/interop/**/*.*proj" />
     <ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
   </ItemGroup>
+  <!-- Build android projects only on Windows, where we have installed android workload -->
+  <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))">
+    <ProjectReference Remove="src/Android/**/*.*proj" />
+  </ItemGroup>
 
   <ItemGroup>
     <PackageReference Include="SlnGen" Version="2.0.40" PrivateAssets="all" />

+ 1 - 0
global.json

@@ -4,6 +4,7 @@
     },
     "msbuild-sdks": {
         "Microsoft.Build.Traversal": "1.0.43",
+        "Xamarin.Legacy.Sdk": "0.1.2-alpha6",
         "MSBuild.Sdk.Extras": "3.0.22",
         "AggregatePackage.NuGet.Sdk" : "0.1.12"
     }

+ 3 - 2
nukebuild/Build.cs

@@ -87,7 +87,8 @@ partial class Build : NukeBuild
             Console.WriteLine(preamble);
             Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit();
         }
-        ExecWait("dotnet version:", "dotnet", "--version");
+        ExecWait("dotnet version:", "dotnet", "--info");
+        ExecWait("dotnet workloads:", "dotnet", "workload list");
     }
 
     IReadOnlyCollection<Output> MsBuildCommon(
@@ -99,7 +100,7 @@ partial class Build : NukeBuild
             // This is required for VS2019 image on Azure Pipelines
             .When(Parameters.IsRunningOnWindows &&
                   Parameters.IsRunningOnAzure, _ => _
-                .AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_8_X64")))
+                .AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64")))
             .AddProperty("PackageVersion", Parameters.Version)
             .AddProperty("iOSRoslynPathHackRequired", true)
             .SetProcessToolPath(MsBuildExe.Value)

+ 9 - 14
readme.md

@@ -3,13 +3,11 @@
 <br />
 [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) 
 
-## 📖 About AvaloniaUI 
+## 📖 About 
 
-Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. 
+Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, MacOs. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM.
 
-<img src="https://user-images.githubusercontent.com/6759207/84751662-7c79da00-afc5-11ea-8780-dda28db70b76.png" width="700" />
-
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+![image](https://user-images.githubusercontent.com/4672627/152126443-932966cf-57e7-4e77-9be6-62463a66b9f8.png)
 
 To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
 
@@ -28,18 +26,15 @@ Install-Package Avalonia.Desktop
 ## Showcase
 
 Examples of UIs built with Avalonia
-![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png)
-
-([Synfonia](https://github.com/jmacato/Synfonia))
+<video src="https://user-images.githubusercontent.com/4672627/152325602-28df36ec-6444-44a6-aebe-90ad52c8f27a.mp4"></video>
+([Lunacy](https://icons8.com/lunacy))
 
-![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG)
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+![image](https://user-images.githubusercontent.com/4672627/152325740-261c27a3-e6f0-4662-bff7-4796d4940e04.png)
+([PlasticSCM](https://www.plasticscm.com/))
 
-![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG)
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+![image](https://user-images.githubusercontent.com/4672627/152326453-14944c4d-33da-4d50-a268-b87f80927adb.png)
+([WasabiWallet](https://www.wasabiwallet.io/))
 
-![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png)
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
 
 ## JetBrains Rider
 

+ 0 - 19
samples/ControlCatalog.Android/Assets/AboutAssets.txt

@@ -1,19 +0,0 @@
-Any raw assets you want to be deployed with your application can be placed in
-this directory (and child directories) and given a Build Action of "AndroidAsset".
-
-These files will be deployed with your package and will be accessible using Android's
-AssetManager, like this:
-
-public class ReadAsset : Activity
-{
-	protected override void OnCreate (Bundle bundle)
-	{
-		base.OnCreate (bundle);
-
-		InputStream input = Assets.Open ("my_asset.txt");
-	}
-}
-
-Additionally, some Android functions will automatically load asset files:
-
-Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

+ 32 - 151
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -1,165 +1,46 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{29132311-1848-4FD6-AE0C-4FF841151BD3}</ProjectGuid>
-    <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>ControlCatalog.Android</RootNamespace>
-    <AssemblyName>ControlCatalog.Android</AssemblyName>
-    <FileAlignment>512</FileAlignment>
-    <AndroidApplication>true</AndroidApplication>
-    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
-    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
-    <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
-    <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
+    <TargetFramework>net6.0-android</TargetFramework>
+    <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
+    <OutputType>Exe</OutputType>
+    <Nullable>enable</Nullable>
+    <ApplicationId>com.Avalonia.ControlCatalog</ApplicationId>
+    <ApplicationVersion>1</ApplicationVersion>
+    <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
+    <AndroidPackageFormat>apk</AndroidPackageFormat>
+    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
   </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>
-    <AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
-    <AndroidLinkMode>None</AndroidLinkMode>
-    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
-    <BundleAssemblies>False</BundleAssemblies>
-    <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a;x86;x86_64</AndroidSupportedAbis>
-    <Debugger>Xamarin</Debugger>
-    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
-    <AotAssemblies>False</AotAssemblies>
-    <EnableLLVM>False</EnableLLVM>
-    <EnableProguard>False</EnableProguard>
-  </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>
-    <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
-    <AndroidLinkMode>Full</AndroidLinkMode>
-    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
-    <BundleAssemblies>False</BundleAssemblies>
-    <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a,x86;x86_64</AndroidSupportedAbis>
-    <Debugger>Xamarin</Debugger>
-    <AotAssemblies>False</AotAssemblies>
-    <EnableLLVM>False</EnableLLVM>
-    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
-    <EnableProguard>False</EnableProguard>
-    <DebugSymbols>False</DebugSymbols>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="Mono.Android" />
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="MainActivity.cs" />
-    <Compile Include="Resources\Resource.Designer.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="SplashActivity.cs" />
-  </ItemGroup>
   <ItemGroup>
-    <None Include="Resources\AboutResources.txt" />
-    <None Include="Assets\AboutAssets.txt" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\drawable\splash_screen.xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\values\colors.xml" />
-    <AndroidResource Include="Resources\values\styles.xml" />
+    <None Remove="Assets\AboutAssets.txt" />
   </ItemGroup>
   <ItemGroup>
     <AndroidResource Include="..\..\build\Assets\Icon.png">
       <Link>Resources\drawable\Icon.png</Link>
     </AndroidResource>
   </ItemGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
+    <DebugSymbols>True</DebugSymbols>
+    <RunAOTCompilation>False</RunAOTCompilation>
+    <EnableLLVM>True</EnableLLVM>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
+    <RunAOTCompilation>False</RunAOTCompilation>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
+  </PropertyGroup>
+
   <ItemGroup>
-    <None Include="Properties\AndroidManifest.xml" />
+    <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
+    <PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
   </ItemGroup>
+  
   <ItemGroup>
-    <ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj">
-      <Project>{7B92AF71-6287-4693-9DCB-BD5B6E927E23}</Project>
-      <Name>Avalonia.Android</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.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.Themes.Fluent\Avalonia.Themes.Fluent.csproj">
-      <Project>{c42d2fc1-a531-4ed4-84b9-89aec7c962fc}</Project>
-      <Name>Avalonia.Themes.Fluent</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="..\ControlCatalog\ControlCatalog.csproj">
-      <Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
-      <Name>ControlCatalog</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
+    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
-  <Import Project="..\..\build\Rx.props" />
-  <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
-  <Import Project="..\..\build\AndroidWorkarounds.props" />
-  <Import Project="..\..\build\LegacyProject.targets" />
 </Project>

+ 2 - 3
samples/ControlCatalog.Android/MainActivity.cs

@@ -5,10 +5,10 @@ using Avalonia.Android;
 
 namespace ControlCatalog.Android
 {
-    [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
+    [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
     public class MainActivity : AvaloniaActivity
     {
-        protected override void OnCreate(Bundle savedInstanceState)
+        protected override void OnCreate(Bundle? savedInstanceState)
         {
             base.OnCreate(savedInstanceState);
 
@@ -16,4 +16,3 @@ namespace ControlCatalog.Android
         }
     }
 }
-

+ 3 - 4
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
-	<uses-sdk android:targetSdkVersion="30" />
-	<application android:label="ControlCatalog.Android"></application>
-</manifest>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
+	<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
+</manifest>

+ 0 - 30
samples/ControlCatalog.Android/Properties/AssemblyInfo.cs

@@ -1,30 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using Android.App;
-
-// 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.Android")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ControlCatalog.Android")]
-[assembly: AssemblyCopyright("Copyright ©  2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-// 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")]

+ 0 - 101
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@@ -1,101 +0,0 @@
-#pragma warning disable 1591
-//------------------------------------------------------------------------------
-// <auto-generated>
-//     This code was generated by a tool.
-//
-//     Changes to this file may cause incorrect behavior and will be lost if
-//     the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-[assembly: global::Android.Runtime.ResourceDesignerAttribute("ControlCatalog.Android.Resource", IsApplication=true)]
-
-namespace ControlCatalog.Android
-{
-	
-	
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")]
-	public partial class Resource
-	{
-		
-		static Resource()
-		{
-			global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-		}
-		
-		public static void UpdateIdValues()
-		{
-		}
-		
-		public partial class Attribute
-		{
-			
-			static Attribute()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Attribute()
-			{
-			}
-		}
-		
-		public partial class Color
-		{
-			
-			// aapt resource value: 0x7F010000
-			public const int splash_background = 2130771968;
-			
-			static Color()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Color()
-			{
-			}
-		}
-		
-		public partial class Drawable
-		{
-			
-			// aapt resource value: 0x7F020000
-			public const int Icon = 2130837504;
-			
-			// aapt resource value: 0x7F020001
-			public const int splash_screen = 2130837505;
-			
-			static Drawable()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Drawable()
-			{
-			}
-		}
-		
-		public partial class Style
-		{
-			
-			// aapt resource value: 0x7F030000
-			public const int MyTheme = 2130903040;
-			
-			// aapt resource value: 0x7F030001
-			public const int MyTheme_NoActionBar = 2130903041;
-			
-			// aapt resource value: 0x7F030002
-			public const int MyTheme_Splash = 2130903042;
-			
-			static Style()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Style()
-			{
-			}
-		}
-	}
-}
-#pragma warning restore 1591

+ 1 - 1
samples/ControlCatalog.Android/Resources/values/styles.xml

@@ -4,7 +4,7 @@
   <style name="MyTheme">
   </style>
 
-  <style name="MyTheme.NoActionBar">
+  <style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
     <item name="android:windowActionBar">false</item>
     <item name="android:windowNoTitle">true</item>
   </style>

+ 1 - 1
samples/ControlCatalog.Android/SplashActivity.cs

@@ -10,7 +10,7 @@ namespace ControlCatalog.Android
     [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
     public class SplashActivity : Activity
     {
-        protected override void OnCreate(Bundle savedInstanceState)
+        protected override void OnCreate(Bundle? savedInstanceState)
         {
             base.OnCreate(savedInstanceState);
         }

+ 14 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -6,6 +6,12 @@
     <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(RunAotCompilation)' == 'true'">
+    <IlcTrimMetadata>true</IlcTrimMetadata>
+    <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json</RestoreAdditionalProjectSources>
+    <NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
@@ -15,6 +21,14 @@
     <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(RunAotCompilation)' == 'true'">
+    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
+    <!-- Cross-compilation for Windows x64-arm64 and Linux x64-arm64 -->
+    <PackageReference Condition="'$(RuntimeIdentifier)'=='win-arm64'" Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
+    <PackageReference Condition="'$(RuntimeIdentifier)'=='linux-arm64'" Include="runtime.linux-x64.Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
+    <RdXmlFile Include="rd.xml" />
+  </ItemGroup>
+
   <PropertyGroup>
     <!-- For Microsoft.CodeAnalysis -->
     <SatelliteResourceLanguages>en</SatelliteResourceLanguages>

+ 7 - 0
samples/ControlCatalog.NetCore/rd.xml

@@ -0,0 +1,7 @@
+<Directives>
+  <Application>
+    <Assembly Name="ControlCatalog" Dynamic="Required All"></Assembly>
+    <Assembly Name="Avalonia.Themes.Default" Dynamic="Required All"></Assembly>
+    <Assembly Name="Avalonia.Themes.Fluent" Dynamic="Required All"></Assembly>
+  </Application>
+</Directives>

+ 1 - 0
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
+    <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
     <Nullable>enable</Nullable>
     <WasmBuildNative>True</WasmBuildNative>
   </PropertyGroup>

+ 5 - 2
samples/ControlCatalog/MainView.xaml

@@ -22,8 +22,8 @@
       <TabItem Header="Border">
         <pages:BorderPage />
       </TabItem>
-      <TabItem Header="Button">
-        <pages:ButtonPage />
+      <TabItem Header="Buttons">
+        <pages:ButtonsPage />
       </TabItem>
       <TabItem Header="ButtonSpinner">
         <pages:ButtonSpinnerPage />
@@ -40,6 +40,9 @@
       <TabItem Header="CheckBox">
         <pages:CheckBoxPage />
       </TabItem>
+      <TabItem Header="Clipboard">
+        <pages:ClipboardPage />
+      </TabItem>
       <TabItem Header="ComboBox">
         <pages:ComboBoxPage />
       </TabItem>

+ 0 - 45
samples/ControlCatalog/Pages/ButtonPage.xaml

@@ -1,45 +0,0 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ButtonPage">
-  <StackPanel Orientation="Vertical" Spacing="4">
-    <TextBlock Classes="h2">A button control</TextBlock>
-
-    <StackPanel Orientation="Horizontal"
-                Margin="0,16,0,0"
-                HorizontalAlignment="Center"
-                Spacing="16">
-      <StackPanel Orientation="Vertical" Spacing="8" Width="200">
-        <Button>Standard _XAML Button</Button>
-        <Button Foreground="White">Foreground</Button>
-        <Button Background="{DynamicResource SystemAccentColor}">Background</Button>
-        <Button IsEnabled="False">Disabled</Button>
-        <Button Content="Re-themed">
-          <Button.Styles>
-            <Style>
-              <Style.Resources>
-                <SolidColorBrush x:Key="ThemeBorderMidBrush">Red</SolidColorBrush>
-                <SolidColorBrush x:Key="ThemeControlHighBrush">DarkRed</SolidColorBrush>
-                <SolidColorBrush x:Key="ButtonBorderBrush">Red</SolidColorBrush>
-                <SolidColorBrush x:Key="ButtonBackground">DarkRed</SolidColorBrush>
-                <SolidColorBrush x:Key="ButtonBackgroundPointerOver">Red</SolidColorBrush>
-                <SolidColorBrush x:Key="ButtonBackgroundPressed">OrangeRed</SolidColorBrush>
-              </Style.Resources>
-            </Style>
-          </Button.Styles>          
-        </Button>
-        <RepeatButton Name="RepeatButton">
-          <TextBlock Name="RepeatButtonTextBlock" Text="Repeat Button: 0" />
-        </RepeatButton>
-        <ToggleButton Content="Toggle Button"/>
-    </StackPanel>
-
-      <StackPanel Orientation="Vertical" Spacing="8" Width="150">
-        <Button BorderThickness="0">No Border</Button>
-        <Button BorderBrush="{DynamicResource SystemAccentColor}">Border Color</Button>
-        <Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4">Thick Border</Button>
-        <Button BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="4" IsEnabled="False">Disabled</Button>
-        <Button BorderBrush="{DynamicResource SystemAccentColor}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
-      </StackPanel>
-    </StackPanel>    
-  </StackPanel>
-</UserControl>

+ 224 - 0
samples/ControlCatalog/Pages/ButtonsPage.xaml

@@ -0,0 +1,224 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="ControlCatalog.Pages.ButtonsPage">
+
+  <UserControl.Resources>
+
+    <MenuFlyout x:Key="SharedMenuFlyout"
+                Placement="Bottom">
+      <MenuItem Header="Item 1">
+        <MenuItem Header="Subitem 1" />
+        <MenuItem Header="Subitem 2" />
+        <MenuItem Header="Subitem 3" />
+      </MenuItem>
+      <MenuItem Header="Item 2"
+                InputGesture="Ctrl+A" />
+      <MenuItem Header="Item 3" />
+    </MenuFlyout>
+
+  </UserControl.Resources>
+      
+  <UserControl.Styles >
+
+    <Style Selector="Border.header-border">
+      <Setter Property="Background">
+        <Setter.Value>
+          <SolidColorBrush Color="Gray" Opacity="0.5" />
+        </Setter.Value>
+      </Setter>
+      <Setter Property="BorderBrush" Value="Gray" />
+      <Setter Property="BorderThickness" Value="0.5" />
+      <Setter Property="CornerRadius" Value="5,5,0,0" />
+      <Setter Property="MaxWidth" Value="450" />
+      <Setter Property="Padding" Value="10" />
+    </Style>
+    
+    <Style Selector="TextBlock.header">
+      <Setter Property="FontSize" Value="18" />
+      <Setter Property="FontWeight" Value="Bold" />
+    </Style>
+
+    <Style Selector="Border.thin">
+      <Setter Property="BorderBrush" Value="Gray" />
+      <Setter Property="BorderThickness" Value="0.5" />
+      <Setter Property="CornerRadius" Value="0,0,5,5" />
+      <Setter Property="Margin" Value="0,0,0,15" />
+    </Style>
+
+  </UserControl.Styles>
+
+  <!-- Styles and overall page design based on AcrylicPage -->
+
+  <StackPanel Orientation="Vertical"
+              Width="450">
+
+    <!-- Button -->
+    <Border Classes="header-border">
+      <StackPanel Orientation="Vertical"
+                  Spacing="4">
+        <TextBlock Text="Button" Classes="header" />
+        <TextBlock TextWrapping="Wrap">A standard button control</TextBlock>
+      </StackPanel>
+    </Border>
+
+    <Border Classes="thin"
+            Padding="15">
+      <StackPanel Orientation="Horizontal"
+                  HorizontalAlignment="Center"
+                  Spacing="10">
+        <StackPanel Orientation="Vertical"
+                    Spacing="8"
+                    Width="200">
+          <Button>Standard _XAML Button</Button>
+          <Button Foreground="White">Foreground</Button>
+          <Button Background="{DynamicResource SystemAccentColor}">Background</Button>
+          <Button IsEnabled="False">Disabled</Button>
+          <Button Content="Re-themed">
+            <Button.Styles>
+              <Style>
+                <Style.Resources>
+                  <SolidColorBrush x:Key="ThemeBorderMidBrush">Red</SolidColorBrush>
+                  <SolidColorBrush x:Key="ThemeControlHighBrush">DarkRed</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonBorderBrush">Red</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonBackground">DarkRed</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonBackgroundPointerOver">Red</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonBackgroundPressed">OrangeRed</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonForeground">White</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonForegroundPointerOver">Black</SolidColorBrush>
+                  <SolidColorBrush x:Key="ButtonForegroundPressed">Black</SolidColorBrush>
+                </Style.Resources>
+              </Style>
+            </Button.Styles>
+          </Button>
+        </StackPanel>
+
+        <StackPanel Orientation="Vertical"
+                    Spacing="8"
+                    Width="200">
+          <Button BorderThickness="0">No Border</Button>
+          <Button BorderBrush="{DynamicResource SystemAccentColor}">Border Color</Button>
+          <Button BorderBrush="{DynamicResource SystemAccentColor}"
+                  BorderThickness="4">Thick Border</Button>
+          <Button BorderBrush="{DynamicResource SystemAccentColor}"
+                  BorderThickness="4"
+                  IsEnabled="False">Disabled</Button>
+          <Button BorderBrush="{DynamicResource SystemAccentColor}"
+                  KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
+        </StackPanel>
+      </StackPanel>
+    </Border>
+
+    <!-- ToggleButton -->
+    <Border Classes="header-border">
+      <StackPanel Orientation="Vertical"
+                  Spacing="4">
+        <TextBlock Text="ToggleButton"
+                   Classes="header" />
+        <TextBlock TextWrapping="Wrap">A button control with multiple states: checked, unchecked or indeterminate.</TextBlock>
+      </StackPanel>
+    </Border>
+
+    <Border Classes="thin"
+            Padding="15">
+      <StackPanel Orientation="Vertical"
+                  Spacing="8">
+        <ToggleButton Content="Toggle Button" />
+      </StackPanel>
+    </Border>
+
+    <!-- RepeatButton -->
+    <Border Classes="header-border">
+      <StackPanel Orientation="Vertical"
+                  Spacing="4">
+        <TextBlock Text="RepeatButton"
+                   Classes="header" />
+        <TextBlock TextWrapping="Wrap">A button control that raises its Click event repeatedly when it is pressed and held.</TextBlock>
+      </StackPanel>
+    </Border>
+
+    <Border Classes="thin"
+            Padding="15">
+      <StackPanel Orientation="Vertical"
+                  Spacing="8">
+        <RepeatButton Name="RepeatButton">
+          <TextBlock Name="RepeatButtonTextBlock"
+                     Text="Repeat Button: 0" />
+        </RepeatButton>
+      </StackPanel>
+    </Border>
+
+    <!-- SplitButton -->
+    <Border Classes="header-border">
+      <StackPanel Orientation="Vertical"
+                  Spacing="4">
+        <TextBlock Text="SplitButton"
+                   Classes="header" />
+        <TextBlock TextWrapping="Wrap">A button with primary and secondary parts that can each be pressed separately. The primary part behaves like a Button and the secondary part opens a flyout.</TextBlock>
+      </StackPanel>
+    </Border>
+
+    <Border Classes="thin"
+            Padding="15">
+      <StackPanel Orientation="Vertical"
+                  Spacing="8">
+        <SplitButton Flyout="{StaticResource SharedMenuFlyout}">
+          <TextBlock Text="Content" />
+        </SplitButton>
+        <SplitButton IsEnabled="False"
+                     Flyout="{StaticResource SharedMenuFlyout}">
+          <TextBlock Text="Disabled" />
+        </SplitButton>
+        <SplitButton Flyout="{StaticResource SharedMenuFlyout}"
+                     Content="Re-themed">
+          <SplitButton.Styles>
+            <Style>
+              <Style.Resources>
+                <x:Double x:Key="SplitButtonSeparatorWidth">1.5</x:Double>
+                <Thickness x:Key="SplitButtonBorderThemeThickness">2</Thickness>
+                <SolidColorBrush x:Key="SplitButtonBorderBrush">Red</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonBorderBrushPointerOver">Red</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonBorderBrushPressed">Red</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonBackground">DarkRed</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonBackgroundPointerOver">Red</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonBackgroundPressed">OrangeRed</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonForeground">White</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonForegroundPointerOver">Black</SolidColorBrush>
+                <SolidColorBrush x:Key="SplitButtonForegroundPressed">Black</SolidColorBrush>
+              </Style.Resources>
+            </Style>
+          </SplitButton.Styles>
+        </SplitButton>
+      </StackPanel>
+    </Border>
+
+    <!-- ToggleSplitButton -->
+    <Border Classes="header-border">
+      <StackPanel Orientation="Vertical"
+                  Spacing="4">
+        <TextBlock Text="ToggleSplitButton"
+                   Classes="header" />
+        <TextBlock TextWrapping="Wrap">A button with primary and secondary parts that can each be pressed separately. The primary part behaves like a ToggleButton with two states and the secondary part opens a flyout.</TextBlock>
+      </StackPanel>
+    </Border>
+
+    <Border Classes="thin"
+            Padding="15">
+      <StackPanel Orientation="Vertical"
+                  Spacing="8">
+        <ToggleSplitButton Flyout="{StaticResource SharedMenuFlyout}">
+          <TextBlock Text="Content" />
+        </ToggleSplitButton>
+        <ToggleSplitButton IsChecked="True"
+                           Flyout="{StaticResource SharedMenuFlyout}">
+          <TextBlock Text="Content" />
+        </ToggleSplitButton>
+        <ToggleSplitButton IsChecked="True"
+                           IsEnabled="False"
+                           Flyout="{StaticResource SharedMenuFlyout}">
+          <TextBlock Text="Disabled" />
+        </ToggleSplitButton>
+      </StackPanel>
+    </Border>
+
+  </StackPanel>
+</UserControl>

+ 2 - 2
samples/ControlCatalog/Pages/ButtonPage.xaml.cs → samples/ControlCatalog/Pages/ButtonsPage.xaml.cs

@@ -3,11 +3,11 @@ using Avalonia.Markup.Xaml;
 
 namespace ControlCatalog.Pages
 {
-    public class ButtonPage : UserControl
+    public class ButtonsPage : UserControl
     {
         private int repeatButtonClickCount = 0;
 
-        public ButtonPage()
+        public ButtonsPage()
         {
             InitializeComponent();
 

+ 23 - 0
samples/ControlCatalog/Pages/ClipboardPage.xaml

@@ -0,0 +1,23 @@
+<UserControl x:Class="ControlCatalog.Pages.ClipboardPage"
+             xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <StackPanel Orientation="Vertical" Spacing="4">
+    <TextBlock Classes="h2">Example of ClipboardPage capabilities</TextBlock>
+
+    <Button Click="CopyText" Content="Copy text to clipboard" />
+    <Button Click="PasteText" Content="Paste text from clipboard" />
+    <Button Click="CopyTextDataObject" Content="Copy text to clipboard (data object)" />
+    <Button Click="PasteTextDataObject" Content="Paste text from clipboard (data object)" />
+
+    <Button Click="CopyFilesDataObject" Content="Copy files to clipboard (data object)" />
+    <Button Click="PasteFilesDataObject" Content="Paste files from clipboard (data object)" />
+
+    <Button Click="GetFormats" Content="Get clipboard formats" />
+    <Button Click="Clear" Content="Clear clipboard" />
+
+    <TextBox x:Name="ClipboardContent"
+             MinHeight="100"
+             AcceptsReturn="True"
+             Watermark="Text to copy of file names per line" />
+  </StackPanel>
+</UserControl>

+ 77 - 0
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public partial class ClipboardPage : UserControl
+    {
+        public ClipboardPage()
+        {
+            InitializeComponent();
+        }
+
+        private TextBox ClipboardContent => this.Get<TextBox>("ClipboardContent");
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        private async void CopyText(object sender, RoutedEventArgs args)
+        {
+            await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text);
+        }
+
+        private async void PasteText(object sender, RoutedEventArgs args)
+        {
+            ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync();
+        }
+
+        private async void CopyTextDataObject(object sender, RoutedEventArgs args)
+        {
+            var dataObject = new DataObject();
+            dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
+            await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
+        }
+
+        private async void PasteTextDataObject(object sender, RoutedEventArgs args)
+        {
+            ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
+        }
+
+        private async void CopyFilesDataObject(object sender, RoutedEventArgs args)
+        {
+            var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
+            if (files.Length == 0)
+            {
+                return;
+            }
+            var dataObject = new DataObject();
+            dataObject.Set(DataFormats.FileNames, files);
+            await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
+        }
+
+        private async void PasteFilesDataObject(object sender, RoutedEventArgs args)
+        {
+            var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
+            ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
+        }
+
+        private async void GetFormats(object sender, RoutedEventArgs args)
+        {
+            var formats = await Application.Current.Clipboard.GetFormatsAsync();
+            ClipboardContent.Text = string.Join(Environment.NewLine, formats);
+        }
+
+        private async void Clear(object sender, RoutedEventArgs args)
+        {
+            await Application.Current.Clipboard.ClearAsync();
+        }
+    }
+}

+ 89 - 71
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@@ -1,77 +1,95 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ComboBoxPage"
-             xmlns:sys="using:System"
-             xmlns:col="using:System.Collections">
-  <StackPanel Orientation="Vertical" Spacing="4">
-    <TextBlock Classes="h2">A drop-down list.</TextBlock>
+<UserControl
+    x:Class="ControlCatalog.Pages.ComboBoxPage"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:col="using:System.Collections"
+    xmlns:sys="using:System">
+    <StackPanel Orientation="Vertical" Spacing="4">
+        <TextBlock Classes="h2">A drop-down list.</TextBlock>
 
-    <WrapPanel HorizontalAlignment="Center" Margin="0 16 0 0"
-               MaxWidth="660">
-      <WrapPanel.Styles>
-        <Style Selector="ComboBox">
-          <Setter Property="Width" Value="250" />
-          <Setter Property="Margin" Value="10" />
-        </Style>
-      </WrapPanel.Styles>
-      <ComboBox PlaceholderText="Pick an Item">
-        <ComboBoxItem>Inline Items</ComboBoxItem>
-        <ComboBoxItem>Inline Item 2</ComboBoxItem>
-        <ComboBoxItem>Inline Item 3</ComboBoxItem>
-        <ComboBoxItem>Inline Item 4</ComboBoxItem>
-      </ComboBox>
+        <StackPanel
+            Margin="0,16,0,0"
+            HorizontalAlignment="Center"
+            Orientation="Horizontal"
+            Spacing="8">
+            <WrapPanel
+                MaxWidth="660"
+                Margin="0,16,0,0"
+                HorizontalAlignment="Center">
+                <WrapPanel.Styles>
+                    <Style Selector="ComboBox">
+                        <Setter Property="Width" Value="250" />
+                        <Setter Property="Margin" Value="10" />
+                    </Style>
+                </WrapPanel.Styles>
 
-      <ComboBox>
-        <ComboBox.Items>
-          <col:ArrayList>
-            <x:Null />
-            <sys:String>Hello</sys:String>
-            <sys:String>World</sys:String>
-          </col:ArrayList>
-        </ComboBox.Items>
-        <ComboBox.ItemTemplate>
-          <DataTemplate>
-            <Panel>
-              <TextBlock Text="{Binding}" />
-              <TextBlock Text="Null object" IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" />
-            </Panel>
-          </DataTemplate>
-        </ComboBox.ItemTemplate>
-      </ComboBox>
+                <ComboBox PlaceholderText="Pick an Item" WrapSelection="{Binding WrapSelection}">
+                    <ComboBoxItem>Inline Items</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 2</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 3</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 4</ComboBoxItem>
+                </ComboBox>
 
-      <ComboBox SelectedIndex="0">
-        <ComboBoxItem>
-          <Panel>
-            <Rectangle Fill="{DynamicResource SystemAccentColor}"/>
-            <TextBlock Margin="8">Control Items</TextBlock>
-          </Panel>
-        </ComboBoxItem>
-        <ComboBoxItem>
-          <Ellipse Width="50" Height="50" Fill="Yellow"/>
-        </ComboBoxItem>
-        <ComboBoxItem>
-          <TextBox Text="TextBox"/>
-        </ComboBoxItem>
-      </ComboBox>
+                <ComboBox WrapSelection="{Binding WrapSelection}">
+                    <ComboBox.Items>
+                        <col:ArrayList>
+                            <x:Null />
+                            <sys:String>Hello</sys:String>
+                            <sys:String>World</sys:String>
+                        </col:ArrayList>
+                    </ComboBox.Items>
+                    <ComboBox.ItemTemplate>
+                        <DataTemplate>
+                            <Panel>
+                                <TextBlock Text="{Binding}" />
+                                <TextBlock IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" Text="Null object" />
+                            </Panel>
+                        </DataTemplate>
+                    </ComboBox.ItemTemplate>
+                </ComboBox>
 
-       <ComboBox x:Name="fontComboBox"  SelectedIndex="0">
-         <ComboBox.ItemTemplate>
-           <DataTemplate>
-             <TextBlock Text="{Binding Name}" FontFamily="{Binding}" />
-           </DataTemplate>
-         </ComboBox.ItemTemplate>
-       </ComboBox>
-      
-      <ComboBox PlaceholderText="Pick an Item">
-        <ComboBoxItem>Inline Items</ComboBoxItem>
-        <ComboBoxItem>Inline Item 2</ComboBoxItem>
-        <ComboBoxItem>Inline Item 3</ComboBoxItem>
-        <ComboBoxItem>Inline Item 4</ComboBoxItem>
-        <DataValidationErrors.Error>
-          <sys:Exception /> 
-        </DataValidationErrors.Error>
-      </ComboBox>
-    </WrapPanel>
+                <ComboBox SelectedIndex="0" WrapSelection="{Binding WrapSelection}">
+                    <ComboBoxItem>
+                        <Panel>
+                            <Rectangle Fill="{DynamicResource SystemAccentColor}" />
+                            <TextBlock Margin="8">Control Items</TextBlock>
+                        </Panel>
+                    </ComboBoxItem>
+                    <ComboBoxItem>
+                        <Ellipse
+                            Width="50"
+                            Height="50"
+                            Fill="Yellow" />
+                    </ComboBoxItem>
+                    <ComboBoxItem>
+                        <TextBox Text="TextBox" />
+                    </ComboBoxItem>
+                </ComboBox>
 
-  </StackPanel>
+                <ComboBox
+                    x:Name="fontComboBox"
+                    SelectedIndex="0"
+                    WrapSelection="{Binding WrapSelection}">
+                    <ComboBox.ItemTemplate>
+                        <DataTemplate>
+                            <TextBlock FontFamily="{Binding}" Text="{Binding Name}" />
+                        </DataTemplate>
+                    </ComboBox.ItemTemplate>
+                </ComboBox>
+
+                <ComboBox PlaceholderText="Pick an Item" WrapSelection="{Binding WrapSelection}">
+                    <ComboBoxItem>Inline Items</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 2</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 3</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 4</ComboBoxItem>
+                    <DataValidationErrors.Error>
+                        <sys:Exception />
+                    </DataValidationErrors.Error>
+                </ComboBox>
+            </WrapPanel>
+
+            <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>
+
+        </StackPanel>
+    </StackPanel>
 </UserControl>

+ 2 - 0
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@@ -2,6 +2,7 @@ using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
+using ControlCatalog.ViewModels;
 
 namespace ControlCatalog.Pages
 {
@@ -10,6 +11,7 @@ namespace ControlCatalog.Pages
         public ComboBoxPage()
         {
             this.InitializeComponent();
+            DataContext = new ComboBoxPageViewModel();
         }
 
         private void InitializeComponent()

+ 7 - 1
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@@ -11,7 +11,13 @@
                 Padding="16"
                 BorderBrush="{DynamicResource SystemAccentColor}"
                 BorderThickness="2">
-          <TextBlock Name="DragStateText" TextWrapping="Wrap">Drag Me</TextBlock>
+          <TextBlock Name="DragStateText" TextWrapping="Wrap">Drag Me (text)</TextBlock>
+        </Border>
+        <Border Name="DragMeFiles"
+                Padding="16"
+                BorderBrush="{DynamicResource SystemAccentColor}"
+                BorderThickness="2">
+          <TextBlock Name="DragStateFiles" TextWrapping="Wrap">Drag Me (files)</TextBlock>
         </Border>
         <Border Name="DragMeCustom"
                 Padding="16"

+ 1 - 3
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@@ -2,11 +2,8 @@
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 using System;
-using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using System.Reflection;
-using System.Text;
 
 namespace ControlCatalog.Pages
 {
@@ -24,6 +21,7 @@ namespace ControlCatalog.Pages
                 $"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
 
             SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move);
+            SetupDnd("Files", d => d.Set(DataFormats.FileNames, new[] { Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName }), DragDropEffects.Copy);
         }
 
         void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)

+ 1 - 1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -111,7 +111,7 @@ namespace ControlCatalog.Pages
         private void ScrollTo(int index)
         {
             System.Diagnostics.Debug.WriteLine("Scroll to " + index);
-            var layoutManager = ((Window)this.GetVisualRoot()).LayoutManager;
+            var layoutManager = ((TopLevel)VisualRoot).LayoutManager;
             var element = _repeater.GetOrCreateElement(index);
             layoutManager.ExecuteLayoutPass();
             element.BringIntoView();

+ 3 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@@ -21,6 +21,7 @@
       <CheckBox IsChecked="{Binding Toggle}">Toggle</CheckBox>
       <CheckBox IsChecked="{Binding AlwaysSelected}">AlwaysSelected</CheckBox>
       <CheckBox IsChecked="{Binding AutoScrollToSelectedItem}">AutoScrollToSelectedItem</CheckBox>
+      <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>
     </StackPanel>
     <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="4">
       <Button Command="{Binding AddItemCommand}">Add</Button>
@@ -30,6 +31,7 @@
     <ListBox Items="{Binding Items}"
              Selection="{Binding Selection}"
              AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"
-             SelectionMode="{Binding SelectionMode^}"/>
+             SelectionMode="{Binding SelectionMode^}"
+             WrapSelection="{Binding WrapSelection}"/>
   </DockPanel>
 </UserControl>

+ 23 - 14
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Globalization;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
@@ -49,25 +50,33 @@ namespace ControlCatalog.Pages
                     context.DrawRectangle(p, boundsRect);
                     context.DrawRectangle(p, workingAreaRect);
 
-                    var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 };
 
-                    text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}";
-                    context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text);
-                    
-                    text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
-                    context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
+                    var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
+                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
 
-                    text.Text = $"Scaling: {screen.PixelDensity * 100}%";
-                    context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
-                    
-                    text.Text = $"Primary: {screen.Primary}";
-                    context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
-                    
-                    text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
-                    context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
+                    formattedText =
+                        CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
+                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
+
+                    formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%");
+                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
+
+                    formattedText = CreateFormattedText($"Primary: {screen.Primary}");
+                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
+
+                    formattedText =
+                        CreateFormattedText(
+                            $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}");
+                    context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
                 }
 
             context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
         }
+
+        private FormattedText CreateFormattedText(string textToFormat)
+        {
+            return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
+                Typeface.Default, 12, Brushes.Green);
+        }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

@@ -11,7 +11,7 @@
       </StackPanel>
     </Border>
 
-    <TextBlock Text="headered ToggleSwitch" Classes="header"/>
+    <TextBlock Text="Headered ToggleSwitch" Classes="header"/>
 
     <Border Classes="Thin">
       <StackPanel>

+ 21 - 0
samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using Avalonia.Controls;
+using Avalonia.Controls.Selection;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels
+{
+    public class ComboBoxPageViewModel : ViewModelBase
+    {
+        private bool _wrapSelection;
+
+        public bool WrapSelection
+        {
+            get => _wrapSelection;
+            set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+        }
+    }
+}

+ 7 - 0
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@@ -14,6 +14,7 @@ namespace ControlCatalog.ViewModels
         private bool _toggle;
         private bool _alwaysSelected;
         private bool _autoScrollToSelectedItem = true;
+        private bool _wrapSelection;
         private int _counter;
         private IObservable<SelectionMode> _selectionMode;
 
@@ -85,6 +86,12 @@ namespace ControlCatalog.ViewModels
             set => this.RaiseAndSetIfChanged(ref _autoScrollToSelectedItem, value);
         }
 
+        public bool WrapSelection
+        {
+            get => _wrapSelection;
+            set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+        }
+
         public MiniCommand AddItemCommand { get; }
         public MiniCommand RemoveItemCommand { get; }
         public MiniCommand SelectRandomItemCommand { get; }

+ 3 - 0
samples/RenderDemo/MainWindow.xaml

@@ -57,6 +57,9 @@
     <TabItem Header="GlyphRun">
       <pages:GlyphRunPage />
     </TabItem>
+    <TabItem Header="FormattedText">
+      <pages:FormattedTextPage />
+    </TabItem>
     <TabItem Header="LineBounds">
       <pages:LineBoundsPage />
     </TabItem>

+ 8 - 5
samples/RenderDemo/Pages/CustomSkiaPage.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Diagnostics;
+using System.Globalization;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Media;
@@ -41,7 +42,10 @@ namespace RenderDemo.Pages
             {
                 var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
                 if (canvas == null)
-                    context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
+                    using (var c = new DrawingContext(context, false))
+                    {
+                        c.DrawText(_noSkia, new Point());
+                    }
                 else
                 {
                     canvas.Save();
@@ -108,10 +112,9 @@ namespace RenderDemo.Pages
         
         public override void Render(DrawingContext context)
         {
-            var noSkia = new FormattedText()
-            {
-                Text = "Current rendering API is not Skia"
-            };
+            var noSkia = new FormattedText("Current rendering API is not Skia", CultureInfo.CurrentCulture,
+                FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Black);
+
             context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
             Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
         }

+ 7 - 0
samples/RenderDemo/Pages/FormattedTextPage.axaml

@@ -0,0 +1,7 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="RenderDemo.Pages.FormattedTextPage">
+</UserControl>

+ 60 - 0
samples/RenderDemo/Pages/FormattedTextPage.axaml.cs

@@ -0,0 +1,60 @@
+using System.Globalization;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+
+namespace RenderDemo.Pages
+{
+    public class FormattedTextPage : UserControl
+    {
+        public FormattedTextPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            const string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";
+
+            // Create the initial formatted text string.
+            var formattedText = new FormattedText(
+                testString,
+                CultureInfo.GetCultureInfo("en-us"),
+                FlowDirection.LeftToRight,
+                new Typeface("Verdana"),
+                32,
+                Brushes.Black) { MaxTextWidth = 300, MaxTextHeight = 240 };
+
+            // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
+
+            // Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters.
+            // The font size is calculated in terms of points -- not as device-independent pixels.
+            formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5);
+
+            // Use a Bold font weight beginning at the 6th character and continuing for 11 characters.
+            formattedText.SetFontWeight(FontWeight.Bold, 6, 11);
+
+            var gradient = new LinearGradientBrush
+            {
+                GradientStops =
+                    new GradientStops { new GradientStop(Colors.Orange, 0), new GradientStop(Colors.Teal, 1) },
+                StartPoint = new RelativePoint(0,0, RelativeUnit.Relative),
+                EndPoint = new RelativePoint(0,1, RelativeUnit.Relative)
+            };
+
+            // Use a linear gradient brush beginning at the 6th character and continuing for 11 characters.
+            formattedText.SetForegroundBrush(gradient, 6, 11);
+
+            // Use an Italic font style beginning at the 28th character and continuing for 28 characters.
+            formattedText.SetFontStyle(FontStyle.Italic, 28, 28);
+
+            context.DrawText(formattedText, new Point(10, 0));
+        }
+    }
+}

+ 5 - 2
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@@ -13,6 +13,7 @@ namespace RenderDemo.Pages
         private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
         private readonly Random _rand = new Random();
         private ushort[] _glyphIndices = new ushort[1];
+        private char[] _characters = new char[1];
         private float _fontSize = 20;
         private int _direction = 10;
 
@@ -38,7 +39,7 @@ namespace RenderDemo.Pages
 
         private void UpdateGlyphRun()
         {
-            var c = (uint)_rand.Next(65, 90);
+            var c = (char)_rand.Next(65, 90);
 
             if (_fontSize + _direction > 200)
             {
@@ -54,6 +55,8 @@ namespace RenderDemo.Pages
 
             _glyphIndices[0] = _glyphTypeface.GetGlyph(c);
 
+            _characters[0] = c;
+
             var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
 
             var drawingGroup = new DrawingGroup();
@@ -61,7 +64,7 @@ namespace RenderDemo.Pages
             var glyphRunDrawing = new GlyphRunDrawing
             {
                 Foreground = Brushes.Black,
-                GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
+                GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
             };
 
             drawingGroup.Children.Add(glyphRunDrawing);

+ 3 - 1
src/Android/Avalonia.Android/AndroidInputMethod.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Android
 
             _host.Focusable = true;
             _host.FocusableInTouchMode = true;
-            _host.ViewTreeObserver.AddOnGlobalLayoutListener(new SoftKeyboardListner(_host));
+            _host.ViewTreeObserver.AddOnGlobalLayoutListener(new SoftKeyboardListener(_host));
         }
 
         public void Reset()
@@ -83,6 +83,8 @@ namespace Avalonia.Android
 
                 if (options.Multiline)
                     outAttrs.InputType |= global::Android.Text.InputTypes.TextFlagMultiLine;
+
+                outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
             });
 
             //_inputElement.PointerReleased += RestoreSoftKeyboard;

+ 4 - 5
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -10,7 +10,6 @@ using Avalonia.Input.Platform;
 using Avalonia.OpenGL.Egl;
 using Avalonia.Platform;
 using Avalonia.Rendering;
-using Avalonia.PlatformSupport;
 using Avalonia.Skia;
 
 namespace Avalonia
@@ -20,9 +19,9 @@ namespace Avalonia
         public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
             var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
-            builder.UseWindowingSubsystem(() => AndroidPlatform.Initialize(builder.ApplicationType, options), "Android");
-            builder.UseSkia();
-            return builder;
+            return builder
+                .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android")
+                .UseSkia();
         }
     }
 }
@@ -44,7 +43,7 @@ namespace Avalonia.Android
 
         public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
 
-        public static void Initialize(Type appType, AndroidPlatformOptions options)
+        public static void Initialize(AndroidPlatformOptions options)
         {
             Options = options;
 

+ 0 - 14
src/Android/Avalonia.Android/AppBuilder.cs

@@ -1,14 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.PlatformSupport;
-
-namespace Avalonia
-{
-    public sealed class AppBuilder : AppBuilderBase<AppBuilder>
-    {
-        public AppBuilder() : base(new StandardRuntimePlatform(),
-            builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType()?.Assembly))
-        {
-
-        }
-    }
-}

+ 10 - 7
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -1,15 +1,18 @@
-<Project Sdk="MSBuild.Sdk.Extras">
+<Project Sdk="Xamarin.Legacy.Sdk">
   <PropertyGroup>
-    <TargetFramework>monoandroid11.0</TargetFramework>
+    <TargetFrameworks>net6.0-android;monoandroid11.0</TargetFrameworks>
+    <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
+    <DebugType>portable</DebugType>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
-    <ProjectReference Include="..\..\Avalonia.PlatformSupport\Avalonia.PlatformSupport.csproj">
-      <SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
-    </ProjectReference>
+    <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
+    <PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
   </ItemGroup>
-  <Import Project="..\..\..\build\Rx.props" />
-  <Import Project="..\..\..\build\AndroidWorkarounds.props" />
 </Project>

+ 28 - 8
src/Android/Avalonia.Android/AvaloniaActivity.cs

@@ -1,35 +1,55 @@
 using Android.App;
 using Android.OS;
 using Android.Views;
+using Android.Content.PM;
+using AndroidX.AppCompat.App;
+using Android.Content.Res;
+using AndroidX.Lifecycle;
 
 namespace Avalonia.Android
 {
-    public abstract class AvaloniaActivity : Activity
+    public abstract class AvaloniaActivity : AppCompatActivity
     {
         internal AvaloniaView View;
-        object _content;
-
+        internal AvaloniaViewModel _viewModel;
         protected override void OnCreate(Bundle savedInstanceState)
         {
             View = new AvaloniaView(this);
-            if (_content != null)
-                View.Content = _content;
             SetContentView(View);
+
+            _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
+
+            if (_viewModel.Content != null)
+            {
+                View.Content = _viewModel.Content;
+            }
+
             base.OnCreate(savedInstanceState);
         }
-
         public object Content
         {
             get
             {
-                return _content;
+                return _viewModel.Content;
             }
             set
             {
-                _content = value;
+                _viewModel.Content = value;
                 if (View != null)
                     View.Content = value;
             }
         }
+
+        public override void OnConfigurationChanged(Configuration newConfig)
+        {
+            base.OnConfigurationChanged(newConfig);
+        }
+
+        protected override void OnDestroy()
+        {
+            View.Content = null;
+
+            base.OnDestroy();
+        }
     }
 }

+ 11 - 0
src/Android/Avalonia.Android/AvaloniaViewModel.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Android
+{
+    internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel
+    {
+        public object Content { get; set; }
+    }
+}

+ 0 - 50
src/Android/Avalonia.Android/Resources/AboutResources.txt

@@ -1,50 +0,0 @@
-Images, layout descriptions, binary blobs and string dictionaries can be included 
-in your application as resource files.  Various Android APIs are designed to 
-operate on the resource IDs instead of dealing with images, strings or binary blobs 
-directly.
-
-For example, a sample Android app that contains a user interface layout (main.xml),
-an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 
-would keep its resources in the "Resources" directory of the application:
-
-Resources/
-    drawable-hdpi/
-        icon.png
-
-    drawable-ldpi/
-        icon.png
-
-    drawable-mdpi/
-        icon.png
-
-    layout/
-        main.xml
-
-    values/
-        strings.xml
-
-In order to get the build system to recognize Android resources, set the build action to
-"AndroidResource".  The native Android APIs do not operate directly with filenames, but 
-instead operate on resource IDs.  When you compile an Android application that uses resources, 
-the build system will package the resources for distribution and generate a class called
-"Resource" that contains the tokens for each one of the resources included. For example, 
-for the above Resources layout, this is what the Resource class would expose:
-
-public class Resource {
-    public class drawable {
-        public const int icon = 0x123;
-    }
-
-    public class layout {
-        public const int main = 0x456;
-    }
-
-    public class strings {
-        public const int first_string = 0xabc;
-        public const int second_string = 0xbcd;
-    }
-}
-
-You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 
-to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 
-string in the dictionary file values/strings.xml.

+ 0 - 6
src/Android/Avalonia.Android/Resources/Values/Strings.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<resources>
-  <string name="Hello">Hello World, Click Me!</string>
-  <string name="ApplicationName">$projectname$</string>
-</resources>

+ 2 - 2
src/Android/Avalonia.Android/SoftKeyboardListner.cs → src/Android/Avalonia.Android/SoftKeyboardListener.cs

@@ -9,7 +9,7 @@ using Avalonia.Input;
 
 namespace Avalonia.Android
 {
-    class SoftKeyboardListner : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
+    class SoftKeyboardListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
     {
         private const int DefaultKeyboardHeightDP = 100;
         private static readonly int EstimatedKeyboardDP = DefaultKeyboardHeightDP + (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop ? 48 : 0);
@@ -17,7 +17,7 @@ namespace Avalonia.Android
         private readonly View _host;
         private bool _wasKeyboard;
 
-        public SoftKeyboardListner(View view)
+        public SoftKeyboardListener(View view)
         {
             _host = view;
         }

+ 0 - 19
src/Android/Avalonia.AndroidTestApplication/Assets/AboutAssets.txt

@@ -1,19 +0,0 @@
-Any raw assets you want to be deployed with your application can be placed in
-this directory (and child directories) and given a Build Action of "AndroidAsset".
-
-These files will be deployed with you package and will be accessible using Android's
-AssetManager, like this:
-
-public class ReadAsset : Activity
-{
-	protected override void OnCreate (Bundle bundle)
-	{
-		base.OnCreate (bundle);
-
-		InputStream input = Assets.Open ("my_asset.txt");
-	}
-}
-
-Additionally, some Android functions will automatically load asset files:
-
-Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

+ 28 - 144
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@@ -1,153 +1,37 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{FF69B927-C545-49AE-8E16-3D14D621AA12}</ProjectGuid>
-    <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
-    <OutputType>Library</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>Avalonia.AndroidTestApplication</RootNamespace>
-    <AssemblyName>Avalonia.AndroidTestApplication</AssemblyName>
-    <FileAlignment>512</FileAlignment>
-    <AndroidApplication>true</AndroidApplication>
-    <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
-    <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
-    <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
-    <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
+    <TargetFramework>net6.0-android</TargetFramework>
+    <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
+    <OutputType>Exe</OutputType>
+    <Nullable>enable</Nullable>
+    <ApplicationId>com.Avalonia.AndroidTestApplication</ApplicationId>
+    <ApplicationVersion>1</ApplicationVersion>
+    <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
+    <AndroidPackageFormat>apk</AndroidPackageFormat>
+    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
+    <DebugType>portable</DebugType>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+  
+  <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
     <DebugSymbols>True</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
-    <AndroidLinkMode>None</AndroidLinkMode>
-    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
-    <BundleAssemblies>False</BundleAssemblies>
-    <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a;x86</AndroidSupportedAbis>
-    <Debugger>Xamarin</Debugger>
-    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
-    <DevInstrumentationEnabled>True</DevInstrumentationEnabled>
-    <AotAssemblies>False</AotAssemblies>
-    <EnableLLVM>False</EnableLLVM>
-    <EnableProguard>False</EnableProguard>
+    <RunAOTCompilation>True</RunAOTCompilation>
+    <EnableLLVM>True</EnableLLVM>
+    <!--<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot>-->
   </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>
-    <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
-    <AndroidLinkMode>Full</AndroidLinkMode>
-    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
-    <BundleAssemblies>False</BundleAssemblies>
-    <AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
-    <AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
-    <Debugger>Xamarin</Debugger>
-    <AotAssemblies>False</AotAssemblies>
-    <EnableLLVM>False</EnableLLVM>
-    <AndroidEnableMultiDex>False</AndroidEnableMultiDex>
-    <EnableProguard>False</EnableProguard>
-    <DevInstrumentationEnabled>False</DevInstrumentationEnabled>
-    <DebugSymbols>False</DebugSymbols>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="Mono.Android" />
-    <Reference Include="mscorlib" />
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Xml" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="MainActivity.cs" />
-    <Compile Include="Resources\Resource.Designer.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="app.config" />
-    <None Include="Resources\AboutResources.txt" />
-    <None Include="Assets\AboutAssets.txt" />
-  </ItemGroup>
+  
   <ItemGroup>
-    <AndroidResource Include="Resources\values\Strings.xml">
-      <SubType>Designer</SubType>
-    </AndroidResource>
-  </ItemGroup>
-  <ItemGroup>
-    <AndroidResource Include="Resources\drawable\Icon.png" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="Properties\AndroidManifest.xml" />
+    <None Remove="Assets\AboutAssets.txt" />
   </ItemGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <BundleAssemblies>True</BundleAssemblies>
+  </PropertyGroup>
+  
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <BundleAssemblies>True</BundleAssemblies>
+  </PropertyGroup>
+
   <ItemGroup>
-    <ProjectReference Include="..\..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj">
-      <Project>{7b92af71-6287-4693-9dcb-bd5b6e927e23}</Project>
-      <Name>Avalonia.Android</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\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.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="..\..\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="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-    </ProjectReference>
+    <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\Avalonia.Android\Avalonia.Android.csproj" />
   </ItemGroup>
-  <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
-  <Import Project="..\..\..\build\Base.props" />
-  <Import Project="..\..\..\build\Rx.props" />
-  <Import Project="..\..\..\build\System.Memory.props" />
-  <Import Project="..\..\..\build\AndroidWorkarounds.props" />
-  <Import Project="..\..\..\build\LegacyProject.targets" />
 </Project>

+ 2 - 0
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@@ -14,6 +14,8 @@ namespace Avalonia.AndroidTestApplication
     [Activity(Label = "Main",
         MainLauncher = true,
         Icon = "@drawable/icon",
+         Theme = "@style/Theme.AppCompat.NoActionBar",
+         ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize,
         LaunchMode = LaunchMode.SingleInstance/*,
         ScreenOrientation = ScreenOrientation.Landscape*/)]
     public class MainBaseActivity : AvaloniaActivity

+ 2 - 4
src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml

@@ -1,6 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
-	<uses-sdk android:targetSdkVersion="30" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 	<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application>
-	<uses-permission android:name="android.permission.INTERNET" />
-</manifest>
+</manifest>

+ 0 - 30
src/Android/Avalonia.AndroidTestApplication/Properties/AssemblyInfo.cs

@@ -1,30 +0,0 @@
-using System.Reflection;
-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("Avalonia.AndroidTestApplication")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Avalonia.AndroidTestApplication")]
-[assembly: AssemblyCopyright("Copyright ©  2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-
-// 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")]

+ 0 - 79
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@@ -1,79 +0,0 @@
-#pragma warning disable 1591
-//------------------------------------------------------------------------------
-// <auto-generated>
-//     This code was generated by a tool.
-//
-//     Changes to this file may cause incorrect behavior and will be lost if
-//     the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.AndroidTestApplication.Resource", IsApplication=true)]
-
-namespace Avalonia.AndroidTestApplication
-{
-	
-	
-	[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")]
-	public partial class Resource
-	{
-		
-		static Resource()
-		{
-			global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-		}
-		
-		public static void UpdateIdValues()
-		{
-		}
-		
-		public partial class Attribute
-		{
-			
-			static Attribute()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Attribute()
-			{
-			}
-		}
-		
-		public partial class Drawable
-		{
-			
-			// aapt resource value: 0x7F010000
-			public const int Icon = 2130771968;
-			
-			static Drawable()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private Drawable()
-			{
-			}
-		}
-		
-		public partial class String
-		{
-			
-			// aapt resource value: 0x7F020000
-			public const int ApplicationName = 2130837504;
-			
-			// aapt resource value: 0x7F020001
-			public const int Hello = 2130837505;
-			
-			static String()
-			{
-				global::Android.Runtime.ResourceIdManager.UpdateIdValues();
-			}
-			
-			private String()
-			{
-			}
-		}
-	}
-}
-#pragma warning restore 1591

+ 0 - 11
src/Android/Avalonia.AndroidTestApplication/app.config

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="System.Runtime.InteropServices.WindowsRuntime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>

+ 9 - 24
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@@ -22,19 +22,9 @@ namespace Avalonia.Data.Core.Plugins
 
             var method = GetFirstMethodWithName(instance.GetType(), methodName);
 
-            if (method != null)
+            if (method is not null)
             {
-                var parameters = method.GetParameters();
-
-                if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8)
-                {
-                    var exception = new ArgumentException(
-                        "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.",
-                        nameof(methodName));
-                    return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
-                }
-
-                return new Accessor(reference, method, parameters);
+                return new Accessor(reference, method);
             }
             else
             {
@@ -82,18 +72,20 @@ namespace Avalonia.Data.Core.Plugins
 
         private sealed class Accessor : PropertyAccessorBase
         {
-            public Accessor(WeakReference<object?> reference, MethodInfo method, ParameterInfo[] parameters)
+            public Accessor(WeakReference<object?> reference, MethodInfo method)
             {
                 _ = reference ?? throw new ArgumentNullException(nameof(reference));
                 _ = method ?? throw new ArgumentNullException(nameof(method));
 
                 var returnType = method.ReturnType;
-                bool hasReturn = returnType != typeof(void);
 
-                var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length;
+                var parameters = method.GetParameters();
+
+                var signatureTypeCount = parameters.Length + 1;
 
                 var paramTypes = new Type[signatureTypeCount];
 
+
                 for (var i = 0; i < parameters.Length; i++)
                 {
                     ParameterInfo parameter = parameters[i];
@@ -101,16 +93,9 @@ namespace Avalonia.Data.Core.Plugins
                     paramTypes[i] = parameter.ParameterType;
                 }
 
-                if (hasReturn)
-                {
-                    paramTypes[paramTypes.Length - 1] = returnType;
+                paramTypes[paramTypes.Length - 1] = returnType;
 
-                    PropertyType = Expression.GetFuncType(paramTypes);
-                }
-                else
-                {
-                    PropertyType = Expression.GetActionType(paramTypes);
-                }
+                PropertyType = Expression.GetDelegateType(paramTypes);
 
                 if (method.IsStatic)
                 {

+ 1 - 1
src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs

@@ -3,7 +3,7 @@ using System.Collections.Generic;
 
 namespace Avalonia.Utilities
 {
-    public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator, IEnumerator<T>
+    public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator<T>
     {
         private readonly IReadOnlyList<T> _readOnlyList;
         private int _pos;

+ 6 - 1
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -124,6 +124,9 @@ namespace Avalonia.Build.Tasks
             var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
                 TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
             asm.MainModule.Types.Add(indexerAccessorClosure);
+            var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+            asm.MainModule.Types.Add(trampolineBuilder);
 
             var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
             var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
@@ -133,6 +136,7 @@ namespace Avalonia.Build.Tasks
                 AvaloniaXamlIlLanguage.CustomValueConverter,
                 new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
                 new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
+                new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
                 new DeterministicIdGenerator());
 
 
@@ -256,7 +260,8 @@ namespace Avalonia.Build.Tasks
                                 true),
                             (closureName, closureBaseType) =>
                                 populateBuilder.DefineSubType(closureBaseType, closureName, false),
-                            (s, returnType, parameters) => builder.DefineDelegateSubType(s, false, returnType, parameters),
+                            (closureName, returnType, parameterTypes) =>
+                                populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
                             res.Uri, res
                         );
                         

+ 5 - 1
src/Avalonia.Controls/ApiCompatBaseline.txt

@@ -43,6 +43,10 @@ MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.Off
 MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
 MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
+MembersMustExist : Member 'protected Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.CreateFormattedText()' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.FormattedText.get()' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract.
 CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
 EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
@@ -63,4 +67,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
 MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
-Total Issues: 64
+Total Issues: 68

+ 20 - 15
src/Avalonia.Controls/AppBuilderBase.cs

@@ -14,9 +14,9 @@ namespace Avalonia.Controls
     public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
     {
         private static bool s_setupWasAlreadyCalled;
-        private Action _optionsInitializers;
-        private Func<Application> _appFactory;
-        private IApplicationLifetime _lifetime;
+        private Action? _optionsInitializers;
+        private Func<Application>? _appFactory;
+        private IApplicationLifetime? _lifetime;
         
         /// <summary>
         /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
@@ -31,32 +31,32 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the <see cref="Application"/> instance being initialized.
         /// </summary>
-        public Application Instance { get; private set; }
+        public Application? Instance { get; private set; }
         
         /// <summary>
         /// Gets the type of the Instance (even if it's not created yet)
         /// </summary>
-        public Type ApplicationType { get; private set; }
+        public Type? ApplicationType { get; private set; }
         
         /// <summary>
         /// Gets or sets a method to call the initialize the windowing subsystem.
         /// </summary>
-        public Action WindowingSubsystemInitializer { get; private set; }
+        public Action? WindowingSubsystemInitializer { get; private set; }
 
         /// <summary>
         /// Gets the name of the currently selected windowing subsystem.
         /// </summary>
-        public string WindowingSubsystemName { get; private set; }
+        public string? WindowingSubsystemName { get; private set; }
 
         /// <summary>
         /// Gets or sets a method to call the initialize the windowing subsystem.
         /// </summary>
-        public Action RenderingSubsystemInitializer { get; private set; }
+        public Action? RenderingSubsystemInitializer { get; private set; }
 
         /// <summary>
         /// Gets the name of the currently selected rendering subsystem.
         /// </summary>
-        public string RenderingSubsystemName { get; private set; }
+        public string? RenderingSubsystemName { get; private set; }
 
         /// <summary>
         /// Gets or sets a method to call after the <see cref="Application"/> is setup.
@@ -126,7 +126,7 @@ namespace Avalonia.Controls
         /// <typeparam name="TMainWindow">The window type.</typeparam>
         /// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
         [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")]
-        public void Start<TMainWindow>(Func<object> dataContextProvider = null)
+        public void Start<TMainWindow>(Func<object>? dataContextProvider = null)
             where TMainWindow : Window, new()
         {
             AfterSetup(builder =>
@@ -134,7 +134,7 @@ namespace Avalonia.Controls
                 var window = new TMainWindow();
                 if (dataContextProvider != null)
                     window.DataContext = dataContextProvider();
-                ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime)
+                ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!)
                     .MainWindow = window;
             });
             
@@ -155,7 +155,7 @@ namespace Avalonia.Controls
         public void Start(AppMainDelegate main, string[] args)
         {
             Setup();
-            main(Instance, args);
+            main(Instance!, args);
         }
 
         /// <summary>
@@ -226,8 +226,8 @@ namespace Avalonia.Controls
             var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
             var platformClassFullName = assemblyName + "." + platformClassName;
             var platformClass = assembly.GetType(platformClassFullName);
-            var init = platformClass.GetRuntimeMethod("Initialize", Type.EmptyTypes);
-            init.Invoke(null, null);
+            var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes);
+            init!.Invoke(null, null);
         };
 
         public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
@@ -251,7 +251,7 @@ namespace Avalonia.Controls
                                              where constructor.GetParameters().Length == 0 && !constructor.IsStatic
                                              select constructor).Single() into constructor
                                      select (Action)(() => constructor.Invoke(Array.Empty<object>()));
-            Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke();
+            Delegate.Combine(moduleInitializers.ToArray())!.DynamicInvoke();
         }
 
         /// <summary>
@@ -292,6 +292,11 @@ namespace Avalonia.Controls
                 throw new InvalidOperationException("No rendering system configured.");
             }
 
+            if (_appFactory == null)
+            {
+                throw new InvalidOperationException("No Application factory configured.");
+            }
+
             if (s_setupWasAlreadyCalled && CheckSetup)
             {
                 throw new InvalidOperationException("Setup was already called on one of AppBuilder instances");

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

@@ -13,7 +13,6 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Threading;
-#nullable enable
 
 namespace Avalonia
 {
@@ -177,13 +176,13 @@ namespace Avalonia
         /// </summary>
         public IApplicationLifetime? ApplicationLifetime { get; set; }
 
-        event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesAdded
+        event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
         {
             add => _stylesAdded += value;
             remove => _stylesAdded -= value;
         }
 
-        event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesRemoved
+        event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesRemoved
         {
             add => _stylesRemoved += value;
             remove => _stylesRemoved -= value;

+ 13 - 13
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -15,26 +15,26 @@ namespace Avalonia.Controls.ApplicationLifetimes
     public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
     {
         private int _exitCode;
-        private CancellationTokenSource _cts;
+        private CancellationTokenSource? _cts;
         private bool _isShuttingDown;
         private HashSet<Window> _windows = new HashSet<Window>();
 
-        private static ClassicDesktopStyleApplicationLifetime _activeLifetime;
+        private static ClassicDesktopStyleApplicationLifetime? _activeLifetime;
         static ClassicDesktopStyleApplicationLifetime()
         {
             Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened);
             Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent);
         }
 
-        private static void WindowClosedEvent(object sender, RoutedEventArgs e)
+        private static void WindowClosedEvent(object? sender, RoutedEventArgs e)
         {
-            _activeLifetime?._windows.Remove((Window)sender);
-            _activeLifetime?.HandleWindowClosed((Window)sender);
+            _activeLifetime?._windows.Remove((Window)sender!);
+            _activeLifetime?.HandleWindowClosed((Window)sender!);
         }
 
-        private static void OnWindowOpened(object sender, RoutedEventArgs e)
+        private static void OnWindowOpened(object? sender, RoutedEventArgs e)
         {
-            _activeLifetime?._windows.Add((Window)sender);
+            _activeLifetime?._windows.Add((Window)sender!);
         }
 
         public ClassicDesktopStyleApplicationLifetime()
@@ -46,24 +46,24 @@ namespace Avalonia.Controls.ApplicationLifetimes
         }
 
         /// <inheritdoc/>
-        public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
+        public event EventHandler<ControlledApplicationLifetimeStartupEventArgs>? Startup;
 
         /// <inheritdoc/>
-        public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
+        public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
 
         /// <inheritdoc/>
-        public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
+        public event EventHandler<ControlledApplicationLifetimeExitEventArgs>? Exit;
 
         /// <summary>
         /// Gets the arguments passed to the AppBuilder Start method.
         /// </summary>
-        public string[] Args { get; set; }
+        public string[]? Args { get; set; }
         
         /// <inheritdoc/>
         public ShutdownMode ShutdownMode { get; set; }
         
         /// <inheritdoc/>
-        public Window MainWindow { get; set; }
+        public Window? MainWindow { get; set; }
 
         public IReadOnlyList<Window> Windows => _windows.ToList();
 
@@ -183,7 +183,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
             return true;
         }
         
-        private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) => DoShutdown(e);
+        private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e);
     }
     
     public class ClassicDesktopStyleApplicationLifetimeOptions

+ 3 - 3
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
         /// <see cref="ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime{T}(T, string[], ShutdownMode)"/>
         /// method.
         /// </summary>
-        string[] Args { get; }
+        string[]? Args { get; }
         
         /// <summary>
         /// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly. 
@@ -38,7 +38,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
         /// <value>
         /// The main window.
         /// </value>
-        Window MainWindow { get; set; }
+        Window? MainWindow { get; set; }
         
         IReadOnlyList<Window> Windows { get; }
 
@@ -58,6 +58,6 @@ namespace Avalonia.Controls.ApplicationLifetimes
         /// will try to close each non-owned open window, invoking the <see cref="Window.Closing"/> event on each and allowing
         /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown.
         /// </remarks>
-        event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
+        event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
     }
 }

+ 1 - 1
src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs

@@ -2,6 +2,6 @@ namespace Avalonia.Controls.ApplicationLifetimes
 {
     public interface ISingleViewApplicationLifetime : IApplicationLifetime
     {
-        Control MainView { get; set; }
+        Control? MainView { get; set; }
     }
 }

文件差异内容过多而无法显示
+ 154 - 140
src/Avalonia.Controls/AutoCompleteBox.cs


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

@@ -18,4 +18,5 @@
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\JetBrains.Annotations.props" />
   <Import Project="..\..\build\ApiDiff.props" />
+  <Import Project="..\..\build\NullableEnable.props" />
 </Project>

+ 6 - 6
src/Avalonia.Controls/Border.cs

@@ -17,14 +17,14 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Background"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBrush> BackgroundProperty =
-            AvaloniaProperty.Register<Border, IBrush>(nameof(Background));
+        public static readonly StyledProperty<IBrush?> BackgroundProperty =
+            AvaloniaProperty.Register<Border, IBrush?>(nameof(Background));
 
         /// <summary>
         /// Defines the <see cref="BorderBrush"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBrush> BorderBrushProperty =
-            AvaloniaProperty.Register<Border, IBrush>(nameof(BorderBrush));
+        public static readonly StyledProperty<IBrush?> BorderBrushProperty =
+            AvaloniaProperty.Register<Border, IBrush?>(nameof(BorderBrush));
 
         /// <summary>
         /// Defines the <see cref="BorderThickness"/> property.
@@ -91,7 +91,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets a brush with which to paint the background.
         /// </summary>
-        public IBrush Background
+        public IBrush? Background
         {
             get { return GetValue(BackgroundProperty); }
             set { SetValue(BackgroundProperty, value); }
@@ -100,7 +100,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets a brush with which to paint the border.
         /// </summary>
-        public IBrush BorderBrush
+        public IBrush? BorderBrush
         {
             get { return GetValue(BorderBrushProperty); }
             set { SetValue(BorderBrushProperty, value); }

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

@@ -42,21 +42,21 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Command"/> property.
         /// </summary>
-        public static readonly DirectProperty<Button, ICommand> CommandProperty =
-            AvaloniaProperty.RegisterDirect<Button, ICommand>(nameof(Command),
+        public static readonly DirectProperty<Button, ICommand?> CommandProperty =
+            AvaloniaProperty.RegisterDirect<Button, ICommand?>(nameof(Command),
                 button => button.Command, (button, command) => button.Command = command, enableDataValidation: true);
 
         /// <summary>
         /// Defines the <see cref="HotKey"/> property.
         /// </summary>
-        public static readonly StyledProperty<KeyGesture> HotKeyProperty =
+        public static readonly StyledProperty<KeyGesture?> HotKeyProperty =
             HotKeyManager.HotKeyProperty.AddOwner<Button>();
 
         /// <summary>
         /// Defines the <see cref="CommandParameter"/> property.
         /// </summary>
-        public static readonly StyledProperty<object> CommandParameterProperty =
-            AvaloniaProperty.Register<Button, object>(nameof(CommandParameter));
+        public static readonly StyledProperty<object?> CommandParameterProperty =
+            AvaloniaProperty.Register<Button, object?>(nameof(CommandParameter));
 
         /// <summary>
         /// Defines the <see cref="IsDefault"/> property.
@@ -85,12 +85,12 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Flyout"/> property
         /// </summary>
-        public static readonly StyledProperty<FlyoutBase> FlyoutProperty =
-            AvaloniaProperty.Register<Button, FlyoutBase>(nameof(Flyout));
+        public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
+            AvaloniaProperty.Register<Button, FlyoutBase?>(nameof(Flyout));
 
-        private ICommand _command;
+        private ICommand? _command;
         private bool _commandCanExecute = true;
-        private KeyGesture _hotkey;
+        private KeyGesture? _hotkey;
 
         /// <summary>
         /// Initializes static members of the <see cref="Button"/> class.
@@ -112,7 +112,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Raised when the user clicks the button.
         /// </summary>
-        public event EventHandler<RoutedEventArgs> Click
+        public event EventHandler<RoutedEventArgs>? Click
         {
             add => AddHandler(ClickEvent, value);
             remove => RemoveHandler(ClickEvent, value);
@@ -130,7 +130,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets an <see cref="ICommand"/> to be invoked when the button is clicked.
         /// </summary>
-        public ICommand Command
+        public ICommand? Command
         {
             get => _command;
             set => SetAndRaise(CommandProperty, ref _command, value);
@@ -139,7 +139,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets an <see cref="KeyGesture"/> associated with this control
         /// </summary>
-        public KeyGesture HotKey
+        public KeyGesture? HotKey
         {
             get => GetValue(HotKeyProperty);
             set => SetValue(HotKeyProperty, value);
@@ -148,7 +148,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets a parameter to be passed to the <see cref="Command"/>.
         /// </summary>
-        public object CommandParameter
+        public object? CommandParameter
         {
             get => GetValue(CommandParameterProperty);
             set => SetValue(CommandParameterProperty, value);
@@ -186,7 +186,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the Flyout that should be shown with this button.
         /// </summary>
-        public FlyoutBase Flyout
+        public FlyoutBase? Flyout
         {
             get => GetValue(FlyoutProperty);
             set => SetValue(FlyoutProperty, value);
@@ -477,7 +477,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void CanExecuteChanged(object sender, EventArgs e)
+        private void CanExecuteChanged(object? sender, EventArgs e)
         {
             var canExecute = Command == null || Command.CanExecute(CommandParameter);
 
@@ -529,7 +529,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void RootDefaultKeyDown(object sender, KeyEventArgs e)
+        private void RootDefaultKeyDown(object? sender, KeyEventArgs e)
         {
             if (e.Key == Key.Enter && IsVisible && IsEnabled)
             {
@@ -542,7 +542,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void RootCancelKeyDown(object sender, KeyEventArgs e)
+        private void RootCancelKeyDown(object? sender, KeyEventArgs e)
         {
             if (e.Key == Key.Escape && IsVisible && IsEnabled)
             {

+ 7 - 7
src/Avalonia.Controls/ButtonSpinner.cs

@@ -42,11 +42,11 @@ namespace Avalonia.Controls
             UpdatePseudoClasses(ButtonSpinnerLocation);
         }
 
-        private Button _decreaseButton;
+        private Button? _decreaseButton;
         /// <summary>
         /// Gets or sets the DecreaseButton template part.
         /// </summary>
-        private Button DecreaseButton
+        private Button? DecreaseButton
         {
             get { return _decreaseButton; }
             set
@@ -63,11 +63,11 @@ namespace Avalonia.Controls
             }
         }
 
-        private Button _increaseButton;
+        private Button? _increaseButton;
         /// <summary>
         /// Gets or sets the IncreaseButton template part.
         /// </summary>
-        private Button IncreaseButton
+        private Button? IncreaseButton
         {
             get
             {
@@ -241,8 +241,8 @@ namespace Avalonia.Controls
         {
             if (e.Sender is ButtonSpinner spinner)
             {
-                var oldValue = (bool)e.OldValue;
-                var newValue = (bool)e.NewValue;
+                var oldValue = (bool)e.OldValue!;
+                var newValue = (bool)e.NewValue!;
                 spinner.OnAllowSpinChanged(oldValue, newValue);
             }
         }
@@ -268,7 +268,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private void OnButtonClick(object sender, RoutedEventArgs e)
+        private void OnButtonClick(object? sender, RoutedEventArgs e)
         {
             if (AllowSpin)
             {

+ 45 - 31
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -240,11 +240,11 @@ namespace Avalonia.Controls
         private bool _isShiftPressed;
         private bool _displayDateIsChanging = false;
 
-        internal CalendarDayButton FocusButton { get; set; }
-        internal CalendarButton FocusCalendarButton { get; set; }
+        internal CalendarDayButton? FocusButton { get; set; }
+        internal CalendarButton? FocusCalendarButton { get; set; }
 
-        internal Panel Root { get; set; }
-        internal CalendarItem MonthControl
+        internal Panel? Root { get; set; }
+        internal CalendarItem? MonthControl
         {
             get
             {
@@ -280,7 +280,7 @@ namespace Avalonia.Controls
         private void OnFirstDayOfWeekChanged(AvaloniaPropertyChangedEventArgs e)
         {
 
-            if (IsValidFirstDayOfWeek(e.NewValue))
+            if (IsValidFirstDayOfWeek(e.NewValue!))
             {
                 UpdateMonths();
             }
@@ -373,9 +373,9 @@ namespace Avalonia.Controls
         /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
         private void OnDisplayModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            CalendarMode mode = (CalendarMode)e.NewValue;
-            CalendarMode oldMode = (CalendarMode)e.OldValue;
-            CalendarItem monthControl = MonthControl;
+            CalendarMode mode = (CalendarMode)e.NewValue!;
+            CalendarMode oldMode = (CalendarMode)e.OldValue!;
+            CalendarItem? monthControl = MonthControl;
 
             if (monthControl != null)
             {
@@ -459,7 +459,7 @@ namespace Avalonia.Controls
         }
         private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            if (IsValidSelectionMode(e.NewValue))
+            if (IsValidSelectionMode(e.NewValue!))
             {
                 _displayDateIsChanging = true;
                 SelectedDate = null;
@@ -656,7 +656,7 @@ namespace Avalonia.Controls
                     {
                         FocusButton.IsCurrent = false;
                     }
-                    FocusButton = FindDayButtonFromDay(LastSelectedDate.Value);
+                    FocusButton = FindDayButtonFromDay(LastSelectedDate!.Value);
                     if (FocusButton != null)
                     {
                         FocusButton.IsCurrent = HasFocusInternal;
@@ -754,11 +754,11 @@ namespace Avalonia.Controls
 
         private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            UpdateDisplayDate(this, (DateTime)e.NewValue, (DateTime)e.OldValue);
+            UpdateDisplayDate(this, (DateTime)e.NewValue!, (DateTime)e.OldValue!);
         }
         private static void UpdateDisplayDate(Calendar c, DateTime addedDate, DateTime removedDate)
         {
-            Contract.Requires<ArgumentNullException>(c != null);
+            _ = c ?? throw new ArgumentNullException(nameof(c));
 
             // If DisplayDate < DisplayDateStart, DisplayDate = DisplayDateStart
             if (DateTime.Compare(addedDate, c.DisplayDateRangeStart) < 0)
@@ -871,7 +871,7 @@ namespace Avalonia.Controls
             if (cal.SelectedDates.Count > 0)
             {
                 selectedDateMin = cal.SelectedDates[0];
-                Debug.Assert(DateTime.Compare(cal.SelectedDate.Value, selectedDateMin) == 0, "The SelectedDate should be the minimum selected date!");
+                Debug.Assert(DateTime.Compare(cal.SelectedDate!.Value, selectedDateMin) == 0, "The SelectedDate should be the minimum selected date!");
             }
             else
             {
@@ -959,7 +959,7 @@ namespace Avalonia.Controls
             if (cal.SelectedDates.Count > 0)
             {
                 selectedDateMax = cal.SelectedDates[0];
-                Debug.Assert(DateTime.Compare(cal.SelectedDate.Value, selectedDateMax) == 0, "The SelectedDate should be the maximum SelectedDate!");
+                Debug.Assert(DateTime.Compare(cal.SelectedDate!.Value, selectedDateMax) == 0, "The SelectedDate should be the maximum SelectedDate!");
             }
             else
             {
@@ -1003,9 +1003,9 @@ namespace Avalonia.Controls
         /// </summary>
         internal bool CalendarDatePickerDisplayDateFlag { get; set; }
 
-        internal CalendarDayButton FindDayButtonFromDay(DateTime day)
+        internal CalendarDayButton? FindDayButtonFromDay(DateTime day)
         {
-            CalendarItem monthControl = MonthControl;
+            CalendarItem? monthControl = MonthControl;
 
             // REMOVE_RTM: should be updated if we support MultiCalendar
             int count = RowsPerMonth * ColumnsPerMonth;
@@ -1054,7 +1054,7 @@ namespace Avalonia.Controls
         internal void OnHeaderClick()
         {
             Debug.Assert(DisplayMode == CalendarMode.Year || DisplayMode == CalendarMode.Decade, "The DisplayMode should be Year or Decade");
-            CalendarItem monthControl = MonthControl;
+            CalendarItem? monthControl = MonthControl;
             if (monthControl != null && monthControl.MonthView != null && monthControl.YearView != null)
             {
                 monthControl.MonthView.IsVisible = false;
@@ -1065,7 +1065,7 @@ namespace Avalonia.Controls
 
         internal void ResetStates()
         {
-            CalendarItem monthControl = MonthControl;
+            CalendarItem? monthControl = MonthControl;
             int count = RowsPerMonth * ColumnsPerMonth;
             if (monthControl != null)
             {
@@ -1083,7 +1083,7 @@ namespace Avalonia.Controls
 
         internal void UpdateMonths()
         {
-            CalendarItem monthControl = MonthControl;
+            CalendarItem? monthControl = MonthControl;
             if (monthControl != null)
             {
                 switch (DisplayMode)
@@ -1163,6 +1163,8 @@ namespace Avalonia.Controls
         {
             if (HoverEnd != null && HoverStart != null)
             {
+                Debug.Assert(MonthControl is not null);
+
                 int startIndex, endIndex, i;
                 CalendarItem monthControl = MonthControl;
 
@@ -1173,7 +1175,7 @@ namespace Avalonia.Controls
 
                     for (i = startIndex; i <= endIndex; i++)
                     {
-                        if (monthControl.MonthView.Children[i] is CalendarDayButton b)
+                        if (monthControl.MonthView!.Children[i] is CalendarDayButton b)
                         {
                             b.IsSelected = true;
                             var d = b.DataContext as DateTime?;
@@ -1201,6 +1203,8 @@ namespace Avalonia.Controls
         {
             if (HoverEnd != null && HoverStart != null)
             {
+                Debug.Assert(MonthControl is not null);
+
                 CalendarItem monthControl = MonthControl;
 
                 if (HoverEndIndex != null && HoverStartIndex != null)
@@ -1212,7 +1216,7 @@ namespace Avalonia.Controls
                     {
                         for (i = startIndex; i <= endIndex; i++)
                         {
-                            if (monthControl.MonthView.Children[i] is CalendarDayButton b)
+                            if (monthControl.MonthView!.Children[i] is CalendarDayButton b)
                             {
                                 var d = b.DataContext as DateTime?;
 
@@ -1231,7 +1235,7 @@ namespace Avalonia.Controls
                         // It is SingleRange
                         for (i = startIndex; i <= endIndex; i++)
                         {
-                            ((CalendarDayButton)monthControl.MonthView.Children[i]).IsSelected = false;
+                            ((CalendarDayButton)monthControl.MonthView!.Children[i]).IsSelected = false;
                         }
                     }
                 }
@@ -1239,6 +1243,11 @@ namespace Avalonia.Controls
         }
         internal void SortHoverIndexes(out int startIndex, out int endIndex)
         {
+            Debug.Assert(HoverStart.HasValue);
+            Debug.Assert(HoverEnd.HasValue);
+            Debug.Assert(HoverStartIndex.HasValue);
+            Debug.Assert(HoverEndIndex.HasValue);
+
             if (DateTimeHelper.CompareDays(HoverEnd.Value, HoverStart.Value) > 0)
             {
                 startIndex = HoverStartIndex.Value;
@@ -1373,6 +1382,8 @@ namespace Avalonia.Controls
         }
         private void OnMonthClick()
         {
+            Debug.Assert(MonthControl is not null);
+
             CalendarItem monthControl = MonthControl;
             if (monthControl != null && monthControl.YearView != null && monthControl.MonthView != null)
             {
@@ -1400,7 +1411,7 @@ namespace Avalonia.Controls
             }
         }
 
-        public event EventHandler<SelectionChangedEventArgs> SelectedDatesChanged;
+        public event EventHandler<SelectionChangedEventArgs>? SelectedDatesChanged;
 
         /// <summary>
         /// Occurs when the
@@ -1410,19 +1421,19 @@ namespace Avalonia.Controls
         /// <remarks>
         /// This event occurs after DisplayDate is assigned its new value.
         /// </remarks>
-        public event EventHandler<CalendarDateChangedEventArgs> DisplayDateChanged;
+        public event EventHandler<CalendarDateChangedEventArgs>? DisplayDateChanged;
 
         /// <summary>
         /// Occurs when the
         /// <see cref="P:System.Windows.Controls.Calendar.DisplayMode" />
         /// property is changed.
         /// </summary>
-        public event EventHandler<CalendarModeChangedEventArgs> DisplayModeChanged;
+        public event EventHandler<CalendarModeChangedEventArgs>? DisplayModeChanged;
 
         /// <summary>
         /// Inherited code: Requires comment.
         /// </summary>
-        internal event EventHandler<PointerReleasedEventArgs> DayButtonMouseUp;
+        internal event EventHandler<PointerReleasedEventArgs>? DayButtonMouseUp;
 
         /// <summary>
         /// This method adds the days that were selected by Keyboard to the
@@ -1461,7 +1472,7 @@ namespace Avalonia.Controls
                     SelectedDates.ClearInternal();
                     if (shift)
                     {
-                        CalendarDayButton b;
+                        CalendarDayButton? b;
                         _isShiftPressed = true;
                         if (HoverStart == null)
                         {
@@ -1513,6 +1524,8 @@ namespace Avalonia.Controls
                             }
                             else
                             {
+                                Debug.Assert(HoverEndInternal is not null);
+
                                 // For Home, End, PageUp and PageDown Keys there
                                 // is no easy way to predict the index value
                                 b = FindDayButtonFromDay(HoverEndInternal.Value);
@@ -1524,6 +1537,7 @@ namespace Avalonia.Controls
                             }
                         }
 
+                        Debug.Assert(HoverEnd is not null);
                         OnDayClick(HoverEnd.Value);
                         HighlightDays();
                     }
@@ -1557,7 +1571,7 @@ namespace Avalonia.Controls
             base.OnPointerReleased(e);
             if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
             {
-                FocusManager.Instance.Focus(this);
+                FocusManager.Instance?.Focus(this);
             }
         }
 
@@ -1876,8 +1890,8 @@ namespace Avalonia.Controls
                                 // since DisplayDate is not equal to
                                 // DateTime.MaxValue we are sure selectedDate is\
                                 // not null
-                                selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1).Value;
-                                selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1).Value;
+                                selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
+                                selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
                             }
                             else
                             {
@@ -2098,7 +2112,7 @@ namespace Avalonia.Controls
 
             if (Root != null)
             {
-                CalendarItem month = e.NameScope.Find<CalendarItem>(PART_ElementMonth);
+                CalendarItem? month = e.NameScope.Find<CalendarItem>(PART_ElementMonth);
 
                 if (month != null)
                 {

+ 3 - 3
src/Avalonia.Controls/Calendar/CalendarButton.cs

@@ -45,7 +45,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets or sets the Calendar associated with this button.
         /// </summary>
-        internal Calendar Owner { get; set; }
+        internal Calendar? Owner { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether the button is focused.
@@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives
         /// stylus touches the tablet PC) while the mouse pointer is over a
         /// UIElement.
         /// </summary>
-        public event EventHandler<PointerPressedEventArgs> CalendarLeftMouseButtonDown;
+        public event EventHandler<PointerPressedEventArgs>? CalendarLeftMouseButtonDown;
 
         /// <summary>
         /// Occurs when the left mouse button is released (or the tip of the
@@ -128,7 +128,7 @@ namespace Avalonia.Controls.Primitives
         /// stylus) is over a UIElement (or while a UIElement holds mouse
         /// capture).
         /// </summary>
-        public event EventHandler<PointerReleasedEventArgs> CalendarLeftMouseButtonUp;
+        public event EventHandler<PointerReleasedEventArgs>? CalendarLeftMouseButtonUp;
 
         /// <summary>
         /// Provides class handling for the MouseLeftButtonDown event that

+ 45 - 45
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@@ -123,15 +123,15 @@ namespace Avalonia.Controls
         private const string ElementPopup = "PART_Popup";
         private const string ElementCalendar = "PART_Calendar";
 
-        private Calendar _calendar;
+        private Calendar? _calendar;
         private string _defaultText;
-        private Button _dropDownButton;
+        private Button? _dropDownButton;
         //private Canvas _outsideCanvas;
         //private Canvas _outsidePopupCanvas;
-        private Popup _popUp;
-        private TextBox _textBox;
-        private IDisposable _textBoxTextChangedSubscription;
-        private IDisposable _buttonPointerPressedSubscription;
+        private Popup? _popUp;
+        private TextBox? _textBox;
+        private IDisposable? _textBoxTextChangedSubscription;
+        private IDisposable? _buttonPointerPressedSubscription;
 
         private DateTime? _onOpenSelectedDate;
         private bool _settingSelectedDate;
@@ -141,7 +141,7 @@ namespace Avalonia.Controls
         private DateTime? _displayDateEnd;
         private bool _isDropDownOpen;
         private DateTime? _selectedDate;
-        private string _text;
+        private string? _text;
         private bool _suspendTextChangeHandler = false;
         private bool _isPopupClosing = false;
         private bool _ignoreButtonClick = false;
@@ -153,7 +153,7 @@ namespace Avalonia.Controls
         /// A collection of dates that cannot be selected. The default value is
         /// an empty collection.
         /// </value>
-        public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
+        public CalendarBlackoutDatesCollection? BlackoutDates { get; private set; }
 
         public static readonly DirectProperty<CalendarDatePicker, DateTime> DisplayDateProperty =
             AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime>(
@@ -186,7 +186,8 @@ namespace Avalonia.Controls
                 nameof(SelectedDate),
                 o => o.SelectedDate,
                 (o, v) => o.SelectedDate = v,
-                enableDataValidation: true);
+                enableDataValidation: true, 
+                defaultBindingMode:BindingMode.TwoWay);
 
         public static readonly StyledProperty<CalendarDatePickerFormat> SelectedDateFormatProperty =
             AvaloniaProperty.Register<CalendarDatePicker, CalendarDatePickerFormat>(
@@ -200,12 +201,12 @@ namespace Avalonia.Controls
                 defaultValue: "d",
                 validate: IsValidDateFormatString);
 
-        public static readonly DirectProperty<CalendarDatePicker, string> TextProperty =
-            AvaloniaProperty.RegisterDirect<CalendarDatePicker, string>(
+        public static readonly DirectProperty<CalendarDatePicker, string?> TextProperty =
+            AvaloniaProperty.RegisterDirect<CalendarDatePicker, string?>(
                 nameof(Text),
                 o => o.Text,
                 (o, v) => o.Text = v);
-        public static readonly StyledProperty<string> WatermarkProperty =
+        public static readonly StyledProperty<string?> WatermarkProperty =
             TextBox.WatermarkProperty.AddOwner<CalendarDatePicker>();
         public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
             TextBox.UseFloatingWatermarkProperty.AddOwner<CalendarDatePicker>();
@@ -361,13 +362,13 @@ namespace Avalonia.Controls
         /// <exception cref="T:System.ArgumentOutOfRangeException">
         /// The text entered parses to a date that is not selectable.
         /// </exception>
-        public string Text
+        public string? Text
         {
             get { return _text; }
             set { SetAndRaise(TextProperty, ref _text, value); }
         }
 
-        public string Watermark
+        public string? Watermark
         {
             get { return GetValue(WatermarkProperty); }
             set { SetValue(WatermarkProperty, value); }
@@ -401,26 +402,26 @@ namespace Avalonia.Controls
         /// Occurs when the drop-down
         /// <see cref="T:Avalonia.Controls.Calendar" /> is closed.
         /// </summary>
-        public event EventHandler CalendarClosed;
+        public event EventHandler? CalendarClosed;
 
         /// <summary>
         /// Occurs when the drop-down
         /// <see cref="T:Avalonia.Controls.Calendar" /> is opened.
         /// </summary>
-        public event EventHandler CalendarOpened;
+        public event EventHandler? CalendarOpened;
 
         /// <summary>
         /// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
         /// is assigned a value that cannot be interpreted as a date.
         /// </summary>
-        public event EventHandler<CalendarDatePickerDateValidationErrorEventArgs> DateValidationError;
+        public event EventHandler<CalendarDatePickerDateValidationErrorEventArgs>? DateValidationError;
 
         /// <summary>
         /// Occurs when the
         /// <see cref="P:Avalonia.Controls.CalendarDatePicker.SelectedDate" />
         /// property is changed.
         /// </summary>
-        public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged;
+        public event EventHandler<SelectionChangedEventArgs>? SelectedDateChanged;
 
         static CalendarDatePicker()
         {
@@ -579,14 +580,14 @@ namespace Avalonia.Controls
         
         private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var oldValue = (bool)e.OldValue;
-            var value = (bool)e.NewValue;
+            var oldValue = (bool)e.OldValue!;
+            var value = (bool)e.NewValue!;
 
             if (_popUp != null && _popUp.Child != null)
             {
                 if (value != oldValue)
                 {
-                    if (_calendar.DisplayMode != CalendarMode.Month)
+                    if (_calendar!.DisplayMode != CalendarMode.Month)
                     {
                         _calendar.DisplayMode = CalendarMode.Month;
                     }
@@ -660,7 +661,7 @@ namespace Avalonia.Controls
 
                     if (date != null)
                     {
-                        string s = DateTimeToString((DateTime)date);
+                        string? s = DateTimeToString((DateTime)date);
                         Text = s;
                     }
                 }
@@ -679,8 +680,8 @@ namespace Avalonia.Controls
         }
         private void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var oldValue = (string)e.OldValue;
-            var value = (string)e.NewValue;
+            var oldValue = (string?)e.OldValue;
+            var value = (string?)e.NewValue;
 
             if (!_suspendTextChangeHandler)
             {
@@ -731,7 +732,7 @@ namespace Avalonia.Controls
         }
         private void OnDateSelected(DateTime? addedDate, DateTime? removedDate)
         {
-            EventHandler<SelectionChangedEventArgs> handler = this.SelectedDateChanged;
+            EventHandler<SelectionChangedEventArgs>? handler = this.SelectedDateChanged;
             if (null != handler)
             {
                 Collection<DateTime> addedItems = new Collection<DateTime>();
@@ -759,23 +760,23 @@ namespace Avalonia.Controls
             CalendarOpened?.Invoke(this, e);
         }
 
-        private void Calendar_DayButtonMouseUp(object sender, PointerReleasedEventArgs e)
+        private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e)
         {
             Focus();
             IsDropDownOpen = false;
         }      
-        private void Calendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
+        private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e)
         {
             if (e.AddedDate != this.DisplayDate)
             {
-                SetValue(DisplayDateProperty, (DateTime) e.AddedDate);
+                SetValue(DisplayDateProperty, (DateTime) e.AddedDate!);
             }
         }
-        private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
+        private void Calendar_SelectedDatesChanged(object? sender, SelectionChangedEventArgs e)
         {
             Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!");
 
-            if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0], SelectedDate.Value) != 0)
+            if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0)
             {
                 SelectedDate = (DateTime?)e.AddedItems[0];
             }
@@ -796,7 +797,7 @@ namespace Avalonia.Controls
                 }
             }
         }
-        private void Calendar_PointerReleased(object sender, PointerReleasedEventArgs e)
+        private void Calendar_PointerReleased(object? sender, PointerReleasedEventArgs e)
         {
              
             if (e.InitialPressMouseButton == MouseButton.Left)
@@ -804,10 +805,9 @@ namespace Avalonia.Controls
                 e.Handled = true;
             }
         }
-        private void Calendar_KeyDown(object sender, KeyEventArgs e)
+        private void Calendar_KeyDown(object? sender, KeyEventArgs e)
         {
-            Calendar c = sender as Calendar;
-            Contract.Requires<ArgumentNullException>(c != null);
+            Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender));
 
             if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
             {
@@ -820,11 +820,11 @@ namespace Avalonia.Controls
                 }
             }
         }
-        private void TextBox_GotFocus(object sender, RoutedEventArgs e)
+        private void TextBox_GotFocus(object? sender, RoutedEventArgs e)
         {
             IsDropDownOpen = false;
         }
-        private void TextBox_KeyDown(object sender, KeyEventArgs e)
+        private void TextBox_KeyDown(object? sender, KeyEventArgs e)
         {
             if (!e.Handled)
             {
@@ -840,11 +840,11 @@ namespace Avalonia.Controls
                 _suspendTextChangeHandler = false;
             }
         }
-        private void DropDownButton_PointerPressed(object sender, PointerPressedEventArgs e)
+        private void DropDownButton_PointerPressed(object? sender, PointerPressedEventArgs e)
         {
             _ignoreButtonClick = _isPopupClosing;
         }
-        private void DropDownButton_Click(object sender, RoutedEventArgs e)
+        private void DropDownButton_Click(object? sender, RoutedEventArgs e)
         {
             if (!_ignoreButtonClick)
             {
@@ -855,7 +855,7 @@ namespace Avalonia.Controls
                 _ignoreButtonClick = false;
             }
         }
-        private void PopUp_Closed(object sender, EventArgs e)
+        private void PopUp_Closed(object? sender, EventArgs e)
         {
             IsDropDownOpen = false;
 
@@ -891,7 +891,7 @@ namespace Avalonia.Controls
         private void OpenPopUp()
         {
             _onOpenSelectedDate = SelectedDate;
-            _popUp.IsOpen = true;
+            _popUp!.IsOpen = true;
         }
 
         /// <summary>
@@ -914,7 +914,7 @@ namespace Avalonia.Controls
             {
                 newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
 
-                if (Calendar.IsValidDateSelection(this._calendar, newSelectedDate))
+                if (Calendar.IsValidDateSelection(this._calendar!, newSelectedDate))
                 {
                     return newSelectedDate;
                 }
@@ -941,7 +941,7 @@ namespace Avalonia.Controls
             }
             return null;
         }
-        private string DateTimeToString(DateTime d)
+        private string? DateTimeToString(DateTime d)
         {
             DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
 
@@ -982,7 +982,7 @@ namespace Avalonia.Controls
         {
             SetSelectedDate();
             IsDropDownOpen = true;
-            _calendar.Focus();
+            _calendar!.Focus();
         }
         private void SetSelectedDate()
         {
@@ -1001,7 +1001,7 @@ namespace Avalonia.Controls
                         // ex: SelectedDate = DateTime(1008,12,19) but when
                         // "12/19/08" is parsed it is interpreted as
                         // DateTime(2008,12,19)
-                        string selectedDate = DateTimeToString(SelectedDate.Value);
+                        string? selectedDate = DateTimeToString(SelectedDate.Value);
                         if (selectedDate == s)
                         {
                             return;
@@ -1053,7 +1053,7 @@ namespace Avalonia.Controls
                     // SelectedDate value:
                     if (SelectedDate != null)
                     {
-                        string newtext = this.DateTimeToString(SelectedDate.Value);
+                        string? newtext = this.DateTimeToString(SelectedDate.Value);
                         SetValue(TextProperty, newtext);
                         return SelectedDate;
                     }

+ 1 - 1
src/Avalonia.Controls/Calendar/CalendarDateRange.cs

@@ -65,7 +65,7 @@ namespace Avalonia.Controls
         /// <returns>Inherited code: Requires comment 2.</returns>
         internal bool ContainsAny(CalendarDateRange range)
         {
-            Contract.Requires<ArgumentNullException>(range != null);
+            _ = range ?? throw new ArgumentNullException(nameof(range));
 
             int start = DateTime.Compare(Start, range.Start);
 

+ 3 - 3
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@@ -40,7 +40,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets or sets the Calendar associated with this button.
         /// </summary>
-        internal Calendar Owner { get; set; }
+        internal Calendar? Owner { get; set; }
         internal int Index { get; set; }
 
         /// <summary>
@@ -177,7 +177,7 @@ namespace Avalonia.Controls.Primitives
         /// stylus touches the tablet PC) while the mouse pointer is over a
         /// UIElement.
         /// </summary>
-        public event EventHandler<PointerPressedEventArgs> CalendarDayButtonMouseDown;
+        public event EventHandler<PointerPressedEventArgs>? CalendarDayButtonMouseDown;
 
         /// <summary>
         /// Occurs when the left mouse button is released (or the tip of the
@@ -185,7 +185,7 @@ namespace Avalonia.Controls.Primitives
         /// stylus) is over a UIElement (or while a UIElement holds mouse
         /// capture).
         /// </summary>
-        public event EventHandler<PointerReleasedEventArgs> CalendarDayButtonMouseUp;
+        public event EventHandler<PointerReleasedEventArgs>? CalendarDayButtonMouseUp;
 
         /// <summary>
         /// Provides class handling for the MouseLeftButtonDown event that

+ 46 - 54
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -33,10 +33,10 @@ namespace Avalonia.Controls.Primitives
         private const string PART_ElementMonthView = "MonthView";
         private const string PART_ElementYearView = "YearView";
 
-        private Button _headerButton;
-        private Button _nextButton;
-        private Button _previousButton;
-        private ITemplate<IControl> _dayTitleTemplate;
+        private Button? _headerButton;
+        private Button? _nextButton;
+        private Button? _previousButton;
+        private ITemplate<IControl>? _dayTitleTemplate;
         
         private DateTime _currentMonth;
         private bool _isMouseLeftButtonDown = false;
@@ -45,11 +45,11 @@ namespace Avalonia.Controls.Primitives
 
         private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar();
 
-        private PointerPressedEventArgs _downEventArg;
-        private PointerPressedEventArgs _downEventArgYearView;
+        private PointerPressedEventArgs? _downEventArg;
+        private PointerPressedEventArgs? _downEventArgYearView;
 
-        internal Calendar Owner { get; set; }
-        internal CalendarDayButton CurrentButton { get; set; }
+        internal Calendar? Owner { get; set; }
+        internal CalendarDayButton? CurrentButton { get; set; }
 
         public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
         public IBrush HeaderBackground
@@ -57,13 +57,13 @@ namespace Avalonia.Controls.Primitives
             get { return GetValue(HeaderBackgroundProperty); }
             set { SetValue(HeaderBackgroundProperty, value); }
         }
-        public static readonly DirectProperty<CalendarItem, ITemplate<IControl>> DayTitleTemplateProperty =
-                AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<IControl>>(
+        public static readonly DirectProperty<CalendarItem, ITemplate<IControl>?> DayTitleTemplateProperty =
+                AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<IControl>?>(
                     nameof(DayTitleTemplate),
                     o => o.DayTitleTemplate,
                     (o,v) => o.DayTitleTemplate = v,
                     defaultBindingMode: BindingMode.OneTime);
-        public ITemplate<IControl> DayTitleTemplate
+        public ITemplate<IControl>? DayTitleTemplate
         {
             get { return _dayTitleTemplate; }
             set { SetAndRaise(DayTitleTemplateProperty, ref _dayTitleTemplate, value); }
@@ -73,7 +73,7 @@ namespace Avalonia.Controls.Primitives
         /// Gets the button that allows switching between month mode, year mode,
         /// and decade mode. 
         /// </summary>
-        internal Button HeaderButton
+        internal Button? HeaderButton
         {
             get { return _headerButton; }
             private set
@@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives
         /// Gets the button that displays the next page of the calendar when it
         /// is clicked.
         /// </summary>
-        internal Button NextButton
+        internal Button? NextButton
         {
             get { return _nextButton; }
             private set
@@ -125,7 +125,7 @@ namespace Avalonia.Controls.Primitives
         /// Gets the button that displays the previous page of the calendar when
         /// it is clicked.
         /// </summary>
-        internal Button PreviousButton
+        internal Button? PreviousButton
         {
             get { return _previousButton; }
             private set
@@ -156,11 +156,11 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the Grid that hosts the content when in month mode.
         /// </summary>
-        internal Grid MonthView { get; set; }
+        internal Grid? MonthView { get; set; }
         /// <summary>
         /// Gets the Grid that hosts the content when in year or decade mode.
         /// </summary>
-        internal Grid YearView { get; set; }
+        internal Grid? YearView { get; set; }
         
         private void PopulateGrids()
         {
@@ -294,7 +294,7 @@ namespace Avalonia.Controls.Primitives
         {
             for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)
             {
-                var daytitle = MonthView.Children[childIndex];
+                var daytitle = MonthView!.Children[childIndex];
                 if (daytitle != null)
                 {
                     if (Owner != null)
@@ -495,8 +495,7 @@ namespace Avalonia.Controls.Primitives
 
             for (int childIndex = Calendar.ColumnsPerMonth; childIndex < count; childIndex++)
             {
-                CalendarDayButton childButton = MonthView.Children[childIndex] as CalendarDayButton;
-                Contract.Requires<ArgumentNullException>(childButton != null);
+                CalendarDayButton childButton = (CalendarDayButton)MonthView!.Children[childIndex];
 
                 childButton.Index = childIndex;
                 SetButtonState(childButton, dateToAdd);
@@ -532,8 +531,7 @@ namespace Avalonia.Controls.Primitives
                     childIndex++;
                     for (int i = childIndex; i < count; i++)
                     {
-                        childButton = MonthView.Children[i] as CalendarDayButton;
-                        Contract.Requires<ArgumentNullException>(childButton != null);
+                        childButton = (CalendarDayButton)MonthView.Children[i];
                         // button needs a content to occupy the necessary space
                         // for the content presenter
                         childButton.Content = i.ToString(DateTimeHelper.GetCurrentDateFormat());
@@ -626,10 +624,9 @@ namespace Avalonia.Controls.Primitives
         private void SetMonthButtonsForYearMode()
         {
             int count = 0;
-            foreach (object child in YearView.Children)
+            foreach (object child in YearView!.Children)
             {
-                CalendarButton childButton = child as CalendarButton;
-                Contract.Requires<ArgumentNullException>(childButton != null);
+                CalendarButton childButton = (CalendarButton)child;
                 // There should be no time component. Time is 12:00 AM
                 DateTime day = new DateTime(_currentMonth.Year, count + 1, 1);
                 childButton.DataContext = day;
@@ -703,7 +700,7 @@ namespace Avalonia.Controls.Primitives
         {
             if (Owner != null && calendarButton != null && calendarButton.DataContext != null)
             {
-                Owner.FocusCalendarButton.IsCalendarButtonFocused = false;
+                Owner.FocusCalendarButton!.IsCalendarButtonFocused = false;
                 Owner.FocusCalendarButton = calendarButton;
                 calendarButton.IsCalendarButtonFocused = Owner.HasFocusInternal;
 
@@ -722,10 +719,9 @@ namespace Avalonia.Controls.Primitives
         {
             int year;
             int count = -1;
-            foreach (object child in YearView.Children)
+            foreach (object child in YearView!.Children)
             {
-                CalendarButton childButton = child as CalendarButton;
-                Contract.Requires<ArgumentNullException>(childButton != null);
+                CalendarButton childButton = (CalendarButton)child;
                 year = decade + count;
 
                 if (year <= DateTime.MaxValue.Year && year >= DateTime.MinValue.Year)
@@ -797,7 +793,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        internal void HeaderButton_Click(object sender, RoutedEventArgs e)
+        internal void HeaderButton_Click(object? sender, RoutedEventArgs e)
         {
             if (Owner != null)
             {
@@ -805,7 +801,7 @@ namespace Avalonia.Controls.Primitives
                 {
                     Owner.Focus();
                 }
-                Button b = (Button)sender;
+                Button b = (Button)sender!;
                 DateTime d;
 
                 if (b.IsEnabled)
@@ -833,7 +829,7 @@ namespace Avalonia.Controls.Primitives
                 }
             }
         }
-        internal void PreviousButton_Click(object sender, RoutedEventArgs e)
+        internal void PreviousButton_Click(object? sender, RoutedEventArgs e)
         {
             if (Owner != null)
             {
@@ -842,14 +838,14 @@ namespace Avalonia.Controls.Primitives
                     Owner.Focus();
                 }
 
-                Button b = (Button)sender;
+                Button b = (Button)sender!;
                 if (b.IsEnabled)
                 {
                     Owner.OnPreviousClick();
                 }
             }
         }
-        internal void NextButton_Click(object sender, RoutedEventArgs e)
+        internal void NextButton_Click(object? sender, RoutedEventArgs e)
         {
             if (Owner != null)
             {
@@ -857,7 +853,7 @@ namespace Avalonia.Controls.Primitives
                 {
                     Owner.Focus();
                 }
-                Button b = (Button)sender;
+                Button b = (Button)sender!;
 
                 if (b.IsEnabled)
                 {
@@ -866,7 +862,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        internal void Cell_MouseEnter(object sender, PointerEventArgs e)
+        internal void Cell_MouseEnter(object? sender, PointerEventArgs e)
         {
             if (Owner != null)
             {
@@ -878,7 +874,7 @@ namespace Avalonia.Controls.Primitives
                     {
                         case CalendarSelectionMode.SingleDate:
                             {
-                                DateTime selectedDate = (DateTime)b.DataContext;
+                                DateTime selectedDate = (DateTime)b.DataContext!;
                                 Owner.CalendarDatePickerDisplayDateFlag = true;
                                 if (Owner.SelectedDates.Count == 0)
                                 {
@@ -906,7 +902,7 @@ namespace Avalonia.Controls.Primitives
             }
         }
         
-        internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e)
+        internal void Cell_MouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
         {
             if (Owner != null)
             {
@@ -917,15 +913,14 @@ namespace Avalonia.Controls.Primitives
 
                 bool ctrl, shift;
                 CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift);
-                CalendarDayButton b = sender as CalendarDayButton;
+                CalendarDayButton b = (CalendarDayButton)sender!;
 
                 if (b != null)
                 {
                     _isControlPressed = ctrl;
                     if (b.IsEnabled && !b.IsBlackout)
                     {
-                        DateTime selectedDate = (DateTime)b.DataContext;
-                        Contract.Requires<ArgumentNullException>(selectedDate != null);
+                        DateTime selectedDate = (DateTime)b.DataContext!;
                         _isMouseLeftButtonDown = true;
                         // null check is added for unit tests
                         if (e != null)
@@ -1027,7 +1022,7 @@ namespace Avalonia.Controls.Primitives
             if (Owner != null)
             {
                 Owner.HoverEndIndex = b.Index;
-                Owner.HoverEnd = (DateTime)b.DataContext;
+                Owner.HoverEnd = (DateTime)b.DataContext!;
 
                 if (Owner.HoverEnd != null && Owner.HoverStart != null)
                 {
@@ -1041,11 +1036,11 @@ namespace Avalonia.Controls.Primitives
                 }
             }
         }
-        internal void Cell_MouseLeftButtonUp(object sender, PointerReleasedEventArgs e)
+        internal void Cell_MouseLeftButtonUp(object? sender, PointerReleasedEventArgs e)
         {
             if (Owner != null)
             {
-                CalendarDayButton b = sender as CalendarDayButton;
+                CalendarDayButton? b = sender as CalendarDayButton;
                 if (b != null && !b.IsBlackout)
                 {
                     Owner.OnDayButtonMouseUp(e);
@@ -1094,14 +1089,13 @@ namespace Avalonia.Controls.Primitives
                 }
             }
         }
-        private void Cell_Click(object sender, RoutedEventArgs e)
+        private void Cell_Click(object? sender, RoutedEventArgs e)
         {
             if (Owner != null)
             {
                 if (_isControlPressed && Owner.SelectionMode == CalendarSelectionMode.MultipleRange)
                 {
-                    CalendarDayButton b = sender as CalendarDayButton;
-                    Contract.Requires<ArgumentNullException>(b != null);
+                    CalendarDayButton b = (CalendarDayButton)sender!;
 
                     if (b.IsSelected)
                     {
@@ -1118,10 +1112,9 @@ namespace Avalonia.Controls.Primitives
             _isControlPressed = false;
         }
 
-        private void Month_CalendarButtonMouseDown(object sender, PointerPressedEventArgs e)
+        private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e)
         {
-            CalendarButton b = sender as CalendarButton;
-            Contract.Requires<ArgumentNullException>(b != null);
+            CalendarButton b = (CalendarButton)sender!;
 
             _isMouseLeftButtonDownYearView = true;
 
@@ -1133,13 +1126,13 @@ namespace Avalonia.Controls.Primitives
             UpdateYearViewSelection(b);
         }
 
-        internal void Month_CalendarButtonMouseUp(object sender, PointerReleasedEventArgs e)
+        internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e)
         {
             _isMouseLeftButtonDownYearView = false;
 
             if (Owner != null)
             {
-                DateTime newmonth = (DateTime)((CalendarButton)sender).DataContext;
+                DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!;
 
                 if (Owner.DisplayMode == CalendarMode.Year)
                 {
@@ -1155,12 +1148,11 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        private void Month_MouseEnter(object sender, PointerEventArgs e)
+        private void Month_MouseEnter(object? sender, PointerEventArgs e)
         {
             if (_isMouseLeftButtonDownYearView)
             {
-                CalendarButton b = sender as CalendarButton;
-                Contract.Requires<ArgumentNullException>(b != null);
+                CalendarButton b = (CalendarButton)sender!;
                 UpdateYearViewSelection(b);
             }
         }

+ 2 - 0
src/Avalonia.Controls/Calendar/DateTimeHelper.cs

@@ -5,6 +5,7 @@
 
 using System;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 
 namespace Avalonia.Controls
@@ -73,6 +74,7 @@ namespace Avalonia.Controls
             return newD;
         }
 
+        [return: NotNullIfNotNull("d")]
         public static DateTime? DiscardTime(DateTime? d)
         {
             if (d == null)

+ 2 - 2
src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs

@@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives
 
         private void InvokeCollectionChanged(System.Collections.IList removedItems, System.Collections.IList addedItems)
         {
-            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, removedItems, addedItems));
+            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, removedItems, addedItems));
         }
 
         /// <summary>
@@ -119,7 +119,7 @@ namespace Avalonia.Controls.Primitives
                 }
             }
 
-            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, _owner.RemovedItems, _addedItems));
+            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, _owner.RemovedItems, _addedItems));
             _owner.RemovedItems.Clear();
             _owner.UpdateMonths();
             _isRangeAdded = false;

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

@@ -135,7 +135,7 @@ namespace Avalonia.Controls
         /// <param name="from">The control from which movement begins.</param>
         /// <param name="wrap">Whether to wrap around when the first or last item is reached.</param>
         /// <returns>The control.</returns>
-        IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
+        IInputElement? INavigableContainer.GetControl(NavigationDirection direction, IInputElement? from, bool wrap)
         {
             // TODO: Implement this
             return null;

+ 0 - 2
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@@ -3,8 +3,6 @@ using System.Reactive.Disposables;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 
-#nullable enable
-
 namespace Avalonia.Controls.Chrome
 {
     /// <summary>

+ 1 - 3
src/Avalonia.Controls/Chrome/TitleBar.cs

@@ -3,8 +3,6 @@ using System.Reactive.Disposables;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 
-#nullable enable
-
 namespace Avalonia.Controls.Chrome
 {
     /// <summary>
@@ -36,7 +34,7 @@ namespace Avalonia.Controls.Chrome
                     }
                 }
 
-                IsVisible = window.PlatformImpl.NeedsManagedDecorations;
+                IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false;
             }
         }
 

+ 42 - 23
src/Avalonia.Controls/ComboBox.cs

@@ -10,9 +10,7 @@ using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
-using Avalonia.LogicalTree;
 using Avalonia.Media;
-using Avalonia.Threading;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
@@ -46,8 +44,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="SelectionBoxItem"/> property.
         /// </summary>
-        public static readonly DirectProperty<ComboBox, object> SelectionBoxItemProperty =
-            AvaloniaProperty.RegisterDirect<ComboBox, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
+        public static readonly DirectProperty<ComboBox, object?> SelectionBoxItemProperty =
+            AvaloniaProperty.RegisterDirect<ComboBox, object?>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
 
         /// <summary>
         /// Defines the <see cref="VirtualizationMode"/> property.
@@ -58,14 +56,14 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="PlaceholderText"/> property.
         /// </summary>
-        public static readonly StyledProperty<string> PlaceholderTextProperty =
-            AvaloniaProperty.Register<ComboBox, string>(nameof(PlaceholderText));
+        public static readonly StyledProperty<string?> PlaceholderTextProperty =
+            AvaloniaProperty.Register<ComboBox, string?>(nameof(PlaceholderText));
 
         /// <summary>
         /// Defines the <see cref="PlaceholderForeground"/> property.
         /// </summary>
-        public static readonly StyledProperty<IBrush> PlaceholderForegroundProperty =
-            AvaloniaProperty.Register<ComboBox, IBrush>(nameof(PlaceholderForeground));
+        public static readonly StyledProperty<IBrush?> PlaceholderForegroundProperty =
+            AvaloniaProperty.Register<ComboBox, IBrush?>(nameof(PlaceholderForeground));
 
         /// <summary>
         /// Defines the <see cref="HorizontalContentAlignment"/> property.
@@ -80,8 +78,8 @@ namespace Avalonia.Controls
             ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
 
         private bool _isDropDownOpen;
-        private Popup _popup;
-        private object _selectionBoxItem;
+        private Popup? _popup;
+        private object? _selectionBoxItem;
         private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
 
         /// <summary>
@@ -91,7 +89,7 @@ namespace Avalonia.Controls
         {
             ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
             FocusableProperty.OverrideDefaultValue<ComboBox>(true);
-            SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e));
+            SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x, e) => x.SelectedItemChanged(e));
             KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
             IsTextSearchEnabledProperty.OverrideDefaultValue<ComboBox>(true);
         }
@@ -117,7 +115,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the item to display as the control's content.
         /// </summary>
-        protected object SelectionBoxItem
+        protected object? SelectionBoxItem
         {
             get { return _selectionBoxItem; }
             set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
@@ -126,7 +124,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the PlaceHolder text.
         /// </summary>
-        public string PlaceholderText
+        public string? PlaceholderText
         {
             get { return GetValue(PlaceholderTextProperty); }
             set { SetValue(PlaceholderTextProperty, value); }
@@ -135,7 +133,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the Brush that renders the placeholder text.
         /// </summary>
-        public IBrush PlaceholderForeground
+        public IBrush? PlaceholderForeground
         {
             get { return GetValue(PlaceholderForegroundProperty); }
             set { SetValue(PlaceholderForegroundProperty, value); }
@@ -221,8 +219,9 @@ namespace Avalonia.Controls
                     e.Handled = true;
                 }
             }
+            // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl.
             else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
-                      (e.Key == Key.Up || e.Key == Key.Down))
+                      (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true)
             {
                 var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
                 if (firstChild != null)
@@ -262,9 +261,9 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
-            if (!e.Handled)
+            if (!e.Handled && e.Source is IVisual source)
             {
-                if (_popup?.IsInsidePopup((IVisual)e.Source) == true)
+                if (_popup?.IsInsidePopup(source) == true)
                 {
                     if (UpdateSelectionFromEventSource(e.Source))
                     {
@@ -304,7 +303,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private void PopupClosed(object sender, EventArgs e)
+        private void PopupClosed(object? sender, EventArgs e)
         {
             _subscriptionsOnOpen.Clear();
 
@@ -314,7 +313,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private void PopupOpened(object sender, EventArgs e)
+        private void PopupOpened(object? sender, EventArgs e)
         {
             TryFocusSelectedItem();
 
@@ -326,7 +325,7 @@ namespace Avalonia.Controls
                 toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) =>
                 {
                     //eat wheel scroll event outside dropdown popup while it's open
-                    if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)
+                    if (IsDropDownOpen && (ev.Source as IVisual)?.GetVisualRoot() == toplevel)
                     {
                         ev.Handled = true;
                     }
@@ -377,7 +376,7 @@ namespace Avalonia.Controls
 
         private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible;
 
-        private void UpdateSelectionBoxItem(object item)
+        private void UpdateSelectionBoxItem(object? item)
         {
             var contentControl = item as IContentControl;
 
@@ -430,7 +429,18 @@ namespace Avalonia.Controls
             int next = SelectedIndex + 1;
 
             if (next >= ItemCount)
-                next = 0;
+            {
+                if (WrapSelection == true)
+                {
+                    next = 0;
+                }
+                else
+                {
+                    return;
+                }
+            }
+
+
 
             SelectedIndex = next;
         }
@@ -440,7 +450,16 @@ namespace Avalonia.Controls
             int prev = SelectedIndex - 1;
 
             if (prev < 0)
-                prev = ItemCount - 1;
+            {
+                if (WrapSelection == true)
+                {
+                    prev = ItemCount - 1;
+                }
+                else
+                {
+                    return;
+                }
+            }
 
             SelectedIndex = prev;
         }

+ 7 - 7
src/Avalonia.Controls/ContentControl.cs

@@ -17,14 +17,14 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Content"/> property.
         /// </summary>
-        public static readonly StyledProperty<object> ContentProperty =
-            AvaloniaProperty.Register<ContentControl, object>(nameof(Content));
+        public static readonly StyledProperty<object?> ContentProperty =
+            AvaloniaProperty.Register<ContentControl, object?>(nameof(Content));
 
         /// <summary>
         /// Defines the <see cref="ContentTemplate"/> property.
         /// </summary>
-        public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
-            AvaloniaProperty.Register<ContentControl, IDataTemplate>(nameof(ContentTemplate));
+        public static readonly StyledProperty<IDataTemplate?> ContentTemplateProperty =
+            AvaloniaProperty.Register<ContentControl, IDataTemplate?>(nameof(ContentTemplate));
 
         /// <summary>
         /// Defines the <see cref="HorizontalContentAlignment"/> property.
@@ -48,7 +48,7 @@ namespace Avalonia.Controls
         /// </summary>
         [Content]
         [DependsOn(nameof(ContentTemplate))]
-        public object Content
+        public object? Content
         {
             get { return GetValue(ContentProperty); }
             set { SetValue(ContentProperty, value); }
@@ -57,7 +57,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the data template used to display the content of the control.
         /// </summary>
-        public IDataTemplate ContentTemplate
+        public IDataTemplate? ContentTemplate
         {
             get { return GetValue(ContentTemplateProperty); }
             set { SetValue(ContentTemplateProperty, value); }
@@ -66,7 +66,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the presenter from the control's template.
         /// </summary>
-        public IContentPresenter Presenter
+        public IContentPresenter? Presenter
         {
             get;
             private set;

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

@@ -14,8 +14,6 @@ using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Styling;
 
-#nullable enable
-
 namespace Avalonia.Controls
 {
     /// <summary>
@@ -368,7 +366,7 @@ namespace Avalonia.Controls
             });
         }
 
-        private void PopupOpened(object sender, EventArgs e)
+        private void PopupOpened(object? sender, EventArgs e)
         {
             _previousFocus = FocusManager.Instance?.Current;
             Focus();
@@ -376,12 +374,12 @@ namespace Avalonia.Controls
             _popupHostChangedHandler?.Invoke(_popup!.Host);
         }
 
-        private void PopupClosing(object sender, CancelEventArgs e)
+        private void PopupClosing(object? sender, CancelEventArgs e)
         {
             e.Cancel = CancelClosing();
         }
 
-        private void PopupClosed(object sender, EventArgs e)
+        private void PopupClosed(object? sender, EventArgs e)
         {
             foreach (var i in LogicalChildren)
             {
@@ -411,7 +409,7 @@ namespace Avalonia.Controls
             _popupHostChangedHandler?.Invoke(null);
         }
 
-        private void PopupKeyUp(object sender, KeyEventArgs e)
+        private void PopupKeyUp(object? sender, KeyEventArgs e)
         {
             if (IsOpen)
             {
@@ -426,7 +424,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private static void ControlContextRequested(object sender, ContextRequestedEventArgs e)
+        private static void ControlContextRequested(object? sender, ContextRequestedEventArgs e)
         {
             if (sender is Control control
                 && control.ContextMenu is ContextMenu contextMenu
@@ -439,7 +437,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private static void ControlDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+        private static void ControlDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
         {
             if (sender is Control control
                 && control.ContextMenu is ContextMenu contextMenu)

+ 0 - 2
src/Avalonia.Controls/ContextRequestedEventArgs.cs

@@ -1,8 +1,6 @@
 using Avalonia.Input;
 using Avalonia.Interactivity;
 
-#nullable enable
-
 namespace Avalonia.Controls
 {
     /// <summary>

+ 19 - 4
src/Avalonia.Controls/Control.cs

@@ -1,16 +1,16 @@
 using System;
 using System.ComponentModel;
+using System.Runtime.CompilerServices;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.Interactivity;
+using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 
-#nullable enable
-
 namespace Avalonia.Controls
 {
     /// <summary>
@@ -60,7 +60,13 @@ namespace Avalonia.Controls
         public static readonly RoutedEvent<ContextRequestedEventArgs> ContextRequestedEvent =
             RoutedEvent.Register<Control, ContextRequestedEventArgs>(nameof(ContextRequested),
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
-
+        
+        /// <summary>
+        /// Defines the <see cref="FlowDirection"/> property.
+        /// </summary>
+        public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
+            AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
+        
         private DataTemplates? _dataTemplates;
         private IControl? _focusAdorner;
 
@@ -108,11 +114,20 @@ namespace Avalonia.Controls
             get => GetValue(TagProperty);
             set => SetValue(TagProperty, value);
         }
+        
+        /// <summary>
+        /// Gets or sets the text flow direction.
+        /// </summary>
+        public FlowDirection FlowDirection
+        {
+            get => GetValue(FlowDirectionProperty);
+            set => SetValue(FlowDirectionProperty, value);
+        }
 
         /// <summary>
         /// Occurs when the user has completed a context input gesture, such as a right-click.
         /// </summary>
-        public event EventHandler<ContextRequestedEventArgs> ContextRequested
+        public event EventHandler<ContextRequestedEventArgs>? ContextRequested
         {
             add => AddHandler(ContextRequestedEvent, value);
             remove => RemoveHandler(ContextRequestedEvent, value);

+ 29 - 6
src/Avalonia.Controls/ControlExtensions.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Controls
         /// <param name="control">The control.</param>
         public static void BringIntoView(this IControl control)
         {
-            Contract.Requires<ArgumentNullException>(control != null);
+            _ = control ?? throw new ArgumentNullException(nameof(control));
 
             control.BringIntoView(new Rect(control.Bounds.Size));
         }
@@ -29,7 +29,7 @@ namespace Avalonia.Controls
         /// <param name="rect">The area of the control to being into view.</param>
         public static void BringIntoView(this IControl control, Rect rect)
         {
-            Contract.Requires<ArgumentNullException>(control != null);
+            _ = control ?? throw new ArgumentNullException(nameof(control));
 
             if (control.IsEffectivelyVisible)
             {
@@ -51,10 +51,10 @@ namespace Avalonia.Controls
         /// <param name="control">The control to look in.</param>
         /// <param name="name">The name of the control to find.</param>
         /// <returns>The control or null if not found.</returns>
-        public static T FindControl<T>(this IControl control, string name) where T : class, IControl
+        public static T? FindControl<T>(this IControl control, string name) where T : class, IControl
         {
-            Contract.Requires<ArgumentNullException>(control != null);
-            Contract.Requires<ArgumentNullException>(name != null);
+            _ = control ?? throw new ArgumentNullException(nameof(control));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
 
             var nameScope = control.FindNameScope();
 
@@ -66,6 +66,29 @@ namespace Avalonia.Controls
             return nameScope.Find<T>(name);
         }
 
+        /// <summary>
+        /// Finds the named control in the scope of the specified control and throws if not found.
+        /// </summary>
+        /// <typeparam name="T">The type of the control to find.</typeparam>
+        /// <param name="control">The control to look in.</param>
+        /// <param name="name">The name of the control to find.</param>
+        /// <returns>The control.</returns>
+        public static T GetControl<T>(this IControl control, string name) where T : class, IControl
+        {
+            _ = control ?? throw new ArgumentNullException(nameof(control));
+            _ = name ?? throw new ArgumentNullException(nameof(name));
+
+            var nameScope = control.FindNameScope();
+
+            if (nameScope == null)
+            {
+                throw new InvalidOperationException("Could not find parent name scope.");
+            }
+
+            return nameScope.Find<T>(name) ??
+                throw new ArgumentException($"Could not find control named '{name}'.");
+        }
+
         /// <summary>
         /// Sets a pseudoclass depending on an observable trigger.
         /// </summary>
@@ -75,7 +98,7 @@ namespace Avalonia.Controls
         /// <returns>A disposable used to cancel the subscription.</returns>
         public static IDisposable Set(this IPseudoClasses classes, string name, IObservable<bool> trigger)
         {
-            Contract.Requires<ArgumentNullException>(classes != null);
+            _ = classes ?? throw new ArgumentNullException(nameof(classes));
 
             return trigger.Subscribe(x => classes.Set(name, x));
         }

+ 1 - 2
src/Avalonia.Controls/Converters/CornerRadiusFilterConverter.cs

@@ -1,5 +1,4 @@
-#nullable enable
-using System;
+using System;
 using System.Globalization;
 
 using Avalonia.Data.Converters;

+ 2 - 2
src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls.Converters
 
         public bool Bottom { get; set; } = false;
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             if (value is int scalarDepth)
             {
@@ -38,7 +38,7 @@ namespace Avalonia.Controls.Converters
             
         }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             throw new System.NotImplementedException();
         }

+ 1 - 1
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Controls.Converters
     {
         public static readonly MenuScrollingVisibilityConverter Instance = new MenuScrollingVisibilityConverter();
 
-        public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
+        public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
         {
             if (parameter == null ||
                 values == null ||

+ 2 - 2
src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Controls.Converters
     /// </summary>
     public class PlatformKeyGestureConverter : IValueConverter
     {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             if (value is null)
             {
@@ -29,7 +29,7 @@ namespace Avalonia.Controls.Converters
             }
         }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             throw new NotImplementedException();
         }

+ 15 - 14
src/Avalonia.Controls/DataValidationErrors.cs

@@ -21,8 +21,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the DataValidationErrors.Errors attached property.
         /// </summary>
-        public static readonly AttachedProperty<IEnumerable<object>> ErrorsProperty =
-            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>>("Errors");
+        public static readonly AttachedProperty<IEnumerable<object>?> ErrorsProperty =
+            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>?>("Errors");
 
         /// <summary>
         /// Defines the DataValidationErrors.HasErrors attached property.
@@ -34,15 +34,15 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<DataValidationErrors, IDataTemplate>(nameof(ErrorTemplate));
 
 
-        private Control _owner;
+        private Control? _owner;
 
-        public static readonly DirectProperty<DataValidationErrors, Control> OwnerProperty =
-            AvaloniaProperty.RegisterDirect<DataValidationErrors, Control>(
+        public static readonly DirectProperty<DataValidationErrors, Control?> OwnerProperty =
+            AvaloniaProperty.RegisterDirect<DataValidationErrors, Control?>(
                 nameof(Owner),
                 o => o.Owner,
                 (o, v) => o.Owner = v);
 
-        public Control Owner
+        public Control? Owner
         {
             get { return _owner; }
             set { SetAndRaise(OwnerProperty, ref _owner, value); }
@@ -75,7 +75,7 @@ namespace Avalonia.Controls
         private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
         {
             var control = (Control)e.Sender;
-            var errors = (IEnumerable<object>)e.NewValue;
+            var errors = (IEnumerable<object>?)e.NewValue;
 
             var hasErrors = false;
             if (errors != null && errors.Any())
@@ -87,18 +87,18 @@ namespace Avalonia.Controls
         {
             var control = (Control)e.Sender;
             var classes = (IPseudoClasses)control.Classes;
-            classes.Set(":error", (bool)e.NewValue);
+            classes.Set(":error", (bool)e.NewValue!);
         }
 
-        public static IEnumerable<object> GetErrors(Control control)
+        public static IEnumerable<object>? GetErrors(Control control)
         {
             return control.GetValue(ErrorsProperty);
         }
-        public static void SetErrors(Control control, IEnumerable<object> errors)
+        public static void SetErrors(Control control, IEnumerable<object>? errors)
         {
             control.SetValue(ErrorsProperty, errors);
         }
-        public static void SetError(Control control, Exception error)
+        public static void SetError(Control control, Exception? error)
         {
             SetErrors(control, UnpackException(error));
         }
@@ -111,7 +111,7 @@ namespace Avalonia.Controls
             return control.GetValue(HasErrorsProperty);
         }
 
-        private static IEnumerable<object> UnpackException(Exception exception)
+        private static IEnumerable<object>? UnpackException(Exception? exception)
         {
             if (exception != null)
             {
@@ -132,8 +132,9 @@ namespace Avalonia.Controls
 
         private static object GetExceptionData(Exception exception)
         {
-            if (exception is DataValidationException dataValidationException)
-                return dataValidationException.ErrorData;
+            if (exception is DataValidationException dataValidationException &&
+                dataValidationException.ErrorData is object data)
+                return data;
 
             return exception;
         }

+ 42 - 32
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Interactivity;
+using Avalonia.Layout;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -93,15 +94,15 @@ namespace Avalonia.Controls
                 defaultBindingMode: BindingMode.TwoWay);
 
         // Template Items
-        private Button _flyoutButton;
-        private TextBlock _dayText;
-        private TextBlock _monthText;
-        private TextBlock _yearText;
-        private Grid _container;
-        private Rectangle _spacer1;
-        private Rectangle _spacer2;
-        private Popup _popup;
-        private DatePickerPresenter _presenter;
+        private Button? _flyoutButton;
+        private TextBlock? _dayText;
+        private TextBlock? _monthText;
+        private TextBlock? _yearText;
+        private Grid? _container;
+        private Rectangle? _spacer1;
+        private Rectangle? _spacer2;
+        private Popup? _popup;
+        private DatePickerPresenter? _presenter;
 
         private bool _areControlsAvailable;
 
@@ -256,7 +257,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Raised when the <see cref="SelectedDate"/> changes
         /// </summary>
-        public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged;
+        public event EventHandler<DatePickerSelectedValueChangedEventArgs>? SelectedDateChanged;
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
@@ -307,16 +308,16 @@ namespace Avalonia.Controls
             }
         }
 
-        private void OnDismissPicker(object sender, EventArgs e)
+        private void OnDismissPicker(object? sender, EventArgs e)
         {
-            _popup.Close();
+            _popup!.Close();
             Focus();
         }
 
-        private void OnConfirmed(object sender, EventArgs e)
+        private void OnConfirmed(object? sender, EventArgs e)
         {
-            _popup.Close();
-            SelectedDate = _presenter.Date;
+            _popup!.Close();
+            SelectedDate = _presenter!.Date;
         }
 
         private void SetGrid()
@@ -325,7 +326,7 @@ namespace Avalonia.Controls
                 return;
 
             var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
-            var columns = new List<(TextBlock, int)>
+            var columns = new List<(TextBlock?, int)>
             {
                 (_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
                 (_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
@@ -366,11 +367,11 @@ namespace Avalonia.Controls
             var isSpacer1Visible = columnIndex > 1;
             var isSpacer2Visible = columnIndex > 2;
             // ternary conditional operator is used to make sure grid cells will be validated
-            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
-            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+            Grid.SetColumn(_spacer1!, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2!, isSpacer2Visible ? 3 : 0);
 
-            _spacer1.IsVisible = isSpacer1Visible;
-            _spacer2.IsVisible = isSpacer2Visible;
+            _spacer1!.IsVisible = isSpacer1Visible;
+            _spacer2!.IsVisible = isSpacer2Visible;
         }
 
         private void SetSelectedDateText()
@@ -382,37 +383,46 @@ namespace Avalonia.Controls
             {
                 PseudoClasses.Set(":hasnodate", false);
                 var selDate = SelectedDate.Value;
-                _monthText.Text = selDate.ToString(MonthFormat);
-                _yearText.Text = selDate.ToString(YearFormat);
-                _dayText.Text = selDate.ToString(DayFormat);
+                _monthText!.Text = selDate.ToString(MonthFormat);
+                _yearText!.Text = selDate.ToString(YearFormat);
+                _dayText!.Text = selDate.ToString(DayFormat);
             }
             else
             {
                 PseudoClasses.Set(":hasnodate", true);
-                _monthText.Text = "month";
-                _yearText.Text = "year";
-                _dayText.Text = "day";
+                _monthText!.Text = "month";
+                _yearText!.Text = "year";
+                _dayText!.Text = "day";
             }
         }
 
-        private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e)
+        private void OnFlyoutButtonClicked(object? sender, RoutedEventArgs e)
         {
             if (_presenter == null)
-                throw new InvalidOperationException("No DatePickerPresenter found");
+                throw new InvalidOperationException("No DatePickerPresenter found.");
+            if (_popup == null)
+                throw new InvalidOperationException("No Popup found.");
 
             _presenter.Date = SelectedDate ?? DateTimeOffset.Now;
 
+            _popup.PlacementMode = PlacementMode.AnchorAndGravity;
+            _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
+            _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
+            _popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
             _popup.IsOpen = true;
 
+            // Overlay popup hosts won't get measured until the next layout pass, but we need the
+            // template to be applied to `_presenter` now. Detect this case and force a layout pass.
+            if (!_presenter.IsMeasureValid)
+                (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+
             var deltaY = _presenter.GetOffsetForPopup();
 
             // The extra 5 px I think is related to default popup placement behavior
-            _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
-                Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
-                 Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
+            _popup.VerticalOffset = deltaY + 5;
         }
 
-        protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e)
+        protected virtual void OnSelectedDateChanged(object? sender, DatePickerSelectedValueChangedEventArgs e)
         {
             SelectedDateChanged?.Invoke(sender, e);
         }

+ 55 - 49
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -78,23 +78,23 @@ namespace Avalonia.Controls
             x.YearVisible, (x, v) => x.YearVisible = v);
 
         // Template Items
-        private Grid _pickerContainer;
-        private Button _acceptButton;
-        private Button _dismissButton;
-        private Rectangle _spacer1;
-        private Rectangle _spacer2;
-        private Panel _monthHost;
-        private Panel _yearHost;
-        private Panel _dayHost;
-        private DateTimePickerPanel _monthSelector;
-        private DateTimePickerPanel _yearSelector;
-        private DateTimePickerPanel _daySelector;
-        private Button _monthUpButton;
-        private Button _dayUpButton;
-        private Button _yearUpButton;
-        private Button _monthDownButton;
-        private Button _dayDownButton;
-        private Button _yearDownButton;
+        private Grid? _pickerContainer;
+        private Button? _acceptButton;
+        private Button? _dismissButton;
+        private Rectangle? _spacer1;
+        private Rectangle? _spacer2;
+        private Panel? _monthHost;
+        private Panel? _yearHost;
+        private Panel? _dayHost;
+        private DateTimePickerPanel? _monthSelector;
+        private DateTimePickerPanel? _yearSelector;
+        private DateTimePickerPanel? _daySelector;
+        private Button? _monthUpButton;
+        private Button? _dayUpButton;
+        private Button? _yearUpButton;
+        private Button? _monthDownButton;
+        private Button? _dayDownButton;
+        private Button? _yearDownButton;
 
         private DateTimeOffset _date;
         private string _dayFormat = "%d";
@@ -308,9 +308,12 @@ namespace Avalonia.Controls
                     e.Handled = true;
                     break;
                 case Key.Tab:
-                    var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
-                    KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
-                    e.Handled = true;
+                    if (FocusManager.Instance?.Current is IInputElement focus)
+                    {
+                        var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
+                        KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+                        e.Handled = true;
+                    }
                     break;
                 case Key.Enter:
                     Date = _syncDate;
@@ -332,13 +335,13 @@ namespace Avalonia.Controls
 
             _suppressUpdateSelection = true;
 
-            _monthSelector.MaximumValue = 12;
+            _monthSelector!.MaximumValue = 12;
             _monthSelector.MinimumValue = 1;
             _monthSelector.ItemFormat = MonthFormat;
 
-            _daySelector.ItemFormat = DayFormat;
+            _daySelector!.ItemFormat = DayFormat;
 
-            _yearSelector.MaximumValue = MaxYear.Year;
+            _yearSelector!.MaximumValue = MaxYear.Year;
             _yearSelector.MinimumValue = MinYear.Year;
             _yearSelector.ItemFormat = YearFormat;
 
@@ -375,7 +378,7 @@ namespace Avalonia.Controls
         private void SetGrid()
         {
             var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
-            var columns = new List<(Panel, int)>
+            var columns = new List<(Panel?, int)>
             {
                 (_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
                 (_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
@@ -383,7 +386,7 @@ namespace Avalonia.Controls
             };
 
             columns.Sort((x, y) => x.Item2 - y.Item2);
-            _pickerContainer.ColumnDefinitions.Clear();
+            _pickerContainer!.ColumnDefinitions.Clear();
 
             var columnIndex = 0;
 
@@ -416,18 +419,18 @@ namespace Avalonia.Controls
             var isSpacer1Visible = columnIndex > 1;
             var isSpacer2Visible = columnIndex > 2;
             // ternary conditional operator is used to make sure grid cells will be validated
-            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
-            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+            Grid.SetColumn(_spacer1!, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2!, isSpacer2Visible ? 3 : 0);
 
-            _spacer1.IsVisible = isSpacer1Visible;
-            _spacer2.IsVisible = isSpacer2Visible;
+            _spacer1!.IsVisible = isSpacer1Visible;
+            _spacer2!.IsVisible = isSpacer2Visible;
         }
 
         private void SetInitialFocus()
         {
-            int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue;
-            int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue;
-            int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue;
+            int monthCol = MonthVisible ? Grid.GetColumn(_monthHost!) : int.MaxValue;
+            int dayCol = DayVisible ? Grid.GetColumn(_dayHost!) : int.MaxValue;
+            int yearCol = YearVisible ? Grid.GetColumn(_yearHost!) : int.MaxValue;
 
             if (monthCol < dayCol && monthCol < yearCol)
             {
@@ -443,39 +446,39 @@ namespace Avalonia.Controls
             }
         }
 
-        private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
+        private void OnDismissButtonClicked(object? sender, RoutedEventArgs e)
         {
             OnDismiss();
         }
 
-        private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
+        private void OnAcceptButtonClicked(object? sender, RoutedEventArgs e)
         {
             Date = _syncDate;
             OnConfirmed();
         }
 
-        private void OnSelectorButtonClick(object sender, RoutedEventArgs e)
+        private void OnSelectorButtonClick(object? sender, RoutedEventArgs e)
         {
             if (sender == _monthUpButton)
-                _monthSelector.ScrollUp();
+                _monthSelector!.ScrollUp();
             else if (sender == _monthDownButton)
-                _monthSelector.ScrollDown();
+                _monthSelector!.ScrollDown();
             else if (sender == _yearUpButton)
-                _yearSelector.ScrollUp();
+                _yearSelector!.ScrollUp();
             else if (sender == _yearDownButton)
-                _yearSelector.ScrollDown();
+                _yearSelector!.ScrollDown();
             else if (sender == _dayUpButton)
-                _daySelector.ScrollUp();
+                _daySelector!.ScrollUp();
             else if (sender == _dayDownButton)
-                _daySelector.ScrollDown();
+                _daySelector!.ScrollDown();
         }
 
-        private void OnYearChanged(object sender, EventArgs e)
+        private void OnYearChanged(object? sender, EventArgs e)
         {
             if (_suppressUpdateSelection)
                 return;
 
-            int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month);
+            int maxDays = _calendar.GetDaysInMonth(_yearSelector!.SelectedValue, _syncDate.Month);
             var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month,
                 _syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
 
@@ -487,7 +490,7 @@ namespace Avalonia.Controls
 
             _suppressUpdateSelection = true;
 
-            _daySelector.FormatDate = newDate.Date;
+            _daySelector!.FormatDate = newDate.Date;
 
             if (_daySelector.MaximumValue != maxDays)
                 _daySelector.MaximumValue = maxDays;
@@ -497,19 +500,19 @@ namespace Avalonia.Controls
             _suppressUpdateSelection = false;
         }
 
-        private void OnDayChanged(object sender, EventArgs e)
+        private void OnDayChanged(object? sender, EventArgs e)
         {
             if (_suppressUpdateSelection)
                 return;
-            _syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset);
+            _syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector!.SelectedValue, 0, 0, 0, _syncDate.Offset);
         }
 
-        private void OnMonthChanged(object sender, EventArgs e)
+        private void OnMonthChanged(object? sender, EventArgs e)
         {
             if (_suppressUpdateSelection)
                 return;
 
-            int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue);
+            int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector!.SelectedValue);
             var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue,
                 _syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
 
@@ -521,7 +524,7 @@ namespace Avalonia.Controls
 
             _suppressUpdateSelection = true;
 
-            _daySelector.FormatDate = newDate.Date;
+            _daySelector!.FormatDate = newDate.Date;
             _syncDate = newDate;
 
             if (_daySelector.MaximumValue != maxDays)
@@ -534,6 +537,9 @@ namespace Avalonia.Controls
 
         internal double GetOffsetForPopup()
         {
+            if (_monthSelector is null)
+                return 0;
+
             var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
             return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2);
         }

+ 16 - 12
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Controls.Primitives
         private Vector _offset;
         private bool _hasInit;
         private bool _suppressUpdateOffset;
-        private ListBoxItem _pressedItem;
+        private ListBoxItem? _pressedItem;
 
         public DateTimePickerPanel()
         {
@@ -271,9 +271,9 @@ namespace Avalonia.Controls.Primitives
 
         public Size Viewport => new Size(0, ItemHeight);
 
-        public event EventHandler ScrollInvalidated;
+        public event EventHandler? ScrollInvalidated;
 
-        public event EventHandler SelectionChanged;
+        public event EventHandler? SelectionChanged;
 
         protected override Size MeasureOverride(Size availableSize)
         {
@@ -523,40 +523,44 @@ namespace Avalonia.Controls.Primitives
             return newValue;
         }
 
-        private void OnItemPointerDown(object sender, PointerPressedEventArgs e)
+        private void OnItemPointerDown(object? sender, PointerPressedEventArgs e)
         {
-            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
+                e.Source is IVisual source)
             {
-                _pressedItem = GetItemFromSource((IVisual)e.Source);
+                _pressedItem = GetItemFromSource(source);
                 e.Handled = true;
             }
         }
 
-        private void OnItemPointerUp(object sender, PointerReleasedEventArgs e)
+        private void OnItemPointerUp(object? sender, PointerReleasedEventArgs e)
         {
             if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased &&
-                _pressedItem != null)
+                _pressedItem != null &&
+                e.Source is IVisual source &&
+                GetItemFromSource(source) is ListBoxItem item &&
+                item.Tag is int tag)
             {
-                SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag;
+                SelectedValue = tag;
                 _pressedItem = null;
                 e.Handled = true;
             }
         }
 
         //Helper to get ListBoxItem from pointerevent source
-        private ListBoxItem GetItemFromSource(IVisual src)
+        private ListBoxItem? GetItemFromSource(IVisual src)
         {
             var item = src;
             while (item != null && !(item is ListBoxItem))
             {
                 item = item.VisualParent;
             }
-            return (ListBoxItem)item;
+            return (ListBoxItem?)item;
         }
 
         public bool BringIntoView(IControl target, Rect targetRect) { return false; }
 
-        public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; }
+        public IControl? GetControlInDirection(NavigationDirection direction, IControl? from) { return null; }
 
         public void RaiseScrollInvalidated(EventArgs e)
         {

+ 3 - 3
src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs

@@ -16,10 +16,10 @@ namespace Avalonia.Controls.Primitives
 
         protected virtual void OnDismiss()
         {
-            Dismissed.Invoke(this, EventArgs.Empty);
+            Dismissed?.Invoke(this, EventArgs.Empty);
         }
 
-        public event EventHandler Confirmed;
-        public event EventHandler Dismissed;
+        public event EventHandler? Confirmed;
+        public event EventHandler? Dismissed;
     }
 }

+ 41 - 28
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
+using Avalonia.Layout;
 using System;
 using System.Globalization;
 
@@ -49,18 +50,18 @@ namespace Avalonia.Controls
                 defaultBindingMode: BindingMode.TwoWay);
 
         // Template Items
-        private TimePickerPresenter _presenter;
-        private Button _flyoutButton;
-        private Border _firstPickerHost;
-        private Border _secondPickerHost;
-        private Border _thirdPickerHost;
-        private TextBlock _hourText;
-        private TextBlock _minuteText;
-        private TextBlock _periodText;
-        private Rectangle _firstSplitter;
-        private Rectangle _secondSplitter;
-        private Grid _contentGrid;
-        private Popup _popup;
+        private TimePickerPresenter? _presenter;
+        private Button? _flyoutButton;
+        private Border? _firstPickerHost;
+        private Border? _secondPickerHost;
+        private Border? _thirdPickerHost;
+        private TextBlock? _hourText;
+        private TextBlock? _minuteText;
+        private TextBlock? _periodText;
+        private Rectangle? _firstSplitter;
+        private Rectangle? _secondSplitter;
+        private Grid? _contentGrid;
+        private Popup? _popup;
 
         private TimeSpan? _selectedTime;
         private int _minuteIncrement = 1;
@@ -142,7 +143,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Raised when the <see cref="SelectedTime"/> property changes
         /// </summary>
-        public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged;
+        public event EventHandler<TimePickerSelectedValueChangedEventArgs>? SelectedTimeChanged;
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
@@ -200,15 +201,15 @@ namespace Avalonia.Controls
             var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
             _contentGrid.ColumnDefinitions = new ColumnDefinitions(columnsD);
 
-            _thirdPickerHost.IsVisible = !use24HourClock;
-            _secondSplitter.IsVisible = !use24HourClock;
+            _thirdPickerHost!.IsVisible = !use24HourClock;
+            _secondSplitter!.IsVisible = !use24HourClock;
 
-            Grid.SetColumn(_firstPickerHost, 0);
-            Grid.SetColumn(_secondPickerHost, 2);
+            Grid.SetColumn(_firstPickerHost!, 0);
+            Grid.SetColumn(_secondPickerHost!, 2);
 
             Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4);
 
-            Grid.SetColumn(_firstSplitter, 1);
+            Grid.SetColumn(_firstSplitter!, 1);
             Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3);
         }
 
@@ -220,7 +221,7 @@ namespace Avalonia.Controls
             var time = SelectedTime;
             if (time.HasValue)
             {
-                var newTime = SelectedTime.Value;
+                var newTime = SelectedTime!.Value;
 
                 if (ClockIdentifier == "12HourClock")
                 {
@@ -252,30 +253,42 @@ namespace Avalonia.Controls
             SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
         }
 
-        private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e)
+        private void OnFlyoutButtonClicked(object? sender, Interactivity.RoutedEventArgs e)
         {
+            if (_presenter == null)
+                throw new InvalidOperationException("No DatePickerPresenter found.");
+            if (_popup == null)
+                throw new InvalidOperationException("No Popup found.");
+
             _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
 
+            _popup.PlacementMode = PlacementMode.AnchorAndGravity;
+            _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
+            _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
+            _popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
             _popup.IsOpen = true;
 
+            // Overlay popup hosts won't get measured until the next layout pass, but we need the
+            // template to be applied to `_presenter` now. Detect this case and force a layout pass.
+            if (!_presenter.IsMeasureValid)
+                (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+
             var deltaY = _presenter.GetOffsetForPopup();
 
             // The extra 5 px I think is related to default popup placement behavior
-            _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
-                Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
-                 Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
+            _popup.VerticalOffset = deltaY + 5;
         }
 
-        private void OnDismissPicker(object sender, EventArgs e)
+        private void OnDismissPicker(object? sender, EventArgs e)
         {
-            _popup.Close();
+            _popup!.Close();
             Focus();
         }
 
-        private void OnConfirmed(object sender, EventArgs e)
+        private void OnConfirmed(object? sender, EventArgs e)
         {
-            _popup.Close();
-            SelectedTime = _presenter.Time;
+            _popup!.Close();
+            SelectedTime = _presenter!.Time;
         }
     }
 }

部分文件因为文件数量过多而无法显示