Browse Source

Merge branch 'master' into pointer-capture-transfer-fix

Steven Kirk 6 years ago
parent
commit
da1ff18a5d

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

@@ -125,9 +125,8 @@ namespace Avalonia.Controls
             });
             
             // Copy-pasted because we can't call extension methods due to generic constraints
-            var lifetime = new ClassicDesktopStyleApplicationLifetime(Instance) {ShutdownMode = ShutdownMode.OnMainWindowClose};
-            Instance.ApplicationLifetime = lifetime;
-            SetupWithoutStarting();
+            var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = ShutdownMode.OnMainWindowClose};
+            SetupWithLifetime(lifetime);
             lifetime.Start(Array.Empty<string>());
         }
 

+ 4 - 5
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -5,12 +5,12 @@ using System.Threading;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Interactivity;
+using Avalonia.Threading;
 
 namespace Avalonia.Controls.ApplicationLifetimes
 {
     public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
     {
-        private readonly Application _app;
         private int _exitCode;
         private CancellationTokenSource _cts;
         private bool _isShuttingDown;
@@ -34,12 +34,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
             _activeLifetime?._windows.Add((Window)sender);
         }
 
-        public ClassicDesktopStyleApplicationLifetime(Application app)
+        public ClassicDesktopStyleApplicationLifetime()
         {
             if (_activeLifetime != null)
                 throw new InvalidOperationException(
                     "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
-            _app = app;
             _activeLifetime = this;
         }
         
@@ -103,7 +102,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
             Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
             _cts = new CancellationTokenSource();
             MainWindow?.Show();
-            _app.Run(_cts.Token);
+            Dispatcher.UIThread.MainLoop(_cts.Token);
             Environment.ExitCode = _exitCode;
             return _exitCode;
         }
@@ -124,7 +123,7 @@ namespace Avalonia
             this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
             where T : AppBuilderBase<T>, new()
         {
-            var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
+            var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode};
             builder.SetupWithLifetime(lifetime);
             return lifetime.Start(args);
         }

+ 10 - 3
src/Avalonia.Controls/ColumnDefinition.cs

@@ -26,6 +26,16 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<GridLength> WidthProperty =
             AvaloniaProperty.Register<ColumnDefinition, GridLength>(nameof(Width), new GridLength(1, GridUnitType.Star));
 
+        /// <summary>
+        /// Initializes static members of the <see cref="ColumnDefinition"/> class.
+        /// </summary>
+        static ColumnDefinition()
+        {
+            AffectsParentMeasure(MinWidthProperty, MaxWidthProperty);
+
+            WidthProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ColumnDefinition"/> class.
         /// </summary>
@@ -68,7 +78,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MaxWidthProperty, value);
             }
         }
@@ -84,7 +93,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MinWidthProperty, value);
             }
         }
@@ -100,7 +108,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(WidthProperty, value);
             }
         }

+ 51 - 3
src/Avalonia.Controls/DefinitionBase.cs

@@ -7,9 +7,6 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics;
-
-using Avalonia;
-using Avalonia.Collections;
 using Avalonia.Utilities;
 
 namespace Avalonia.Controls
@@ -50,6 +47,8 @@ namespace Avalonia.Controls
                     }
                 }
             }
+
+            Parent?.InvalidateMeasure();
         }
 
         /// <summary>
@@ -63,6 +62,8 @@ namespace Avalonia.Controls
                 _sharedState.RemoveMember(this);
                 _sharedState = null;
             }
+
+            Parent?.InvalidateMeasure();
         }
 
         /// <summary>
@@ -114,6 +115,36 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <remarks>
+        /// Notifies parent <see cref="Grid"/> or size scope that definition size has been changed.
+        /// </remarks>
+        internal static void OnUserSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (definition.Parent == null)
+            {
+                return;
+            }
+
+            if (definition._sharedState != null)
+            {
+                definition._sharedState.Invalidate();
+            }
+            else
+            {
+                GridUnitType oldUnitType = ((GridLength)e.OldValue).GridUnitType;
+                GridUnitType newUnitType = ((GridLength)e.NewValue).GridUnitType;
+
+                if (oldUnitType != newUnitType)
+                {
+                    definition.Parent.Invalidate();
+                }
+                else
+                {
+                    definition.Parent.InvalidateMeasure();
+                }
+            }
+        }
+
         /// <summary>
         /// Returns <c>true</c> if this definition is a part of shared group.
         /// </summary>
