Browse Source

Merge branch 'master' into refactor/selectionmodel-rewrite

Steven Kirk 5 năm trước cách đây
mục cha
commit
c7683854d9
51 tập tin đã thay đổi với 1675 bổ sung194 xóa
  1. 1 0
      samples/BindingDemo/MainWindow.xaml
  2. 12 0
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  3. 1 2
      samples/ControlCatalog/App.xaml
  4. 14 0
      samples/ControlCatalog/App.xaml.cs
  5. 4 4
      samples/ControlCatalog/Models/GDPValueConverter.cs
  6. 12 0
      samples/ControlCatalog/Models/Person.cs
  7. 3 2
      samples/ControlCatalog/Pages/DataGridPage.xaml
  8. 7 2
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  9. 0 41
      src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
  10. 1 1
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  11. 230 0
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  12. 1 1
      src/Avalonia.Base/Metadata/DependsOnAttribute.cs
  13. 1 0
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  14. 64 10
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  15. 12 1
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  16. 9 8
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  17. 1 1
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  18. 5 5
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  19. 14 8
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  20. 20 5
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  21. 1 1
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  22. 3 1
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  23. 9 3
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  24. 645 0
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  25. 17 0
      src/Avalonia.Controls/AppBuilderBase.cs
  26. 12 10
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  27. 105 21
      src/Avalonia.Controls/TextBox.cs
  28. 1 1
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  29. 14 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  30. 6 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  31. 9 0
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  32. 52 19
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  33. 13 4
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  34. 16 11
      src/Avalonia.Native/ScreenImpl.cs
  35. 10 5
      src/Avalonia.Native/WindowImplBase.cs
  36. 1 1
      src/Avalonia.Themes.Default/Expander.xaml
  37. 9 0
      src/Avalonia.Themes.Default/TextBox.xaml
  38. 2 2
      src/Avalonia.Themes.Fluent/Expander.xaml
  39. 6 0
      src/Avalonia.Themes.Fluent/TextBox.xaml
  40. 1 3
      src/Avalonia.Visuals/Media/GlyphRun.cs
  41. 16 0
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  42. 1 1
      src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
  43. 3 3
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  44. 2 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  45. 4 0
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  46. 10 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  47. 39 2
      tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs
  48. 35 0
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  49. 22 4
      tests/Avalonia.LeakTests/ControlTests.cs
  50. 140 4
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
  51. 59 1
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

+ 1 - 0
samples/BindingDemo/MainWindow.xaml

@@ -116,6 +116,7 @@
         <RadioButton Content="Radio Button" IsChecked="{Binding !!BooleanFlag, Mode=OneWay}" Command="{Binding StringValueCommand}" CommandParameter="RadioButton"/>
         <TextBox Text="{Binding Path=StringValue}"/>
         <Button Content="Nested View Model Button" Name="NestedTest" Command="{Binding NestedModel.Command}" />
+        <Button Content="Command Method Do" Command="{Binding Do}" x:Name="ToDo"/>
       </StackPanel>
     </TabItem>
   </TabControl>

+ 12 - 0
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using System.Threading;
 using ReactiveUI;
 using Avalonia.Controls;
+using Avalonia.Metadata;
 using Avalonia.Controls.Selection;
 
 namespace BindingDemo.ViewModels
@@ -103,5 +104,16 @@ namespace BindingDemo.ViewModels
             get { return _nested; }
             private set { this.RaiseAndSetIfChanged(ref _nested, value); }
         }
+
+        public void Do(object parameter)
+        {
+
+        }
+
+        [DependsOn(nameof(BooleanFlag))]
+        bool CanDo(object parameter)
+        {
+            return BooleanFlag;
+        }
     }
 }

+ 1 - 2
samples/ControlCatalog/App.xaml

@@ -1,8 +1,7 @@
 <Application xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.App">
-  <Application.Styles>        
-    <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
+  <Application.Styles>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="16" />
       <Setter Property="FontWeight" Value="Medium" />

+ 14 - 0
samples/ControlCatalog/App.xaml.cs

@@ -9,12 +9,23 @@ namespace ControlCatalog
 {
     public class App : Application
     {
+        private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+        {
+            Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
+        };
+
+        private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+        {
+            Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml")
+        };
+
         public static Styles FluentDark = new Styles
         {
             new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
             {
                 Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml")
             },
+            DataGridFluent
         };
 
         public static Styles FluentLight = new Styles
@@ -23,6 +34,7 @@ namespace ControlCatalog
             {
                 Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml")
             },
+            DataGridFluent
         };
 
         public static Styles DefaultLight = new Styles
@@ -43,6 +55,7 @@ namespace ControlCatalog
             {
                 Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
             },
+            DataGridDefault
         };
 
         public static Styles DefaultDark = new Styles
@@ -63,6 +76,7 @@ namespace ControlCatalog
             {
                 Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
             },
+            DataGridDefault
         };
 
         public override void Initialize()

+ 4 - 4
samples/ControlCatalog/Models/GDPValueConverter.cs

@@ -19,11 +19,11 @@ namespace ControlCatalog.Models
             if (value is int gdp)
             {
                 if (gdp <= 5000)
-                    return Brushes.Orange;
+                    return new SolidColorBrush(Colors.Orange, 0.6);
                 else if (gdp <= 10000)
-                    return Brushes.Yellow;
+                    return new SolidColorBrush(Colors.Yellow, 0.6);
                 else
-                    return Brushes.LightGreen;
+                    return new SolidColorBrush(Colors.LightGreen, 0.6);
             }
 
             return value;
@@ -34,4 +34,4 @@ namespace ControlCatalog.Models
             throw new NotImplementedException();
         }
     }
-}
+}

+ 12 - 0
samples/ControlCatalog/Models/Person.cs

@@ -15,6 +15,7 @@ namespace ControlCatalog.Models
     {
         string _firstName;
         string _lastName;
+        bool _isBanned;
 
         public string FirstName
         {
@@ -47,6 +48,17 @@ namespace ControlCatalog.Models
             }
         }
 
+        public bool IsBanned
+        {
+            get => _isBanned;
+            set
+            {
+                _isBanned = value;
+
+                OnPropertyChanged(nameof(_isBanned));
+            }
+        }
+
         Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
 
         void SetError(string propertyName, string error)

+ 3 - 2
samples/ControlCatalog/Pages/DataGridPage.xaml

@@ -18,7 +18,7 @@
     </StackPanel>
     <TabControl Grid.Row="1">
       <TabItem Header="DataGrid">
-        <DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True">
+        <DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
           <DataGrid.Columns>
             <DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
             <DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" />
@@ -44,7 +44,8 @@
           <DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0">
             <DataGrid.Columns>
               <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" />
-              <DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="*" />
+              <DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" />
+              <DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" />
             </DataGrid.Columns>
           </DataGrid>
           <Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" />

+ 7 - 2
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@@ -13,7 +13,7 @@ namespace ControlCatalog.Pages
             this.InitializeComponent();
             var dg1 = this.FindControl<DataGrid>("dataGrid1");
             dg1.IsReadOnly = true;
-
+            dg1.LoadingRow += Dg1_LoadingRow;
             var collectionView1 = new DataGridCollectionView(Countries.All);
             //collectionView.GroupDescriptions.Add(new PathGroupDescription("Region"));
 
@@ -33,7 +33,7 @@ namespace ControlCatalog.Pages
             var items = new List<Person>
             {
                 new Person { FirstName = "John", LastName = "Doe" },
-                new Person { FirstName = "Elizabeth", LastName = "Thomas" },
+                new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true },
                 new Person { FirstName = "Zack", LastName = "Ward" }
             };
             var collectionView3 = new DataGridCollectionView(items);
@@ -44,6 +44,11 @@ namespace ControlCatalog.Pages
             addButton.Click += (a, b) => collectionView3.AddNew();
         }
 
+        private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e)
+        {
+            e.Row.Header = e.Row.GetIndex() + 1;
+        }
+
         private void InitializeComponent()
         {
             AvaloniaXamlLoader.Load(this);

+ 0 - 41
src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs

@@ -1,41 +0,0 @@
-using System;
-using System.Globalization;
-using System.Reflection;
-using System.Windows.Input;
-using Avalonia.Utilities;
-
-namespace Avalonia.Data.Converters
-{
-    class AlwaysEnabledDelegateCommand : ICommand
-    {
-        private readonly Delegate action;
-
-        private ParameterInfo parameterInfo;
-
-        public AlwaysEnabledDelegateCommand(Delegate action)
-        {
-            this.action = action;
-            var parameters = action.Method.GetParameters();
-            parameterInfo = parameters.Length == 0 ? null : parameters[0];
-        }
-
-#pragma warning disable 0067
-        public event EventHandler CanExecuteChanged;
-#pragma warning restore 0067
-
-        public bool CanExecute(object parameter) => true;
-
-        public void Execute(object parameter)
-        {
-            if (parameterInfo == null)
-            {
-                action.DynamicInvoke();
-            }
-            else
-            {
-                TypeUtilities.TryConvert(parameterInfo.ParameterType, parameter, CultureInfo.CurrentCulture, out object convertedParameter);
-                action.DynamicInvoke(convertedParameter); 
-            }
-        }
-    }
-}

+ 1 - 1
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Data.Converters
 
             if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)
             {
-                return new AlwaysEnabledDelegateCommand(d);
+                return new MethodToCommandConverter(d);
             }
 
             if (TypeUtilities.TryConvert(targetType, value, culture, out object result))

