diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index d234bcbd0d..feb40d7dd9 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -629,7 +629,7 @@ namespace Avalonia.Controls generator.ItemContainerPrepared(controlItem, item, index); } - controlItem.IsVisible = true; + controlItem.SetCurrentValue(Visual.IsVisibleProperty, true); return controlItem; } @@ -645,7 +645,7 @@ namespace Avalonia.Controls if (_recyclePool?.TryGetValue(recycleKey, out var recyclePool) == true && recyclePool.Count > 0) { var recycled = recyclePool.Pop(); - recycled.IsVisible = true; + recycled.SetCurrentValue(Visual.IsVisibleProperty, true); generator.PrepareItemContainer(recycled, item, index); generator.ItemContainerPrepared(recycled, item, index); return recycled; @@ -684,7 +684,7 @@ namespace Avalonia.Controls } else if (recycleKey == s_itemIsItsOwnContainer) { - element.IsVisible = false; + element.SetCurrentValue(Visual.IsVisibleProperty, false); } else if (KeyboardNavigation.GetTabOnceActiveElement(ItemsControl) == element) { @@ -695,7 +695,7 @@ namespace Avalonia.Controls { ItemContainerGenerator!.ClearItemContainer(element); PushToRecyclePool(recycleKey, element); - element.IsVisible = false; + element.SetCurrentValue(Visual.IsVisibleProperty, false); } } @@ -713,7 +713,7 @@ namespace Avalonia.Controls { ItemContainerGenerator!.ClearItemContainer(element); PushToRecyclePool(recycleKey, element); - element.IsVisible = false; + element.SetCurrentValue(Visual.IsVisibleProperty, false); } } diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index a9506f5a59..8e5aa2f737 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -1048,6 +1048,74 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new(0, 200), scroll.Offset); } + [Fact] + public void Can_Bind_Item_IsVisible() + { + using var app = App(); + var style = CreateIsVisibleBindingStyle(); + var items = Enumerable.Range(0, 100).Select(x => new ItemWithIsVisible(x)).ToList(); + var (target, scroll, itemsControl) = CreateTarget(items: items, styles: new[] { style }); + var container = target.ContainerFromIndex(2)!; + + Assert.True(container.IsVisible); + Assert.Equal(20, container.Bounds.Top); + + items[2].IsVisible = false; + Layout(target); + + Assert.False(container.IsVisible); + + // Next container should be in correct position. + Assert.Equal(20, target.ContainerFromIndex(3)!.Bounds.Top); + } + + [Fact] + public void IsVisible_Binding_Persists_After_Scrolling() + { + using var app = App(); + var style = CreateIsVisibleBindingStyle(); + var items = Enumerable.Range(0, 100).Select(x => new ItemWithIsVisible(x)).ToList(); + var (target, scroll, itemsControl) = CreateTarget(items: items, styles: new[] { style }); + var container = target.ContainerFromIndex(2)!; + + Assert.True(container.IsVisible); + Assert.Equal(20, container.Bounds.Top); + + items[2].IsVisible = false; + scroll.Offset = new Vector(0, 200); + Layout(target); + + scroll.Offset = new Vector(0, 0); + Layout(target); + + container = target.ContainerFromIndex(2)!; + Assert.False(container.IsVisible); + } + + [Fact] + public void Recycling_A_Hidden_Control_Shows_It() + { + using var app = App(); + var style = CreateIsVisibleBindingStyle(); + var items = Enumerable.Range(0, 3).Select(x => new ItemWithIsVisible(x)).ToList(); + var (target, scroll, itemsControl) = CreateTarget(items: items, styles: new[] { style }); + var container = target.ContainerFromIndex(2)!; + + Assert.True(container.IsVisible); + Assert.Equal(20, container.Bounds.Top); + + items[2].IsVisible = false; + Layout(target); + + Assert.False(container.IsVisible); + + items.RemoveAt(2); + items.Add(new ItemWithIsVisible(3)); + Layout(target); + + Assert.True(container.IsVisible); + } + private static IReadOnlyList GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) { return target.GetRealizedElements() @@ -1173,6 +1241,17 @@ namespace Avalonia.Controls.UnitTests return root; } + private static Style CreateIsVisibleBindingStyle() + { + return new Style(x => x.OfType()) + { + Setters = + { + new Setter(Visual.IsVisibleProperty, new Binding("IsVisible")), + } + }; + } + private static IDataTemplate DefaultItemTemplate() { return new FuncDataTemplate((x, _) => new Canvas { Width = 100, Height = 10 }); @@ -1218,6 +1297,24 @@ namespace Avalonia.Controls.UnitTests public double Height { get; set; } } + private class ItemWithIsVisible : NotifyingBase + { + private bool _isVisible = true; + + public ItemWithIsVisible(int index) + { + Caption = $"Item {index}"; + } + + public string Caption { get; set; } + + public bool IsVisible + { + get => _isVisible; + set => SetField(ref _isVisible, value); + } + } + private class ResettingCollection : List, INotifyCollectionChanged { public ResettingCollection(IEnumerable items)