Browse Source

Merge branch 'master' into fixes/treeview-selecteditem-inside-selectionchanged-event

Max Katz 2 years ago
parent
commit
80f3fadac4
100 changed files with 1010 additions and 671 deletions
  1. 35 0
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  2. 1 1
      src/Avalonia.Base/Layout/Layoutable.cs
  3. 1 1
      src/Avalonia.Base/Media/Color.cs
  4. 1 1
      src/Avalonia.Base/Media/HsvColor.cs
  5. 45 1
      src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs
  6. 7 1
      src/Avalonia.Base/Platform/PixelFormat.cs
  7. 7 1
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs
  8. 1 1
      src/Avalonia.Base/Styling/ITemplate.cs
  9. 1 1
      src/Avalonia.Base/Visual.cs
  10. 1 3
      src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs
  11. 1 1
      src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs
  12. 22 8
      src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
  13. 5 5
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs
  14. 6 5
      src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs
  15. 26 0
      src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs
  16. 18 0
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs
  17. 15 20
      src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs
  18. 152 17
      src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs
  19. 113 33
      src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs
  20. 0 2
      src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
  21. 4 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  22. 38 29
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml
  23. 2 1
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  24. 3 1
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  25. 38 29
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
  26. 2 1
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  27. 1 1
      src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs
  28. 47 57
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  29. 28 37
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  30. 3 4
      src/Avalonia.Controls/Carousel.cs
  31. 2 5
      src/Avalonia.Controls/ComboBox.cs
  32. 2 3
      src/Avalonia.Controls/ContextMenu.cs
  33. 5 7
      src/Avalonia.Controls/ItemsControl.cs
  34. 2 5
      src/Avalonia.Controls/ListBox.cs
  35. 2 4
      src/Avalonia.Controls/Menu.cs
  36. 2 3
      src/Avalonia.Controls/MenuItem.cs
  37. 9 3
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  38. 1 1
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  39. 2 4
      src/Avalonia.Controls/Primitives/TabStrip.cs
  40. 11 8
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  41. 1 1
      src/Avalonia.Controls/ScrollViewer.cs
  42. 5 5
      src/Avalonia.Controls/Slider.cs
  43. 2 5
      src/Avalonia.Controls/TabControl.cs
  44. 2 2
      src/Avalonia.Controls/Templates/FuncTemplate`1.cs
  45. 1 3
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  46. 1 1
      src/Avalonia.Controls/TopLevel.cs
  47. 4 0
      src/Avalonia.Controls/TreeView.cs
  48. 19 4
      src/Avalonia.Controls/TreeViewItem.cs
  49. 1 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  50. 2 3
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  51. 8 6
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  52. 4 2
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs
  53. 6 5
      src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs
  54. 4 4
      src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs
  55. 5 5
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  56. 7 6
      src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs
  57. 4 5
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  58. 4 9
      src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  59. 16 17
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  60. 8 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  61. 4 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs
  62. 17 19
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  63. 28 26
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  64. 6 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs
  65. 1 5
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs
  66. 6 8
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs
  67. 26 26
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  68. 4 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs
  69. 3 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs
  70. 9 9
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs
  71. 1 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  72. 1 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs
  73. 2 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
  74. 1 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs
  75. 12 15
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  76. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  77. 7 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs
  78. 18 16
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  79. 4 4
      src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs
  80. 0 2
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  81. 1 2
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs
  82. 0 3
      src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs
  83. 0 2
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
  84. 2 4
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  85. 3 3
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  86. 5 5
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  87. 4 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  88. 4 4
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  89. 8 9
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  90. 6 6
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  91. 2 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs
  92. 18 22
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  93. 1 2
      src/Markup/Avalonia.Markup/Data/Binding.cs
  94. 2 3
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  95. 17 10
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  96. 4 5
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  97. 2 4
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  98. 4 4
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  99. 8 10
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  100. 2 4
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

+ 35 - 0
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@@ -28,6 +28,41 @@
     <Grid Grid.Column="2"
           Grid.Row="0"
           RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+      <Grid.Resources>
+
+        <x:Double x:Key="ColorSliderSize">24</x:Double>
+        <x:Double x:Key="ColorSliderTrackSize">18</x:Double>
+        <CornerRadius x:Key="ColorSliderCornerRadius">12</CornerRadius>
+        <CornerRadius x:Key="ColorSliderTrackCornerRadius">9</CornerRadius>
+
+        <!-- Due to 'SystemControlForegroundBaseHighBrush' usage this only works in Fluent theme. -->
+        <!-- Otherwise it would be necessary to make custom light/dark resources. -->
+        <ControlTheme x:Key="ColorSliderThumbTheme"
+                      TargetType="Thumb">
+          <Setter Property="Background" Value="Transparent" />
+          <Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+          <Setter Property="BorderThickness" Value="5" />
+          <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
+          <Setter Property="Template">
+            <Setter.Value>
+              <ControlTemplate>
+                <Grid>
+                  <Border Background="{TemplateBinding Background}"
+                          BorderBrush="{TemplateBinding BorderBrush}"
+                          BorderThickness="{TemplateBinding BorderThickness}"
+                          CornerRadius="{TemplateBinding CornerRadius}" />
+                  <Ellipse Height="{TemplateBinding Height}"
+                           Width="{TemplateBinding Width}"
+                           Fill="Transparent"
+                           Stroke="{TemplateBinding Foreground}"
+                           StrokeThickness="1" />
+                </Grid>
+              </ControlTemplate>
+            </Setter.Value>
+          </Setter>
+        </ControlTheme>
+
+      </Grid.Resources>
       <ColorSpectrum x:Name="ColorSpectrum1"
                      Grid.Row="0"
                      Color="Red"

+ 1 - 1
src/Avalonia.Base/Layout/Layoutable.cs

@@ -125,7 +125,7 @@ namespace Avalonia.Layout
             AvaloniaProperty.Register<Layoutable, VerticalAlignment>(nameof(VerticalAlignment));
 
         /// <summary>
-        /// Defines the <see cref="UseLayoutRoundingProperty"/> property.
+        /// Defines the <see cref="UseLayoutRounding"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> UseLayoutRoundingProperty =
             AvaloniaProperty.Register<Layoutable, bool>(nameof(UseLayoutRounding), defaultValue: true, inherits: true);

+ 1 - 1
src/Avalonia.Base/Media/Color.cs

@@ -309,7 +309,7 @@ namespace Avalonia.Media
             if (input.Length == 3 || input.Length == 4)
             {
                 var extendedLength = 2 * input.Length;
-                
+
 #if !BUILDTASK
                 Span<char> extended = stackalloc char[extendedLength];
 #else

+ 1 - 1
src/Avalonia.Base/Media/HsvColor.cs

@@ -131,7 +131,7 @@ namespace Avalonia.Media
         /// </summary>
         /// <remarks>
         /// <list type="bullet">
-        ///   <item>0 is a shade of gray (no color).</item>
+        ///   <item>0 is fully white (or a shade of gray) and shows no color.</item>
         ///   <item>1 is the full color.</item>
         /// </list>
         /// </remarks>

+ 45 - 1
src/Avalonia.Base/Media/Imaging/PixelFormatReaders.cs

@@ -228,6 +228,44 @@ static unsafe class PixelFormatReader
 
         public void Reset(IntPtr address) => _address = (Rgba64*)address;
     }
+    
+    public unsafe struct Rgb24PixelFormatReader : IPixelFormatReader
+    {
+        private byte* _address;
+        public Rgba8888Pixel ReadNext()
+        {
+            var addr = _address;
+            _address += 3;
+            return new Rgba8888Pixel
+            {
+                R = addr[0],
+                G = addr[1],
+                B = addr[2],
+                A = 255,
+            };
+        }
+
+        public void Reset(IntPtr address) => _address = (byte*)address;
+    }
+    
+    public unsafe struct Bgr24PixelFormatReader : IPixelFormatReader
+    {
+        private byte* _address;
+        public Rgba8888Pixel ReadNext()
+        {
+            var addr = _address;
+            _address += 3;
+            return new Rgba8888Pixel
+            {
+                R = addr[2],
+                G = addr[1],
+                B = addr[0],
+                A = 255,
+            };
+        }
+
+        public void Reset(IntPtr address) => _address = (byte*)address;
+    }
 
     public static void Transcode(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst,
         PixelFormat format)
@@ -242,6 +280,10 @@ static unsafe class PixelFormatReader
             Transcode<Gray8PixelReader>(dst, src, size, strideSrc, strideDst);
         else if (format == PixelFormats.Gray16)
             Transcode<Gray16PixelReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Rgb24)
+            Transcode<Rgb24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
+        else if (format == PixelFormats.Bgr24)
+            Transcode<Bgr24PixelFormatReader>(dst, src, size, strideSrc, strideDst);
         else if (format == PixelFormats.Gray32Float)
             Transcode<Gray32FloatPixelReader>(dst, src, size, strideSrc, strideDst);
         else if (format == PixelFormats.Rgba64)
@@ -258,7 +300,9 @@ static unsafe class PixelFormatReader
                || format == PixelFormats.Gray8
                || format == PixelFormats.Gray16
                || format == PixelFormats.Gray32Float
-               || format == PixelFormats.Rgba64;
+               || format == PixelFormats.Rgba64
+               || format == PixelFormats.Bgr24
+               || format == PixelFormats.Rgb24;
     }
     
     public static void Transcode<TReader>(IntPtr dst, IntPtr src, PixelSize size, int strideSrc, int strideDst) where TReader : struct, IPixelFormatReader

+ 7 - 1
src/Avalonia.Base/Platform/PixelFormat.cs

@@ -13,7 +13,9 @@ namespace Avalonia.Platform
         Gray8,
         Gray16,
         Gray32Float,
-        Rgba64
+        Rgba64,
+        Rgb24,
+        Bgr24
     }
 
     public record struct PixelFormat
@@ -35,6 +37,8 @@ namespace Avalonia.Platform
                 else if (FormatEnum == PixelFormatEnum.Rgb565 
                          || FormatEnum == PixelFormatEnum.Gray16)
                     return 16;
+                else if (FormatEnum is PixelFormatEnum.Bgr24 or PixelFormatEnum.Rgb24)
+                    return 24;
                 else if (FormatEnum == PixelFormatEnum.Rgba64)
                     return 64;
 
@@ -70,5 +74,7 @@ namespace Avalonia.Platform
         public static PixelFormat Gray8 { get; } = new PixelFormat(PixelFormatEnum.Gray8);
         public static PixelFormat Gray16 { get; } = new PixelFormat(PixelFormatEnum.Gray16);
         public static PixelFormat Gray32Float { get; } = new PixelFormat(PixelFormatEnum.Gray32Float);
+        public static PixelFormat Rgb24 { get; } = new PixelFormat(PixelFormatEnum.Rgb24);
+        public static PixelFormat Bgr24 { get; } = new PixelFormat(PixelFormatEnum.Bgr24);
     }
 }

+ 7 - 1
src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs

@@ -72,6 +72,11 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
 
     protected abstract T CreateItem();
 
+    protected virtual void ClearItem(T item)
+    {
+        
+    }
+
     protected virtual void DestroyItem(T item)
     {
         
@@ -94,6 +99,7 @@ internal abstract class BatchStreamPoolBase<T> : IDisposable
 
     public void Return(T item)
     {
+        ClearItem(item);
         lock (_pool)
         {
             _usage--;
@@ -138,7 +144,7 @@ internal sealed class BatchStreamObjectPool<T> : BatchStreamPoolBase<T[]> where
         return new T[ArraySize];
     }
 
-    protected override void DestroyItem(T[] item)
+    protected override void ClearItem(T[] item)
     {
         Array.Clear(item, 0, item.Length);
     }

+ 1 - 1
src/Avalonia.Base/Styling/ITemplate.cs

@@ -2,6 +2,6 @@
 {
     public interface ITemplate
     {
-        object Build();
+        object? Build();
     }
 }

+ 1 - 1
src/Avalonia.Base/Visual.cs

@@ -50,7 +50,7 @@ namespace Avalonia
             AvaloniaProperty.Register<Visual, Geometry?>(nameof(Clip));
 
         /// <summary>
-        /// Defines the <see cref="IsVisibleProperty"/> property.
+        /// Defines the <see cref="IsVisible"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> IsVisibleProperty =
             AvaloniaProperty.Register<Visual, bool>(nameof(IsVisible), true);

+ 1 - 3
src/Avalonia.Controls.ColorPicker/ColorPicker/ColorPicker.cs

@@ -1,6 +1,4 @@
-using Avalonia.Controls.Primitives;
-
-namespace Avalonia.Controls
+namespace Avalonia.Controls
 {
     /// <summary>
     /// Presents a color for user editing using a spectrum, palette and component sliders within a drop down.

+ 1 - 1
src/Avalonia.Controls.ColorPicker/ColorPreviewer/ColorPreviewer.cs

@@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives
             if (accentStep != 0)
             {
                 // ColorChanged will be invoked in OnPropertyChanged if the value is different
-                HsvColor = AccentColorConverter.GetAccent(hsvColor, accentStep);
+                SetCurrentValue(HsvColorProperty, AccentColorConverter.GetAccent(hsvColor, accentStep));
             }
         }
     }

+ 22 - 8
src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs

@@ -96,8 +96,22 @@ namespace Avalonia.Controls.Primitives
             // independent pixels of controls.
 
             var scale = LayoutHelper.GetLayoutScale(this);
-            var pixelWidth = Convert.ToInt32(Bounds.Width * scale);
-            var pixelHeight = Convert.ToInt32(Bounds.Height * scale);
+            int pixelWidth;
+            int pixelHeight;
+
+            if (base._track != null)
+            {
+                pixelWidth = Convert.ToInt32(base._track.Bounds.Width * scale);
+                pixelHeight = Convert.ToInt32(base._track.Bounds.Height * scale);
+            }
+            else
+            {
+                // As a fallback, attempt to calculate using the overall control size
+                // This shouldn't happen as a track is a required template part of a slider
+                // However, if it does, the spectrum will still be shown
+                pixelWidth = Convert.ToInt32(Bounds.Width * scale);
+                pixelHeight = Convert.ToInt32(Bounds.Height * scale);
+            }
 
             if (pixelWidth != 0 && pixelHeight != 0)
             {
@@ -373,7 +387,7 @@ namespace Avalonia.Controls.Primitives
                 ignorePropertyChanged = true;
 
                 // Always keep the two color properties in sync
-                HsvColor = Color.ToHsv();
+                SetCurrentValue(HsvColorProperty, Color.ToHsv());
 
                 SetColorToSliderValues();
                 UpdateBackground();
@@ -403,7 +417,7 @@ namespace Avalonia.Controls.Primitives
                 ignorePropertyChanged = true;
 
                 // Always keep the two color properties in sync
-                Color = HsvColor.ToRgb();
+                SetCurrentValue(ColorProperty, HsvColor.ToRgb());
 
                 SetColorToSliderValues();
                 UpdateBackground();
@@ -440,13 +454,13 @@ namespace Avalonia.Controls.Primitives
 
                 if (ColorModel == ColorModel.Hsva)
                 {
-                    HsvColor = hsvColor;
-                    Color = hsvColor.ToRgb();
+                    SetCurrentValue(HsvColorProperty, hsvColor);
+                    SetCurrentValue(ColorProperty, hsvColor.ToRgb());
                 }
                 else
                 {
-                    Color = color;
-                    HsvColor = color.ToHsv();
+                    SetCurrentValue(ColorProperty, color);
+                    SetCurrentValue(HsvColorProperty, color.ToHsv());
                 }
 
                 UpdatePseudoClasses();

+ 5 - 5
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.Properties.cs

@@ -96,10 +96,10 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Defines the <see cref="ThirdComponent"/> property.
         /// </summary>
-        public static readonly StyledProperty<ColorComponent> ThirdComponentProperty =
-            AvaloniaProperty.Register<ColorSpectrum, ColorComponent>(
+        public static readonly DirectProperty<ColorSpectrum, ColorComponent> ThirdComponentProperty =
+            AvaloniaProperty.RegisterDirect<ColorSpectrum, ColorComponent>(
                 nameof(ThirdComponent),
-                ColorComponent.Component3); // Value
+                o => o.ThirdComponent);
 
         /// <summary>
         /// Gets or sets the currently selected color in the RGB color model.
@@ -239,8 +239,8 @@ namespace Avalonia.Controls.Primitives
         /// </remarks>
         public ColorComponent ThirdComponent
         {
-            get => GetValue(ThirdComponentProperty);
-            protected set => SetValue(ThirdComponentProperty, value);
+            get => _thirdComponent;
+            private set => SetAndRaise(ThirdComponentProperty, ref _thirdComponent, value);
         }
     }
 }

+ 6 - 5
src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs

@@ -13,9 +13,9 @@ using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Media.Imaging;
+using Avalonia.Reactive;
 using Avalonia.Threading;
 using Avalonia.Utilities;
-using Avalonia.Reactive;
 
 namespace Avalonia.Controls.Primitives
 {
@@ -48,6 +48,7 @@ namespace Avalonia.Controls.Primitives
         private bool _isPointerPressed = false;
         private bool _shouldShowLargeSelection = false;
         private List<Hsv> _hsvValues = new List<Hsv>();
+        private ColorComponent _thirdComponent = ColorComponent.Component3; // HsvComponent.Value
 
         private IDisposable? _layoutRootDisposable;
         private IDisposable? _selectionEllipsePanelDisposable;
@@ -403,7 +404,7 @@ namespace Avalonia.Controls.Primitives
 
                     _updatingHsvColor = true;
                     Hsv newHsv = (new Rgb(color)).ToHsv();
-                    HsvColor = newHsv.ToHsvColor(color.A / 255.0);
+                    SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(color.A / 255.0));
                     _updatingHsvColor = false;
 
                     UpdateEllipse();
@@ -534,7 +535,7 @@ namespace Avalonia.Controls.Primitives
             _updatingColor = true;
             Rgb newRgb = (new Hsv(hsvColor)).ToRgb();
 
-            Color = newRgb.ToColor(hsvColor.A);
+            SetCurrentValue(ColorProperty, newRgb.ToColor(hsvColor.A));
 
             _updatingColor = false;
 
@@ -608,8 +609,8 @@ namespace Avalonia.Controls.Primitives
             Rgb newRgb = newHsv.ToRgb();
             double alpha = HsvColor.A;
 
-            Color = newRgb.ToColor(alpha);
-            HsvColor = newHsv.ToHsvColor(alpha);
+            SetCurrentValue(ColorProperty, newRgb.ToColor(alpha));
+            SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha));
 
             UpdateEllipse();
             UpdatePseudoClasses();

+ 26 - 0
src/Avalonia.Controls.ColorPicker/ColorView/AlphaComponentPosition.cs

@@ -0,0 +1,26 @@
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines the position of a color's alpha component relative to all other components.
+    /// </summary>
+    public enum AlphaComponentPosition
+    {
+        /// <summary>
+        /// The alpha component occurs before all other components.
+        /// </summary>
+        /// <remarks>
+        /// For example, this may indicate the #AARRGGBB or ARGB format which
+        /// is the default format for XAML itself and the Color struct.
+        /// </remarks>
+        Leading,
+
+        /// <summary>
+        /// The alpha component occurs after all other components.
+        /// </summary>
+        /// <remarks>
+        /// For example, this may indicate the #RRGGBBAA or RGBA format which
+        /// is the default format for CSS.
+        /// </remarks>
+        Trailing,
+    }
+}

+ 18 - 0
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs

@@ -42,6 +42,14 @@ namespace Avalonia.Controls
                 nameof(ColorSpectrumShape),
                 ColorSpectrumShape.Box);
 
+        /// <summary>
+        /// Defines the <see cref="HexInputAlphaPosition"/> property.
+        /// </summary>
+        public static readonly StyledProperty<AlphaComponentPosition> HexInputAlphaPositionProperty =
+            AvaloniaProperty.Register<ColorView, AlphaComponentPosition>(
+                nameof(HexInputAlphaPosition),
+                AlphaComponentPosition.Trailing); // Match CSS (and default slider order) instead of XAML/WinUI
+
         /// <summary>
         /// Defines the <see cref="HsvColor"/> property.
         /// </summary>
@@ -260,6 +268,16 @@ namespace Avalonia.Controls
             set => SetValue(ColorSpectrumShapeProperty, value);
         }
 
+        /// <summary>
+        /// Gets or sets the position of the alpha component in the hexadecimal input box relative to
+        /// all other color components.
+        /// </summary>
+        public AlphaComponentPosition HexInputAlphaPosition
+        {
+            get => GetValue(HexInputAlphaPositionProperty);
+            set => SetValue(HexInputAlphaPositionProperty, value);
+        }
+
         /// <inheritdoc cref="ColorSpectrum.HsvColor"/>
         public HsvColor HsvColor
         {

+ 15 - 20
src/Avalonia.Controls.ColorPicker/ColorView/ColorView.cs

@@ -1,14 +1,10 @@
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Globalization;
 using Avalonia.Controls.Converters;
 using Avalonia.Controls.Metadata;
-using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Media;
 using Avalonia.Threading;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -28,8 +24,7 @@ namespace Avalonia.Controls
         private TextBox?    _hexTextBox;
         private TabControl? _tabControl;
 
-        private ColorToHexConverter colorToHexConverter = new ColorToHexConverter();
-        protected bool ignorePropertyChanged = false;
+        protected bool _ignorePropertyChanged = false;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ColorView"/> class.
@@ -46,11 +41,11 @@ namespace Avalonia.Controls
         {
             if (_hexTextBox != null)
             {
-                var convertedColor = colorToHexConverter.ConvertBack(_hexTextBox.Text, typeof(Color), null, CultureInfo.CurrentCulture);
+                var convertedColor = ColorToHexConverter.ParseHexString(_hexTextBox.Text ?? string.Empty, HexInputAlphaPosition);
 
                 if (convertedColor is Color color)
                 {
-                    Color = color;
+                    SetCurrentValue(ColorProperty, color);
                 }
 
                 // Re-apply the hex value
@@ -66,7 +61,7 @@ namespace Avalonia.Controls
         {
             if (_hexTextBox != null)
             {
-                _hexTextBox.Text = colorToHexConverter.Convert(Color, typeof(string), null, CultureInfo.CurrentCulture) as string;
+                _hexTextBox.Text = ColorToHexConverter.ToHexString(Color, HexInputAlphaPosition);
             }
         }
 
@@ -167,7 +162,7 @@ namespace Avalonia.Controls
                 // The work-around for this is done here where SelectedIndex is forcefully
                 // synchronized with whatever the TabControl property value is. This is
                 // possible since selection validation is already done by this method.
-                SelectedIndex = _tabControl.SelectedIndex;
+                SetCurrentValue(SelectedIndexProperty, _tabControl.SelectedIndex);
             }
 
             return;
@@ -200,7 +195,7 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
-            if (ignorePropertyChanged)
+            if (_ignorePropertyChanged)
             {
                 base.OnPropertyChanged(change);
                 return;
@@ -209,29 +204,29 @@ namespace Avalonia.Controls
             // Always keep the two color properties in sync
             if (change.Property == ColorProperty)
             {
-                ignorePropertyChanged = true;
+                _ignorePropertyChanged = true;
 
-                HsvColor = Color.ToHsv();
+                SetCurrentValue(HsvColorProperty, Color.ToHsv());
                 SetColorToHexTextBox();
 
                 OnColorChanged(new ColorChangedEventArgs(
                     change.GetOldValue<Color>(),
                     change.GetNewValue<Color>()));
 
-                ignorePropertyChanged = false;
+                _ignorePropertyChanged = false;
             }
             else if (change.Property == HsvColorProperty)
             {
-                ignorePropertyChanged = true;
+                _ignorePropertyChanged = true;
 
-                Color = HsvColor.ToRgb();
+                SetCurrentValue(ColorProperty, HsvColor.ToRgb());
                 SetColorToHexTextBox();
 
                 OnColorChanged(new ColorChangedEventArgs(
                     change.GetOldValue<HsvColor>().ToRgb(),
                     change.GetNewValue<HsvColor>().ToRgb()));
 
-                ignorePropertyChanged = false;
+                _ignorePropertyChanged = false;
             }
             else if (change.Property == PaletteProperty)
             {
@@ -241,7 +236,7 @@ namespace Avalonia.Controls
                 // bound properties controlling the palette grid
                 if (palette != null)
                 {
-                    PaletteColumnCount = palette.ColorCount;
+                    SetCurrentValue(PaletteColumnCountProperty, palette.ColorCount);
 
                     List<Color> newPaletteColors = new List<Color>();
                     for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++)
@@ -252,14 +247,14 @@ namespace Avalonia.Controls
                         }
                     }
 
-                    PaletteColors = newPaletteColors;
+                    SetCurrentValue(PaletteColorsProperty, newPaletteColors);
                 }
             }
             else if (change.Property == IsAlphaEnabledProperty)
             {
                 // Manually coerce the HsvColor value
                 // (Color will be coerced automatically if HsvColor changes)
-                HsvColor = OnCoerceHsvColor(HsvColor);
+                SetCurrentValue(HsvColorProperty,  OnCoerceHsvColor(HsvColor));
             }
             else if (change.Property == IsColorComponentsVisibleProperty ||
                      change.Property == IsColorPaletteVisibleProperty ||

+ 152 - 17
src/Avalonia.Controls.ColorPicker/Converters/ColorToHexConverter.cs

@@ -2,6 +2,7 @@
 using System.Globalization;
 using Avalonia.Data.Converters;
 using Avalonia.Media;
+using Avalonia.Utilities;
 
 namespace Avalonia.Controls.Converters
 {
@@ -10,6 +11,11 @@ namespace Avalonia.Controls.Converters
     /// </summary>
     public class ColorToHexConverter : IValueConverter
     {
+        /// <summary>
+        /// Gets or sets the position of a color's alpha component relative to all other components.
+        /// </summary>
+        public AlphaComponentPosition AlphaPosition { get; set; } = AlphaComponentPosition.Leading;
+
         /// <inheritdoc/>
         public object? Convert(
             object? value,
@@ -42,16 +48,7 @@ namespace Avalonia.Controls.Converters
                 return AvaloniaProperty.UnsetValue;
             }
 
-            string hexColor = color.ToUint32().ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
-
-            if (includeSymbol == false)
-            {
-                // TODO: When .net standard 2.0 is dropped, replace the below line
-                //hexColor = hexColor.Replace("#", string.Empty, StringComparison.Ordinal);
-                hexColor = hexColor.Replace("#", string.Empty);
-            }
-
-            return hexColor;
+            return ToHexString(color, AlphaPosition, includeSymbol);
         }
 
         /// <inheritdoc/>
@@ -62,21 +59,159 @@ namespace Avalonia.Controls.Converters
             CultureInfo culture)
         {
             string hexValue = value?.ToString() ?? string.Empty;
+            return ParseHexString(hexValue, AlphaPosition) ?? AvaloniaProperty.UnsetValue;
+        }
+
+        /// <summary>
+        /// Converts the given color to its hex color value string representation.
+        /// </summary>
+        /// <param name="color">The color to represent as a hex value string.</param>
+        /// <param name="alphaPosition">The output position of the alpha component.</param>
+        /// <param name="includeSymbol">Whether the hex symbol '#' will be added.</param>
+        /// <returns>The input color converted to its hex value string.</returns>
+        public static string ToHexString(
+            Color color,
+            AlphaComponentPosition alphaPosition,
+            bool includeSymbol = false)
+        {
+            uint intColor;
+            if (alphaPosition == AlphaComponentPosition.Trailing)
+            {
+                intColor = ((uint)color.R << 24) | ((uint)color.G << 16) | ((uint)color.B << 8) | (uint)color.A;
+            }
+            else
+            {
+                // Default is Leading alpha
+                intColor = ((uint)color.A << 24) | ((uint)color.R << 16) | ((uint)color.G << 8) | (uint)color.B;
+            }
 
-            if (Color.TryParse(hexValue, out Color color))
+            string hexColor = intColor.ToString("x8", CultureInfo.InvariantCulture).ToUpperInvariant();
+
+            if (includeSymbol)
+            {
+                hexColor = '#' + hexColor;
+            }
+
+            return hexColor;
+        }
+
+        /// <summary>
+        /// Parses a hex color value string into a new <see cref="Color"/>.
+        /// </summary>
+        /// <param name="hexColor">The hex color string to parse.</param>
+        /// <param name="alphaPosition">The input position of the alpha component.</param>
+        /// <returns>The parsed <see cref="Color"/>; otherwise, null.</returns>
+        public static Color? ParseHexString(
+            string hexColor,
+            AlphaComponentPosition alphaPosition)
+        {
+            hexColor = hexColor.Trim();
+
+            if (!hexColor.StartsWith("#", StringComparison.Ordinal))
+            {
+                hexColor = "#" + hexColor;
+            }
+
+            if (TryParseHexFormat(hexColor.AsSpan(), alphaPosition, out Color color))
             {
                 return color;
             }
-            else if (hexValue.StartsWith("#", StringComparison.Ordinal) == false &&
-                     Color.TryParse("#" + hexValue, out Color color2))
+
+            return null;
+        }
+
+        /// <summary>
+        /// Parses the given span of characters representing a hex color value into a new <see cref="Color"/>.
+        /// </summary>
+        /// <remarks>
+        /// This is based on the Color.TryParseHexFormat() method.
+        /// It is copied because it needs to be extended to handle alpha position.
+        /// However, the alpha position enum is only available in the controls namespace with the ColorPicker control.
+        /// </remarks>
+        private static bool TryParseHexFormat(
+            ReadOnlySpan<char> s,
+            AlphaComponentPosition alphaPosition,
+            out Color color)
+        {
+            static bool TryParseCore(ReadOnlySpan<char> input, AlphaComponentPosition alphaPosition, ref Color color)
             {
-                return color2;
+                var alphaComponent = 0u;
+
+                if (input.Length == 6)
+                {
+                    if (alphaPosition == AlphaComponentPosition.Trailing)
+                    {
+                        alphaComponent = 0x000000FF;
+                    }
+                    else
+                    {
+                        alphaComponent = 0xFF000000;
+                    }
+                }
+                else if (input.Length != 8)
+                {
+                    return false;
+                }
+
+                if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
+                {
+                    return false;
+                }
+
+                if (alphaComponent != 0)
+                {
+                    if (alphaPosition == AlphaComponentPosition.Trailing)
+                    {
+                        parsed = (parsed << 8) | alphaComponent;
+                    }
+                    else
+                    {
+                        parsed = parsed | alphaComponent;
+                    }
+                }
+
+                if (alphaPosition == AlphaComponentPosition.Trailing)
+                {
+                    // #RRGGBBAA
+                    color = new Color(
+                        a: (byte)(parsed & 0xFF),
+                        r: (byte)((parsed >> 24) & 0xFF),
+                        g: (byte)((parsed >> 16) & 0xFF),
+                        b: (byte)((parsed >> 8) & 0xFF));
+                }
+                else
+                {
+                    // #AARRGGBB
+                    color = new Color(
+                        a: (byte)((parsed >> 24) & 0xFF),
+                        r: (byte)((parsed >> 16) & 0xFF),
+                        g: (byte)((parsed >> 8) & 0xFF),
+                        b: (byte)(parsed & 0xFF));
+                }
+
+                return true;
             }
-            else
+
+            color = default;
+
+            ReadOnlySpan<char> input = s.Slice(1);
+
+            // Handle shorthand cases like #FFF (RGB) or #FFFF (ARGB).
+            if (input.Length == 3 || input.Length == 4)
             {
-                // Invalid hex color value provided
-                return AvaloniaProperty.UnsetValue;
+                var extendedLength = 2 * input.Length;
+                Span<char> extended = stackalloc char[extendedLength];
+
+                for (int i = 0; i < input.Length; i++)
+                {
+                    extended[2 * i + 0] = input[i];
+                    extended[2 * i + 1] = input[i];
+                }
+
+                return TryParseCore(extended, alphaPosition, ref color);
             }
+
+            return TryParseCore(input, alphaPosition, ref color);
         }
     }
 }

+ 113 - 33
src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs

@@ -1,6 +1,6 @@
 using System;
-using System.Globalization;
 using System.Collections.Generic;
+using System.Globalization;
 using Avalonia.Media;
 using Avalonia.Utilities;
 
@@ -11,8 +11,11 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     public static class ColorHelper
     {
-        private static readonly Dictionary<Color, string> cachedDisplayNames = new Dictionary<Color, string>();
-        private static readonly object cacheMutex = new object();
+        private static readonly Dictionary<HsvColor, string> _cachedDisplayNames = new Dictionary<HsvColor, string>();
+        private static readonly Dictionary<KnownColor, string> _cachedKnownColorNames = new Dictionary<KnownColor, string>();
+        private static readonly object _displayNameCacheMutex = new object();
+        private static readonly object _knownColorCacheMutex = new object();
+        private static readonly KnownColor[] _knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
 
         /// <summary>
         /// Gets the relative (perceptual) luminance/brightness of the given color.
@@ -59,7 +62,36 @@ namespace Avalonia.Controls.Primitives
         /// <returns>The approximate color display name.</returns>
         public static string ToDisplayName(Color color)
         {
-            // Without rounding, there are 16,777,216 possible RGB colors (without alpha).
+            var hsvColor = color.ToHsv();
+
+            // Handle extremes that are outside the below algorithm
+            if (color.A == 0x00)
+            {
+                return GetDisplayName(KnownColor.Transparent);
+            }
+
+            // HSV ----------------------------------------------------------------------
+            //
+            // There are far too many possible HSV colors to cache and search through
+            // for performance reasons. Therefore, the HSV color is rounded.
+            // Rounding is tolerable in this algorithm because it is perception based.
+            // Hue is the most important for user perception so is rounded the least.
+            // Then there is a lot of loss in rounding the saturation and value components
+            // which are not as closely related to perceived color.
+            //
+            //         Hue : Round to nearest int (0..360)
+            //  Saturation : Round to the nearest 1/10 (0..1)
+            //       Value : Round to the nearest 1/10 (0..1)
+            //       Alpha : Is ignored in this algorithm
+            //
+            // Rounding results in ~36_000 values to cache in the worse case.
+            //
+            // RGB ----------------------------------------------------------------------
+            //
+            // The original algorithm worked in RGB color space.
+            // If this code is every adjusted to work in RGB again note the following:
+            //
+            // Without rounding, there are 16_777_216 possible RGB colors (without alpha).
             // This is too many to cache and search through for performance reasons.
             // It is also needlessly large as there are only ~140 known/named colors.
             // Therefore, rounding of the input color's component values is done to
@@ -68,42 +100,67 @@ namespace Avalonia.Controls.Primitives
             // The rounding value of 5 is specially chosen.
             // It is a factor of 255 and therefore evenly divisible which improves
             // the quality of the calculations.
-            double rounding = 5;
-            var roundedColor = new Color(
-                0xFF,
-                Convert.ToByte(Math.Round(color.R / rounding) * rounding),
-                Convert.ToByte(Math.Round(color.G / rounding) * rounding),
-                Convert.ToByte(Math.Round(color.B / rounding) * rounding));
+            var roundedHsvColor = new HsvColor(
+                1.0,
+                Math.Round(hsvColor.H, 0),
+                Math.Round(hsvColor.S, 1),
+                Math.Round(hsvColor.V, 1));
 
             // Attempt to use a previously cached display name
-            lock (cacheMutex)
+            lock (_displayNameCacheMutex)
             {
-                if (cachedDisplayNames.TryGetValue(roundedColor, out var displayName))
+                if (_cachedDisplayNames.TryGetValue(roundedHsvColor, out var displayName))
                 {
                     return displayName;
                 }
             }
 
+            // Build the KnownColor name cache if it doesn't already exist
+            lock (_knownColorCacheMutex)
+            {
+                if (_cachedKnownColorNames.Count == 0)
+                {
+                    for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
+                    {
+                        KnownColor knownColor = _knownColors[i];
+
+                        // Some known colors have the same numerical value. For example:
+                        //  - Aqua = 0xff00ffff
+                        //  - Cyan = 0xff00ffff
+                        //
+                        // This is not possible to represent in a dictionary which requires
+                        // unique values. Therefore, only the first value is used.
+
+                        if (!_cachedKnownColorNames.ContainsKey(knownColor))
+                        {
+                            _cachedKnownColorNames.Add(knownColor, GetDisplayName(knownColor));
+                        }
+                    }
+                }
+            }
+
             // Find the closest known color by measuring 3D Euclidean distance (ignore alpha)
+            // This is done in HSV color space to most closely match user-perception
             var closestKnownColor = KnownColor.None;
             var closestKnownColorDistance = double.PositiveInfinity;
-            var knownColors = (KnownColor[])Enum.GetValues(typeof(KnownColor));
 
-            for (int i = 1; i < knownColors.Length; i++) // Skip 'None'
+            for (int i = 1; i < _knownColors.Length; i++) // Skip 'None' so start at 1
             {
+                KnownColor knownColor = _knownColors[i];
+
                 // Transparent is skipped since alpha is ignored making it equivalent to White
-                if (knownColors[i] != KnownColor.Transparent)
+                if (knownColor != KnownColor.Transparent)
                 {
-                    Color knownColor = KnownColors.ToColor(knownColors[i]);
+                    HsvColor knownHsvColor = KnownColors.ToColor(knownColor).ToHsv();
 
                     double distance = Math.Sqrt(
-                        Math.Pow((double)(roundedColor.R - knownColor.R), 2.0) +
-                        Math.Pow((double)(roundedColor.G - knownColor.G), 2.0) +
-                        Math.Pow((double)(roundedColor.B - knownColor.B), 2.0));
+                        Math.Pow((roundedHsvColor.H - knownHsvColor.H), 2.0) +
+                        Math.Pow((roundedHsvColor.S - knownHsvColor.S), 2.0) +
+                        Math.Pow((roundedHsvColor.V - knownHsvColor.V), 2.0));
 
                     if (distance < closestKnownColorDistance)
                     {
-                        closestKnownColor = knownColors[i];
+                        closestKnownColor = knownColor;
                         closestKnownColorDistance = distance;
                     }
                 }
@@ -113,26 +170,19 @@ namespace Avalonia.Controls.Primitives
             // Cache results for next time as well
             if (closestKnownColor != KnownColor.None)
             {
-                var sb = StringBuilderCache.Acquire();
-                string name = closestKnownColor.ToString();
+                string? displayName;
 
-                // Add spaces converting PascalCase to human-readable names
-                for (int i = 0; i < name.Length; i++)
+                lock (_knownColorCacheMutex)
                 {
-                    if (i != 0 &&
-                        char.IsUpper(name[i]))
+                    if (!_cachedKnownColorNames.TryGetValue(closestKnownColor, out displayName))
                     {
-                        sb.Append(' ');
+                        displayName = GetDisplayName(closestKnownColor);
                     }
-
-                    sb.Append(name[i]);
                 }
 
-                string displayName = StringBuilderCache.GetStringAndRelease(sb);
-
-                lock (cacheMutex)
+                lock (_displayNameCacheMutex)
                 {
-                    cachedDisplayNames.Add(roundedColor, displayName);
+                    _cachedDisplayNames.Add(roundedHsvColor, displayName);
                 }
 
                 return displayName;
@@ -142,5 +192,35 @@ namespace Avalonia.Controls.Primitives
                 return string.Empty;
             }
         }
+
+        /// <summary>
+        /// Gets the human-readable display name for the given <see cref="KnownColor"/>.
+        /// </summary>
+        /// <remarks>
+        /// This currently uses the <see cref="KnownColor"/> enum value's C# name directly
+        /// which limits it to the EN language only. In the future this should be localized
+        /// to other cultures.
+        /// </remarks>
+        /// <param name="knownColor">The <see cref="KnownColor"/> to get the display name for.</param>
+        /// <returns>The human-readable display name for the given <see cref="KnownColor"/>.</returns>
+        private static string GetDisplayName(KnownColor knownColor)
+        {
+            var sb = StringBuilderCache.Acquire();
+            string name = knownColor.ToString();
+
+            // Add spaces converting PascalCase to human-readable names
+            for (int i = 0; i < name.Length; i++)
+            {
+                if (i != 0 &&
+                    char.IsUpper(name[i]))
+                {
+                    sb.Append(' ');
+                }
+
+                sb.Append(name[i]);
+            }
+
+            return StringBuilderCache.GetStringAndRelease(sb);
+        }
     }
 }

+ 0 - 2
src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs

@@ -4,8 +4,6 @@
 // Licensed to The Avalonia Project under the MIT License.
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 using Avalonia.Layout;

+ 4 - 1
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@@ -42,7 +42,8 @@
             </Panel>
           </DropDownButton.Content>
           <DropDownButton.Flyout>
-            <Flyout FlyoutPresenterClasses="nopadding">
+            <Flyout FlyoutPresenterClasses="nopadding"
+                    Placement="Top">
 
               <!-- The following is copy-pasted from the ColorView's control template.
                    It MUST always be kept in sync with the ColorView (which is master).
@@ -216,6 +217,7 @@
                                        Content="RGB"
                                        CornerRadius="4,0,0,4"
                                        BorderThickness="1,1,0,1"
+                                       Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                        IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
                           <RadioButton x:Name="HsvRadioButton"
                                        Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@@ -223,6 +225,7 @@
                                        Content="HSV"
                                        CornerRadius="0,4,4,0"
                                        BorderThickness="0,1,1,1"
+                                       Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                        IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
                         </Grid>
                         <Grid x:Name="HexInputGrid"

+ 38 - 29
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorSlider.xaml

@@ -1,13 +1,20 @@
 <ResourceDictionary xmlns="https://github.com/avaloniaui"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
+  <!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
+       Therefore, there are not currently separate resources to control it. -->
+  <x:Double x:Key="ColorSliderSize">20</x:Double>
+  <x:Double x:Key="ColorSliderTrackSize">20</x:Double>
+  <CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
+  <CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
+
   <ControlTheme x:Key="ColorSliderThumbTheme"
                 TargetType="Thumb">
     <Setter Property="Background" Value="Transparent" />
     <Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
     <!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
     <Setter Property="BorderThickness" Value="3" />
-    <Setter Property="CornerRadius" Value="10" />
+    <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
     <Setter Property="Template">
       <Setter.Value>
         <ControlTemplate>
@@ -25,27 +32,28 @@
 
     <Style Selector="^:horizontal">
       <Setter Property="BorderThickness" Value="0" />
-      <Setter Property="CornerRadius" Value="10" />
-      <Setter Property="Height" Value="20" />
+      <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
+      <Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
       <Setter Property="Template">
         <ControlTemplate TargetType="{x:Type ColorSlider}">
           <Border BorderThickness="{TemplateBinding BorderThickness}"
                   BorderBrush="{TemplateBinding BorderBrush}"
                   CornerRadius="{TemplateBinding CornerRadius}">
             <Grid Margin="{TemplateBinding Padding}">
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{TemplateBinding Background}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
+              <Border HorizontalAlignment="Stretch"
+                      VerticalAlignment="Center"
+                      Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
+                      Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
+              <Border HorizontalAlignment="Stretch"
+                      VerticalAlignment="Center"
+                      Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
+                      Background="{TemplateBinding Background}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
               <Track Name="PART_Track"
+                     Height="{DynamicResource ColorSliderTrackSize}"
                      HorizontalAlignment="Stretch"
-                     VerticalAlignment="Stretch"
+                     VerticalAlignment="Center"
                      Minimum="{TemplateBinding Minimum}"
                      Maximum="{TemplateBinding Maximum}"
                      Value="{TemplateBinding Value, Mode=TwoWay}"
@@ -82,7 +90,7 @@
                   </RepeatButton>
                 </Track.IncreaseButton>
                 <Thumb Name="ColorSliderThumb"
-                       Theme="{StaticResource ColorSliderThumbTheme}"
+                       Theme="{DynamicResource ColorSliderThumbTheme}"
                        Margin="0"
                        Padding="0"
                        DataContext="{TemplateBinding Value}"
@@ -97,26 +105,27 @@
 
     <Style Selector="^:vertical">
       <Setter Property="BorderThickness" Value="0" />
-      <Setter Property="CornerRadius" Value="10" />
-      <Setter Property="Width" Value="20" />
+      <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
+      <Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
       <Setter Property="Template">
         <ControlTemplate TargetType="{x:Type ColorSlider}">
           <Border BorderThickness="{TemplateBinding BorderThickness}"
                   BorderBrush="{TemplateBinding BorderBrush}"
                   CornerRadius="{TemplateBinding CornerRadius}">
             <Grid Margin="{TemplateBinding Padding}">
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{TemplateBinding Background}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
+              <Border HorizontalAlignment="Center"
+                      VerticalAlignment="Stretch"
+                      Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
+                      Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
+              <Border HorizontalAlignment="Center"
+                      VerticalAlignment="Stretch"
+                      Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
+                      Background="{TemplateBinding Background}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
               <Track Name="PART_Track"
-                     HorizontalAlignment="Stretch"
+                     Width="{DynamicResource ColorSliderTrackSize}"
+                     HorizontalAlignment="Center"
                      VerticalAlignment="Stretch"
                      Minimum="{TemplateBinding Minimum}"
                      Maximum="{TemplateBinding Maximum}"
@@ -154,7 +163,7 @@
                   </RepeatButton>
                 </Track.IncreaseButton>
                 <Thumb Name="ColorSliderThumb"
-                       Theme="{StaticResource ColorSliderThumbTheme}"
+                       Theme="{DynamicResource ColorSliderThumbTheme}"
                        Margin="0"
                        Padding="0"
                        DataContext="{TemplateBinding Value}"

+ 2 - 1
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@@ -8,7 +8,6 @@
 
   <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
   <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
-  <converters:ColorToHexConverter x:Key="ColorToHexConverter" />
   <converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
   <globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
   <x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@@ -465,6 +464,7 @@
                                  Content="RGB"
                                  CornerRadius="4,0,0,4"
                                  BorderThickness="1,1,0,1"
+                                 Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                  IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
                     <RadioButton x:Name="HsvRadioButton"
                                  Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@@ -472,6 +472,7 @@
                                  Content="HSV"
                                  CornerRadius="0,4,4,0"
                                  BorderThickness="0,1,1,1"
+                                 Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                  IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
                   </Grid>
                   <Grid x:Name="HexInputGrid"

+ 3 - 1
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@@ -42,7 +42,7 @@
             </Panel>
           </DropDownButton.Content>
           <DropDownButton.Flyout>
-            <Flyout>
+            <Flyout Placement="Top">
 
               <!-- The following is copy-pasted from the ColorView's control template.
                    It MUST always be kept in sync with the ColorView (which is master).
@@ -216,6 +216,7 @@
                                        Content="RGB"
                                        CornerRadius="0,0,0,0"
                                        BorderThickness="1,1,0,1"
+                                       Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                        IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
                           <RadioButton x:Name="HsvRadioButton"
                                        Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@@ -223,6 +224,7 @@
                                        Content="HSV"
                                        CornerRadius="0,0,0,0"
                                        BorderThickness="0,1,1,1"
+                                       Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                        IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
                         </Grid>
                         <Grid x:Name="HexInputGrid"

+ 38 - 29
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml

@@ -1,13 +1,20 @@
 <ResourceDictionary xmlns="https://github.com/avaloniaui"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
+  <!-- Note that the Slider thumb should generally follow the overall Slider dimensions.
+       Therefore, there are not currently separate resources to control it. -->
+  <x:Double x:Key="ColorSliderSize">20</x:Double>
+  <x:Double x:Key="ColorSliderTrackSize">20</x:Double>
+  <CornerRadius x:Key="ColorSliderCornerRadius">10</CornerRadius>
+  <CornerRadius x:Key="ColorSliderTrackCornerRadius">10</CornerRadius>
+
   <ControlTheme x:Key="ColorSliderThumbTheme"
                 TargetType="Thumb">
     <Setter Property="Background" Value="Transparent" />
     <Setter Property="BorderBrush" Value="{DynamicResource ThemeForegroundBrush}" />
     <!--TODO: <Setter Property="BorderBrush" Value="{DynamicResource ColorControlDefaultSelectorBrush}" />-->
     <Setter Property="BorderThickness" Value="3" />
-    <Setter Property="CornerRadius" Value="10" />
+    <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
     <Setter Property="Template">
       <Setter.Value>
         <ControlTemplate>
@@ -25,27 +32,28 @@
 
     <Style Selector="^:horizontal">
       <Setter Property="BorderThickness" Value="0" />
-      <Setter Property="CornerRadius" Value="10" />
-      <Setter Property="Height" Value="20" />
+      <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
+      <Setter Property="Height" Value="{DynamicResource ColorSliderSize}" />
       <Setter Property="Template">
         <ControlTemplate TargetType="{x:Type ColorSlider}">
           <Border BorderThickness="{TemplateBinding BorderThickness}"
                   BorderBrush="{TemplateBinding BorderBrush}"
                   CornerRadius="{TemplateBinding CornerRadius}">
             <Grid Margin="{TemplateBinding Padding}">
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{TemplateBinding Background}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
+              <Border HorizontalAlignment="Stretch"
+                      VerticalAlignment="Center"
+                      Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
+                      Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
+              <Border HorizontalAlignment="Stretch"
+                      VerticalAlignment="Center"
+                      Height="{Binding ElementName=PART_Track, Path=Bounds.Height}"
+                      Background="{TemplateBinding Background}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
               <Track Name="PART_Track"
+                     Height="{DynamicResource ColorSliderTrackSize}"
                      HorizontalAlignment="Stretch"
-                     VerticalAlignment="Stretch"
+                     VerticalAlignment="Center"
                      Minimum="{TemplateBinding Minimum}"
                      Maximum="{TemplateBinding Maximum}"
                      Value="{TemplateBinding Value, Mode=TwoWay}"
@@ -82,7 +90,7 @@
                   </RepeatButton>
                 </Track.IncreaseButton>
                 <Thumb Name="ColorSliderThumb"
-                       Theme="{StaticResource ColorSliderThumbTheme}"
+                       Theme="{DynamicResource ColorSliderThumbTheme}"
                        Margin="0"
                        Padding="0"
                        DataContext="{TemplateBinding Value}"
@@ -97,26 +105,27 @@
 
     <Style Selector="^:vertical">
       <Setter Property="BorderThickness" Value="0" />
-      <Setter Property="CornerRadius" Value="10" />
-      <Setter Property="Width" Value="20" />
+      <Setter Property="CornerRadius" Value="{DynamicResource ColorSliderCornerRadius}" />
+      <Setter Property="Width" Value="{DynamicResource ColorSliderSize}" />
       <Setter Property="Template">
         <ControlTemplate TargetType="{x:Type ColorSlider}">
           <Border BorderThickness="{TemplateBinding BorderThickness}"
                   BorderBrush="{TemplateBinding BorderBrush}"
                   CornerRadius="{TemplateBinding CornerRadius}">
             <Grid Margin="{TemplateBinding Padding}">
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{StaticResource ColorControlCheckeredBackgroundBrush}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
-              <Rectangle HorizontalAlignment="Stretch"
-                         VerticalAlignment="Stretch"
-                         Fill="{TemplateBinding Background}"
-                         RadiusX="{TemplateBinding CornerRadius, Converter={StaticResource TopLeftCornerRadiusConverter}}"
-                         RadiusY="{TemplateBinding CornerRadius, Converter={StaticResource BottomRightCornerRadiusConverter}}" />
+              <Border HorizontalAlignment="Center"
+                      VerticalAlignment="Stretch"
+                      Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
+                      Background="{StaticResource ColorControlCheckeredBackgroundBrush}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
+              <Border HorizontalAlignment="Center"
+                      VerticalAlignment="Stretch"
+                      Width="{Binding ElementName=PART_Track, Path=Bounds.Width}"
+                      Background="{TemplateBinding Background}"
+                      CornerRadius="{DynamicResource ColorSliderTrackCornerRadius}" />
               <Track Name="PART_Track"
-                     HorizontalAlignment="Stretch"
+                     Width="{DynamicResource ColorSliderTrackSize}"
+                     HorizontalAlignment="Center"
                      VerticalAlignment="Stretch"
                      Minimum="{TemplateBinding Minimum}"
                      Maximum="{TemplateBinding Maximum}"
@@ -154,7 +163,7 @@
                   </RepeatButton>
                 </Track.IncreaseButton>
                 <Thumb Name="ColorSliderThumb"
-                       Theme="{StaticResource ColorSliderThumbTheme}"
+                       Theme="{DynamicResource ColorSliderThumbTheme}"
                        Margin="0"
                        Padding="0"
                        DataContext="{TemplateBinding Value}"

+ 2 - 1
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@@ -8,7 +8,6 @@
 
   <pc:ContrastBrushConverter x:Key="ContrastBrushConverter" />
   <converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
-  <converters:ColorToHexConverter x:Key="ColorToHexConverter" />
   <converters:DoNothingForNullConverter x:Key="DoNothingForNullConverter" />
   <globalization:NumberFormatInfo x:Key="ColorViewComponentNumberFormat" NumberDecimalDigits="0" />
   <x:Double x:Key="ColorViewTabStripHeight">48</x:Double>
@@ -427,6 +426,7 @@
                                  Content="RGB"
                                  CornerRadius="0,0,0,0"
                                  BorderThickness="1,1,0,1"
+                                 Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                  IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Rgba}, Mode=TwoWay}" />
                     <RadioButton x:Name="HsvRadioButton"
                                  Theme="{StaticResource ColorViewColorModelRadioButtonTheme}"
@@ -434,6 +434,7 @@
                                  Content="HSV"
                                  CornerRadius="0,0,0,0"
                                  BorderThickness="0,1,1,1"
+                                 Height="{Binding ElementName=PART_HexTextBox, Path=Bounds.Height}"
                                  IsChecked="{TemplateBinding ColorModel, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static controls:ColorModel.Hsva}, Mode=TwoWay}" />
                   </Grid>
                   <Grid x:Name="HexInputGrid"

+ 1 - 1
src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs

@@ -113,7 +113,7 @@ namespace Avalonia.Layout
             AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing));
 
         /// <summary>
-        /// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
+        /// Defines the <see cref="MaximumRowsOrColumns"/> property.
         /// </summary>
         public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
             AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MaximumRowsOrColumns));

+ 47 - 57
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@@ -52,34 +52,32 @@ namespace Avalonia.Controls
         /// </summary>
         /// <value>The identifier for the <see cref="IsTextCompletionEnabled" /> property.</value>
         public static readonly StyledProperty<bool> IsTextCompletionEnabledProperty =
-            AvaloniaProperty.Register<AutoCompleteBox, bool>(nameof(IsTextCompletionEnabled));
+            AvaloniaProperty.Register<AutoCompleteBox, bool>(
+                nameof(IsTextCompletionEnabled));
 
         /// <summary>
         /// Identifies the <see cref="ItemTemplate" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="ItemTemplate" /> property.</value>
         public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
-            AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(nameof(ItemTemplate));
+            AvaloniaProperty.Register<AutoCompleteBox, IDataTemplate>(
+                nameof(ItemTemplate));
 
         /// <summary>
         /// Identifies the <see cref="IsDropDownOpen" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="IsDropDownOpen" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, bool> IsDropDownOpenProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, bool>(
-                nameof(IsDropDownOpen),
-                o => o.IsDropDownOpen,
-                (o, v) => o.IsDropDownOpen = v);
+        public static readonly StyledProperty<bool> IsDropDownOpenProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, bool>(
+                nameof(IsDropDownOpen));
 
         /// <summary>
         /// Identifies the <see cref="SelectedItem" /> property.
         /// </summary>
         /// <value>The identifier the <see cref="SelectedItem" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, object?> SelectedItemProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, object?>(
+        public static readonly StyledProperty<object?> SelectedItemProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, object?>(
                 nameof(SelectedItem),
-                o => o.SelectedItem,
-                (o, v) => o.SelectedItem = v,
                 defaultBindingMode: BindingMode.TwoWay,
                 enableDataValidation: true);
 
@@ -115,58 +113,50 @@ namespace Avalonia.Controls
         /// Identifies the <see cref="ItemFilter" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="ItemFilter" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
-                nameof(ItemFilter),
-                o => o.ItemFilter,
-                (o, v) => o.ItemFilter = v);
+        public static readonly StyledProperty<AutoCompleteFilterPredicate<object?>?> ItemFilterProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterPredicate<object?>?>(
+                nameof(ItemFilter));
 
         /// <summary>
         /// Identifies the <see cref="TextFilter" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="TextFilter" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
+        public static readonly StyledProperty<AutoCompleteFilterPredicate<string?>?> TextFilterProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteFilterPredicate<string?>?>(
                 nameof(TextFilter),
-                o => o.TextFilter,
-                (o, v) => o.TextFilter = v,
-                unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
+                defaultValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
 
         /// <summary>
         /// Identifies the <see cref="ItemSelector" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="ItemSelector" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<object>?> ItemSelectorProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<object>?>(
-                nameof(ItemSelector),
-                o => o.ItemSelector,
-                (o, v) => o.ItemSelector = v);
+        public static readonly StyledProperty<AutoCompleteSelector<object>?> ItemSelectorProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteSelector<object>?>(
+                nameof(ItemSelector));
 
         /// <summary>
         /// Identifies the <see cref="TextSelector" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="TextSelector" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, AutoCompleteSelector<string?>?> TextSelectorProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, AutoCompleteSelector<string?>?>(
-                nameof(TextSelector),
-                o => o.TextSelector,
-                (o, v) => o.TextSelector = v);
+        public static readonly StyledProperty<AutoCompleteSelector<string?>?> TextSelectorProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, AutoCompleteSelector<string?>?>(
+                nameof(TextSelector));
 
         /// <summary>
         /// Identifies the <see cref="Items" /> property.
         /// </summary>
         /// <value>The identifier for the <see cref="Items" /> property.</value>
-        public static readonly DirectProperty<AutoCompleteBox, IEnumerable?> ItemsProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, IEnumerable?>(
-                nameof(Items),
-                o => o.Items,
-                (o, v) => o.Items = v);
+        public static readonly StyledProperty<IEnumerable?> ItemsProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, IEnumerable?>(
+                nameof(Items));
 
-        public static readonly DirectProperty<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
-            AvaloniaProperty.RegisterDirect<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
-                nameof(AsyncPopulator),
-                o => o.AsyncPopulator,
-                (o, v) => o.AsyncPopulator = v);
+        /// <summary>
+        /// Identifies the <see cref="AsyncPopulator" /> property.
+        /// </summary>
+        /// <value>The identifier for the <see cref="AsyncPopulator" /> property.</value>
+        public static readonly StyledProperty<Func<string?, CancellationToken, Task<IEnumerable<object>>>?> AsyncPopulatorProperty =
+            AvaloniaProperty.Register<AutoCompleteBox, Func<string?, CancellationToken, Task<IEnumerable<object>>>?>(
+                nameof(AsyncPopulator));
 
         /// <summary>
         /// Gets or sets the minimum number of characters required to be entered
@@ -265,8 +255,8 @@ namespace Avalonia.Controls
         /// </value>
         public bool IsDropDownOpen
         {
-            get => _isDropDownOpen;
-            set => SetAndRaise(IsDropDownOpenProperty, ref  _isDropDownOpen, value);
+            get => GetValue(IsDropDownOpenProperty);
+            set => SetValue(IsDropDownOpenProperty, value);
         }
 
         /// <summary>
@@ -303,8 +293,8 @@ namespace Avalonia.Controls
         /// </remarks>
         public object? SelectedItem
         {
-            get => _selectedItem;
-            set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
+            get => GetValue(SelectedItemProperty);
+            set => SetValue(SelectedItemProperty, value);
         }
 
         /// <summary>
@@ -388,8 +378,8 @@ namespace Avalonia.Controls
         /// </remarks>
         public AutoCompleteFilterPredicate<object?>? ItemFilter
         {
-            get => _itemFilter;
-            set => SetAndRaise(ItemFilterProperty, ref _itemFilter, value);
+            get => GetValue(ItemFilterProperty);
+            set => SetValue(ItemFilterProperty, value);
         }
 
         /// <summary>
@@ -406,8 +396,8 @@ namespace Avalonia.Controls
         /// </remarks>
         public AutoCompleteFilterPredicate<string?>? TextFilter
         {
-            get => _textFilter;
-            set => SetAndRaise(TextFilterProperty, ref _textFilter, value);
+            get => GetValue(TextFilterProperty);
+            set => SetValue(TextFilterProperty, value);
         }
 
         /// <summary>
@@ -420,8 +410,8 @@ namespace Avalonia.Controls
         /// </value>
         public AutoCompleteSelector<object>? ItemSelector
         {
-            get => _itemSelector;
-            set => SetAndRaise(ItemSelectorProperty, ref _itemSelector, value);
+            get => GetValue(ItemSelectorProperty);
+            set => SetValue(ItemSelectorProperty, value);
         }
 
         /// <summary>
@@ -436,14 +426,14 @@ namespace Avalonia.Controls
         /// </value>
         public AutoCompleteSelector<string?>? TextSelector
         {
-            get => _textSelector;
-            set => SetAndRaise(TextSelectorProperty, ref _textSelector, value);
+            get => GetValue(TextSelectorProperty);
+            set => SetValue(TextSelectorProperty, value);
         }
 
         public Func<string?, CancellationToken, Task<IEnumerable<object>>>? AsyncPopulator
         {
-            get => _asyncPopulator;
-            set => SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value);
+            get => GetValue(AsyncPopulatorProperty);
+            set => SetValue(AsyncPopulatorProperty, value);
         }
 
         /// <summary>
@@ -454,8 +444,8 @@ namespace Avalonia.Controls
         /// drop-down portion of the <see cref="AutoCompleteBox" /> control.</value>
         public IEnumerable? Items
         {
-            get => _itemsEnumerable;
-            set => SetAndRaise(ItemsProperty, ref _itemsEnumerable, value);
+            get => GetValue(ItemsProperty);
+            set => SetValue(ItemsProperty, value);
         }
     }
 }

+ 28 - 37
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@@ -94,8 +94,6 @@ namespace Avalonia.Controls
         /// </summary>
         private const string ElementTextBox = "PART_TextBox";
 
-        private IEnumerable? _itemsEnumerable;
-
         /// <summary>
         /// Gets or sets a local cached copy of the items data.
         /// </summary>
@@ -188,24 +186,15 @@ namespace Avalonia.Controls
         /// </summary>
         private IDisposable? _collectionChangeSubscription;
 
-        private Func<string?, CancellationToken, Task<IEnumerable<object>>>? _asyncPopulator;
         private CancellationTokenSource? _populationCancellationTokenSource;
 
         private bool _itemTemplateIsFromValueMemberBinding = true;
         private bool _settingItemTemplateFromValueMemberBinding;
 
-        private object? _selectedItem;
-        private bool _isDropDownOpen;
         private bool _isFocused = false;
 
         private string? _searchText = string.Empty;
 
-        private AutoCompleteFilterPredicate<object?>? _itemFilter;
-        private AutoCompleteFilterPredicate<string?>? _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith);
-
-        private AutoCompleteSelector<object>? _itemSelector;
-        private AutoCompleteSelector<string?>? _textSelector;
-
         private readonly EventHandler _populateDropDownHandler;
 
         /// <summary>
@@ -264,7 +253,7 @@ namespace Avalonia.Controls
             bool isEnabled = (bool)e.NewValue!;
             if (!isEnabled)
             {
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
             }
         }
 
@@ -388,7 +377,7 @@ namespace Avalonia.Controls
             {
                 // Reset the old value before it was incorrectly written
                 _ignorePropertyChange = true;
-                SetValue(e.Property, e.OldValue);
+                SetCurrentValue(e.Property, e.OldValue);
 
                 throw new InvalidOperationException("Cannot set read-only property SearchText.");
             }
@@ -403,7 +392,7 @@ namespace Avalonia.Controls
             AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue!;
 
             // Sets the filter predicate for the new value
-            TextFilter = AutoCompleteSearch.GetFilter(mode);
+            SetCurrentValue(TextFilterProperty, AutoCompleteSearch.GetFilter(mode));
         }
 
         /// <summary>
@@ -417,12 +406,12 @@ namespace Avalonia.Controls
             // If null, revert to the "None" predicate
             if (value == null)
             {
-                FilterMode = AutoCompleteFilterMode.None;
+                SetCurrentValue(FilterModeProperty, AutoCompleteFilterMode.None);
             }
             else
             {
-                FilterMode = AutoCompleteFilterMode.Custom;
-                TextFilter = null;
+                SetCurrentValue(FilterModeProperty, AutoCompleteFilterMode.Custom);
+                SetCurrentValue(TextFilterProperty, null);
             }
         }
 
@@ -442,7 +431,7 @@ namespace Avalonia.Controls
         }
         private void OnValueMemberBindingChanged(IBinding? value)
         {
-            if(_itemTemplateIsFromValueMemberBinding)
+            if (_itemTemplateIsFromValueMemberBinding)
             {
                 var template =
                     new FuncDataTemplate(
@@ -456,7 +445,7 @@ namespace Avalonia.Controls
                         });
 
                 _settingItemTemplateFromValueMemberBinding = true;
-                ItemTemplate = template;
+                SetCurrentValue(ItemTemplateProperty, template);
                 _settingItemTemplateFromValueMemberBinding = false;
             }
         }
@@ -713,7 +702,7 @@ namespace Avalonia.Controls
                 // The drop down is not open, the Down key will toggle it open.
                 if (e.Key == Key.Down)
                 {
-                    IsDropDownOpen = true;
+                    SetCurrentValue(IsDropDownOpenProperty, true);
                     e.Handled = true;
                 }
             }
@@ -722,7 +711,7 @@ namespace Avalonia.Controls
             switch (e.Key)
             {
                 case Key.F4:
-                    IsDropDownOpen = !IsDropDownOpen;
+                    SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
                     e.Handled = true;
                     break;
 
@@ -827,7 +816,7 @@ namespace Avalonia.Controls
             }
             else
             {
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
                 _userCalledPopulate = false;
                 ClearTextBoxSelection();
             }
@@ -1021,7 +1010,7 @@ namespace Avalonia.Controls
             if (args.Cancel)
             {
                 _ignorePropertyChange = true;
-                SetValue(IsDropDownOpenProperty, oldValue);
+                SetCurrentValue(IsDropDownOpenProperty, oldValue);
             }
             else
             {
@@ -1046,7 +1035,7 @@ namespace Avalonia.Controls
             if (args.Cancel)
             {
                 _ignorePropertyChange = true;
-                SetValue(IsDropDownOpenProperty, oldValue);
+                SetCurrentValue(IsDropDownOpenProperty, oldValue);
             }
             else
             {
@@ -1066,7 +1055,7 @@ namespace Avalonia.Controls
             // Force the drop down dependency property to be false.
             if (IsDropDownOpen)
             {
-                IsDropDownOpen = false;
+                SetCurrentValue(IsDropDownOpenProperty, false);
             }
 
             // Fire the DropDownClosed event
@@ -1088,7 +1077,7 @@ namespace Avalonia.Controls
             // Update the prefix/search text.
             SearchText = Text;
 
-            if(TryPopulateAsync(SearchText))
+            if (TryPopulateAsync(SearchText))
             {
                 return;
             }
@@ -1110,7 +1099,7 @@ namespace Avalonia.Controls
             _populationCancellationTokenSource?.Dispose();
             _populationCancellationTokenSource = null;
 
-            if(_asyncPopulator == null)
+            if (AsyncPopulator == null)
             {
                 return false;
             }
@@ -1127,7 +1116,7 @@ namespace Avalonia.Controls
 
             try
             {
-                IEnumerable<object> result = await _asyncPopulator!.Invoke(searchText, cancellationToken);
+                IEnumerable<object> result = await AsyncPopulator!.Invoke(searchText, cancellationToken);
                 var resultList = result.ToList();
 
                 if (cancellationToken.IsCancellationRequested)
@@ -1139,7 +1128,7 @@ namespace Avalonia.Controls
                 {
                     if (!cancellationToken.IsCancellationRequested)
                     {
-                        Items = resultList;
+                        SetCurrentValue(ItemsProperty, resultList);
                         PopulateComplete();
                     }
                 });
@@ -1199,7 +1188,7 @@ namespace Avalonia.Controls
         private string? FormatValue(object? value, bool clearDataContext)
         {
             string? result = FormatValue(value);
-            if(clearDataContext && _valueBindingEvaluator != null)
+            if (clearDataContext && _valueBindingEvaluator != null)
             {
                 _valueBindingEvaluator.ClearDataContext();
             }
@@ -1332,7 +1321,7 @@ namespace Avalonia.Controls
             // 1. Minimum prefix length
             // 2. If a delay timer is in use, use it
             bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0;
-            if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
+            if (populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText))
             {
                 populateReady = false;
             }
@@ -1361,10 +1350,12 @@ namespace Avalonia.Controls
                 {
                     _skipSelectedItemTextUpdate = true;
                 }
-                SelectedItem = null;
+
+                SetCurrentValue(SelectedItemProperty, null);
+
                 if (IsDropDownOpen)
                 {
-                    IsDropDownOpen = false;
+                    SetCurrentValue(IsDropDownOpenProperty, false);
                 }
             }
         }
@@ -1600,7 +1591,7 @@ namespace Avalonia.Controls
             if (isDropDownOpen != IsDropDownOpen)
             {
                 _ignorePropertyChange = true;
-                IsDropDownOpen = isDropDownOpen;
+                SetCurrentValue(IsDropDownOpenProperty, isDropDownOpen);
             }
             if (IsDropDownOpen)
             {
@@ -1688,7 +1679,7 @@ namespace Avalonia.Controls
             {
                 _skipSelectedItemTextUpdate = true;
             }
-            SelectedItem = newSelectedItem;
+            SetCurrentValue(SelectedItemProperty, newSelectedItem);
 
             // Restore updates for TextSelection
             if (_ignoreTextSelectionChange)
@@ -1784,7 +1775,7 @@ namespace Avalonia.Controls
         /// <param name="e">The selection changed event data.</param>
         private void OnAdapterSelectionChanged(object? sender, SelectionChangedEventArgs e)
         {
-            SelectedItem = _adapter!.SelectedItem;
+            SetCurrentValue(SelectedItemProperty, _adapter!.SelectedItem);
         }
 
         //TODO Check UpdateTextCompletion
@@ -1795,7 +1786,7 @@ namespace Avalonia.Controls
         /// <param name="e">The event data.</param>
         private void OnAdapterSelectionComplete(object? sender, RoutedEventArgs e)
         {
-            IsDropDownOpen = false;
+            SetCurrentValue(IsDropDownOpenProperty, false);
 
             // Completion will update the selected value
             //UpdateTextCompletion(false);

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

@@ -1,7 +1,6 @@
 using Avalonia.Animation;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
-using Avalonia.Controls.Utils;
 
 namespace Avalonia.Controls
 {
@@ -20,8 +19,8 @@ namespace Avalonia.Controls
         /// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for 
         /// <see cref="Carousel"/>.
         /// </summary>
-        private static readonly ITemplate<Panel> PanelTemplate =
-            new FuncTemplate<Panel>(() => new VirtualizingCarouselPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new VirtualizingCarouselPanel());
 
         private IScrollable? _scroller;
 
@@ -31,7 +30,7 @@ namespace Avalonia.Controls
         static Carousel()
         {
             SelectionModeProperty.OverrideDefaultValue<Carousel>(SelectionMode.AlwaysSelected);
-            ItemsPanelProperty.OverrideDefaultValue<Carousel>(PanelTemplate);
+            ItemsPanelProperty.OverrideDefaultValue<Carousel>(DefaultPanel);
         }
 
         /// <summary>

+ 2 - 5
src/Avalonia.Controls/ComboBox.cs

@@ -1,13 +1,10 @@
 using System;
-using System.Diagnostics;
 using System.Linq;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Selection;
 using Avalonia.Controls.Shapes;
 using Avalonia.Controls.Templates;
-using Avalonia.Controls.Utils;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
@@ -29,8 +26,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
         /// </summary>
-        private static readonly FuncTemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new VirtualizingStackPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new VirtualizingStackPanel());
 
         /// <summary>
         /// Defines the <see cref="IsDropDownOpen"/> property.

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

@@ -4,7 +4,6 @@ using System.ComponentModel;
 using Avalonia.Automation.Peers;
 using System.Linq;
 using Avalonia.Controls.Diagnostics;
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives.PopupPositioning;
@@ -84,8 +83,8 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Control?> PlacementTargetProperty =
             Popup.PlacementTargetProperty.AddOwner<ContextMenu>();
 
-        private static readonly ITemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Vertical });
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new StackPanel { Orientation = Orientation.Vertical });
         private Popup? _popup;
         private List<Control>? _attachedControls;
         private IInputElement? _previousFocus;

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

@@ -2,9 +2,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
-using System.Diagnostics.CodeAnalysis;
 using Avalonia.Automation.Peers;
-using Avalonia.Collections;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Presenters;
@@ -29,8 +27,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// The default value for the <see cref="ItemsPanel"/> property.
         /// </summary>
-        private static readonly FuncTemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new StackPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new StackPanel());
 
         /// <summary>
         /// Defines the <see cref="Items"/> property.
@@ -58,8 +56,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="ItemsPanel"/> property.
         /// </summary>
-        public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
-            AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
+        public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
+            AvaloniaProperty.Register<ItemsControl, ITemplate<Panel?>>(nameof(ItemsPanel), DefaultPanel);
 
         /// <summary>
         /// Defines the <see cref="ItemsSource"/> property.
@@ -202,7 +200,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets or sets the panel used to display the items.
         /// </summary>
-        public ITemplate<Panel> ItemsPanel
+        public ITemplate<Panel?> ItemsPanel
         {
             get => GetValue(ItemsPanelProperty);
             set => SetValue(ItemsPanelProperty, value);

+ 2 - 5
src/Avalonia.Controls/ListBox.cs

@@ -1,13 +1,10 @@
 using System.Collections;
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Metadata;
-using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Selection;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -20,8 +17,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
         /// </summary>
-        private static readonly FuncTemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new VirtualizingStackPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new VirtualizingStackPanel());
 
         /// <summary>
         /// Defines the <see cref="Scroll"/> property.

+ 2 - 4
src/Avalonia.Controls/Menu.cs

@@ -1,7 +1,6 @@
 using Avalonia.Automation;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Platform;
-using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Interactivity;
@@ -14,9 +13,8 @@ namespace Avalonia.Controls
     /// </summary>
     public class Menu : MenuBase, IMainMenu
     {
-        private static readonly ITemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new StackPanel { Orientation = Orientation.Horizontal });
-
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new (() => new StackPanel { Orientation = Orientation.Horizontal });
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Menu"/> class.

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

@@ -4,7 +4,6 @@ using System.Linq;
 using Avalonia.Reactive;
 using System.Windows.Input;
 using Avalonia.Automation.Peers;
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
@@ -107,8 +106,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
         /// </summary>
-        private static readonly ITemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new StackPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new StackPanel());
 
         private bool _commandCanExecute = true;
         private bool _commandBindingError;

+ 9 - 3
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="ItemsPanel"/> property.
         /// </summary>
-        public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
+        public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
             ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
 
         private PanelContainerGenerator? _generator;
@@ -68,7 +68,7 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
         /// </summary>
-        public ITemplate<Panel> ItemsPanel
+        public ITemplate<Panel?> ItemsPanel
         {
             get => GetValue(ItemsPanelProperty);
             set => SetValue(ItemsPanelProperty, value);
@@ -166,6 +166,12 @@ namespace Avalonia.Controls.Presenters
                 }
 
                 Panel = ItemsPanel.Build();
+
+                if (Panel is null)
+                {
+                    return;
+                }
+
                 Panel.TemplatedParent = TemplatedParent;
                 Panel.IsItemsHost = true;
                 _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
@@ -183,7 +189,7 @@ namespace Avalonia.Controls.Presenters
                 else
                     CreateSimplePanelGenerator();
 
-                if(Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
+                if (Panel is IScrollSnapPointsInfo scrollSnapPointsInfo)
                 {
                     scrollSnapPointsInfo.VerticalSnapPointsChanged += (s, e) =>
                     {

+ 1 - 1
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives
             AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
 
         /// <summary>
-        /// Defines the <see cref="IsExpandedProperty"/> property.
+        /// Defines the <see cref="IsExpanded"/> property.
         /// </summary>
         public static readonly DirectProperty<ScrollBar, bool> IsExpandedProperty =
             AvaloniaProperty.RegisterDirect<ScrollBar, bool>(

+ 2 - 4
src/Avalonia.Controls/Primitives/TabStrip.cs

@@ -1,15 +1,13 @@
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Layout;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Primitives
 {
     public class TabStrip : SelectingItemsControl
     {
-        private static readonly FuncTemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new WrapPanel { Orientation = Orientation.Horizontal });
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new WrapPanel { Orientation = Orientation.Horizontal });
 
         static TabStrip()
         {

+ 11 - 8
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -286,14 +286,17 @@ namespace Avalonia.Controls.Primitives
                 {
                     Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
 
-                    var (child, nameScope) = template.Build(this);
-                    ApplyTemplatedParent(child, this);
-                    ((ISetLogicalParent)child).SetParent(this);
-                    VisualChildren.Add(child);
-
-                    var e = new TemplateAppliedEventArgs(nameScope);
-                    OnApplyTemplate(e);
-                    RaiseEvent(e);
+                    if (template.Build(this) is { } templateResult)
+                    {
+                        var (child, nameScope) = templateResult;
+                        ApplyTemplatedParent(child, this);
+                        ((ISetLogicalParent)child).SetParent(this);
+                        VisualChildren.Add(child);
+
+                        var e = new TemplateAppliedEventArgs(nameScope);
+                        OnApplyTemplate(e);
+                        RaiseEvent(e);
+                    }
                 }
 
                 _appliedTemplate = template;

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

@@ -200,7 +200,7 @@ namespace Avalonia.Controls
                 ScrollBarVisibility.Auto);
 
         /// <summary>
-        /// Defines the <see cref="IsExpandedProperty"/> property.
+        /// Defines the <see cref="IsExpanded"/> property.
         /// </summary>
         public static readonly DirectProperty<ScrollViewer, bool> IsExpandedProperty =
             ScrollBar.IsExpandedProperty.AddOwner<ScrollViewer>(o => o.IsExpanded);

+ 5 - 5
src/Avalonia.Controls/Slider.cs

@@ -80,16 +80,16 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<Slider, TickPlacement>(nameof(TickPlacement), 0d);
 
         /// <summary>
-        /// Defines the <see cref="TicksProperty"/> property.
+        /// Defines the <see cref="Ticks"/> property.
         /// </summary>
         public static readonly StyledProperty<AvaloniaList<double>?> TicksProperty =
             TickBar.TicksProperty.AddOwner<Slider>();
 
         // Slider required parts
-        private bool _isDragging;
-        private Track? _track;
-        private Button? _decreaseButton;
-        private Button? _increaseButton;
+        protected bool _isDragging;
+        protected Track? _track;
+        protected Button? _decreaseButton;
+        protected Button? _increaseButton;
         private IDisposable? _decreaseButtonPressDispose;
         private IDisposable? _decreaseButtonReleaseDispose;
         private IDisposable? _increaseButtonSubscription;

+ 2 - 5
src/Avalonia.Controls/TabControl.cs

@@ -1,8 +1,6 @@
-using System.ComponentModel;
 using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Automation.Peers;
-using Avalonia.Controls.Generators;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
@@ -12,7 +10,6 @@ using Avalonia.LogicalTree;
 using Avalonia.VisualTree;
 using Avalonia.Automation;
 using Avalonia.Controls.Metadata;
-using Avalonia.Data;
 
 namespace Avalonia.Controls
 {
@@ -61,8 +58,8 @@ namespace Avalonia.Controls
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
         /// </summary>
-        private static readonly FuncTemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new WrapPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new WrapPanel());
 
         /// <summary>
         /// Initializes static members of the <see cref="TabControl"/> class.

+ 2 - 2
src/Avalonia.Controls/Templates/FuncTemplate`1.cs