+ 230 - 0
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@@ -0,0 +1,230 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Windows.Input;
+using Avalonia.Utilities;
+
+namespace Avalonia.Data.Converters
+{
+    class MethodToCommandConverter : ICommand
+    {
+        readonly static Func<object, bool> AlwaysEnabled = (_) => true;
+        readonly static MethodInfo tryConvert = typeof(TypeUtilities)
+            .GetMethod(nameof(TypeUtilities.TryConvert), BindingFlags.Public | BindingFlags.Static);
+        readonly static PropertyInfo currentCulture = typeof(CultureInfo)
+            .GetProperty(nameof(CultureInfo.CurrentCulture), BindingFlags.Public | BindingFlags.Static);
+        readonly Func<object, bool> canExecute;
+        readonly Action<object> execute;
+        readonly WeakPropertyChangedProxy weakPropertyChanged;
+        readonly PropertyChangedEventHandler propertyChangedEventHandler;
+        readonly string[] dependencyProperties;
+
+        public MethodToCommandConverter(Delegate action)
+        {
+            var target = action.Target;
+            var canExecuteMethodName = "Can" + action.Method.Name;
+            var parameters = action.Method.GetParameters();
+            var parameterInfo = parameters.Length == 0 ? null : parameters[0].ParameterType;
+
+            if (parameterInfo == null)
+            {
+                execute = CreateExecute(target, action.Method);
+            }
+            else
+            {
+                execute = CreateExecute(target, action.Method, parameterInfo);
+            }
+
+            var canExecuteMethod = action.Method.DeclaringType.GetRuntimeMethods()
+                .FirstOrDefault(m => m.Name == canExecuteMethodName
+                    && m.GetParameters().Length == 1
+                    && m.GetParameters()[0].ParameterType == typeof(object));
+            if (canExecuteMethod == null)
+            {
+                canExecute = AlwaysEnabled;
+            }
+            else
+            {
+                canExecute = CreateCanExecute(target, canExecuteMethod);
+                dependencyProperties = canExecuteMethod
+                    .GetCustomAttributes(typeof(Metadata.DependsOnAttribute), true)
+                    .OfType<Metadata.DependsOnAttribute>()
+                    .Select(x => x.Name)
+                    .ToArray();
+                if (dependencyProperties.Any()
+                    && target is INotifyPropertyChanged inpc)
+                {
+                    propertyChangedEventHandler = OnPropertyChanged;
+                    weakPropertyChanged = new WeakPropertyChangedProxy(inpc, propertyChangedEventHandler);
+                }
+            }
+        }
+
+        void OnPropertyChanged(object sender,PropertyChangedEventArgs args)
+        {
+            if (string.IsNullOrWhiteSpace(args.PropertyName)
+                               || dependencyProperties?.Contains(args.PropertyName) == true)
+            {
+                Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
+                    , Threading.DispatcherPriority.Input);
+            }
+        }
+
+#pragma warning disable 0067
+        public event EventHandler CanExecuteChanged;
+#pragma warning restore 0067
+
+        public bool CanExecute(object parameter) => canExecute(parameter);
+
+        public void Execute(object parameter) => execute(parameter);
+
+
+        static Action<object> CreateExecute(object target
+            , System.Reflection.MethodInfo method)
+        {
+
+            var parameter = Expression.Parameter(typeof(object), "parameter");
+
+            var instance = Expression.Convert
+            (
+                Expression.Constant(target),
+                method.DeclaringType
+            );
+
+
+            var call = Expression.Call
+            (
+                instance,
+                method
+            );
+
+
+            return Expression
+                .Lambda<Action<object>>(call, parameter)
+                .Compile();
+        }
+
+        static Action<object> CreateExecute(object target
+            , System.Reflection.MethodInfo method
+            , Type parameterType)
+        {
+
+            var parameter = Expression.Parameter(typeof(object), "parameter");
+
+            var instance = Expression.Convert
+            (
+                Expression.Constant(target),
+                method.DeclaringType
+            );
+
+            Expression body;
+
+            if (parameterType == typeof(object))
+            {
+                body = Expression.Call(instance,
+                    method,
+                    parameter
+                    );
+            }
+            else
+            {
+                var arg0 = Expression.Variable(typeof(object), "argX");
+                var convertCall = Expression.Call(tryConvert,
+                     Expression.Constant(parameterType),
+                     parameter,
+                      Expression.Property(null, currentCulture),
+                     arg0
+                    );
+
+                var call = Expression.Call(instance,
+                    method,
+                    Expression.Convert(arg0, parameterType)
+                    );
+                body = Expression.Block(new[] { arg0 },
+                    convertCall,
+                    call
+                    );
+
+            }
+            Action<object> action = null;
+            try
+            {
+                action = Expression
+                   .Lambda<Action<object>>(body, parameter)
+                   .Compile();
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            return action;
+        }
+
+        static Func<object, bool> CreateCanExecute(object target
+            , System.Reflection.MethodInfo method)
+        {
+            var parameter = Expression.Parameter(typeof(object), "parameter");
+            var instance = Expression.Convert
+            (
+                Expression.Constant(target),
+                method.DeclaringType
+            );
+            var call = Expression.Call
+            (
+                instance,
+                method,
+                parameter
+            );
+            return Expression
+                .Lambda<Func<object, bool>>(call, parameter)
+                .Compile();
+        }
+
+
+        internal class WeakPropertyChangedProxy
+        {
+            readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
+            readonly PropertyChangedEventHandler _handler;
+            internal WeakReference<INotifyPropertyChanged> Source { get; } = new WeakReference<INotifyPropertyChanged>(null);
+
+            public WeakPropertyChangedProxy()
+            {
+                _handler = new PropertyChangedEventHandler(OnPropertyChanged);
+            }
+
+            public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
+            {
+                SubscribeTo(source, listener);
+            }
+
+            public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
+            {
+                source.PropertyChanged += _handler;
+
+                Source.SetTarget(source);
+                _listener.SetTarget(listener);
+            }
+
+            public void Unsubscribe()
+            {
+                if (Source.TryGetTarget(out INotifyPropertyChanged source) && source != null)
+                    source.PropertyChanged -= _handler;
+
+                Source.SetTarget(null);
+                _listener.SetTarget(null);
+            }
+
+            void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
+            {
+                if (_listener.TryGetTarget(out var handler) && handler != null)
+                    handler(sender, e);
+                else
+                    Unsubscribe();
+            }
+           
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Base/Metadata/DependsOnAttribute.cs

@@ -5,7 +5,7 @@ namespace Avalonia.Metadata
     /// <summary>
     /// Indicates that the property depends on the value of another property in markup.
     /// </summary>
-    [AttributeUsage(AttributeTargets.Property)]
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
     public class DependsOnAttribute : Attribute
     {
         /// <summary>

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

@@ -9,3 +9,4 @@ using Avalonia.Metadata;
 [assembly: InternalsVisibleTo("Avalonia.UnitTests")]
 [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
 [assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]
+[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]

+ 64 - 10
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -39,6 +39,7 @@ namespace Avalonia.Controls
         private const string DATAGRID_elementRowHeadersPresenterName = "PART_RowHeadersPresenter";
         private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader";
         private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader";
+        private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner";
         private const string DATAGRID_elementValidationSummary = "PART_ValidationSummary";
         private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar";
 
@@ -79,6 +80,7 @@ namespace Avalonia.Controls
         private INotifyCollectionChanged _topLevelGroup;
         private ContentControl _clipboardContentControl;
 
+        private IVisual _bottomRightCorner;
         private DataGridColumnHeadersPresenter _columnHeadersPresenter;
         private DataGridRowsPresenter _rowsPresenter;
         private ScrollBar _vScrollBar;
@@ -1825,6 +1827,22 @@ namespace Avalonia.Controls
             }
         }
 
+        private bool IsHorizontalScrollBarOverCells
+        {
+            get
+            {
+                return _columnHeadersPresenter != null && Grid.GetColumnSpan(_columnHeadersPresenter) == 2;
+            }
+        }
+
+        private bool IsVerticalScrollBarOverCells
+        {
+            get
+            {
+                return _rowsPresenter != null && Grid.GetRowSpan(_rowsPresenter) == 2;
+            }
+        }
+
         private int NoSelectionChangeCount
         {
             get
@@ -2323,6 +2341,7 @@ namespace Avalonia.Controls
             _topLeftCornerHeader = e.NameScope.Find<ContentControl>(DATAGRID_elementTopLeftCornerHeaderName);
             EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader;
             _topRightCornerHeader = e.NameScope.Find<ContentControl>(DATAGRID_elementTopRightCornerHeaderName);
+            _bottomRightCorner = e.NameScope.Find<IVisual>(DATAGRID_elementBottomRightCornerHeaderName);
         }
 
         /// <summary>
@@ -2753,7 +2772,7 @@ namespace Avalonia.Controls
                         //We don't need to refresh the state of AutoGenerated column headers because they're up-to-date
                         if (!column.IsAutoGenerated && column.HasHeaderCell)
                         {
-                            column.HeaderCell.ApplyState();
+                            column.HeaderCell.UpdatePseudoClasses();
                         }
                     }
 
@@ -3293,6 +3312,10 @@ namespace Avalonia.Controls
                 //  
 
             }
+
+            bool isHorizontalScrollBarOverCells = IsHorizontalScrollBarOverCells;
+            bool isVerticalScrollBarOverCells = IsVerticalScrollBarOverCells;
+
             double cellsWidth = CellsWidth;
             double cellsHeight = CellsHeight;
 
@@ -3308,10 +3331,17 @@ namespace Avalonia.Controls
                 // Compensate if the horizontal scrollbar is already taking up space
                 if (!forceHorizScrollbar && _hScrollBar.IsVisible)
                 {
-                    cellsHeight += _hScrollBar.DesiredSize.Height;
+                    if (!isHorizontalScrollBarOverCells)
+                    {
+                        cellsHeight += _hScrollBar.DesiredSize.Height;
+                    }
+                }
+                if (!isHorizontalScrollBarOverCells)
+                {
+                    horizScrollBarHeight = _hScrollBar.Height + _hScrollBar.Margin.Top + _hScrollBar.Margin.Bottom;
                 }
-                horizScrollBarHeight = _hScrollBar.Height + _hScrollBar.Margin.Top + _hScrollBar.Margin.Bottom;
             }
+
             bool allowVertScrollbar = false;
             bool forceVertScrollbar = false;
             double vertScrollBarWidth = 0;
@@ -3324,9 +3354,15 @@ namespace Avalonia.Controls
                 // Compensate if the vertical scrollbar is already taking up space
                 if (!forceVertScrollbar && _vScrollBar.IsVisible)
                 {
-                    cellsWidth += _vScrollBar.DesiredSize.Width;
+                    if (!isVerticalScrollBarOverCells)
+                    {
+                        cellsWidth += _vScrollBar.DesiredSize.Width;
+                    }
+                }
+                if (!isVerticalScrollBarOverCells)
+                {
+                    vertScrollBarWidth = _vScrollBar.Width + _vScrollBar.Margin.Left + _vScrollBar.Margin.Right;
                 }
-                vertScrollBarWidth = _vScrollBar.Width + _vScrollBar.Margin.Left + _vScrollBar.Margin.Right;
             }
 
             // Now cellsWidth is the width potentially available for displaying data cells.
@@ -3354,7 +3390,9 @@ namespace Avalonia.Controls
                     cellsHeight -= horizScrollBarHeight;
                     Debug.Assert(cellsHeight >= 0);
                     needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
-                    if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
+
+                    if (vertScrollBarWidth > 0 && 
+                        allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
                         MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
                     {
                         // Would we still need a horizontal scrollbar without the vertical one?
@@ -3372,7 +3410,12 @@ namespace Avalonia.Controls
                     }
                 }
 
-                UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
+                // Store the current FirstScrollingSlot because removing the horizontal scrollbar could scroll
+                // the DataGrid up; however, if we realize later that we need to keep the horizontal scrollbar
+                // then we should use the first slot stored here which is not scrolled.
+                int firstScrollingSlot = DisplayData.FirstScrollingSlot;
+
+                UpdateDisplayedRows(firstScrollingSlot, cellsHeight);
                 if (allowVertScrollbar &&
                     MathUtilities.GreaterThan(cellsHeight, 0) &&
                     MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
@@ -3384,10 +3427,12 @@ namespace Avalonia.Controls
                 }
 
                 DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn();
+
                 // we compute the number of visible columns only after we set up the vertical scroll bar.
                 ComputeDisplayedColumns();
 
-                if (allowHorizScrollbar &&
+                if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && 
+                    allowHorizScrollbar &&
                     needVertScrollbar && !needHorizScrollbar &&
                     MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
                     MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
@@ -3398,7 +3443,7 @@ namespace Avalonia.Controls
                     Debug.Assert(cellsHeight >= 0);
                     needVertScrollbar = false;
 
-                    UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
+                    UpdateDisplayedRows(firstScrollingSlot, cellsHeight);
                     if (cellsHeight > 0 &&
                         vertScrollBarWidth <= cellsWidth &&
                         DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
@@ -3479,6 +3524,15 @@ namespace Avalonia.Controls
                     _topRightCornerHeader.IsVisible = false;
                 }
             }
+
+            if (_bottomRightCorner != null)
+            {
+                // Show the BottomRightCorner when both scrollbars are visible.
+                _bottomRightCorner.IsVisible =
+                    _hScrollBar != null && _hScrollBar.IsVisible &&
+                    _vScrollBar != null && _vScrollBar.IsVisible;
+            }
+
             DisplayData.FullyRecycleElements();
         }
 
@@ -5417,7 +5471,7 @@ namespace Avalonia.Controls
             }
             else if (displayedElement is DataGridRowGroupHeader groupHeader)
             {
-                groupHeader.ApplyState(useTransitions: true);
+                groupHeader.UpdatePseudoClasses();
                 if (AreRowHeadersVisible)
                 {
                     groupHeader.ApplyHeaderStatus();

+ 12 - 1
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         private Rectangle _rightGridLine;
         private DataGridColumn _owningColumn;
 
-        bool _isValid;
+        bool _isValid = true;
 
         public static readonly DirectProperty<DataGridCell, bool> IsValidProperty =
             AvaloniaProperty.RegisterDirect<DataGridCell, bool>(
@@ -180,7 +180,18 @@ namespace Avalonia.Controls
 
         internal void UpdatePseudoClasses()
         {
+            if (OwningGrid == null || OwningColumn == null || OwningRow == null || !OwningRow.IsVisible || OwningRow.Slot == -1)
+            {
+                return;
+            }
+
+            PseudoClasses.Set(":selected", OwningRow.IsSelected);
+
+            PseudoClasses.Set(":current", IsCurrent);
+
+            PseudoClasses.Set(":edited", IsEdited);
 
+            PseudoClasses.Set(":invalid", !IsValid);
         }
 
         // Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the 

+ 9 - 8
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -13,6 +13,7 @@ using System.Diagnostics;
 using Avalonia.Utilities;
 using System;
 using Avalonia.Controls.Utils;
+using Avalonia.Controls.Mixins;
 
 namespace Avalonia.Controls
 {
@@ -39,7 +40,7 @@ namespace Avalonia.Controls
         private static Cursor _originalCursor;
         private static double _originalHorizontalOffset;
         private static double _originalWidth;
-        private bool _desiredSeparatorVisibility;
+        private bool _desiredSeparatorVisibility = true;
         private static Point? _dragStart;
         private static DataGridColumn _dragColumn;
         private static double _frozenColumnsWidth;
@@ -68,6 +69,7 @@ namespace Avalonia.Controls
         static DataGridColumnHeader()
         {
             AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x, e) => x.OnAreSeparatorsVisibleChanged(e));
+            PressedMixin.Attach<DataGridColumnHeader>();
         }
 
         /// <summary>
@@ -149,8 +151,7 @@ namespace Avalonia.Controls
             }
         }
 
-        //TODO Implement
-        internal void ApplyState()
+        internal void UpdatePseudoClasses()
         {
             CurrentSortingState = null;
             if (OwningGrid != null
@@ -441,7 +442,7 @@ namespace Avalonia.Controls
 
             Point mousePosition = e.GetPosition(this);
             OnMouseEnter(mousePosition);
-            ApplyState();
+            UpdatePseudoClasses();
         }
 
         private void DataGridColumnHeader_PointerLeave(object sender, PointerEventArgs e)
@@ -452,12 +453,12 @@ namespace Avalonia.Controls
             }
 
             OnMouseLeave();
-            ApplyState();
+            UpdatePseudoClasses();
         }
 
         private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e)
         {
-            if (OwningColumn == null || e.Handled || !IsEnabled || e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+            if (OwningColumn == null || e.Handled || !IsEnabled || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
             {
                 return;
             }
@@ -467,7 +468,7 @@ namespace Avalonia.Controls
             OnMouseLeftButtonDown(ref handled, e, mousePosition);
             e.Handled = handled;
 
-            ApplyState();
+            UpdatePseudoClasses();
         }
 
         private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e)
@@ -483,7 +484,7 @@ namespace Avalonia.Controls
             OnMouseLeftButtonUp(ref handled, e, mousePosition, mousePositionHeaders);
             e.Handled = handled;
 
-            ApplyState();
+            UpdatePseudoClasses();
         }
 
         private void DataGridColumnHeader_PointerMove(object sender, PointerEventArgs e)

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs

@@ -610,7 +610,7 @@ namespace Avalonia.Controls
             // refresh sort description
             foreach (DataGridColumn column in _owner.ColumnsItemsInternal)
             {
-                column.HeaderCell.ApplyState();
+                column.HeaderCell.UpdatePseudoClasses();
             }
         }
 

+ 5 - 5
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -624,17 +624,17 @@ namespace Avalonia.Controls
         {
             if (_headerElement != null && OwningGrid.AreRowHeadersVisible)
             {
-                _headerElement.ApplyOwnerStatus();
+                _headerElement.UpdatePseudoClasses();
             }
         }
 
-        //TODO Implement
         internal void UpdatePseudoClasses()
         {
-            PseudoClasses.Set(":selected", IsSelected);
-            PseudoClasses.Set(":editing", IsEditing);
             if (RootElement != null && OwningGrid != null && IsVisible)
             {
+                PseudoClasses.Set(":selected", IsSelected);
+                PseudoClasses.Set(":editing", IsEditing);
+                PseudoClasses.Set(":invalid", !IsValid);
                 ApplyHeaderStatus();
             } 
         }
@@ -789,7 +789,7 @@ namespace Avalonia.Controls
 
         private void DataGridRow_PointerPressed(PointerPressedEventArgs e)
         {
-            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+            if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
             {
                 return;
             }

+ 14 - 8
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@@ -3,6 +3,7 @@
 // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 // All other rights reserved.
 
+using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Media;
@@ -96,6 +97,7 @@ namespace Avalonia.Controls
         static DataGridRowGroupHeader()
         {
             SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>((x,e) => x.OnSublevelIndentChanged(e));
+            PressedMixin.Attach<DataGridRowGroupHeader>();
         }
 
         /// <summary>
@@ -205,14 +207,18 @@ namespace Avalonia.Controls
         {
             if (_headerElement != null && OwningGrid.AreRowHeadersVisible)
             {
-                _headerElement.ApplyOwnerStatus();
+                _headerElement.UpdatePseudoClasses();
             }
         }
 
-        //TODO Implement
-        internal void ApplyState(bool useTransitions)
+        internal void UpdatePseudoClasses()
         {
+            PseudoClasses.Set(":current", IsCurrent);
 
+            if (RowGroupInfo?.CollectionViewGroup != null)
+            {
+                PseudoClasses.Set(":expanded", RowGroupInfo.IsVisible && RowGroupInfo.CollectionViewGroup.ItemCount > 0);
+            }
         }
 
         protected override Size ArrangeOverride(Size finalSize)
@@ -328,7 +334,7 @@ namespace Avalonia.Controls
         {
             if (_headerElement != null && OwningGrid != null)
             {
-                _headerElement.IsVisible = OwningGrid.AreColumnHeadersVisible;
+                _headerElement.IsVisible = OwningGrid.AreRowHeadersVisible;
             }
         }
 
@@ -344,7 +350,7 @@ namespace Avalonia.Controls
         {
             EnsureExpanderButtonIsChecked();
             EnsureHeaderVisibility();
-            ApplyState(useTransitions: false);
+            UpdatePseudoClasses();
             ApplyHeaderStatus();
         }
 
@@ -353,7 +359,7 @@ namespace Avalonia.Controls
             if (IsEnabled)
             {
                 IsMouseOver = true;
-                ApplyState(useTransitions: true);
+                UpdatePseudoClasses();
             }
 
             base.OnPointerEnter(e);
@@ -364,7 +370,7 @@ namespace Avalonia.Controls
             if (IsEnabled)
             {
                 IsMouseOver = false;
-                ApplyState(useTransitions: true);
+                UpdatePseudoClasses();
             }
 
             base.OnPointerLeave(e);
@@ -402,7 +408,7 @@ namespace Avalonia.Controls
 
                 EnsureExpanderButtonIsChecked();
 
-                ApplyState(true /*useTransitions*/);
+                UpdatePseudoClasses();
             }
         }
 

+ 20 - 5
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     public class DataGridRowHeader : ContentControl
     {
-        private const string DATAGRIDROWHEADER_elementRootName = "Root";
+        private const string DATAGRIDROWHEADER_elementRootName = "PART_Root";
         private const double DATAGRIDROWHEADER_separatorThickness = 1;
 
         private Control _rootElement;
@@ -99,7 +99,7 @@ namespace Avalonia.Controls.Primitives
             _rootElement = e.NameScope.Find<Control>(DATAGRIDROWHEADER_elementRootName);
             if (_rootElement != null)
             {
-                ApplyOwnerStatus();
+                UpdatePseudoClasses();
             }
         } 
 
@@ -131,12 +131,27 @@ namespace Avalonia.Controls.Primitives
             return measuredSize;
         }
 
-        //TODO Implement
-        internal void ApplyOwnerStatus()
+        internal void UpdatePseudoClasses()
         {
             if (_rootElement != null && Owner != null && Owner.IsVisible)
             {
+                if (OwningRow != null)
+                {
+                    PseudoClasses.Set(":invalid", !OwningRow.IsValid);
+
+                    PseudoClasses.Set(":selected", OwningRow.IsSelected);
+
+                    PseudoClasses.Set(":editing", OwningRow.IsEditing);
 
+                    if (OwningGrid != null)
+                    {
+                        PseudoClasses.Set(":current", OwningRow.Slot == OwningGrid.CurrentSlot);
+                    }
+                }
+                else if (OwningRowGroupHeader != null && OwningGrid != null)
+                {
+                    PseudoClasses.Set(":current", OwningRowGroupHeader.RowGroupInfo.Slot == OwningGrid.CurrentSlot);
+                }
             }
         }
 
@@ -162,7 +177,7 @@ namespace Avalonia.Controls.Primitives
         //TODO TabStop
         private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
         {
-            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+            if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
             {
                 return;
             }

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@@ -1912,7 +1912,7 @@ namespace Avalonia.Controls
             {
                 // Assume it's a RowGroupHeader
                 DataGridRowGroupHeader groupHeader = element as DataGridRowGroupHeader;
-                groupHeader.ApplyState(useTransitions: true);
+                groupHeader.UpdatePseudoClasses();
             }
         }
 

+ 3 - 1
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@@ -9,6 +9,7 @@ using Avalonia.Media;
 using System;
 using System.ComponentModel;
 using Avalonia.Layout;
