diff --git a/build/Microsoft.Reactive.Testing.props b/build/Microsoft.Reactive.Testing.props index 777bc4bb53..a0ba2163bb 100644 --- a/build/Microsoft.Reactive.Testing.props +++ b/build/Microsoft.Reactive.Testing.props @@ -1,5 +1,5 @@  - + diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index 7caeaf90ae..f827cb9a32 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/dirs.proj b/dirs.proj index e56320e73f..4b3b1183f0 100644 --- a/dirs.proj +++ b/dirs.proj @@ -8,7 +8,8 @@ - + + diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index 37c7178c56..649c64ab1d 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -28,12 +28,11 @@ namespace VirtualizationDemo.ViewModels public MainWindowViewModel() { this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); + RecreateCommand = ReactiveCommand.Create(() => Recreate()); - RecreateCommand = ReactiveCommand.Create(Recreate); + AddItemCommand = ReactiveCommand.Create(() => AddItem()); - AddItemCommand = ReactiveCommand.Create(AddItem); - - RemoveItemCommand = ReactiveCommand.Create(Remove); + RemoveItemCommand = ReactiveCommand.Create(() => Remove()); SelectFirstCommand = ReactiveCommand.Create(() => SelectItem(0)); diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index cc0c5f52be..a50175bdee 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -304,6 +304,11 @@ namespace Avalonia.Controls.Primitives { base.ItemsCollectionChanged(sender, e); + if (_updateCount > 0) + { + return; + } + switch (e.Action) { case NotifyCollectionChangedAction.Add: @@ -1071,13 +1076,20 @@ namespace Avalonia.Controls.Primitives private void UpdateFinished() { - if (_updateSelectedIndex != int.MinValue) + if (_updateSelectedItem != null) { - SelectedIndex = _updateSelectedIndex; + SelectedItem = _updateSelectedItem; } - else if (_updateSelectedItem != null) + else { - SelectedItem = _updateSelectedItem; + if (ItemCount == 0 && SelectedIndex != -1) + { + SelectedIndex = -1; + } + else + { + SelectedIndex = _updateSelectedIndex != int.MinValue ? _updateSelectedIndex : 0; + } } } diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 5d96fd264a..ac5db32c14 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -118,4 +118,4 @@ namespace Avalonia.ReactiveUI Content = viewInstance; } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 0ff0285a04..efcc555159 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -540,7 +540,7 @@ namespace Avalonia.Rendering foreach (var visual in _recalculateChildren) { var node = scene.FindNode(visual); - ((VisualNode)node)?.UpdateChildren(scene); + ((VisualNode)node)?.SortChildren(scene); } _recalculateChildren.Clear(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 2fcea8c205..161cbc099e 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -53,6 +53,16 @@ namespace Avalonia.Rendering.SceneGraph if (visual.VisualRoot != null) { + if (node?.Parent != null && + visual.VisualParent != null && + node.Parent.Visual != visual.VisualParent) + { + // The control has changed parents. Remove the node and recurse into the new parent node. + ((VisualNode)node.Parent).RemoveChild(node); + Deindex(scene, node); + node = (VisualNode)scene.FindNode(visual.VisualParent); + } + if (visual.IsVisible) { // If the node isn't yet part of the scene, find the nearest ancestor that is. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index f079023c6d..f579bf0a62 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -174,12 +174,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// Sorts the collection according to the order of the visual's - /// children and their z-index and removes controls that are no longer children. + /// children and their z-index. /// /// The scene that the node is a part of. - public void UpdateChildren(Scene scene) + public void SortChildren(Scene scene) { - if (_children == null || _children.Count == 0) + if (_children == null || _children.Count <= 1) { return; } @@ -193,12 +193,9 @@ namespace Avalonia.Rendering.SceneGraph keys.Add(((long)zIndex << 32) + i); } - var toRemove = _children.ToList(); - keys.Sort(); _children.Clear(); - foreach (var i in keys) { var child = Visual.VisualChildren[(int)(i & 0xffffffff)]; @@ -207,14 +204,8 @@ namespace Avalonia.Rendering.SceneGraph if (node != null) { _children.Add(node); - toRemove.Remove(node); } } - - foreach (var node in toRemove) - { - scene.Remove(node); - } } /// diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 4e4d92afdc..8f27ebe853 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -109,6 +109,28 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(items[1].IsSelected); } + [Fact] + public void Setting_SelectedIndex_During_Initialize_Should_Select_Item_When_AlwaysSelected_Is_Used() + { + var listBox = new ListBox + { + SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected + }; + + listBox.BeginInit(); + + listBox.SelectedIndex = 1; + var items = new AvaloniaList(); + listBox.Items = items; + items.Add("A"); + items.Add("B"); + items.Add("C"); + + listBox.EndInit(); + + Assert.Equal("B", listBox.SelectedItem); + } + [Fact] public void Setting_SelectedIndex_Before_ApplyTemplate_Should_Set_Item_IsSelected_True() { diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs index 5b97ab7ae6..ff9e259eab 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs @@ -120,6 +120,8 @@ namespace Avalonia.Markup.UnitTests.Parsers var result = run(); result.Item1.Subscribe(x => { }); + GC.Collect(); + GC.WaitForPendingFinalizers(); GC.Collect(); Assert.Null(result.Item2.Target); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 568ccb81d8..b4743e900d 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -96,6 +96,55 @@ namespace Avalonia.Visuals.UnitTests.Rendering Assert.Equal(new List { root, decorator, border, canvas }, result); } + [Fact] + public void Should_Add_Dirty_Rect_On_Child_Remove() + { + var dispatcher = new ImmediateDispatcher(); + var loop = new Mock(); + + Decorator decorator; + Border border; + var root = new TestRoot + { + Width = 100, + Height= 100, + Child = decorator = new Decorator + { + Child = border = new Border + { + Width = 50, + Height = 50, + Background = Brushes.Red, + }, + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var sceneBuilder = new SceneBuilder(); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder, + dispatcher: dispatcher); + + root.Renderer = target; + target.Start(); + RunFrame(target); + + decorator.Child = null; + + RunFrame(target); + + var scene = target.UnitTestScene(); + var stackNode = scene.FindNode(decorator); + var dirty = scene.Layers[0].Dirty.ToList(); + + Assert.Equal(1, dirty.Count); + Assert.Equal(new Rect(25, 25, 50, 50), dirty[0]); + } + [Fact] public void Should_Update_VisualNode_Order_On_Child_Remove_Insert() { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index dcf23e94e2..13bcd27240 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -521,8 +521,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph moveFromNode = (VisualNode)scene.FindNode(moveFrom); moveToNode = (VisualNode)scene.FindNode(moveTo); - moveFromNode.UpdateChildren(scene); - moveToNode.UpdateChildren(scene); + moveFromNode.SortChildren(scene); + moveToNode.SortChildren(scene); sceneBuilder.Update(scene, moveFrom); sceneBuilder.Update(scene, moveTo); sceneBuilder.Update(scene, moveMe); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs index d4f7a6a142..24ba2d1c48 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var node = new VisualNode(Mock.Of(), null); var scene = new Scene(Mock.Of()); - node.UpdateChildren(scene); + node.SortChildren(scene); } } }