Skip to content

Instantly share code, notes, and snippets.

@jsuarezruiz
Created June 2, 2020 11:57
Show Gist options
  • Save jsuarezruiz/bbed609b8382a72c6304c4244f48b964 to your computer and use it in GitHub Desktop.
Save jsuarezruiz/bbed609b8382a72c6304c4244f48b964 to your computer and use it in GitHub Desktop.
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using System.Linq;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.Specialized;
using System;
#if UITEST
using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif
namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[NUnit.Framework.Category(UITestCategories.Navigation)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 10826, "[Bug] CollectionView Items are reversed on AddRange", PlatformAffected.UWP)]
public class Issue10826 : TestContentPage
{
protected override void Init()
{
Title = "Issue 10826";
BindingContext = new Issue10826ViewModel();
var layout = new Grid
{
Padding = 12
};
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
layout.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
var buttonsLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.Center
};
var clearButton = new Button
{
Text = "Clear Items"
};
clearButton.SetBinding(Button.CommandProperty, "ClearItemsCommand");
var addButton = new Button
{
Text = "Add Items"
};
addButton.SetBinding(Button.CommandProperty, "AddItemsCommand");
var addRangeButton = new Button
{
Text = "AddRange Items"
};
addRangeButton.SetBinding(Button.CommandProperty, "AddRangeItemsCommand");
buttonsLayout.Children.Add(clearButton);
buttonsLayout.Children.Add(addButton);
buttonsLayout.Children.Add(addRangeButton);
var collectionView = new CollectionView();
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Items");
layout.Children.Add(buttonsLayout);
Grid.SetRow(buttonsLayout, 0);
layout.Children.Add(collectionView);
Grid.SetRow(collectionView, 1);
Content = layout;
}
}
[Preserve(AllMembers = true)]
public class Issue10826ViewModel : BindableObject
{
public ObservableRangeCollection<string> Items { get; } = new ObservableRangeCollection<string>();
public Command ClearItemsCommand { get; }
public Command AddItemsCommand { get; }
public Command AddRangeItemsCommand { get; }
public Command<string> RemoveItemCommand { get; }
public Issue10826ViewModel()
{
AddItemsCommand = new Command(AddItems);
AddRangeItemsCommand = new Command(AddRangeItems);
RemoveItemCommand = new Command<string>(s => Items.Remove(s));
ClearItemsCommand = new Command(() => Items.Clear());
}
private void AddItems()
{
var items = Enumerable.Range(Items.Count, 10).Select(i => $"Item {i}").ToArray();
foreach (var i in items)
{
Items.Add(i);
}
}
private void AddRangeItems()
{
var items = Enumerable.Range(Items.Count, 10).Select(i => $"Item {i}").ToArray();
Items.AddRange(items);
}
}
[Preserve(AllMembers = true)]
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
public ObservableRangeCollection()
: base()
{
}
public ObservableRangeCollection(IEnumerable<T> collection)
: base(collection)
{
}
public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
{
if (notificationMode != NotifyCollectionChangedAction.Add && notificationMode != NotifyCollectionChangedAction.Reset)
throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode));
if (collection == null)
throw new ArgumentNullException(nameof(collection));
CheckReentrancy();
var startIndex = Count;
var itemsAdded = AddArrangeCore(collection);
if (!itemsAdded)
return;
if (notificationMode == NotifyCollectionChangedAction.Reset)
{
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
return;
}
var changedItems = collection is List<T> ? (List<T>)collection : new List<T>(collection);
RaiseChangeNotificationEvents(
action: NotifyCollectionChangedAction.Add,
changedItems: changedItems,
startingIndex: startIndex);
}
public void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset)
{
if (notificationMode != NotifyCollectionChangedAction.Remove && notificationMode != NotifyCollectionChangedAction.Reset)
throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode));
if (collection == null)
throw new ArgumentNullException(nameof(collection));
CheckReentrancy();
if (notificationMode == NotifyCollectionChangedAction.Reset)
{
var raiseEvents = false;
foreach (var item in collection)
{
Items.Remove(item);
raiseEvents = true;
}
if (raiseEvents)
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
return;
}
var changedItems = new List<T>(collection);
for (var i = 0; i < changedItems.Count; i++)
{
if (!Items.Remove(changedItems[i]))
{
changedItems.RemoveAt(i); //Can't use a foreach because changedItems is intended to be (carefully) modified
i--;
}
}
if (changedItems.Count == 0)
return;
RaiseChangeNotificationEvents(
action: NotifyCollectionChangedAction.Remove,
changedItems: changedItems);
}
public void Replace(T item) => ReplaceRange(new T[] { item });
public void ReplaceRange(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
CheckReentrancy();
var previouslyEmpty = Items.Count == 0;
Items.Clear();
AddArrangeCore(collection);
var currentlyEmpty = Items.Count == 0;
if (previouslyEmpty && currentlyEmpty)
return;
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
}
private bool AddArrangeCore(IEnumerable<T> collection)
{
var itemAdded = false;
foreach (var item in collection)
{
Items.Add(item);
itemAdded = true;
}
return itemAdded;
}
private void RaiseChangeNotificationEvents(NotifyCollectionChangedAction action, List<T> changedItems = null, int startingIndex = -1)
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
if (changedItems is null)
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action));
else
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment