From 2b4d4aa1d44e5256d0f00e74308d085d33b9521a Mon Sep 17 00:00:00 2001 From: Martin Zikmund Date: Tue, 17 Nov 2020 15:34:50 +0100 Subject: [PATCH] feat: ItemsSource-bound ItemCollection --- src/Uno.UI/UI/Xaml/Controls/ItemCollection.cs | 91 ++++++++++++++++--- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/ItemCollection.cs b/src/Uno.UI/UI/Xaml/Controls/ItemCollection.cs index e31c410b6879..9b87503f5090 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ItemCollection.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ItemCollection.cs @@ -7,6 +7,8 @@ using Uno.Extensions; using Uno.Extensions.Specialized; using System.Linq; +using Uno.Disposables; +using System.Collections.Specialized; namespace Windows.UI.Xaml.Controls { @@ -14,7 +16,8 @@ public sealed partial class ItemCollection : IList, IEnumerable, { private readonly IList _inner = new List(); - private IEnumerable _itemsSource = null; + private IList _itemsSource = null; + private readonly SerialDisposable _itemsSourceCollectionChangeDisposable = new SerialDisposable(); public event VectorChangedEventHandler VectorChanged; @@ -58,7 +61,7 @@ public void Clear() public bool Contains(object item) { - if (_itemsSource != null) + if (_itemsSource == null) { return _inner.Contains(item); } @@ -70,13 +73,18 @@ public bool Contains(object item) public void CopyTo(object[] array, int arrayIndex) { - if (_itemsSource != null) + if (_itemsSource == null) { _inner.CopyTo(array, arrayIndex); } else { - throw new NotImplementedException(); + int targetIndex = arrayIndex; + foreach (var item in _itemsSource) + { + array[targetIndex] = item; + targetIndex++; + } } } @@ -138,22 +146,83 @@ public object this[int index] internal void SetItemsSource(object itemsSource) { - var unwrappedSource = UnwrapItemsSource(itemsSource); + if (_itemsSource == itemsSource) + { + // Items source did not actually change. + return; + } - if (unwrappedSource is IList itemsSourceList) + if (itemsSource == null) { - _itemsSource = itemsSourceList; + _itemsSource = null; } - else if (unwrappedSource is IEnumerable itemsSourceEnumerable) + else { - _itemsSource = itemsSourceEnumerable.ToObjectArray(); + var unwrappedSource = UnwrapItemsSource(itemsSource); + + if (unwrappedSource is IList itemsSourceList) + { + _itemsSource = itemsSourceList; + } + else if (unwrappedSource is IEnumerable itemsSourceEnumerable) + { + _itemsSource = itemsSourceEnumerable.ToObjectArray(); + } + else + { + throw new InvalidOperationException("Only IList- or IEnumerable-based ItemsSource is supported."); + } + + ObserveCollectionChanged(); + } + + VectorChanged?.Invoke(this, new VectorChangedEventArgs(CollectionChange.Reset, 0)); + } + + private void ObserveCollectionChanged() + { + if (_itemsSource is INotifyCollectionChanged existingObservable) + { + // This is a workaround for a bug with EventRegistrationTokenTable on Xamarin, where subscribing/unsubscribing to a class method directly won't + // remove the handler. + NotifyCollectionChangedEventHandler handler = OnItemsSourceCollectionChanged; + _itemsSourceCollectionChangeDisposable.Disposable = Disposable.Create(() => + existingObservable.CollectionChanged -= handler + ); + existingObservable.CollectionChanged += handler; + } + else if (_itemsSource is IObservableVector observableVector) + { + // This is a workaround for a bug with EventRegistrationTokenTable on Xamarin, where subscribing/unsubscribing to a class method directly won't + // remove the handler. + VectorChangedEventHandler handler = OnItemsSourceVectorChanged; + _itemsSourceCollectionChangeDisposable.Disposable = Disposable.Create(() => + observableVector.VectorChanged -= handler + ); + observableVector.VectorChanged += handler; + } + else if (_itemsSource is IObservableVector genericObservableVector) + { + VectorChangedEventHandler handler = OnItemsSourceVectorChanged; + _itemsSourceCollectionChangeDisposable.Disposable = Disposable.Create(() => + genericObservableVector.UntypedVectorChanged -= handler + ); + genericObservableVector.UntypedVectorChanged += handler; } else { - throw new InvalidOperationException("Only IList- or IEnumerable-based ItemsSource is supported."); + _itemsSourceCollectionChangeDisposable.Disposable = null; } + } - //TODO: Observe items source changes to raise VectorChanged + private void OnItemsSourceVectorChanged(object sender, IVectorChangedEventArgs args) + { + VectorChanged?.Invoke(this, args); + } + + private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + VectorChanged?.Invoke(this, args.ToVectorChangedEventArgs()); } private void ThrowIfItemsSourceSet()