@@ -730,5 +761,22 @@ namespace Avalonia.Controls
             SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
             PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged);
         }
+
+        /// <summary>
+        /// Marks a property on a definition as affecting the parent grid's measurement.
+        /// </summary>
+        /// <param name="properties">The properties.</param>
+        protected static void AffectsParentMeasure(params AvaloniaProperty[] properties)
+        {
+            void Invalidate(AvaloniaPropertyChangedEventArgs e)
+            {
+                (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure();
+            }
+
+            foreach (var property in properties)
+            {
+                property.Changed.Subscribe(Invalidate);
+            }
+        }
     }
 }

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

@@ -50,6 +50,7 @@ namespace Avalonia.Controls
         /// On desktop-style platforms runs the application's main loop with custom CancellationToken
         /// without setting a lifetime.
         /// </summary>
+        /// <param name="app">The application.</param>
         /// <param name="token">The token to track.</param>
         public static void Run(this Application app, CancellationToken token)
         {

+ 12 - 0
src/Avalonia.Controls/Grid.cs

@@ -6,6 +6,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Diagnostics;
 using System.Linq;
 using System.Threading;
@@ -178,6 +179,7 @@ namespace Avalonia.Controls
                 if (_data == null) { _data = new ExtendedData(); }
                 _data.ColumnDefinitions = value;
                 _data.ColumnDefinitions.Parent = this;
+                InvalidateMeasure();
             }
         }
 
@@ -198,6 +200,7 @@ namespace Avalonia.Controls
                 if (_data == null) { _data = new ExtendedData(); }
                 _data.RowDefinitions = value;
                 _data.RowDefinitions.Parent = this;
+                InvalidateMeasure();
             }
         }
 
@@ -569,6 +572,15 @@ namespace Avalonia.Controls
             return (arrangeSize);
         }
 
+        /// <summary>
+        /// <see cref="Panel.ChildrenChanged"/>
+        /// </summary>
+        protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            CellsStructureDirty = true;
+            base.ChildrenChanged(sender, e);
+        }
+
         /// <summary>
         /// Invalidates grid caches and makes the grid dirty for measure.
         /// </summary>

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

@@ -41,6 +41,8 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Initializes a new instance of the <see cref="PopupRoot"/> class.
         /// </summary>
+        /// <param name="parent">The popup parent.</param>
+        /// <param name="impl">The popup implementation.</param>
         /// <param name="dependencyResolver">
         /// The dependency resolver to use. If null the default dependency resolver will be used.
         /// </param>

+ 10 - 3
src/Avalonia.Controls/RowDefinition.cs

@@ -26,6 +26,16 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<GridLength> HeightProperty =
             AvaloniaProperty.Register<RowDefinition, GridLength>(nameof(Height), new GridLength(1, GridUnitType.Star));
 
+        /// <summary>
+        /// Initializes static members of the <see cref="RowDefinition"/> class.
+        /// </summary>
+        static RowDefinition()
+        {
+            AffectsParentMeasure(MaxHeightProperty, MinHeightProperty);
+
+            HeightProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="RowDefinition"/> class.
         /// </summary>
@@ -68,7 +78,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MaxHeightProperty, value);
             }
         }
@@ -84,7 +93,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(MinHeightProperty, value);
             }
         }
@@ -100,7 +108,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                Parent?.InvalidateMeasure();
                 SetValue(HeightProperty, value);
             }
         }

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

@@ -16,7 +16,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Set_ExitCode_After_Shutdown()
         {
             using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))    
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())    
             {
                 lifetime.Shutdown(1337);
 
@@ -31,7 +31,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() };
 
@@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Only_Exit_On_Explicit_Exit()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown;
 
@@ -84,7 +84,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Exit_After_MainWindow_Closed()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
 
