diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 9b5f8a9c1b..52e329189a 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -50,13 +50,14 @@ - + + diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index 8199333f47..2e02bdc647 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs @@ -16,7 +16,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the currently realized containers. /// - IEnumerable Containers { get; } + IEnumerable Containers { get; } /// /// Gets or sets the data template used to display the items in the control. @@ -34,17 +34,17 @@ namespace Avalonia.Controls.Generators event EventHandler Dematerialized; /// - /// Creates container controls for a collection of items. + /// Creates a container control for an item. /// - /// - /// The index of the first item of the data in the containing collection. + /// + /// The index of the item of the data in the containing collection. /// - /// The items. + /// The item. /// An optional member selector. /// The created controls. - IEnumerable Materialize( - int startingIndex, - IEnumerable items, + ItemContainerInfo Materialize( + int index, + object item, IMemberSelector selector); /// @@ -55,7 +55,7 @@ namespace Avalonia.Controls.Generators /// /// The the number of items to remove. /// The removed containers. - IEnumerable Dematerialize(int startingIndex, int count); + IEnumerable Dematerialize(int startingIndex, int count); /// /// Inserts space for newly inserted containers in the index. @@ -73,13 +73,13 @@ namespace Avalonia.Controls.Generators /// /// The the number of items to remove. /// The removed containers. - IEnumerable RemoveRange(int startingIndex, int count); + IEnumerable RemoveRange(int startingIndex, int count); /// /// Clears all created containers and returns the removed controls. /// /// The removed controls. - IEnumerable Clear(); + IEnumerable Clear(); /// /// Gets the container control representing the item with the specified index. diff --git a/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs b/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs index 304e0a99fa..cd26b0ba83 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls.Generators /// The container. public ItemContainerEventArgs( int startingIndex, - ItemContainer container) + ItemContainerInfo container) { StartingIndex = startingIndex; Containers = new[] { container }; @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Generators /// The containers. public ItemContainerEventArgs( int startingIndex, - IList containers) + IList containers) { StartingIndex = startingIndex; Containers = containers; @@ -41,7 +41,7 @@ namespace Avalonia.Controls.Generators /// /// Gets the containers. /// - public IList Containers { get; } + public IList Containers { get; } /// /// Gets the index of the first container in the source items. diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index 114d31c5a6..3bf69d910a 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 List _containers = new List(); + private List _containers = new List(); /// /// Initializes a new instance of the class. @@ -29,7 +29,7 @@ namespace Avalonia.Controls.Generators } /// - public IEnumerable Containers => _containers.Where(x => x != null); + public IEnumerable Containers => _containers.Where(x => x != null); /// public event EventHandler Materialized; @@ -48,33 +48,24 @@ namespace Avalonia.Controls.Generators public IControl Owner { get; } /// - public IEnumerable Materialize( - int startingIndex, - IEnumerable items, + public ItemContainerInfo Materialize( + int index, + object item, IMemberSelector selector) { - Contract.Requires(items != null); + var i = selector != null ? selector.Select(item) : item; + var container = new ItemContainerInfo(CreateContainer(i), item, index); - int index = startingIndex; - var result = new List(); + AddContainer(container); + Materialized?.Invoke(this, new ItemContainerEventArgs(index, container)); - foreach (var item in items) - { - var i = selector != null ? selector.Select(item) : item; - var container = new ItemContainer(CreateContainer(i), item, index++); - result.Add(container); - } - - AddContainers(result); - Materialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); - - return result.Where(x => x != null).ToList(); + return container; } /// - public virtual IEnumerable Dematerialize(int startingIndex, int count) + public virtual IEnumerable Dematerialize(int startingIndex, int count) { - var result = new List(); + var result = new List(); for (int i = startingIndex; i < startingIndex + count; ++i) { @@ -93,13 +84,13 @@ namespace Avalonia.Controls.Generators /// public virtual void InsertSpace(int index, int count) { - _containers.InsertRange(index, Enumerable.Repeat(null, count)); + _containers.InsertRange(index, Enumerable.Repeat(null, count)); } /// - public virtual IEnumerable RemoveRange(int startingIndex, int count) + public virtual IEnumerable RemoveRange(int startingIndex, int count) { - List result = new List(); + List result = new List(); if (startingIndex < _containers.Count) { @@ -112,10 +103,10 @@ namespace Avalonia.Controls.Generators } /// - public virtual IEnumerable Clear() + public virtual IEnumerable Clear() { var result = _containers.Where(x => x != null).ToList(); - _containers = new List(); + _containers = new List(); if (result.Count > 0) { @@ -172,32 +163,29 @@ namespace Avalonia.Controls.Generators } /// - /// Adds a collection of containers to the index. + /// Adds a container to the index. /// - /// The containers. - protected void AddContainers(IList containers) + /// The container. + protected void AddContainer(ItemContainerInfo container) { - Contract.Requires(containers != null); + Contract.Requires(container != null); - foreach (var c in containers) + while (_containers.Count < container.Index) { - while (_containers.Count < c.Index) - { - _containers.Add(null); - } + _containers.Add(null); + } - if (_containers.Count == c.Index) - { - _containers.Add(c); - } - else if (_containers[c.Index] == null) - { - _containers[c.Index] = c; - } - else - { - throw new InvalidOperationException("Container already created."); - } + if (_containers.Count == container.Index) + { + _containers.Add(container); + } + else if (_containers[container.Index] == null) + { + _containers[container.Index] = container; + } + else + { + throw new InvalidOperationException("Container already created."); } } @@ -207,7 +195,7 @@ namespace Avalonia.Controls.Generators /// The first index. /// The number of elements in the range. /// The containers. - protected IEnumerable GetContainerRange(int index, int count) + protected IEnumerable GetContainerRange(int index, int count) { return _containers.GetRange(index, count); } diff --git a/src/Avalonia.Controls/Generators/ItemContainer.cs b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs similarity index 91% rename from src/Avalonia.Controls/Generators/ItemContainer.cs rename to src/Avalonia.Controls/Generators/ItemContainerInfo.cs index ed2e433a28..b0fcd2867e 100644 --- a/src/Avalonia.Controls/Generators/ItemContainer.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs @@ -7,17 +7,17 @@ namespace Avalonia.Controls.Generators /// Holds information about an item container generated by an /// . /// - public class ItemContainer + public class ItemContainerInfo { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The container control. /// The item that the container represents. /// /// The index of the item in the collection. /// - public ItemContainer(IControl container, object item, int index) + public ItemContainerInfo(IControl container, object item, int index) { ContainerControl = container; Item = item; diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index b7a9bb2dc1..f58f7e019d 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -47,7 +47,7 @@ namespace Avalonia.Controls.Generators Materialized?.Invoke( this, - new ItemContainerEventArgs(0, new ItemContainer(container, item, 0))); + new ItemContainerEventArgs(0, new ItemContainerInfo(container, item, 0))); } /// @@ -62,14 +62,14 @@ namespace Avalonia.Controls.Generators Dematerialized?.Invoke( this, - new ItemContainerEventArgs(0, new ItemContainer(container, item, 0))); + new ItemContainerEventArgs(0, new ItemContainerInfo(container, item, 0))); } /// /// Removes a set of containers from the index. /// /// The item containers. - public void Remove(IEnumerable containers) + public void Remove(IEnumerable containers) { foreach (var container in containers) { diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 681bea865a..68e410ae19 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -99,20 +99,20 @@ namespace Avalonia.Controls.Generators } } - public override IEnumerable Clear() + public override IEnumerable Clear() { var items = base.Clear(); Index.Remove(items); return items; } - public override IEnumerable Dematerialize(int startingIndex, int count) + public override IEnumerable Dematerialize(int startingIndex, int count) { Index.Remove(GetContainerRange(startingIndex, count)); return base.Dematerialize(startingIndex, count); } - public override IEnumerable RemoveRange(int startingIndex, int count) + public override IEnumerable RemoveRange(int startingIndex, int count) { Index.Remove(GetContainerRange(startingIndex, count)); return base.RemoveRange(startingIndex, count); diff --git a/src/Avalonia.Controls/ItemVirtualizationMode.cs b/src/Avalonia.Controls/ItemVirtualizationMode.cs new file mode 100644 index 0000000000..f17e8c07ad --- /dev/null +++ b/src/Avalonia.Controls/ItemVirtualizationMode.cs @@ -0,0 +1,21 @@ +// 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. + +namespace Avalonia.Controls +{ + /// + /// Describes the item virtualization method to use for a list. + /// + public enum ItemVirtualizationMode + { + /// + /// Do not virtualize items. + /// + None, + + /// + /// Virtualize items without smooth scrolling. + /// + Simple, + } +} diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index a4f9c0cb43..29488c8b80 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -175,12 +175,8 @@ namespace Avalonia.Controls.Presenters if (container == null) { var item = Items.Cast().ElementAt(index); - var materialized = ItemContainerGenerator.Materialize( - index, - new[] { item }, - MemberSelector); - container = materialized.First().ContainerControl; - Panel.Children.Add(container); + var materialized = ItemContainerGenerator.Materialize(index, item, MemberSelector); + Panel.Children.Add(materialized.ContainerControl); } return container; diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 8c8ab5c0f8..1c4443811d 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -1,9 +1,12 @@ // 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; using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Controls.Generators; +using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; using Avalonia.Input; @@ -12,8 +15,18 @@ namespace Avalonia.Controls.Presenters /// /// Displays items inside an . /// - public class ItemsPresenter : ItemsPresenterBase + public class ItemsPresenter : ItemsPresenterBase, IScrollable { + /// + /// Defines the property. + /// + public static readonly StyledProperty VirtualizationModeProperty = + AvaloniaProperty.Register( + nameof(VirtualizationMode), + defaultValue: ItemVirtualizationMode.Simple); + + private VirtualizationInfo _virt; + /// /// Initializes static members of the class. /// @@ -24,11 +37,72 @@ namespace Avalonia.Controls.Presenters KeyboardNavigationMode.Once); } + /// + /// Gets or sets the virtualization mode for the items. + /// + public ItemVirtualizationMode VirtualizationMode + { + get { return GetValue(VirtualizationModeProperty); } + set { SetValue(VirtualizationModeProperty, value); } + } + + /// + bool IScrollable.IsLogicalScrollEnabled + { + get { return _virt != null && VirtualizationMode != ItemVirtualizationMode.None; } + } + + /// + Action IScrollable.InvalidateScroll { get; set; } + + Size IScrollable.Extent + { + get + { + switch (VirtualizationMode) + { + case ItemVirtualizationMode.Simple: + return new Size(0, Items?.Count() ?? 0); + default: + return default(Size); + } + } + } + + Vector IScrollable.Offset { get; set; } + + Size IScrollable.Viewport + { + get + { + throw new NotImplementedException(); + } + } + + Size IScrollable.ScrollSize + { + get + { + throw new NotImplementedException(); + } + } + + Size IScrollable.PageScrollSize + { + get + { + throw new NotImplementedException(); + } + } + /// protected override void CreatePanel() { base.CreatePanel(); + var virtualizingPanel = Panel as IVirtualizingPanel; + _virt = virtualizingPanel != null ? new VirtualizationInfo(virtualizingPanel) : null; + if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty)) { KeyboardNavigation.SetDirectionalNavigation( @@ -55,7 +129,7 @@ namespace Avalonia.Controls.Presenters generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count); } - AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector)); + AddContainers(e.NewStartingIndex, e.NewItems); break; case NotifyCollectionChangedAction.Remove: @@ -64,8 +138,7 @@ namespace Avalonia.Controls.Presenters case NotifyCollectionChangedAction.Replace: RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count)); - var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector); - AddContainers(containers); + var containers = AddContainers(e.NewStartingIndex, e.NewItems); var i = e.NewStartingIndex; @@ -83,7 +156,7 @@ namespace Avalonia.Controls.Presenters if (Items != null) { - AddContainers(generator.Materialize(0, Items, MemberSelector)); + AddContainers(0, Items); } break; @@ -92,17 +165,20 @@ namespace Avalonia.Controls.Presenters InvalidateMeasure(); } - private void AddContainersToPanel(IEnumerable items) + private IList AddContainers(int index, IEnumerable items) { - foreach (var i in items) + var generator = ItemContainerGenerator; + var result = new List(); + + foreach (var item in items) { + var i = generator.Materialize(index++, item, MemberSelector); + if (i.ContainerControl != null) { if (i.Index < this.Panel.Children.Count) { - // HACK: This will insert at the wrong place when there are null items, - // but all of this will need to be rewritten when we implement - // virtualization so hope no-one notices until then :) + // TODO: This will insert at the wrong place when there are null items. this.Panel.Children.Insert(i.Index, i.ContainerControl); } else @@ -110,39 +186,34 @@ namespace Avalonia.Controls.Presenters this.Panel.Children.Add(i.ContainerControl); } } + + result.Add(i); } + + return result; } - private void AddContainers(IEnumerable items) + private void RemoveContainers(IEnumerable items) { foreach (var i in items) { if (i.ContainerControl != null) { - if (i.Index < this.Panel.Children.Count) - { - // HACK: This will insert at the wrong place when there are null items, - // but all of this will need to be rewritten when we implement - // virtualization so hope no-one notices until then :) - this.Panel.Children.Insert(i.Index, i.ContainerControl); - } - else - { - this.Panel.Children.Add(i.ContainerControl); - } + this.Panel.Children.Remove(i.ContainerControl); } } } - private void RemoveContainers(IEnumerable items) + private class VirtualizationInfo { - foreach (var i in items) + public VirtualizationInfo(IVirtualizingPanel panel) { - if (i.ContainerControl != null) - { - this.Panel.Children.Remove(i.ContainerControl); - } + Panel = panel; } + + public IVirtualizingPanel Panel { get; } + public int FirstIndex { get; set; } + public int LastIndex { get; set; } } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 91ffec9c4c..718c6f018f 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -206,7 +206,7 @@ namespace Avalonia.Controls.Presenters /// /// Called when the items for the presenter change, either because - /// has been set, or the items collection has been modified. + /// has been set, the items collection has been modified, or the panel has been created. /// /// A description of the change. protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e); diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 173163c849..d1d6e3da86 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -91,6 +91,7 @@ + diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index cdff65b35a..ff35d90237 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs @@ -1,8 +1,11 @@ // 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.Generic; using System.Linq; using Avalonia.Controls.Generators; +using Avalonia.Controls.Templates; using Xunit; namespace Avalonia.Controls.UnitTests.Generators @@ -15,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null); + var containers = Materialize(target, 0, items); var result = containers .Select(x => x.ContainerControl) .OfType() @@ -31,20 +34,36 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null).ToList(); + var containers = Materialize(target, 0, items); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); Assert.Equal(containers[1].ContainerControl, target.ContainerFromIndex(1)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } + private IList Materialize( + IItemContainerGenerator generator, + int index, + string[] items) + { + var result = new List(); + + foreach (var item in items) + { + var container = generator.Materialize(index++, item, null); + result.Add(container); + } + + return result; + } + [Fact] public void IndexFromContainer_Should_Return_Index() { var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null).ToList(); + var containers = Materialize(target, 0, items); Assert.Equal(0, target.IndexFromContainer(containers[0].ContainerControl)); Assert.Equal(1, target.IndexFromContainer(containers[1].ContainerControl)); @@ -57,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null).ToList(); + var containers = Materialize(target, 0, items); target.Dematerialize(1, 1); @@ -72,7 +91,7 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null); + var containers = Materialize(target, 0, items); var expected = target.Containers.Take(2).ToList(); var result = target.Dematerialize(0, 2); @@ -85,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner); - var containers = target.Materialize(0, items, null).ToList(); + var containers = Materialize(target, 0, items); var removed = target.RemoveRange(1, 1).Single(); diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs index 2522f1ef03..f63c0efbf9 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.Linq; using Avalonia.Controls.Generators; using Xunit; @@ -15,7 +16,7 @@ namespace Avalonia.Controls.UnitTests.Generators var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); - var containers = target.Materialize(0, items, null); + var containers = Materialize(target, 0, items); var result = containers .Select(x => x.ContainerControl) .OfType() @@ -24,5 +25,21 @@ namespace Avalonia.Controls.UnitTests.Generators Assert.Equal(items, result); } + + private IList Materialize( + IItemContainerGenerator generator, + int index, + string[] items) + { + var result = new List(); + + foreach (var item in items) + { + var container = generator.Materialize(index++, item, null); + result.Add(container); + } + + return result; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs new file mode 100644 index 0000000000..ff602bbbb4 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -0,0 +1,103 @@ +// 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 Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Presenters +{ + public class ItemsPresenterTests_Virtualization + { + [Fact] + public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel() + { + var target = new ItemsPresenter + { + }; + + target.ApplyTemplate(); + + Assert.False(((IScrollable)target).IsLogicalScrollEnabled); + } + + [Fact] + public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None() + { + var target = new ItemsPresenter + { + ItemsPanel = VirtualizingPanelTemplate(), + VirtualizationMode = ItemVirtualizationMode.None, + }; + + target.ApplyTemplate(); + + Assert.False(((IScrollable)target).IsLogicalScrollEnabled); + } + + [Fact] + public void Should_Return_IsLogicalScrollEnabled_True_When_Has_Virtualizing_Panel() + { + var target = new ItemsPresenter + { + ItemsPanel = VirtualizingPanelTemplate(), + }; + + target.ApplyTemplate(); + + Assert.True(((IScrollable)target).IsLogicalScrollEnabled); + } + + public class Simple + { + [Fact] + public void Should_Return_Items_Count_For_Extent() + { + var target = new ItemsPresenter + { + Items = new string[10], + ItemsPanel = VirtualizingPanelTemplate(), + VirtualizationMode = ItemVirtualizationMode.Simple, + }; + + target.ApplyTemplate(); + + Assert.Equal(new Size(0, 10), ((IScrollable)target).Extent); + } + + [Fact] + public void Should_Have_Number_Of_Visible_Items_As_Viewport() + { + var target = new ItemsPresenter + { + Items = new string[20], + ItemsPanel = VirtualizingPanelTemplate(), + ItemTemplate = ItemTemplate(), + VirtualizationMode = ItemVirtualizationMode.Simple, + }; + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + Assert.Equal(10, ((IScrollable)target).Viewport.Height); + } + } + + private static IDataTemplate ItemTemplate() + { + return new FuncDataTemplate(x => new TextBlock + { + Text = x, + Height = 10, + }); + } + + private static ITemplate VirtualizingPanelTemplate() + { + return new FuncTemplate(() => new VirtualizingStackPanel()); + } + } +}