@@ -7,7 +7,7 @@ namespace Avalonia.Controls.Templates
     /// Creates a control from a <see cref="Func{TControl}"/>.
     /// </summary>
     /// <typeparam name="TControl">The type of control.</typeparam>
-    public class FuncTemplate<TControl> : ITemplate<TControl> where TControl : Control
+    public class FuncTemplate<TControl> : ITemplate<TControl> where TControl : Control?
     {
         private readonly Func<TControl> _func;
 
@@ -31,6 +31,6 @@ namespace Avalonia.Controls.Templates
             return _func();
         }
 
-        object ITemplate.Build() => Build();
+        object? ITemplate.Build() => Build();
     }
 }

+ 1 - 3
src/Avalonia.Controls/Templates/IControlTemplate.cs

@@ -1,13 +1,11 @@
-using System;
 using Avalonia.Controls.Primitives;
-using Avalonia.Styling;
 
 namespace Avalonia.Controls.Templates
 {
     /// <summary>
     /// Interface representing a template used to build a <see cref="TemplatedControl"/>.
     /// </summary>
-    public interface IControlTemplate : ITemplate<TemplatedControl, ControlTemplateResult>
+    public interface IControlTemplate : ITemplate<TemplatedControl, ControlTemplateResult?>
     {
     }
 

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

@@ -75,7 +75,7 @@ namespace Avalonia.Controls
                 unsetValue: WindowTransparencyLevel.None);        
 
         /// <summary>
