Browse Source

Use dictionary to track item containers.

Because even when virtualized we were still creating a list the size of
the Items collection to store the containers. Using Dictionary here
still isn't ideal - we'd ideally use some sort of sparse array but that
can be optimized later.
pull/558/head
Steven Kirk 10 years ago
parent
commit
e686786959
  1. 111
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  2. 4
      src/Avalonia.Controls/Generators/ItemContainerInfo.cs
  3. 49
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  4. 4
      tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

111
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Generators
/// </summary> /// </summary>
public class ItemContainerGenerator : IItemContainerGenerator public class ItemContainerGenerator : IItemContainerGenerator
{ {
private List<ItemContainerInfo> _containers = new List<ItemContainerInfo>(); private Dictionary<int, ItemContainerInfo> _containers = new Dictionary<int, ItemContainerInfo>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class. /// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@ -30,7 +30,7 @@ namespace Avalonia.Controls.Generators
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<ItemContainerInfo> Containers => _containers.Where(x => x != null); public IEnumerable<ItemContainerInfo> Containers => _containers.Values;
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ItemContainerEventArgs> Materialized; public event EventHandler<ItemContainerEventArgs> Materialized;
@ -60,7 +60,7 @@ namespace Avalonia.Controls.Generators
var i = selector != null ? selector.Select(item) : item; var i = selector != null ? selector.Select(item) : item;
var container = new ItemContainerInfo(CreateContainer(i), item, index); var container = new ItemContainerInfo(CreateContainer(i), item, index);
AddContainer(container); _containers.Add(container.Index, container);
Materialized?.Invoke(this, new ItemContainerEventArgs(container)); Materialized?.Invoke(this, new ItemContainerEventArgs(container));
return container; return container;
@ -73,11 +73,8 @@ namespace Avalonia.Controls.Generators
for (int i = startingIndex; i < startingIndex + count; ++i) for (int i = startingIndex; i < startingIndex + count; ++i)
{ {
if (i < _containers.Count &&_containers[i] != null) result.Add(_containers[i]);
{ _containers.Remove(i);
result.Add(_containers[i]);
_containers[i] = null;
}
} }
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
@ -88,18 +85,47 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/> /// <inheritdoc/>
public virtual void InsertSpace(int index, int count) public virtual void InsertSpace(int index, int count)
{ {
_containers.InsertRange(index, Enumerable.Repeat<ItemContainerInfo>(null, count)); if (count > 0)
{
var toMove = _containers.Where(x => x.Key >= index).ToList();
foreach (var i in toMove)
{
_containers.Remove(i.Key);
i.Value.Index += count;
_containers[i.Value.Index] = i.Value;
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count) public virtual IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
{ {
List<ItemContainerInfo> result = new List<ItemContainerInfo>(); var result = new List<ItemContainerInfo>();
if (startingIndex < _containers.Count) if (count > 0)
{ {
result.AddRange(_containers.GetRange(startingIndex, count)); for (var i = startingIndex; i < startingIndex + count; ++i)
_containers.RemoveRange(startingIndex, count); {
ItemContainerInfo found;
if (_containers.TryGetValue(i, out found))
{
result.Add(found);
}
_containers.Remove(i);
}
var toMove = _containers.Where(x => x.Key >= startingIndex).ToList();
foreach (var i in toMove)
{
_containers.Remove(i.Key);
i.Value.Index -= count;
_containers.Add(i.Value.Index, i.Value);
}
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
} }
@ -119,8 +145,8 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/> /// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> Clear() public virtual IEnumerable<ItemContainerInfo> Clear()
{ {
var result = _containers.Where(x => x != null).ToList(); var result = Containers.ToList();
_containers = new List<ItemContainerInfo>(); _containers.Clear();
if (result.Count > 0) if (result.Count > 0)
{ {
@ -133,27 +159,20 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/> /// <inheritdoc/>
public IControl ContainerFromIndex(int index) public IControl ContainerFromIndex(int index)
{ {
if (index < _containers.Count) ItemContainerInfo result;
{ _containers.TryGetValue(index, out result);
return _containers[index]?.ContainerControl; return result?.ContainerControl;
}
return null;
} }
/// <inheritdoc/> /// <inheritdoc/>
public int IndexFromContainer(IControl container) public int IndexFromContainer(IControl container)
{ {
var index = 0;
foreach (var i in _containers) foreach (var i in _containers)
{ {
if (i?.ContainerControl == container) if (i.Value.ContainerControl == container)
{ {
return index; return i.Key;
} }
++index;
} }
return -1; return -1;
@ -176,33 +195,6 @@ namespace Avalonia.Controls.Generators
return result; return result;
} }
/// <summary>
/// Adds a container to the index.
/// </summary>
/// <param name="container">The container.</param>
protected void AddContainer(ItemContainerInfo container)
{
Contract.Requires<ArgumentNullException>(container != null);
while (_containers.Count < container.Index)
{
_containers.Add(null);
}
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.");
}
}
/// <summary> /// <summary>
/// Moves a container. /// Moves a container.
/// </summary> /// </summary>
@ -213,10 +205,11 @@ namespace Avalonia.Controls.Generators
protected ItemContainerInfo MoveContainer(int oldIndex, int newIndex, object item) protected ItemContainerInfo MoveContainer(int oldIndex, int newIndex, object item)
{ {
var container = _containers[oldIndex]; var container = _containers[oldIndex];
var newContainer = new ItemContainerInfo(container.ContainerControl, item, newIndex); container.Index = newIndex;
_containers[oldIndex] = null; container.Item = item;
AddContainer(newContainer); _containers.Remove(oldIndex);
return newContainer; _containers.Add(newIndex, container);
return container;
} }
/// <summary> /// <summary>
@ -227,7 +220,7 @@ namespace Avalonia.Controls.Generators
/// <returns>The containers.</returns> /// <returns>The containers.</returns>
protected IEnumerable<ItemContainerInfo> GetContainerRange(int index, int count) protected IEnumerable<ItemContainerInfo> GetContainerRange(int index, int count)
{ {
return _containers.GetRange(index, count); return _containers.Where(x => x.Key >= index && x.Key <= index + count).Select(x => x.Value);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/Generators/ItemContainerInfo.cs

@ -35,11 +35,11 @@ namespace Avalonia.Controls.Generators
/// <summary> /// <summary>
/// Gets the item that the container represents. /// Gets the item that the container represents.
/// </summary> /// </summary>
public object Item { get; } public object Item { get; internal set; }
/// <summary> /// <summary>
/// Gets the index of the item in the <see cref="ItemsControl.Items"/> collection. /// Gets the index of the item in the <see cref="ItemsControl.Items"/> collection.
/// </summary> /// </summary>
public int Index { get; } public int Index { get; internal set; }
} }
} }

49
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@ -1,11 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests.Generators namespace Avalonia.Controls.UnitTests.Generators
@ -41,22 +39,6 @@ namespace Avalonia.Controls.UnitTests.Generators
Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2));
} }
private IList<ItemContainerInfo> Materialize(
IItemContainerGenerator generator,
int index,
string[] items)
{
var result = new List<ItemContainerInfo>();
foreach (var item in items)
{
var container = generator.Materialize(index++, item, null);
result.Add(container);
}
return result;
}
[Fact] [Fact]
public void IndexFromContainer_Should_Return_Index() public void IndexFromContainer_Should_Return_Index()
{ {
@ -98,6 +80,20 @@ namespace Avalonia.Controls.UnitTests.Generators
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
[Fact]
public void InsertSpace_Should_Alter_Successive_Container_Indexes()
{
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator(owner);
var containers = Materialize(target, 0, items);
target.InsertSpace(1, 3);
Assert.Equal(3, target.Containers.Count());
Assert.Equal(new[] { 0, 4, 5 }, target.Containers.Select(x => x.Index));
}
[Fact] [Fact]
public void RemoveRange_Should_Alter_Successive_Container_Indexes() public void RemoveRange_Should_Alter_Successive_Container_Indexes()
{ {
@ -111,6 +107,23 @@ namespace Avalonia.Controls.UnitTests.Generators
Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0));
Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(1)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(1));
Assert.Equal(containers[1], removed); Assert.Equal(containers[1], removed);
Assert.Equal(new[] { 0, 1 }, target.Containers.Select(x => x.Index));
}
private IList<ItemContainerInfo> Materialize(
IItemContainerGenerator generator,
int index,
string[] items)
{
var result = new List<ItemContainerInfo>();
foreach (var item in items)
{
var container = generator.Materialize(index++, item, null);
result.Add(container);
}
return result;
} }
} }
} }

4
tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@ -155,7 +155,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
} }
[Fact] [Fact]
public void Should_have_correct_index_itemscontainer() public void Should_Have_Correct_ItemsContainer_Index()
{ {
ObservableCollection<string> items = new ObservableCollection<string>(); ObservableCollection<string> items = new ObservableCollection<string>();
@ -186,7 +186,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
items.Remove(items[0]); items.Remove(items[0]);
Assert.Equal(1, target.ItemContainerGenerator.Containers.Count()); Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, target.Panel.Children.Count); Assert.Equal(1, target.Panel.Children.Count);
Assert.Equal(1, target.ItemContainerGenerator.Containers.First().Index); Assert.Equal(0, target.ItemContainerGenerator.Containers.First().Index);
items.Remove(items[0]); items.Remove(items[0]);
Assert.Equal(0, target.ItemContainerGenerator.Containers.Count()); Assert.Equal(0, target.ItemContainerGenerator.Containers.Count());

Loading…
Cancel
Save