+using Avalonia.Markup.Xaml.MarkupExtensions;
 
 namespace Avalonia.Controls
 {
@@ -17,6 +18,7 @@ namespace Avalonia.Controls
     /// </summary>
     public class DataGridTextColumn : DataGridBoundColumn
     {
+        private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin";
 
         private double? _fontSize;
         private FontStyle? _fontStyle;
@@ -186,7 +188,7 @@ namespace Avalonia.Controls
         {
             TextBlock textBlockElement = new TextBlock
             {
-                Margin = new Thickness(4),
+                [!Layoutable.MarginProperty] = new DynamicResourceExtension(DATAGRID_TextColumnCellTextBlockMarginKey),
                 VerticalAlignment = VerticalAlignment.Center
             };
 

+ 9 - 3
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@@ -1,5 +1,11 @@
-<Styles xmlns="https://github.com/avaloniaui">
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <!--TODO: Validation and Focus-->
+
+  <Styles.Resources>
+    <Thickness x:Key="DataGridTextColumnCellTextBlockMargin">4</Thickness>
+  </Styles.Resources>
+  
   <Style Selector="DataGridCell">
     <Setter Property="Background" Value="Transparent"/>
     <Setter Property="HorizontalContentAlignment" Value="Stretch" />
@@ -133,7 +139,7 @@
   <Style Selector="DataGridRowHeader">
     <Setter Property="Template">
       <ControlTemplate>
-        <Grid
+        <Grid x:Name="PART_Root"
         RowDefinitions="*,*,Auto"
         ColumnDefinitions="Auto,*">
 
@@ -218,7 +224,7 @@
             <Rectangle Name="PART_ColumnHeadersAndRowsSeparator" Grid.ColumnSpan="3" VerticalAlignment="Bottom" StrokeThickness="1" Height="1" Fill="{DynamicResource ThemeControlMidHighBrush}"/>
 
             <DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" />
-            <Rectangle Name="BottomRightCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Column="2" Grid.Row="2" />
+            <Rectangle Name="PART_BottomRightCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Column="2" Grid.Row="2" />
             <Rectangle Name="BottomLeftCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Row="2" Grid.ColumnSpan="2" />
             <ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}"/>
 

+ 645 - 0
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@@ -0,0 +1,645 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Styles.Resources>
+    <Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
+
+    <x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
+    <x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
+
+    <StreamGeometry x:Key="DataGridSortIconAscendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
+    <StreamGeometry x:Key="DataGridSortIconDescendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
+    <StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
+    <StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
+
+    <SolidColorBrush x:Key="DataGridGridLinesBrush"
+                     Color="{StaticResource SystemBaseMediumLowColor}"
+                     Opacity="0.4" />
+    <SolidColorBrush x:Key="DataGridDropLocationIndicatorBackground"
+                     Color="#3F4346" />
+    <SolidColorBrush x:Key="DataGridDisabledVisualElementBackground"
+                     Color="#8CFFFFFF" />
+    <SolidColorBrush x:Key="DataGridFillerGridLinesBrush"
+                     Color="Transparent" />
+    <SolidColorBrush x:Key="DataGridCurrencyVisualPrimaryBrush"
+                     Color="Transparent" />
+    <StaticResource x:Key="DataGridColumnHeaderBackgroundColor"
+                    ResourceKey="SystemAltHighColor" />
+    <SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush"
+                     Color="{StaticResource DataGridColumnHeaderBackgroundColor}" />
+    <StaticResource x:Key="DataGridScrollBarsSeparatorBackground"
+                    ResourceKey="SystemChromeLowColor" />
+    <StaticResource x:Key="DataGridColumnHeaderForegroundBrush"
+                    ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="DataGridColumnHeaderHoveredBackgroundColor"
+                    ResourceKey="SystemListLowColor" />
+    <StaticResource x:Key="DataGridColumnHeaderPressedBackgroundColor"
+                    ResourceKey="SystemListMediumColor" />
+    <StaticResource x:Key="DataGridColumnHeaderDraggedBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
+    <StaticResource x:Key="DataGridColumnHeaderPointerOverBrush"
+                    ResourceKey="SystemControlHighlightListLowBrush" />
+    <StaticResource x:Key="DataGridColumnHeaderPressedBrush"
+                    ResourceKey="SystemControlHighlightListMediumBrush" />
+    <StaticResource x:Key="DataGridDetailsPresenterBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
+    <StaticResource x:Key="DataGridFillerColumnGridLinesBrush"
+                    ResourceKey="DataGridFillerGridLinesBrush" />
+    <StaticResource x:Key="DataGridRowSelectedBackgroundColor"
+                    ResourceKey="SystemAccentColor" />
+    <StaticResource x:Key="DataGridRowSelectedBackgroundOpacity"
+                    ResourceKey="ListAccentLowOpacity" />
+    <StaticResource x:Key="DataGridRowSelectedHoveredBackgroundColor"
+                    ResourceKey="SystemAccentColor" />
+    <StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity"
+                    ResourceKey="ListAccentMediumOpacity" />
+    <StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundColor"
+                    ResourceKey="SystemAccentColor" />
+    <StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity"
+                    ResourceKey="ListAccentLowOpacity" />
+    <StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundColor"
+                    ResourceKey="SystemAccentColor" />
+    <StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity"
+                    ResourceKey="ListAccentMediumOpacity" />
+    <StaticResource x:Key="DataGridRowHeaderForegroundBrush"
+                    ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="DataGridRowHeaderBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundAltHighBrush" />
+    <StaticResource x:Key="DataGridRowGroupHeaderBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundChromeMediumBrush" />
+    <StaticResource x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundListLowBrush" />
+    <StaticResource x:Key="DataGridRowGroupHeaderPressedBackgroundBrush"
+                    ResourceKey="SystemControlBackgroundListMediumBrush" />
+    <StaticResource x:Key="DataGridRowGroupHeaderForegroundBrush"
+                    ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="DataGridRowInvalidBrush"
+                    ResourceKey="SystemErrorTextColor" />
+    <StaticResource x:Key="DataGridCellBackgroundBrush"
+                    ResourceKey="SystemControlTransparentBrush" />
+    <StaticResource x:Key="DataGridCellFocusVisualPrimaryBrush"
+                    ResourceKey="SystemControlFocusVisualPrimaryBrush" />
+    <StaticResource x:Key="DataGridCellFocusVisualSecondaryBrush"
+                    ResourceKey="SystemControlFocusVisualSecondaryBrush" />
+    <StaticResource x:Key="DataGridCellInvalidBrush"
+                    ResourceKey="SystemErrorTextColor" />
+  </Styles.Resources>
+
+  <Style Selector="DataGridCell">
+    <Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
+    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
+    <Setter Property="VerticalContentAlignment" Value="Stretch" />
+    <Setter Property="FontSize" Value="15" />
+    <Setter Property="MinHeight" Value="32" />
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Grid x:Name="PART_CellRoot"
+              ColumnDefinitions="*,Auto"
+              Background="{TemplateBinding Background}">
+
+          <Rectangle x:Name="CurrencyVisual"
+                     HorizontalAlignment="Stretch"
+                     VerticalAlignment="Stretch"
+                     Fill="Transparent"
+                     IsHitTestVisible="False"
+                     Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
+                     StrokeThickness="1" />
+          <Grid x:Name="FocusVisual"
+                IsHitTestVisible="False">
+            <Rectangle HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
+                       StrokeThickness="2" />
+            <Rectangle Margin="2"
+                       HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
+                       StrokeThickness="1" />
+          </Grid>
+
+          <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
+                            Margin="{TemplateBinding Padding}"
+                            TextBlock.Foreground="{TemplateBinding Foreground}"
+                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
+
+          <Rectangle x:Name="InvalidVisualElement"
+                     HorizontalAlignment="Stretch"
+                     VerticalAlignment="Stretch"
+                     IsHitTestVisible="False"
+                     Stroke="{DynamicResource DataGridCellInvalidBrush}"
+                     StrokeThickness="1" />
+
+          <Rectangle Name="PART_RightGridLine"
+                     Grid.Column="1"
+                     VerticalAlignment="Stretch"
+                     Width="1"
+                     Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
+        </Grid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridCell /template/ Rectangle#CurrencyVisual">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridCell /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridCell:current /template/ Rectangle#CurrencyVisual">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+  <Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+  <Style Selector="DataGridCell /template/ Rectangle#InvalidVisualElement">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridCell:invalid /template/ Rectangle#InvalidVisualElement">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader">
+    <Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
+    <Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
+    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
+    <Setter Property="VerticalContentAlignment" Value="Center" />
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
+    <Setter Property="Padding" Value="12,0,0,0" />
+    <Setter Property="FontSize" Value="12" />
+    <Setter Property="MinHeight" Value="32" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Grid Name="PART_ColumnHeaderRoot"
+              ColumnDefinitions="*,Auto"
+              Background="{TemplateBinding Background}">
+
+          <Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                Margin="{TemplateBinding Padding}">
+            <Grid.ColumnDefinitions>
+              <ColumnDefinition Width="*" />
+              <ColumnDefinition MinWidth="32"
+                                Width="Auto" />
+            </Grid.ColumnDefinitions>
+
+            <ContentPresenter Content="{TemplateBinding Content}" />
+
+            <Path Name="SortIcon"
+                  Grid.Column="1"
+                  Fill="{TemplateBinding Foreground}"
+                  HorizontalAlignment="Center"
+                  VerticalAlignment="Center"
+                  Stretch="Uniform"
+                  Height="12" />
+          </Grid>
+
+          <Rectangle Name="VerticalSeparator"
+                     Grid.Column="1"
+                     Width="1"
+                     VerticalAlignment="Stretch"
+                     Fill="{TemplateBinding SeparatorBrush}"
+                     IsVisible="{TemplateBinding AreSeparatorsVisible}" />
+
+          <Grid x:Name="FocusVisual"
+                IsHitTestVisible="False">
+            <Rectangle x:Name="FocusVisualPrimary"
+                       HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
+                       StrokeThickness="2" />
+            <Rectangle x:Name="FocusVisualSecondary"
+                       Margin="2"
+                       HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
+                       StrokeThickness="1" />
+          </Grid>
+        </Grid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridColumnHeader /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridColumnHeader:focus-visible /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
+    <Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundColor}" />
+  </Style>
+  <Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
+    <Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundColor}" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader:dragIndicator">
+    <Setter Property="Opacity" Value="0.5" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader /template/ Path#SortIcon">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader:sortascending /template/ Path#SortIcon">
+    <Setter Property="IsVisible" Value="True" />
+    <Setter Property="Data" Value="{StaticResource DataGridSortIconAscendingPath}" />
+  </Style>
+
+  <Style Selector="DataGridColumnHeader:sortdescending /template/ Path#SortIcon">
+    <Setter Property="IsVisible" Value="True" />
+    <Setter Property="Data" Value="{StaticResource DataGridSortIconDescendingPath}" />
+  </Style>
+
+  <Style Selector="DataGridRow">
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <DataGridFrozenGrid Name="PART_Root"
+                            RowDefinitions="*,Auto,Auto"
+                            ColumnDefinitions="Auto,*">
+
+          <Rectangle Name="BackgroundRectangle"
+                     Grid.RowSpan="2"
+                     Grid.ColumnSpan="2" />
+          <Rectangle x:Name="InvalidVisualElement"
+                     Grid.ColumnSpan="2"
+                     Fill="{DynamicResource DataGridRowInvalidBrush}" />
+
+          <DataGridRowHeader Name="PART_RowHeader"
+                             Grid.RowSpan="3"
+                             DataGridFrozenGrid.IsFrozen="True" />
+          <DataGridCellsPresenter Name="PART_CellsPresenter"
+                                  Grid.Column="1"
+                                  DataGridFrozenGrid.IsFrozen="True" />
+          <DataGridDetailsPresenter Name="PART_DetailsPresenter"
+                                    Grid.Row="1"
+                                    Grid.Column="1"
+                                    Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
+          <Rectangle Name="PART_BottomGridLine"
+                     Grid.Row="2"
+                     Grid.Column="1"
+                     HorizontalAlignment="Stretch"
+                     Height="1" />
+
+        </DataGridFrozenGrid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridRow /template/ Rectangle#InvalidVisualElement">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+  <Style Selector="DataGridRow:invalid /template/ Rectangle#InvalidVisualElement">
+    <Setter Property="Opacity" Value="0.4" />
+  </Style>
+  <Style Selector="DataGridRow:invalid /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+
+  <Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
+  </Style>
+  <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
+  </Style>
+  <Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
+  </Style>
+
+  <Style Selector="DataGridRowHeader">
+    <Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
+    <Setter Property="AreSeparatorsVisible" Value="False" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Grid x:Name="PART_Root"
+              RowDefinitions="*,*,Auto"
+              ColumnDefinitions="Auto,*">
+          <Border Grid.RowSpan="3"
+                  Grid.ColumnSpan="2"
+                  BorderBrush="{TemplateBinding SeparatorBrush}"
+                  BorderThickness="0,0,1,0">
+            <Grid Background="{TemplateBinding Background}">
+              <Rectangle x:Name="RowInvalidVisualElement"
+                         Fill="{DynamicResource DataGridRowInvalidBrush}"
+                         Stretch="Fill" />
+              <Rectangle x:Name="BackgroundRectangle"
+                         Stretch="Fill" />
+            </Grid>
+          </Border>
+          <Rectangle x:Name="HorizontalSeparator"
+                     Grid.Row="2"
+                     Grid.ColumnSpan="2"
+                     Height="1"
+                     Margin="1,0,1,0"
+                     HorizontalAlignment="Stretch"
+                     Fill="{TemplateBinding SeparatorBrush}"
+                     IsVisible="{TemplateBinding AreSeparatorsVisible}" />
+
+          <ContentPresenter Grid.RowSpan="2"
+                            Grid.Column="1"
+                            HorizontalAlignment="Center"
+                            VerticalAlignment="Center"
+                            Content="{TemplateBinding Content}" />
+        </Grid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridRowHeader /template/ Rectangle#RowInvalidVisualElement">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+  <Style Selector="DataGridRowHeader:invalid /template/ Rectangle#RowInvalidVisualElement">
+    <Setter Property="Opacity" Value="0.4" />
+  </Style>
+  <Style Selector="DataGridRowHeader:invalid /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Opacity" Value="0" />
+  </Style>
+
+  <Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
+  </Style>
+  <Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
+  </Style>
+  <Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
+  </Style>
+  <Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
+    <Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
+    <Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader">
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
+    <Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
+    <Setter Property="FontSize" Value="15" />
+    <Setter Property="MinHeight" Value="32" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <DataGridFrozenGrid Name="PART_Root"
+                            MinHeight="{TemplateBinding MinHeight}"
+                            ColumnDefinitions="Auto,Auto,Auto,Auto,*"
+                            RowDefinitions="*,Auto">
+
+          <Rectangle Name="IndentSpacer"
+                     Grid.Column="1" />
+          <ToggleButton Name="ExpanderButton"
+                        Grid.Column="2"
+                        Width="12"
+                        Height="12"
+                        Margin="12,0,0,0"
+                        Background="{TemplateBinding Background}"
+                        Foreground="{TemplateBinding Foreground}"
+                        Focusable="False" />
+
+          <StackPanel Grid.Column="3"
+                      Orientation="Horizontal"
+                      VerticalAlignment="Center"
+                      Margin="12,0,0,0">
+            <TextBlock Name="PropertyNameElement"
+                       Margin="4,0,0,0"
+                       IsVisible="{TemplateBinding IsPropertyNameVisible}"
+                       Foreground="{TemplateBinding Foreground}" />
+            <TextBlock Margin="4,0,0,0"
+                       Text="{Binding Key}"
+                       Foreground="{TemplateBinding Foreground}" />
+            <TextBlock Name="ItemCountElement"
+                       Margin="4,0,0,0"
+                       IsVisible="{TemplateBinding IsItemCountVisible}"
+                       Foreground="{TemplateBinding Foreground}" />
+          </StackPanel>
+
+          <Rectangle x:Name="CurrencyVisual"
+                     Grid.ColumnSpan="5"
+                     HorizontalAlignment="Stretch"
+                     VerticalAlignment="Stretch"
+                     Fill="Transparent"
+                     IsHitTestVisible="False"
+                     Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
+                     StrokeThickness="1" />
+          <Grid x:Name="FocusVisual"
+                Grid.ColumnSpan="5"
+                IsHitTestVisible="False">
+            <Rectangle HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
+                       StrokeThickness="2" />
+            <Rectangle Margin="2"
+                       HorizontalAlignment="Stretch"
+                       VerticalAlignment="Stretch"
+                       Fill="Transparent"
+                       IsHitTestVisible="False"
+                       Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
+                       StrokeThickness="1" />
+          </Grid>
+
+          <DataGridRowHeader Name="PART_RowHeader"
+                             Grid.RowSpan="2"
+                             DataGridFrozenGrid.IsFrozen="True" />
+
+          <Rectangle x:Name="PART_BottomGridLine"
+                     Grid.Row="1"
+                     Grid.ColumnSpan="5"
+                     Height="1" />
+        </DataGridFrozenGrid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton">
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Grid.Column="0"
+                Width="12"
+                Height="12"
+                Background="Transparent"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center">
+          <Path Fill="{TemplateBinding Foreground}"
+                HorizontalAlignment="Right"
+                VerticalAlignment="Center"
+                Stretch="Uniform" />
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton /template/ Path">
+    <Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
+    <Setter Property="Stretch" Value="Uniform" />
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton:checked /template/ Path">
+    <Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
+    <Setter Property="Stretch" Value="UniformToFill" />
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader /template/ DataGridFrozenGrid#PART_Root">
+    <Setter Property="Background" Value="{Binding $parent[DataGridRowGroupHeader].Background}" />
+  </Style>
+  <Style Selector="DataGridRowGroupHeader:pointerover /template/ DataGridFrozenGrid#PART_Root">
+    <Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderHoveredBackgroundBrush}" />
+  </Style>
+  <Style Selector="DataGridRowGroupHeader:pressed /template/ DataGridFrozenGrid#PART_Root">
+    <Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderPressedBackgroundBrush}" />
+  </Style>
+
+  <Style Selector="DataGridRowGroupHeader /template/ Rectangle#CurrencyVisual">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridRowGroupHeader /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+  <Style Selector="DataGridRowGroupHeader:current /template/ Rectangle#CurrencyVisual">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+  <Style Selector="DataGrid:focus DataGridRowGroupHeader:current /template/ Grid#FocusVisual">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+
+  <Style Selector="DataGrid">
+    <Setter Property="RowBackground" Value="Transparent" />
+    <Setter Property="AlternatingRowBackground" Value="Transparent" />
+    <Setter Property="HeadersVisibility" Value="Column" />
+    <Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
+    <Setter Property="VerticalScrollBarVisibility" Value="Auto" />
+    <Setter Property="SelectionMode" Value="Extended" />
+    <Setter Property="GridLinesVisibility" Value="None" />
+    <Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
+    <Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
+    <Setter Property="DropLocationIndicatorTemplate">
+      <Template>
+        <Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
+                   Width="2" />
+      </Template>
+    </Setter>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Background="{TemplateBinding Background}"
+                BorderThickness="{TemplateBinding BorderThickness}"
+                BorderBrush="{TemplateBinding BorderBrush}">
+          <Grid RowDefinitions="Auto,*,Auto,Auto"
+                ColumnDefinitions="Auto,*,Auto">
+            <Grid.Resources>
+              <ControlTemplate x:Key="TopLeftHeaderTemplate"
+                               TargetType="DataGridColumnHeader">
+                <Grid x:Name="TopLeftHeaderRoot"
+                      RowDefinitions="*,*,Auto">
+                  <Border Grid.RowSpan="2"
+                          BorderThickness="0,0,1,0"
+                          BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
+                  <Rectangle Grid.RowSpan="2"
+                             VerticalAlignment="Bottom"
+                             StrokeThickness="1"
+                             Height="1"
+                             Fill="{DynamicResource DataGridGridLinesBrush}" />
+                </Grid>
+              </ControlTemplate>
+              <ControlTemplate x:Key="TopRightHeaderTemplate"
+                               TargetType="DataGridColumnHeader">
+                <Grid x:Name="RootElement" />
+              </ControlTemplate>
+            </Grid.Resources>
+
+            <DataGridColumnHeader Name="PART_TopLeftCornerHeader"
+                                  Template="{StaticResource TopLeftHeaderTemplate}" />
+            <DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
+                                            Grid.Column="1"
+                                            Grid.ColumnSpan="2" />
+            <!--<DataGridColumnHeader Name="PART_TopRightCornerHeader"
+                                  Grid.Column="2"
+                                  Template="{StaticResource TopRightHeaderTemplate}" />-->
+            <!--<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
+                       Grid.ColumnSpan="3"
+                       VerticalAlignment="Bottom"
+                       StrokeThickness="1"
+                       Height="1"
+                       Fill="{DynamicResource DataGridGridLinesBrush}" />-->
+            <Border Name="PART_ColumnHeadersAndRowsSeparator"
+                    Grid.ColumnSpan="3"
+                    Height="2"
+                    VerticalAlignment="Bottom"
+                    BorderThickness="0,0,0,1"
+                    BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
+
+            <DataGridRowsPresenter Name="PART_RowsPresenter"
+                                   Grid.Row="1"
+                                   Grid.RowSpan="2"
+                                   Grid.ColumnSpan="3" />
+            <Rectangle Name="PART_BottomRightCorner"
+                       Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
+                       Grid.Column="2"
+                       Grid.Row="2" />
+            <!--<Rectangle Name="BottomLeftCorner"
+                       Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
+                       Grid.Row="2"
+                       Grid.ColumnSpan="2" />-->
+            <ScrollBar Name="PART_VerticalScrollbar"
+                       Orientation="Vertical"
+                       Grid.Column="2"
+                       Grid.Row="1"
+                       Width="{DynamicResource ScrollBarSize}" />
+
+            <Grid Grid.Column="1"
+                  Grid.Row="2"
+                  ColumnDefinitions="Auto,*">
+              <Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
+              <ScrollBar Name="PART_HorizontalScrollbar"
+                         Grid.Column="1"
+                         Orientation="Horizontal"
+                         Height="{DynamicResource ScrollBarSize}" />
+            </Grid>
+            <Border x:Name="PART_DisabledVisualElement"
+                    Grid.ColumnSpan="3"
+                    Grid.RowSpan="4"
+                    IsHitTestVisible="False"
+                    HorizontalAlignment="Stretch"
+                    VerticalAlignment="Stretch"
+                    CornerRadius="2"
+                    Background="{DynamicResource DataGridDisabledVisualElementBackground}"
+                    IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
+          </Grid>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+</Styles>