-        /// Defines the <see cref="TransparencyBackgroundFallbackProperty"/> property.
+        /// Defines the <see cref="TransparencyBackgroundFallback"/> property.
         /// </summary>
         public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
             AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);

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

@@ -10,6 +10,7 @@ using Avalonia.Controls.Generators;
 using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
+using Avalonia.Layout;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
 
@@ -163,6 +164,9 @@ namespace Avalonia.Controls
         {
             item.IsExpanded = true;
 
+            if (item.Presenter?.Panel is null)
+                (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+
             if (item.Presenter?.Panel is { } panel)
             {
                 foreach (var child in panel.Children)

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

@@ -40,8 +40,8 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<TreeViewItem, int>(
                 nameof(Level), o => o.Level);
 
-        private static readonly ITemplate<Panel> DefaultPanel =
-            new FuncTemplate<Panel>(() => new StackPanel());
+        private static readonly FuncTemplate<Panel?> DefaultPanel =
+            new(() => new StackPanel());
 
         private TreeView? _treeView;
         private Control? _header;
@@ -90,8 +90,20 @@ namespace Avalonia.Controls
 
         internal TreeView? TreeViewOwner => _treeView;
 
-        protected internal override Control CreateContainerForItemOverride() => new TreeViewItem();
-        protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is TreeViewItem;
+        protected internal override Control CreateContainerForItemOverride()
+        {
+            return EnsureTreeView().CreateContainerForItemOverride();
+        }
+
+        protected internal override bool IsItemItsOwnContainerOverride(Control item)
+        {
+            return EnsureTreeView().IsItemItsOwnContainerOverride(item);
+        }
+
+        protected internal override void PrepareContainerForItemOverride(Control container, object? item, int index)
+        {
+            EnsureTreeView().PrepareContainerForItemOverride(container, item, index);
+        }
 
         /// <inheritdoc/>
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@@ -283,6 +295,9 @@ namespace Avalonia.Controls
             return logical != null ? result : @default;
         }
 
+        private TreeView EnsureTreeView() => _treeView ??
+            throw new InvalidOperationException("The TreeViewItem is not part of a TreeView.");
+
         private void HeaderDoubleTapped(object? sender, TappedEventArgs e)
         {
             OnHeaderDoubleTapped(e);

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

@@ -58,7 +58,6 @@
         <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
         <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
         <Compile Include="XamlLoadException.cs" />
-        <Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
         <Compile Include="XamlTypes.cs" />
     </ItemGroup>
   <ItemGroup>
@@ -69,6 +68,7 @@
   <Import Project="..\..\..\build\ApiDiff.props" />
   <Import Project="..\..\..\build\DevAnalyzers.props" />
   <Import Project="..\..\..\build\TrimmingEnable.props" />
+  <Import Project="..\..\..\build\NullableEnable.props" />
 
   <ItemGroup Label="InternalsVisibleTo">
     <InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />

+ 2 - 3
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -1,8 +1,7 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
-using System.IO;
 using Avalonia.Platform;
-#nullable enable
+
 namespace Avalonia.Markup.Xaml
 {
     /// <summary>
@@ -79,7 +78,7 @@ namespace Avalonia.Markup.Xaml
 
             var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
                 ?.GetType("CompiledAvaloniaXaml.!XamlLoader")
-                ?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) });
+                ?.GetMethod("TryLoad", new[] { typeof(IServiceProvider), typeof(string) });
             if (compiledLoader != null)
             {
                 var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()});