@@ -112,7 +112,7 @@ namespace Avalonia.Controls.UnitTests
         public void Should_Exit_After_Last_Window_Closed()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose;
 
@@ -142,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
         public void Show_Should_Add_Window_To_OpenWindows()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
         public void Window_Should_Be_Added_To_OpenWindows_Only_Once()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
         public void Close_Should_Remove_Window_From_OpenWindows()
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 
@@ -197,7 +197,7 @@ namespace Avalonia.Controls.UnitTests
                 windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
 
             using (UnitTestApplication.Start(services))
-            using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var window = new Window();
 

+ 277 - 9
tests/Avalonia.Controls.UnitTests/GridTests.cs

@@ -1,11 +1,6 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
-using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.Platform;
-using Avalonia.UnitTests;
-
-using Moq;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -34,7 +29,6 @@ namespace Avalonia.Controls.UnitTests
 
         private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns)
         {
-
             var grid = new Grid();
             foreach (var k in columns.Select(c => new ColumnDefinition
             {
@@ -1178,6 +1172,41 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth);
         }
 
+        [Fact]
+        public void Size_Group_Definition_Resizes_Are_Tracked()
+        {
+            var grids = new[] {
+                CreateGrid(("A", new GridLength(5, GridUnitType.Pixel)), (null, new GridLength())),
+                CreateGrid(("A", new GridLength(5, GridUnitType.Pixel)), (null, new GridLength())) };
+            var scope = new Grid();
+            foreach (var xgrids in grids)
+                scope.Children.Add(xgrids);
+
+            var root = new Grid();
+            root.UseLayoutRounding = false;
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Children.Add(scope);
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+
+            PrintColumnDefinitions(grids[0]);
+            Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth);
+            Assert.Equal(5, grids[1].ColumnDefinitions[0].ActualWidth);
+
+            grids[0].ColumnDefinitions[0].Width = new GridLength(10, GridUnitType.Pixel);
+
+            foreach (Grid grid in grids)
+            {
+                grid.Measure(new Size(50, 50));
+                grid.Arrange(new Rect(new Point(), new Point(50, 50)));
+            }
+
+            PrintColumnDefinitions(grids[0]);
+            Assert.Equal(10, grids[0].ColumnDefinitions[0].ActualWidth);
+            Assert.Equal(10, grids[1].ColumnDefinitions[0].ActualWidth);
+        }
+
         [Fact]
         public void Collection_Changes_Are_Tracked()
         {
@@ -1270,11 +1299,11 @@ namespace Avalonia.Controls.UnitTests
             // grid.Measure(new Size(100, 100));
             // grid.Arrange(new Rect(new Point(), new Point(100, 100)));
             // PrintColumnDefinitions(grid);
-            
+
             // NOTE: THIS IS BROKEN IN WPF
             // all in group are equal to width (MinWidth) of the sizer in the second column
             // Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth));
-            
+
             // NOTE: THIS IS BROKEN IN WPF
             // grid.ColumnDefinitions[2].SharedSizeGroup = null;
 
@@ -1382,6 +1411,245 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Size(100, 100), grid.Bounds.Size);
         }
 
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_Width_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.WidthProperty] = new GridLength(5);
+                else
+                    grid.ColumnDefinitions[0].Width = new GridLength(5);
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_MinWidth_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.MinWidthProperty] = 5;
+                else
+                    grid.ColumnDefinitions[0].MinWidth = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Column_MaxWidth_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.ColumnDefinitions[0][ColumnDefinition.MaxWidthProperty] = 5;
+                else
+                    grid.ColumnDefinitions[0].MaxWidth = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_Height_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.HeightProperty] = new GridLength(5);
+                else
+                    grid.RowDefinitions[0].Height = new GridLength(5);
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_MinHeight_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.MinHeightProperty] = 5;
+                else
+                    grid.RowDefinitions[0].MinHeight = 5;
+            });
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public void Changing_Row_MaxHeight_Should_Invalidate_Grid(bool setUsingAvaloniaProperty)
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                if (setUsingAvaloniaProperty)
+                    grid.RowDefinitions[0][RowDefinition.MaxHeightProperty] = 5;
+                else
+                    grid.RowDefinitions[0].MaxHeight = 5;
+            });
+        }
+
+        [Fact]
+        public void Adding_Column_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(5)));
+            });
+        }
+
+        [Fact]
+        public void Adding_Row_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions.Add(new RowDefinition(new GridLength(5)));
+            });
+        }
+
+        [Fact]
+        public void Replacing_Columns_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions = ColumnDefinitions.Parse("2*,1*");
+            });
+        }
+
+        [Fact]
+        public void Replacing_Rows_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions = RowDefinitions.Parse("2*,1*");
+            });
+        }
+
+        [Fact]
+        public void Removing_Column_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.ColumnDefinitions.RemoveAt(0);
+            });
+        }
+
+        [Fact]
+        public void Removing_Row_Should_Invalidate_Grid()
+        {
+            var grid = new Grid { RowDefinitions = RowDefinitions.Parse("1*,1*") };
+
+            Change_Property_And_Verify_Measure_Requested(grid, () =>
+            {
+                grid.RowDefinitions.RemoveAt(0);
+            });
+        }
+
+        [Fact]
+        public void Removing_Child_Should_Invalidate_Grid_And_Be_Operational()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("*,Auto") };
+
+            grid.Children.Add(new Decorator() { [Grid.ColumnProperty] = 0 });
+            grid.Children.Add(new Decorator() { Width = 10, Height = 10, [Grid.ColumnProperty] = 1 });
+
+            var size = new Size(100, 100);
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(90, grid.Children[0].Bounds.Width);
+            Assert.Equal(10, grid.Children[1].Bounds.Width);
+
+            grid.Children.RemoveAt(1);
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(100, grid.Children[0].Bounds.Width);
+        }
+
+        [Fact]
+        public void Adding_Child_Should_Invalidate_Grid_And_Be_Operational()
+        {
+            var grid = new Grid { ColumnDefinitions = ColumnDefinitions.Parse("*,Auto") };
+
+            grid.Children.Add(new Decorator() { [Grid.ColumnProperty] = 0 });
+
+            var size = new Size(100, 100);
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(100, grid.Children[0].Bounds.Width);
+
+            grid.Children.Add(new Decorator() { Width = 10, Height = 10, [Grid.ColumnProperty] = 1 });
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+
+            grid.Measure(size);
+            grid.Arrange(new Rect(size));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            Assert.Equal(90, grid.Children[0].Bounds.Width);
+            Assert.Equal(10, grid.Children[1].Bounds.Width);
+        }
+
+        private static void Change_Property_And_Verify_Measure_Requested(Grid grid, Action change)
+        {
+            grid.Measure(new Size(100, 100));
+            grid.Arrange(new Rect(grid.DesiredSize));
+
+            Assert.True(grid.IsMeasureValid);
+            Assert.True(grid.IsArrangeValid);
+
+            change();
+
+            Assert.False(grid.IsMeasureValid);
+            Assert.False(grid.IsArrangeValid);
+        }
+
         private class TestControl : Control
         {
             public Size MeasureSize { get; set; }

+ 3 - 3
tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs

@@ -45,7 +45,7 @@ namespace Avalonia.ReactiveUI.UnitTests
         public void AutoSuspendHelper_Should_Immediately_Fire_IsLaunchingNew() 
         {
             using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) 
-            using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+            using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
             {
                 var isLaunchingReceived = false;
                 var application = AvaloniaLocator.Current.GetService<Application>();
@@ -86,7 +86,7 @@ namespace Avalonia.ReactiveUI.UnitTests
         public void ShouldPersistState_Should_Fire_On_App_Exit_When_SuspensionDriver_Is_Initialized() 
         {
             using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
-            using (var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) 
+            using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) 
             {
                 var shouldPersistReceived = false;
                 var application = AvaloniaLocator.Current.GetService<Application>();
@@ -105,4 +105,4 @@ namespace Avalonia.ReactiveUI.UnitTests
             }
         }
     }
-}
+}