+ 17 - 0
src/Avalonia.Controls/AppBuilderBase.cs

@@ -88,6 +88,23 @@ namespace Avalonia.Controls
             };
         }
 
+        /// <summary>
+        /// Begin configuring an <see cref="Application"/>.
+        /// </summary>
+        /// <param name="appFactory">Factory function for <typeparamref name="TApp"/>.</param>
+        /// <typeparam name="TApp">The subclass of <see cref="Application"/> to configure.</typeparam>
+        /// <remarks><paramref name="appFactory"/> is useful for passing of dependencies to <typeparamref name="TApp"/>.</remarks>
+        /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
+        public static TAppBuilder Configure<TApp>(Func<TApp> appFactory)
+            where TApp : Application
+        {
+            return new TAppBuilder()
+            {
+                ApplicationType = typeof(TApp),
+                _appFactory = appFactory
+            };
+        }
+
         protected TAppBuilder Self => (TAppBuilder)this;
 
         public TAppBuilder AfterSetup(Action<TAppBuilder> callback)

+ 12 - 10
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -257,15 +257,7 @@ namespace Avalonia.Controls.Presenters
                 return base.ArrangeOverride(finalSize);
             }
 
-            try
-            {
-                _arranging = true;
-                return ArrangeWithAnchoring(finalSize);
-            }
-            finally
-            {
-                _arranging = false;
-            }
+            return ArrangeWithAnchoring(finalSize);
         }
 
         private Size ArrangeWithAnchoring(Size finalSize)
