Created
June 2, 2020 11:57
-
-
Save jsuarezruiz/bbed609b8382a72c6304c4244f48b964 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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