Skip to content

Commit

Permalink
fix(ItemsControl): avoid rebinding containers on collection change
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Oct 4, 2021
1 parent 5d9ad17 commit 4ed5bc4
Show file tree
Hide file tree
Showing 19 changed files with 462 additions and 205 deletions.
87 changes: 85 additions & 2 deletions src/Uno.Foundation/Collections/ObservableVector.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Windows.Foundation.Collections;

namespace Windows.Foundation.Collections
{
internal class ObservableVector<T> : IObservableVector<T>, IObservableVector
internal class ObservableVector<T> : IObservableVector<T>, IObservableVector, IList
{
private readonly List<T> _list = new List<T>();

Expand Down Expand Up @@ -38,7 +39,6 @@ public virtual T this[int index]
public virtual void Add(T item)
{
_list.Add(item);

RaiseVectorChanged(CollectionChange.ItemInserted, _list.Count - 1);
}

Expand Down Expand Up @@ -96,5 +96,88 @@ private void RaiseVectorChanged(CollectionChange change, int index)
VectorChanged?.Invoke(this, new VectorChangedEventArgs(change, (uint)index));
UntypedVectorChanged?.Invoke(this, new VectorChangedEventArgs(change, (uint)index));
}

bool IList.IsFixedSize => false;

object ICollection.SyncRoot => ((IList)_list).SyncRoot;

bool ICollection.IsSynchronized => false;

int IList.Add(object value)
{
if (value is T typedValue)
{
var ret = ((IList)this).Add(typedValue);
RaiseVectorChanged(CollectionChange.ItemInserted, _list.Count - 1);
return ret;
}
else
{
throw new ArgumentException($"Cannot add an instance of type {value?.GetType()}");
}
}

bool IList.Contains(object value)
=> GenericIndexOf(value) != -1;

int IList.IndexOf(object value)
=> GenericIndexOf(value);

private int GenericIndexOf(object value)
{
for (int i = 0; i < Count; i++)
{
if (object.Equals(this[i], value))
{
return i;
}
}

return -1;
}

void IList.Insert(int index, object value)
{
if (value is T typedValue)
{
Insert(index, typedValue);
}
else
{
throw new ArgumentException($"Cannot use an instance of type {value?.GetType()}");
}
}

void IList.Remove(object value)
{
if (value is T typedValue)
{
Remove(typedValue);
}
}

void ICollection.CopyTo(Array array, int index)
{
((IList)_list).CopyTo(array, index);
}

object IList.this[int index]
{
get
{
return this[index];
}
set
{
if (value is T typedValue)
{
this[index] = typedValue;
}
else
{
throw new ArgumentException($"Cannot use an instance of type {value?.GetType()}");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Private.Infrastructure;
using Windows.Foundation.Collections;
#if NETFX_CORE
using Uno.UI.Extensions;
Expand All @@ -20,16 +19,16 @@
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using static Private.Infrastructure.TestServices;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
{
[TestClass]
#if !IS_UNIT_TESTS
[RunsOnUIThread]
#endif
[TestClass]
public partial class Given_ListViewBase_Items
{
[TestMethod]
[RunsOnUIThread]
public async Task When_Items_Added_Count_Updated()
{
var listView = new ListView();
Expand All @@ -38,7 +37,6 @@ public async Task When_Items_Added_Count_Updated()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_Items_Added_ItemsSource_Stays_Null()
{
var listView = new ListView();
Expand All @@ -47,7 +45,6 @@ public async Task When_Items_Added_ItemsSource_Stays_Null()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Used_Items_NotNull()
{
var listView = new ListView();
Expand All @@ -56,7 +53,6 @@ public async Task When_ItemsSource_Used_Items_NotNull()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Unset_Items_NotNull()
{
var listView = new ListView();
Expand All @@ -67,7 +63,6 @@ public async Task When_ItemsSource_Unset_Items_NotNull()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Set_To_Empty_Items_Cleared()
{
var listView = new ListView();
Expand All @@ -77,7 +72,6 @@ public async Task When_ItemsSource_Set_To_Empty_Items_Cleared()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Unset_Items_Not_Cleared()
{
var listView = new ListView();
Expand All @@ -88,7 +82,6 @@ public async Task When_ItemsSource_Unset_Items_Not_Cleared()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Unset_When_Already_Null_Items_Not_Cleared()
{
var listView = new ListView();
Expand All @@ -98,7 +91,6 @@ public async Task When_ItemsSource_Unset_When_Already_Null_Items_Not_Cleared()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Set_Items_Not_Modifiable()
{
var listView = new ListView();
Expand All @@ -118,7 +110,6 @@ public async Task When_ItemsSource_Set_Items_Not_Modifiable()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Unset_Items_Modifiable()
{
var listView = new ListView();
Expand All @@ -128,7 +119,6 @@ public async Task When_ItemsSource_Unset_Items_Modifiable()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_List_Modified_Change_Is_Reflected()
{
var listView = new ListView();
Expand All @@ -140,7 +130,6 @@ public async Task When_ItemsSource_List_Modified_Change_Is_Reflected()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_List_Modified_VectorChange_Not_Triggered()
{
var listView = new ListView();
Expand All @@ -158,7 +147,6 @@ public async Task When_ItemsSource_List_Modified_VectorChange_Not_Triggered()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Resets_ItemsCollection_Reference_Does_Not_Change()
{
var listView = new ListView();
Expand All @@ -174,7 +162,6 @@ public async Task When_ItemsSource_Resets_ItemsCollection_Reference_Does_Not_Cha
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_ObservableCollection_Modified_VectorChange_Triggered()
{
var listView = new ListView();
Expand All @@ -192,7 +179,6 @@ public async Task When_ItemsSource_ObservableCollection_Modified_VectorChange_Tr
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Set_Items_Sync()
{
var listView = new ListView();
Expand All @@ -205,7 +191,6 @@ public async Task When_ItemsSource_Set_Items_Sync()
}

[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Updated_Items_Sync()
{
var listView = new ListView();
Expand All @@ -224,7 +209,6 @@ public async Task When_ItemsSource_Updated_Items_Sync()


[TestMethod]
[RunsOnUIThread]
public async Task When_ItemsSource_Changes_Items_VectorChanged_Triggered()
{
var listView = new ListView();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Windows.Foundation.Collections;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Controls;
using Uno.UI.Tests.Helpers;

namespace Uno.UI.Tests.CollectionViewTests
{
Expand Down Expand Up @@ -190,21 +191,6 @@ public void When_Grouped_Observable()
Assert.AreEqual(1, timesGroupItemsCalled);
}

public class GroupedObservableCollection<TKey> : ObservableCollection<object>, IGrouping<TKey, object>
{
public TKey Key { get; }

public GroupedObservableCollection(TKey key) : base()
{
Key = key;
}

public GroupedObservableCollection(IGrouping<TKey, object> collection) : base(collection)
{
Key = collection.Key;
}
}

[TestMethod]
public void When_Set_As_ItemsSource_And_Current_Initially_Set()
{
Expand Down
25 changes: 25 additions & 0 deletions src/Uno.UI.Tests/Helpers/GroupedObservableCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Uno.UI.Tests.Helpers
{
public class GroupedObservableCollection<TKey> : ObservableCollection<object>, IGrouping<TKey, object>
{
public TKey Key { get; }

public GroupedObservableCollection(TKey key) : base()
{
Key = key;
}

public GroupedObservableCollection(IGrouping<TKey, object> collection) : base(collection)
{
Key = collection.Key;
}
}

}
2 changes: 2 additions & 0 deletions src/Uno.UI.Tests/Uno.UI.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Uno.UI.RuntimeTests\Helpers\SizeAssertion.cs" Link="Extensions\SizeAssertion.cs" />
<Compile Include="..\Uno.UI.RuntimeTests\Tests\Windows_UI_Xaml_Controls\Given_ListViewBase_Items.cs" Link="Windows_UI_XAML_Controls\ListViewBaseTests\Given_ListViewBase_Items.cs" />
<Compile Include="..\Uno.UI.RuntimeTests\Tests\Windows_UI_Xaml_Controls\GroupingObservableCollection.cs" Link="Windows_UI_XAML_Controls\ListViewBaseTests\GroupingObservableCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AddIns\Uno.UI.Lottie\Uno.UI.Lottie.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -66,5 +67,45 @@ public void When_Item_Removed_VectorChanged()
vector.RemoveAt(2);
vector.Remove("one");
}

[TestMethod]
public void When_IListOverride_IndexOf()
{
var vector = new MyObservableVector();
vector.Add(21);
vector.Add(42);

var vectorAsIList = (IList)vector;

Assert.AreEqual(1, vectorAsIList.IndexOf(42));

Assert.AreEqual(2, vector.IndexGetCount);
}

private class MyObservableVector : ObservableVector<int>
{
public int IndexGetCount;
public int IndexSetCount;

public MyObservableVector()
{

}

public override int this[int index]
{
get
{
IndexGetCount++;
return base[index];
}

set
{
IndexSetCount++;
base[index] = value;
}
}
}
}
}
Loading

0 comments on commit 4ed5bc4

Please sign in to comment.