@@ -316,7 +308,17 @@ namespace Avalonia.Controls.Presenters
                 }
 
                 Extent = newExtent;
-                Offset = newOffset;
+
+                try
+                {
+                    _arranging = true;
+                    Offset = newOffset;
+                }
+                finally
+                {
+                    _arranging = false;    
+                }
+                
                 ArrangeOverrideImpl(size, -Offset);
             }
 

+ 105 - 21
src/Avalonia.Controls/TextBox.cs

@@ -18,6 +18,15 @@ namespace Avalonia.Controls
 {
     public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
     {
+        public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current
+            .GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
+
+        public static KeyGesture CopyGesture { get; } = AvaloniaLocator.Current
+            .GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault();
+
+        public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current
+            .GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
+        
         public static readonly StyledProperty<bool> AcceptsReturnProperty =
             AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
 
@@ -103,6 +112,21 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<bool> RevealPasswordProperty =
             AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
+        
+        public static readonly DirectProperty<TextBox, bool> CanCutProperty =
+                    AvaloniaProperty.RegisterDirect<TextBox, bool>(
+                        nameof(CanCut),
+                        o => o.CanCut);
+
+        public static readonly DirectProperty<TextBox, bool> CanCopyProperty =
+            AvaloniaProperty.RegisterDirect<TextBox, bool>(
+                nameof(CanCopy),
+                o => o.CanCopy);
+
+        public static readonly DirectProperty<TextBox, bool> CanPasteProperty =
+                    AvaloniaProperty.RegisterDirect<TextBox, bool>(
+                        nameof(CanPaste),
+                        o => o.CanPaste);
 
         struct UndoRedoState : IEquatable<UndoRedoState>
         {
@@ -126,6 +150,9 @@ namespace Avalonia.Controls
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
         private bool _isUndoingRedoing;
         private bool _ignoreTextChanges;
+        private bool _canCut;
+        private bool _canCopy;
+        private bool _canPaste;
         private string _newLine = Environment.NewLine;
         private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
 
@@ -369,6 +396,41 @@ namespace Avalonia.Controls
             get { return _newLine; }
             set { SetAndRaise(NewLineProperty, ref _newLine, value); }
         }
+        
+        /// <summary>
+        /// Clears the current selection, maintaining the <see cref="CaretIndex"/>
+        /// </summary>
+        public void ClearSelection()
+        {
+            SelectionStart = SelectionEnd = CaretIndex;
+        }
+
+        /// <summary>
+        /// Property for determining if the Cut command can be executed.
+        /// </summary>
+        public bool CanCut
+        {
+            get { return _canCut; }
+            private set { SetAndRaise(CanCutProperty, ref _canCut, value); }
+        }
+
+        /// <summary>
+        /// Property for determining if the Copy command can be executed.
+        /// </summary>
+        public bool CanCopy
+        {
+            get { return _canCopy; }
+            private set { SetAndRaise(CanCopyProperty, ref _canCopy, value); }
+        }
+
+        /// <summary>
+        /// Property for determining if the Paste command can be executed.
+        /// </summary>
+        public bool CanPaste
+        {
+            get { return _canPaste; }
+            private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); }
+        }
 
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
@@ -387,9 +449,19 @@ namespace Avalonia.Controls
             if (change.Property == TextProperty)
             {
                 UpdatePseudoclasses();
+                UpdateCommandStates();
             }
         }
 
+        private void UpdateCommandStates()
+        {
+            var text = GetSelection();
+            var isSelectionNullOrEmpty = string.IsNullOrEmpty(text);
+            CanCopy = !IsPasswordBox && !isSelectionNullOrEmpty;
+            CanCut = !IsPasswordBox && !isSelectionNullOrEmpty && !IsReadOnly;
+            CanPaste = !IsReadOnly;
+        }
+
         protected override void OnGotFocus(GotFocusEventArgs e)
         {
             base.OnGotFocus(e);
@@ -404,6 +476,8 @@ namespace Avalonia.Controls
                 SelectAll();
             }
 
+            UpdateCommandStates();
+
             _presenter?.ShowCaret();
         }
 
@@ -413,11 +487,12 @@ namespace Avalonia.Controls
 
             if (ContextMenu == null || !ContextMenu.IsOpen)
             {
-                SelectionStart = 0;
-                SelectionEnd = 0;
+                ClearSelection();
                 RevealPassword = false;
             }
-            
+
+            UpdateCommandStates();
+
             _presenter?.HideCaret();
         }
 
@@ -444,7 +519,7 @@ namespace Avalonia.Controls
                     text = Text ?? string.Empty;
                     SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
                     CaretIndex += input.Length;
-                    SelectionStart = SelectionEnd = CaretIndex;
+                    ClearSelection();
                     _undoRedoHelper.DiscardRedo();
                 }
             }
@@ -460,19 +535,31 @@ namespace Avalonia.Controls
             return text;
         }
 
-        private async void Copy()
+        public async void Cut()
         {
+            var text = GetSelection();
+            if (text is null) return;
+
+            _undoRedoHelper.Snapshot();
+            Copy();
+            DeleteSelection();
+            _undoRedoHelper.Snapshot();
+        }
+
+        public async void Copy()
+        {
+            var text = GetSelection();
+            if (text is null) return;
+
             await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))
-                .SetTextAsync(GetSelection());
+                .SetTextAsync(text);
         }
 
-        private async void Paste()
+        public async void Paste()
         {
             var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
-            if (text == null)
-            {
-                return;
-            }
+
+            if (text is null) return;
 
             _undoRedoHelper.Snapshot();
             HandleTextInput(text);
@@ -511,23 +598,18 @@ namespace Avalonia.Controls
             {
                 if (!IsPasswordBox)
                 {
-                    _undoRedoHelper.Snapshot();
-                    Copy();
-                    DeleteSelection();
-                    _undoRedoHelper.Snapshot();
+                    Cut();
                 }
 
                 handled = true;
             }
             else if (Match(keymap.Paste))
             {
-
                 Paste();
                 handled = true;
             }
             else if (Match(keymap.Undo))
             {
-
                 try
                 {
                     _isUndoingRedoing = true;
@@ -662,7 +744,7 @@ namespace Avalonia.Controls
                             SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
                                             text.Substring(caretIndex));
                             CaretIndex -= removedCharacters;
-                            SelectionStart = SelectionEnd = CaretIndex;
+                            ClearSelection();
                         }
                         _undoRedoHelper.Snapshot();
 
@@ -735,7 +817,7 @@ namespace Avalonia.Controls
             }
             else if (movement)
             {
-                SelectionStart = SelectionEnd = CaretIndex;
+                ClearSelection();
             }
 
             if (handled || movement)
@@ -1042,7 +1124,8 @@ namespace Avalonia.Controls
                     var end = Math.Max(selectionStart, selectionEnd);
                     var text = Text;
                     SetTextInternal(text.Substring(0, start) + text.Substring(end));
-                    SelectionStart = SelectionEnd = CaretIndex = start;
+                    CaretIndex = start;
+                    ClearSelection();
                     return true;
                 }
                 else
@@ -1131,7 +1214,8 @@ namespace Avalonia.Controls
             set
             {
                 Text = value.Text;
-                SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition;
+                CaretIndex = value.CaretPosition;
+                ClearSelection();
             }
         }
     }

+ 1 - 1
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@@ -141,7 +141,7 @@ namespace Avalonia.Controls.Utils
             var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
             if (radiusX != 0 || radiusY != 0)
             {
-                context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
+                context.ArcTo(keypoints.RightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
             }
 
             // Right

+ 14 - 2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@@ -18,12 +18,13 @@ namespace Avalonia.Diagnostics.ViewModels
         private int _selectedTab;
         private string _focusedControl;
         private string _pointerOverElement;
+        private bool _shouldVisualizeMarginPadding = true;
 
         public MainViewModel(IControl root)
         {
             _root = root;
-            _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
-            _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
+            _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
+            _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
             _events = new EventsPageViewModel(root);
 
             UpdateFocusedControl();
@@ -34,6 +35,17 @@ namespace Avalonia.Diagnostics.ViewModels
             Console = new ConsoleViewModel(UpdateConsoleContext);
         }
 
+        public bool ShouldVisualizeMarginPadding
+        {
+            get => _shouldVisualizeMarginPadding;
+            set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value);
+        }
+
+        public void ToggleVisualizeMarginPadding()
+        {
+            ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding;
+        }
+
         public ConsoleViewModel Console { get; }
 
         public ViewModelBase Content

+ 6 - 2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Controls;
+using Avalonia.Controls.Selection;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Diagnostics.ViewModels
@@ -10,10 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
         private ControlDetailsViewModel _details;
         private string _propertyFilter;
 
-        public TreePageViewModel(TreeNode[] nodes)
+        public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
         {
+            MainView = mainView;
             Nodes = nodes;
-       }
+        }
+
+        public MainViewModel MainView { get; }
 
         public TreeNode[] Nodes { get; protected set; }
 

+ 9 - 0
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@@ -16,6 +16,15 @@
           </MenuItem.Icon>
         </MenuItem>
       </MenuItem>
+      <MenuItem Header="_Options">
+        <MenuItem Header="Visualize margin/padding" Command="{Binding ToggleVisualizeMarginPadding}">
+          <MenuItem.Icon>
+            <CheckBox BorderThickness="0"
+                      IsChecked="{Binding ShouldVisualizeMarginPadding}"
+                      IsEnabled="False"/>
+          </MenuItem.Icon>
+        </MenuItem>
+      </MenuItem>
     </Menu>
     
     <TabStrip Grid.Row="1" SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">

