From 1df826281bbd61df064bcb334e5f968c2fbedb71 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 Sep 2018 16:26:32 +0200 Subject: [PATCH 1/8] Added failing test for #1932. --- .../Primitives/SelectingItemsControlTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 14e1b15ebc..bbe1d85acb 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -707,6 +707,26 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(target.SelectedIndex == 1); } + [Fact] + public void Binding_With_DelayedBinding_And_Initialization_Where_DataContext_Is_Root_Works() + { + // Test for #1932. + var root = new RootWithItems(); + + root.BeginInit(); + root.DataContext = root; + + var target = new ListBox(); + target.BeginInit(); + root.Child = target; + + DelayedBinding.Add(target, ItemsControl.ItemsProperty, new Binding(nameof(RootWithItems.Items))); + DelayedBinding.Add(target, ListBox.SelectedItemProperty, new Binding(nameof(RootWithItems.Selected))); + target.EndInit(); + root.EndInit(); + + Assert.Equal("b", target.SelectedItem); + } private FuncControlTemplate Template() { @@ -745,5 +765,11 @@ namespace Avalonia.Controls.UnitTests.Primitives public IList Items { get; set; } public Item SelectedItem { get; set; } } + + private class RootWithItems : TestRoot + { + public List Items { get; set; } = new List() { "a", "b", "c", "d", "e" }; + public string Selected { get; set; } = "b"; + } } } From d0b77ecca191fe630f01e8af8eb2945f9f89ce55 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 29 Sep 2018 16:30:21 +0200 Subject: [PATCH 2/8] Update before calling base.EndInit. Fixes #1932. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index d7db04f369..f8440aac47 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -289,12 +289,12 @@ namespace Avalonia.Controls.Primitives /// public override void EndInit() { - base.EndInit(); - if (--_updateCount == 0) { UpdateFinished(); } + + base.EndInit(); } /// From b53b3d590f6db78df737c878b1b3391e0334cada Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Mon, 24 Sep 2018 13:08:48 +0300 Subject: [PATCH 3/8] add failing unit test for listbox issue #1936 --- .../ListBoxTests.cs | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index a5f5f8d328..eb3a6cf0c0 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -1,16 +1,17 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; +using System.Collections.ObjectModel; using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; -using Avalonia.Input; +using Avalonia.Data; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; -using Avalonia.Collections; namespace Avalonia.Controls.UnitTests { @@ -170,6 +171,109 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(100, 10), target.Scroll.Viewport); } + [Theory] + [InlineData(ItemVirtualizationMode.Simple)] + [InlineData(ItemVirtualizationMode.None)] + public void When_Added_Removed_AfterItems_Reset_Should_Work(ItemVirtualizationMode virtMode) + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = new ObservableCollection(); + + Action create = () => + { + foreach (var i in Enumerable.Range(1, 7)) + { + items.Add(i.ToString()); + } + }; + + create(); + + var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight }; + + wnd.IsVisible = true; + + var target = new ListBox() { VirtualizationMode = virtMode }; + + wnd.Content = target; + + var lm = wnd.LayoutManager; + + target.Height = 110;//working fine when <=106 or >=119 + target.Width = 50; + + target.ItemTemplate = new FuncDataTemplate(c => + { + var tb = new TextBlock() { Height = 10, Width = 30 }; + tb.Bind(TextBlock.TextProperty, new Binding()); + return tb; + }, true); + + target.DataContext = items; + + lm.ExecuteInitialLayoutPass(wnd); + + target.Bind(ItemsControl.ItemsProperty, new Binding()); + + lm.ExecuteLayoutPass(); + + var panel = target.Presenter.Panel; + + Func itemsToString = () => + string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); + + Action addafter = (item, newitem) => + { + items.Insert(items.IndexOf(item) + 1, newitem); + + lm.ExecuteLayoutPass(); + }; + + Action remove = item => + { + items.Remove(item); + lm.ExecuteLayoutPass(); + }; + + addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 + + addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 + + remove("2+");//expected 1,1+,2,3,4,5,6,7 + + //Reset items + items.Clear(); + create(); + + addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 + + addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 + + remove("2+");//expected 1,1+,2,3,4,5,6,7 + + var sti = itemsToString(); + + var lbItems = panel.Children.OfType().ToArray(); + + Assert.Equal("1", lbItems[0].Content); + Assert.Equal("1+", lbItems[1].Content); + Assert.Equal("2", lbItems[2].Content); + Assert.Equal("3", lbItems[3].Content); //bug it's 2+ instead + Assert.Equal("4", lbItems[4].Content); + + int lbi = 0; + + //ensure all items are fine + foreach (var lb in lbItems) + { + Assert.Equal(items[lbi++], lb.Content); + } + + //Assert.Equal("1,1+,2,3,4,5,6,7", sti); + } + } + private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate(parent => From caa65545d7e1f1e9ffac0dc36908b5ef58474bfd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Oct 2018 18:36:56 +0300 Subject: [PATCH 4/8] [SKIA] Call NotifyPixelsChanged when WritableBitmap gets unlocked --- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index ab6c399ff4..915aa77b52 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -126,6 +126,7 @@ namespace Avalonia.Skia /// public void Dispose() { + _bitmap.NotifyPixelsChanged(); _bitmap = null; } From 64629128ff00c97d86312957e7aba64d959cac4b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 3 Oct 2018 11:54:47 +0100 Subject: [PATCH 5/8] fixes a NRE in menu interaction handler --- src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index a44495b90c..6b03e67897 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -331,7 +331,7 @@ namespace Avalonia.Controls.Platform { var item = GetMenuItem(e.Source as IControl); - if (e.MouseButton == MouseButton.Left && item.HasSubMenu == false) + if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == false) { Click(item); e.Handled = true; From a865f6dddfcc7bfee925af7bdfc407601ead504b Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 6 Oct 2018 01:41:08 +0300 Subject: [PATCH 6/8] introduce local functions for listbox issue #1936 --- .../ListBoxTests.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index eb3a6cf0c0..c6968f19f8 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -180,13 +180,13 @@ namespace Avalonia.Controls.UnitTests { var items = new ObservableCollection(); - Action create = () => + void create() { foreach (var i in Enumerable.Range(1, 7)) { items.Add(i.ToString()); } - }; + } create(); @@ -220,21 +220,20 @@ namespace Avalonia.Controls.UnitTests var panel = target.Presenter.Panel; - Func itemsToString = () => - string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); + string itemsToString() => + string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); - Action addafter = (item, newitem) => - { - items.Insert(items.IndexOf(item) + 1, newitem); - - lm.ExecuteLayoutPass(); - }; + void addafter(string item, string newitem) + { + items.Insert(items.IndexOf(item) + 1, newitem); + lm.ExecuteLayoutPass(); + } - Action remove = item => + void remove(string item) { items.Remove(item); lm.ExecuteLayoutPass(); - }; + } addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 @@ -276,7 +275,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate ListBoxTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate(parent => new ScrollViewer { Name = "PART_ScrollViewer", @@ -293,7 +292,7 @@ namespace Avalonia.Controls.UnitTests private FuncControlTemplate ListBoxItemTemplate() { - return new FuncControlTemplate(parent => + return new FuncControlTemplate(parent => new ContentPresenter { Name = "PART_ContentPresenter", From 6efba9837db65f3a0eef0674a9277f1e345c1731 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 7 Oct 2018 00:59:50 +0200 Subject: [PATCH 7/8] Simplified test. Simplified @donandren's test for #1936. --- .../ListBoxTests.cs | 113 +++--------------- 1 file changed, 18 insertions(+), 95 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index c6968f19f8..1debccd3c5 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; +using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; @@ -171,106 +172,28 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Size(100, 10), target.Scroll.Viewport); } - [Theory] - [InlineData(ItemVirtualizationMode.Simple)] - [InlineData(ItemVirtualizationMode.None)] - public void When_Added_Removed_AfterItems_Reset_Should_Work(ItemVirtualizationMode virtMode) + [Fact] + public void Containers_Correct_After_Clear_Add_Remove() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + // Issue #1936 + var items = new AvaloniaList(Enumerable.Range(0, 11).Select(x => $"Item {x}")); + var target = new ListBox { - var items = new ObservableCollection(); - - void create() - { - foreach (var i in Enumerable.Range(1, 7)) - { - items.Add(i.ToString()); - } - } - - create(); - - var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight }; - - wnd.IsVisible = true; - - var target = new ListBox() { VirtualizationMode = virtMode }; - - wnd.Content = target; - - var lm = wnd.LayoutManager; - - target.Height = 110;//working fine when <=106 or >=119 - target.Width = 50; - - target.ItemTemplate = new FuncDataTemplate(c => - { - var tb = new TextBlock() { Height = 10, Width = 30 }; - tb.Bind(TextBlock.TextProperty, new Binding()); - return tb; - }, true); - - target.DataContext = items; - - lm.ExecuteInitialLayoutPass(wnd); - - target.Bind(ItemsControl.ItemsProperty, new Binding()); - - lm.ExecuteLayoutPass(); - - var panel = target.Presenter.Panel; - - string itemsToString() => - string.Join(",", panel.Children.OfType().Select(l => l.Content.ToString()).ToArray()); - - void addafter(string item, string newitem) - { - items.Insert(items.IndexOf(item) + 1, newitem); - lm.ExecuteLayoutPass(); - } - - void remove(string item) - { - items.Remove(item); - lm.ExecuteLayoutPass(); - } - - addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 - - addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 - - remove("2+");//expected 1,1+,2,3,4,5,6,7 - - //Reset items - items.Clear(); - create(); - - addafter("1", "1+");//expected 1,1+,2,3,4,5,6,7 - - addafter("2", "2+");//expected 1,1+,2,2+,3,4,5,6 - - remove("2+");//expected 1,1+,2,3,4,5,6,7 - - var sti = itemsToString(); - - var lbItems = panel.Children.OfType().ToArray(); - - Assert.Equal("1", lbItems[0].Content); - Assert.Equal("1+", lbItems[1].Content); - Assert.Equal("2", lbItems[2].Content); - Assert.Equal("3", lbItems[3].Content); //bug it's 2+ instead - Assert.Equal("4", lbItems[4].Content); + Template = ListBoxTemplate(), + Items = items, + ItemTemplate = new FuncDataTemplate(x => new TextBlock { Width = 20, Height = 10 }), + SelectedIndex = 0, + }; - int lbi = 0; + Prepare(target); - //ensure all items are fine - foreach (var lb in lbItems) - { - Assert.Equal(items[lbi++], lb.Content); - } + items.Clear(); + items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}")); + items.Remove("Item 2"); - //Assert.Equal("1,1+,2,3,4,5,6,7", sti); - } + Assert.Equal( + items, + target.Presenter.Panel.Children.Cast().Select(x => (string)x.Content)); } private FuncControlTemplate ListBoxTemplate() From c608163fe99412bf7ce1da9d898e08eeebef3341 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 7 Oct 2018 01:00:18 +0200 Subject: [PATCH 8/8] Ensure containers are ordered correctly. Fixes #1936. --- src/Avalonia.Controls/Generators/ItemContainerGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 882d2f4ddd..f1a1f94a01 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators /// public class ItemContainerGenerator : IItemContainerGenerator { - private Dictionary _containers = new Dictionary(); + private SortedDictionary _containers = new SortedDictionary(); /// /// Initializes a new instance of the class. @@ -246,4 +246,4 @@ namespace Avalonia.Controls.Generators Recycled?.Invoke(this, e); } } -} \ No newline at end of file +}