+ 8 - 6
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@@ -14,18 +14,20 @@ namespace Avalonia.Markup.Xaml.Converters
     [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
     public class AvaloniaPropertyTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        /// <inheritdoc />
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var registry = AvaloniaPropertyRegistry.Instance;
             var (ns, owner, propertyName) = PropertyParser.Parse(new CharacterReader(((string)value).AsSpan()));
             var ownerType = TryResolveOwnerByName(context, ns, owner);
-            var targetType = context.GetFirstParent<ControlTemplate>()?.TargetType ??
-                context.GetFirstParent<Style>()?.Selector?.TargetType ??
+            var targetType = context?.GetFirstParent<ControlTemplate>()?.TargetType ??
+                context?.GetFirstParent<Style>()?.Selector?.TargetType ??
                 typeof(Control);
             var effectiveOwner = ownerType ?? targetType;
             var property = registry.FindRegistered(effectiveOwner, propertyName);
@@ -51,11 +53,11 @@ namespace Avalonia.Markup.Xaml.Converters
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
-        private static Type TryResolveOwnerByName(ITypeDescriptorContext context, string ns, string owner)
+        private static Type? TryResolveOwnerByName(ITypeDescriptorContext? context, string? ns, string? owner)
         {
             if (owner != null)
             {
-                var result = context.ResolveType(ns, owner);
+                var result = context?.ResolveType(ns, owner);
 
                 if (result == null)
                 {

+ 4 - 2
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs

@@ -6,12 +6,14 @@ namespace Avalonia.Markup.Xaml.Converters
 {
     public class AvaloniaUriTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        /// <inheritdoc />
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var s = value as string;
             if (s == null)

+ 6 - 5
src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs

@@ -1,20 +1,21 @@
 using System;
+using System.ComponentModel;
 using System.Globalization;
 using Avalonia.Media.Imaging;
 using Avalonia.Platform;
 
 namespace Avalonia.Markup.Xaml.Converters
 {
-    using System.ComponentModel;
-
     public class BitmapTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        /// <inheritdoc />
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var s = (string)value;
             var uri = s.StartsWith("/")
@@ -25,7 +26,7 @@ namespace Avalonia.Markup.Xaml.Converters
                 return new Bitmap(uri.LocalPath);
 
             var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
-            return new Bitmap(assets.Open(uri, context.GetContextBaseUri()));
+            return new Bitmap(assets.Open(uri, context?.GetContextBaseUri()));
         }
     }
 }

+ 4 - 4
src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters
         /// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
         /// is <see cref="IBrush"/> then converts the color to a solid color brush.
         /// </returns>
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             return Convert(value, targetType);
         }
@@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.Converters
         /// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
         /// is <see cref="Color"/> then converts the solid color brush to a color.
         /// </returns>
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
         {
             return ConvertBack(value, targetType);
         }
@@ -55,7 +55,7 @@ namespace Avalonia.Markup.Xaml.Converters
         /// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
         /// is <see cref="IBrush"/> then converts the color to a solid color brush.
         /// </returns>
-        public static object Convert(object value, Type targetType)
+        public static object? Convert(object? value, Type? targetType)
         {
             if (targetType == typeof(IBrush) && value is Color c)
             {
@@ -75,7 +75,7 @@ namespace Avalonia.Markup.Xaml.Converters
         /// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
         /// is <see cref="Color"/> then converts the solid color brush to a color.
         /// </returns>
-        public static object ConvertBack(object value, Type targetType)
+        public static object? ConvertBack(object? value, Type? targetType)
         {
             if (targetType == typeof(Color) && value is ISolidColorBrush brush)
             {

+ 5 - 5
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@@ -1,24 +1,24 @@
 using System;
 using System.ComponentModel;
 using System.Globalization;
-
 using Avalonia.Media;
 
-
 namespace Avalonia.Markup.Xaml.Converters
 {
     public class FontFamilyTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        /// <inheritdoc />
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var s = (string)value;
 
-            return FontFamily.Parse(s, context.GetContextBaseUri());
+            return FontFamily.Parse(s, context?.GetContextBaseUri());
         }
     }
 }

+ 7 - 6
src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs

@@ -2,20 +2,21 @@
 using Avalonia.Media.Imaging;
 using Avalonia.Platform;
 using System;
+using System.ComponentModel;
 using System.Globalization;
 
 namespace Avalonia.Markup.Xaml.Converters
 {
-	using System.ComponentModel;
-
     public class IconTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        /// <inheritdoc />
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var path = value as string;
             if (path != null)
@@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.Converters
             throw new NotSupportedException();
         }
 
-        private static WindowIcon CreateIconFromPath(ITypeDescriptorContext context, string s)
+        private static WindowIcon CreateIconFromPath(ITypeDescriptorContext? context, string s)
         {
             var uri = s.StartsWith("/")
                 ? new Uri(s, UriKind.Relative)
@@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Converters
             if(uri.IsAbsoluteUri && uri.IsFile)
                 return new WindowIcon(uri.LocalPath);
             var assets = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
-            return new WindowIcon(assets.Open(uri, context.GetContextBaseUri()));
+            return new WindowIcon(assets.Open(uri, context?.GetContextBaseUri()));
         }
     }
 }

+ 4 - 5
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@@ -1,20 +1,19 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Globalization;
+using Avalonia.Utilities;
 
 namespace Avalonia.Markup.Xaml.Converters
 {
-    using System.ComponentModel;
-    using Avalonia.Utilities;
-
     public class PointsListTypeConverter : TypeConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
         {
             return sourceType == typeof(string);
         }
 
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var points = new List<Point>();
 

+ 4 - 9
src/Markup/Avalonia.Markup.Xaml/Converters/TimeSpanTypeConverter.cs

@@ -1,18 +1,13 @@
 using System;
+using System.ComponentModel;
 using System.Globalization;
 
 namespace Avalonia.Markup.Xaml.Converters
 {
-	using System.ComponentModel;
-
-    public class TimeSpanTypeConverter : System.ComponentModel.TimeSpanConverter
+    public class TimeSpanTypeConverter : TimeSpanConverter
     {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
-        {
-            return base.CanConvertFrom(context, sourceType);
-        }
-
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        /// <inheritdoc />
+        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
         {
             var valueStr = (string)value;
             if (!valueStr.Contains(':'))

+ 16 - 17
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Avalonia.Controls;
@@ -11,38 +10,38 @@ namespace Avalonia.Markup.Xaml
 {
     internal static class Extensions
     {
-        public static T GetService<T>(this IServiceProvider sp) => (T)sp?.GetService(typeof(T));
-        
-        
-        public static Uri GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>().BaseUri;
+        public static T? GetService<T>(this IServiceProvider sp) => (T?)sp.GetService(typeof(T));
+
+        public static T GetRequiredService<T>(this IServiceProvider sp)
+            => sp.GetService<T>() ?? throw new InvalidOperationException($"Service {typeof(T)} hasn't been registered");
 
-        public static T GetFirstParent<T>(this IServiceProvider ctx) where T : class 
-            => ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().FirstOrDefault();
+        public static Uri? GetContextBaseUri(this IServiceProvider ctx) => ctx.GetService<IUriContext>()?.BaseUri;
 
-        public static T GetLastParent<T>(this IServiceProvider ctx) where T : class 
-            => ctx.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>().LastOrDefault();
+        public static T? GetFirstParent<T>(this IServiceProvider ctx) where T : class
+            => ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().FirstOrDefault();
+
+        public static T? GetLastParent<T>(this IServiceProvider ctx) where T : class
+            => ctx.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>().LastOrDefault();
 
         public static IEnumerable<T> GetParents<T>(this IServiceProvider sp)
-        {
-            return sp.GetService<IAvaloniaXamlIlParentStackProvider>().Parents.OfType<T>();
-        }
+            => sp.GetService<IAvaloniaXamlIlParentStackProvider>()?.Parents.OfType<T>() ?? Enumerable.Empty<T>();
 
         public static bool IsInControlTemplate(this IServiceProvider sp) => sp.GetService<IAvaloniaXamlIlControlTemplateProvider>() != null;
 
         [RequiresUnreferencedCode(TrimmingMessages.XamlTypeResolvedRequiresUnreferenceCodeMessage)]
-        public static Type ResolveType(this IServiceProvider ctx, string namespacePrefix, string type)
+        public static Type ResolveType(this IServiceProvider ctx, string? namespacePrefix, string type)
         {
-            var tr = ctx.GetService<IXamlTypeResolver>();
+            var tr = ctx.GetRequiredService<IXamlTypeResolver>();
             string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
-            return tr?.Resolve(name);
+            return tr.Resolve(name);
         }
         
-        public static object GetDefaultAnchor(this IServiceProvider provider)
+        public static object? GetDefaultAnchor(this IServiceProvider provider)
         {
             // If the target is not a control, so we need to find an anchor that will let us look
             // up named controls and style resources. First look for the closest Control in
             // the context.
-            object anchor = provider.GetFirstParent<Control>();
+            object? anchor = provider.GetFirstParent<Control>();
 
             if (anchor is null)
             {

+ 8 - 4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
             };
         }
 
-        private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
+        private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, bool enableDataValidation)
         {
             if (Source != null)
             {
@@ -61,8 +61,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
             }
             else
             {
+                var styledElement = target as StyledElement
+                    ?? anchor as StyledElement
+                    ?? throw new ArgumentException($"Cannot find a valid {nameof(StyledElement)} to use as the binding source.");
+
                 return CreateSourceObserver(
-                    (target as StyledElement) ?? (anchor as StyledElement),
+                    styledElement,
                     Path.BuildExpression(enableDataValidation));
             }
         }
@@ -70,8 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         [ConstructorArgument("path")]
         public CompiledBindingPath Path { get; set; }
 
-        public object Source { get; set; }
+        public object? Source { get; set; }
 
-        public Type DataType { get; set; }
+        public Type? DataType { get; set; }
     }
 }

+ 4 - 6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs

@@ -1,13 +1,11 @@
 using System;
-using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
-using System.Text;
 using Avalonia.Data;
 using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
-    class ArrayElementPlugin : IPropertyAccessorPlugin
+    internal class ArrayElementPlugin : IPropertyAccessorPlugin
     {
         private readonly int[] _indices;
         private readonly Type _elementType;
@@ -25,7 +23,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
-        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
+        public IPropertyAccessor? Start(WeakReference<object?> reference, string propertyName)
         {
             if (reference.TryGetTarget(out var target) && target is Array arr)
             {
@@ -48,9 +46,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
             public override Type PropertyType { get; }
 
-            public override object Value => _reference.TryGetTarget(out var arr) ? arr.GetValue(_indices) : null;
+            public override object? Value => _reference.TryGetTarget(out var arr) ? arr.GetValue(_indices) : null;
 
-            public override bool SetValue(object value, BindingPriority priority)
+            public override bool SetValue(object? value, BindingPriority priority)
             {
                 if (_reference.TryGetTarget(out var arr))
                 {

+ 17 - 19
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs

@@ -2,8 +2,6 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using System.Text;
 using System.Windows.Input;
 using Avalonia.Data;
 using Avalonia.Data.Core.Plugins;
@@ -13,11 +11,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
     internal class CommandAccessorPlugin : IPropertyAccessorPlugin
     {
-        private readonly Action<object, object> _execute;
-        private readonly Func<object, object, bool> _canExecute;
+        private readonly Action<object, object?> _execute;
+        private readonly Func<object, object?, bool>? _canExecute;
         private readonly ISet<string> _dependsOnProperties;
 
-        public CommandAccessorPlugin(Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
+        public CommandAccessorPlugin(Action<object, object?> execute, Func<object, object?, bool>? canExecute, ISet<string> dependsOnProperties)
         {
             _execute = execute;
             _canExecute = canExecute;
@@ -31,18 +29,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
-        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
+        public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
         {
             return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties);
         }
 
         private sealed class CommandAccessor : PropertyAccessorBase
         {
-            private readonly WeakReference<object> _reference;
-            private Command _command;
+            private readonly WeakReference<object?> _reference;
+            private readonly Command _command;
             private readonly ISet<string> _dependsOnProperties;
 
-            public CommandAccessor(WeakReference<object> reference, Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
+            public CommandAccessor(WeakReference<object?> reference, Action<object, object?> execute, Func<object, object?, bool>? canExecute, ISet<string> dependsOnProperties)
             {
                 _reference = reference ?? throw new ArgumentNullException(nameof(reference));
                 _dependsOnProperties = dependsOnProperties;
@@ -50,7 +48,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
             }
 
-            public override object Value => _reference.TryGetTarget(out var _) ? _command : null;
+            public override object? Value => _reference.TryGetTarget(out _) ? _command : null;
 
             private void RaiseCanExecuteChanged()
             {
@@ -59,13 +57,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
             private sealed class Command : ICommand
             {
-                private readonly WeakReference<object> _target;
-                private readonly Action<object, object> _execute;
-                private readonly Func<object, object, bool> _canExecute;
+                private readonly WeakReference<object?> _target;
+                private readonly Action<object, object?> _execute;
+                private readonly Func<object, object?, bool>? _canExecute;
 
-                public event EventHandler CanExecuteChanged;
+                public event EventHandler? CanExecuteChanged;
 
-                public Command(WeakReference<object> target, Action<object, object> execute, Func<object, object, bool> canExecute)
+                public Command(WeakReference<object?> target, Action<object, object?> execute, Func<object, object?, bool>? canExecute)
                 {
                     _target = target;
                     _execute = execute;
@@ -78,7 +76,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                        , Threading.DispatcherPriority.Input);
                 }
 
-                public bool CanExecute(object parameter)
+                public bool CanExecute(object? parameter)
                 {
                     if (_target.TryGetTarget(out var target))
                     {
@@ -91,7 +89,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                     return false;
                 }
 
-                public void Execute(object parameter)
+                public void Execute(object? parameter)
                 {
                     if (_target.TryGetTarget(out var target))
                     {
@@ -102,12 +100,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
             public override Type PropertyType => typeof(ICommand);
 
-            public override bool SetValue(object value, BindingPriority priority)
+            public override bool SetValue(object? value, BindingPriority priority)
             {
                 return false;
             }
 
-            void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
+            private void OnNotifyPropertyChanged(object? sender, PropertyChangedEventArgs e)
             {
                 if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName))
                 {

+ 28 - 26
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         public CompiledBindingPath()
             => _elements = Array.Empty<ICompiledBindingPathElement>();
 
-        internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object rawSource)
+        internal CompiledBindingPath(ICompiledBindingPathElement[] elements, object? rawSource)
         {
             _elements = elements;
             RawSource = rawSource;
@@ -26,14 +26,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.CompiledBindingSafeSupressWarningMessage)]
         internal ExpressionNode BuildExpression(bool enableValidation)
         {
-            ExpressionNode pathRoot = null;
-            ExpressionNode path = null;
+            ExpressionNode? pathRoot = null;
+            ExpressionNode? path = null;
             foreach (var element in _elements)
             {
-                ExpressionNode node = null;
+                ExpressionNode? node;
                 switch (element)
                 {
-                    case NotExpressionPathElement _:
+                    case NotExpressionPathElement:
                         node = new LogicalNotNode();
                         break;
                     case PropertyElement prop:
@@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                     case AncestorPathElement ancestor:
                         node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
                         break;
-                    case SelfPathElement _:
+                    case SelfPathElement:
                         node = new SelfNode();
                         break;
                     case ElementNameElement name:
@@ -70,7 +70,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                         throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
                 }
 
-                path = pathRoot is null ? (pathRoot = node) : path.Next = node;
+                path = pathRoot is null ? (pathRoot = node) : path!.Next = node;
             }
 
             return pathRoot ?? new EmptyExpressionNode();
@@ -81,16 +81,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
             ? SourceMode.Control : SourceMode.Data;
 
-        internal object RawSource { get; }
+        internal object? RawSource { get; }
 
+        /// <inheritdoc />
         public override string ToString()
             => string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements);
     }
 
     public class CompiledBindingPathBuilder
     {
-        private object _rawSource;
-        private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
+        private object? _rawSource;
+        private readonly List<ICompiledBindingPathElement> _elements = new();
 
         public CompiledBindingPathBuilder Not()
         {
@@ -98,7 +99,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return this;
         }
 
-        public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
+        public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory)
         {
             _elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
             return this;
@@ -110,7 +111,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return this;
         }
 
-        public CompiledBindingPathBuilder Command(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnProperties)
+        public CompiledBindingPathBuilder Command(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[]? dependsOnProperties)
         {
             _elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
             return this;
@@ -163,7 +164,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return this;
         }
 
-        public CompiledBindingPathBuilder SetRawSource(object rawSource)
+        public CompiledBindingPathBuilder SetRawSource(object? rawSource)
         {
             _rawSource = rawSource;
             return this;
@@ -187,7 +188,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
     {
         private readonly bool _isFirstElement;
 
-        public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
+        public PropertyElement(IPropertyInfo property, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
         {
             Property = property;
             AccessorFactory = accessorFactory;
@@ -196,7 +197,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
         public IPropertyInfo Property { get; }
 
-        public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
+        public Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
 
         public override string ToString()
             => _isFirstElement ? Property.Name : $".{Property.Name}";
@@ -206,7 +207,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
     {
         public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType)
         {
-            Method = (MethodInfo)MethodBase.GetMethodFromHandle(method);
+            Method = MethodBase.GetMethodFromHandle(method) as MethodInfo
+                ?? throw new ArgumentException("Invalid method handle", nameof(method));
             DelegateType = Type.GetTypeFromHandle(delegateType);
         }
 
@@ -217,7 +219,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class MethodAsCommandElement : ICompiledBindingPathElement
     {
-        public MethodAsCommandElement(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnElements)
+        public MethodAsCommandElement(string methodName, Action<object, object?> executeHelper, Func<object, object?, bool>? canExecuteHelper, string[] dependsOnElements)
         {
             MethodName = methodName;
             ExecuteMethod = executeHelper;
@@ -226,8 +228,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
 
         public string MethodName { get; }
-        public Action<object, object> ExecuteMethod { get; }
-        public Func<object, object, bool> CanExecuteMethod { get; }
+        public Action<object, object?> ExecuteMethod { get; }
+        public Func<object, object?, bool>? CanExecuteMethod { get; }
         public HashSet<string> DependsOnProperties { get; }
     }
 
@@ -240,7 +242,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
     {
         Type Type { get; }
 
-        Func<object, object> Cast { get; }
+        Func<object?, object?> Cast { get; }
     }
 
     internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
@@ -267,13 +269,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
     {
-        public AncestorPathElement(Type ancestorType, int level)
+        public AncestorPathElement(Type? ancestorType, int level)
         {
             AncestorType = ancestorType;
             Level = level;
         }
 
-        public Type AncestorType { get; }
+        public Type? AncestorType { get; }
         public int Level { get; }
 
         public override string ToString()
@@ -282,13 +284,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
     {
-        public VisualAncestorPathElement(Type ancestorType, int level)
+        public VisualAncestorPathElement(Type? ancestorType, int level)
         {
             AncestorType = ancestorType;
             Level = level;
         }
 
-        public Type AncestorType { get; }
+        public Type? AncestorType { get; }
         public int Level { get; }
     }
 
@@ -323,7 +325,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class TypeCastPathElement<T> : ITypeCastElement
     {
-        private static object TryCast(object obj)
+        private static object? TryCast(object? obj)
         {
             if (obj is T result)
                 return result;
@@ -332,7 +334,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
         public Type Type => typeof(T);
 
-        public Func<object, object> Cast => TryCast;
+        public Func<object?, object?> Cast => TryCast;
 
         public override string ToString()
             => $"({Type.FullName})";

+ 6 - 6
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs

@@ -5,13 +5,13 @@ using Avalonia.Reactive;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
-    class FindVisualAncestorNode : ExpressionNode
+    internal class FindVisualAncestorNode : ExpressionNode
     {
         private readonly int _level;
-        private readonly Type _ancestorType;
-        private IDisposable _subscription;
+        private readonly Type? _ancestorType;
+        private IDisposable? _subscription;
 
-        public FindVisualAncestorNode(Type ancestorType, int level)
+        public FindVisualAncestorNode(Type? ancestorType, int level)
         {
             _level = level;
             _ancestorType = ancestorType;
@@ -32,9 +32,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             }
         }
 
-        protected override void StartListeningCore(WeakReference<object> reference)
+        protected override void StartListeningCore(WeakReference<object?> reference)
         {
-            if (reference.TryGetTarget(out object target) && target is Visual visual)
+            if (reference.TryGetTarget(out object? target) && target is Visual visual)
             {
                 _subscription = VisualLocator.Track(visual, _level, _ancestorType).Subscribe(ValueChanged);
             }

+ 1 - 5
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs

@@ -1,19 +1,15 @@
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
-using System.Text;
 using Avalonia.Data;
 using Avalonia.Data.Core.Plugins;
 
-#nullable enable
-
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
     internal class MethodAccessorPlugin : IPropertyAccessorPlugin
     {
-        private MethodInfo _method;
+        private readonly MethodInfo _method;
         private readonly Type _delegateType;
 
         public MethodAccessorPlugin(MethodInfo method, Type delegateType)

+ 6 - 8
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs

@@ -1,33 +1,31 @@
 using System;
-using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
-using System.Text;
 using Avalonia.Data.Core.Plugins;
 using Avalonia.Reactive;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
-    class ObservableStreamPlugin<T> : IStreamPlugin
+    internal class ObservableStreamPlugin<T> : IStreamPlugin
     {
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
-        public bool Match(WeakReference<object> reference)
+        public bool Match(WeakReference<object?> reference)
         {
             return reference.TryGetTarget(out var target) && target is IObservable<T>;
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
-        public IObservable<object> Start(WeakReference<object> reference)
+        public IObservable<object?> Start(WeakReference<object?> reference)
         {
             if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs))
             {
-                return Observable.Empty<object>();
+                return Observable.Empty<object?>();
             }
-            else if (target is IObservable<object> obj)
+            else if (target is IObservable<object?> obj)
             {
                 return obj;
             }
 
-            return obs.Select(x => (object)x);
+            return obs.Select(x => (object?)x);
         }
     }
 }

+ 26 - 26
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@@ -11,29 +11,29 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
     public static class PropertyInfoAccessorFactory
     {
-        public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
+        public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
             => new InpcPropertyAccessor(target, property);
 
-        public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
-            => new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject>((AvaloniaObject)(target.TryGetTarget(out var o) ? o : null)), (AvaloniaProperty)property);
+        public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object?> target, IPropertyInfo property)
+            => new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject?>((AvaloniaObject?)(target.TryGetTarget(out var o) ? o : null)), (AvaloniaProperty)property);
 
-        public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object> target, IPropertyInfo property, int argument)
+        public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object?> target, IPropertyInfo property, int argument)
             => new IndexerAccessor(target, property, argument);
     }
 
     internal class AvaloniaPropertyAccessor : PropertyAccessorBase
     {
-        private readonly WeakReference<AvaloniaObject> _reference;
+        private readonly WeakReference<AvaloniaObject?> _reference;
         private readonly AvaloniaProperty _property;
-        private IDisposable _subscription;
+        private IDisposable? _subscription;
 
-        public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject> reference, AvaloniaProperty property)
+        public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject?> reference, AvaloniaProperty property)
         {
-            _reference = reference ?? throw new ArgumentNullException(nameof(reference));;
-            _property = property ?? throw new ArgumentNullException(nameof(property));;
+            _reference = reference ?? throw new ArgumentNullException(nameof(reference));
+            _property = property ?? throw new ArgumentNullException(nameof(property));
         }
 
-        public AvaloniaObject Instance
+        public AvaloniaObject? Instance
         {
             get
             {
@@ -43,13 +43,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
 
         public override Type PropertyType => _property.PropertyType;
-        public override object Value => Instance?.GetValue(_property);
+        public override object? Value => Instance?.GetValue(_property);
 
-        public override bool SetValue(object value, BindingPriority priority)
+        public override bool SetValue(object? value, BindingPriority priority)
         {
-            if (!_property.IsReadOnly)
+            if (!_property.IsReadOnly && Instance is { } instance)
             {
-                Instance.SetValue(_property, value, priority);
+                instance.SetValue(_property, value, priority);
                 return true;
             }
 
@@ -70,10 +70,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
     {
-        protected readonly WeakReference<object> _reference;
+        protected readonly WeakReference<object?> _reference;
         private readonly IPropertyInfo _property;
 
-        public InpcPropertyAccessor(WeakReference<object> reference, IPropertyInfo property)
+        public InpcPropertyAccessor(WeakReference<object?> reference, IPropertyInfo property)
         {
             _reference = reference ?? throw new ArgumentNullException(nameof(reference));
             _property = property ?? throw new ArgumentNullException(nameof(property));
@@ -81,7 +81,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
         public override Type PropertyType => _property.PropertyType;
 
-        public override object Value
+        public override object? Value
         {
             get
             {
@@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             }
         }
 
-        public override bool SetValue(object value, BindingPriority priority)
+        public override bool SetValue(object? value, BindingPriority priority)
         {
             if (_property.CanSet && _reference.TryGetTarget(out var o))
             {
@@ -103,7 +103,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return false;
         }
 
-        public void OnEvent(object sender, WeakEvent ev, PropertyChangedEventArgs e)
+        public void OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e)
         {
             if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
             {
@@ -144,9 +144,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 
     internal class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber<NotifyCollectionChangedEventArgs>
     {
-        private int _index;
+        private readonly int _index;
 
-        public IndexerAccessor(WeakReference<object> target, IPropertyInfo basePropertyInfo, int argument)
+        public IndexerAccessor(WeakReference<object?> target, IPropertyInfo basePropertyInfo, int argument)
             :base(target, basePropertyInfo)
         {
             _index = argument;
@@ -167,7 +167,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                 WeakEvents.CollectionChanged.Unsubscribe(incc, this);
         }
         
-        public void OnEvent(object sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
+        public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
         {
             if (ShouldNotifyListeners(args))
             {
@@ -175,7 +175,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             }
         }
 
-        bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
+        private bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
         {
             switch (e.Action)
             {
@@ -185,12 +185,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                     return _index >= e.OldStartingIndex;
                 case NotifyCollectionChangedAction.Replace:
                     return _index >= e.NewStartingIndex &&
-                           _index < e.NewStartingIndex + e.NewItems.Count;
+                           _index < e.NewStartingIndex + e.NewItems!.Count;
                 case NotifyCollectionChangedAction.Move:
                     return (_index >= e.NewStartingIndex &&
-                            _index < e.NewStartingIndex + e.NewItems.Count) ||
+                            _index < e.NewStartingIndex + e.NewItems!.Count) ||
                            (_index >= e.OldStartingIndex &&
-                            _index < e.OldStartingIndex + e.OldItems.Count);
+                            _index < e.OldStartingIndex + e.OldItems!.Count);
                 case NotifyCollectionChangedAction.Reset:
                     return true;
             }

+ 4 - 7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs

@@ -1,20 +1,17 @@
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
-using System.Text;
-using Avalonia.Data;
 using Avalonia.Data.Core;
 using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
-    class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
+    internal class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
     {
         private readonly IPropertyInfo _propertyInfo;
-        private readonly Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> _accessorFactory;
+        private readonly Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> _accessorFactory;
 
-        public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
+        public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference<object?>, IPropertyInfo, IPropertyAccessor> accessorFactory)
         {
             _propertyInfo = propertyInfo;
             _accessorFactory = accessorFactory;
@@ -27,7 +24,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
-        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
+        public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
         {
             Debug.Assert(_propertyInfo.Name == propertyName);
             return _accessorFactory(reference, _propertyInfo);

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs

@@ -5,14 +5,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
     internal class StrongTypeCastNode : TypeCastNode
     {
-        private Func<object, object> _cast;
+        private readonly Func<object?, object?> _cast;
 
-        public StrongTypeCastNode(Type type, Func<object, object> cast) : base(type)
+        public StrongTypeCastNode(Type type, Func<object?, object?> cast) : base(type)
         {
             _cast = cast;
         }
 
-        protected override object Cast(object value)
+        protected override object? Cast(object? value)
             => _cast(value);
     }
 }

+ 9 - 9
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs

@@ -7,20 +7,20 @@ using Avalonia.Reactive;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
 {
-    class TaskStreamPlugin<T> : IStreamPlugin
+    internal class TaskStreamPlugin<T> : IStreamPlugin
     {
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
-        public bool Match(WeakReference<object> reference)
+        public bool Match(WeakReference<object?> reference)
         {
             return reference.TryGetTarget(out var target) && target is Task<T>;
         }
 
         [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
-        public IObservable<object> Start(WeakReference<object> reference)
+        public IObservable<object?> Start(WeakReference<object?> reference)
         {
             if(!(reference.TryGetTarget(out var target) && target is Task<T> task))
             {
-                return Observable.Empty<object>();
+                return Observable.Empty<object?>();
             }
 
             switch (task.Status)
@@ -29,9 +29,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
                 case TaskStatus.Faulted:
                     return HandleCompleted(task);
                 default:
-                    var subject = new LightweightSubject<object>();
+                    var subject = new LightweightSubject<object?>();
                     task.ContinueWith(
-                            x => HandleCompleted(task).Subscribe(subject),
+                            _ => HandleCompleted(task).Subscribe(subject),
                             TaskScheduler.FromCurrentSynchronizationContext())
                         .ConfigureAwait(false);
                     return subject;
@@ -39,14 +39,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
         
         
-        private static IObservable<object> HandleCompleted(Task<T> task)
+        private static IObservable<object?> HandleCompleted(Task<T> task)
         {
             switch (task.Status)
             {
                 case TaskStatus.RanToCompletion:
-                    return Observable.Return((object)task.Result);
+                    return Observable.Return((object?)task.Result);
                 case TaskStatus.Faulted:
-                    return Observable.Return(new BindingNotification(task.Exception, BindingErrorType.Error));
+                    return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
                 default:
                     throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
             }

+ 1 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@@ -4,8 +4,6 @@ using Avalonia.Data;
 using Avalonia.Markup.Xaml.Converters;
 using Avalonia.Media;
 
-#nullable enable
-
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
     public class DynamicResourceExtension : IBinding
@@ -31,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
             var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
 
-            if (!(provideTarget.TargetObject is StyledElement))
+            if (provideTarget?.TargetObject is not StyledElement)
             {
                 _anchor = serviceProvider.GetFirstParent<StyledElement>() ??
                     serviceProvider.GetFirstParent<IResourceProvider>() ??

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs

@@ -1,5 +1,4 @@
-#nullable enable
-using System.Collections.Generic;
+using System.Collections.Generic;
 using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions;

+ 2 - 4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs

@@ -1,6 +1,4 @@
-#nullable enable
 using System;
-
 using Avalonia.Metadata;
 using Avalonia.Platform;
 
@@ -20,7 +18,7 @@ public sealed class OnFormFactorExtension : OnFormFactorExtensionBase<object, On
 
     public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
     {
-        return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
+        return serviceProvider.GetService<IRuntimePlatform>()?.GetRuntimeInfo().FormFactor == option;
     }
 }
 
@@ -38,7 +36,7 @@ public sealed class OnFormFactorExtension<TReturn> : OnFormFactorExtensionBase<T
 
     public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
     {
-        return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
+        return serviceProvider.GetService<IRuntimePlatform>()?.GetRuntimeInfo().FormFactor == option;
     }
 }
 

+ 1 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs

@@ -1,6 +1,4 @@
-#nullable enable
-using System;
-using Avalonia.Compatibility;
+using Avalonia.Compatibility;
 using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions;

+ 12 - 15
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@@ -2,7 +2,6 @@ using Avalonia.Data;
 using System;
 using Avalonia.Controls;
 using Avalonia.Data.Converters;
-using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions
@@ -21,11 +20,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public Binding ProvideValue(IServiceProvider serviceProvider)
         {
-            var descriptorContext = (ITypeDescriptorContext)serviceProvider;
-
             return new Binding
             {
-                TypeResolver = descriptorContext.ResolveType,
+                TypeResolver = serviceProvider.ResolveType,
                 Converter = Converter,
                 ConverterParameter = ConverterParameter,
                 ElementName = ElementName,
@@ -36,33 +33,33 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 Source = Source,
                 StringFormat = StringFormat,
                 RelativeSource = RelativeSource,
-                DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()),
+                DefaultAnchor = new WeakReference(serviceProvider.GetDefaultAnchor()),
                 TargetNullValue = TargetNullValue,
-                NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
+                NameScope = new WeakReference<INameScope?>(serviceProvider.GetService<INameScope>())
             };
         }
 
-        public IValueConverter Converter { get; set; }
+        public IValueConverter? Converter { get; set; }
 
-        public object ConverterParameter { get; set; }
+        public object? ConverterParameter { get; set; }
 
-        public string ElementName { get; set; }
+        public string? ElementName { get; set; }
 
-        public object FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
+        public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
 
         public BindingMode Mode { get; set; }
 
         [ConstructorArgument("path")]
-        public string Path { get; set; }
+        public string Path { get; set; } = "";
 
         public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
 
-        public object Source { get; set; }
+        public object? Source { get; set; }
 
-        public string StringFormat { get; set; }
+        public string? StringFormat { get; set; }
 
-        public RelativeSource RelativeSource { get; set; }
+        public RelativeSource? RelativeSource { get; set; }
 
-        public object TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
+        public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@@ -28,7 +28,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         [ConstructorArgument("mode")]
         public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
 
-        public Type AncestorType { get; set; }
+        public Type? AncestorType { get; set; }
 
         public TreeType Tree { get; set; }
 

+ 7 - 4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResolveByNameExtension.cs

@@ -2,8 +2,6 @@
 using Avalonia.Controls;
 using Avalonia.Data.Core;
 
-#nullable  enable
-
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
     public class ResolveByNameExtension
@@ -18,6 +16,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         public object? ProvideValue(IServiceProvider serviceProvider)
         {
             var nameScope = serviceProvider.GetService<INameScope>();
+
+            if (nameScope is null)
+                return null;
             
             var value = nameScope.FindAsync(Name);
 
@@ -25,10 +26,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 return value.GetResult();
 
             var provideValueTarget = serviceProvider.GetService<IProvideValueTarget>();
-            var target = provideValueTarget.TargetObject;
 
-            if (provideValueTarget.TargetProperty is IPropertyInfo property) 
+            if (provideValueTarget?.TargetProperty is IPropertyInfo property)
+            {
+                var target = provideValueTarget.TargetObject;
                 value.OnCompleted(() => property.Set(target, value.GetResult()));
+            }
 
             return AvaloniaProperty.UnsetValue;
         }

+ 18 - 16
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Reflection;
 using Avalonia.Controls;
 using Avalonia.Markup.Data;
@@ -8,8 +7,6 @@ using Avalonia.Markup.Xaml.Converters;
 using Avalonia.Markup.Xaml.XamlIl.Runtime;
 using Avalonia.Styling;
 
-#nullable enable
-
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
     public class StaticResourceExtension
@@ -25,7 +22,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public object? ResourceKey { get; set; }
 
-        public object ProvideValue(IServiceProvider serviceProvider)
+        public object? ProvideValue(IServiceProvider serviceProvider)
         {
             if (ResourceKey is not { } resourceKey)
             {
@@ -34,32 +31,37 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
             var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
             var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
-            var themeVariant = (provideTarget.TargetObject as IThemeVariantHost)?.ActualThemeVariant;
+            var targetObject = provideTarget?.TargetObject;
+            var targetProperty = provideTarget?.TargetProperty;
+            var themeVariant = (targetObject as IThemeVariantHost)?.ActualThemeVariant;
 
-            var targetType = provideTarget.TargetProperty switch
+            var targetType = targetProperty switch
             {
                 AvaloniaProperty ap => ap.PropertyType,
                 PropertyInfo pi => pi.PropertyType,
-                _ => null,
+                _ => null
             };
 
-            if (provideTarget.TargetObject is Setter { Property: not null } setter)
+            if (targetObject is Setter { Property: { } setterProperty })
             {
-                targetType = setter.Property?.PropertyType;
+                targetType = setterProperty.PropertyType;
             }
-            
+
             // Look upwards though the ambient context for IResourceNodes
             // which might be able to give us the resource.
-            foreach (var parent in stack.Parents)
+            if (stack is not null)
             {
-                if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
+                foreach (var parent in stack.Parents)
                 {
-                    return ColorToBrushConverter.Convert(value, targetType);
+                    if (parent is IResourceNode node && node.TryGetResource(resourceKey, themeVariant, out var value))
+                    {
+                        return ColorToBrushConverter.Convert(value, targetType);
+                    }
                 }
             }
 
-            if (provideTarget.TargetObject is Control target &&
-                provideTarget.TargetProperty is PropertyInfo property)
+            if (targetObject is Control target &&
+                targetProperty is PropertyInfo property)
             {
                 // This is stored locally to avoid allocating closure in the outer scope.
                 var localTargetType = targetType;
@@ -72,7 +74,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
             throw new KeyNotFoundException($"Static resource '{resourceKey}' not found.");
         }
 
-        private object GetValue(StyledElement control, Type? targetType)
+        private object? GetValue(StyledElement control, Type? targetType)
         {
             return ColorToBrushConverter.Convert(control.FindResource(ResourceKey!), targetType);
         }

+ 4 - 4
src/Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs

@@ -5,7 +5,7 @@ namespace Avalonia.Markup.Xaml.Parsers
 {
     internal class PropertyParser
     {
-        public static (string ns, string owner, string name) Parse(CharacterReader r)
+        public static (string? ns, string? owner, string name) Parse(CharacterReader r)
         {
             if (r.End)
             {
@@ -14,9 +14,9 @@ namespace Avalonia.Markup.Xaml.Parsers
 
             var openParens = r.TakeIf('(');
             bool closeParens = false;
-            string ns = null;
-            string owner = null;
-            string name = null;
+            string? ns = null;
+            string? owner = null;
+            string? name = null;
 
             do
             {

+ 0 - 2
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@@ -2,8 +2,6 @@ using System.Reflection;
 
 namespace Avalonia.Markup.Xaml;
 
-#nullable enable
-
 public class RuntimeXamlLoaderConfiguration
 {
     /// <summary>

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs

@@ -1,5 +1,4 @@
-#nullable enable
-using System;
+using System;
 using System.IO;
 using System.Text;
 

+ 0 - 3
src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs

@@ -1,8 +1,5 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
-using Avalonia.Controls;
-
-#nullable enable
 
 namespace Avalonia.Markup.Xaml.Styling;
 

+ 0 - 2
src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@@ -3,8 +3,6 @@ using System.Diagnostics.CodeAnalysis;
 using Avalonia.Controls;
 using Avalonia.Styling;
 
-#nullable enable
-
 namespace Avalonia.Markup.Xaml.Styling
 {
     /// <summary>

+ 2 - 4
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@@ -4,8 +4,6 @@ using Avalonia.Controls;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 
-#nullable enable
-
 namespace Avalonia.Markup.Xaml.Styling
 {
     /// <summary>
@@ -65,11 +63,11 @@ namespace Avalonia.Markup.Xaml.Styling
                     _isLoading = false;
                 }
 
-                return _loaded?[0]!;
+                return _loaded[0];
             }
         }
 
-        bool IResourceNode.HasResources => Loaded?.HasResources ?? false;
+        bool IResourceNode.HasResources => Loaded.HasResources;
 
         IReadOnlyList<IStyle> IStyle.Children => _loaded ?? Array.Empty<IStyle>();
 

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs

@@ -9,10 +9,10 @@ namespace Avalonia.Markup.Xaml.Templates
     {
         [Content]
         [TemplateContent]
-        public object Content { get; set; }
+        public object? Content { get; set; }
 
-        public Type TargetType { get; set; }
+        public Type? TargetType { get; set; }
 
-        public ControlTemplateResult Build(TemplatedControl control) => TemplateContent.Load(Content);
+        public ControlTemplateResult? Build(TemplatedControl control) => TemplateContent.Load(Content);
     }
 }

+ 5 - 5
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@@ -8,13 +8,13 @@ namespace Avalonia.Markup.Xaml.Templates
     public class DataTemplate : IRecyclingDataTemplate, ITypedDataTemplate
     {
         [DataType]
-        public Type DataType { get; set; }
+        public Type? DataType { get; set; }
 
         [Content]
         [TemplateContent]
-        public object Content { get; set; }
+        public object? Content { get; set; }
 
-        public bool Match(object data)
+        public bool Match(object? data)
         {
             if (DataType == null)
             {
@@ -26,9 +26,9 @@ namespace Avalonia.Markup.Xaml.Templates
             }
         }
 
-        public Control Build(object data) => Build(data, null);
+        public Control? Build(object? data) => Build(data, null);
 
-        public Control Build(object data, Control existing)
+        public Control? Build(object? data, Control? existing)
         {
             return existing ?? TemplateContent.Load(Content)?.Control;
         }

+ 4 - 4
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@@ -4,14 +4,14 @@ using Avalonia.Styling;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class ItemsPanelTemplate : ITemplate<Panel>
+    public class ItemsPanelTemplate : ITemplate<Panel?>
     {
         [Content]
         [TemplateContent]
-        public object Content { get; set; }
+        public object? Content { get; set; }
 
-        public Panel Build() => (Panel)TemplateContent.Load(Content)?.Control;
+        public Panel? Build() => (Panel?)TemplateContent.Load(Content)?.Control;
 
-        object ITemplate.Build() => Build();
+        object? ITemplate.Build() => Build();
     }
 }

+ 4 - 4
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@@ -4,14 +4,14 @@ using Avalonia.Styling;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class Template : ITemplate<Control>
+    public class Template : ITemplate<Control?>
     {
         [Content]
         [TemplateContent]
-        public object Content { get; set; }
+        public object? Content { get; set; }
 
-        public Control Build() => TemplateContent.Load(Content)?.Control;
+        public Control? Build() => TemplateContent.Load(Content)?.Control;
 
-        object ITemplate.Build() => Build();
+        object? ITemplate.Build() => Build();
     }
 }

+ 8 - 9
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@@ -3,14 +3,13 @@ using Avalonia.Controls.Templates;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    
     public static class TemplateContent
     {
-        public static ControlTemplateResult Load(object templateContent)
+        public static ControlTemplateResult? Load(object? templateContent)
         {
-            if (templateContent is Func<IServiceProvider, object> direct)
+            if (templateContent is Func<IServiceProvider?, object?> direct)
             {
-                return (ControlTemplateResult)direct(null);
+                return (ControlTemplateResult?)direct(null);
             }
 
             if (templateContent is null)
@@ -18,18 +17,18 @@ namespace Avalonia.Markup.Xaml.Templates
                 return null;
             }
 
-            throw new ArgumentException(nameof(templateContent));
+            throw new ArgumentException($"Unexpected content {templateContent.GetType()}", nameof(templateContent));
         }
 
-        public static TemplateResult<T> Load<T>(object templateContent)
+        public static TemplateResult<T>? Load<T>(object? templateContent)
         {
-            if (templateContent is Func<IServiceProvider, object> direct)
-                return (TemplateResult<T>)direct(null);
+            if (templateContent is Func<IServiceProvider?, object?> direct)
+                return (TemplateResult<T>?)direct(null);
 
             if (templateContent is null)
                 return null;
 
-            throw new ArgumentException(nameof(templateContent));
+            throw new ArgumentException($"Unexpected content {templateContent.GetType()}", nameof(templateContent));
         }
     }
 }

+ 6 - 6
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@@ -13,16 +13,16 @@ namespace Avalonia.Markup.Xaml.Templates
     public class TreeDataTemplate : ITreeDataTemplate, ITypedDataTemplate
     {
         [DataType]
-        public Type DataType { get; set; }
+        public Type? DataType { get; set; }
 
         [Content]
         [TemplateContent]
-        public object Content { get; set; }
+        public object? Content { get; set; }
 
         [AssignBinding]
-        public BindingBase ItemsSource { get; set; }
+        public BindingBase? ItemsSource { get; set; }
 
-        public bool Match(object data)
+        public bool Match(object? data)
         {
             if (DataType == null)
             {
@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.Templates
         }
 
         [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "If ItemsSource is a CompiledBinding, then path members will be preserver")]
-        public InstancedBinding ItemsSelector(object item)
+        public InstancedBinding? ItemsSelector(object item)
         {
             if (ItemsSource != null)
             {
@@ -52,7 +52,7 @@ namespace Avalonia.Markup.Xaml.Templates
             return null;
         }
 
-        public Control Build(object data)
+        public Control? Build(object? data)
         {
             var visualTreeForItem = TemplateContent.Load(Content)?.Control;
             if (visualTreeForItem != null)

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
     
     public class AvaloniaXamlIlXmlNamespaceInfo
     {
-        public string ClrNamespace { get; set; }
-        public string ClrAssemblyName { get; set; }
+        public string ClrNamespace { get; set; } = string.Empty;
+        public string ClrAssemblyName { get; set; } = string.Empty;
     }
 }

+ 18 - 22
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@@ -6,10 +6,7 @@ using System.Reflection;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
-using Avalonia.Markup.Xaml.MarkupExtensions;
-using Avalonia.Markup.Xaml.Styling;
 using Avalonia.Platform;
-using Avalonia.Styling;
 
 // ReSharper disable UnusedMember.Global
 // ReSharper disable UnusedParameter.Global
@@ -27,9 +24,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
         public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
             IServiceProvider provider)
         {
-            var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
+            var resourceNodes = provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>().Parents
                 .OfType<IResourceNode>().ToList();
-            var rootObject = provider.GetService<IRootObjectProvider>().RootObject;
+            var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
             var parentScope = provider.GetService<INameScope>();
             return sp =>
             {
@@ -44,17 +41,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
             };
         }
 
-        class DeferredParentServiceProvider :
+        private class DeferredParentServiceProvider :
             IAvaloniaXamlIlParentStackProvider,
             IServiceProvider,
             IRootObjectProvider,
             IAvaloniaXamlIlControlTemplateProvider
         {
-            private readonly IServiceProvider _parentProvider;
-            private readonly List<IResourceNode> _parentResourceNodes;
+            private readonly IServiceProvider? _parentProvider;
+            private readonly List<IResourceNode>? _parentResourceNodes;
             private readonly INameScope _nameScope;
 
-            public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
+            public DeferredParentServiceProvider(IServiceProvider? parentProvider, List<IResourceNode>? parentResourceNodes,
                 object rootObject, INameScope nameScope)
             {
                 _parentProvider = parentProvider;
@@ -73,7 +70,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
                     yield return p;
             }
 
-            public object GetService(Type serviceType)
+            public object? GetService(Type serviceType)
             {
                 if (serviceType == typeof(INameScope))
                     return _nameScope;
@@ -115,25 +112,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
         public static IServiceProvider CreateInnerServiceProviderV1(IServiceProvider compiled)
             => new InnerServiceProvider(compiled);
 
-        class InnerServiceProvider : IServiceProvider
+        private class InnerServiceProvider : IServiceProvider
         {
             private readonly IServiceProvider _compiledProvider;
-            private XamlTypeResolver _resolver;
+            private XamlTypeResolver? _resolver;
 
             public InnerServiceProvider(IServiceProvider compiledProvider)
             {
                 _compiledProvider = compiledProvider;
             }
-            public object GetService(Type serviceType)
+            public object? GetService(Type serviceType)
             {
                 if (serviceType == typeof(IXamlTypeResolver))
-                    return _resolver ?? (_resolver = new XamlTypeResolver(
-                               _compiledProvider.GetService<IAvaloniaXamlIlXmlNamespaceInfoProvider>()));
+                    return _resolver ??= new XamlTypeResolver(
+                        _compiledProvider.GetRequiredService<IAvaloniaXamlIlXmlNamespaceInfoProvider>());
                 return null;
             }
         }
 
-        class XamlTypeResolver : IXamlTypeResolver
+        private class XamlTypeResolver : IXamlTypeResolver
         {
             private readonly IAvaloniaXamlIlXmlNamespaceInfoProvider _nsInfo;
 
@@ -148,7 +145,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
                 var sp = qualifiedTypeName.Split(new[] {':'}, 2);
                 var (ns, name) = sp.Length == 1 ? ("", qualifiedTypeName) : (sp[0], sp[1]);
                 var namespaces = _nsInfo.XmlNamespaces;
-                var dic = (Dictionary<string, IReadOnlyList<AvaloniaXamlIlXmlNamespaceInfo>>)namespaces;
                 if (!namespaces.TryGetValue(ns, out var lst))
                     throw new ArgumentException("Unable to resolve namespace for type " + qualifiedTypeName);
                 foreach (var entry in lst)
@@ -177,20 +173,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
         }
         #line default
 
-        class RootServiceProvider : IServiceProvider
+        private class RootServiceProvider : IServiceProvider
         {
             private readonly INameScope _nameScope;
-            private readonly IServiceProvider _parentServiceProvider;
-            private readonly IRuntimePlatform _runtimePlatform;
+            private readonly IServiceProvider? _parentServiceProvider;
+            private readonly IRuntimePlatform? _runtimePlatform;
 
-            public RootServiceProvider(INameScope nameScope, IServiceProvider parentServiceProvider)
+            public RootServiceProvider(INameScope nameScope, IServiceProvider? parentServiceProvider)
             {
                 _nameScope = nameScope;
                 _parentServiceProvider = parentServiceProvider;
                 _runtimePlatform = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
             }
 
-            public object GetService(Type serviceType)
+            public object? GetService(Type serviceType)
             {
                 if (serviceType == typeof(INameScope))
                     return _nameScope;

+ 1 - 2
src/Markup/Avalonia.Markup/Data/Binding.cs

@@ -16,7 +16,6 @@ namespace Avalonia.Data
         /// Initializes a new instance of the <see cref="Binding"/> class.
         /// </summary>
         public Binding()
-            :base()
         {
         }
 
@@ -54,7 +53,7 @@ namespace Avalonia.Data
         /// <summary>
         /// Gets or sets a function used to resolve types from names in the binding path.
         /// </summary>
-        public Func<string, string, Type>? TypeResolver { get; set; }
+        public Func<string?, string, Type>? TypeResolver { get; set; }
 
         private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, bool enableDataValidation)
         {

+ 2 - 3
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@@ -7,7 +7,6 @@ using Avalonia.LogicalTree;
 using Avalonia.Reactive;
 using Avalonia.VisualTree;
 
-
 namespace Avalonia.Data
 {
     public abstract class BindingBase : IBinding
@@ -68,7 +67,7 @@ namespace Avalonia.Data
 
         public WeakReference? DefaultAnchor { get; set; }
 
-        public WeakReference<INameScope>? NameScope { get; set; }
+        public WeakReference<INameScope?>? NameScope { get; set; }
 
         private protected abstract ExpressionObserver CreateExpressionObserver(
             AvaloniaObject target,
@@ -169,7 +168,7 @@ namespace Avalonia.Data
         {
             _ = target ?? throw new ArgumentNullException(nameof(target));
 
-            if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null)
+            if (NameScope is null || !NameScope.TryGetTarget(out var scope))
                 throw new InvalidOperationException("Name scope is null or was already collected");
             var result = new ExpressionObserver(
                 NameScopeLocator.Track(scope, elementName),

+ 17 - 10
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Data
         ISetterValue
     {
         private bool _isSetterValue;
-        private StyledElement _target = default!;
+        private StyledElement? _target;
         private Type? _targetType;
         private bool _hasProducedValue;
 
@@ -42,7 +42,7 @@ namespace Avalonia.Data
             //
             // If the binding appears in a `Setter`, then make a clone and instantiate that because
             // because the setter can outlive the control and cause a leak.
-            if (_target == null && !_isSetterValue)
+            if (_target is null && !_isSetterValue)
             {
                 _target = (StyledElement)target;
                 _targetType = targetProperty?.PropertyType;
@@ -93,9 +93,9 @@ namespace Avalonia.Data
 
         void IObserver<object?>.OnNext(object? value)
         {
-            if (_target.TemplatedParent is AvaloniaObject templatedParent && Property != null)
+            if (_target?.TemplatedParent is { } templatedParent && Property is not null)
             {
-                if (Converter != null)
+                if (Converter is not null)
                 {
                     value = Converter.ConvertBack(
                         value,
@@ -114,24 +114,31 @@ namespace Avalonia.Data
         protected override void Subscribed()
         {
             TemplatedParentChanged();
-            _target.PropertyChanged += TargetPropertyChanged;
+
+            if (_target is not null)
+            {
+                _target.PropertyChanged += TargetPropertyChanged;
+            }
         }
 
         protected override void Unsubscribed()
         {
-            if (_target.TemplatedParent is AvaloniaObject templatedParent)
+            if (_target?.TemplatedParent is { } templatedParent)
             {
                 templatedParent.PropertyChanged -= TemplatedParentPropertyChanged;
             }
 
-            _target.PropertyChanged -= TargetPropertyChanged;
+            if (_target is not null)
+            {
+                _target.PropertyChanged -= TargetPropertyChanged;
+            }
         }
 
         private void PublishValue()
         {
-            if (_target.TemplatedParent is AvaloniaObject templatedParent)
+            if (_target?.TemplatedParent is { } templatedParent)
             {
-                var value = Property != null ?
+                var value = Property is not null ?
                     templatedParent.GetValue(Property) :
                     _target.TemplatedParent;
 
@@ -152,7 +159,7 @@ namespace Avalonia.Data
 
         private void TemplatedParentChanged()
         {
-            if (_target.TemplatedParent is AvaloniaObject templatedParent)
+            if (_target?.TemplatedParent is { } templatedParent)
             {
                 templatedParent.PropertyChanged += TemplatedParentPropertyChanged;
             }

+ 4 - 5
src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs

@@ -20,8 +20,7 @@ namespace Avalonia.Markup.Data
     /// </remarks>
     public static class DelayedBinding
     {
-        private static ConditionalWeakTable<StyledElement, List<Entry>> _entries = 
-            new ConditionalWeakTable<StyledElement, List<Entry>>();
+        private static readonly ConditionalWeakTable<StyledElement, List<Entry>> _entries = new();
 
         /// <summary>
         /// Adds a delayed binding to a control.
@@ -57,7 +56,7 @@ namespace Avalonia.Markup.Data
         /// <param name="target">The control.</param>
         /// <param name="property">The property on the control to bind to.</param>
         /// <param name="value">A function which returns the value.</param>
-        public static void Add(StyledElement target, PropertyInfo property, Func<StyledElement, object> value)
+        public static void Add(StyledElement target, PropertyInfo property, Func<StyledElement, object?> value)
         {
             if (target.IsInitialized)
             {
@@ -126,14 +125,14 @@ namespace Avalonia.Markup.Data
 
         private class ClrPropertyValueEntry : Entry
         {
-            public ClrPropertyValueEntry(PropertyInfo property, Func<StyledElement, object> value)
+            public ClrPropertyValueEntry(PropertyInfo property, Func<StyledElement, object?> value)
             {
                 Property = property;
                 Value = value;
             }
 
             public PropertyInfo Property { get; }
-            public Func<StyledElement, object> Value { get; }
+            public Func<StyledElement, object?> Value { get; }
 
             public override void Apply(StyledElement control)
             {

+ 2 - 4
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@@ -6,8 +6,6 @@ using Avalonia.Utilities;
 using System;
 using System.Collections.Generic;
 
-#nullable enable
-
 namespace Avalonia.Markup.Parsers
 {
     internal enum SourceMode
@@ -201,8 +199,8 @@ namespace Avalonia.Markup.Parsers
 
             nodes.Add(new AttachedPropertyNameNode
             {
-                Namespace = ns.ToString(),
-                TypeName = owner.ToString(),
+                Namespace = ns,
+                TypeName = owner,
                 PropertyName = name.ToString()
             });
             return State.AfterMember;

+ 4 - 4
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Markup.Parsers
     internal static class ExpressionObserverBuilder
     {
         [RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
-        internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type>? typeResolver = null,
+        internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string?, string, Type>? typeResolver = null,
             INameScope? nameScope = null)
         {
             if (string.IsNullOrWhiteSpace(expression))
@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Parsers
             string expression,
             bool enableDataValidation = false,
             string? description = null,
-            Func<string, string, Type>? typeResolver = null)
+            Func<string?, string, Type>? typeResolver = null)
         {
             return new ExpressionObserver(
                 root,
@@ -49,7 +49,7 @@ namespace Avalonia.Markup.Parsers
             string expression,
             bool enableDataValidation = false,
             string? description = null,
-            Func<string, string, Type>? typeResolver = null)
+            Func<string?, string, Type>? typeResolver = null)
         {
             _ = rootObservable ?? throw new ArgumentNullException(nameof(rootObservable));
 
@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Parsers
             IObservable<ValueTuple> update,
             bool enableDataValidation = false,
             string? description = null,
-            Func<string, string, Type>? typeResolver = null)
+            Func<string?, string, Type>? typeResolver = null)
         {
             _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
 

+ 8 - 10
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@@ -2,8 +2,6 @@ using Avalonia.Data.Core;
 using Avalonia.Markup.Parsers.Nodes;
 using Avalonia.Utilities;
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using Avalonia.Controls;
 using System.Diagnostics.CodeAnalysis;
 
@@ -15,10 +13,10 @@ namespace Avalonia.Markup.Parsers
     internal class ExpressionParser
     {
         private readonly bool _enableValidation;
-        private readonly Func<string, string, Type>? _typeResolver;
+        private readonly Func<string?, string, Type>? _typeResolver;
         private readonly INameScope? _nameScope;
 
-        public ExpressionParser(bool enableValidation, Func<string, string, Type>? typeResolver, INameScope? nameScope)
+        public ExpressionParser(bool enableValidation, Func<string?, string, Type>? typeResolver, INameScope? nameScope)
         {
             _typeResolver = typeResolver;
             _nameScope = nameScope;
@@ -37,13 +35,13 @@ namespace Avalonia.Markup.Parsers
                 ExpressionNode? nextNode = null;
                 switch (astNode)
                 {
-                    case BindingExpressionGrammar.EmptyExpressionNode _:
+                    case BindingExpressionGrammar.EmptyExpressionNode:
                         nextNode = new EmptyExpressionNode();
                         break;
-                    case BindingExpressionGrammar.NotNode _:
+                    case BindingExpressionGrammar.NotNode:
                         nextNode = new LogicalNotNode();
                         break;
-                    case BindingExpressionGrammar.StreamNode _:
+                    case BindingExpressionGrammar.StreamNode:
                         nextNode = new StreamNode();
                         break;
                     case BindingExpressionGrammar.PropertyNameNode propName:
@@ -55,7 +53,7 @@ namespace Avalonia.Markup.Parsers
                     case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
                         nextNode = ParseAttachedProperty(attachedProp);
                         break;
-                    case BindingExpressionGrammar.SelfNode _:
+                    case BindingExpressionGrammar.SelfNode:
                         nextNode = new SelfNode();
                         break;
                     case BindingExpressionGrammar.AncestorNode ancestor:
@@ -90,7 +88,7 @@ namespace Avalonia.Markup.Parsers
             Type? ancestorType = null;
             var ancestorLevel = node.Level;
 
-            if (!(node.Namespace is null) && !(node.TypeName is null))
+            if (!string.IsNullOrEmpty(node.TypeName))
             {
                 if (_typeResolver == null)
                 {
@@ -106,7 +104,7 @@ namespace Avalonia.Markup.Parsers
         private TypeCastNode ParseTypeCastNode(BindingExpressionGrammar.TypeCastNode node)
         {
             Type? castType = null;
-            if (!(node.Namespace is null) && !(node.TypeName is null))
+            if (!string.IsNullOrEmpty(node.TypeName))
             {
                 if (_typeResolver == null)
                 {

+ 2 - 4
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@@ -4,9 +4,7 @@ using System.Linq;
 using Avalonia.Data.Core;
 using Avalonia.Utilities;
 
-#nullable enable
-
-// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the 
+// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the
 // only reason they have overridden Equals methods is for unit testing.
 #pragma warning disable 659
 
@@ -642,7 +640,7 @@ namespace Avalonia.Markup.Parsers
         {
             public override bool Equals(object? obj)
             {
-                return obj is CommaSyntax or;
+                return obj is CommaSyntax;
             }
         }
 

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