+ 52 - 19
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@@ -1,7 +1,7 @@
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Shapes;
 using Avalonia.Diagnostics.ViewModels;
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
@@ -11,45 +11,78 @@ namespace Avalonia.Diagnostics.Views
 {
     internal class TreePageView : UserControl
     {
-        private Control _adorner;
+        private readonly Panel _adorner;
+        private AdornerLayer _currentLayer;
         private TreeView _tree;
 
         public TreePageView()
         {
-            this.InitializeComponent();
+            InitializeComponent();
             _tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
+
+            _adorner = new Panel
+            {
+                ClipToBounds = false,
+                Children =
+                {
+                    //Padding frame
+                    new Border { BorderBrush = new SolidColorBrush(Colors.Green, 0.5) },
+                    //Content frame
+                    new Border { Background = new SolidColorBrush(Color.FromRgb(160, 197, 232), 0.5) },
+                    //Margin frame
+                    new Border { BorderBrush = new SolidColorBrush(Colors.Yellow, 0.5) }
+                },
+            };
         }
 
         protected void AddAdorner(object sender, PointerEventArgs e)
         {
             var node = (TreeNode)((Control)sender).DataContext;
-            var layer = AdornerLayer.GetAdornerLayer(node.Visual);
+            var visual = (Visual)node.Visual;
+
+            _currentLayer = AdornerLayer.GetAdornerLayer(visual);
 
-            if (layer != null)
+            if (_currentLayer == null ||
+                _currentLayer.Children.Contains(_adorner))
             {
-                if (_adorner != null)
-                {
-                    ((Panel)_adorner.Parent).Children.Remove(_adorner);
-                    _adorner = null;
-                }
+                return;
+            }
 
-                _adorner = new Rectangle
-                {
-                    Fill = new SolidColorBrush(0x80a0c5e8),
-                    [AdornerLayer.AdornedElementProperty] = node.Visual,
-                };
+            _currentLayer.Children.Add(_adorner);
+            AdornerLayer.SetAdornedElement(_adorner, visual);
+
+            var vm = (TreePageViewModel) DataContext;
 
-                layer.Children.Add(_adorner);
+            if (vm.MainView.ShouldVisualizeMarginPadding)
+            {
+                var paddingBorder = (Border)_adorner.Children[0];
+                paddingBorder.BorderThickness = visual.GetValue(PaddingProperty);
+
+                var contentBorder = (Border)_adorner.Children[1];
+                contentBorder.Margin = visual.GetValue(PaddingProperty);
+
+                var marginBorder = (Border)_adorner.Children[2];
+                marginBorder.BorderThickness = visual.GetValue(MarginProperty);
+                marginBorder.Margin = InvertThickness(visual.GetValue(MarginProperty));
             }
         }
 
+        private static Thickness InvertThickness(Thickness input)
+        {
+            return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom);
+        }
+
         protected void RemoveAdorner(object sender, PointerEventArgs e)
         {
-            if (_adorner != null)
+            foreach (var border in _adorner.Children.OfType<Border>())
             {
-                ((Panel)_adorner.Parent).Children.Remove(_adorner);
-                _adorner = null;
+                border.Margin = default;
+                border.Padding = default;
+                border.BorderThickness = default;
             }
+
+            _currentLayer?.Children.Remove(_adorner);
+            _currentLayer = null;
         }
 
         private void InitializeComponent()

+ 13 - 4
src/Avalonia.Native/AvaloniaNativePlatform.cs

@@ -110,11 +110,20 @@ namespace Avalonia.Native
                 .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
                 .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
                 .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
-                .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
-                ;
+                .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory));
+
             if (_options.UseGpu)
-                AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
-                    .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
+            {
+                try
+                {
+                    AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
+                        .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
+                }
+                catch (Exception)
+                {
+                    // ignored
+                }
+            }
         }
 
         public IWindowImpl CreateWindow()

+ 16 - 11
src/Avalonia.Native/ScreenImpl.cs

@@ -20,21 +20,26 @@ namespace Avalonia.Native
         {
             get
             {
-                var count = ScreenCount;
-                var result = new Screen[count];
-
-                for(int i = 0; i < count; i++)
+                if (_native != null)
                 {
-                    var screen = _native.GetScreen(i);
+                    var count = ScreenCount;
+                    var result = new Screen[count];
+
+                    for (int i = 0; i < count; i++)
+                    {
+                        var screen = _native.GetScreen(i);
+
+                        result[i] = new Screen(
+                            screen.PixelDensity,
+                            screen.Bounds.ToAvaloniaPixelRect(),
+                            screen.WorkingArea.ToAvaloniaPixelRect(),
+                            screen.Primary);
+                    }
 
-                    result[i] = new Screen(
-                        screen.PixelDensity,
-                        screen.Bounds.ToAvaloniaPixelRect(),
-                        screen.WorkingArea.ToAvaloniaPixelRect(),
-                        screen.Primary);
+                    return result;
                 }
 
-                return result;
+                return Array.Empty<Screen>();
             }
         }
 

+ 10 - 5
src/Avalonia.Native/WindowImplBase.cs

@@ -94,8 +94,13 @@ namespace Avalonia.Native
         {
             get
             {
-                var s = _native.GetClientSize();
-                return new Size(s.Width, s.Height);
+                if (_native != null)
+                {
+                    var s = _native.GetClientSize();
+                    return new Size(s.Width, s.Height);
+                }
+
+                return default;
             }
         }
 
@@ -144,7 +149,6 @@ namespace Avalonia.Native
             void IAvnWindowBaseEvents.Closed()
             {
                 var n = _parent._native;
-                _parent._native = null;
                 try
                 {
                     _parent?.Closed?.Invoke();
@@ -153,6 +157,7 @@ namespace Avalonia.Native
                 {
                     n?.Dispose();
                 }
+                
                 _parent._mouse.Dispose();
             }
 
@@ -351,12 +356,12 @@ namespace Avalonia.Native
 
         public Point PointToClient(PixelPoint point)
         {
-            return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
+            return _native?.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint() ?? default;
         }
 
         public PixelPoint PointToScreen(Point point)
         {
-            return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
+            return _native?.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint() ?? default;
         }
 
         public void Hide()

+ 1 - 1
src/Avalonia.Themes.Default/Expander.xaml

@@ -86,7 +86,7 @@
   <Style Selector="Expander /template/ ToggleButton#PART_toggle">
     <Setter Property="Template">
       <ControlTemplate>
-        <Border BorderThickness="1">
+        <Border BorderThickness="1" Background="Transparent">
           <Grid ColumnDefinitions="Auto,Auto">
             <Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
               <Path Fill="{DynamicResource ThemeForegroundBrush}"

+ 9 - 0
src/Avalonia.Themes.Default/TextBox.xaml

@@ -1,11 +1,20 @@
 <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Styles.Resources>
+    <ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
+      <MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
+      <MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
+      <MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
+    </ContextMenu>
+  </Styles.Resources>
   <Style Selector="TextBox">
+    <Setter Property="CaretBrush" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
     <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
     <Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
     <Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
     <Setter Property="Padding" Value="4"/>
+    <Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
     <Setter Property="Template">
       <ControlTemplate>
         <Border Name="border"

+ 2 - 2
src/Avalonia.Themes.Fluent/Expander.xaml

@@ -86,10 +86,10 @@
   <Style Selector="Expander /template/ ToggleButton#PART_toggle">
     <Setter Property="Template">
       <ControlTemplate>
-        <Border BorderThickness="1">
+        <Border BorderThickness="1" Background="Transparent">
           <Grid ColumnDefinitions="Auto,Auto">
             <Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
-              <Path Fill="{DynamicResource SystemControlForegroundAltMediumHighBrush}"
+              <Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Data="M 0 2 L 4 6 L 0 10 Z" />

+ 6 - 0
src/Avalonia.Themes.Fluent/TextBox.xaml

@@ -14,6 +14,11 @@
     <StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
     <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
     <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
+    <ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
+      <MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
+      <MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
+      <MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
+    </ContextMenu>
   </Styles.Resources>
 
   <Style Selector="TextBox">
@@ -28,6 +33,7 @@
     <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
     <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
     <Setter Property="FocusAdorner" Value="{x:Null}" />
+    <Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
     <Setter Property="Template">
       <ControlTemplate>
         <DockPanel>

+ 1 - 3
src/Avalonia.Visuals/Media/GlyphRun.cs

@@ -555,7 +555,7 @@ namespace Avalonia.Media
                 }
             }
 
-            return new Rect(0, 0, width, height);
+            return new Rect(0, GlyphTypeface.Ascent * Scale, width, height);
         }
 
         private void Set<T>(ref T field, T value)
@@ -595,8 +595,6 @@ namespace Avalonia.Media
             _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
 
             var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
-
-            _bounds = new Rect(0, 0, width, height);
         }
 
         void IDisposable.Dispose()

+ 16 - 0
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@@ -181,6 +181,17 @@ namespace Avalonia.Media.TextFormatting
                 return nextCharacterHit;
             }
 
+            if (characterHit.FirstCharacterIndex + characterHit.TrailingLength <= TextRange.Start + TextRange.Length)
+            {
+                return characterHit; // Can't move, we're after the last character
+            }
+
+            var runIndex = GetRunIndexAtCodepointIndex(TextRange.End);
+
+            var textRun = _textRuns[runIndex];
+
+            characterHit = textRun.GlyphRun.GetNextCaretCharacterHit(characterHit);
+
             return characterHit; // Can't move, we're after the last character
         }
 
@@ -192,6 +203,11 @@ namespace Avalonia.Media.TextFormatting
                 return previousCharacterHit;
             }
 
+            if (characterHit.FirstCharacterIndex < TextRange.Start)
+            {
+                characterHit = new CharacterHit(TextRange.Start);
+            }
+
             return characterHit; // Can't move, we're before the first character
         }
 

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
             GlyphRun glyphRun,
             Point baselineOrigin,
             IDictionary<IVisual, Scene> childScenes = null)
-            : base(glyphRun.Bounds, transform)
+            : base(glyphRun.Bounds.Translate(baselineOrigin), transform)
         {
             Transform = transform;
             Foreground = foreground?.ToImmutable();

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@@ -24,7 +24,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             {
                 AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
                 AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
-                bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate);
 
                 for (int i = 0; i < on.Children.Count; ++i)
                 {
@@ -57,7 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                         {
                             inferredDataContextTypeNode = ParseDataContext(context, on, obj);
                         }
-                        else if(isDataTemplate
+                        else if(context.GetAvaloniaTypes().DataTemplate.IsAssignableFrom(on.Type.GetClrType())
                             && pa.Property.Name == "DataType"
                             && pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
                         {
@@ -70,7 +69,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 // do more specialized inference
                 if (directiveDataContextTypeNode is null)
                 {
-                    if (isDataTemplate && inferredDataContextTypeNode is null)
+                    if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType())
+                        && inferredDataContextTypeNode is null)
                     {
                         // Infer data type from collection binding on a control that displays items.
                         var parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlType ResolveByNameExtension { get; }
 
         public IXamlType DataTemplate { get; }
+        public IXamlType IDataTemplate { get; }
         public IXamlType IItemsPresenterHost { get; }
         public IXamlType ItemsRepeater { get; }
         public IXamlType ReflectionBindingExtension { get; }
@@ -98,6 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
             ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
             DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
+            IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate");
             IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
             ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater");
             ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");

+ 4 - 0
src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt

@@ -0,0 +1,4 @@
+Compat issues with assembly Avalonia.Markup.Xaml:
+MembersMustExist : Member 'public Avalonia.Data.Binding Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.get()' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public void Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.set(Avalonia.Data.Binding)' does not exist in the implementation but it does exist in the contract.
+Total Issues: 2

+ 10 - 2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@@ -2,7 +2,9 @@ using System;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
+using Avalonia.Data.Core;
 using Avalonia.Markup.Parsers;
+using Avalonia.Markup.Xaml.MarkupExtensions;
 using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.Templates
@@ -16,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Templates
         public object Content { get; set; }
 
         [AssignBinding]
-        public Binding ItemsSource { get; set; }
+        public BindingBase ItemsSource { get; set; }
 
         public bool Match(object data)
         {
@@ -34,7 +36,13 @@ namespace Avalonia.Markup.Xaml.Templates
         {
             if (ItemsSource != null)
             {
-                var obs = ExpressionObserverBuilder.Build(item, ItemsSource.Path);
+                var obs = ItemsSource switch
+                {
+                    Binding reflection => ExpressionObserverBuilder.Build(item, reflection.Path),
+                    CompiledBindingExtension compiled => new ExpressionObserver(item, compiled.Path.BuildExpression(false)),
+                    _ => throw new InvalidOperationException("TreeDataTemplate currently only supports Binding and CompiledBindingExtension!")
+                };
+
                 return InstancedBinding.OneWay(obs, BindingPriority.Style);
             }
 

+ 39 - 2
tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs

@@ -1,4 +1,5 @@
-using Avalonia.Controls.UnitTests;
+using System;
+using Avalonia.Controls.UnitTests;
 using Avalonia.Platform;
 using Xunit;
 
@@ -18,6 +19,19 @@ namespace Avalonia.Controls.UnitTests
         {
         }
 
+        public class AppWithDependencies : Application
+        {
+            public AppWithDependencies(object dependencyA, object dependencyB)
+            {
+                DependencyA = dependencyA;
+                DependencyB = dependencyB;
+            }
+
+            public object DependencyA { get; }
+
+            public object DependencyB { get; }
+        }
+
         public class DefaultModule
         {
             public static bool IsLoaded = false;
@@ -53,7 +67,30 @@ namespace Avalonia.Controls.UnitTests
                 IsLoaded = true;
             }
         }
-        
+
+        [Fact]
+        public void UseAppFactory()
+        {
+            using (AvaloniaLocator.EnterScope())
+            {
+                ResetModuleLoadStates();
+
+                Func<AppWithDependencies> appFactory = () => new AppWithDependencies(dependencyA: new object(), dependencyB: new object());
+
+                var builder = AppBuilder.Configure<AppWithDependencies>(appFactory)
+                    .UseWindowingSubsystem(() => { })
+                    .UseRenderingSubsystem(() => { })
+                    .UseAvaloniaModules()
+                    .SetupWithoutStarting();
+
+                AppWithDependencies app = (AppWithDependencies)builder.Instance;
+                Assert.NotNull(app.DependencyA);
+                Assert.NotNull(app.DependencyB);
+
+                Assert.True(DefaultModule.IsLoaded);
+            }
+        }
+
         [Fact]
         public void LoadsDefaultModule()
         {

+ 35 - 0
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@@ -562,6 +562,41 @@ namespace Avalonia.Controls.UnitTests
             }
         }
         
+        [Fact]
+        public void TextBox_CaretIndex_Persists_When_Focus_Lost()
+        {
+            using (UnitTestApplication.Start(FocusServices))
+            {
+                var target1 = new TextBox
+                {
+                    Template = CreateTemplate(),
+                    Text = "1234"
+                };
+                var target2 = new TextBox
+                {
+                    Template = CreateTemplate(),
+                    Text = "5678"
+                };
+                var sp = new StackPanel();
+                sp.Children.Add(target1);
+                sp.Children.Add(target2);
+
+                target1.ApplyTemplate();
+                target2.ApplyTemplate();
+                
+                var root = new TestRoot { Child = sp };
+
+                target2.Focus();
+                target2.CaretIndex = 2;
+                Assert.False(target1.IsFocused);
+                Assert.True(target2.IsFocused);
+
+                target1.Focus();
+                
+                Assert.Equal(2, target2.CaretIndex);
+            }
+        }
+        
         [Fact]
         public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
         {

+ 22 - 4
tests/Avalonia.LeakTests/ControlTests.cs

@@ -449,13 +449,22 @@ namespace Avalonia.LeakTests
 
                 Assert.Same(window, FocusManager.Instance.Current);
 
+                // Context menu in resources means the baseline may not be 0.
+                var initialMenuCount = 0;
+                var initialMenuItemCount = 0;
+                dotMemory.Check(memory =>
+                {
+                    initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
+                    initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
+                });
+                
                 AttachShowAndDetachContextMenu(window);
 
                 Mock.Get(window.PlatformImpl).ResetCalls();
                 dotMemory.Check(memory =>
-                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
+                    Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
                 dotMemory.Check(memory =>
-                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
+                    Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
             }
         }
 
@@ -484,14 +493,23 @@ namespace Avalonia.LeakTests
 
                 Assert.Same(window, FocusManager.Instance.Current);
 
+                // Context menu in resources means the baseline may not be 0.
+                var initialMenuCount = 0;
+                var initialMenuItemCount = 0;
+                dotMemory.Check(memory =>
+                {
+                    initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
+                    initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
+                });
+                
                 BuildAndShowContextMenu(window);
                 BuildAndShowContextMenu(window);
 
                 Mock.Get(window.PlatformImpl).ResetCalls();
                 dotMemory.Check(memory =>
-                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
+                    Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
                 dotMemory.Check(memory =>
-                    Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
+                    Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
             }
         }
 

+ 140 - 4
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs

@@ -1,5 +1,5 @@
-using System.Reactive.Subjects;
-using System.Windows.Input;
+using System;
+using System.ComponentModel;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.UnitTests;
@@ -56,7 +56,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
                 Assert.Equal("Called 5", vm.Value);
             }
         }
-        
+
         [Fact]
         public void Binding_Method_To_TextBlock_Text_Works()
         {
@@ -79,6 +79,111 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             }
         }
 
+
+        [Theory]
+        [InlineData(null, "Not called")]
+        [InlineData("A", "Do A")]
+        public void Binding_Method_With_Parameter_To_Command_CanExecute(object commandParameter, string result)
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Button Name='button' Command='{Binding Do}' CommandParameter='{Binding Parameter, Mode=OneTime}'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+                var vm = new ViewModel()
+                {
+                    Parameter = commandParameter
+                };
+
+                button.DataContext = vm;
+                window.ApplyTemplate();
+
+                Assert.NotNull(button.Command);
+                PerformClick(button);
+                Assert.Equal(vm.Value, result);
+            }
+        }
+
+        [Fact]
+        public void Binding_Method_With_Parameter_To_Command_CanExecute_DependsOn()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <Button Name='button' Command='{Binding Do}' CommandParameter='{Binding Parameter, Mode=OneWay}'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var button = window.FindControl<Button>("button");
+                var vm = new ViewModel()
+                {
+                    Parameter = null,
+                };
+
+                button.DataContext = vm;
+                window.ApplyTemplate();
+
+                Assert.NotNull(button.Command);
+
+                Assert.Equal(button.IsEffectivelyEnabled, false);
+
+                vm.Parameter = true;
+                Threading.Dispatcher.UIThread.RunJobs();
+
+                Assert.Equal(button.IsEffectivelyEnabled, true);
+            }
+        }
+
+        [Fact]
+        public void Binding_Method_To_Command_Collected()
+        {
+            WeakReference<ViewModel> MakeRef()
+            {
+                var weakVm = new WeakReference<ViewModel>(null);
+                {
+                    var vm = new ViewModel()
+                    {
+                        Parameter = null,
+                    };
+                    weakVm.SetTarget(vm);
+                    var canExecuteCount = 0;
+                    var action = new Action<object>(vm.Do);
+                    var command = new Avalonia.Data.Converters.MethodToCommandConverter(action);
+                    command.CanExecuteChanged += (s, e) => canExecuteCount++;
+                    vm.Parameter = 0;
+                    Threading.Dispatcher.UIThread.RunJobs();
+                    vm.Parameter = null;
+                    Threading.Dispatcher.UIThread.RunJobs();
+                    Assert.Equal(2, canExecuteCount);
+                }
+                return weakVm;
+            }
+            bool IsAlive(WeakReference<ViewModel> @ref)
+            {
+                return @ref.TryGetTarget(out var instance)
+                    && instance is null == false;
+            }
+
+            var vmref = MakeRef();
+
+            var beforeCollect = IsAlive(vmref);
+
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
+
+            var afterCollect = IsAlive(vmref);
+
+            Assert.True(beforeCollect, "Invalid ViewModel instance, it is already collected.");
+            Assert.False(afterCollect, "ViewModel instance was not collected");
+        }
+
         static void PerformClick(Button button)
         {
             button.RaiseEvent(new KeyEventArgs
@@ -88,12 +193,43 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             });
         }
 
-        private class ViewModel
+        private class ViewModel : INotifyPropertyChanged
         {
+            public event PropertyChangedEventHandler PropertyChanged;
+
             public string Method() => Value = "Called";
             public string Method1(int i) => Value = $"Called {i}";
             public string Method2(int i, int j) => Value = $"Called {i},{j}";
             public string Value { get; private set; } = "Not called";
+
+            object _parameter;
+            public object Parameter
+            {
+                get
+                {
+                    return _parameter;
+                }
+                set
+                {
+                    if (_parameter == value)
+                    {
+                        return;
+                    }
+                    _parameter = value;
+                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Parameter)));
+                }
+            }
+
+            public void Do(object parameter)
+            {
+                Value = $"Do {parameter}";
+            }
+
+            [Metadata.DependsOn(nameof(Parameter))]
+            public bool CanDo(object parameter)
+            {
+                return ReferenceEquals(null, parameter) == false;
+            }
         }
     }
 }

+ 59 - 1
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Media;
 using Avalonia.Media.TextFormatting;
@@ -10,6 +9,65 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
 {
     public class TextLineTests
     {
+        private static readonly string s_multiLineText = "012345678\r\r0123456789";
+
+        [Fact]
+        public void Should_Get_First_CharacterHit()
+        {
+            using (Start())
+            {
+                var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+                var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
+
+                var formatter = new TextFormatterImpl();
+
+                var currentIndex = 0;
+
+                while (currentIndex < s_multiLineText.Length)
+                {
+                    var textLine =
+                        formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
+                            new GenericTextParagraphProperties(defaultProperties));
+
+                    var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue));
+
+                    Assert.Equal(textLine.TextRange.Start, firstCharacterHit.FirstCharacterIndex);
+
+                    currentIndex += textLine.TextRange.Length;
+                }
+            }
+        }
+
+        [Fact]
+        public void Should_Get_Last_CharacterHit()
+        {
+            using (Start())
+            {
+                var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+                var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
+
+                var formatter = new TextFormatterImpl();
+
+                var currentIndex = 0;
+
+                while (currentIndex < s_multiLineText.Length)
+                {
+                    var textLine =
+                        formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
+                            new GenericTextParagraphProperties(defaultProperties));
+
+                    var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue));
+
+                    Assert.Equal(textLine.TextRange.Start + textLine.TextRange.Length,
+                        lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength);
+
+                    currentIndex += textLine.TextRange.Length;
+                }
+            }
+        }
+
         [InlineData("𐐷𐐷𐐷𐐷𐐷")]
         [InlineData("𐐷1234")]
         [Theory]